mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/mounts-module ready: every member ready, and composed together they pass
mesh/delivery superseded: a newer head of the same pull request
A machine sharing folders and a machine mounting them each need one holder per machine, with verbs an agent calls instead of exportfs, fstab edits or zfs set. Both seats deliver nothing: nfs-share is provided at the mesh's scope. The two adopt verbs are dry runs unless confirmed.
129 lines
4.8 KiB
Go
129 lines
4.8 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0263: a machine's shares and the shares a machine mounts are two node seats, each
|
|
// with its verbs required of every holder, each delivering nothing — `nfs-share` is provided at the mesh's
|
|
// scope by the holder of node-nfs-server, and a node seat cannot answer for a provision at the mesh's.
|
|
|
|
func TestTheSharesAndMountsAreNodeSeatsWithTheirVerbs(t *testing.T) {
|
|
for seat, want := range map[string]string{
|
|
NFSServerSeat: "exports clients test reload adopt",
|
|
MountsSeat: "list test mount unmount adopt",
|
|
} {
|
|
s, ok := SeatNamed(seat)
|
|
if !ok {
|
|
t.Fatalf("%s is not in the mesh's set", seat)
|
|
}
|
|
if s.Scope != ScopeNode || s.Decision != "novox/hq ADR 0263" || s.Delivers != "" || s.Replicated {
|
|
t.Errorf("%s is %+v; a node seat under ADR 0263 that delivers nothing", seat, s)
|
|
}
|
|
var got []string
|
|
for _, v := range s.Serves {
|
|
got = append(got, v.Name)
|
|
if v.Optional {
|
|
t.Errorf("%s.%s is optional; its first holder serves it", seat, v.Name)
|
|
}
|
|
if v.Description == "" || v.Input["type"] != "object" || len(v.Replaces) == 0 {
|
|
t.Errorf("%s.%s has no description, no object schema or says it replaces nothing", seat, v.Name)
|
|
}
|
|
}
|
|
if strings.Join(got, " ") != want {
|
|
t.Errorf("%s serves %v, not %s", seat, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Both acts that take over what a person wrote by hand are dry runs unless confirmed, and name the path.
|
|
func TestTheAdoptVerbsAreDryRunsUnlessConfirmed(t *testing.T) {
|
|
for seat, key := range map[string]string{NFSServerSeat: "path", MountsSeat: "mountpoint"} {
|
|
s, _ := SeatNamed(seat)
|
|
for _, v := range s.Serves {
|
|
if v.Name != "adopt" {
|
|
continue
|
|
}
|
|
props, _ := v.Input["properties"].(map[string]any)
|
|
confirm, _ := props["confirm"].(map[string]any)
|
|
if confirm == nil || confirm["enum"] == nil {
|
|
t.Errorf("%s.adopt has no confirm switch: %v", seat, v.Input)
|
|
}
|
|
if req, _ := v.Input["required"].([]string); len(req) != 1 || req[0] != key {
|
|
t.Errorf("%s.adopt requires %v, not %q alone", seat, v.Input["required"], key)
|
|
}
|
|
if !strings.Contains(v.Description, "Without confirm, says what it would do and changes nothing") {
|
|
t.Errorf("%s.adopt does not say a call without confirm changes nothing: %s", seat, v.Description)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A holder claiming the seat at a node with every verb holds it; one naming a verb the seat does not
|
|
// promise, or leaving one out, is refused — as the catalogue's nfs-server and mounts modules claim them.
|
|
func TestAHolderOfTheShareSeatsServesEveryVerbAndNothingElse(t *testing.T) {
|
|
cases := []struct {
|
|
seat, module string
|
|
verbs []string
|
|
}{
|
|
{NFSServerSeat, "nfs-server", []string{"exports", "clients", "test", "reload", "adopt"}},
|
|
{MountsSeat, "mounts", []string{"list", "test", "mount", "unmount", "adopt"}},
|
|
}
|
|
for _, c := range cases {
|
|
seat, _ := SeatNamed(c.seat)
|
|
holder := Manifest{Module: c.module, Version: "1",
|
|
Claims: []Claim{{Name: c.seat, Scope: ScopeNode, Serves: c.verbs}}}
|
|
if c.seat == NFSServerSeat {
|
|
holder.Provides = []Offer{{Name: "nfs-share", Scope: ScopeMesh}}
|
|
}
|
|
if err := CanHold(holder, seat); err != nil {
|
|
t.Errorf("%s serving every verb is refused: %v", c.module, err)
|
|
}
|
|
typo := holder
|
|
typo.Claims = []Claim{{Name: c.seat, Scope: ScopeNode, Serves: append(append([]string{}, c.verbs...), "export")}}
|
|
if err := CanHold(typo, seat); err == nil || !strings.Contains(err.Error(), "does not promise") {
|
|
t.Errorf("%s naming a verb the seat does not promise was accepted: %v", c.module, err)
|
|
}
|
|
short := holder
|
|
short.Claims = []Claim{{Name: c.seat, Scope: ScopeNode, Serves: c.verbs[:len(c.verbs)-1]}}
|
|
if err := CanHold(short, seat); err == nil || !strings.Contains(err.Error(), "adopt") {
|
|
t.Errorf("%s leaving adopt out was accepted: %v", c.module, err)
|
|
}
|
|
mesh := holder
|
|
mesh.Claims = []Claim{{Name: c.seat, Scope: ScopeMesh, Serves: c.verbs}}
|
|
if err := CanHold(mesh, seat); err == nil {
|
|
t.Errorf("%s claiming a node seat at the mesh's scope was accepted", c.module)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What each verb replaces is what an agent would type over ssh to read or change a share by hand.
|
|
func TestTheShareVerbsSayWhatTheyReplace(t *testing.T) {
|
|
want := map[string]string{
|
|
NFSServerSeat + ".exports": "exportfs -v",
|
|
NFSServerSeat + ".adopt": "zfs set sharenfs=off",
|
|
MountsSeat + ".list": "cat /etc/fstab",
|
|
MountsSeat + ".adopt": "sed -i /etc/fstab",
|
|
}
|
|
for _, s := range DefaultSeats() {
|
|
for _, v := range s.Serves {
|
|
cmd, ok := want[s.Name+"."+v.Name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
delete(want, s.Name+"."+v.Name)
|
|
found := false
|
|
for _, r := range v.Replaces {
|
|
found = found || r == cmd
|
|
}
|
|
if !found {
|
|
t.Errorf("%s.%s does not say it replaces %q: %v", s.Name, v.Name, cmd, v.Replaces)
|
|
}
|
|
}
|
|
}
|
|
for verb := range want {
|
|
t.Errorf("%s is not a verb of the compiled seats", verb)
|
|
}
|
|
}
|