Commit Graph
37 Commits
Author SHA1 Message Date
evangelostsak 1343f77929 refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_write.py 2026-10-16 19:10:01 +02:00
evangelostsak 277a2aad76 refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_transport.py 2026-10-16 19:10:01 +02:00
evangelostsak 7c85cedc8e refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_plan.py 2026-10-16 19:10:01 +02:00
evangelostsak 814fc8b336 refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_inventory.py 2026-10-16 19:10:01 +02:00
evangelostsak a88f69bd35 refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_full_mesh.py 2026-10-16 19:10:01 +02:00
evangelostsak c95e5d06c4 refactor: drop rationale and narration from tests/unit/python/cli/administration/inventory/mesh/test_cli.py 2026-10-16 19:10:01 +02:00
evangelostsak 617879f680 test(mesh-cli): full meshes peer every member with every other
Covers the declarations read from every host, the refusal of differing ones,
every peer pinned to its /32, no hub anywhere, the host of one mesh holding no
peer of the other, and a second run leaving every file alone.
2026-10-16 03:40:15 +02:00
evangelostsak f534c973a7 test(mesh-cli): an inventory without a hub host has no hub 2026-10-16 03:40:15 +02:00
evangelostsak b1279525bf test(mesh): the transport switch under a baked layout 2026-10-14 15:03:41 +02:00
evangelostsak 56ce03d370 test(mesh): the CLI reads its meshes from host_vars
Mesh names are asserted structurally: the baked layout names every mesh in
every file, so a substring match would pass on a host holding no interface.
2026-10-14 15:03:41 +02:00
evangelostsak a0a7436d19 test(mesh): the writer under a layout baked into host_vars 2026-10-14 15:03:41 +02:00
evangelostsak 54e237bddb test(mesh): plan against a topology instead of raw groups 2026-10-14 15:03:41 +02:00
evangelostsak e30b17276c test(mesh): declarations, pool and membership read from host_vars
Covers per-role and per-host membership, a host two roles put into one mesh,
the hub never being a spoke, an undeclared mesh being refused, and the
example's members matching its table.
2026-10-14 15:03:41 +02:00
evangelostsak 31cf7362d5 test(mesh): remove the contract tests with the contract 2026-10-14 15:03:41 +02:00
evangelostsak e9dbab5835 test(mesh): the contract shape carries the primary flag 2026-10-14 11:23:23 +02:00
evangelostsak 05dc4af041 test(mesh): the controller joins the first declared mesh by default 2026-10-14 11:23:23 +02:00
evangelostsak a521937794 test(mesh): no two interfaces of a host route one prefix
Covers the swarm lab under the three-mesh example, where the NFS server is a
spoke of two meshes, and generated topologies of one to eight meshes.
2026-10-14 11:23:23 +02:00
evangelostsak 06cc872c1c test(mesh): pin how the primary mesh is chosen 2026-10-14 11:23:23 +02:00
evangelostsak e043ad0c00 test(mesh): zip the planned meshes strictly against their specs
The any-number-of-meshes test zipped the specs against the planned meshes
without strict=, which ruff reports as B905 and the lint-python job failed on.
Both sequences come from the same declarations, so strict=True also turns a
plan that drops a mesh into a failure instead of a silently shorter loop.
2026-10-14 00:27:17 +02:00
evangelostsak b777b65e84 test(mesh): hold the planning guarantees at any mesh count
The guarantees were asserted against the declared swarm and data pair,
so they proved two meshes work rather than that the planner works. A
deployment declares its own, and the review asks for any number of them with
the nodes arranged however the client wants.

The same invariants now run over generated topologies: one to eight meshes,
three arrangements each, hosts dealt out so some belong to several meshes and
some to none. 2247 subtests, four seconds, and the planner needed no change to
pass them.
2026-10-13 19:36:43 +02:00
evangelostsak ff1db88753 revert(swarm-test): remove the SSH-on-underlay measurement arm
The arm existed to answer one question: how much of the meshed arm's
cost is the connection plugin and how much is the tunnel. Runs 37592061960
(docker), 37753506661 (SSH on the underlay) and 37621556682 (SSH over
WireGuard) answered it on web-app-gitea#0, debian/ext4:

  pass 1  5964s / 8511s / 8832s
  pass 2  5319s / 7472s / 7705s

