Files
mesh-controller/cmd/mesh-control/stores.go
T
jschoubben 6eeb10f066 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.
2026-08-31 13:35:39 +02:00

179 lines
5.1 KiB
Go

package main
import (
"context"
"fmt"
"time"
"github.com/novox/mesh-control/internal/identity"
"github.com/novox/mesh-control/internal/inventory"
"github.com/novox/mesh-control/internal/licences"
"github.com/novox/mesh-control/internal/store"
)
// reaching each context's store, which no other context may touch.
//
// Split out of main.go, which had reached 2,769 lines because appending was always the
// cheapest next step. That is how novox/hq ADR 0001 records `hal/sdk` reaching 34,636:
// nothing in it was wrong, and no one edit was the one that should have been a new file.
// migrate brings every held context's schema up to date.
//
// Reported per context and per migration, because this runs during a bootstrap on a machine with
// nothing else on it — the output is the only account of what happened, and "migrated" is not one.
func migrate(ctx context.Context) error {
for _, c := range held {
migrations, err := c.migrations()
if err != nil {
return err
}
s, err := store.Open(ctx, c.name)
if err != nil {
return err
}
defer s.Close()
// The bootstrap raises PostgreSQL moments before this runs, and a container that is
// running is not a database that will answer — a distinction this project has already
// paid for once, when a crash-looping database reported itself as up between restarts.
if err := s.Ready(ctx, 60*time.Second); err != nil {
return err
}
done, err := s.Migrate(ctx, migrations)
for _, m := range done {
fmt.Printf("%s: applied %04d-%s\n", c.name, m.Number, m.Name)
}
if err != nil {
return err
}
if len(done) == 0 {
applied, err := s.AppliedMigrations(ctx)
if err != nil {
return err
}
fmt.Printf("%s: already up to date — %d migration(s)\n", c.name, len(applied))
}
}
// 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
// and no way to say why.
open, err := openStores(ctx)
if err != nil {
return err
}
defer open.Close()
inv := open.inventory
for _, m := range provided {
if err := inv.Provide(ctx, m); err != nil {
return err
}
fmt.Printf("provided %s\n", m.Module)
}
return nil
}
// openInventory connects and waits, the way every command that touches it needs to.
func openInventory(ctx context.Context) (*inventory.Inventory, error) {
inv, err := inventory.Open(ctx)
if err != nil {
return nil, err
}
if err := inv.Ready(ctx, 30*time.Second); err != nil {
inv.Close()
return nil, err
}
return inv, nil
}
func openIdentity(ctx context.Context) (*identity.Identity, error) {
ident, err := identity.Open(ctx)
if err != nil {
return nil, err
}
if err := ident.Ready(ctx, 30*time.Second); err != nil {
ident.Close()
return nil, err
}
return ident, nil
}
// openLicences connects to the context that holds which model access exists and who may use it.
func openLicences(ctx context.Context) (*licences.Licences, error) {
held, err := licences.Open(ctx)
if err != nil {
return nil, err
}
if err := held.Ready(ctx, 30*time.Second); err != nil {
held.Close()
return nil, err
}
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()
}
}