diff --git a/internal/apply/apply.go b/internal/apply/apply.go index 134b767..00abb28 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -728,25 +728,25 @@ func ApplyMindingWindows( Origin: origin, ID: resource.Identity(), Type: string(resource.Kind()), Target: outcome.Target, AppliedAt: time.Now().UTC(), - Wrote: outcome.wrote, - Into: outcome.into, - Kept: kept, - KeptMode: keptMode, - KeptOwner: keptOwner, - Reads: outcome.reads, - Stateless: outcome.stateless, - Scope: outcome.scope, - User: outcome.user, - Found: outcome.found, - Declared: outcome.declared, - Shell: outcome.shell, - Linger: outcome.linger, - Groups: outcome.groups, - Unpacked: outcome.unpacked, - AsFound: outcome.asFound, - Declared: outcome.asFoundDeclared, - FoundAt: outcome.asFoundAt, - Holds: holds(resource), + Wrote: outcome.wrote, + Into: outcome.into, + Kept: kept, + KeptMode: keptMode, + KeptOwner: keptOwner, + Reads: outcome.reads, + Stateless: outcome.stateless, + Scope: outcome.scope, + User: outcome.user, + Found: outcome.found, + DeclaredUnit: outcome.declared, + Shell: outcome.shell, + Linger: outcome.linger, + Groups: outcome.groups, + Unpacked: outcome.unpacked, + AsFound: outcome.asFound, + Declared: outcome.asFoundDeclared, + FoundAt: outcome.asFoundAt, + Holds: holds(resource), }) if outcome.found != nil { known.DropFound(resource.Identity()) @@ -2847,9 +2847,9 @@ func giveUnitBack(ctx context.Context, sys system.System, a store.Applied, run R // mesh's doing outliving the mesh — a pool whose distribution scrub timer a module stopped would have // no schedule at all once the module went. A unit stopped for any other reason is not known to have // been stopped by the mesh, and is left as before. - if !made && a.Declared != nil && (a.Declared.Unit == "" || a.Declared.Unit == a.Target) { - start := a.Found.State == "running" && a.Declared.State == "stopped" - enable := a.Found.Boot == "enabled" && a.Declared.Boot == "disabled" + if !made && a.DeclaredUnit != nil && (a.DeclaredUnit.Unit == "" || a.DeclaredUnit.Unit == a.Target) { + start := a.Found.State == "running" && a.DeclaredUnit.State == "stopped" + enable := a.Found.Boot == "enabled" && a.DeclaredUnit.Boot == "disabled" if start || enable { return startUnitBack(ctx, sys, a, run, start, enable) } @@ -2953,9 +2953,9 @@ func startUnitBack(ctx context.Context, sys system.System, a store.Applied, run errRemovalWaits, a.Target, err) } } - if state != a.Declared.State || (a.Declared.Boot != "" && boot != a.Declared.Boot) { + if state != a.DeclaredUnit.State || (a.DeclaredUnit.Boot != "" && boot != a.DeclaredUnit.Boot) { return "forgotten", fmt.Sprintf("it is %s and %s at boot, not the %s and %s the mesh held it to: somebody "+ - "changed it since, so it is left as it is", state, boot, a.Declared.State, orUnset(a.Declared.Boot)), nil + "changed it since, so it is left as it is", state, boot, a.DeclaredUnit.State, orUnset(a.DeclaredUnit.Boot)), nil } var did []string if enable { @@ -3027,7 +3027,7 @@ func foundAs(ctx context.Context, sys system.System, r *declaration.Service, run // Given back as if undeclared, never as the mesh's own: whether the mesh wrote the old // unit's file is known to the removal of orphans, and that file's own record goes with it. action, detail, err := removeService(ctx, sys, - store.Applied{Type: string(declaration.TypeService), Target: unit, Found: f, Declared: previous.Declared, + store.Applied{Type: string(declaration.TypeService), Target: unit, Found: f, DeclaredUnit: previous.DeclaredUnit, Scope: previous.Scope, User: previous.User}, run, false) switch { case errors.Is(err, errRemovalWaits): diff --git a/internal/apply/plan.go b/internal/apply/plan.go index e355737..bbd42de 100644 --- a/internal/apply/plan.go +++ b/internal/apply/plan.go @@ -125,14 +125,14 @@ func Plan(d *declaration.Declaration, known store.State, origin string) []Step { case f == nil: step.Verb, step.Why = "forget", "no longer declared; recorded before the host kept what it "+ "found, so it is left as it is" - case orphan.Declared != nil && ((f.State == "running" && orphan.Declared.State == "stopped") || - (f.Boot == "enabled" && orphan.Declared.Boot == "disabled")): + case orphan.DeclaredUnit != nil && ((f.State == "running" && orphan.DeclaredUnit.State == "stopped") || + (f.Boot == "enabled" && orphan.DeclaredUnit.Boot == "disabled")): // startUnitBack's words (novox/hq ADR 0269): what the mesh's declaration stopped goes back. var back []string - if f.State == "running" && orphan.Declared.State == "stopped" { + if f.State == "running" && orphan.DeclaredUnit.State == "stopped" { back = append(back, "started") } - if f.Boot == "enabled" && orphan.Declared.Boot == "disabled" { + if f.Boot == "enabled" && orphan.DeclaredUnit.Boot == "disabled" { back = append(back, "enabled at boot") } // A preview: whether it is still as the mesh held it is read when the removal runs, and a diff --git a/internal/store/declared_keys_test.go b/internal/store/declared_keys_test.go new file mode 100644 index 0000000..cf34caa --- /dev/null +++ b/internal/store/declared_keys_test.go @@ -0,0 +1,35 @@ +package store + +import ( + "encoding/json" + "testing" +) + +// A directory used as found and a service's held state are two records that both say "declared" +// (novox/hq issue 339, ADR 0269). The directory's was on nodes first, under the key "declared", and a +// record a node already holds must keep reading back; the service's has a key of its own. +func TestADirectorysDeclaredAndAUnitsDeclaredKeepTheirOwnKeys(t *testing.T) { + onDisk := []byte(`{"id":"notes.data","type":"directory","as_found":true,"declared":"root, mode 755",` + + `"declared_unit":{"unit":"a.timer","state":"stopped","boot":"disabled"}}`) + var a Applied + if err := json.Unmarshal(onDisk, &a); err != nil { + t.Fatal(err) + } + if a.Declared != "root, mode 755" { + t.Errorf("the directory's declared owner and mode read back as %q", a.Declared) + } + if a.DeclaredUnit == nil || a.DeclaredUnit.State != "stopped" || a.DeclaredUnit.Boot != "disabled" { + t.Errorf("the unit's held state read back as %+v", a.DeclaredUnit) + } + body, err := json.Marshal(a) + if err != nil { + t.Fatal(err) + } + var keys map[string]json.RawMessage + if err := json.Unmarshal(body, &keys); err != nil { + t.Fatal(err) + } + if string(keys["declared"]) != `"root, mode 755"` || keys["declared_unit"] == nil { + t.Errorf("written as %s", body) + } +} diff --git a/internal/store/store.go b/internal/store/store.go index b2f3db3..3db306e 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -99,12 +99,13 @@ type Applied struct { // when its declaration goes; one the mesh started is stopped again. Absent on a record written // before the host kept it, and then the unit is left exactly as it is. Found *FoundUnit `json:"found,omitempty"` - // Declared is, for a service with a state, what the mesh last held its unit to: running or stopped, + // DeclaredUnit is, for a service with a state, what the mesh last held its unit to: running or stopped, // and enabled or disabled where the declaration said. With Found, it tells a unit the mesh's own // declaration stopped and disabled from one somebody stopped since, so removal gives back running // and enabled only the first (novox/hq ADR 0269). Absent on a record from before the host kept it, // and then nothing is started on the way out. - Declared *FoundUnit `json:"declared,omitempty"` + // Its key is not "declared": that one is a directory's (AsFound, below) and was on disk first. + DeclaredUnit *FoundUnit `json:"declared_unit,omitempty"` // Into is set for a file written into rather than over (novox/hq ADR 0102): the format, what // each of the mesh's keys held before it set them, which of them were absent, and whether the