What review found in the lab, fixed

A segment named "uplink" is refused. The lab claims that name for the
NAT bridge behind `egress: true`, and a scenario wearing it first would
have its egress machines silently attached to an isolated bridge — a
declared key doing nothing, which is the fault this repo exists to
refuse, in the repo that refuses it.

settled() parses inside the try. A truncated status from a struggling
machine was the one shape of bad answer that still threw out of the
wait, and the likeliest moment for one is exactly the machine the poll
is watching. Malformed now counts as "could not ask", like the exec
that times out.

And a sentence on the uplink's UseDNS saying its inertness is
load-bearing: it matters only where systemd-resolved runs, and on a
machine whose modules own resolv.conf the uplink must not outvote the
resolver a scenario is testing.
This commit is contained in:
2026-09-01 21:54:59 +02:00
parent 4a343a2652
commit 0d286b7cc8
4 changed files with 35 additions and 9 deletions
+15 -9
View File
@@ -131,23 +131,29 @@ async function settled(node: string, withinMs = 240_000): Promise<void> {
// And a poll that *threw* — an exec timeout, a lost fifo — is also "could not ask", not a
// verdict. The distinction failed once as an IncusError surfacing at minute four of a wait
// whose machine was merely slow.
let said = "", ok = false;
let state: {
wrong: { node: string; outcome: string; refused?: string;
failed?: { id: string; error: string }[] }[];
waiting: { node: string; never: boolean }[];
} | undefined;
let said = "";
try {
({ out: said, ok } = await on("anchor",
`docker exec mesh-control /mesh-control status --json`));
const asked = await on("anchor",
`docker exec mesh-control /mesh-control status --json`);
said = asked.out;
// Parsed inside the try on purpose: a truncated answer from a struggling machine is the
// same fact as no answer, and the likeliest moment for one is exactly the machine this
// poll is watching.
if (asked.ok) state = JSON.parse(said);
} catch (err) {
said = (err as Error).message;
}
if (!ok) {
if (!state) {
last = said;
await new Promise((r) => setTimeout(r, 5000));
continue;
}
const state = JSON.parse(said) as {
wrong: { node: string; outcome: string; refused?: string;
failed?: { id: string; error: string }[] }[];
waiting: { node: string; never: boolean }[];
};
const bad = state.wrong.find((w) => w.node === node);
if (bad) {
const why = [bad.refused, ...(bad.failed ?? []).map((f) => `${f.id}: ${f.error}`)]
+7
View File
@@ -250,3 +250,10 @@ segments: { net: { kind: public, cidr: [192.0.2.0/24] } }
machines: { a: { at: detached, egress: true } }`,
/detached but declares egress/);
});
test("a segment may not be named 'uplink' — the lab claims that name for egress", () => {
refuses(`scenario: x
segments: { uplink: { kind: public, cidr: [192.0.2.0/24] } }
machines: { a: { at: { segment: uplink, address: [192.0.2.1] }, egress: true } }`,
/reserved for the lab's own NAT bridge/);
});