events: an e2e test — an emitted event reaches the audit trail over the mesh's broker #2
@@ -199,6 +199,16 @@ export function validate(scenario: Scenario): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "uplink" is the one network name the lab itself claims, for the NAT bridge behind
|
||||||
|
// `egress: true`. A segment wearing it would be created first, as an isolated bridge — and the
|
||||||
|
// uplink code, finding a network by that name, would attach egress machines to it. No error
|
||||||
|
// anywhere, no route anywhere: a scenario key silently ignored, which is the fault this file
|
||||||
|
// exists to refuse.
|
||||||
|
if (scenario.segments["uplink"]) {
|
||||||
|
problems.push(`segment 'uplink': the name is reserved for the lab's own NAT bridge — ` +
|
||||||
|
`an egress machine would be silently attached to this segment instead of the world`);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [name, machine] of Object.entries(scenario.machines)) {
|
for (const [name, machine] of Object.entries(scenario.machines)) {
|
||||||
if (machine.at === "detached") {
|
if (machine.at === "detached") {
|
||||||
if (machine.published?.length) {
|
if (machine.published?.length) {
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ function networkUnit(wire: Wire): string {
|
|||||||
"IPv6AcceptRA=no",
|
"IPv6AcceptRA=no",
|
||||||
"",
|
"",
|
||||||
"[DHCPv4]",
|
"[DHCPv4]",
|
||||||
|
// UseDNS matters only where systemd-resolved runs; on a machine whose mesh modules own
|
||||||
|
// /etc/resolv.conf it is inert, and that inertness is load-bearing — the uplink must not
|
||||||
|
// outvote the resolver a scenario is testing.
|
||||||
// **Worse than any route the scenario states.** A machine behind a declared gateway must
|
// **Worse than any route the scenario states.** A machine behind a declared gateway must
|
||||||
// keep using it: the uplink is a way out of the scenario, not a better way around inside
|
// keep using it: the uplink is a way out of the scenario, not a better way around inside
|
||||||
// it. On-link segments win regardless, being connected routes; this only settles which
|
// it. On-link segments win regardless, being connected routes; this only settles which
|
||||||
|
|||||||
@@ -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
|
// 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
|
// verdict. The distinction failed once as an IncusError surfacing at minute four of a wait
|
||||||
// whose machine was merely slow.
|
// 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 {
|
try {
|
||||||
({ out: said, ok } = await on("anchor",
|
const asked = await on("anchor",
|
||||||
`docker exec mesh-control /mesh-control status --json`));
|
`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) {
|
} catch (err) {
|
||||||
said = (err as Error).message;
|
said = (err as Error).message;
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!state) {
|
||||||
last = said;
|
last = said;
|
||||||
await new Promise((r) => setTimeout(r, 5000));
|
await new Promise((r) => setTimeout(r, 5000));
|
||||||
continue;
|
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);
|
const bad = state.wrong.find((w) => w.node === node);
|
||||||
if (bad) {
|
if (bad) {
|
||||||
const why = [bad.refused, ...(bad.failed ?? []).map((f) => `${f.id}: ${f.error}`)]
|
const why = [bad.refused, ...(bad.failed ?? []).map((f) => `${f.id}: ${f.error}`)]
|
||||||
|
|||||||
@@ -250,3 +250,10 @@ segments: { net: { kind: public, cidr: [192.0.2.0/24] } }
|
|||||||
machines: { a: { at: detached, egress: true } }`,
|
machines: { a: { at: detached, egress: true } }`,
|
||||||
/detached but declares egress/);
|
/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/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user