Files
mesh-host/internal/compose/resolution/seat_dependencies_test.go
T

112 lines
4.5 KiB
Go

package resolution
import (
"reflect"
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
func res(kind, id string) map[string]any {
r := map[string]any{"id": id, "type": kind}
switch kind {
case "service":
r["unit"] = id + ".service"
case "package":
r["package"] = id
case "container":
r["image"] = id
case "file":
r["path"] = "/etc/" + id
}
return r
}
func withResources(m mcat.Manifest, rs ...map[string]any) mcat.Manifest {
m.Resources = rs
return m
}
// The three holders as to-be 42 names them, each declaring what it really does: systemd's own
// package needs the package manager, pacman's timer needs the service manager, docker's package and
// service need both.
func coreThree() []mcat.Manifest {
return []mcat.Manifest{
withResources(mod("systemd", nil, nil, nil, mcat.Claim{Name: mcat.ServiceManagerSeat}), res("package", "systemd")),
withResources(mod("pacman", nil, nil, nil, mcat.Claim{Name: mcat.PackageManagerSeat}), res("service", "pacman-refresh")),
withResources(mod("docker", nil, nil, nil, mcat.Claim{Name: mcat.ContainerRuntimeSeat}),
res("package", "docker"), res("service", "docker")),
}
}
func TestADependencyIsDerivedFromEachResourceTypeThatAppliesThroughASeat(t *testing.T) {
cases := map[string][]string{
"service": {mcat.ServiceManagerSeat},
"package": {mcat.PackageManagerSeat},
"container": {mcat.ContainerRuntimeSeat},
// The host's own acts, or the mesh's: nothing in between holds a role for them.
"file": nil, "directory": nil, "process": nil, "archive": nil, "user": nil,
"action": nil, "network": nil, "access": nil,
}
for kind, want := range cases {
got := DependsOn(nil, withResources(mod("m", nil, nil, nil), res(kind, "x")))
if len(got) == 0 {
got = nil
}
if !reflect.DeepEqual(got, want) {
t.Errorf("a %s resource depends on %v, want %v", kind, got, want)
}
}
all := DependsOn(nil, withResources(mod("m", nil, nil, nil),
res("container", "a"), res("service", "b"), res("package", "c"), res("package", "d")))
if want := []string{mcat.ContainerRuntimeSeat, mcat.PackageManagerSeat, mcat.ServiceManagerSeat}; !reflect.DeepEqual(all, want) {
t.Errorf("a module of every kind depends on %v, want each seat once: %v", all, want)
}
}
func TestStatusIsToldOfAnUnmetDependencyAndTheNodeStillResolves(t *testing.T) {
// Before the switch (the state the mesh ran in until every node held the three seats).
enforceSeatDependencies = false
defer func() { enforceSeatDependencies = true }()
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
cat := shelf(append(coreThree(), web)...)
got, err := Resolve(cat, []string{"web"}, workstation(), mcat.World{})
if err != nil {
t.Fatalf("an unmet dependency refused the node before the switch: %v", err)
}
want := []mcat.Unheld{{Node: "workstation", Module: "web", Seat: mcat.ContainerRuntimeSeat, Holders: []string{"docker"}}}
if !reflect.DeepEqual(got.Unheld, want) {
t.Errorf("reported %+v, want %+v", got.Unheld, want)
}
}
func TestWithTheSwitchFlippedAnUnmetDependencyRefusesTheNode(t *testing.T) {
enforceSeatDependencies = true
defer func() { enforceSeatDependencies = true }()
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
cat := shelf(append(coreThree(), web)...)
_, err := Resolve(cat, []string{"web"}, workstation(), mcat.World{})
if err == nil || !strings.Contains(err.Error(), mcat.ContainerRuntimeSeat) || !strings.Contains(err.Error(), "docker") {
t.Fatalf("with the switch on, an unmet dependency gave %v", err)
}
// Never in the first pass, whose refusals take a machine off the network instead.
if _, err := Resolve(cat, []string{"web"}, workstation(), mcat.World{Unchecked: true}); err != nil {
t.Errorf("the first pass refused an unmet dependency: %v", err)
}
}
func TestTheFoundationHostIsTheEntryThatClaimsTheNodeEngineSeat(t *testing.T) {
svc := res("service", "x")
// Whatever its name, the entry claiming the seat is the foundation's: nothing to depend on.
engine := withResources(mod("the-engine", nil, nil, nil, mcat.Claim{Name: "node-engine"}), svc)
if got := DependsOn(nil, engine); len(got) != 0 {
t.Errorf("the entry claiming node-engine depends on %v, want nothing (§527)", got)
}
// A module named as the old code named the engine, claiming no such seat, is an ordinary module.
named := withResources(mod("mesh-host", nil, nil, nil), svc)
if got := DependsOn(nil, named); !reflect.DeepEqual(got, []string{mcat.ServiceManagerSeat}) {
t.Errorf("a module merely named mesh-host depends on %v, want the service manager seat", got)
}
}