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
|
# 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.
|
# discarded the plaintext and cannot tell a database to start accepting it.
|
||||||
- mesh-provision-postgres:development
|
- mesh-provision-postgres:development
|
||||||
|
# And the proxy, which is what turns a route grant into traffic actually arriving.
|
||||||
|
- mesh-route-proxy:development
|
||||||
|
|
||||||
place:
|
place:
|
||||||
all: [host, runtime]
|
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,
|
assert.ok(!(await login(first)).ok,
|
||||||
"the password that was rotated away still authenticates, so nothing was rotated");
|
"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