Leaving ansible_connection: docker costs +4700s, the tunnel +554s - 89.5%
against 10.5%. The answer is recorded; the scaffolding does not need to ship.

Reverts d792519725, 1b28f87b37, 89b8a8c8d1, 0470491e1a, 529c5ace43,
dd3d10a9a2, c20dd0e5f5, 458d8956e1, 63ade1e03b, d281555945 and 4adfb6e3d2.
95cfc8b7b7 is already superseded by bbfd82f89, which sets the profiling limit
upstream.
2026-10-09 12:12:03 +02:00
evangelostsak 4adfb6e3d8 test(mesh-cli): cover the addresses the transport dials
One test asserts ansible_host lands on the given address, the other
that a missing member raises. Either would have caught the UNREACHABLE run
before it was dispatched.
2026-10-08 10:58:23 +02:00
evangelostsak 25c7fdd1a5 test(mesh): pin the switched host to a tty-free transport
The carriage returns a pty adds are invisible until a role parses its own
output, so the absence of the tty is asserted where the transport is
written rather than left to the next role that reads stdout.
2026-10-03 23:20:26 +02:00
evangelostsak 152588b511 perf(mesh): retune the multiplexer for the hosts the mesh switches
The pass that runs over the mesh reaches every host by SSH where the
pass before it used the container connection, and it pays that difference
once per task. Measured in a container: an exec through a warm multiplexer
costs 7ms, a fresh connection 85ms, and 165ms once sudo asks for the
password every task demands. The pass runs thousands of tasks.

Two settings threw the multiplexer away. A persist window of 20s closes the
master whenever a host waits longer than that for its next task, which in a
linear play is most of them; and the named ControlPath points at a directory
nothing creates, so a master that cannot open its socket reconnects per task
without reporting it.

Neither is retuned for the repository. The tool that moves a host onto the
mesh now writes that host's own ssh_args, derived from the repository's
value so every other option is kept and only the window and the socket
differ. A host the mesh never touches keeps exactly what it had.

Confirmed against the built command: a switched host gets ControlPersist=600s
and a ControlPath under the directory Ansible creates, while a host beside it
keeps the repository's own.
2026-10-02 16:35:29 +02:00
evangelostsak dc14e8a3a8 fix(mesh): keep every member on the address it already holds
The offset came from the spoke's position in a sorted list, so a host that
sorts earlier renumbered every member behind it. `wg syncconf` carries keys
and peers onto a live interface and never its address, so a renumbered member
keeps answering on the old address while the peers and the inventory have
moved to the new one -- and the address it gave up can be handed to a new
member while the old owner still holds it.

The planner now reuses the addresses already on disk and allocates only an
unseen member, from the lowest free offset. A value outside the subnet, a
duplicate, and the hub's own offset are refused, so a mirrored host_vars
cannot hand a spoke the hub's address. Rotation keeps addresses: it replaces
identities, not addresses.

The routing assertions re-planned the mesh and compared the result with
itself, so a writer that stored the mesh subnet in place of the routed range
would have passed; they now read the entry off disk, which is what the role
reads.
2026-10-02 00:54:20 +02:00
evangelostsak 94f2401fab test(mesh): pin one key per host across a rotation
Asserts the hub's two mesh entries carry the same key and that its spoke holds
it. A first attempt matched only the first entry and passed against the bug.
2026-09-29 08:59:23 +02:00
evangelostsak 7634c1ed32 test(mesh): pin the controller's key across a rerun
Without the fix its public half changes on every write, which is what made
the hub re-render and re-key a tunnel nothing had asked to rotate.
2026-09-28 15:44:30 +02:00
evangelostsak fbf17949d7 test(mesh): cover the endpoint argument
A stated address replaces the name peers dial, an unstated host keeps its
name, and a malformed pair aborts rather than being ignored.
2026-09-27 02:05:00 +02:00
evangelostsak a868045d69 test(mesh): cover the controller in the foreign-entry prune
Mirrors the hub's host_vars onto the controller the way the credential reset
does, then asserts the rerun leaves only the mesh the controller belongs to.
Without the fix the file keeps the hub's data entry, is_hub included.
2026-09-26 19:47:10 +02:00
evangelostsak 5ae72d3b88 test(mesh): cover the controller spoke and the transport switch
Pins the two failure modes the design turns on: a host with no mesh address is
left reachable rather than stranded, and an address is only read from the host
that owns it, because the swarm reset mirrors one node's host_vars over every
other.
2026-09-26 01:27:07 +02:00
evangelostsak 74bdfbae9c test(inventory): pin pruning, and assert mesh membership structurally
Adds the mirror case pruning has to survive and its inverse, that an owned mesh
is left untouched.

