pacman falls back to the controller's mirror for every node when the engine's state cannot be read, and says the engine's error (ADR 0565 clause 6, review round 2)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -118,12 +118,19 @@ func (m *Machine) installedOn(node string) ([]nodeInstalled, error) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, map[string]string, error) {
|
||||
// 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) {
|
||||
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
|
||||
@@ -131,21 +138,32 @@ func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, ma
|
||||
var v declaredValue
|
||||
found, err := readValue(r, n, &v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
failed = err
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
from[n] = source
|
||||
out[n] = map[string][]string{}
|
||||
readFrom[n] = source
|
||||
read[n] = map[string][]string{}
|
||||
for _, p := range v.Packages {
|
||||
if !p.Absent {
|
||||
out[n][p.Package] = p.Modules
|
||||
read[n][p.Package] = p.Modules
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed != nil {
|
||||
if source == SourceEngine {
|
||||
engineErr = SourceEngine + " cannot be read: " + failed.Error()
|
||||
continue
|
||||
}
|
||||
return nil, nil, "", failed
|
||||
}
|
||||
for n, d := range read {
|
||||
out[n], from[n] = d, readFrom[n]
|
||||
}
|
||||
}
|
||||
return out, from, nil
|
||||
return out, from, engineErr, nil
|
||||
}
|
||||
|
||||
// NodeView is one node in the view.
|
||||
@@ -198,7 +216,7 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) {
|
||||
for _, ni := range all {
|
||||
nodes = append(nodes, ni.Node)
|
||||
}
|
||||
declared, sources, err := m.declaredOn(nodes)
|
||||
declared, sources, engineErr, err := m.declaredOn(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -220,7 +238,11 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) {
|
||||
}
|
||||
on = append(on, p)
|
||||
}
|
||||
return map[string]any{"package": name, "nodes": on}, nil
|
||||
answer := map[string]any{"package": name, "nodes": on}
|
||||
if engineErr != "" {
|
||||
answer["declared_engine_error"] = engineErr
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
views := []NodeView{}
|
||||
versions := map[string]map[string]string{}
|
||||
@@ -269,6 +291,9 @@ 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
|
||||
}
|
||||
for key, list := range map[string][]Spread{"differing": differing, "some_nodes": some} {
|
||||
b := bound(key, list, limit)
|
||||
out[key] = b[key]
|
||||
|
||||
@@ -6,10 +6,13 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// viewStates is two nodes' packages on the bus: node-a current and declared, node-b over the cap since a while,
|
||||
@@ -66,6 +69,31 @@ func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) {
|
||||
on2[1].DeclaredFrom != SourceController || strings.Join(on2[1].DeclaredBy, ",") != "legacy" {
|
||||
t.Errorf("fallback: %+v", on2)
|
||||
}
|
||||
// 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")}
|
||||
}
|
||||
return read(name)
|
||||
}
|
||||
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") {
|
||||
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 {
|
||||
t.Errorf("node-a: %+v", a)
|
||||
@@ -174,3 +202,10 @@ func TestANodeArgumentIsOneKeyToken(t *testing.T) {
|
||||
t.Errorf("%q %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
// failingReader is a state that cannot be read.
|
||||
type failingReader struct{ err error }
|
||||
|
||||
func (f failingReader) Get(string) (*stdio.StateEntry, error) { return nil, f.err }
|
||||
func (f failingReader) Keys() ([]string, error) { return nil, f.err }
|
||||
func (f failingReader) History(string) ([]stdio.StateRevision, error) { return nil, f.err }
|
||||
|
||||
Reference in New Issue
Block a user