Prove every name under a machine resolves to that machine

The mesh's half: the data is right, complete on every machine, and agrees with
the hosts file — two accounts of where a machine is, disagreeing, would be
worse than either alone, and this is the one place they could drift because
they are generated separately.

And it follows the machines: a node that leaves the private network stops being
answered for, because a wildcard pointing at nothing resolves and then hangs,
where an unresolvable name fails at once and says which name it was.
This commit is contained in:
2026-08-31 11:40:19 +02:00
parent 8bc5f498cd
commit cce39a6ba3
+54
View File
@@ -1304,3 +1304,57 @@ test("a container reaches another machine by the name the mesh gave it", {
await mesh("unassign laptop resolves");
await mesh("push laptop");
});
test("every name under a machine resolves to that machine", {
skip, timeout: 900_000,
}, async () => {
// Services are named under the machine they run on — postgres.novox.internal,
// plex.ace.internal. The first label is the service and the rest is the node, so what must
// resolve is anything under a node's name. What routes it once it arrives is a proxy's, and
// stays separate.
//
// The mesh writes the data and runs no daemon: a resolver is third-party software, and the
// mesh has no business choosing one. So what is checked here is the mesh's half — that the
// data is right, complete, and follows the machines.
await mesh("assign anchor mesh-resolver");
await mesh("assign laptop mesh-resolver");
await mesh("push");
await new Promise((r) => setTimeout(r, 15_000));
for (const machine of ["anchor", "laptop"]) {
const written = await must(machine, `cat /etc/mesh-resolver/nodes.conf`);
// A wildcard per machine, matching the name and everything under it. Both machines get the
// whole mesh: a node resolves every other node, and itself.
for (const node of ["anchor", "laptop"]) {
assert.match(written, new RegExp(`address=/${node}\\.internal/10\\.42\\.0\\.\\d+`),
`${machine} cannot resolve names under ${node}:\n${written}`);
}
// And the addresses agree with what the machine's own hosts file says. Two accounts of where
// a machine is, disagreeing, would be worse than either alone — and this is the one place
// they could drift, because they are generated separately.
const hosts = await must(machine, `getent hosts anchor.internal | head -1 | cut -d' ' -f1`);
assert.match(written, new RegExp(`address=/anchor\\.internal/${hosts.trim().replace(/\./g, "\\.")}`),
`the resolver data and the hosts file disagree about where anchor is:\n${written}`);
}
// It follows the machines. A node leaving the private network must stop being answered for,
// because a wildcard pointing at nothing resolves and then hangs — where an unresolvable name
// fails at once and says which name it was.
await mesh("unassign laptop networking");
await mesh("push anchor");
await new Promise((r) => setTimeout(r, 15_000));
const after = await must("anchor", `cat /etc/mesh-resolver/nodes.conf`);
assert.doesNotMatch(after, /address=\/laptop\.internal\//,
`a machine that left the private network is still answered for:\n${after}`);
assert.match(after, /address=\/anchor\.internal\//,
`the machine that stayed lost its own name:\n${after}`);
await mesh("assign laptop networking");
await mesh("unassign anchor mesh-resolver");
await mesh("unassign laptop mesh-resolver");
await mesh("push");
await new Promise((r) => setTimeout(r, 15_000));
});