The existing assertions searched the file for 'swarm:' and 'data:', which the
credential keys mesh_private_key_swarm and mesh_private_key_data also match, so
they passed against a file whose mesh block was empty. They now read the mesh
names out of the parsed document.
2026-09-19 12:07:43 +02:00
evangelostsak 82c5d00c71 test(inventory): pin that a mirrored host_vars is reminted, not adopted
The failure this covers is silent in the inventory and total on the cluster:
every node adopts the hub's key and address, every interface claims 10.100.0.1,
and nothing handshakes. Asserts the mirrored hosts read as unkeyed and that a
rewrite gives the three swarm members three distinct addresses again.
2026-09-19 11:16:59 +02:00
evangelostsak 160b0eaff7 fix(inventory): route the whole pool through the hub from a spoke
A spoke's AllowedIPs for the hub was its own mesh subnet, so a worker on
10.100.0.0/24 had no route to the NFS server on 10.100.1.11 and could not
reach the plane it is deliberately not a member of -- which is the entire
reason the hub sits in both.

The symptom would have been misleading: every tunnel reports a healthy
handshake, the gate passes because it only pings peers the host is configured
for, and the failure surfaces later as an NFS mount that hangs. It points at
storage rather than at routing.

Spokes now route the declared pool through the hub while the hub keeps each
spoke pinned to a single address; routing the pool in both directions would
blackhole every member behind whichever spoke came first.

Found by running the writer against the prefixed five-host topology before the
deploy rather than during it.
2026-09-18 16:37:18 +02:00
evangelostsak 0b8f8ac46f test(inventory): exercise the mesh CLI as the swarm routine invokes it
Covers the entry point rather than the library: one invocation writes every
member across both inventory files, a missing host_vars directory fails instead
of silently writing nothing, and a rerun is a no-op end to end.
2026-09-18 09:51:17 +02:00
evangelostsak 33dbd1ed8c test(inventory): pin what the mesh writer may put on disk
Asserted against real files in a tempdir, because the guarantees are about
files: which host_vars a secret may appear in, and whether a second run of an
unchanged mesh leaves them alone.

Two cases carry the security argument. A private key must reach exactly one
host_vars file, which is what keeps one host's inventory from impersonating
another, and it must never appear there in plaintext. A third covers the
inverse of the idempotence shortcut: a public key whose credential has gone
missing is worse than no key, because peers accept it while its owner cannot
use it, so that member is reported unkeyed and the pair is reminted together.

A worker is asserted NOT to receive the data mesh. Carrying it would peer the
worker directly with the NFS server and bypass the routing through the hub that
this topology depends on.
2026-09-18 09:29:35 +02:00
evangelostsak 10f0b94877 test(inventory): pin mesh planning, which fails silently when wrong
Every failure mode here is invisible at deploy time. A spoke missing from the
hub's peer list configures cleanly and never handshakes. A public key that does
not answer for its private half looks identical until traffic is attempted. A
partial rotation leaves the hub holding an old key while the spoke presents a
new one, and the handshake is refused with nothing logged on either side.

The load-bearing case is the hub filter: the manager is a member of
svc-swarm-node as well as the hub group, so left unfiltered it peers with
itself, which WireGuard accepts and which then routes the hub's own traffic
into its own tunnel.

The subnet guards are asserted against the shipped declaration too, so the
configuration proves its own collision checks rather than only the synthetic
ones.
2026-09-18 09:29:16 +02:00
evangelostsak c629fce2b7 test(inventory): add the mesh test package root
Resolves PROJECT_ROOT for the mesh suites, which read the shipped mesh
declaration rather than restating it.
2026-09-18 09:27:31 +02:00