H2b engine-found-handed: the found state of a unit passes to the record that stays

When one record of a unit goes while another still declares it, what the going
record found of the unit is kept for the staying one when that has no finding of
its own, so the module's leave gives back the state found before the mesh. The
preview test and the new case are one test (A-22).

Builds: design 42 zfs-owns-zfs §2, answers A-21, A-22
This commit is contained in:
2026-11-13 00:20:59 +01:00
parent 039415cdd9
commit 86048f0ed9
2 changed files with 83 additions and 13 deletions
+11
View File
@@ -578,6 +578,17 @@ func applyScoped(
}
if declaration.Type(orphan.Type) == declaration.TypeService {
if by, held := heldUnits[unitKey(orphan.Scope, orphan.User, orphan.Target)]; held {
// **What the going record found passes to the record that stays** (design 42 zfs-owns-zfs §2, answer
// A-21), as a file is handed to the resource still declaring its path (ADR 0223): the unit as the machine
// had it before the mesh is what the staying record gives back when it goes, not what the mesh made of
// it. Only where the staying record has no finding of its own, kept as an unfinished apply's is.
if orphan.Found != nil && declared[by] {
if rec, has := known.Find(by); !has || rec.Found == nil {
if _, pending := known.FoundFirst[by]; !pending {
known.KeepFound(by, origin, *orphan.Found)
}
}
}
known.Forget(orphan.ID)
detail := "no longer declared; " + by + " still holds the unit, so it was left as it is"
report.Outcomes = append(report.Outcomes, Outcome{
+72 -13
View File
@@ -112,18 +112,77 @@ func TestTheRegistryMovesToTheRuntimesModuleWithoutLeavingTheList(t *testing.T)
}
}
// And the preview says the same before it happens.
func TestThePlanForgetsARecordOfAUnitStillHeld(t *testing.T) {
path := filepath.Join(t.TempDir(), "daemon.json")
var commands []string
_, state, err := Apply(context.Background(), archHost(t), runtimeDecl(t, path, true), store.State{},
store.OriginDeclared, recordingServices(&commands), nil, nil)
if err != nil {
t.Fatal(err)
}
for _, step := range Plan(runtimeDecl(t, path, false), state, store.OriginDeclared) {
if step.ID == "networking.registry-trust-reload" && step.Verb != "forget" {
t.Errorf("the plan would %s the network's record of a unit docker.runtime holds: %s", step.Verb, step.Why)
// A unit two records name, under opposite conditions, passes between them (design 42 zfs-owns-zfs §2, answer A-21): the
// going record is forgotten, the preview says so before it happens, and what it found of the unit before the mesh is what
// the staying record gives back when its module leaves, not what the mesh made of it.
func TestARecordOfAUnitStillHeldIsForgottenAndPassesWhatItFound(t *testing.T) {
t.Run("the preview forgets it", func(t *testing.T) {
path := filepath.Join(t.TempDir(), "daemon.json")
var commands []string
_, state, err := Apply(context.Background(), archHost(t), runtimeDecl(t, path, true), store.State{},
store.OriginDeclared, recordingServices(&commands), nil, nil)
if err != nil {
t.Fatal(err)
}
}
for _, step := range Plan(runtimeDecl(t, path, false), state, store.OriginDeclared) {
if step.ID == "networking.registry-trust-reload" && step.Verb != "forget" {
t.Errorf("the plan would %s the network's record of a unit docker.runtime holds: %s", step.Verb, step.Why)
}
}
})
t.Run("what it found passes to the record that stays", func(t *testing.T) {
// A unit found stopped and disabled, so the mesh's first record of it started it.
active, enabled := false, false
var commands []string
run := func(_ context.Context, name string, args ...string) (string, error) {
args = oldShape(name, args)
line := name + " " + strings.Join(args, " ")
commands = append(commands, line)
switch {
case strings.Contains(line, " start "):
active = true
case strings.Contains(line, " stop "):
active = false
case strings.Contains(line, " enable "):
enabled = true
case strings.Contains(line, " disable "):
enabled = false
case strings.Contains(line, "is-enabled"):
if enabled {
return "enabled", nil
}
return "disabled", nil
case strings.Contains(line, "show") && strings.Contains(line, "ActiveState"):
if active {
return "LoadState=loaded\nActiveState=active\nSubState=running", nil
}
return "LoadState=loaded\nActiveState=inactive\nSubState=dead", nil
}
return "", nil
}
unit := func(id, state, boot string) *declaration.Declaration {
return parse(t, `{"declaration":1,"resources":[{"id":"`+id+`","type":"service","unit":"z.service","state":"`+
state+`","boot":"`+boot+`"}]}`)
}
apply := func(d *declaration.Declaration, known store.State) store.State {
t.Helper()
_, known, err := Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, run, nil, nil)
if err != nil {
t.Fatal(err)
}
return known
}
known := apply(unit("z.on", "running", "enabled"), store.State{})
known = apply(unit("z.off", "stopped", "disabled"), known) // the value changed: one record goes, the other stays
if rec, _ := known.Find("z.off"); rec.Found == nil || rec.Found.State != "stopped" || rec.Found.Boot != "disabled" {
t.Fatalf("the staying record did not receive what the going one found: %+v", rec.Found)
}
commands = nil
apply(somethingElse(t), known) // the module leaves
for _, c := range commands {
if strings.Contains(c, " start ") || strings.Contains(c, " enable ") {
t.Errorf("the unit was given back as the mesh made it, not as it was found: %q", c)
}
}
})
}