@mark1russell7/harness 0.12.1 → 0.12.2

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 CHANGED
@@ -203,6 +203,7 @@ From 0.11.20, the harness builds each image with the CLI of Docker (`docker buil
203
203
  - It serves `grpc.health.v1.Health`. A check of the service `hold:<ms>` holds its reply for that time, and Watch holds a stream open. It writes JSON lines on the monotonic clock (`Target.lines`).
204
204
  - At SIGTERM, it writes `stopping`, and GracefulStop closes its listener and sends GOAWAY on each connection.
205
205
  - From 0.11.26, `--name` gives a name to each line (`p`) and to each reply of Check (the header `x-pod`), and `--clock ms` gives `t` in milliseconds. A kind cell of `@mark1russell7/kind` uses both.
206
+ - From 0.12.2, `--fail-file PATH` adds the service `liveness`, the seam of a liveness restart. Check of `liveness` answers NOT_SERVING while the file at PATH exists, and each such reply writes a `not-serving` line. Check of the service `""` still answers SERVING. Without the flag, the server does not change.
206
207
  - **The etcd image** (`image/etcd`) is etcd v3.6.15 on a digest-pinned Alpine. The binaries come unchanged from the image of etcd, pinned by digest. That image has no shell, and a cell holds the server as the child of a shell. A target takes it as the image of a subject: `{ kind: "shipped", name: "etcd" }`.
207
208
  - **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:
208
209
  - 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.
@@ -7,7 +7,7 @@
7
7
  // [--permit-without-stream] [--max-connection-idle D] [--max-connection-age D]
8
8
  // [--max-connection-age-grace D] [--listener-keepalive D]
9
9
  // [--listener-keepalive-interval D] [--listener-keepalive-count N]
10
- // [--name S] [--clock ns|ms]
10
+ // [--name S] [--clock ns|ms] [--fail-file PATH]
11
11
  //
12
12
  // Each D is a duration of Go, for example 20s. A setting at 0 is the default of grpc-go or of Go. A
13
13
  // negative --listener-keepalive turns the TCP keepalive of the accepted connections off.
@@ -16,10 +16,15 @@
16
16
  // "hold:<ms>" holds its reply for that time, or until the client ends the call. Any other service is
17
17
  // NOT_FOUND. Watch sends SERVING and then holds the stream open until the client ends it.
18
18
  //
19
+ // With --fail-file, the server also serves the service "liveness", the seam of a liveness restart.
20
+ // Check of "liveness" answers NOT_SERVING while the file at PATH exists, and SERVING when it does not.
21
+ // Check of "" still answers SERVING. Without the flag, "liveness" is NOT_FOUND, as each other service.
22
+ //
19
23
  // The lines: listening (the ready line of the target), accept (each connection, with its addresses),
20
- // hold, held and hold-ended (a held reply), watch and watch-ended, and stopping. At SIGTERM or SIGINT,
21
- // the server writes stopping and then calls GracefulStop: it closes its listener, sends GOAWAY on each
22
- // connection and waits for each call in flight.
24
+ // hold, held and hold-ended (a held reply), watch and watch-ended, not-serving (each NOT_SERVING
25
+ // reply of "liveness"), and stopping. At SIGTERM or SIGINT, the server writes stopping and then calls
26
+ // GracefulStop: it closes its listener, sends GOAWAY on each connection and waits for each call in
27
+ // flight.
23
28
  //
24
29
  // A kind cell runs the server in a pod. --name gives the name of the pod: each line has it in the
25
30
  // field p, and each reply of Check has it in the header x-pod. --clock ms gives t in milliseconds, the
@@ -61,6 +66,16 @@ var name string
61
66
  // millis tells if t of each line is in milliseconds (--clock ms) and not in nanoseconds.
62
67
  var millis bool
63
68
 
69
+ // failFile is the path of the flag file that fails the service "liveness" (--fail-file), or "" for no
70
+ // such service.
71
+ var failFile string
72
+
73
+ // failing tells if the flag file exists. Then Check of "liveness" answers NOT_SERVING.
74
+ func failing() bool {
75
+ _, err := os.Stat(failFile)
76
+ return err == nil
77
+ }
78
+
64
79
  // stamp gives t of a line: the time on CLOCK_MONOTONIC in the unit of --clock.
65
80
  func stamp() any {
66
81
  if millis {
@@ -100,6 +115,13 @@ func (health) Check(ctx context.Context, request *healthpb.HealthCheckRequest) (
100
115
  if service == "" {
101
116
  return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil
102
117
  }
118
+ if service == "liveness" && failFile != "" {
119
+ if failing() {
120
+ emit("not-serving", map[string]any{"service": service})
121
+ return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_NOT_SERVING}, nil
122
+ }
123
+ return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil
124
+ }
103
125
  text, ok := strings.CutPrefix(service, "hold:")
104
126
  if !ok {
105
127
  return nil, status.Error(codes.NotFound, "unknown service")
@@ -160,6 +182,7 @@ func main() {
160
182
  keepCount := flag.Int("listener-keepalive-count", 0, "the TCP keepalive count (0: 9)")
161
183
  flag.StringVar(&name, "name", "", "the name of the server in each line (p) and each reply of Check (x-pod)")
162
184
  clock := flag.String("clock", "ns", "the unit of t: ns or ms")
185
+ flag.StringVar(&failFile, "fail-file", "", "the flag file that fails Check of the service liveness (\"\": no service liveness)")
163
186
  flag.Parse()
164
187
  if *clock != "ns" && *clock != "ms" {
165
188
  emit("fail", map[string]any{"why": "--clock ns|ms"})
@@ -202,6 +225,7 @@ func main() {
202
225
  "keepalive_time": keepaliveTime.String(), "keepalive_timeout": keepaliveTimeout.String(), "min_time": minTime.String(),
203
226
  "permit_without_stream": *permitWithoutStream, "listener_keepalive": keepIdle.String(),
204
227
  "listener_keepalive_interval": keepInterval.String(), "listener_keepalive_count": *keepCount,
228
+ "fail_file": failFile,
205
229
  })
206
230
  if err := server.Serve(listener{inner}); err != nil {
207
231
  emit("fail", map[string]any{"why": err.Error()})
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/harness",
4
- "version": "0.12.1",
4
+ "version": "0.12.2",
5
5
  "description": "A fault-injection harness for connection timers: a cell runs a client against a stock target in Docker, through a topology of routers and proxies, a replicated group under Sentinels or a replica set, injects a fault, measures the kernel's own records and judges a prediction. HTTP clients of Go, Node and undici race the keep-alive of a Node server, and the MongoDB driver meets the faults of a replica set. The WebSocket client of @procedures/client drops a silent connection on its own heartbeat, and the harness times that detection and the reconnect from the program's lines and the kernel's stamps.",
6
6
  "keywords": [
7
7
  "fault-injection",