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, asked.waiting) 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 Waiting []waitingMachine At string } type waitingMachine struct { Node string // Never told is not out of date: nobody has ever asked this machine to be anything. Same // remedy, different situation, and the page says which. Never bool } 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 _, m := range asked.waiting { out.Waiting = append(out.Waiting, waitingMachine{Node: m.Node, Never: m.Never}) } 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}} {{if .Waiting}}

Never told is not out of date: nobody has asked that machine to be anything yet. Both are sent by push --behind.

{{else}}

Every machine is running what the mesh would send it.

{{end}} {{end}} `))