From 92133c340b63f481333b69e3a3ffd8f080cef43b Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 31 Aug 2026 04:49:21 +0200 Subject: [PATCH] A board that reads through the same interfaces and holds nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit novox/hq 03-DESIGN/01-to-be/11-a-board.md, built. The board being replaced is one service reading every context's database directly — ADR 0008 violated by the one component with a reason to violate it. The cost is not hypothetical: a boundary nothing may cross can move, and one thing crossing it is enough to freeze it. A board that reads the provisioning tables breaks when provisioning changes them, and the change then gets weighed against the board. So the three questions are read once, by one function, for all three ways of saying them — a person's status, its JSON, and this page. Three implementations of "which machine is not doing what it was told" would be three chances to disagree. Refused and failed stay distinct all the way to the page: refused means the machine is exactly as it was and what is wrong is in what was sent; failed means it is in a state nobody declared. Different places to fix, so one word for both would send half the readers to the wrong one. It stores nothing, changes nothing, and every action it might offer already exists as a command. A board that cannot reach the mesh says so rather than rendering an empty page — an empty page says "nothing is wrong" in the one situation where nobody can know that. One test earns its place twice: a machine's own words are the whole reason the page is useful and the one thing on it nobody in this repository wrote, so they are shown and are not markup. --- cmd/mesh-control/board.go | 248 +++++++++++++++++++++++++++++++++ cmd/mesh-control/board_test.go | 121 ++++++++++++++++ cmd/mesh-control/main.go | 91 +++++++----- 3 files changed, 428 insertions(+), 32 deletions(-) create mode 100644 cmd/mesh-control/board.go create mode 100644 cmd/mesh-control/board_test.go diff --git a/cmd/mesh-control/board.go b/cmd/mesh-control/board.go new file mode 100644 index 0000000..1b8ae60 --- /dev/null +++ b/cmd/mesh-control/board.go @@ -0,0 +1,248 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "html/template" + "net/http" + "time" +) + +// boardCommand serves the three questions as a page. +// +// **It reads through the same functions everything else does and holds nothing** +// (novox/hq 03-DESIGN/01-to-be/11-a-board.md). The board being replaced is one service that reads +// every context's database directly — [ADR 0008](novox/hq) violated by the one component with a +// reason to violate it, and the cost is that a boundary nothing may cross can move, while one +// thing crossing it is enough to freeze it. A board that reads the provisioning tables is a board +// that breaks when provisioning changes its tables, and the change then gets weighed against the +// board. +// +// **It stores nothing of its own.** No cache that can disagree, no table of what the mesh looked +// like last time. Every request reads the mesh now; if that is slow, the answer belongs in the +// context that owns it, where everything else asking gets it too. +// +// **Reading is the whole of it.** Every action a board could offer already exists as a command, +// and a button that does something no command does is a second implementation of a decision. +func boardCommand(ctx context.Context, args []string) error { + set := flag.NewFlagSet("board", flag.ContinueOnError) + // The private network, not everything. A board says which machines are broken and what they + // are running, which is exactly the map somebody attacking this would like — and there is no + // reason for it to be reachable from further away than the mesh. + listen := set.String("listen", "127.0.0.1:8080", "where to serve it") + if _, err := parseAround(set, args); err != nil { + return err + } + + server := &http.Server{ + Addr: *listen, + ReadHeaderTimeout: 10 * time.Second, + Handler: board(), + } + fmt.Printf("the board is on http://%s\n", *listen) + fmt.Printf(" it reads the mesh on every request and keeps nothing\n") + + go func() { + <-ctx.Done() + closing, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = server.Shutdown(closing) + }() + if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil +} + +// board is the handler, separate so a test can drive it without a listener. +func board() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + asked, err := ask(r.Context()) + if err != nil { + // **Said, not blank.** A board that cannot reach the mesh and renders an empty page + // says "nothing is wrong" in the one situation where nobody can know that. + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusServiceUnavailable) + _ = page.Execute(w, view{Unreachable: err.Error()}) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := page.Execute(w, asked); err != nil { + // The page is half-written by now; there is nothing useful left to say to the + // browser, and saying it here is what stops the failure being silent. + fmt.Printf("the board could not render: %v\n", err) + } + }) + // The same answers for something that is not a person, from the same read. A board and a + // script disagreeing about which machine is broken would be worse than either alone. + mux.HandleFunc("/mesh.json", func(w http.ResponseWriter, r *http.Request) { + inv, err := openInventory(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer inv.Close() + asked, err := theThreeQuestions(r.Context(), inv) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + body, err := statusAsJSON(asked.wrong, asked.nodes, asked.quiet, asked.behind, asked.sources) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(append(body, '\n')) + }) + return mux +} + +// ask reads the mesh for one request. +func ask(ctx context.Context) (view, error) { + inv, err := openInventory(ctx) + if err != nil { + return view{}, err + } + defer inv.Close() + + asked, err := theThreeQuestions(ctx, inv) + if err != nil { + return view{}, err + } + return viewOf(asked), nil +} + +// view is what the page is given. Nothing is derived here that the reader could not derive. +type view struct { + Unreachable string + Machines int + Broken []brokenMachine + Quiet []quietMachine + Behind []staleModule + At string +} + +type brokenMachine struct { + Node string + // Outcome is refused or failed, and stays distinct all the way to the page. **Refused means + // the machine is exactly as it was and what is wrong is in what was sent; failed means it is + // in a state nobody declared and what is wrong is on the machine.** They are fixed in + // different places, so one word for both would send half the readers to the wrong one. + Outcome string + Said []string + When string +} + +type quietMachine struct { + Node string + // Heard is "never" or how long ago. Never heard from is not the same as quiet for a while: + // one may be a machine that was never sent anything. + Heard string +} + +type staleModule struct { + Module string + Holds string + Source string + Running []string +} + +func viewOf(asked answers) view { + out := view{Machines: len(asked.nodes), At: time.Now().Format("15:04:05")} + for _, d := range asked.wrong { + one := brokenMachine{Node: d.Node, Outcome: d.Outcome, + When: d.At.Local().Format("2006-01-02 15:04")} + if d.Refused != "" { + // The host's own words. It says exactly what it could not accept, and nothing + // written here would say it better. + one.Said = append(one.Said, firstLine(d.Refused)) + } + for _, f := range d.Failed { + one.Said = append(one.Said, f.ID+": "+firstLine(f.Error)) + } + out.Broken = append(out.Broken, one) + } + for _, n := range asked.quiet { + out.Quiet = append(out.Quiet, quietMachine{Node: n.Name, Heard: heardFrom(n)}) + } + for module, on := range asked.behind { + from := asked.sources[module] + out.Behind = append(out.Behind, staleModule{ + Module: module, Holds: short(from.BuiltFrom), Source: short(from.Head), Running: on, + }) + } + return out +} + +// The page. Deliberately one file with no assets: a board that cannot render without fetching +// something is a board that is blank exactly when the mesh is unwell. +var page = template.Must(template.New("board").Parse(` + + +the mesh + +{{if .Unreachable}} +

