pacman reads a node's declared packages from the node-engine alone (ADR 0565 clause 6)
The controller's declared-packages mirror ended with its lease: the view no longer falls back to it, the manifest no longer reads it, and a node whose engine wrote no key is named in the answer (declared_none) instead. The fallback rows of the view test became the no-key and unreadable-engine cases; no test added. (issue 774, 774/S7) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -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
|
||||
`<node>.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 `<node>.<run>`, 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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
}
|
||||
],
|
||||
"reads": [
|
||||
"mesh-host.packages",
|
||||
"mesh-controller.declared-packages"
|
||||
"mesh-host.packages"
|
||||
],
|
||||
"uses": [
|
||||
"operator-channel"
|
||||
|
||||
Reference in New Issue
Block a user