Which module owns /etc/hosts or /etc/resolv.conf on a machine was answered only by reading every manifest, and a module not holding node-hostname could declare /etc/hosts where nobody held it. hq ADR 0287 makes the paths part of the seat's definition: node-hostname names /etc/hosts and /etc/hostname, node-uplink names /etc/resolv.conf stepping back for node-resolver, node-resolver names /etc/resolv.conf. CanHold refuses a holder that does not declare every path (registration and handover), registration refuses a module declaring a seat's path while claiming none of the seats naming it, a module's own seat names paths under the same rules (node only, literal, not named by another seat), composition refuses a generated resource on a seat's path, the uplink's step-back is read from the seat instead of a special case, and `seats` shows each seat's paths and who owns each on each machine.
66 lines
2.4 KiB
Go
66 lines
2.4 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The node-uplink seat's verbs (novox/hq ADR 0241): promised and routed, optional — not yet a condition of
|
|
// holding — so the holders of today still hold it, a holder may name them, and nothing it does not promise;
|
|
// and still optional once the store's row is read back (optionalAsCompiled).
|
|
|
|
func uplinkSeat(t *testing.T) Seat {
|
|
t.Helper()
|
|
for _, s := range Seats() {
|
|
if s.Name == "node-uplink" {
|
|
return s
|
|
}
|
|
}
|
|
t.Fatal("no node-uplink seat")
|
|
return Seat{}
|
|
}
|
|
|
|
func TestTheUplinkSeatPromisesItsVerbsAndRequiresNoneYet(t *testing.T) {
|
|
seat := uplinkSeat(t)
|
|
var names []string
|
|
for _, v := range seat.Serves {
|
|
names = append(names, v.Name)
|
|
if !v.Optional {
|
|
t.Errorf("%s is required already", v.Name)
|
|
}
|
|
}
|
|
if !slices.Equal(names, []string{"resolvers", "links"}) {
|
|
t.Fatalf("node-uplink promises %v", names)
|
|
}
|
|
// The resolver file is the seat's path (novox/hq ADR 0287), which every holder of today renders.
|
|
today := Manifest{Module: "networkmanager", Version: "1", Claims: []Claim{{Name: "node-uplink", Scope: ScopeNode}},
|
|
Facts: map[string]RosterFile{"resolvers": {Path: ResolverFile, Template: "x"}}}
|
|
if err := CanHold(today, seat); err != nil {
|
|
t.Fatalf("a holder that serves no verb yet is refused: %v", err)
|
|
}
|
|
serving := today
|
|
serving.Claims = []Claim{{Name: "node-uplink", Scope: ScopeNode, Serves: []string{"resolvers", "links"}}}
|
|
if err := CanHold(serving, seat); err != nil {
|
|
t.Fatalf("a holder serving both is refused: %v", err)
|
|
}
|
|
typo := today
|
|
typo.Claims = []Claim{{Name: "node-uplink", Scope: ScopeNode, Serves: []string{"resolver"}}}
|
|
if err := CanHold(typo, seat); err == nil || !strings.Contains(err.Error(), "does not promise") {
|
|
t.Fatalf("a verb the seat does not promise was accepted: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTheUplinkVerbsReadBackFromTheRowStayOptional(t *testing.T) {
|
|
before := seats
|
|
t.Cleanup(func() { seats = before })
|
|
// The row as seeding stores it: both verbs, the mark not a column.
|
|
UseSeats([]Seat{{Name: "node-uplink", Scope: ScopeNode, Serves: []Verb{{Name: "resolvers"}, {Name: "links"}}}})
|
|
seat := uplinkSeat(t)
|
|
holder := Manifest{Module: "systemd-networkd", Version: "1", Claims: []Claim{{Name: "node-uplink", Scope: ScopeNode}},
|
|
Facts: map[string]RosterFile{"resolvers": {Path: ResolverFile, Template: "x"}}}
|
|
if err := CanHold(holder, seat); err != nil {
|
|
t.Fatalf("read back from the row, the verbs are required: %v", err)
|
|
}
|
|
}
|