The grants part composes the bus's user list with the composer the controller uses (ADR 0547 §5), copied rather than moved: the controller is not changed and keeps its own until it loses the lease. Left out: the bus client, the broker's address from the environment and the client's two methods; the lease's two subjects are copied in. One table test holds the copy to four meshes' lists and the server golden, written by the controller's own internal/broker from the same fixtures (testdata/controller-user-lists.json).
140 lines
5.4 KiB
Go
140 lines
5.4 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Whether a mesh could move its bus, and what is missing if not.
|
|
//
|
|
// **Asked before anything moves, and answerable from records alone.** The rollout moves every node at
|
|
// once (novox/hq ADR 0116 step 5), so there is no partial state to inspect afterwards and no half to
|
|
// roll back: either the mesh was ready or it was not. That makes a readiness question the most
|
|
// valuable thing here — it costs nothing, it can be asked of a running mesh any number of times, and
|
|
// every answer is a thing somebody can go and fix.
|
|
//
|
|
// Deliberately pure. It is handed what the mesh knows and returns sentences; nothing here connects to
|
|
// anything, so it can be asked on a workstation about a mesh it has never reached.
|
|
|
|
// Readiness is what the mesh knows about its own ability to move.
|
|
type Readiness struct {
|
|
// TheBus is the address the mesh's own traffic would move to, empty when nothing names one.
|
|
TheBus string
|
|
// ServerStanding is whether a bus is reachable at that address, as somebody checked.
|
|
ServerStanding bool
|
|
// Holder is the node running the module that holds the bus seat, empty when nothing does.
|
|
Holder string
|
|
// AccountsComposed is whether that node has been sent the composed user list.
|
|
AccountsComposed bool
|
|
// Nodes is every machine the mesh knows.
|
|
Nodes []string
|
|
// Credentialled is which of them has a credential for the new bus.
|
|
Credentialled map[string]bool
|
|
// Modules is every assigned module, as `<node>/<module>`.
|
|
Modules []string
|
|
// ModuleCredentialled is which of those has one.
|
|
ModuleCredentialled map[string]bool
|
|
}
|
|
|
|
// NotReady is every reason this mesh cannot move its bus yet, in the order somebody would fix them.
|
|
//
|
|
// Empty means ready. **Each entry names one thing and what to do about it**, because a readiness check
|
|
// that says "not ready" is a check nobody can act on — and this is read at the point where the next
|
|
// step is irreversible.
|
|
func NotReady(r Readiness) []string {
|
|
var why []string
|
|
|
|
if strings.TrimSpace(r.TheBus) == "" {
|
|
why = append(why, "nothing names the bus to move to: set "+NATSVar+" on the control node "+
|
|
"to the address the new server answers on")
|
|
}
|
|
if !r.ServerStanding {
|
|
why = append(why, "no bus is answering at that address. Step 2 of the change raises it beside "+
|
|
"the one the mesh is on, carrying nothing — assign the module that holds "+
|
|
"mesh-broker and push the machine that runs it")
|
|
}
|
|
if r.Holder == "" {
|
|
why = append(why, "no machine holds mesh-broker, so nothing would compose the bus's user "+
|
|
"list. Assign the module that claims it")
|
|
} else if !r.AccountsComposed {
|
|
why = append(why, fmt.Sprintf(
|
|
"%s holds mesh-broker and has not been sent the composed user list, so the bus would "+
|
|
"refuse every connection. `push %s`", r.Holder, r.Holder))
|
|
}
|
|
|
|
// A node with no credential cannot come back after the move, and a node that cannot come back is
|
|
// a machine the mesh has lost until somebody goes to it.
|
|
var missing []string
|
|
for _, n := range r.Nodes {
|
|
if !r.Credentialled[n] {
|
|
missing = append(missing, n)
|
|
}
|
|
}
|
|
sort.Strings(missing)
|
|
if len(missing) > 0 {
|
|
why = append(why, fmt.Sprintf(
|
|
"%d machine(s) have no credential for the new bus and would not come back: %s. Each needs "+
|
|
"one minted before the move, not after — after, there is no bus to ask over",
|
|
len(missing), strings.Join(missing, ", ")))
|
|
}
|
|
|
|
// A module without one keeps running and stops being reachable, which is a smaller fault and still
|
|
// one somebody should choose rather than discover.
|
|
var quiet []string
|
|
for _, m := range r.Modules {
|
|
if !r.ModuleCredentialled[m] {
|
|
quiet = append(quiet, m)
|
|
}
|
|
}
|
|
sort.Strings(quiet)
|
|
if len(quiet) > 0 {
|
|
why = append(why, fmt.Sprintf(
|
|
"%d module(s) have no credential for the new bus: %s. Each keeps serving and stops "+
|
|
"answering tools and hearing events until it is issued one",
|
|
len(quiet), strings.Join(quiet, ", ")))
|
|
}
|
|
|
|
return why
|
|
}
|
|
|
|
// WhatMoves is what the rollout would do, in order, for somebody reading before they commit.
|
|
//
|
|
// **Written out rather than summarised.** This is the one step with nothing to inspect afterwards, so
|
|
// the last useful moment to disagree with it is while reading this.
|
|
func WhatMoves(r Readiness) []string {
|
|
out := []string{
|
|
fmt.Sprintf("compose the bus's user list and send it to %s", holderOr(r.Holder)),
|
|
fmt.Sprintf("move this control plane to %s, and confirm it is heard", busOr(r.TheBus)),
|
|
}
|
|
nodes := append([]string(nil), r.Nodes...)
|
|
sort.Strings(nodes)
|
|
for _, n := range nodes {
|
|
out = append(out, fmt.Sprintf("move %s, and confirm it reports", n))
|
|
}
|
|
if len(r.Modules) > 0 {
|
|
out = append(out, fmt.Sprintf("move %d module runtime(s), and confirm each answers",
|
|
len(r.Modules)))
|
|
}
|
|
// **The old broker goes, and it goes last** (novox/hq ADR 0131). AMQP is not a provision, so once
|
|
// every machine reports on the new bus nothing of the mesh is left speaking to it, and its module
|
|
// is unassigned. Said as a step so nobody reads the move as leaving a second bus behind.
|
|
out = append(out, "then unassign the old broker's module: AMQP is not a provision (ADR 0131), and "+
|
|
"once every machine reports on the new bus nothing of the mesh speaks to it")
|
|
return out
|
|
}
|
|
|
|
func holderOr(node string) string {
|
|
if node == "" {
|
|
return "whichever machine holds mesh-broker"
|
|
}
|
|
return node
|
|
}
|
|
|
|
func busOr(address string) string {
|
|
if address == "" {
|
|
return "the new bus"
|
|
}
|
|
return address
|
|
}
|