mesh/delivery-group group feat/sdk1 rejected: composed together: the checker's verdict at the members' heads (428d048dd50a): unit: git.novox.be/novox/mesh-sdk/…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-sdk/go/process.TestStatePutWritesAKeyWhole: a Put through a short blackout: 0, mesh-mail's state send…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
100 lines
4.1 KiB
Go
100 lines
4.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// The mesh has two resolvers (novox/hq ADR 0223): `mesh-dns-resolver` is replicated, held on the
|
|
// anchor and on the home server, each answering the same names; every machine's resolver file lists
|
|
// every holder — its own first when it is one — and no public resolver. ADR 0196 listed the mesh's
|
|
// resolver then a public one, and musl asks both at once and takes the first reply: from the home
|
|
// server the public "no such name" for the anchor's mesh name won, every time, in every Alpine build.
|
|
// The file is written by the module holding the machine's uplink (ADR 0223 part 2).
|
|
|
|
// resolverMachines is the anchor and the home server holding the resolver, and a laptop holding nothing.
|
|
var resolverMachines = map[string]string{
|
|
"anchor.internal": "10.42.0.1", "laptop.internal": "10.42.0.2", "home.internal": "10.42.0.3"}
|
|
|
|
// bothResolvers is the two holders on record, as `seat mesh-dns-resolver --add` leaves them.
|
|
var bothResolvers = []Held{
|
|
{Claim: "mesh-dns-resolver", Scope: ScopeMesh, Node: "anchor", Module: "dnsmasq"},
|
|
{Claim: "mesh-dns-resolver", Scope: ScopeMesh, Node: "home", Module: "dnsmasq"},
|
|
}
|
|
|
|
// uplinks is every module in the catalogue holding node-uplink, and so writing the machine's resolver
|
|
// file (novox/hq ADR 0223 part 2): the program that would otherwise rewrite it is the one that writes it.
|
|
// dhcpcd's left the catalogue with ADR 0226, held by no machine.
|
|
var uplinks = []string{"networkmanager", "systemd-networkd"}
|
|
|
|
// managing is a machine as each uplink module needs it: able to install, run a service and run the
|
|
// manager that module is for.
|
|
func managing(node string) Node {
|
|
caps := map[string]bool{"package-manager": true, "service-manager": true}
|
|
for _, u := range uplinks {
|
|
caps["uplink-"+u] = true
|
|
}
|
|
return Node{Name: node, At: node + ".internal", Capabilities: caps}
|
|
}
|
|
|
|
// twoResolverShelf is the resolver, the uplink modules that write what a machine asks, and a stand-in
|
|
// answering `mesh-addressing`.
|
|
func twoResolverShelf(t *testing.T) map[string]Manifest {
|
|
t.Helper()
|
|
out := map[string]Manifest{
|
|
"net": {Module: "net", Version: "1", Provides: []Offer{{Name: "mesh-addressing"}}},
|
|
"dnsmasq": catalogueManifest(t, "dnsmasq"),
|
|
}
|
|
for _, u := range uplinks {
|
|
out[u] = catalogueManifest(t, u)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// worldWithout is the rest of the mesh as a plan for one machine sees it: every other holder's claim
|
|
// and offer, and both holders on record.
|
|
func worldWithout(node string) World {
|
|
w := World{Holdings: bothResolvers, Offered: map[string][]Provider{}}
|
|
for _, h := range bothResolvers {
|
|
if h.Node == node {
|
|
continue
|
|
}
|
|
w.Held = append(w.Held, h)
|
|
w.Offered["wildcard-resolution"] = append(w.Offered["wildcard-resolution"],
|
|
Provider{Node: h.Node, At: h.Node + ".internal", Module: h.Module})
|
|
}
|
|
return w
|
|
}
|
|
|
|
// One file, whichever manager the machine runs: the three modules carry the same template, so a
|
|
// machine changing its manager changes nothing in what it asks, and a fix made to one is made to all.
|
|
func TestEveryUplinkWritesTheSameResolverFile(t *testing.T) {
|
|
var first, firstOf string
|
|
for _, uplink := range uplinks {
|
|
fact, ok := catalogueManifest(t, uplink).Facts["resolvers"]
|
|
if !ok || fact.Path != "/etc/resolv.conf" || fact.Shared || fact.Home {
|
|
t.Fatalf("%s does not write the machine's resolver file whole: %+v", uplink, fact)
|
|
}
|
|
if first == "" {
|
|
first, firstOf = fact.Template, uplink
|
|
continue
|
|
}
|
|
if fact.Template != first {
|
|
t.Errorf("%s's resolver file differs from %s's; the three are kept identical", uplink, firstOf)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only the mesh's resolver is replicated: a seat being replicated is a decision, recorded.
|
|
func TestOnlyTheResolverIsReplicated(t *testing.T) {
|
|
for _, s := range Seats() {
|
|
if s.Replicated != (s.Name == "mesh-dns-resolver") {
|
|
t.Errorf("%s replicated = %v; only mesh-dns-resolver is (ADR 0223)", s.Name, s.Replicated)
|
|
}
|
|
}
|
|
UseSeats([]Seat{{Name: "mesh-dns-resolver", Scope: ScopeMesh, Delivers: "wildcard-resolution"}})
|
|
defer UseSeats(DefaultSeats())
|
|
if s, _ := SeatNamed("mesh-dns-resolver"); !s.Replicated {
|
|
t.Error("loading the set from the store, which has no column for it, lost the resolver's replication")
|
|
}
|
|
}
|