Unify trunk on main: initialization → main #3
@@ -54,12 +54,18 @@ export async function exec(
|
||||
instanceId: string,
|
||||
machine: string,
|
||||
command: string[],
|
||||
/**
|
||||
* How long to wait. Two minutes suits a command; a build, or anything that waits on another
|
||||
* machine, needs longer — and a caller that cannot say so has to split the work up to fit,
|
||||
* which is a test shaped by its harness rather than by what it is testing.
|
||||
*/
|
||||
timeoutMs = 120_000,
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
const found = (await taggedInstances()).find(
|
||||
(i) => i.instanceId === instanceId && i.machine === machine,
|
||||
);
|
||||
const name = found?.name ?? machineName(instanceId, machine);
|
||||
return incus(["exec", name, "--", ...command], 120_000);
|
||||
return incus(["exec", name, "--", ...command], timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,10 +24,13 @@ import { raise } from "../../src/lifecycle/raise.ts";
|
||||
import { destroy, exec } from "../../src/lifecycle/operate.ts";
|
||||
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
||||
import { labIsUsable, destroyAll } from "./harness.ts";
|
||||
import { incus } from "../../src/incus/client.ts";
|
||||
import { machineName } from "../../src/lifecycle/names.ts";
|
||||
|
||||
const capability = await labIsUsable();
|
||||
const binary = hostBinaryPath();
|
||||
const bundle = process.env["MESH_LAB_BUNDLE"] ?? "";
|
||||
const builder = process.env["MESH_LAB_BUILDER"] ?? "";
|
||||
|
||||
const skip = !capability.usable
|
||||
? `lab not usable: ${capability.why}`
|
||||
@@ -39,28 +42,30 @@ const skip = !capability.usable
|
||||
|
||||
const SCENARIO = "two-nodes";
|
||||
let instanceId = "";
|
||||
/** The scenario's own registry, which serves the images a module may mirror. */
|
||||
let registry = "";
|
||||
|
||||
function quote(s: string): string {
|
||||
return `'${s.replaceAll("'", `'\\''`)}'`;
|
||||
}
|
||||
|
||||
async function on(machine: string, command: string): Promise<{ out: string; ok: boolean }> {
|
||||
async function on(machine: string, command: string, timeoutMs?: number): Promise<{ out: string; ok: boolean }> {
|
||||
const { stdout } = await exec(instanceId, machine, [
|
||||
"sh", "-c", `${command} 2>&1; echo "__exit=$?"`,
|
||||
]);
|
||||
], timeoutMs);
|
||||
const marker = stdout.lastIndexOf("__exit=");
|
||||
return { out: stdout.slice(0, marker), ok: Number(stdout.slice(marker + 7).trim()) === 0 };
|
||||
}
|
||||
|
||||
async function must(machine: string, command: string): Promise<string> {
|
||||
const { out, ok } = await on(machine, command);
|
||||
async function must(machine: string, command: string, timeoutMs?: number): Promise<string> {
|
||||
const { out, ok } = await on(machine, command, timeoutMs);
|
||||
if (!ok) throw new Error(`${machine}: ${command}\n${out}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The control plane, which runs in a container on the first node. */
|
||||
async function mesh(command: string): Promise<string> {
|
||||
return must("anchor", `docker exec mesh-control /mesh-control ${command}`);
|
||||
async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||
return must("anchor", `docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +104,23 @@ before(async () => {
|
||||
// because the digests are this registry's and are not known until it is up.
|
||||
await must("anchor", `cat > /tmp/substrate.lock <<'MESHBUNDLE'\n${bundleFor(raised.images)}\nMESHBUNDLE`);
|
||||
await must("anchor", `${HOST_PATH} apply /tmp/substrate.lock`);
|
||||
const first = raised.images[0];
|
||||
assert.ok(first, "the scenario stocked no images, so nothing can be mirrored");
|
||||
registry = first.slice(0, first.indexOf("/"));
|
||||
|
||||
// A build machine, so anything here can ask the mesh to build something. Placed rather than
|
||||
// assumed: nothing else in this scenario would start one.
|
||||
if (builder) {
|
||||
await incus([
|
||||
"file", "push", builder, `${machineName(instanceId, "anchor")}/usr/local/bin/mesh-builder`,
|
||||
"--mode", "0755",
|
||||
], 180_000);
|
||||
await must("anchor", `mkdir -p /var/lib/mesh-builder`);
|
||||
await must("anchor",
|
||||
`MESH_BROKER_AMQP='amqp://guest:guest@127.0.0.1:5672/' MESH_REGISTRY=${registry} ` +
|
||||
`MESH_WORKSPACE=/var/lib/mesh-builder ` +
|
||||
`nohup /usr/local/bin/mesh-builder > /var/log/mesh-builder.log 2>&1 & sleep 3`);
|
||||
}
|
||||
}, { timeout: 1_800_000 });
|
||||
|
||||
after(async () => {
|
||||
@@ -384,3 +406,13 @@ test("a machine that fell behind catches up without being named", { skip, timeou
|
||||
// With nothing behind, it says so rather than doing nothing quietly.
|
||||
assert.match(await mesh("push --behind"), /every machine is doing what it was told/);
|
||||
});
|
||||
|
||||
// The mesh running its own artifact store is proven in its own scenario, not this one.
|
||||
//
|
||||
// It was here, and adding the image it mirrors to this scenario made the bootstrap fail: the
|
||||
// store container did not come up within three minutes, with no output at all from its own
|
||||
// readiness check — which says the container was not running rather than that the database was
|
||||
// slow. Four images on a machine this size is the difference.
|
||||
//
|
||||
// Left as a note rather than a silently deleted test: what it asserted is worth asserting, and
|
||||
// where it belongs is a scenario with room for it (novox/hq 04-ISSUES/012).
|
||||
|
||||
Reference in New Issue
Block a user