Let a caller say how long to wait, and note where the artifact-store test
went `exec` waited two minutes always. A build, or anything that waits on another machine, needs longer — and a caller that cannot say so has to split the work to fit, which is a test shaped by its harness rather than by what it is testing. The scenario also places a build machine when one is given, so anything in it can ask the mesh to build something. Nothing else here would start one. And the mesh-runs-its-own-artifact-store test is not here. It needs a fourth image so the module has a registry to mirror, and that is caught behind 04-ISSUES/012 — left as a note saying where it went and why, rather than silently deleted, because what it asserted is worth asserting.
This commit is contained in:
@@ -54,12 +54,18 @@ export async function exec(
|
|||||||
instanceId: string,
|
instanceId: string,
|
||||||
machine: string,
|
machine: string,
|
||||||
command: 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 }> {
|
): Promise<{ stdout: string; stderr: string }> {
|
||||||
const found = (await taggedInstances()).find(
|
const found = (await taggedInstances()).find(
|
||||||
(i) => i.instanceId === instanceId && i.machine === machine,
|
(i) => i.instanceId === instanceId && i.machine === machine,
|
||||||
);
|
);
|
||||||
const name = found?.name ?? machineName(instanceId, 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 { destroy, exec } from "../../src/lifecycle/operate.ts";
|
||||||
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
||||||
import { labIsUsable, destroyAll } from "./harness.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 capability = await labIsUsable();
|
||||||
const binary = hostBinaryPath();
|
const binary = hostBinaryPath();
|
||||||
const bundle = process.env["MESH_LAB_BUNDLE"] ?? "";
|
const bundle = process.env["MESH_LAB_BUNDLE"] ?? "";
|
||||||
|
const builder = process.env["MESH_LAB_BUILDER"] ?? "";
|
||||||
|
|
||||||
const skip = !capability.usable
|
const skip = !capability.usable
|
||||||
? `lab not usable: ${capability.why}`
|
? `lab not usable: ${capability.why}`
|
||||||
@@ -39,28 +42,30 @@ const skip = !capability.usable
|
|||||||
|
|
||||||
const SCENARIO = "two-nodes";
|
const SCENARIO = "two-nodes";
|
||||||
let instanceId = "";
|
let instanceId = "";
|
||||||
|
/** The scenario's own registry, which serves the images a module may mirror. */
|
||||||
|
let registry = "";
|
||||||
|
|
||||||
function quote(s: string): string {
|
function quote(s: string): string {
|
||||||
return `'${s.replaceAll("'", `'\\''`)}'`;
|
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, [
|
const { stdout } = await exec(instanceId, machine, [
|
||||||
"sh", "-c", `${command} 2>&1; echo "__exit=$?"`,
|
"sh", "-c", `${command} 2>&1; echo "__exit=$?"`,
|
||||||
]);
|
], timeoutMs);
|
||||||
const marker = stdout.lastIndexOf("__exit=");
|
const marker = stdout.lastIndexOf("__exit=");
|
||||||
return { out: stdout.slice(0, marker), ok: Number(stdout.slice(marker + 7).trim()) === 0 };
|
return { out: stdout.slice(0, marker), ok: Number(stdout.slice(marker + 7).trim()) === 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function must(machine: string, command: string): Promise<string> {
|
async function must(machine: string, command: string, timeoutMs?: number): Promise<string> {
|
||||||
const { out, ok } = await on(machine, command);
|
const { out, ok } = await on(machine, command, timeoutMs);
|
||||||
if (!ok) throw new Error(`${machine}: ${command}\n${out}`);
|
if (!ok) throw new Error(`${machine}: ${command}\n${out}`);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The control plane, which runs in a container on the first node. */
|
/** The control plane, which runs in a container on the first node. */
|
||||||
async function mesh(command: string): Promise<string> {
|
async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||||
return must("anchor", `docker exec mesh-control /mesh-control ${command}`);
|
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.
|
// 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", `cat > /tmp/substrate.lock <<'MESHBUNDLE'\n${bundleFor(raised.images)}\nMESHBUNDLE`);
|
||||||
await must("anchor", `${HOST_PATH} apply /tmp/substrate.lock`);
|
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 });
|
}, { timeout: 1_800_000 });
|
||||||
|
|
||||||
after(async () => {
|
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.
|
// 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/);
|
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