diff --git a/modules/pacman/README.md b/modules/pacman/README.md index c0b770d2..8724d792 100644 --- a/modules/pacman/README.md +++ b/modules/pacman/README.md @@ -35,7 +35,7 @@ that seat (ADR 0207). `package.changed`. A list over the bus's cap on a value is not written: the key keeps its last value and `.not-current` says the size and since when, which the health says too. `pacman_packages`, `pacman_updates`, `pacman_installed` and `pacman_foreign` answer every node from these states, and what no - module declares from the node-engine's `packages` (read as `mesh-host.packages`, the engine's manifest's name), else the controller's `declared-packages` until its lease is written, without asking a machine. + module declares from the node-engine's `packages` (read as `mesh-host.packages`, the engine's manifest's name), without asking a machine; a node whose engine has written no key is named in the answer (`declared_none`) and the controller is not read (ADR 0565 clause 6). - **Upgrades as runs, one node at a time (ADR 0508).** Every full upgrade and every rollback is a run in the state `runs` at `.`, written at each step and emitted as it: the package record taken and read back (`records`, kept `record-days`), the DKMS gate before the transaction (a kernel an installed DKMS module's own diff --git a/modules/pacman/cmd/pacman-tools/keeper_manifest_test.go b/modules/pacman/cmd/pacman-tools/keeper_manifest_test.go index 61d4fc6a..59f04fbb 100644 --- a/modules/pacman/cmd/pacman-tools/keeper_manifest_test.go +++ b/modules/pacman/cmd/pacman-tools/keeper_manifest_test.go @@ -135,7 +135,7 @@ func TestTheKeeperDeclaresTheModulesStatesAndEvents(t *testing.T) { } // The verbs read which packages each node's modules declare; the process reads no other module's state // (ADR 0507 §5, ADR 0436 §1). - if got := strs(raw["reads"]); !reflect.DeepEqual(got, []string{delivery.EnginePackagesRead, "mesh-controller.declared-packages"}) { + if got := strs(raw["reads"]); !reflect.DeepEqual(got, []string{delivery.EnginePackagesRead}) { t.Errorf("reads: %v", got) } } diff --git a/modules/pacman/cmd/pacman-tools/view.go b/modules/pacman/cmd/pacman-tools/view.go index 4521429b..be671028 100644 --- a/modules/pacman/cmd/pacman-tools/view.go +++ b/modules/pacman/cmd/pacman-tools/view.go @@ -20,18 +20,13 @@ import ( // through the manifest's `reads`. const DeclaredPackagesState = SourceEngine -// ControllerDeclaredState is the controller's mirror of the same packages (ADR 0507 §5), the per-node fallback for a node -// whose engine has written no key yet, until the controller's lease is written away (ADR 0565 clause 6). -const ControllerDeclaredState = "mesh-controller.declared-packages" +// SourceEngine is the one source a node's declaration is read from, said in the answer: the node-engine's state, read by +// its manifest's name (mesh-host.packages, its bucket mesh-host_packages; mesh-sdk delivery.EnginePackagesRead). The +// controller's mirror of the same packages ended with its lease (ADR 0565 clause 6, issue 774): a node whose engine has +// written no key has no declaration here, and the answer says so. +const SourceEngine = delivery.EnginePackagesRead -// The sources a node's declaration is read from, said in the answer: the node-engine's state, read by its manifest's -// name (mesh-host.packages, its bucket mesh-host_packages; mesh-sdk delivery.EnginePackagesRead), and the mirror. -const ( - SourceEngine = delivery.EnginePackagesRead - SourceController = "mesh-controller.declared-packages" -) - -// declaredValue is a node's key of `packages`, in mesh-sdk's layout (the controller's mirror unchanged). +// declaredValue is a node's key of `packages`, in mesh-sdk's layout. type declaredValue = delivery.DeclaredPackages // nodeInstalled is one node's key of `installed` with its not-current mark. @@ -118,53 +113,42 @@ func (m *Machine) installedOn(node string) ([]nodeInstalled, error) { return out, nil } -// declaredOn is what each node's modules declare present, by node and package, with the declaring modules, and the source -// that answered for each node: its engine's key, else the controller's mirror; a node neither holds is missing. When the -// engine's state cannot be read at all (its bucket is not on the bus yet, or the read is refused) the mirror answers for -// every node and the engine's error is returned as a word to say beside the sources; it is an error only when the mirror -// cannot be read either (ADR 0565 clause 6). -func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, map[string]string, string, error) { +// declaredOn is what each node's modules declare present, by node and package, with the source that answered for each +// node (its engine's key), the nodes whose engine has written none, and a word when the engine's state cannot be read at +// all (its bucket is not on the bus yet, or the read is refused): then no node has a declaration and the word says why. +// Nothing else is read: the controller's mirror ended with its lease (ADR 0565 clause 6). +func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, map[string]string, []string, string) { out := map[string]map[string][]string{} from := map[string]string{} - engineErr := "" - for _, source := range []string{SourceEngine, SourceController} { - r := m.States(source) - read := map[string]map[string][]string{} - readFrom := map[string]string{} - var failed error - for _, n := range nodes { - if from[n] != "" { - continue - } - var v declaredValue - found, err := readValue(r, n, &v) - if err != nil { - failed = err - break - } - if !found { - continue - } - readFrom[n] = source - read[n] = map[string][]string{} - for _, p := range v.Packages { - if !p.Absent { - read[n][p.Package] = p.Modules - } - } + var none []string + r := m.States(SourceEngine) + for _, n := range nodes { + var v declaredValue + found, err := readValue(r, n, &v) + if err != nil { + return map[string]map[string][]string{}, map[string]string{}, nil, SourceEngine + " cannot be read: " + err.Error() } - if failed != nil { - if source == SourceEngine { - engineErr = SourceEngine + " cannot be read: " + failed.Error() - continue - } - return nil, nil, "", failed + if !found { + none = append(none, n) + continue } - for n, d := range read { - out[n], from[n] = d, readFrom[n] + from[n] = SourceEngine + out[n] = map[string][]string{} + for _, p := range v.Packages { + if !p.Absent { + out[n][p.Package] = p.Modules + } } } - return out, from, engineErr, nil + return out, from, none, "" +} + +// declaredWord says which nodes' engines have written no packages key. +func declaredWord(none []string) string { + if len(none) == 0 { + return "" + } + return "the node-engine has written no packages key for " + strings.Join(none, ", ") + ": no declaration is shown for them" } // NodeView is one node in the view. @@ -178,10 +162,10 @@ type NodeView struct { Current bool `json:"current"` NotCurrent *NotCurrent `json:"not_current,omitempty"` // Undeclared is the packages installed explicitly from a repository that no module assigned to the node - // declares; null when neither the node-engine nor the controller's mirror holds a declaration of the node. + // declares; null when the node-engine holds no declaration of the node. Undeclared []PackageVersion `json:"undeclared"` Foreign []PackageVersion `json:"foreign"` - // DeclaredFrom is the source that answered for the node's declaration; empty when neither holds one. + // DeclaredFrom is the source that answered for the node's declaration; empty when the engine holds none. DeclaredFrom string `json:"declared_from,omitempty"` } @@ -201,7 +185,7 @@ type PackageOnNode struct { Foreign bool `json:"foreign,omitempty"` DeclaredBy []string `json:"declared_by"` Current bool `json:"current"` - // DeclaredFrom is the source that answered for the node's declaration; empty when neither holds one. + // DeclaredFrom is the source that answered for the node's declaration; empty when the engine holds none. DeclaredFrom string `json:"declared_from,omitempty"` } @@ -217,10 +201,7 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) { for _, ni := range all { nodes = append(nodes, ni.Node) } - declared, sources, engineErr, err := m.declaredOn(nodes) - if err != nil { - return nil, err - } + declared, sources, none, engineErr := m.declaredOn(nodes) if name != "" { on := []PackageOnNode{} for _, ni := range all { @@ -242,6 +223,8 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) { answer := map[string]any{"package": name, "nodes": on} if engineErr != "" { answer["declared_engine_error"] = engineErr + } else if w := declaredWord(none); w != "" { + answer["declared_none"] = w } return answer, nil } @@ -294,6 +277,8 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) { out := map[string]any{"nodes": views} if engineErr != "" { out["declared_engine_error"] = engineErr + } else if w := declaredWord(none); w != "" { + out["declared_none"] = w } for key, list := range map[string][]Spread{"differing": differing, "some_nodes": some} { b := bound(key, list, limit) diff --git a/modules/pacman/cmd/pacman-tools/view_test.go b/modules/pacman/cmd/pacman-tools/view_test.go index 6e9dbd51..82c1f3a5 100644 --- a/modules/pacman/cmd/pacman-tools/view_test.go +++ b/modules/pacman/cmd/pacman-tools/view_test.go @@ -43,7 +43,7 @@ func viewStates(t *testing.T) (*fakeStates, *Machine) { } func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) { - states, m := viewStates(t) + _, m := viewStates(t) r, err := m.Packages("zsh", 500) if err != nil { t.Fatal(err) @@ -56,22 +56,22 @@ func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) { if a.DeclaredFrom != SourceEngine || b.DeclaredFrom != "" { t.Errorf("sources: %q, %q", a.DeclaredFrom, b.DeclaredFrom) } - // A node whose engine has written no key reads the controller's mirror, said so; a node with both reads its engine's. - var legacy, stale declaredValue - json.Unmarshal([]byte(`{"node":"node-b","packages":[{"package":"zsh","modules":["legacy"],"absent":false}]}`), &legacy) - json.Unmarshal([]byte(`{"node":"node-a","packages":[{"package":"zsh","modules":["stale"],"absent":false}]}`), &stale) - mirror := states.store(ControllerDeclaredState) - mirror.Put("node-b", legacy) - mirror.Put("node-a", stale) + // A node whose engine has written no key has no declaration, said so, and no controller state is read (774/S7/AC2); + // an engine whose state cannot be read at all is said once beside the sources, for every node (774/S7/AC1 reads the + // engine's key above). + read := m.States + m.States = func(name string) StateReader { + if name != SourceEngine && name != StateInstalled { + t.Errorf("state %s was read: only the engine's packages are", name) + } + return read(name) + } r2, _ := m.Packages("zsh", 500) on2 := r2["nodes"].([]PackageOnNode) - if on2[0].DeclaredFrom != SourceEngine || strings.Join(on2[0].DeclaredBy, ",") != "login,shell" || - on2[1].DeclaredFrom != SourceController || strings.Join(on2[1].DeclaredBy, ",") != "legacy" { - t.Errorf("fallback: %+v", on2) + if on2[0].DeclaredFrom != SourceEngine || on2[1].DeclaredFrom != "" || len(on2[1].DeclaredBy) != 0 || + !strings.Contains(fmt.Sprint(r2["declared_none"]), "no packages key for node-b") { + t.Errorf("a node with no key: %+v %v", on2, r2["declared_none"]) } - // The engine's state cannot be read (its bucket is not on the bus yet): the mirror answers for every node, and the - // answer says why beside declared_from; a mirror that cannot be read either is an error. - read := m.States m.States = func(name string) StateReader { if name == SourceEngine { return failingReader{errors.New("is issued and its bucket is not on the bus yet")} @@ -80,19 +80,10 @@ func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) { } r3, err3 := m.Packages("zsh", 500) on3, _ := r3["nodes"].([]PackageOnNode) - if err3 != nil || len(on3) != 2 || on3[0].DeclaredFrom != SourceController || strings.Join(on3[0].DeclaredBy, ",") != "stale" || - on3[1].DeclaredFrom != SourceController || !strings.Contains(fmt.Sprint(r3["declared_engine_error"]), "not on the bus yet") { + if err3 != nil || len(on3) != 2 || on3[0].DeclaredFrom != "" || on3[1].DeclaredFrom != "" || + !strings.Contains(fmt.Sprint(r3["declared_engine_error"]), "not on the bus yet") { t.Errorf("engine unreadable: %+v %v", r3, err3) } - m.States = func(name string) StateReader { - if name == SourceEngine || name == SourceController { - return failingReader{errors.New("refused")} - } - return read(name) - } - if _, err := m.Packages("zsh", 500); err == nil { - t.Error("neither state could be read and no error was said") - } m.States = read if a.Node != "node-a" || !a.Installed || a.Version != "5.9-2" || !a.Explicit || a.Repository != "extra" || strings.Join(a.DeclaredBy, ",") != "login,shell" || !a.Current { diff --git a/modules/pacman/module.json b/modules/pacman/module.json index 2213c136..307c79a4 100644 --- a/modules/pacman/module.json +++ b/modules/pacman/module.json @@ -12,8 +12,7 @@ } ], "reads": [ - "mesh-host.packages", - "mesh-controller.declared-packages" + "mesh-host.packages" ], "uses": [ "operator-channel"