musl takes the first reply from any listed nameserver, so a public fallback beside the mesh's resolver answered NXDOMAIN for mesh names in every Alpine container (hq ADR 0223). The fix is two mesh resolvers and no public one, which needs mesh-dns-resolver held on two machines: a seat can now be replicated, each holder recorded by 'seat <name> --add', checkClaims accepts every holder on record and still refuses a second holder of any other mesh seat, a holder answers its own requirement, and a roster fact gives each replicated seat's holders, this machine first, so resolv-conf can list them. Migration 0062 keys a holding by seat and assignment.
164 lines
5.6 KiB
Go
164 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/overlay"
|
|
)
|
|
|
|
// recordDerivedHolders writes down who holds each mesh-scoped seat that nobody was ever recorded
|
|
// as holding.
|
|
//
|
|
// **A seat held by derivation is a seat held by accident of being alone** (novox/hq
|
|
// 04-ISSUES/170). ADR 0131 lets a holder on record settle a seat, and lets any other assignment
|
|
// whose module could hold it stand beside the holder, eligible and silent. But a seat nobody
|
|
// ever handed over has no record, so its holder is whichever assignment happened to be the sole
|
|
// claimant — and the day a second one is assigned, both claim, both are refused, and the first
|
|
// one's whole machine stops resolving. That is what assigning a second postgres did to the
|
|
// control plane's own store.
|
|
//
|
|
// So the mesh writes the derived answer down before it acts on an assignment: for every
|
|
// mesh-scoped seat with exactly one resolved holder and nothing on record, that holder is
|
|
// recorded as the standing one — the same record `seat <name> --to <node>/<module>` makes by
|
|
// hand, made from what the mesh already resolved. A seat with two derived claimants is left
|
|
// alone: that is the ambiguity a person settles, and recording either would be guessing.
|
|
//
|
|
// Node-scoped seats are untouched: a record is one holder per seat, and a node-scoped seat has
|
|
// one holder per machine (ADR 0121), so there is nothing for a record to settle there.
|
|
func recordDerivedHolders(ctx context.Context, open *stores) ([]string, error) {
|
|
inv := open.inventory
|
|
shelf, err := inv.Catalogue(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// exclude nobody: every node's claims, resolved with the holdings on record.
|
|
world, err := theRestOfTheMesh(ctx, inv, shelf, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
recorded, err := inv.Holdings(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// A record is a row against a seat the store knows. A seat it does not — a mesh whose seats
|
|
// were never seeded, a seat a module declares for itself — stays held by derivation, as it
|
|
// always was; a missing row is not a reason an assignment fails.
|
|
known, err := inv.Seats(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
recordable := map[string]bool{}
|
|
for _, s := range known {
|
|
recordable[s.Name] = true
|
|
}
|
|
onRecord := map[string]bool{}
|
|
for _, h := range recorded {
|
|
if s, ok := catalogue.SeatNamed(h.Claim); ok {
|
|
onRecord[s.Name] = true
|
|
}
|
|
}
|
|
holders := map[string][]catalogue.Held{}
|
|
for _, h := range world.Held {
|
|
if h.Scope != catalogue.ScopeMesh {
|
|
continue
|
|
}
|
|
s, ok := catalogue.SeatNamed(h.Claim)
|
|
if !ok || onRecord[s.Name] || !recordable[s.Name] {
|
|
continue
|
|
}
|
|
holders[s.Name] = append(holders[s.Name], h)
|
|
}
|
|
names := make([]string, 0, len(holders))
|
|
for name := range holders {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
var said []string
|
|
for _, name := range names {
|
|
if len(holders[name]) != 1 {
|
|
continue
|
|
}
|
|
h := holders[name][0]
|
|
if err := inv.HoldSeat(ctx, name, catalogue.ScopeMesh, h.Node, h.Module); err != nil {
|
|
return said, err
|
|
}
|
|
said = append(said, fmt.Sprintf(
|
|
"recorded %s on %s as the standing holder of %s, which it held only by being alone",
|
|
h.Module, h.Node, name))
|
|
}
|
|
return said, nil
|
|
}
|
|
|
|
// replicatedHolders is, for each replicated mesh seat, every machine on the private network holding
|
|
// it — by internal name, at its private address (novox/hq ADR 0223). What a machine's resolver file
|
|
// lists for `mesh-dns-resolver`; the rendering puts the machine itself first when it is one.
|
|
//
|
|
// **The holders on record, and only the sole claimant when there are none** — the same answer the
|
|
// resolver gives about who holds (ADR 0131, issue 170). An assignment standing beside the holders,
|
|
// eligible and silent, is not listed: it becomes a holder by `seat <name> --add`, an act, never by
|
|
// being assigned. Two claimants with nothing on record are refused at resolution, so neither is
|
|
// listed here. A holder off the private network is left out: a resolver named at an address nothing
|
|
// answers is a lookup that waits out its timeout on every name.
|
|
func replicatedHolders(ctx context.Context, inv *inventory.Inventory,
|
|
shelf map[string]catalogue.Manifest) (map[string]map[string]string, error) {
|
|
var replicated []catalogue.Seat
|
|
for _, s := range catalogue.Seats() {
|
|
if s.Replicated && s.Scope == catalogue.ScopeMesh {
|
|
replicated = append(replicated, s)
|
|
}
|
|
}
|
|
if len(replicated) == 0 {
|
|
return nil, nil
|
|
}
|
|
recorded, err := inv.Holdings(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
places, err := onTheNetwork(ctx, inv, shelf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
address := map[string]string{}
|
|
for _, p := range places {
|
|
address[p.Name] = p.Address
|
|
}
|
|
entries, err := inv.Catalogued(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]map[string]string{}
|
|
for _, seat := range replicated {
|
|
var nodes []string
|
|
for _, h := range recorded {
|
|
if hs, ok := catalogue.SeatNamed(h.Claim); ok && hs.Name == seat.Name && h.Scope == seat.Scope {
|
|
nodes = append(nodes, h.Node)
|
|
}
|
|
}
|
|
if len(nodes) == 0 {
|
|
var derived []string
|
|
for _, e := range entries {
|
|
for _, c := range e.Manifest.Claims {
|
|
if cs, ok := catalogue.SeatNamed(c.Name); ok && cs.Name == seat.Name && c.At() == seat.Scope {
|
|
derived = append(derived, e.On...)
|
|
}
|
|
}
|
|
}
|
|
if len(derived) == 1 {
|
|
nodes = derived
|
|
}
|
|
}
|
|
at := map[string]string{}
|
|
for _, n := range nodes {
|
|
if address[n] != "" {
|
|
at[overlay.InternalName(n)] = address[n]
|
|
}
|
|
}
|
|
out[seat.Name] = at
|
|
}
|
|
return out, nil
|
|
}
|