@mark1russell7/harness 0.11.15 → 0.11.17
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 +25 -1
- package/build/{cell-DOoU1LRf.d.ts → cell-BBPhdtav.d.ts} +62 -8
- package/build/{image-DIJ1AFg_.js → image-67OW9r9u.js} +13 -2
- package/build/image-67OW9r9u.js.map +1 -0
- package/build/index.d.ts +196 -5
- package/build/index.js +1018 -193
- package/build/index.js.map +1 -1
- package/build/schema.d.ts +1 -1
- package/build/schema.js +1 -1
- package/image/amqp/Dockerfile +11 -0
- package/image/amqp/admin-cluster.mjs +38 -0
- package/image/amqp/client-amqp.mjs +195 -0
- package/image/amqp/client-cluster.mjs +188 -0
- package/image/amqp/npm-lock.json +22 -0
- package/image/amqp/package.json +8 -0
- package/package.json +7 -1
- package/build/image-DIJ1AFg_.js.map +0 -1
package/README.md
CHANGED
|
@@ -103,6 +103,21 @@ A step-down holds its old primary out of the elections for 60 s. Thus before eac
|
|
|
103
103
|
|
|
104
104
|
A fault names a member by its role at the start of a run: `primary`, or `replica` for a secondary. A cut from one member (`one`, `one-way`) cuts the first other member in the sequence of the set.
|
|
105
105
|
|
|
106
|
+
## The clusters of RabbitMQ
|
|
107
|
+
|
|
108
|
+
A cluster (`Cluster`, `Cell.cluster`) is a cluster of RabbitMQ nodes with one quorum queue, as data. The members of the queue elect its leader by Raft, one member on each node. The cluster replaces the target of its cell.
|
|
109
|
+
|
|
110
|
+
- **The nodes.** Each node is a container from one stock image, pinned by digest, in its own network namespace. Its hostname is the name of its node (`rabbit@<name>`), and all nodes share one Erlang cookie. Its server is the child of a shell.
|
|
111
|
+
- **The start.** The nodes form the cluster from the list of their names (`classic_config`), and the lab waits until each node runs in it. Then the admin (`image/amqp/admin-cluster.mjs`) declares the queue on the first node, with the leader locator `client-local`.
|
|
112
|
+
- **The sidecars.** Each node has an injector sidecar in its network namespace and in its process namespace.
|
|
113
|
+
- **The client.** The client of each run is amqplib, in `image/amqp/client-cluster.mjs`. The client publishes a stream of sequence numbers on a confirm channel through one node. That node is the last follower, or the node of the leader (`Cell.clientAt`). The library does not connect again by itself. When the connection ends, the client connects to the next node after 1 s, as an application does.
|
|
114
|
+
- **The timeline.** A log consumer reads the console log of each node. A run keeps the lines of the queue, each warning and error, and the lines of the maintenance mode. Ra writes a NOTICE for each change from or to the leader, and an INFO for each new leader that a follower detects.
|
|
115
|
+
- **The clocks.** The log has the wall clock in µs. The run puts each line on the monotonic clock with the clock lines of the client.
|
|
116
|
+
|
|
117
|
+
Each run starts from a queue that converged (`rabbitmq-queues quorum_status`): one leader, and a follower on each other node in its term at its commit index. The run then purges the queue. After the run, the queue converges again, and the admin reads the sequence numbers that the queue holds.
|
|
118
|
+
|
|
119
|
+
A step-down (`step-down`) moves the leadership to the first follower (`rabbitmq-queues transfer_leadership`). A drain (`drain`) puts the node in maintenance mode (`rabbitmq-upgrade drain`), and the run revives the node after it. Each of them starts an Erlang VM for the tools first. Thus its stamp is a line of the log, and not the command. For a step-down it is the NOTICE of the old leader that gives the leadership away (`GIVES_AWAY`). A drain stops the member of the leader, thus its stamp is the first follower that sees its monitor of the leader go down (`MONITOR_DOWN`).
|
|
120
|
+
|
|
106
121
|
## The clients and the agents
|
|
107
122
|
|
|
108
123
|
- **A client image** (`ClientImage`, `Cell.client`) replaces the TCP client of the harness in each run. It operates in the namespace of the client. Its command gets the arguments of the TCP client, and it prints the same JSON lines. Its image is a stock image by its name, or the source of the image of a subject (`ImageSource`).
|
|
@@ -168,6 +183,10 @@ The harness ships ten images, each as a shipped source (`ImageSource`). The tag
|
|
|
168
183
|
- The user is `postgres`, unless the cell gives `username` (and `password`). Supavisor finds the tenant of a client in its user, for example `postgres.harness`.
|
|
169
184
|
- It prints the lines of the pg client on the same clock. Each socket comes from the `PGStream` of its connection, and a `pool` line gives each change of the counts of the pool. The errno of a failure is the SQLState of pgjdbc.
|
|
170
185
|
- Before its first line, it loads the classes of a connect and of a pool on the loopback of its namespace. It also starts the SecureRandom that HikariCP reads for the UUID of each new connection. Then it parses an ERROR of the server, as for the timeout of a statement. A new JVM loads each class at its first use. Thus a band does not time that load.
|
|
186
|
+
- **The RabbitMQ client image** (`image/amqp`) is amqplib 2.2.0 on Node 24.21.0. The digest of the image and an npm lockfile pin them. The probe is `{ driver: { name: "amqp" } }`.
|
|
187
|
+
- `client-amqp.mjs` opens one connection and one channel. Each operation is an RPC of the channel: the first declares a queue that the broker names, and each later one declares it passively (checkQueue). The queue is exclusive, because RabbitMQ 4.3 refuses a transient queue that is not.
|
|
188
|
+
- The options of the cell (`Cell.driverOptions`) give `heartbeat` in seconds, the user and its password (guest at the defaults), and the socket options of amqplib.
|
|
189
|
+
- It prints the lines of the pg client on the same clock. A `connection` line gives each event of the connection, and a `channel` line each event of the channel. The errno of a failure is the error of the connection when it has one.
|
|
171
190
|
- **The MongoDB client image** (`image/mongo`) is the driver mongodb 7.7.0 on Node 24.21.0. The digest of the image and an npm lockfile pin them. `client-mongo.mjs` prints the lines of the TCP client on the same clock:
|
|
172
191
|
- Each socket of the driver is a `socket` line with its role: `monitor`, `rtt` or `pool`. The stack of the call that makes the socket gives the role.
|
|
173
192
|
- 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).
|
|
@@ -247,6 +266,7 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
247
266
|
- **Recovery.** After a stall (`prediction.recovers`), it times the send to the first segment back after the stall stopped. That segment is the answer of the server to the retransmission that got through. A run whose stall stopped outside the window of its prediction is void. A run where nothing came back is a finding.
|
|
248
267
|
- **An end of file.** A FIN counts from the change of state of the socket of the peer when it closes (`peer-closed`). It ends at the change of the socket of the observer to CLOSE_WAIT. The kernel stamps the two changes.
|
|
249
268
|
- **A check of a pool.** A check on an idle connection counts from the first segment with data that the socket sends after the fault (`first-sent`). The keepalive of HikariCP comes at a phase from the creation of its connection. Thus no line of the client gives its time, and the kernel stamps it. The prediction gives the latest time of that send after the reply (`startsBy`), and the run waits that much longer.
|
|
269
|
+
- **A heartbeat of a driver.** The receive check of a heartbeat counts the bytes of the peer, not an ACK. Thus its band counts from the last segment with data that the socket received before the end of the band (`last-data`). An idle client of amqplib sends its own heartbeat frames. The ACK of the broker for one of them can come after the last data of the broker.
|
|
250
270
|
- **The connect of a pooler.** A band of a driver can be on the socket that a hop opens toward the target (`socket: "upstream"`). It is the first socket of a hop that moves to SYN_SENT after the outset. A pooler connects to the server by itself, thus no line of the client names that socket.
|
|
251
271
|
- A connect timer counts from the change of that socket to SYN_SENT (`syn-sent`).
|
|
252
272
|
- The band ends at the error that the kernel reports, for example ETIMEDOUT after the SYNs. Without an error, it ends when the program closes its socket in SYN_SENT (`closed`). That close has no error and no FIN, only the change to CLOSE. A refused connect also leaves SYN_SENT, but it has its error.
|
|
@@ -288,6 +308,7 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
288
308
|
- **Void runs.** A run is void when a Sentinel entered its TILT mode, or when the election split its votes. Both measured another path than the band assumes. A run whose Sentinels reached `+odown` and started no failover is void: the hold-off of an earlier failover stopped them. A band whose hold lifted more than δ_abs late is void too. The rules of the RTT and of a slow commit apply.
|
|
289
309
|
- **The stamps.** A calibration gives the delay of each stamp of the observer after the write of its Sentinel that it read (`lags`).
|
|
290
310
|
- **A replica set** (`measureGroup`, `replicaSetStagesOf`). The outcome is the first accepted write of a write that the client sent after the commit of the fault. Thus a partial cut, which leaves the old primary its writes, gives the first write after the fault.
|
|
311
|
+
- **A cluster of RabbitMQ** (`measureGroup`, `clusterStagesOf`). The outcome is the first confirm of a publish that the client sent after the commit of the fault, through any node. The stages are the NOTICE and the INFO lines of Ra (`NODE_MARKS`).
|
|
291
312
|
- The time of the acceptance is the last segment with data on a socket of the pool. The monitor and the pinger have their own sockets to each member.
|
|
292
313
|
- **The timeline.** A log line of a member marks each stage. The first stages are a member that marks the old primary DOWN, the start of an election and its win. The last stages are the end of the catch-up, the first write of the new primary, and the step-down of the old primary.
|
|
293
314
|
- The lines of the client mark the discovery of the new primary, the write-off of the old one, and the first accepted write.
|
|
@@ -337,6 +358,7 @@ The distribution test needs a simulator, which does not exist at this time.
|
|
|
337
358
|
- The harness sets the `targetSettings` of a cell in the target before a client connects, with the `settings` commands of the target (`redis-cli CONFIG SET`, then `CONFIG GET`).
|
|
338
359
|
- A group cell opens a group environment (`openGroupLab`), and each run is `groupOnce`. Its warm-up is a whole run with its fault, because no fault of a group ends its environment. The manifest also holds the settings of each member and of each Sentinel, as each part read them.
|
|
339
360
|
- A replica set cell opens a replica set environment (`openReplicaSetLab`), and each run is `replicaSetOnce`. A run with a hold continues to the end of the hold, thus its record shows what the set did in all of it.
|
|
361
|
+
- A cluster cell opens a cluster environment (`openClusterLab`), and each run is `clusterOnce`.
|
|
340
362
|
- A driver cell (`prediction.driver`) waits for the end of its band and of each later band. A silent driver cell waits for its window.
|
|
341
363
|
- **The observer.** A cell observes the client, the server, or a socket of a proxy toward one side (`observer`). A cell on the server side measures the socket of the target for the connection of the client. The run waits for the error of that socket. `freeze-process` freezes the peer of the observer: the target for the client, the client for the server.
|
|
342
364
|
- A socket of a proxy toward the target is the socket that carries the request of the run. It is the first socket of the hop that sends data to the target after the request. A pooler can reuse a server connection from the warm-up of the environment. Then the first socket that it opens in the run is not the socket of the request. A run without such a socket uses the first socket that the run opens.
|
|
@@ -366,6 +388,8 @@ The tests also measure a group run from test lines. They find the first accepted
|
|
|
366
388
|
|
|
367
389
|
The tests of a replica set read the log of mongod, and they put its lines on the monotonic clock. They test the views that make a set healthy and the peers of each cut. They measure the stages of a set and the bands of a driver from test lines.
|
|
368
390
|
|
|
391
|
+
The tests of a cluster read the console log of RabbitMQ. They test the views that make a queue healthy, and the peers of each cut. They measure the stages of a cluster from test lines.
|
|
392
|
+
|
|
369
393
|
The tests of `image.ts` plan the build of each image that the harness ships, and of a build context of a caller. They test the rules of a build, the problems of a source and of a subject, and the schemas.
|
|
370
394
|
|
|
371
395
|
The tests of `application.ts` measure a heartbeat of a program from test lines and records. They find the first ping without an answer, the drop, the waits of the reconnect and the call in flight. They also test a report of the kernel before the drop, a VM that stopped, the heartbeat of a server, and an idle stall.
|
|
@@ -374,6 +398,6 @@ The cells themselves need Docker and privileged containers. CI does not start th
|
|
|
374
398
|
|
|
375
399
|
In the lab tests, each HTTP client sends two requests on one connection to a Node server, from a script in the stock Node image. The procedures WebSocket client drops a silent connection on its heartbeat, and it connects again to the procedures server. With the global `WebSocket`, the drop writes a close frame. With the `WebSocket` of ws, the socket leaves ESTABLISHED at once. The subject of that test is a build context that the test gives, as another repository gives its own subject.
|
|
376
400
|
|
|
377
|
-
The lab tests also step a replica set of three members of MongoDB 9.0.4 down, and they write the free memory of the machine. They also reset a write of the MongoDB driver in flight to a set of one member.
|
|
401
|
+
The lab tests also step a replica set of three members of MongoDB 9.0.4 down, and they write the free memory of the machine. They also reset a write of the MongoDB driver in flight to a set of one member. They move the leadership of a quorum queue of a cluster of three nodes of RabbitMQ 4.3.6.
|
|
378
402
|
|
|
379
403
|
`pnpm --filter @mark1russell7/harness build` writes the package for npm into `build/`.
|
|
@@ -53,8 +53,14 @@ import { KernelRecord } from "@mark1russell7/prober";
|
|
|
53
53
|
*
|
|
54
54
|
* This fault is of a replica set (`Cell.replicaSet`):
|
|
55
55
|
* - `step-down` sends `replSetStepDown` to the primary, as a client sends a command.
|
|
56
|
+
*
|
|
57
|
+
* These faults are of a cluster of RabbitMQ (`Cell.cluster`):
|
|
58
|
+
* - `step-down` moves the leadership of the queue to a follower: `rabbitmq-queues transfer_leadership`.
|
|
59
|
+
* - `drain` puts the node of the part in maintenance mode: `rabbitmq-upgrade drain`. The node closes the
|
|
60
|
+
* connections of its clients, and it moves the leadership of each of its queues to another node.
|
|
61
|
+
* `rabbitmq-upgrade revive` ends it after the run.
|
|
56
62
|
*/
|
|
57
|
-
type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "partition" | "planned-failover" | "app-stall" | "step-down" | "hop-event" | "none";
|
|
63
|
+
type Fault = "blackhole" | "freeze-process" | "rst" | "stall" | "fin" | "unreachable" | "simplex" | "mtu-blackhole" | "netem" | "middlebox-forget" | "proxy-idle" | "toxic" | "server-close" | "partition" | "planned-failover" | "app-stall" | "step-down" | "drain" | "hop-event" | "none";
|
|
58
64
|
/**
|
|
59
65
|
* A middlebox form tells what a router does with a flow that it forgot, when a late packet comes:
|
|
60
66
|
* - `reset-both` sends a reset to the two ends when it forgets the flow.
|
|
@@ -220,6 +226,44 @@ interface ReplicaSet {
|
|
|
220
226
|
/** This line tells that a member serves. */
|
|
221
227
|
readonly ready: string;
|
|
222
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* A cluster is a cluster of RabbitMQ nodes with one quorum queue, as data. The members of the queue
|
|
231
|
+
* elect its leader by Raft, one member on each node. Each node is a container from the same stock
|
|
232
|
+
* image, pinned by digest, in its own network namespace. The nodes form the cluster from a fixed
|
|
233
|
+
* list of names. The client publishes a stream of sequence numbers to the queue, each with a
|
|
234
|
+
* confirm, through one node. When its connection ends, it connects to the next node.
|
|
235
|
+
*/
|
|
236
|
+
interface Cluster {
|
|
237
|
+
/** This image is the stock image of each node, pinned by digest. */
|
|
238
|
+
readonly image: string;
|
|
239
|
+
/** These are the names of the nodes: each node is `rabbit@<name>`. The first node declares the queue, thus its member is the first leader. */
|
|
240
|
+
readonly members: readonly string[];
|
|
241
|
+
readonly port: number;
|
|
242
|
+
/** This text is the Erlang cookie that each node shares. */
|
|
243
|
+
readonly cookie: string;
|
|
244
|
+
/** These are lines of rabbitmq.conf after the formation of the cluster, for example `raft.adaptive_failure_detector.poll_interval = 1000`. */
|
|
245
|
+
readonly conf: readonly string[];
|
|
246
|
+
/** This name is the name of the quorum queue. */
|
|
247
|
+
readonly queue: string;
|
|
248
|
+
/** This line tells that a node serves. */
|
|
249
|
+
readonly ready: string;
|
|
250
|
+
/** These are the programs of the image: the server, whose process a signal acts on, and the command that starts the node again. */
|
|
251
|
+
readonly programs: {
|
|
252
|
+
readonly server: string;
|
|
253
|
+
readonly start: string;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* A cluster stage is one step of the timeline of a cluster after a fault. A line of the log of a
|
|
258
|
+
* node marks it, or a line of the client. These are the stages:
|
|
259
|
+
* - `monitor-down`: a follower sees the process of its leader go down.
|
|
260
|
+
* - `elected`: the new leader, and `followed`: a follower that sees it.
|
|
261
|
+
* - `old-leader-steps-down`: the old leader becomes a follower.
|
|
262
|
+
* - `first-write`: the line of the client of the first publish that the queue confirms.
|
|
263
|
+
*/
|
|
264
|
+
type ClusterStage = "monitor-down" | "elected" | "followed" | "old-leader-steps-down" | "first-write";
|
|
265
|
+
/** This type is the node of the client in a cluster at the start of a run. It is the node of the leader of the queue, or of a follower. */
|
|
266
|
+
type ClusterClientAt = "leader" | "follower";
|
|
223
267
|
/** A group role is a part of a group by its role at the start of a run: the primary, a replica, or a Sentinel. */
|
|
224
268
|
type GroupRole = "primary" | "replica" | "sentinel";
|
|
225
269
|
/**
|
|
@@ -264,7 +308,7 @@ interface Toxic {
|
|
|
264
308
|
* A connect timer counts from the change of its socket to SYN_SENT (`syn-sent`). A pooler connects
|
|
265
309
|
* to the server by itself, thus the kernel stamps that connect, and no line of the client does.
|
|
266
310
|
*/
|
|
267
|
-
type Reference = "last-received" | "first-retransmission" | "request-sent" | "peer-closed" | "fault" | "client-start" | "ping-sent" | "first-sent" | "syn-sent";
|
|
311
|
+
type Reference = "last-received" | "last-data" | "first-retransmission" | "request-sent" | "peer-closed" | "fault" | "client-start" | "ping-sent" | "first-sent" | "syn-sent";
|
|
268
312
|
/**
|
|
269
313
|
* This prediction tells when a connection moves again after a stall. That occurs at the first
|
|
270
314
|
* firing of its retransmission timer after the stall stops, and its reply comes back one round
|
|
@@ -332,8 +376,8 @@ type SentinelStage = "+sdown" | "+odown" | "+try-failover" | "+elected-leader" |
|
|
|
332
376
|
* of the new primary, the write-off of the old one, and the first write that a primary accepts.
|
|
333
377
|
*/
|
|
334
378
|
type ReplicaSetStage = "member-down" | "election-start" | "elected" | "caught-up" | "writable" | "old-primary-steps-down" | "discovered" | "written-off" | "first-write";
|
|
335
|
-
/** A stage is one step of the timeline of a group
|
|
336
|
-
type Stage = SentinelStage | ReplicaSetStage;
|
|
379
|
+
/** A stage is one step of the timeline of a group under Sentinels, of a replica set, or of a cluster of RabbitMQ. */
|
|
380
|
+
type Stage = SentinelStage | ReplicaSetStage | ClusterStage;
|
|
337
381
|
/** A stage band is when the first part announces a stage, from the reference of the cell. */
|
|
338
382
|
interface StageBand {
|
|
339
383
|
readonly stage: Stage;
|
|
@@ -614,7 +658,7 @@ type HarnessImage = "procedures";
|
|
|
614
658
|
* A shipped image is an image that the harness ships, by its name. Its build context is in the
|
|
615
659
|
* package of the harness, and in the package of the prober for `harness`.
|
|
616
660
|
*/
|
|
617
|
-
type Shipped = "harness" | "node" | "go-http" | "node-http" | "mongo" | "pg" | "hikari" | "procedures" | "router" | "pgbouncer" | "pgpool";
|
|
661
|
+
type Shipped = "harness" | "node" | "go-http" | "node-http" | "mongo" | "pg" | "hikari" | "amqp" | "procedures" | "router" | "pgbouncer" | "pgpool";
|
|
618
662
|
/**
|
|
619
663
|
* An image source tells where an image comes from, as data. The harness resolves each kind to an
|
|
620
664
|
* image that it can start:
|
|
@@ -673,7 +717,7 @@ interface Subject {
|
|
|
673
717
|
* (`http-agent`) and an HTTP/1.1 `Client` of undici (`undici`). The MongoDB driver is `mongodb`, node-postgres is `pg`, HikariCP over pgjdbc is `hikari`, and
|
|
674
718
|
* the WebSocket client of `@procedures/client` is `procedures-ws`.
|
|
675
719
|
*/
|
|
676
|
-
type Client = "tcp" | "ioredis" | "go-http" | "http-agent" | "undici" | "mongodb" | "pg" | "hikari" | "procedures-ws";
|
|
720
|
+
type Client = "tcp" | "ioredis" | "go-http" | "http-agent" | "undici" | "mongodb" | "pg" | "hikari" | "amqp" | "procedures-ws";
|
|
677
721
|
/**
|
|
678
722
|
* A WebSocket probe is the workload and the settings of the WebSocket client of
|
|
679
723
|
* `@procedures/client` (`procedures-ws`). The cell gives each setting, thus the client takes
|
|
@@ -813,6 +857,8 @@ interface Cell {
|
|
|
813
857
|
readonly name: "hikari";
|
|
814
858
|
readonly query?: string;
|
|
815
859
|
readonly inflight?: string;
|
|
860
|
+
} | {
|
|
861
|
+
readonly name: "amqp";
|
|
816
862
|
};
|
|
817
863
|
/** This workload is the workload of an HTTP client (`Cell.client` is an HTTP client). The client then ignores `keepalive` and `userTimeout`. */
|
|
818
864
|
readonly http?: HttpProbe;
|
|
@@ -880,6 +926,14 @@ interface Cell {
|
|
|
880
926
|
* `target` stays for the record, and the set does not read it.
|
|
881
927
|
*/
|
|
882
928
|
readonly replicaSet?: ReplicaSet;
|
|
929
|
+
/**
|
|
930
|
+
* This cluster replaces the target: a cluster of RabbitMQ with one quorum queue. The client is then
|
|
931
|
+
* amqplib, and it publishes a stream of sequence numbers with confirms. `target` stays for the
|
|
932
|
+
* record, and the cluster does not read it.
|
|
933
|
+
*/
|
|
934
|
+
readonly cluster?: Cluster;
|
|
935
|
+
/** This place is the node of the client in a cluster: the node of the leader, or of a follower. Without it, the node of a follower. */
|
|
936
|
+
readonly clientAt?: ClusterClientAt;
|
|
883
937
|
/**
|
|
884
938
|
* This time is how long a fault on a group holds after its commit. The injector then lifts it at
|
|
885
939
|
* that instant, on the clock of the VM. On a direct pair, a frozen target thaws after it.
|
|
@@ -1051,5 +1105,5 @@ interface RunTopology {
|
|
|
1051
1105
|
}>>;
|
|
1052
1106
|
}
|
|
1053
1107
|
//#endregion
|
|
1054
|
-
export {
|
|
1055
|
-
//# sourceMappingURL=cell-
|
|
1108
|
+
export { Target as $, ProbeEvent as A, RouterHop as B, LineMatch as C, Observer as D, MiddleboxForm as E, Reference as F, RunTopology as G, Run as H, ReplicaSet as I, Shipped as J, SentinelEvent as K, ReplicaSetStage as L, ProxyTime as M, Reconnects as N, PinnedImage as O, Recovers as P, Subject as Q, Replies as R, KernelRecord as S, MiddleboxExpectation as T, RunGroup as U, RttRange as V, RunPart as W, Stage as X, ShippedImage as Y, StageBand as Z, Hop as _, Cluster as a, HttpProbe as b, DriverBand as c, FaultPlace as d, Topology as et, Group as f, HarnessImage as g, GroupRole as h, ClientImage as i, latestEndOf as it, ProxyHop as j, Prediction as k, DriverStep as l, GroupPrediction as m, Cell as n, WsProbe as nt, ClusterClientAt as o, GroupPlace as p, SentinelStage as q, Client as r, isHopPlace as rt, ClusterStage as s, BuildImage as t, Toxic as tt, Fault as u, HopEvent as v, MiddleboxEvent as w, ImageSource as x, HopPlace as y, RequestPrediction as z };
|
|
1109
|
+
//# sourceMappingURL=cell-BBPhdtav.d.ts.map
|
|
@@ -72,6 +72,11 @@ const SHIPPED = {
|
|
|
72
72
|
dockerfile: "harness/image/hikari/Dockerfile",
|
|
73
73
|
from: ["harness/image/hikari"]
|
|
74
74
|
},
|
|
75
|
+
amqp: {
|
|
76
|
+
name: "loom-harness-amqp",
|
|
77
|
+
dockerfile: "harness/image/amqp/Dockerfile",
|
|
78
|
+
from: ["harness/image/amqp"]
|
|
79
|
+
},
|
|
75
80
|
procedures: {
|
|
76
81
|
name: "loom-harness-procedures",
|
|
77
82
|
dockerfile: "harness/image/procedures/Dockerfile",
|
|
@@ -160,6 +165,10 @@ const SHIPPED_CLIENTS = {
|
|
|
160
165
|
"ClientHikari"
|
|
161
166
|
]
|
|
162
167
|
},
|
|
168
|
+
amqp: {
|
|
169
|
+
image: shipped("amqp"),
|
|
170
|
+
command: ["node", "/opt/harness/amqp/client-amqp.mjs"]
|
|
171
|
+
},
|
|
163
172
|
"procedures-ws": {
|
|
164
173
|
image: SUBJECTS.procedures.image,
|
|
165
174
|
command: SUBJECTS.procedures.programs["client"]
|
|
@@ -422,6 +431,8 @@ const mongoClientImage = () => imageOf(shipped("mongo"));
|
|
|
422
431
|
const pgClientImage = () => imageOf(shipped("pg"));
|
|
423
432
|
/** This function gives the image of the client of HikariCP over pgjdbc. */
|
|
424
433
|
const hikariClientImage = () => imageOf(shipped("hikari"));
|
|
434
|
+
/** This function gives the image of the RabbitMQ client: amqplib, with the client and the admin of a cluster cell. */
|
|
435
|
+
const amqpClientImage = () => imageOf(shipped("amqp"));
|
|
425
436
|
/** This function gives the image of the procedures subject: the WebSocket client of `@procedures/client`, and its server. */
|
|
426
437
|
const proceduresImage = () => imageOf(SUBJECTS.procedures.image);
|
|
427
438
|
/**
|
|
@@ -432,6 +443,6 @@ const targetSourceOf = (target) => target.subject ?? (target.harness === void 0
|
|
|
432
443
|
/** This function gives the image that a target operates: the image of its subject, or its stock image. */
|
|
433
444
|
const targetImageOf = (target) => imageOf(targetSourceOf(target));
|
|
434
445
|
//#endregion
|
|
435
|
-
export {
|
|
446
|
+
export { subjectProblems as C, unpinnedBases as D, unlockedManifests as E, subjectClient as S, targetSourceOf as T, pgClientImage as _, SHIPPED_NAMES as a, programOf as b, goHttpImage as c, imageOf as d, imageProblems as f, nodeHttpImage as g, nodeClientImage as h, SHIPPED_CLIENTS as i, harnessImage as l, mongoClientImage as m, PINNED as n, SUBJECTS as o, inContext as p, REPOSITORY as r, amqpClientImage as s, CLIENT_NAMES as t, hikariClientImage as u, planOf as v, targetImageOf as w, routerImage as x, proceduresImage as y };
|
|
436
447
|
|
|
437
|
-
//# sourceMappingURL=image-
|
|
448
|
+
//# sourceMappingURL=image-67OW9r9u.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-67OW9r9u.js","names":[],"sources":["../src/image.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, posix, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport type { BuildImage, Client, ClientImage, ImageSource, Shipped, ShippedImage, Subject, Target } from \"./cell.ts\";\n\n/**\n * This module resolves the source of each image (`ImageSource`) to an image that a lab can start.\n * A pinned image is its own name. A build context, or an image that the harness ships, becomes a\n * build with a tag from its content. Each image of the harness is a shipped source in one table.\n * Thus the procedures image of a subject is data, as the image of a caller is.\n */\n\n/**\n * These are the roots of the two packages that the shipped images read: this package, and the\n * prober that Node resolves for it. In a workspace, they are two folders side by side. From npm,\n * the prober is a link, and a Docker build context does not follow a link. Thus each image builds\n * from a context that the harness copies from the two roots.\n */\nconst ROOTS: Readonly<Record<string, string>> = {\n harness: fileURLToPath(new URL(\"..\", import.meta.url)),\n prober: dirname(realpathSync(createRequire(import.meta.url).resolve(\"@mark1russell7/prober/package.json\"))),\n};\n\n/** This function gives the path of a part of a shipped image, which is `<package>/<path>` in the context. */\nconst pathOf = (part: string): string => {\n const [root, ...rest] = part.split(\"/\");\n const base = ROOTS[root!];\n if (base === undefined) throw new Error(`${part} is in no package that the images read`);\n return join(base, ...rest);\n};\n\n/** A shipped context is the build context of a shipped image: its Dockerfile, and each file or folder that the Dockerfile reads, as `<package>/<path>`. */\ninterface ShippedContext {\n readonly name: string;\n readonly dockerfile: string;\n readonly from: readonly string[];\n}\n\n/** These are the build contexts of the images that the harness ships, by their names. */\nconst SHIPPED: Readonly<Record<Shipped, ShippedContext>> = {\n // the prober, the TCP client, the injector and the namespace admin\n harness: {\n name: \"loom-harness\",\n dockerfile: \"harness/image/Dockerfile\",\n from: [\"harness/image/Dockerfile\", \"harness/image/native\", \"prober/bpf\", \"prober/native\", \"prober/fixtures\"],\n },\n // the Node client: ioredis 6.0.0 on Node 24.21.0\n node: { name: \"loom-harness-node\", dockerfile: \"harness/image/node/Dockerfile\", from: [\"harness/image/node\"] },\n // the Go HTTP client: the http.Transport of Go 1.27.2\n \"go-http\": { name: \"loom-harness-go-http\", dockerfile: \"harness/image/go-http/Dockerfile\", from: [\"harness/image/go-http\"] },\n // the Node HTTP clients: the http.Agent of Node 24.21.0, and undici 8.12.2\n \"node-http\": { name: \"loom-harness-node-http\", dockerfile: \"harness/image/node-http/Dockerfile\", from: [\"harness/image/node-http\"] },\n // the MongoDB client: the driver mongodb 7.7.0 on Node 24.21.0, and its step-down script\n mongo: { name: \"loom-harness-mongo\", dockerfile: \"harness/image/mongo/Dockerfile\", from: [\"harness/image/mongo\"] },\n // the PostgreSQL client: node-postgres (pg 8.23.1 with pg-pool 3.14.0) on Node 24.21.0\n pg: { name: \"loom-harness-pg\", dockerfile: \"harness/image/pg/Dockerfile\", from: [\"harness/image/pg\"] },\n // the client of HikariCP 7.1.0 over pgjdbc 42.7.13 on Temurin 21.0.12.1: its jars by their SHA-256\n hikari: { name: \"loom-harness-hikari\", dockerfile: \"harness/image/hikari/Dockerfile\", from: [\"harness/image/hikari\"] },\n // the RabbitMQ client: amqplib 2.2.0 on Node 24.21.0\n amqp: { name: \"loom-harness-amqp\", dockerfile: \"harness/image/amqp/Dockerfile\", from: [\"harness/image/amqp\"] },\n // @procedures/client 0.7.0 on Node 24.21.0: its WebSocket client, and the script of its server\n procedures: { name: \"loom-harness-procedures\", dockerfile: \"harness/image/procedures/Dockerfile\", from: [\"harness/image/procedures\"] },\n // the router of a middlebox chain: NAT and conntrack, with a form for a forgotten flow\n router: { name: \"loom-harness-router\", dockerfile: \"harness/image/router/Dockerfile\", from: [\"harness/image/router\"] },\n // PgBouncer 1.26.1, a pooler hop: built from its release tarball, by its SHA-256\n pgbouncer: { name: \"loom-harness-pgbouncer\", dockerfile: \"harness/image/pgbouncer/Dockerfile\", from: [\"harness/image/pgbouncer\"] },\n // Pgpool-II 4.7.3, a pooler hop: built from the source of its tag, by its SHA-256\n pgpool: { name: \"loom-harness-pgpool\", dockerfile: \"harness/image/pgpool/Dockerfile\", from: [\"harness/image/pgpool\"] },\n};\n\n/** These are the names of the images that the harness ships. */\nexport const SHIPPED_NAMES: readonly [Shipped, ...Shipped[]] = Object.keys(SHIPPED) as [Shipped, ...Shipped[]];\n\n/** This image is the stock base of the Node images of the harness: Node 24.21.0 on Alpine, pinned by digest. */\nconst NODE_BASE = \"node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1\";\n\n/**\n * These are the subjects that the harness ships, by their names. `procedures` is\n * `@procedures/client` on Node: its WebSocket client (`client-ws.mjs`) and its server\n * (`server-ws.mjs`), from one image. Thus the client and the server use one release.\n */\nexport const SUBJECTS: Readonly<Record<\"procedures\", Subject>> = {\n procedures: {\n name: \"procedures\",\n image: { kind: \"shipped\", name: \"procedures\" },\n base: NODE_BASE,\n programs: {\n client: [\"node\", \"/opt/harness/procedures/client-ws.mjs\"],\n server: [\"node\", \"/opt/harness/procedures/server-ws.mjs\"],\n },\n },\n};\n\n/** This function gives the source of a shipped image. */\nconst shipped = (name: Shipped): ImageSource => ({ kind: \"shipped\", name });\n\n/**\n * These are the clients that the harness ships, by their names (`Client`): the image of each, and\n * its command. A cell that names a client gets this image and this command.\n */\nexport const SHIPPED_CLIENTS: Readonly<Record<Client, ClientImage>> = {\n tcp: { image: shipped(\"harness\"), command: [\"/opt/harness/client\"] },\n ioredis: { image: shipped(\"node\"), command: [\"node\", \"/opt/harness/node/client-ioredis.mjs\"] },\n \"go-http\": { image: shipped(\"go-http\"), command: [\"/opt/harness/client-go\"] },\n \"http-agent\": { image: shipped(\"node-http\"), command: [\"node\", \"/opt/harness/node-http/client-agent.mjs\"] },\n undici: { image: shipped(\"node-http\"), command: [\"node\", \"/opt/harness/node-http/client-undici.mjs\"] },\n mongodb: { image: shipped(\"mongo\"), command: [\"node\", \"/opt/harness/mongo/client-mongo.mjs\"] },\n pg: { image: shipped(\"pg\"), command: [\"node\", \"/opt/harness/pg/client-pg.mjs\"] },\n hikari: {\n image: shipped(\"hikari\"),\n command: [\"java\", \"-Dslf4j.internal.verbosity=ERROR\", \"-cp\", \"/opt/harness/hikari/classes:/opt/harness/hikari/lib/*\", \"ClientHikari\"],\n },\n amqp: { image: shipped(\"amqp\"), command: [\"node\", \"/opt/harness/amqp/client-amqp.mjs\"] },\n \"procedures-ws\": { image: SUBJECTS.procedures.image, command: SUBJECTS.procedures.programs[\"client\"]! },\n};\n\n/** These are the names of the clients that the harness ships. */\nexport const CLIENT_NAMES: readonly [Client, ...Client[]] = Object.keys(SHIPPED_CLIENTS) as [Client, ...Client[]];\n\n/** This function gives the command of a program of a subject by its role. It fails when the subject has no such program. */\nexport const programOf = (subject: Subject, role: string): readonly string[] => {\n const program = Object.hasOwn(subject.programs, role) ? subject.programs[role] : undefined;\n if (program === undefined) throw new Error(`the subject ${subject.name} has no program ${role}`);\n return program;\n};\n\n/**\n * This function gives the client of a subject for a cell (`Cell.client`): the image of the subject,\n * with the command of a program. A client that the harness ships goes by its name, thus its records\n * stay the same.\n */\nexport const subjectClient = (subject: Subject, role = \"client\"): Client | ClientImage => {\n const client: ClientImage = { image: subject.image, command: programOf(subject, role) };\n return CLIENT_NAMES.find((name) => isDeepStrictEqual(SHIPPED_CLIENTS[name], client)) ?? client;\n};\n\n/** This pattern matches an image pinned by digest: a name, a tag or none, then `@sha256:` and 64 hex digits. */\nexport const PINNED: RegExp = /^[a-z0-9][A-Za-z0-9._\\-/:]*@sha256:[0-9a-f]{64}$/;\n/** This pattern matches the repository of a tag that the harness builds. Its parts are lower case, with `/` between them. */\nexport const REPOSITORY: RegExp = /^[a-z0-9]+(?:[._-]+[a-z0-9]+)*(?:\\/[a-z0-9]+(?:[._-]+[a-z0-9]+)*)*$/;\n\n/** This function tells if a path stays in its context: relative, with `/` between its parts, and no `..`. */\nexport const inContext = (path: string): boolean =>\n typeof path === \"string\" &&\n path.length > 0 &&\n !path.startsWith(\"/\") &&\n !path.includes(\"\\\\\") &&\n !/^[A-Za-z]:/.test(path) &&\n !path.split(\"/\").includes(\"..\");\n\n/** This function tells if a value is a command: an array of one text or more. */\nconst isCommand = (value: unknown): boolean =>\n Array.isArray(value) && value.length > 0 && value.every((word) => typeof word === \"string\" && word.length > 0);\n\n/**\n * This function gives the problems of the source of an image, as data. A pinned image names a\n * digest. A build names a repository, a context, and paths that stay in the context. A shipped image\n * is one that the harness ships.\n */\nexport const imageProblems = (source: ImageSource): string[] => {\n const kind = (source as { readonly kind?: unknown } | null)?.kind;\n if (kind === \"pinned\") {\n const { image } = source as { readonly image?: unknown };\n return typeof image === \"string\" && PINNED.test(image) ? [] : [`${String(image)} is not pinned by digest: name@sha256:<64 hex digits>`];\n }\n if (kind === \"shipped\") {\n const { name } = source as { readonly name?: unknown };\n return typeof name === \"string\" && Object.hasOwn(SHIPPED, name) ? [] : [`the harness ships no image ${String(name)}: ${SHIPPED_NAMES.join(\", \")}`];\n }\n if (kind === \"build\") {\n const build = source as Partial<BuildImage>;\n const problems: string[] = [];\n if (typeof build.name !== \"string\" || !REPOSITORY.test(build.name)) problems.push(`${String(build.name)} is not the name of a repository`);\n if (typeof build.context !== \"string\" || build.context.length === 0) problems.push(\"the build names no context\");\n if (build.files !== undefined && (!Array.isArray(build.files) || build.files.length === 0)) problems.push(\"the build names no files\");\n for (const path of [build.dockerfile ?? \"Dockerfile\", ...(Array.isArray(build.files) ? build.files : [])])\n if (!inContext(path)) problems.push(`${String(path)} is not a path in the context`);\n return problems;\n }\n return [`${JSON.stringify(kind)} is not a kind of image source: pinned, build or shipped`];\n};\n\n/**\n * This function gives the problems of a subject, as data. A subject has a name and a base that a\n * digest pins. Its image source has no problems, and each of its programs is a command.\n */\nexport const subjectProblems = (subject: Subject): string[] => {\n const programs = (subject as { readonly programs?: unknown }).programs;\n const roles = typeof programs === \"object\" && programs !== null ? Object.entries(programs) : [];\n return [\n ...(typeof subject.name === \"string\" && subject.name.length > 0 ? [] : [\"the subject has no name\"]),\n ...(typeof subject.base === \"string\" && PINNED.test(subject.base) ? [] : [`the base ${String(subject.base)} is not pinned by digest`]),\n ...imageProblems(subject.image),\n ...(roles.length === 0 ? [\"the subject has no programs\"] : []),\n ...roles.flatMap(([role, command]) => (isCommand(command) ? [] : [`the program ${role} is not a command`])),\n ];\n};\n\n/**\n * This function gives each `FROM` of a Dockerfile that no digest pins. A `FROM` of an earlier stage\n * by its name, or of `scratch`, needs no digest. A base from an `ARG` has no digest that the harness\n * can read, thus it is in the list too.\n */\nexport const unpinnedBases = (dockerfile: string): string[] => {\n const stages = new Set<string>([\"scratch\"]);\n const found: string[] = [];\n for (const line of dockerfile.replace(/\\\\\\r?\\n/g, \" \").split(/\\r?\\n/)) {\n const from = /^\\s*FROM\\s+(.+)$/i.exec(line)?.[1];\n if (from === undefined) continue;\n const [image, as, stage] = from.split(/\\s+/).filter((word) => !word.startsWith(\"--\"));\n if (image === undefined) continue;\n if (!stages.has(image.toLowerCase()) && !PINNED.test(image)) found.push(image);\n if (as?.toLowerCase() === \"as\" && stage !== undefined) stages.add(stage.toLowerCase());\n }\n return found;\n};\n\n/** These are the lockfiles of each manifest of packages. A manifest without a lockfile installs other packages each time. */\nconst LOCKFILES: Readonly<Record<string, readonly string[]>> = {\n \"package.json\": [\"package-lock.json\", \"npm-lock.json\", \"npm-shrinkwrap.json\", \"pnpm-lock.yaml\", \"yarn.lock\", \"bun.lock\"],\n \"Cargo.toml\": [\"Cargo.lock\"],\n};\n\n/**\n * This function gives each manifest of packages in a context (`package.json`, `Cargo.toml`) that\n * has no lockfile in its folder or in a folder above it. The paths are the paths in the context.\n */\nexport const unlockedManifests = (paths: readonly string[]): string[] => {\n const present = new Set(paths);\n return paths.filter((path) => {\n const parts = path.split(\"/\");\n const locks = LOCKFILES[parts.at(-1)!];\n if (locks === undefined) return false;\n for (let depth = parts.length - 1; depth >= 0; depth -= 1) {\n const folder = parts.slice(0, depth).join(\"/\");\n if (locks.some((lock) => present.has(folder === \"\" ? lock : `${folder}/${lock}`))) return false;\n }\n return true;\n });\n};\n\n/**\n * An image plan tells how the harness gets the image of a source. It is a pull of an image by its\n * name, or a build of a context. A build has its tag, its Dockerfile, and each file with its path.\n */\nexport type ImagePlan =\n | { readonly kind: \"pull\"; readonly image: string }\n | {\n readonly kind: \"build\";\n readonly tag: string;\n readonly dockerfile: string;\n readonly files: ReadonlyArray<{ readonly file: string; readonly at: string }>;\n };\n\n/** This function gives each file under a path, in a stable sequence, but not the output of a local build. */\nconst sources = (path: string): string[] => {\n if (statSync(path).isFile()) return [path];\n return readdirSync(path, { withFileTypes: true })\n .filter((entry) => entry.name !== \"target\" && entry.name !== \"node_modules\")\n .flatMap((entry) => sources(join(path, entry.name)))\n .sort();\n};\n\n/** A part is a file or a folder of a context: its path on the machine, and its path in the context. */\ninterface Part {\n readonly path: string;\n readonly at: string;\n}\n\n/** This function gives the name, the Dockerfile and the parts of the context of a build or of a shipped image. */\nconst contextOf = (source: BuildImage | ShippedImage): { name: string; dockerfile: string; parts: Part[] } => {\n if (source.kind === \"shipped\") {\n const { name, dockerfile, from } = SHIPPED[source.name];\n return { name, dockerfile, parts: from.map((part) => ({ path: pathOf(part), at: part })) };\n }\n const root = resolve(source.context);\n return {\n name: source.name,\n dockerfile: posix.normalize(source.dockerfile ?? \"Dockerfile\"),\n parts: (source.files ?? [\".\"]).map((file) => ({ path: resolve(root, file), at: file === \".\" ? \"\" : file })),\n };\n};\n\n/** This function gives a text for a source in a message. */\nconst labelOf = (source: ImageSource): string =>\n source.kind === \"pinned\" ? source.image : `the ${source.kind === \"build\" ? \"build\" : \"shipped image\"} ${source.name}`;\n\nconst shippedPlans = new Map<Shipped, ImagePlan>();\n\n/**\n * This function gives the plan of the image of a source. A stock image by its name, or a pinned\n * image, is a pull. A build or a shipped image is a build. Its tag is its name, then 12 digits of a\n * sha256. The sha256 reads the path of the Dockerfile, and the path and the content of each file.\n * Thus a changed program cannot operate as an old one.\n *\n * A build fails before it starts on a problem of its source. It also fails on a `FROM` that no\n * digest pins, and on a manifest of packages without a lockfile.\n */\nexport const planOf = (source: string | ImageSource): ImagePlan => {\n if (typeof source === \"string\") return { kind: \"pull\", image: source };\n const problems = imageProblems(source);\n if (problems.length > 0) throw new Error(`${labelOf(source)}: ${problems.join(\", \")}`);\n if (source.kind === \"pinned\") return { kind: \"pull\", image: source.image };\n const known = source.kind === \"shipped\" ? shippedPlans.get(source.name) : undefined;\n if (known !== undefined) return known;\n const { name, dockerfile, parts } = contextOf(source);\n const files = parts.flatMap((part) =>\n sources(part.path).map((file) => ({ file, at: join(part.at, relative(part.path, file)).replaceAll(\"\\\\\", \"/\") })),\n );\n const docker = files.find((each) => each.at === dockerfile);\n if (docker === undefined) throw new Error(`${labelOf(source)}: the context has no ${dockerfile}`);\n const unpinned = unpinnedBases(readFileSync(docker.file, \"utf8\"));\n const unlocked = unlockedManifests(files.map((each) => each.at));\n if (unpinned.length > 0 || unlocked.length > 0)\n throw new Error(\n `${labelOf(source)}: ${[\n ...unpinned.map((image) => `the base ${image} is not pinned by digest`),\n ...unlocked.map((path) => `${path} has no lockfile`),\n ].join(\", \")}`,\n );\n const hash = createHash(\"sha256\");\n hash.update(dockerfile);\n for (const { file, at } of files) {\n hash.update(at);\n hash.update(readFileSync(file));\n }\n const plan: ImagePlan = { kind: \"build\", tag: `${name}:${hash.digest(\"hex\").slice(0, 12)}`, dockerfile, files };\n if (source.kind === \"shipped\") shippedPlans.set(source.name, plan);\n return plan;\n};\n\n/** This function builds a staged context with Docker, through Testcontainers. */\nconst dockerBuild = async (context: string, plan: Extract<ImagePlan, { readonly kind: \"build\" }>): Promise<void> => {\n const { GenericContainer } = await import(\"testcontainers\");\n await GenericContainer.fromDockerfile(context, plan.dockerfile).build(plan.tag, { deleteOnExit: false });\n};\n\nconst built = new Map<string, Promise<string>>();\n\n/**\n * This function builds the image of a plan one time for each process, from a new context that it\n * copies the files into. Docker keeps its layers between runs. A build that fails leaves the cache.\n * Thus a later cell builds again after a fault of Docker or of the network.\n */\nexport const buildOnce = (\n plan: Extract<ImagePlan, { readonly kind: \"build\" }>,\n builder: (context: string, plan: Extract<ImagePlan, { readonly kind: \"build\" }>) => Promise<void> = dockerBuild,\n): Promise<string> => {\n let tag = built.get(plan.tag);\n if (tag === undefined) {\n const building = (async () => {\n const context = mkdtempSync(join(tmpdir(), `${plan.tag.split(\":\")[0]!.replaceAll(\"/\", \"-\")}-`));\n try {\n for (const { file, at } of plan.files) {\n mkdirSync(dirname(join(context, at)), { recursive: true });\n copyFileSync(file, join(context, at));\n }\n await builder(context, plan);\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n return plan.tag;\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built.get(plan.tag) === building) built.delete(plan.tag);\n });\n built.set(plan.tag, building);\n tag = building;\n }\n return tag;\n};\n\n/**\n * This function gives an image that a lab can start, from its source. It is the name of a stock\n * image or of a pinned image, which the lab pulls, or the tag of a build. This function loads\n * Testcontainers only for a build.\n */\nexport const imageOf = async (source: string | ImageSource): Promise<string> => {\n const plan = planOf(source);\n return plan.kind === \"pull\" ? plan.image : buildOnce(plan);\n};\n\n/** This function gives the image of the harness: the prober, the TCP client, the injector and the namespace admin. */\nexport const harnessImage = (): Promise<string> => imageOf(shipped(\"harness\"));\n/** This function gives the image of the Node client. */\nexport const nodeClientImage = (): Promise<string> => imageOf(shipped(\"node\"));\n/** This function gives the image of the router. */\nexport const routerImage = (): Promise<string> => imageOf(shipped(\"router\"));\n/** This function gives the image of the Go HTTP client. */\nexport const goHttpImage = (): Promise<string> => imageOf(shipped(\"go-http\"));\n/** This function gives the image of the Node HTTP clients. */\nexport const nodeHttpImage = (): Promise<string> => imageOf(shipped(\"node-http\"));\n/** This function gives the image of the MongoDB client. */\nexport const mongoClientImage = (): Promise<string> => imageOf(shipped(\"mongo\"));\n/** This function gives the image of the PostgreSQL client. */\nexport const pgClientImage = (): Promise<string> => imageOf(shipped(\"pg\"));\n/** This function gives the image of the client of HikariCP over pgjdbc. */\nexport const hikariClientImage = (): Promise<string> => imageOf(shipped(\"hikari\"));\n/** This function gives the image of the RabbitMQ client: amqplib, with the client and the admin of a cluster cell. */\nexport const amqpClientImage = (): Promise<string> => imageOf(shipped(\"amqp\"));\n/** This function gives the image of the procedures subject: the WebSocket client of `@procedures/client`, and its server. */\nexport const proceduresImage = (): Promise<string> => imageOf(SUBJECTS.procedures.image);\n\n/**\n * This function gives the source of the image that a target operates. It is the image of its\n * subject (`Target.subject`), a shipped image by its short form (`Target.harness`), or its stock image.\n */\nexport const targetSourceOf = (target: Target): string | ImageSource =>\n target.subject ?? (target.harness === undefined ? target.image : shipped(target.harness));\n\n/** This function gives the image that a target operates: the image of its subject, or its stock image. */\nexport const targetImageOf = (target: Target): Promise<string> => imageOf(targetSourceOf(target));\n"],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as Target, A as ProbeEvent, B as RouterHop, C as LineMatch, D as Observer, E as MiddleboxForm, F as Reference, G as RunTopology, H as Run, I as ReplicaSet, J as Shipped, K as SentinelEvent, L as ReplicaSetStage, M as ProxyTime, N as Reconnects, O as PinnedImage, P as Recovers, Q as Subject, R as Replies, S as KernelRecord, T as MiddleboxExpectation, U as RunGroup, V as RttRange, W as RunPart, X as Stage, Y as ShippedImage, Z as StageBand, _ as Hop, a as Cluster, b as HttpProbe, c as DriverBand, d as FaultPlace, et as Topology, f as Group, g as HarnessImage, h as GroupRole, i as ClientImage, it as latestEndOf, j as ProxyHop, k as Prediction, l as DriverStep, m as GroupPrediction, n as Cell, nt as WsProbe, o as ClusterClientAt, p as GroupPlace, q as SentinelStage, r as Client, rt as isHopPlace, s as ClusterStage, t as BuildImage, tt as Toxic, u as Fault, v as HopEvent, w as MiddleboxEvent, x as ImageSource, y as HopPlace, z as RequestPrediction } from "./cell-BBPhdtav.js";
|
|
2
2
|
import { Quantity } from "@mark1russell7/quantity";
|
|
3
3
|
import "testcontainers";
|
|
4
4
|
//#region src/application.d.ts
|
|
@@ -155,8 +155,8 @@ export declare const measureDriver: (cell: Cell, run: Run) => Measured;
|
|
|
155
155
|
/**
|
|
156
156
|
* This module measures a run of a group cell. It gives the first write that a primary accepted
|
|
157
157
|
* after the fault, and the timeline of the group. The Sentinels of a group announce its timeline.
|
|
158
|
-
* The logs of the members of a replica set and its client tell it. The
|
|
159
|
-
* became of each write.
|
|
158
|
+
* The logs of the members of a replica set or of the nodes of a cluster, and its client, tell it. The
|
|
159
|
+
* module also gives what became of each write.
|
|
160
160
|
*/
|
|
161
161
|
/** These are the stages of a timeline under Sentinels, in their sequence after a fault. */
|
|
162
162
|
export declare const STAGES: readonly SentinelStage[];
|
|
@@ -168,7 +168,8 @@ interface ErrorOnTheWay {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* A writes record tells what became of the writes of a run, by their sequence numbers:
|
|
171
|
-
* - `old` counts the writes that the primary of the start of the run accepted after the fault.
|
|
171
|
+
* - `old` counts the writes that the primary of the start of the run accepted after the fault. A
|
|
172
|
+
* cluster counts none: its client publishes through one node, not through the leader of the queue.
|
|
172
173
|
* - `lost` are the writes that a primary accepted and the group did not keep. They are the cost
|
|
173
174
|
* of a split brain. It is null when the group did not converge after the run.
|
|
174
175
|
* - `unknown` are the writes that the client sent and did not see accepted: an error, or no answer.
|
|
@@ -222,6 +223,24 @@ export declare const MEMBER_MARKS: Readonly<Record<Exclude<ReplicaSetStage, "dis
|
|
|
222
223
|
* the commit of the fault (`sentAfter`). It is on a new primary when `lands` is `new`.
|
|
223
224
|
*/
|
|
224
225
|
export declare const replicaSetStagesOf: (run: Run, from: number, sentAfter: number, lands: "new" | "same") => Partial<Record<Stage, Quantity>>;
|
|
226
|
+
/** These are the stages of the timeline of a cluster of RabbitMQ, in their usual sequence after a fault. */
|
|
227
|
+
export declare const CLUSTER_STAGES: readonly ClusterStage[];
|
|
228
|
+
/**
|
|
229
|
+
* These are the log lines of RabbitMQ that mark each stage of a cluster, by their message (ra 3.1.10,
|
|
230
|
+
* ra_server_proc.erl:871, :1336-1345 and :1974). `at` tells where the line comes from: the node of the
|
|
231
|
+
* old leader, or another node.
|
|
232
|
+
*/
|
|
233
|
+
export declare const NODE_MARKS: Readonly<Record<Exclude<ClusterStage, "first-write">, {
|
|
234
|
+
readonly message: RegExp;
|
|
235
|
+
readonly at: "old" | "other";
|
|
236
|
+
}>>;
|
|
237
|
+
/**
|
|
238
|
+
* This function gives the time of each stage of a cluster from an instant, in a run. The stages of
|
|
239
|
+
* the nodes come from their log lines (`ev` "rabbit"), whose times the lab puts on the monotonic
|
|
240
|
+
* clock. `first-write` is the first confirm of a publish that the client sent after the commit of
|
|
241
|
+
* the fault (`sentAfter`), through any node.
|
|
242
|
+
*/
|
|
243
|
+
export declare const clusterStagesOf: (run: Run, from: number, sentAfter: number) => Partial<Record<Stage, Quantity>>;
|
|
225
244
|
/** This function gives what became of each write of a run, from the lines of the client and the keys that the group kept. */
|
|
226
245
|
export declare const writesOf: (run: Run, after: number, primary: string) => Writes;
|
|
227
246
|
/** This function gives the errors that the client met after an instant: each kind, its first time, and its count. */
|
|
@@ -1088,6 +1107,8 @@ export declare const mongoClientImage: () => Promise<string>;
|
|
|
1088
1107
|
export declare const pgClientImage: () => Promise<string>;
|
|
1089
1108
|
/** This function gives the image of the client of HikariCP over pgjdbc. */
|
|
1090
1109
|
export declare const hikariClientImage: () => Promise<string>;
|
|
1110
|
+
/** This function gives the image of the RabbitMQ client: amqplib, with the client and the admin of a cluster cell. */
|
|
1111
|
+
export declare const amqpClientImage: () => Promise<string>;
|
|
1091
1112
|
/** This function gives the image of the procedures subject: the WebSocket client of `@procedures/client`, and its server. */
|
|
1092
1113
|
export declare const proceduresImage: () => Promise<string>;
|
|
1093
1114
|
/**
|
|
@@ -1384,6 +1405,176 @@ export declare const replicaSetArgs: (cell: Cell, members: Readonly<Record<strin
|
|
|
1384
1405
|
*/
|
|
1385
1406
|
export declare const replicaSetOnce: (lab: ReplicaSetLab, cell: Cell, environment: number, index: number) => Promise<Run>;
|
|
1386
1407
|
//#endregion
|
|
1408
|
+
//#region src/cluster.d.ts
|
|
1409
|
+
/**
|
|
1410
|
+
* This module is the lab of a cluster of RabbitMQ with one quorum queue. The members of the queue
|
|
1411
|
+
* elect its leader by Raft, one member on each node. It opens one environment for a cluster cell,
|
|
1412
|
+
* for the runs of the cell. Each node is a container from the stock image of the cluster, in its own
|
|
1413
|
+
* network namespace. An injector sidecar shares its network and process namespaces.
|
|
1414
|
+
*
|
|
1415
|
+
* The client of each run is amqplib. It publishes a stream of sequence numbers with confirms through
|
|
1416
|
+
* one node, and it connects to the next node when its connection ends. The timeline comes from
|
|
1417
|
+
* the log of each node and from the lines of the client. The prober watches the namespace of the client.
|
|
1418
|
+
*/
|
|
1419
|
+
/**
|
|
1420
|
+
* A node line is one log line of a node, as RabbitMQ writes it to its console. `real` is its time on
|
|
1421
|
+
* the wall clock in ms, with its microseconds. `t` is its time on the monotonic clock: the run gives
|
|
1422
|
+
* it from the clock lines of its client.
|
|
1423
|
+
*/
|
|
1424
|
+
interface NodeLine extends SentinelEvent {
|
|
1425
|
+
readonly ev: "rabbit";
|
|
1426
|
+
readonly member: string;
|
|
1427
|
+
readonly real: number;
|
|
1428
|
+
readonly level: string;
|
|
1429
|
+
readonly msg: string;
|
|
1430
|
+
}
|
|
1431
|
+
/** This function gives a line of the console without its colors. */
|
|
1432
|
+
export declare const plainOf: (text: string) => string;
|
|
1433
|
+
/** This text is the name of a quorum queue in the log of Ra: its friendly name. */
|
|
1434
|
+
export declare const logNameOf: (queue: string) => string;
|
|
1435
|
+
/**
|
|
1436
|
+
* This function reads one log line of a node, or gives null for a line that a run does not keep. A
|
|
1437
|
+
* run keeps each line of the queue, each warning and each error, and each line of the maintenance
|
|
1438
|
+
* mode of a drain.
|
|
1439
|
+
*/
|
|
1440
|
+
export declare const nodeLineOf: (member: string, text: string, queue: string) => NodeLine | null;
|
|
1441
|
+
/** This function puts the log lines of the nodes on the monotonic clock, with the offset of a client. */
|
|
1442
|
+
export declare const nodesOnMonotonic: (log: readonly NodeLine[], offset: number | null) => NodeLine[];
|
|
1443
|
+
/**
|
|
1444
|
+
* This function reads the JSON output of a tool of RabbitMQ (`--formatter json`). The tools print a
|
|
1445
|
+
* value over one line or over more than one, thus it reads from the first bracket to the end.
|
|
1446
|
+
*/
|
|
1447
|
+
export declare const toolJsonOf: (text: string) => unknown;
|
|
1448
|
+
/** This function gives the name of a node in the Erlang distribution: `rabbit@<name>`. */
|
|
1449
|
+
export declare const nodeNameOf: (member: string) => string;
|
|
1450
|
+
/**
|
|
1451
|
+
* This function gives the file of rabbitmq.conf of each node. The nodes form the cluster from the list
|
|
1452
|
+
* of their names, and the user guest connects from each address. The lines of the cluster come last.
|
|
1453
|
+
*/
|
|
1454
|
+
export declare const clusterConf: (cluster: Cluster) => string;
|
|
1455
|
+
/**
|
|
1456
|
+
* This function gives the command of each node. It writes the cookie of the server and of the tools.
|
|
1457
|
+
* Then it starts the server as the child of a shell. A fin can then stop the server, and the container
|
|
1458
|
+
* stays up.
|
|
1459
|
+
*/
|
|
1460
|
+
export declare const nodeCommand: (cluster: Cluster) => string;
|
|
1461
|
+
/** A status row is one member of the queue in the output of `rabbitmq-queues quorum_status`. */
|
|
1462
|
+
interface StatusRow {
|
|
1463
|
+
readonly "Node Name": string;
|
|
1464
|
+
readonly "Raft State": string;
|
|
1465
|
+
readonly Term: number | string;
|
|
1466
|
+
readonly "Commit Index": number | string;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* This function judges the status of the queue: its leader, and if the queue is healthy. A healthy
|
|
1470
|
+
* queue has one leader and a follower on each other node. Each follower is in the term of the leader,
|
|
1471
|
+
* at the commit index of the leader.
|
|
1472
|
+
*/
|
|
1473
|
+
export declare const clusterViewOf: (cluster: Cluster, rows: readonly StatusRow[]) => GroupView;
|
|
1474
|
+
/**
|
|
1475
|
+
* This function gives the peers that a fault on a node cuts it from. `all` is each other node and the
|
|
1476
|
+
* client, `group` is the other nodes, and `clients` is the client. `one` is the first other node in
|
|
1477
|
+
* the sequence of the cluster, and `one-way` is the same node for the segments from the part only.
|
|
1478
|
+
*/
|
|
1479
|
+
export declare const clusterCutOf: (cluster: Cluster, members: Readonly<Record<string, RunPart>>, client: string, part: string, cuts: NonNullable<GroupPlace["cuts"]>) => Cut;
|
|
1480
|
+
/** A wall stamp is the wall clock in ns around a command of a node: the run puts it on the monotonic clock. */
|
|
1481
|
+
interface WallStamp {
|
|
1482
|
+
readonly wall0: number;
|
|
1483
|
+
readonly wall1: number;
|
|
1484
|
+
}
|
|
1485
|
+
/** A cluster lab is one environment of a cluster cell. */
|
|
1486
|
+
interface ClusterLab {
|
|
1487
|
+
readonly cluster: Cluster;
|
|
1488
|
+
readonly clientIp: string;
|
|
1489
|
+
readonly members: Readonly<Record<string, RunPart>>;
|
|
1490
|
+
readonly netns: {
|
|
1491
|
+
readonly client: number;
|
|
1492
|
+
};
|
|
1493
|
+
/** These are all the records that the prober wrote, in sequence. */
|
|
1494
|
+
readonly kernel: readonly KernelRecord[];
|
|
1495
|
+
/** These are the log lines of the nodes that a run keeps, in the sequence of their arrival, with `t` at 0. */
|
|
1496
|
+
readonly log: readonly NodeLine[];
|
|
1497
|
+
/** These are the last raw lines of the console of each node, for the diagnosis of a lab: at most RAW_TAIL of each. */
|
|
1498
|
+
readonly raw: Readonly<Record<string, readonly string[]>>;
|
|
1499
|
+
/** This method sets the sysctls of the client namespace through its privileged sidecar, and reads them back. */
|
|
1500
|
+
sysctl(values: Readonly<Record<string, string>>): Promise<Record<string, string>>;
|
|
1501
|
+
/** This method waits until the queue is whole and calm, and gives the name of the node of its leader. */
|
|
1502
|
+
converge(within?: number): Promise<string>;
|
|
1503
|
+
/** This method removes each message from the queue. */
|
|
1504
|
+
flush(leader: string): Promise<void>;
|
|
1505
|
+
/** This method gives the sequence numbers that the queue holds, through the node of its leader. The queue is empty after it. */
|
|
1506
|
+
holds(leader: string): Promise<number[]>;
|
|
1507
|
+
/** This method gives the node that has a role, when `leader` is the node of the leader. The role is the leader, or a follower. */
|
|
1508
|
+
partOf(role: GroupRole, leader: string): string;
|
|
1509
|
+
/** This method starts the client of a run, with the arguments of `client-cluster.mjs`. */
|
|
1510
|
+
client(args: readonly string[]): Promise<GroupClient>;
|
|
1511
|
+
/** This method applies a network fault between a node and some of its peers, at once or at an instant of the VM. */
|
|
1512
|
+
fault(kind: GroupNetworkFault, part: string, cuts: NonNullable<GroupPlace["cuts"]>, at?: number): Promise<Stamp>;
|
|
1513
|
+
/** This method removes a network fault, at once or at an instant of the VM. */
|
|
1514
|
+
lift(kind: GroupNetworkFault, part: string, cuts: NonNullable<GroupPlace["cuts"]>, at?: number): Promise<Stamp>;
|
|
1515
|
+
/** This method sends a signal to the server process of a node, at once or at an instant of the VM. */
|
|
1516
|
+
signal(part: string, signal: "KILL" | "STOP" | "CONT", at?: number): Promise<Stamp>;
|
|
1517
|
+
/** This method starts the server of a node again, after a fin. */
|
|
1518
|
+
restart(member: string): Promise<void>;
|
|
1519
|
+
/** This method moves the leadership of the queue from the node of `leader` to the node `to`: `rabbitmq-queues transfer_leadership`. */
|
|
1520
|
+
stepDown(leader: string, to: string): Promise<WallStamp>;
|
|
1521
|
+
/** This method puts a node in maintenance mode (`rabbitmq-upgrade drain`). */
|
|
1522
|
+
drain(member: string): Promise<WallStamp>;
|
|
1523
|
+
/** This method ends the maintenance mode of a node (`rabbitmq-upgrade revive`). */
|
|
1524
|
+
revive(member: string): Promise<void>;
|
|
1525
|
+
/** This method removes the TCP metrics of the client namespace: each run starts from a kernel that keeps nothing. */
|
|
1526
|
+
forget(): Promise<void>;
|
|
1527
|
+
/** This method waits for the next clock line of the prober, which gives the count of lost records. */
|
|
1528
|
+
settle(): Promise<number | null>;
|
|
1529
|
+
manifest(sysctls: Readonly<Record<string, string>>): Promise<Manifest>;
|
|
1530
|
+
close(): Promise<void>;
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* This function opens one environment of a cluster cell:
|
|
1534
|
+
* - one new network, with each node, the client and the admin on it.
|
|
1535
|
+
* - each node: the stock image, its server as the child of a shell, with its hostname as the name of
|
|
1536
|
+
* the node. A log consumer reads its console. The nodes form the cluster from the list of their
|
|
1537
|
+
* names, and the lab waits until each node runs in it.
|
|
1538
|
+
* - the admin declares the quorum queue on the first node, thus its member there is the first leader.
|
|
1539
|
+
* - an injector sidecar for each node, in its network and process namespaces.
|
|
1540
|
+
* - a client that holds its own network namespace, with a privileged sidecar for its sysctls.
|
|
1541
|
+
* - the prober, which watches the namespace of the client.
|
|
1542
|
+
*/
|
|
1543
|
+
export declare const openClusterLab: (cluster: Cluster) => Promise<ClusterLab>;
|
|
1544
|
+
/**
|
|
1545
|
+
* This function gives the hosts of the client, in the sequence of its connects: its node first, then
|
|
1546
|
+
* each other node. The node of a follower is the last follower in the sequence of the cluster. A fault
|
|
1547
|
+
* on one member (`one`, `one-way`) cuts the first follower, thus not the node of the client.
|
|
1548
|
+
*/
|
|
1549
|
+
export declare const clusterHostsOf: (cell: Cell, members: Readonly<Record<string, RunPart>>, leader: string) => string[];
|
|
1550
|
+
/** This form is the NOTICE of a leader of the queue that starts to give its leadership away: to await_condition, for a transfer. */
|
|
1551
|
+
export declare const GIVES_AWAY: RegExp;
|
|
1552
|
+
/** This form is the INFO of a follower whose monitor of the leader goes down (ra_server_proc.erl:871). A drain stops the leader so. */
|
|
1553
|
+
export declare const MONITOR_DOWN: RegExp;
|
|
1554
|
+
/** This function gives the arguments of the client of a cluster cell: its hosts, the queue, and the stream of publishes. */
|
|
1555
|
+
export declare const clusterArgs: (cell: Cell, members: Readonly<Record<string, RunPart>>, leader: string) => string[];
|
|
1556
|
+
/** This function puts a wall stamp on the monotonic clock, with the clock lines of the client. Without one it gives null. */
|
|
1557
|
+
export declare const monotonicOf: (stamp: WallStamp, events: readonly ProbeEvent[]) => Stamp | null;
|
|
1558
|
+
/**
|
|
1559
|
+
* This function puts the stamp of a fault from a line of the log on the monotonic clock again. It uses
|
|
1560
|
+
* the offset of all the clock lines of the client, as the timeline of the run does. Thus the line of the
|
|
1561
|
+
* stamp is at the stamp. The offset of the clock lines at the fault can be up to 1 ms smaller.
|
|
1562
|
+
*/
|
|
1563
|
+
export declare const restamped: (line: NodeLine, events: readonly ProbeEvent[]) => Stamp | null;
|
|
1564
|
+
/**
|
|
1565
|
+
* This function does one run of a cluster cell:
|
|
1566
|
+
* - The queue converges, and it is empty. The client connects through its node, and its stream of
|
|
1567
|
+
* publishes gets steady.
|
|
1568
|
+
* - The fault occurs on its node, as the leader of this run makes the roles. A fault with a hold
|
|
1569
|
+
* lifts at its instant in the VM. A step-down moves the leadership to a follower, and a drain puts
|
|
1570
|
+
* the node in maintenance mode. The stamp of each of them is a line of the log (`stampOf`).
|
|
1571
|
+
* - The run waits for its outcome: the first confirm of a publish sent after the commit of the fault.
|
|
1572
|
+
* Then it waits for the end of the hold, thus the record shows what the cluster did in the whole hold.
|
|
1573
|
+
* - Each fault goes, the client stops, and the queue converges again. The sequence numbers that it
|
|
1574
|
+
* then holds tell which publishes it kept. The log lines of the nodes go onto the monotonic clock.
|
|
1575
|
+
*/
|
|
1576
|
+
export declare const clusterOnce: (lab: ClusterLab, cell: Cell, environment: number, index: number) => Promise<Run>;
|
|
1577
|
+
//#endregion
|
|
1387
1578
|
//#region src/run.d.ts
|
|
1388
1579
|
/** A cell result is a cell with its manifests, its runs, their measurements and its verdict. */
|
|
1389
1580
|
interface CellResult {
|
|
@@ -1432,5 +1623,5 @@ export declare const spread: (runs: number, environments: number) => number[];
|
|
|
1432
1623
|
*/
|
|
1433
1624
|
export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
|
|
1434
1625
|
//#endregion
|
|
1435
|
-
export { type Agent, type BuildImage, type CallReading, type Cell, type CellResult, type Client, type ClientImage, 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 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 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 Subject, type Target, type Topology, type Toxic, type Verdict, type Writes, type WsProbe, isHopPlace, latestEndOf };
|
|
1626
|
+
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 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 };
|
|
1436
1627
|
//# sourceMappingURL=index.d.ts.map
|