diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 0e7ecee..eab9188 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -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)); +});