A command opens each store once, not once per machine

Working out what a machine should be reaches the identity context for its
certificate and the licence context for its model access. Both were opened —
and waited on — inside functions called for every node in a push. Two machines
hid it. Fifty would be fifty connect-and-wait cycles for data that does not
change while the push runs.

So a command holds what it has open, and passes it. Each context is opened on
first use rather than up front, because most commands need one and paying to
reach three would be the same waste from the other side.

The contexts stay separate, which is the point: this is one struct holding
three connections to three databases, not one connection to a shared one. No
context reaches another's store, and each still holds only its own credential
(novox/hq ADR 0008).

A pure move again — the gate is green before and after, and no test changed.
This commit is contained in:
2026-08-31 13:35:39 +02:00
parent 8623613704
commit 6eeb10f066
11 changed files with 177 additions and 91 deletions
+6 -6
View File
@@ -83,13 +83,13 @@ func board() http.Handler {
// The same answers for something that is not a person, from the same read. A board and a // 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. // script disagreeing about which machine is broken would be worse than either alone.
mux.HandleFunc("/mesh.json", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/mesh.json", func(w http.ResponseWriter, r *http.Request) {
inv, err := openInventory(r.Context()) open, err := openStores(r.Context())
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
return return
} }
defer inv.Close() defer open.Close()
asked, err := theThreeQuestions(r.Context(), inv) asked, err := theThreeQuestions(r.Context(), open)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
return return
@@ -108,13 +108,13 @@ func board() http.Handler {
// ask reads the mesh for one request. // ask reads the mesh for one request.
func ask(ctx context.Context) (view, error) { func ask(ctx context.Context) (view, error) {
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return view{}, err return view{}, err
} }
defer inv.Close() defer open.Close()
asked, err := theThreeQuestions(ctx, inv) asked, err := theThreeQuestions(ctx, open)
if err != nil { if err != nil {
return view{}, err return view{}, err
} }
+9 -6
View File
@@ -98,11 +98,12 @@ func buildsCommand(ctx context.Context, args []string) error {
return errors.New("builds [<module>] [-n N]") return errors.New("builds [<module>] [-n N]")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
builds, err := inv.Builds(ctx, module, *limit) builds, err := inv.Builds(ctx, module, *limit)
if err != nil { if err != nil {
@@ -262,11 +263,12 @@ func builderCommand(ctx context.Context, args []string) error {
// reason one broken module no longer blocks a machine's whole declaration: a mesh where one bad // reason one broken module no longer blocks a machine's whole declaration: a mesh where one bad
// repository holds back nine good ones is a mesh where nobody dares add the tenth. // repository holds back nine good ones is a mesh where nobody dares add the tenth.
func buildBehind(ctx context.Context, wait time.Duration) error { func buildBehind(ctx context.Context, wait time.Duration) error {
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
held, err := inv.Catalogued(ctx) held, err := inv.Catalogued(ctx)
if err != nil { if err != nil {
@@ -350,11 +352,12 @@ func buildOne(ctx context.Context, repository, ref string, wait time.Duration) e
// Kept before it is judged. A failed build that leaves no trace is indistinguishable from one // Kept before it is judged. A failed build that leaves no trace is indistinguishable from one
// nobody asked for, and the difference is the whole of whether somebody should be looking at // nobody asked for, and the difference is the whole of whether somebody should be looking at
// something. // something.
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
if err := inv.RecordBuild(ctx, buildFrom(result)); err != nil { if err := inv.RecordBuild(ctx, buildFrom(result)); err != nil {
return err return err
} }
+3 -2
View File
@@ -194,11 +194,12 @@ func licenceKey(ctx context.Context, args []string) error {
} }
defer held.Close() defer held.Close()
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
sealed, err := held.Accept(ctx, name, value, func(node string) (string, error) { sealed, err := held.Accept(ctx, name, value, func(node string) (string, error) {
return inv.SealingKeyOf(ctx, node) return inv.SealingKeyOf(ctx, node)
+13 -9
View File
@@ -50,11 +50,12 @@ func moduleCommand(ctx context.Context, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("module add <file>, module list, or module forget <name>") return errors.New("module add <file>, module list, or module forget <name>")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
switch args[0] { switch args[0] {
case "add": case "add":
@@ -202,11 +203,12 @@ func assignCommand(ctx context.Context, verb string, args []string) error {
if len(args) != 2 { if len(args) != 2 {
return fmt.Errorf("%s <node> <module>", verb) return fmt.Errorf("%s <node> <module>", verb)
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
if verb == "unassign" { if verb == "unassign" {
if err := inv.Unassign(ctx, args[0], args[1]); err != nil { if err := inv.Unassign(ctx, args[0], args[1]); err != nil {
@@ -223,7 +225,7 @@ func assignCommand(ctx context.Context, verb string, args []string) error {
// Resolved immediately, because an assignment that cannot be applied should be said now // Resolved immediately, because an assignment that cannot be applied should be said now
// rather than at the next push. The assignment is kept either way: it is what a person meant, // rather than at the next push. The assignment is kept either way: it is what a person meant,
// and the refusal is about the set rather than about this one. // and the refusal is about the set rather than about this one.
if _, _, err := planFor(ctx, inv, args[0]); err != nil { if _, _, err := planFor(ctx, open, args[0]); err != nil {
fmt.Println() fmt.Println()
return err return err
} }
@@ -235,11 +237,12 @@ func settingsCommand(ctx context.Context, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("settings set <module> <file> [--node <node>], or settings clear <module> [--node <node>]") return errors.New("settings set <module> <file> [--node <node>], or settings clear <module> [--node <node>]")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
set := flag.NewFlagSet("settings", flag.ContinueOnError) set := flag.NewFlagSet("settings", flag.ContinueOnError)
node := set.String("node", "", "one machine, rather than the whole mesh") node := set.String("node", "", "one machine, rather than the whole mesh")
@@ -321,11 +324,12 @@ func pinCommand(ctx context.Context, args []string, setting bool) error {
if !setting && len(args) != 2 { if !setting && len(args) != 2 {
return errors.New("unpin <node> <provision>") return errors.New("unpin <node> <provision>")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
if !setting { if !setting {
if err := inv.UnpinProvision(ctx, args[0], args[1]); err != nil { if err := inv.UnpinProvision(ctx, args[0], args[1]); err != nil {
+15 -11
View File
@@ -39,17 +39,18 @@ func overlayCommand(ctx context.Context, args []string) error {
"what its assignments resolve to — the two are computed from one picture of the " + "what its assignments resolve to — the two are computed from one picture of the " +
"mesh, and sending them separately would let them disagree") "mesh, and sending them separately would let them disagree")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
switch args[0] { switch args[0] {
case "place": case "place":
return overlayPlace(ctx, inv, args[1:]) return overlayPlace(ctx, inv, args[1:])
case "show": case "show":
return overlayShow(ctx, inv) return overlayShow(ctx, open)
default: default:
return fmt.Errorf("overlay has no %q; it has place and show", args[0]) return fmt.Errorf("overlay has no %q; it has place and show", args[0])
@@ -145,8 +146,9 @@ func network(ctx context.Context, inv *inventory.Inventory, on map[string]bool,
} }
// graph is the whole mesh's network, for showing it. // graph is the whole mesh's network, for showing it.
func graph(ctx context.Context, inv *inventory.Inventory) ([]overlay.Node, overlay.Graph, error) { func graph(ctx context.Context, open *stores) ([]overlay.Node, overlay.Graph, error) {
on, refused, err := whoResolves(ctx, inv, overlay.Requirement) inv := open.inventory
on, refused, err := whoResolves(ctx, open, overlay.Requirement)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -167,8 +169,9 @@ func graph(ctx context.Context, inv *inventory.Inventory) ([]overlay.Node, overl
// Resolved rather than read from the assignment table, because a module can arrive by being // Resolved rather than read from the assignment table, because a module can arrive by being
// required by something else, and a machine that needs the private network to do its job is on it // required by something else, and a machine that needs the private network to do its job is on it
// for the same reason as one that was handed it directly. // for the same reason as one that was handed it directly.
func whoResolves(ctx context.Context, inv *inventory.Inventory, requirement string) ( func whoResolves(ctx context.Context, open *stores, requirement string) (
map[string]bool, map[string]string, error) { map[string]bool, map[string]string, error) {
inv := open.inventory
nodes, err := inv.Nodes(ctx) nodes, err := inv.Nodes(ctx)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -178,7 +181,7 @@ func whoResolves(ctx context.Context, inv *inventory.Inventory, requirement stri
// the rest being described, and whoever is rendering that node will raise it themselves. // the rest being described, and whoever is rendering that node will raise it themselves.
refused := map[string]string{} refused := map[string]string{}
for _, n := range nodes { for _, n := range nodes {
plan, _, err := planFor(ctx, inv, n.Name) plan, _, err := planFor(ctx, open, n.Name)
if err != nil { if err != nil {
refused[n.Name] = err.Error() refused[n.Name] = err.Error()
continue continue
@@ -195,9 +198,10 @@ func whoResolves(ctx context.Context, inv *inventory.Inventory, requirement stri
} }
// rendering is everything a declaration needs, computed over the whole mesh. // rendering is everything a declaration needs, computed over the whole mesh.
func generators(ctx context.Context, inv *inventory.Inventory) ( func generators(ctx context.Context, open *stores) (
map[string]catalogue.Generator, error) { map[string]catalogue.Generator, error) {
on, refused, err := whoResolves(ctx, inv, overlay.Addressing) inv := open.inventory
on, refused, err := whoResolves(ctx, open, overlay.Addressing)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -214,8 +218,8 @@ func generators(ctx context.Context, inv *inventory.Inventory) (
}, nil }, nil
} }
func overlayShow(ctx context.Context, inv *inventory.Inventory) error { func overlayShow(ctx context.Context, open *stores) error {
nodes, computed, err := graph(ctx, inv) nodes, computed, err := graph(ctx, open)
if err != nil { if err != nil {
return err return err
} }
+6 -4
View File
@@ -24,11 +24,12 @@ func nodeCommand(ctx context.Context, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("node add <name>, node list, or node show <name>") return errors.New("node add <name>, node list, or node show <name>")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
switch args[0] { switch args[0] {
case "show": case "show":
@@ -89,11 +90,12 @@ func tokenCommand(ctx context.Context, args []string) error {
"machine the mesh already has a record for, the second is one it has never seen") "machine the mesh already has a record for, the second is one it has never seen")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
name := *existing name := *existing
if *fresh != "" { if *fresh != "" {
+26 -25
View File
@@ -21,7 +21,8 @@ import (
// nothing in it was wrong, and no one edit was the one that should have been a new file. // nothing in it was wrong, and no one edit was the one that should have been a new file.
// planFor works out everything a node should run, from what was assigned to it. // planFor works out everything a node should run, from what was assigned to it.
func planFor(ctx context.Context, inv *inventory.Inventory, nodeName string) (catalogue.Resolution, catalogue.SettingsBy, error) { func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Resolution, catalogue.SettingsBy, error) {
inv := open.inventory
shelf, err := inv.Catalogue(ctx) shelf, err := inv.Catalogue(ctx)
if err != nil { if err != nil {
return catalogue.Resolution{}, nil, err return catalogue.Resolution{}, nil, err
@@ -63,7 +64,7 @@ func planFor(ctx context.Context, inv *inventory.Inventory, nodeName string) (ca
// What this mesh can answer with a record rather than a machine, and which record each of // What this mesh can answer with a record rather than a machine, and which record each of
// this node's modules was put on. Read across a context boundary by name, which is what // this node's modules was put on. Read across a context boundary by name, which is what
// crossing one is allowed to carry (novox/hq ADR 0008). // crossing one is allowed to carry (novox/hq ADR 0008).
world.Licences, world.Using, err = licencesFor(ctx, nodeName) world.Licences, world.Using, err = licencesFor(ctx, open, nodeName)
if err != nil { if err != nil {
return catalogue.Resolution{}, nil, err return catalogue.Resolution{}, nil, err
} }
@@ -83,7 +84,7 @@ func planFor(ctx context.Context, inv *inventory.Inventory, nodeName string) (ca
// Answered by something the mesh holds, so there is no pair-wise secret between two // Answered by something the mesh holds, so there is no pair-wise secret between two
// machines. Its key was supplied by a person and sealed to this node then; the mesh // machines. Its key was supplied by a person and sealed to this node then; the mesh
// discarded the plaintext and cannot make another. // discarded the plaintext and cannot make another.
sealed, err := keyFor(ctx, n.From, nodeName, n.For) sealed, err := keyFor(ctx, open, n.From, nodeName, n.For)
if err != nil { if err != nil {
return catalogue.Resolution{}, nil, err return catalogue.Resolution{}, nil, err
} }
@@ -233,21 +234,22 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory,
// One place, because there were three and one of them was written before credentials existed and // One place, because there were three and one of them was written before credentials existed and
// silently produced a declaration missing them — a difference between what `plan` showed and what // silently produced a declaration missing them — a difference between what `plan` showed and what
// `plan --json` handed to anything reading it. // `plan --json` handed to anything reading it.
func declarationFor(ctx context.Context, inv *inventory.Inventory, node string, func declarationFor(ctx context.Context, open *stores, node string,
plan catalogue.Resolution, settings catalogue.SettingsBy) ([]map[string]any, error) { plan catalogue.Resolution, settings catalogue.SettingsBy) ([]map[string]any, error) {
gens, err := generators(ctx, inv) gens, err := generators(ctx, open)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return declarationWith(ctx, inv, node, plan, settings, gens) return declarationWith(ctx, open, node, plan, settings, gens)
} }
// declarationWith is the same, for a caller that has already worked out the generators once and // declarationWith is the same, for a caller that has already worked out the generators once and
// is about to use them for every node. // is about to use them for every node.
func declarationWith(ctx context.Context, inv *inventory.Inventory, node string, func declarationWith(ctx context.Context, open *stores, node string,
plan catalogue.Resolution, settings catalogue.SettingsBy, plan catalogue.Resolution, settings catalogue.SettingsBy,
gens map[string]catalogue.Generator) ([]map[string]any, error) { gens map[string]catalogue.Generator) ([]map[string]any, error) {
grants, err := grantsFor(ctx, inv, node) inv := open.inventory
grants, err := grantsFor(ctx, open, node)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -274,7 +276,7 @@ func declarationWith(ctx context.Context, inv *inventory.Inventory, node string,
if m.Certificate == nil { if m.Certificate == nil {
continue continue
} }
issued, meshCA, err := certificateFor(ctx, inv, node) issued, meshCA, err := certificateFor(ctx, open, node)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -310,12 +312,12 @@ func declarationWith(ctx context.Context, inv *inventory.Inventory, node string,
// the machine and whether it is on the private network, `identity` holds the authority and the // the machine and whether it is on the private network, `identity` holds the authority and the
// key that machine reported. The process holding both grants asks each for its part // key that machine reported. The process holding both grants asks each for its part
// (novox/hq ADR 0008). // (novox/hq ADR 0008).
func certificateFor(ctx context.Context, inv *inventory.Inventory, node string) (string, string, error) { func certificateFor(ctx context.Context, open *stores, node string) (string, string, error) {
ident, err := openIdentity(ctx) inv := open.inventory
ident, err := open.Identity(ctx)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
defer ident.Close()
record, err := inv.NodeByName(ctx, node) record, err := inv.NodeByName(ctx, node)
if err != nil { if err != nil {
@@ -371,7 +373,8 @@ func certificateFor(ctx context.Context, inv *inventory.Inventory, node string)
// The mirror of what a consumer is given, and the half that makes the credential real: a password // The mirror of what a consumer is given, and the half that makes the credential real: a password
// nothing was told to create is a password that authenticates nowhere. Sealed to this node, so // nothing was told to create is a password that authenticates nowhere. Sealed to this node, so
// the mesh hands over something it cannot itself use. // the mesh hands over something it cannot itself use.
func grantsFor(ctx context.Context, inv *inventory.Inventory, node string) ([]catalogue.Grant, error) { func grantsFor(ctx context.Context, open *stores, node string) ([]catalogue.Grant, error) {
inv := open.inventory
issued, err := inv.SecretsFrom(ctx, node) issued, err := inv.SecretsFrom(ctx, node)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -393,7 +396,7 @@ func grantsFor(ctx context.Context, inv *inventory.Inventory, node string) ([]ca
// can do nothing with it, and the name a consumer wants is the consumer's to say. // can do nothing with it, and the name a consumer wants is the consumer's to say.
out := make([]catalogue.Grant, 0, len(issued)) out := make([]catalogue.Grant, 0, len(issued))
for _, s := range issued { for _, s := range issued {
plan, settings, err := planFor(ctx, inv, s.Consumer) plan, settings, err := planFor(ctx, open, s.Consumer)
if err != nil { if err != nil {
// Their set does not resolve. Skipped rather than fatal: this node is not the place // Their set does not resolve. Skipped rather than fatal: this node is not the place
// to report another machine's problem, and a grant for something that is not going to // to report another machine's problem, and a grant for something that is not going to
@@ -428,13 +431,13 @@ func planCommand(ctx context.Context, args []string) error {
return errors.New("plan <node> [--files] [--json]") return errors.New("plan <node> [--files] [--json]")
} }
args = positionals args = positionals
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
plan, settings, err := planFor(ctx, inv, args[0]) plan, settings, err := planFor(ctx, open, args[0])
if err != nil { if err != nil {
return err return err
} }
@@ -443,7 +446,7 @@ func planCommand(ctx context.Context, args []string) error {
return nil return nil
} }
if *asJSON { if *asJSON {
resources, err := declarationFor(ctx, inv, args[0], plan, settings) resources, err := declarationFor(ctx, open, args[0], plan, settings)
if err != nil { if err != nil {
return err return err
} }
@@ -469,7 +472,7 @@ func planCommand(ctx context.Context, args []string) error {
for _, n := range plan.Needs { for _, n := range plan.Needs {
fmt.Printf(" needs %s from %s, for %s\n", n.Name, n.From, n.For) fmt.Printf(" needs %s from %s, for %s\n", n.Name, n.From, n.For)
} }
resources, err := declarationFor(ctx, inv, args[0], plan, settings) resources, err := declarationFor(ctx, open, args[0], plan, settings)
if err != nil { if err != nil {
return err return err
} }
@@ -497,14 +500,13 @@ func planCommand(ctx context.Context, args []string) error {
// A mesh with no licences at all is the ordinary case and must not be an error: every existing // A mesh with no licences at all is the ordinary case and must not be an error: every existing
// mesh is one, and a control plane that refused to plan because nobody had bought an API key // mesh is one, and a control plane that refused to plan because nobody had bought an API key
// would be unusable for the thing it already does. // would be unusable for the thing it already does.
func licencesFor(ctx context.Context, node string) ( func licencesFor(ctx context.Context, open *stores, node string) (
map[string][]catalogue.Record, map[string]map[string]catalogue.Record, error) { map[string][]catalogue.Record, map[string]map[string]catalogue.Record, error) {
held, err := openLicences(ctx) held, err := open.Licences(ctx)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer held.Close()
all, err := held.All(ctx) all, err := held.All(ctx)
if err != nil { if err != nil {
@@ -546,11 +548,10 @@ func licencesFor(ctx context.Context, node string) (
// **Empty is not an error here.** The mesh discarded the plaintext when it was supplied, so a // **Empty is not an error here.** The mesh discarded the plaintext when it was supplied, so a
// holder recorded afterwards genuinely has no key — and the declaration refuses that by name, // holder recorded afterwards genuinely has no key — and the declaration refuses that by name,
// where the module and the path are both in view, rather than here. // where the module and the path are both in view, rather than here.
func keyFor(ctx context.Context, licence, node, module string) (string, error) { func keyFor(ctx context.Context, open *stores, licence, node, module string) (string, error) {
held, err := openLicences(ctx) held, err := open.Licences(ctx)
if err != nil { if err != nil {
return "", err return "", err
} }
defer held.Close()
return held.KeyFor(ctx, licence, node, module) return held.KeyFor(ctx, licence, node, module)
} }
+22 -18
View File
@@ -25,11 +25,12 @@ import (
// serve is the control plane running: one connection to the broker, one queue, one consumer. // serve is the control plane running: one connection to the broker, one queue, one consumer.
func serve(ctx context.Context) error { func serve(ctx context.Context) error {
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
ident, err := openIdentity(ctx) ident, err := openIdentity(ctx)
if err != nil { if err != nil {
@@ -100,11 +101,12 @@ func declare(ctx context.Context, args []string) error {
// The node has to exist before it can be told anything. Publishing to a queue nobody consumes // The node has to exist before it can be told anything. Publishing to a queue nobody consumes
// would sit there looking like success. // would sit there looking like success.
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
if _, err := inv.NodeByName(ctx, node); err != nil { if _, err := inv.NodeByName(ctx, node); err != nil {
return err return err
} }
@@ -155,11 +157,12 @@ func pushCommand(ctx context.Context, args []string) error {
return errors.New("push <node> or push --behind, not both: one names a machine and the " + return errors.New("push <node> or push --behind, not both: one names a machine and the " +
"other asks which machines need one") "other asks which machines need one")
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
ident, err := openIdentity(ctx) ident, err := openIdentity(ctx)
if err != nil { if err != nil {
@@ -190,7 +193,7 @@ func pushCommand(ctx context.Context, args []string) error {
// only "failed or refused", so a machine that applied cleanly and whose declaration has // only "failed or refused", so a machine that applied cleanly and whose declaration has
// since changed was not behind — and novox/hq ADR 0010's question, *did my change go // since changed was not behind — and novox/hq ADR 0010's question, *did my change go
// out?*, was answerable only for the machines that broke. // out?*, was answerable only for the machines that broke.
would, err := wouldSend(ctx, inv, nodes) would, err := wouldSend(ctx, open, nodes)
if err != nil { if err != nil {
return err return err
} }
@@ -211,7 +214,7 @@ func pushCommand(ctx context.Context, args []string) error {
return nil return nil
} }
} }
gens, err := generators(ctx, inv) gens, err := generators(ctx, open)
if err != nil { if err != nil {
return err return err
} }
@@ -254,7 +257,7 @@ func pushCommand(ctx context.Context, args []string) error {
n.Name, doing.Outcome, doing.At.Local().Format("2006-01-02 15:04")) n.Name, doing.Outcome, doing.At.Local().Format("2006-01-02 15:04"))
} }
} }
plan, settings, err := planFor(ctx, inv, n.Name) plan, settings, err := planFor(ctx, open, n.Name)
if err != nil { if err != nil {
refusals = append(refusals, fmt.Sprintf("%s:\n%v", n.Name, err)) refusals = append(refusals, fmt.Sprintf("%s:\n%v", n.Name, err))
continue continue
@@ -262,7 +265,7 @@ func pushCommand(ctx context.Context, args []string) error {
// The private network is in here with everything else. It used to be composed separately // The private network is in here with everything else. It used to be composed separately
// and prepended, which meant every machine with an address was on it and no machine could // and prepended, which meant every machine with an address was on it and no machine could
// be kept off. It is a module now, so it arrives the way a module does. // be kept off. It is a module now, so it arrives the way a module does.
resources, err := declarationWith(ctx, inv, n.Name, plan, settings, gens) resources, err := declarationWith(ctx, open, n.Name, plan, settings, gens)
if err != nil { if err != nil {
refusals = append(refusals, fmt.Sprintf("%s:\n%v", n.Name, err)) refusals = append(refusals, fmt.Sprintf("%s:\n%v", n.Name, err))
continue continue
@@ -307,14 +310,15 @@ func pushCommand(ctx context.Context, args []string) error {
// The same all-or-nothing rule push follows, and for the same reason: a rotation that reached the // The same all-or-nothing rule push follows, and for the same reason: a rotation that reached the
// consumer and refused on the provider would leave one end holding a credential the other has // consumer and refused on the provider would leave one end holding a credential the other has
// never heard of — which is the state this whole mechanism exists to make impossible. // never heard of — which is the state this whole mechanism exists to make impossible.
func sendTo(ctx context.Context, inv *inventory.Inventory, names []string) error { func sendTo(ctx context.Context, open *stores, names []string) error {
inv := open.inventory
ident, err := openIdentity(ctx) ident, err := openIdentity(ctx)
if err != nil { if err != nil {
return err return err
} }
defer ident.Close() defer ident.Close()
gens, err := generators(ctx, inv) gens, err := generators(ctx, open)
if err != nil { if err != nil {
return err return err
} }
@@ -326,12 +330,12 @@ func sendTo(ctx context.Context, inv *inventory.Inventory, names []string) error
var sending []ready var sending []ready
var refusals []string var refusals []string
for _, name := range names { for _, name := range names {
plan, settings, err := planFor(ctx, inv, name) plan, settings, err := planFor(ctx, open, name)
if err != nil { if err != nil {
refusals = append(refusals, fmt.Sprintf("%s:\n%v", name, err)) refusals = append(refusals, fmt.Sprintf("%s:\n%v", name, err))
continue continue
} }
resources, err := declarationWith(ctx, inv, name, plan, settings, gens) resources, err := declarationWith(ctx, open, name, plan, settings, gens)
if err != nil { if err != nil {
refusals = append(refusals, fmt.Sprintf("%s:\n%v", name, err)) refusals = append(refusals, fmt.Sprintf("%s:\n%v", name, err))
continue continue
@@ -382,20 +386,20 @@ func digestOf(body []byte) string {
// //
// Machines that do not resolve are left out rather than reported as waiting: "this machine cannot // Machines that do not resolve are left out rather than reported as waiting: "this machine cannot
// be worked out" is a different problem with a different remedy, and `plan` is where it is said. // be worked out" is a different problem with a different remedy, and `plan` is where it is said.
func wouldSend(ctx context.Context, inv *inventory.Inventory, func wouldSend(ctx context.Context, open *stores,
nodes []inventory.Node) (map[string]string, error) { nodes []inventory.Node) (map[string]string, error) {
gens, err := generators(ctx, inv) gens, err := generators(ctx, open)
if err != nil { if err != nil {
return nil, err return nil, err
} }
out := map[string]string{} out := map[string]string{}
for _, n := range nodes { for _, n := range nodes {
plan, settings, err := planFor(ctx, inv, n.Name) plan, settings, err := planFor(ctx, open, n.Name)
if err != nil { if err != nil {
continue continue
} }
resources, err := declarationWith(ctx, inv, n.Name, plan, settings, gens) resources, err := declarationWith(ctx, open, n.Name, plan, settings, gens)
if err != nil { if err != nil {
continue continue
} }
+4 -3
View File
@@ -42,11 +42,12 @@ func rotateCommand(ctx context.Context, args []string) error {
} }
provision := positionals[0] provision := positionals[0]
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
holders, err := inv.HoldersOf(ctx, provision, *only) holders, err := inv.HoldersOf(ctx, provision, *only)
if err != nil { if err != nil {
@@ -101,7 +102,7 @@ func rotateCommand(ctx context.Context, args []string) error {
// honest thing is to make it as short as the broker allows and to never leave it open across // honest thing is to make it as short as the broker allows and to never leave it open across
// a command boundary, where it depends on somebody's memory. // a command boundary, where it depends on somebody's memory.
fmt.Printf("\nsending to both ends:\n") fmt.Printf("\nsending to both ends:\n")
if err := sendTo(ctx, inv, machines); err != nil { if err := sendTo(ctx, open, machines); err != nil {
return fmt.Errorf( return fmt.Errorf(
"%w\n\nThe old credentials are gone from the mesh and the new ones have not arrived. "+ "%w\n\nThe old credentials are gone from the mesh and the new ones have not arrived. "+
"Nothing on those machines has changed yet, so what is running keeps working "+ "Nothing on those machines has changed yet, so what is running keeps working "+
+6 -5
View File
@@ -40,13 +40,13 @@ func statusCommand(ctx context.Context, args []string) error {
return err return err
} }
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
asked, err := theThreeQuestions(ctx, inv) asked, err := theThreeQuestions(ctx, open)
if err != nil { if err != nil {
return err return err
} }
@@ -171,7 +171,8 @@ type builds struct{ inv *inventory.Inventory }
// The order is the design and not a convenience: is anything broken, is anything not answering, is // The order is the design and not a convenience: is anything broken, is anything not answering, is
// anything out of date. The first has consequences now, the second may, the third is a plan for // anything out of date. The first has consequences now, the second may, the third is a plan for
// later — and anything that led with the third would bury the first. // later — and anything that led with the third would bury the first.
func theThreeQuestions(ctx context.Context, inv *inventory.Inventory) (answers, error) { func theThreeQuestions(ctx context.Context, open *stores) (answers, error) {
inv := open.inventory
var out answers var out answers
var err error var err error
@@ -198,7 +199,7 @@ func theThreeQuestions(ctx context.Context, inv *inventory.Inventory) (answers,
// And which machines are not running what the mesh would send them. The same question as a // And which machines are not running what the mesh would send them. The same question as a
// module being behind its source, one level down: that one says the catalogue is out of date, // module being behind its source, one level down: that one says the catalogue is out of date,
// this one says a machine is — and only the second has anybody's change waiting in it. // this one says a machine is — and only the second has anybody's change waiting in it.
would, err := wouldSend(ctx, inv, out.nodes) would, err := wouldSend(ctx, open, out.nodes)
if err != nil { if err != nil {
return answers{}, err return answers{}, err
} }
+67 -2
View File
@@ -60,11 +60,12 @@ func migrate(ctx context.Context) error {
// The modules the control plane ships with itself. Recorded here rather than by hand, because // The modules the control plane ships with itself. Recorded here rather than by hand, because
// a mesh whose own private network is missing from the catalogue would have nothing to assign // a mesh whose own private network is missing from the catalogue would have nothing to assign
// and no way to say why. // and no way to say why.
inv, err := openInventory(ctx) open, err := openStores(ctx)
if err != nil { if err != nil {
return err return err
} }
defer inv.Close() defer open.Close()
inv := open.inventory
for _, m := range provided { for _, m := range provided {
if err := inv.Provide(ctx, m); err != nil { if err := inv.Provide(ctx, m); err != nil {
return err return err
@@ -111,3 +112,67 @@ func openLicences(ctx context.Context) (*licences.Licences, error) {
} }
return held, nil return held, nil
} }
// stores is what one command has open.
//
// **Opened once, not once per machine.** Working out what a machine should be reaches the identity
// context for its certificate and the licence context for its model access, and both were opened —
// and waited on — inside functions called for every node in a push. Two machines hid it; fifty
// would be fifty connect-and-wait cycles for data that does not change while the push runs.
//
// Each is opened on first use rather than up front, because most commands need one context and
// paying to reach three would be the same waste from the other side.
type stores struct {
inventory *inventory.Inventory
identity *identity.Identity
licences *licences.Licences
}
// open connects to the inventory, which every command that touches the mesh needs.
func openStores(ctx context.Context) (*stores, error) {
inv, err := openInventory(ctx)
if err != nil {
return nil, err
}
return &stores{inventory: inv}, nil
}
// Identity is this control plane's own identity context, opened if it has not been.
func (h *stores) Identity(ctx context.Context) (*identity.Identity, error) {
if h.identity != nil {
return h.identity, nil
}
opened, err := openIdentity(ctx)
if err != nil {
return nil, err
}
h.identity = opened
return opened, nil
}
// Licences is the context holding model access, opened if it has not been.
func (h *stores) Licences(ctx context.Context) (*licences.Licences, error) {
if h.licences != nil {
return h.licences, nil
}
opened, err := openLicences(ctx)
if err != nil {
return nil, err
}
h.licences = opened
return opened, nil
}
// Close lets go of everything that was opened, in any order: they are separate connections to
// separate databases and none of them knows about the others.
func (h *stores) Close() {
if h.licences != nil {
h.licences.Close()
}
if h.identity != nil {
h.identity.Close()
}
if h.inventory != nil {
h.inventory.Close()
}
}