Making every soft dependency a provision made it hard: a mesh without influxdb could not assign
grafana, and one with influxdb in its catalogue would install it for grafana. A requirement may now
be written {"name": ..., "optional": true}. It resolves as a hard one when a provider is assigned or
reachable, and is otherwise absent: never refused, nothing installed for it. Its binding, credential
and only-when-bound files are left out, its ${bound:...|default:...} placeholders take their
defaults, the module check refuses one that could render empty, and plan and node list it unbound.
60 lines
2.3 KiB
Go
60 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// A machine running grafana without influxdb says so (novox/hq ADR 0330 §7, issue 166): `plan` and
|
|
// `node` list the optional requirement nothing assigned answers, and the requirement is left
|
|
// absent rather than refused — influxdb is in the catalogue and is not installed for it.
|
|
func TestPlanAndNodeListAnOptionalRequirementLeftUnbound(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
register(t, open, catalogue.Manifest{Module: "influxdb", Version: "1",
|
|
Provides: catalogue.FromAnywhere("influxdb-api")})
|
|
register(t, open, catalogue.Manifest{Module: "grafana", Version: "1",
|
|
Requires: []string{"influxdb-api"}, OptionalRequires: map[string]bool{"influxdb-api": true},
|
|
Resources: []map[string]any{{"id": "state", "type": "directory", "path": "/var/lib/grafana", "mode": "0700"}}})
|
|
if _, err := assign(ctx, open, "laptop", "grafana"); err != nil {
|
|
t.Fatalf("grafana could not be assigned on a mesh without influxdb: %v", err)
|
|
}
|
|
|
|
plan, _, err := planFor(ctx, open, "laptop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, m := range plan.Modules {
|
|
if m.Module == "influxdb" {
|
|
t.Fatal("influxdb was installed for grafana's optional requirement (ADR 0330 §3)")
|
|
}
|
|
}
|
|
const said = "unbound influxdb-api, optional for grafana"
|
|
if shown := printed(t, func() error { return planCommand(ctx, []string{"laptop"}) }); !strings.Contains(shown, said) {
|
|
t.Errorf("plan laptop does not say influxdb-api is unbound:\n%s", shown)
|
|
}
|
|
if shown := printed(t, func() error { return showNode(ctx, open, "laptop") }); !strings.Contains(shown, said) {
|
|
t.Errorf("node show laptop does not say influxdb-api is unbound:\n%s", shown)
|
|
}
|
|
// And once influxdb is assigned somewhere, grafana binds to it with no act on grafana.
|
|
if _, err := assign(ctx, open, "anchor", "influxdb"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plan, _, err = planFor(ctx, open, "laptop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(plan.Absent) != 0 {
|
|
t.Fatalf("still absent after influxdb was assigned: %+v", plan.Absent)
|
|
}
|
|
var bound bool
|
|
for _, n := range plan.Needs {
|
|
bound = bound || n.For == "grafana" && n.Name == "influxdb-api" && n.From == "anchor"
|
|
}
|
|
if !bound {
|
|
t.Fatalf("grafana was not bound to influxdb on anchor: %+v", plan.Needs)
|
|
}
|
|
}
|