//go:build integration package main import ( "context" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) func TestALeftOutModulesConditionClearsWhenItComposesAgainOrIsUnassigned(t *testing.T) { plan, settings := leftOutNFS(t), catalogue.SettingsBy(nil) withProbes(t, probe{ID: probeLeftOutID, Asserts: "the test's", Kind: kindLeftOut, Phase: 1, run: func(context.Context, *doctor) ([]conditions.Observation, error) { return leftOutObservations("anchor", leftOutOf(plan, settings, false)), nil }}) store := conditions.NewInMemory() k := conditions.NewKeeper(t.Context(), conditions.Options{Store: store, History: store}) defer k.Close(context.Background()) d := &doctor{keeper: k, teller: &conditions.Told{}, host: "anchor"} key := "module.nfs-server.anchor.left-out" openOnes := func() map[string]conditions.Condition { t.Helper() open, err := k.Open(t.Context()) if err != nil { t.Fatal(err) } out := map[string]conditions.Condition{} for _, c := range open { out[c.Key] = c } return out } d.runOnce(t.Context(), "a test") c, raised := openOnes()[key] if !raised || c.Kind != kindNeedsOperator || c.Severity != conditions.Warning { t.Fatalf("a module left out raised %+v", openOnes()) } // A statement from the machine that says nothing of it — the module runs nothing there — leaves it open. if err := judgeModuleHealth(t.Context(), nil, k, "anchor", map[string][]inventory.ResourceHealth{}, nil, time.Now().Add(72*time.Hour)); err != nil { t.Fatal(err) } if _, still := openOnes()[key]; !still { t.Fatal("a health statement that says nothing of the module cleared its left-out condition") } d.runOnce(t.Context(), "a test") if c := openOnes()[key]; c.Severity != conditions.Warning { t.Fatalf("after a health statement and days, it is %+v", openOnes()) } // The setting given: it composes, and the condition clears. settings = sharesGiven("library=/srv/library") d.runOnce(t.Context(), "a test") if _, still := openOnes()[key]; still { t.Fatalf("composed again, still open: %+v", openOnes()) } // Left out again, then unassigned: no longer in the machine's set, and it clears. settings = nil d.runOnce(t.Context(), "a test") if _, raised := openOnes()[key]; !raised { t.Fatal("left out again and not raised") } plan = catalogue.Resolution{} d.runOnce(t.Context(), "a test") if _, still := openOnes()[key]; still { t.Fatalf("unassigned, still open: %+v", openOnes()) } } func TestAWaitsNeedsOperatorStillClearsWhenItsStatementStopsNamingItBesideALeftOutOne(t *testing.T) { store := conditions.NewInMemory() k := conditions.NewKeeper(t.Context(), conditions.Options{Store: store, History: store}) defer k.Close(context.Background()) left := leftOutObservations("anchor", leftOutOf(leftOutNFS(t), nil, false)) if err := k.Reconcile(t.Context(), probeLeftOutID, left); err != nil { t.Fatal(err) } waiting := map[string][]inventory.ResourceHealth{"notes": {{Module: "notes", Resource: "server", State: link.StateWaiting, Waits: []inventory.Wait{{Setting: "domain", What: "the domain it serves"}}}}} if err := judgeModuleHealth(t.Context(), nil, k, "anchor", waiting, map[string]int{"notes": 2}, time.Now()); err != nil { t.Fatal(err) } waitKey, leftKey := needsOperatorKey("notes", "anchor"), "module.nfs-server.anchor.left-out" open := func() map[string]conditions.Condition { t.Helper() list, err := k.Open(t.Context()) if err != nil { t.Fatal(err) } out := map[string]conditions.Condition{} for _, c := range list { out[c.Key] = c } return out } if c, raised := open()[waitKey]; !raised || c.Kind != kindNeedsOperator { t.Fatalf("the wait raised %+v", open()) } if c := open()[leftKey]; c.Kind != kindNeedsOperator { t.Fatalf("the left-out condition is not open as needs-operator: %+v", open()) } // D1 runs again and still finds nfs-server left out: its reconcile clears only what D1 raised, never the wait, // which the machine's statement raised. if err := k.Reconcile(t.Context(), probeLeftOutID, left); err != nil { t.Fatal(err) } if _, still := open()[waitKey]; !still { t.Fatalf("D1's reconcile cleared the wait's needs-operator: %+v", open()) } if err := judgeModuleHealth(t.Context(), nil, k, "anchor", map[string][]inventory.ResourceHealth{}, nil, time.Now()); err != nil { t.Fatal(err) } if _, still := open()[waitKey]; still { t.Fatalf("the statement no longer names the wait, and its needs-operator is still open: %+v", open()) } if _, still := open()[leftKey]; !still { t.Fatalf("the left-out condition was cleared by a health statement: %+v", open()) } }