events: an e2e test — an emitted event reaches the audit trail over the mesh's broker #2
@@ -34,6 +34,8 @@ images:
|
||||
# And the provisioner, which is what makes a sealed credential true on a machine — the mesh
|
||||
# discarded the plaintext and cannot tell a database to start accepting it.
|
||||
- mesh-provision-postgres:development
|
||||
# And the proxy, which is what turns a route grant into traffic actually arriving.
|
||||
- mesh-route-proxy:development
|
||||
|
||||
place:
|
||||
all: [host, runtime]
|
||||
|
||||
@@ -851,3 +851,89 @@ test("rotating a credential moves both ends, and the old one stops working", {
|
||||
assert.ok(!(await login(first)).ok,
|
||||
"the password that was rotated away still authenticates, so nothing was rotated");
|
||||
});
|
||||
|
||||
test("a route is a grant: a workload is reached by the name it asked for", {
|
||||
skip, timeout: 900_000,
|
||||
}, async () => {
|
||||
// novox/hq 08-connectivity §3. The mirror of a database grant: there the consumer supplies a
|
||||
// name and receives credentials; here it supplies a target and receives a name. Nothing new in
|
||||
// the vocabulary — a route is a provision like any other.
|
||||
//
|
||||
// The workload is the registry image, because it is an HTTP server this scenario already has.
|
||||
// What is being tested is the mesh's arrangement, not the workload.
|
||||
await must("anchor", `printf %s '{"module":"frontdoor","version":"1",` +
|
||||
`"provides":[{"name":"route","scope":"mesh"}],` +
|
||||
`"capabilities":["container-runtime"],` +
|
||||
`"receives":{"route":"/etc/frontdoor/routes.json"},` +
|
||||
`"serves":{"route":{"domain":"mesh.test"}},` +
|
||||
`"listens":[{"port":8081,"from":"mesh","why":"the front door"}],` +
|
||||
`"resources":[{"id":"dir","type":"directory","path":"/etc/frontdoor","mode":"0755"},` +
|
||||
`{"id":"proxy","type":"container","name":"front-door",` +
|
||||
`"image":"${pinned("mesh-route-proxy")}","network":"host",` +
|
||||
`"volumes":["/etc/frontdoor:/etc/frontdoor:ro"],` +
|
||||
`"env":{"ROUTES":"/etc/frontdoor/routes.json","LISTEN":":8081"}}]}' ` +
|
||||
`> /tmp/frontdoor.json`);
|
||||
// The workload declares the port it listens on as well as the route it wants. Both, because
|
||||
// they are different questions: one says who may reach it, the other says by what name — and
|
||||
// the earlier test left this machine filtering, so a module that asked for a route and not for
|
||||
// the port would be unreachable by the proxy it just asked for.
|
||||
await must("anchor", `printf %s '{"module":"storefront","version":"1",` +
|
||||
`"requires":["route"],"capabilities":["container-runtime"],` +
|
||||
`"contributes":{"route":{"name":"shop.mesh.test","port":8088}},` +
|
||||
`"binds":{"route":"/etc/storefront/route.json"},` +
|
||||
`"listens":[{"port":8088,"from":"mesh","why":"the proxy reaches it here"}],` +
|
||||
`"resources":[{"id":"dir","type":"directory","path":"/etc/storefront","mode":"0755"},` +
|
||||
`{"id":"app","type":"container","name":"storefront",` +
|
||||
`"image":"${pinned("registry")}","ports":["8088:5000"]}]}' > /tmp/storefront.json`);
|
||||
for (const f of ["frontdoor", "storefront"]) {
|
||||
await must("anchor", `docker cp /tmp/${f}.json mesh-control:/${f}.json`);
|
||||
await mesh(`module add /${f}.json`);
|
||||
}
|
||||
await mesh("assign anchor frontdoor");
|
||||
await mesh("assign laptop storefront");
|
||||
await mesh("push");
|
||||
await new Promise((r) => setTimeout(r, 25_000));
|
||||
|
||||
// The provider was told who asked, and where that machine is — which it needs in order to
|
||||
// reach back, and which it must not have to derive from a naming convention.
|
||||
const routes = await must("anchor", `cat /etc/frontdoor/routes.json`);
|
||||
assert.match(routes, /shop\.mesh\.test/, `the proxy was not told about the route:\n${routes}`);
|
||||
assert.match(routes, /"at": *"laptop\.internal"/,
|
||||
`the proxy was not told where the consumer is, so it cannot reach it:\n${routes}`);
|
||||
|
||||
// And the consumer was told what the provider serves, which is how it knows its own name.
|
||||
const bound = await must("laptop", `cat /etc/storefront/route.json`);
|
||||
assert.match(bound, /mesh\.test/, `the consumer was not told the public name:\n${bound}`);
|
||||
|
||||
// The whole point: a request for the name reaches the workload, across the private network.
|
||||
let reached = false;
|
||||
let said = "";
|
||||
for (let i = 0; i < 20 && !reached; i++) {
|
||||
const answer = await on("anchor",
|
||||
`curl -sf -H 'Host: shop.mesh.test' http://127.0.0.1:8081/v2/ -o /dev/null -w '%{http_code}'`);
|
||||
said = answer.out;
|
||||
reached = answer.ok && said.trim() === "200";
|
||||
if (!reached) await new Promise((r) => setTimeout(r, 4000));
|
||||
}
|
||||
assert.ok(reached, `a request for the name did not reach the workload (${said}):\n` +
|
||||
`${(await on("anchor", `docker logs front-door 2>&1 | tail -20`)).out}`);
|
||||
|
||||
// Withdrawal, which 08-connectivity lists as open: a stale public name pointing at nothing
|
||||
// fails more visibly than a stale grant, so it must not survive the module leaving.
|
||||
await mesh("unassign laptop storefront");
|
||||
await mesh("push");
|
||||
await new Promise((r) => setTimeout(r, 20_000));
|
||||
|
||||
const after = await must("anchor", `cat /etc/frontdoor/routes.json`);
|
||||
assert.doesNotMatch(after, /shop\.mesh\.test/,
|
||||
`the route outlived the module that asked for it:\n${after}`);
|
||||
|
||||
let gone = false;
|
||||
for (let i = 0; i < 15 && !gone; i++) {
|
||||
const answer = await on("anchor",
|
||||
`curl -s -H 'Host: shop.mesh.test' http://127.0.0.1:8081/v2/ -o /dev/null -w '%{http_code}'`);
|
||||
gone = answer.out.trim() === "404";
|
||||
if (!gone) await new Promise((r) => setTimeout(r, 3000));
|
||||
}
|
||||
assert.ok(gone, "the proxy still serves a name whose module was unassigned");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user