the mesh cannot be read

+

{{.Unreachable}}

+

This says nothing about whether the mesh is well — only that this page could not +find out.

+{{else}} +

{{.Machines}} machine{{if ne .Machines 1}}s{{end}}

+ +

Is anything broken?

+{{if .Broken}} + +{{else}}

No. Every machine is doing what it was told.

{{end}} + +

Is anything not answering?

+{{if .Quiet}} + +

Not heard from is not the same as tried and could not — a machine here may be +new, switched off, or unreachable.

+{{else}}

No. Every machine has been heard from.

{{end}} + +

Is anything out of date?

+{{if .Behind}} + +{{else}}

No. Every module is what its source last had.

{{end}} +{{end}} + + +`)) diff --git a/cmd/mesh-control/board_test.go b/cmd/mesh-control/board_test.go new file mode 100644 index 0000000..e257080 --- /dev/null +++ b/cmd/mesh-control/board_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "strings" + "testing" + "time" + + "github.com/novox/mesh-control/internal/inventory" +) + +// Refused and failed stay distinct all the way to the page. +// +// **Refused means the machine is exactly as it was and what is wrong is in what was sent; failed +// means it is in a state nobody declared and what is wrong is on the machine.** They are fixed in +// different places, so a page saying "error" for both sends half its readers to the wrong one. +func TestRefusedAndFailedReachThePageAsDifferentThings(t *testing.T) { + rendered := render(t, viewOf(answers{ + nodes: []inventory.Node{{Name: "anchor"}, {Name: "laptop"}}, + wrong: []inventory.Doing{ + {Node: "anchor", Outcome: "refused", Refused: "not a declaration this host speaks", + At: time.Now()}, + {Node: "laptop", Outcome: "failed", At: time.Now(), + Failed: []inventory.FailedResource{{ID: "web.container", Error: "no such image"}}}, + }, + })) + // The rendered outcome, not the word anywhere on the page: both words appear in the + // stylesheet, so a plain Contains passes whatever the machine actually said. It did. + for _, want := range []string{`refused`, + `failed`} { + if !strings.Contains(rendered, want) { + t.Fatalf("the page does not distinguish the two — missing %s:\n%s", want, rendered) + } + } + // And the host's own words, which say exactly what it could not accept. + for _, want := range []string{"not a declaration this host speaks", "web.container", "no such image"} { + if !strings.Contains(rendered, want) { + t.Fatalf("the page does not say %q, so a reader must go and ask:\n%s", want, rendered) + } + } +} + +// Nothing wrong is said, not left blank. An empty page and a well mesh must not look alike. +func TestAWellMeshSaysSoRatherThanShowingNothing(t *testing.T) { + rendered := render(t, viewOf(answers{nodes: []inventory.Node{{Name: "anchor"}}})) + for _, want := range []string{ + "Every machine is doing what it was told", + "Every machine has been heard from", + "Every module is what its source last had", + } { + if !strings.Contains(rendered, want) { + t.Fatalf("a well mesh does not say %q:\n%s", want, rendered) + } + } +} + +// A board that cannot reach the mesh must not render an empty page: that says "nothing is wrong" +// in the one situation where nobody can know it. +func TestABoardThatCannotReadTheMeshSaysSo(t *testing.T) { + rendered := render(t, view{Unreachable: "the store did not answer"}) + if !strings.Contains(rendered, "the store did not answer") { + t.Fatalf("the reason is not on the page:\n%s", rendered) + } + if strings.Contains(rendered, "Every machine is doing what it was told") { + t.Fatal("a board that could not read the mesh reported that the mesh is well") + } +} + +// Quiet is not broken, and the page says which it is. +func TestQuietIsNotReportedAsBroken(t *testing.T) { + rendered := render(t, viewOf(answers{ + nodes: []inventory.Node{{Name: "laptop"}}, + quiet: []inventory.Node{{Name: "laptop"}}, + })) + if !strings.Contains(rendered, "not the same as tried and could not") { + t.Fatalf("the page does not separate quiet from broken:\n%s", rendered) + } + if !strings.Contains(rendered, "Every machine is doing what it was told") { + t.Fatalf("a quiet machine was counted as broken:\n%s", rendered) + } +} + +// The page renders what a machine said, and a hostile string in it is not markup. +// +// What is on this page comes from machines, and a machine's own words are the whole reason the +// page is useful. They are also the one thing here that nobody in this repository wrote. +func TestWhatAMachineSaidIsNotMarkup(t *testing.T) { + rendered := render(t, viewOf(answers{ + nodes: []inventory.Node{{Name: "anchor"}}, + wrong: []inventory.Doing{{Node: "anchor", Outcome: "refused", + Refused: ``, At: time.Now()}}, + })) + if strings.Contains(rendered, "