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) } }