package apply import ( "strings" "testing" "github.com/novox/mesh-host/internal/store" ) // A unit the mesh found running and enabled, and held stopped and disabled by its own declaration, is // given back running and enabled when that declaration goes (the zfs module holds the distribution's // scrub timer stopped while its own timer scrubs the pool; unassigning it must not leave the pool with no // schedule at all). The report says so. func TestAUnitTheMeshStoppedAndDisabledIsGivenBackRunningAndEnabled(t *testing.T) { m := unitsMachine(map[string]*fakeUnit{ "zfs-scrub-weekly@storage.timer": {active: "active", enabled: "enabled"}, }) _, state, err := applyOn(t, `{"declaration":1,"resources":[ {"id":"zfs.distribution-weekly","type":"service","unit":"zfs-scrub-weekly@storage.timer","state":"stopped","boot":"disabled"}]}`, store.State{}, m) if err != nil { t.Fatal(err) } if u := m.units["zfs-scrub-weekly@storage.timer"]; u.active != "inactive" || u.enabled != "disabled" { t.Fatalf("the fixture did not stop and disable it: %s and %s", u.active, u.enabled) } report, _, err := applyOn(t, nothingButA(t), copyOf(t, state), m) if err != nil { t.Fatal(err) } if u := m.units["zfs-scrub-weekly@storage.timer"]; u.active != "active" || u.enabled != "enabled" { t.Errorf("given back as %s and %s; it was found running and enabled", u.active, u.enabled) } said := "" for _, r := range report.Outcomes { if r.ID == "zfs.distribution-weekly" { said = r.Action + ": " + r.Detail } } if !strings.HasPrefix(said, "restored: ") || !strings.Contains(said, "started") || !strings.Contains(said, "enabled at boot") { t.Errorf("the report says %q", said) } } // Only what the mesh's own declaration did is undone: a unit the mesh held stopped that was found stopped // stays stopped, and one found running that the mesh never stopped is left running as before. func TestOnlyWhatTheMeshsDeclarationStoppedIsStartedAgain(t *testing.T) { m := unitsMachine(map[string]*fakeUnit{ "found-stopped.timer": {active: "inactive", enabled: "disabled"}, "left-running.timer": {active: "active", enabled: "enabled"}, }) _, state, err := applyOn(t, `{"declaration":1,"resources":[ {"id":"a","type":"service","unit":"found-stopped.timer","state":"stopped","boot":"disabled"}, {"id":"b","type":"service","unit":"left-running.timer","state":"running","boot":"enabled"}]}`, store.State{}, m) if err != nil { t.Fatal(err) } if _, _, err := applyOn(t, nothingButA(t), state, m); err != nil { t.Fatal(err) } if u := m.units["found-stopped.timer"]; u.active != "inactive" || u.enabled != "disabled" { t.Errorf("found stopped and disabled, given back as %s and %s", u.active, u.enabled) } if u := m.units["left-running.timer"]; u.active != "active" || u.enabled != "enabled" { t.Errorf("found running, given back as %s and %s", u.active, u.enabled) } } // A record from before the host kept what it declared says nothing about who stopped the unit: it is // left as it is, never started. func TestARecordWithoutWhatWasDeclaredStartsNothing(t *testing.T) { m := unitsMachine(map[string]*fakeUnit{"old.timer": {active: "inactive", enabled: "disabled"}}) state := store.State{} state.Record(store.Applied{ID: "old", Type: "service", Target: "old.timer", Origin: store.OriginDeclared, Found: &store.FoundUnit{Unit: "old.timer", State: "running", Boot: "enabled"}}) if _, _, err := applyOn(t, nothingButA(t), state, m); err != nil { t.Fatal(err) } if u := m.units["old.timer"]; u.active != "inactive" || u.enabled != "disabled" { t.Errorf("an old record started %s and %s", u.active, u.enabled) } } // The plan says it before the apply does it. func TestThePlanSaysAStoppedUnitIsStartedAgain(t *testing.T) { m := unitsMachine(map[string]*fakeUnit{"zfs-scrub-weekly@storage.timer": {active: "active", enabled: "enabled"}}) _, state, err := applyOn(t, `{"declaration":1,"resources":[ {"id":"w","type":"service","unit":"zfs-scrub-weekly@storage.timer","state":"stopped","boot":"disabled"}]}`, store.State{}, m) if err != nil { t.Fatal(err) } said := "" for _, s := range Plan(parse(t, nothingButA(t)), state, store.OriginDeclared) { if s.ID == "w" { said = s.Verb + ": " + s.Why } } if !strings.HasPrefix(said, "restore: ") || !strings.Contains(said, "restore if still as the mesh held it") || strings.Contains(said, "so it is started") { t.Errorf("the plan says %q", said) } } // Third review: on removal a unit is started again only when it is exactly as the mesh held it, stopped and // disabled. A masked unit, or one somebody started or enabled since, is left alone, and the report says why. func TestAMaskedOrChangedUnitIsLeftAloneOnRemoval(t *testing.T) { for _, c := range []struct { name, active, enabled, says string }{ {"masked", "inactive", "masked", "masked"}, {"enabled since", "inactive", "enabled", "not the stopped and disabled"}, {"started since", "active", "disabled", "not the stopped and disabled"}, } { m := unitsMachine(map[string]*fakeUnit{"w.timer": {active: "active", enabled: "enabled"}}) _, state, err := applyOn(t, `{"declaration":1,"resources":[ {"id":"w","type":"service","unit":"w.timer","state":"stopped","boot":"disabled"}]}`, store.State{}, m) if err != nil { t.Fatal(err) } m.units["w.timer"].active, m.units["w.timer"].enabled = c.active, c.enabled report, _, err := applyOn(t, nothingButA(t), state, m) if err != nil { t.Fatalf("%s: %v", c.name, err) } if u := m.units["w.timer"]; u.active != c.active || u.enabled != c.enabled { t.Errorf("%s: changed to %s and %s", c.name, u.active, u.enabled) } said := "" for _, o := range report.Outcomes { if o.ID == "w" { said = o.Action + ": " + o.Detail } } if !strings.HasPrefix(said, "forgotten: ") || !strings.Contains(said, c.says) { t.Errorf("%s: the report says %q", c.name, said) } } } // A restore that fails — the start refused, or the unit not running after it — waits: the record stays, // the report says so, and no apply is made fatal by it. func TestAFailedRestoreWaitsAndIsNeverFatal(t *testing.T) { m := unitsMachine(map[string]*fakeUnit{"w.timer": {active: "active", enabled: "enabled"}}) _, state, err := applyOn(t, `{"declaration":1,"resources":[ {"id":"w","type":"service","unit":"w.timer","state":"stopped","boot":"disabled"}]}`, store.State{}, m) if err != nil { t.Fatal(err) } m.units["w.timer"].wontStart = true for i := 0; i < 2; i++ { report, next, err := applyOn(t, nothingButA(t), state, m) if err != nil { t.Fatalf("apply %d failed: %v", i+1, err) } said := "" for _, o := range report.Outcomes { if o.ID == "w" { said = o.Action + ": " + o.Detail } } if !strings.Contains(said, "wait") || !strings.Contains(said, "not running") { t.Errorf("apply %d: the report says %q", i+1, said) } if _, kept := next.Find("w"); !kept { t.Errorf("apply %d: the record went, so nothing would try again", i+1) } state = next } }