@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/build/schema.d.ts
CHANGED
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
//! CONT freeze a server and thaw it while its kernel stays up; KILL stops it, and its kernel
|
|
33
33
|
//! closes its sockets; HUP asks a pooler to read its configuration again. The stamps bracket the
|
|
34
34
|
//! kill(2) calls.
|
|
35
|
+
//! - `write NAME PATH CONTENT`: CONTENT to the file PATH in the root of the process NAME, through
|
|
36
|
+
//! /proc/PID/root, by a rename in one step. A proxy that watches its configuration file reloads
|
|
37
|
+
//! it, as after an edit. The stamps bracket the rename.
|
|
35
38
|
//! - `resp ADDR:PORT ARG...`: one command to a Redis-protocol server, for example
|
|
36
39
|
//! `SENTINEL FAILOVER mymaster` to a Sentinel. The stamps bracket the write of the command and
|
|
37
40
|
//! the first line of its reply, so a planned failover starts at a known instant.
|
|
@@ -323,6 +326,41 @@ fn signal(args: &[String]) -> Result<(), Box<dyn Error>> {
|
|
|
323
326
|
Ok(())
|
|
324
327
|
}
|
|
325
328
|
|
|
329
|
+
/// an absolute path of letters, digits, dots, dashes, underscores and slashes, with no `..`
|
|
330
|
+
fn file_path(path: &str) -> Result<&str, Box<dyn Error>> {
|
|
331
|
+
if path.starts_with('/')
|
|
332
|
+
&& path.len() <= 255
|
|
333
|
+
&& !path.split('/').any(|part| part == "..")
|
|
334
|
+
&& path.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '/'))
|
|
335
|
+
{
|
|
336
|
+
Ok(path)
|
|
337
|
+
} else {
|
|
338
|
+
Err(format!("not a path that this injector writes: {path}").into())
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// `write NAME PATH CONTENT [--at NS]`: the file at PATH in the root of the oldest process whose name
|
|
343
|
+
/// is NAME, through /proc/PID/root. The content goes to PATH.inject first, and a rename puts it at
|
|
344
|
+
/// PATH in one step, as a program that watches the folder sees an edit. The stamps bracket the rename.
|
|
345
|
+
fn write(args: &[String]) -> Result<(), Box<dyn Error>> {
|
|
346
|
+
let name = process_name(args.get(1).ok_or("write NAME PATH CONTENT [--at NS]")?)?;
|
|
347
|
+
let path = file_path(args.get(2).ok_or("and the path in its root")?)?;
|
|
348
|
+
let content = args.get(3).ok_or("and the content")?;
|
|
349
|
+
let opts = options(&args[4..])?;
|
|
350
|
+
let pid = *processes(name)?.first().ok_or_else(|| format!("no process {name} in this process namespace"))?;
|
|
351
|
+
let target = format!("/proc/{pid}/root{path}");
|
|
352
|
+
let staged = format!("{target}.inject");
|
|
353
|
+
std::fs::write(&staged, content)?;
|
|
354
|
+
if let Some(at) = opts.at {
|
|
355
|
+
until(at)?;
|
|
356
|
+
}
|
|
357
|
+
let t0 = monotonic_ns();
|
|
358
|
+
std::fs::rename(&staged, &target)?;
|
|
359
|
+
let t1 = monotonic_ns();
|
|
360
|
+
println!("{}", json!({ "action": "write", "name": name, "pid": pid, "path": path, "bytes": content.len(), "t0": t0, "t1": t1 }));
|
|
361
|
+
Ok(())
|
|
362
|
+
}
|
|
363
|
+
|
|
326
364
|
/// a command in the Redis protocol: an array of bulk strings
|
|
327
365
|
fn resp_command(args: &[String]) -> Vec<u8> {
|
|
328
366
|
let mut out = format!("*{}\r\n", args.len()).into_bytes();
|
|
@@ -473,6 +511,9 @@ fn run() -> Result<(), Box<dyn Error>> {
|
|
|
473
511
|
if action == "signal" {
|
|
474
512
|
return signal(&args);
|
|
475
513
|
}
|
|
514
|
+
if action == "write" {
|
|
515
|
+
return write(&args);
|
|
516
|
+
}
|
|
476
517
|
if action == "resp" {
|
|
477
518
|
return resp(&args);
|
|
478
519
|
}
|
|
@@ -638,6 +679,15 @@ mod tests {
|
|
|
638
679
|
assert!(!zombie("814 (a) Z) S 1"));
|
|
639
680
|
}
|
|
640
681
|
|
|
682
|
+
#[test]
|
|
683
|
+
fn a_written_path_is_absolute_and_plain() {
|
|
684
|
+
assert!(file_path("/etc/traefik/dynamic.yml").is_ok());
|
|
685
|
+
assert!(file_path("/config/dynamic_1.yaml").is_ok());
|
|
686
|
+
for path in ["", "etc/x", "/etc/../root", "/a b", "/a;b", "/$(x)", "/a\nb"] {
|
|
687
|
+
assert!(file_path(path).is_err(), "{path:?}");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
641
691
|
#[test]
|
|
642
692
|
fn a_resp_command_is_an_array_of_bulk_strings() {
|
|
643
693
|
let args: Vec<String> = ["SENTINEL", "FAILOVER", "mymaster"].iter().map(|a| a.to_string()).collect();
|
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.11.
|
|
4
|
+
"version": "0.11.25",
|
|
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",
|