Undeclaring gives a unit back the state it was found in, and removes a process the mesh made (hq ADR 0118, issue 130)
A service undeclared used to be stopped: unassigning the private network stopped the container runtime, unassigning sshd would stop ssh, an uplink module would take the machine offline. The host now records the unit's state when it first applies it and restores that on undeclare — found running stays running; started by the mesh (the converge filter) is stopped again; nothing is started on the way out; a pre-existing record leaves the unit alone. An undeclared process had no removal at all and failed every apply on its node; its unit, timer and bundle are now removed.
This commit is contained in:
@@ -348,33 +348,111 @@ func TestAnUnknownServiceStateIsRefusedNotGuessed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestADroppedServiceIsStoppedNotDeleted(t *testing.T) {
|
||||
// The host did not install the unit and does not own the unit file — only the state it put
|
||||
// the unit into.
|
||||
var commands []string
|
||||
func TestADroppedServiceIsGivenBackTheStateItWasFoundIn(t *testing.T) {
|
||||
// novox/hq ADR 0118: undeclaring removes what the mesh made, gives back what it changed, and
|
||||
// leaves what was the machine's. A service resource never installs a unit — so undeclaring it
|
||||
// undoes what the mesh did to the unit, and nothing more. Stopping every undeclared unit is
|
||||
// how unassigning the private network stopped the container runtime (novox/hq issue 130).
|
||||
cases := []struct {
|
||||
name string
|
||||
found *store.FoundUnit
|
||||
action string
|
||||
stop bool
|
||||
disable bool
|
||||
}{
|
||||
{"recorded before the host kept what it found", nil, "forgotten", false, false},
|
||||
{"running before the mesh", &store.FoundUnit{State: "running"}, "forgotten", false, false},
|
||||
{"running and enabled before the mesh", &store.FoundUnit{State: "running", Boot: "enabled"}, "forgotten", false, false},
|
||||
{"started by the mesh", &store.FoundUnit{State: "stopped"}, "restored", true, false},
|
||||
{"started and enabled by the mesh", &store.FoundUnit{State: "stopped", Boot: "disabled"}, "restored", true, true},
|
||||
{"enabled by the mesh, running before it", &store.FoundUnit{State: "running", Boot: "disabled"}, "restored", false, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var commands []string
|
||||
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||
commands = append(commands, strings.Join(args, " "))
|
||||
if args[0] == "show" {
|
||||
return "LoadState=loaded\nActiveState=active\n", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
state := store.State{Resources: []store.Applied{
|
||||
{ID: "s", Type: "service", Target: "unit.service", Found: c.found},
|
||||
}}
|
||||
d := parse(t, `{"declaration":1,"resources":[
|
||||
{"id":"other","type":"file","path":"`+filepath.Join(t.TempDir(), "a")+`","content":"a\n"}
|
||||
]}`)
|
||||
report, after, err := Apply(context.Background(), archHost(t), d, state, store.OriginCarried, run, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
joined := strings.Join(commands, "; ")
|
||||
if stopped := strings.Contains(joined, "stop unit.service"); stopped != c.stop {
|
||||
t.Errorf("stopped %v, want %v: %s", stopped, c.stop, joined)
|
||||
}
|
||||
if disabled := strings.Contains(joined, "disable unit.service"); disabled != c.disable {
|
||||
t.Errorf("disabled %v, want %v: %s", disabled, c.disable, joined)
|
||||
}
|
||||
if strings.Contains(joined, "start unit.service") || strings.Contains(joined, "mask") {
|
||||
t.Errorf("the host started or masked a unit on its way out: %s", joined)
|
||||
}
|
||||
if o := outcomeOf(report, "s"); o.Action != c.action {
|
||||
t.Errorf("outcome %+v, want %s", o, c.action)
|
||||
}
|
||||
if _, still := after.Find("s"); still {
|
||||
t.Error("the host still believes it owns the undeclared service")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAServiceRecordsTheStateItWasFoundInOnceAndCarriesIt(t *testing.T) {
|
||||
// Read the first time the host applies the unit — before it starts or enables anything —
|
||||
// and never again: by the next apply, the unit's state is the mesh's doing.
|
||||
active := "inactive"
|
||||
enabled := "disabled"
|
||||
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||
commands = append(commands, strings.Join(args, " "))
|
||||
if args[0] == "show" {
|
||||
return "LoadState=loaded\nActiveState=active\n", nil
|
||||
switch args[0] {
|
||||
case "show":
|
||||
return "LoadState=loaded\nActiveState=" + active + "\n", nil
|
||||
case "is-enabled":
|
||||
return enabled, nil
|
||||
case "start":
|
||||
active = "active"
|
||||
case "enable":
|
||||
enabled = "enabled"
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
state := store.State{Resources: []store.Applied{
|
||||
{ID: "s", Type: "service", Target: "gone.service"},
|
||||
}}
|
||||
d := parse(t, `{"declaration":1,"resources":[
|
||||
{"id":"other","type":"file","path":"`+filepath.Join(t.TempDir(), "a")+`","content":"a\n"}
|
||||
{"id":"s","type":"service","unit":"filter.service","state":"running","boot":"enabled"}
|
||||
]}`)
|
||||
|
||||
if _, _, err := Apply(context.Background(), archHost(t), d, state, store.OriginCarried, run, nil, nil); err != nil {
|
||||
_, first, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
joined := strings.Join(commands, "; ")
|
||||
if !strings.Contains(joined, "stop gone.service") {
|
||||
t.Errorf("the dropped service was not stopped: %s", joined)
|
||||
rec, _ := first.Find("s")
|
||||
if rec.Found == nil || rec.Found.State != "stopped" || rec.Found.Boot != "disabled" {
|
||||
t.Fatalf("first apply recorded %+v, want stopped and disabled", rec.Found)
|
||||
}
|
||||
if strings.Contains(joined, "disable") || strings.Contains(joined, "mask") {
|
||||
t.Errorf("the host did more than stop a unit it does not own: %s", joined)
|
||||
_, second, err := Apply(context.Background(), archHost(t), d, first, store.OriginCarried, run, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec, _ = second.Find("s")
|
||||
if rec.Found == nil || rec.Found.State != "stopped" {
|
||||
t.Errorf("a later apply replaced what was found with what the mesh made: %+v", rec.Found)
|
||||
}
|
||||
|
||||
// A record from before the host kept what it found is not given one later.
|
||||
old := store.State{Resources: []store.Applied{{ID: "s", Type: "service", Target: "filter.service"}}}
|
||||
_, third, err := Apply(context.Background(), archHost(t), d, old, store.OriginCarried, run, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rec, _ = third.Find("s"); rec.Found != nil {
|
||||
t.Errorf("a record that predates the field was given one after the mesh had acted: %+v", rec.Found)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user