Two different things were both written `requires`. A shell, a display
server and a private network have to be on the machine that needs them.
A database does not — it runs somewhere and is reached over the network.
Both were answered the same way, so requiring a database installed
PostgreSQL on every machine that ran a web application.
What a module provides now carries a scope, the same idea claims already
use, written short in the ordinary case:
"provides": ["shell"]
"provides": [{"name": "database", "scope": "mesh"}]
A mesh-scoped requirement is answered by finding the node already running
it — never by installing it here. Choosing a machine to put a database on
is a decision with consequences, and nothing resolving a web application
should make it silently. With nothing anywhere it refuses and says which
module to assign; with two it refuses and says how to choose.
Choosing is `pin <node> <provision> <from>`, kept per node because that
is the granularity the choice has. A pin at a machine that does not
provide it refuses rather than falling back — a fallback would quietly
move somebody's data. One provider does not overrule a pin either.
Resolving a node now needs to know what the others offer, and working
that out needs them resolved, so it is two passes: the first answers only
what each node offers, the second answers everything. Nothing is ever
declared from the first.
A node's plan says what it takes from elsewhere. It is the only part of a
set that stops working when a different machine goes away, and nothing
else in that output would have said so. It is also where a credential
will hang once there is a mechanism for handing one back.
One test found passing for the wrong reason: it read pins through a join
on the provider, which hides a dangling row whether or not it was cleaned
up. It counts rows now, and bites when the cascade is removed.
27 lines
1.4 KiB
SQL
27 lines
1.4 KiB
SQL
-- Which node a machine gets a provision from, when more than one could answer.
|
|
--
|
|
-- One database in a mesh needs no such record: there is one answer and the mesh takes it. Several
|
|
-- is entirely ordinary, and then picking one is a choice with consequences -- somebody's data
|
|
-- lands on the machine that was chosen -- so the mesh refuses to guess and this is where the
|
|
-- answer is kept once a person gives it.
|
|
--
|
|
-- Per node rather than per mesh, because that is the granularity the choice actually has: two
|
|
-- machines may reasonably use two different databases, and a mesh-wide answer could not say so.
|
|
|
|
create table provision_pin (
|
|
node uuid not null references node(id) on delete cascade,
|
|
-- The provision as required -- `database`, not `postgres`. What is being chosen is which node
|
|
-- answers a requirement, and the module answering it may change without the choice changing.
|
|
name text not null,
|
|
-- The node it comes from. Not a module: the same module on two machines is two answers, and
|
|
-- which machine is the whole question.
|
|
provider uuid not null references node(id) on delete cascade,
|
|
pinned_at timestamptz not null default now(),
|
|
|
|
primary key (node, name)
|
|
);
|
|
|
|
-- A pin naming a node that leaves the mesh goes with it. The alternative is a machine pointed at
|
|
-- something that no longer exists, reported as configured.
|
|
create index provision_pin_provider on provision_pin (provider);
|