mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery failed: its walk failed: a gate on a first machine (what it carried put back), a build, a machine
Confirmation pass of #66: the plan is a preview, and the removal reads the unit when it runs; a masked unit or one changed since is left as it is.
172 lines
6.9 KiB
Go
172 lines
6.9 KiB
Go
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
|
|
}
|
|
}
|