109 lines
5.3 KiB
TypeScript
109 lines
5.3 KiB
TypeScript
/**
|
|
* Research 053, question 8: the bus on signed logins (NATS operator mode), over a real network.
|
|
*
|
|
* Three machines on one segment: `a` is today's style server (static users, an account with
|
|
* JetStream), `b` and `c` are a two-server cluster in operator mode with a full resolver, and `a` is
|
|
* joined to `b` as a leaf node, the system account bridged. The measurements run in
|
|
* test/bed-programs/signed-logins (one Go program; it embeds nats-server 2.11.17, the mesh's
|
|
* version, so nothing is downloaded into the machines). It runs on `a` and reaches `b` and `c`
|
|
* across the segment:
|
|
*
|
|
* part 2 revocation: time for b and c to refuse a revoked user and close its open connection
|
|
* part 4 moving a stream and a KV bucket from a to b over the leaf link, with a writer running
|
|
* part 3 c cut off from b by a packet filter on c while a user is revoked, then restored:
|
|
* how long c still accepts the user with the periodic sync alone, and with a re-push
|
|
* triggered by the first system-account event that shows c is back
|
|
*
|
|
* The test asserts only that the measurements ran to the end; the numbers are the result and are
|
|
* printed. It proves nothing about the mesh's own software, which it does not use.
|
|
*/
|
|
|
|
import { test, before, after } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { execFileSync } from "node:child_process";
|
|
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { loadScenario } from "../../src/declaration/parse.ts";
|
|
import { raise } from "../../src/lifecycle/raise.ts";
|
|
import { destroy, exec, push } from "../../src/lifecycle/operate.ts";
|
|
import { labIsUsable } from "./harness.ts";
|
|
|
|
const capability = await labIsUsable();
|
|
const skip = capability.usable ? false : `lab not usable: ${capability.why}`;
|
|
|
|
const ADDR = { a: "192.0.2.11", b: "192.0.2.12", c: "192.0.2.13" };
|
|
let instanceId = "";
|
|
|
|
before(async () => {
|
|
if (skip) return;
|
|
instanceId = (await raise(loadScenario("scenarios/signed-logins.yml"), {})).instanceId;
|
|
});
|
|
|
|
after(async () => {
|
|
if (instanceId && !process.env["MESH_LAB_KEEP"]) await destroy(instanceId);
|
|
});
|
|
|
|
const sh = (machine: string, script: string, timeout = 120_000) =>
|
|
exec(instanceId, machine, ["sh", "-c", script], timeout);
|
|
|
|
test("research 053 q8: signed-login bus over a real network", { skip, timeout: 3_000_000 }, async () => {
|
|
const work = mkdtempSync(join(tmpdir(), "q8lab-"));
|
|
const bin = join(work, "q8lab");
|
|
execFileSync("go", ["build", "-o", bin, "."], {
|
|
cwd: "test/bed-programs/signed-logins",
|
|
env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: "amd64", GOFLAGS: "-mod=readonly" },
|
|
stdio: "inherit",
|
|
});
|
|
const conf = join(work, "conf");
|
|
mkdirSync(conf);
|
|
execFileSync(bin, ["gen", "-out", conf, "-a", ADDR.a, "-b", ADDR.b, "-c", ADDR.c], { stdio: "inherit" });
|
|
|
|
for (const m of ["a", "b", "c"]) {
|
|
await sh(m, "mkdir -p /root/q8 /var/lib/q8");
|
|
await push(instanceId, m, bin, "/root/q8/q8lab", "0755");
|
|
}
|
|
const files: Record<string, string[]> = {
|
|
a: ["A.conf", "leaf-mesh.creds", "leaf-sys.creds", "keys.json"],
|
|
b: ["B.conf", "op.jwt"],
|
|
c: ["C.conf", "op.jwt"],
|
|
};
|
|
for (const [m, list] of Object.entries(files)) {
|
|
for (const f of list) await push(instanceId, m, join(conf, f), `/root/q8/${f}`, "0600");
|
|
}
|
|
|
|
const start = (m: string, cmd: string, out: string) =>
|
|
sh(m, `setsid nohup ${cmd} >/root/q8/${out} 2>&1 </dev/null & sleep 1; echo started`);
|
|
await start("b", "/root/q8/q8lab server -conf /root/q8/B.conf", "B.out");
|
|
await start("c", "/root/q8/q8lab server -conf /root/q8/C.conf", "C.out");
|
|
for (const m of ["b", "c"]) {
|
|
const r = await sh(m, "/root/q8/q8lab wait -url http://127.0.0.1:8222/healthz -s 90");
|
|
assert.match(r.stdout, /up/, `${m} server did not come up`);
|
|
}
|
|
await start("a", "/root/q8/q8lab server -conf /root/q8/A.conf", "A.out");
|
|
assert.match((await sh("a", "/root/q8/q8lab wait -url http://127.0.0.1:8222/healthz -s 60")).stdout, /up/);
|
|
await start("c", `/root/q8/q8lab agent -peer ${ADDR.b}`, "agent.out");
|
|
console.log("servers up:", (await sh("a", "cat /root/q8/A.out")).stdout.trim());
|
|
|
|
await sh("a", "setsid nohup sh -c '/root/q8/q8lab drive -keys /root/q8/keys.json > /root/q8/drive.log 2>&1; echo finished > /root/q8/drive.done' >/dev/null 2>&1 </dev/null & sleep 1; echo started");
|
|
let offset = 0;
|
|
let finished = false;
|
|
let all = "";
|
|
for (let i = 0; i < 400 && !finished; i++) {
|
|
await new Promise((r) => setTimeout(r, 5000));
|
|
const chunk = (await sh("a", `tail -c +${offset + 1} /root/q8/drive.log`)).stdout;
|
|
offset += Buffer.byteLength(chunk);
|
|
finished = (await sh("a", "test -f /root/q8/drive.done && echo yes; true")).stdout.includes("yes");
|
|
if (chunk.trim()) { console.log(chunk.trimEnd()); all += chunk; }
|
|
}
|
|
for (const m of ["a", "b", "c"]) {
|
|
const up = m.toUpperCase();
|
|
const r = await sh(m, `grep -c '\\[ERR\\]' /var/log/q8-${up}.log; grep '\\[ERR\\]' /var/log/q8-${up}.log | cut -c1-220 | sort | uniq -c | sort -rn | head -8; true`);
|
|
console.log(`--- server ${up} log, error lines:\n${r.stdout.trim()}`);
|
|
}
|
|
writeFileSync(join(work, "drive.log"), all);
|
|
assert.ok(finished, "the measurements did not finish");
|
|
assert.match(all, /DRIVE-DONE/);
|
|
assert.doesNotMatch(all, /panic:/);
|
|
});
|