The broker credential resolves the seat, not the hub (issue 059) #30
@@ -467,36 +467,88 @@ func pinCommand(ctx context.Context, args []string, setting bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// theBrokerSeat is the mesh-scoped seat the broker module claims (novox/hq ADR 0079: a
|
||||||
|
// foundation seat is named after the server it guards).
|
||||||
|
const theBrokerSeat = "mesh-broker"
|
||||||
|
|
||||||
// brokerReachableAt is the broker's address as the given node can reach it.
|
// brokerReachableAt is the broker's address as the given node can reach it.
|
||||||
//
|
//
|
||||||
// The genesis address (MESH_BROKER_ADDRESS) is the broker's public endpoint — reachable from the
|
// The genesis address (MESH_BROKER_ADDRESS) is the broker's public endpoint — right for a machine
|
||||||
// control-node itself, but not routed to another node, whose firewall admits only the overlay
|
// that can dial it, wrong across the mesh, where the path is the overlay. A node that is ON the
|
||||||
// (from:mesh). The foundation, and so the broker, sits on the control-node, which is the overlay
|
// overlay is given the overlay name of the node HOLDING the broker — the one assigned a module
|
||||||
// hub; a node that is on the overlay reaches the broker by the hub's `.internal` name, which the
|
// claiming the mesh-broker seat — because that is where the broker is, whatever else the topology
|
||||||
// firewall admits and every node resolves. A node not yet on the overlay — at genesis, before any
|
// says. Only when nothing holds the seat yet (genesis raised the broker as plumbing and no module
|
||||||
// `overlay place`, which is when the builder's account is issued — keeps the genesis address it
|
// has adopted it) does the hub stand in, which is where the foundation is by convention.
|
||||||
// was given, so nothing about bring-up changes. This is issue 055.
|
//
|
||||||
|
// "On the overlay" is what `whereEveryoneIs` answers — a machine that RESOLVED the networking
|
||||||
|
// module — not "has an address", which is true of every placed machine and says nothing about
|
||||||
|
// whether anything can reach it (novox/hq issue 059). A node not on the overlay — at genesis,
|
||||||
|
// before any `overlay place`, which is when the builder's account is issued — keeps the genesis
|
||||||
|
// address, so nothing about bring-up changes. This is issue 055, corrected by 059.
|
||||||
func brokerReachableAt(ctx context.Context, inv *inventory.Inventory, known broker.Broker, node string) (string, error) {
|
func brokerReachableAt(ctx context.Context, inv *inventory.Inventory, known broker.Broker, node string) (string, error) {
|
||||||
overlays, err := inv.Overlays(ctx)
|
shelf, err := inv.Catalogue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var hub string
|
onNetwork, err := whereEveryoneIs(ctx, inv, shelf)
|
||||||
onOverlay := false
|
if err != nil {
|
||||||
for _, o := range overlays {
|
// No catalogue yet is genesis, and at genesis the genesis address is the right one.
|
||||||
if o.Hub && o.Address != "" {
|
|
||||||
hub = o.Name
|
|
||||||
}
|
|
||||||
if o.Name == node && o.Address != "" {
|
|
||||||
onOverlay = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hub == "" || !onOverlay {
|
|
||||||
return known.Address, nil
|
return known.Address, nil
|
||||||
}
|
}
|
||||||
|
if onNetwork[node] == "" {
|
||||||
|
return known.Address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where the broker is: the node whose assigned set includes a module claiming the seat.
|
||||||
|
holders := map[string]bool{}
|
||||||
|
for name, m := range shelf {
|
||||||
|
for _, c := range m.Claims {
|
||||||
|
if c.Name == theBrokerSeat && c.At() == catalogue.ScopeMesh {
|
||||||
|
holders[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
brokerAt := ""
|
||||||
|
if len(holders) > 0 {
|
||||||
|
overlays, err := inv.Overlays(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, o := range overlays {
|
||||||
|
assigned, err := inv.Assigned(ctx, o.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, a := range assigned {
|
||||||
|
if holders[a] && onNetwork[o.Name] != "" {
|
||||||
|
brokerAt = onNetwork[o.Name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if brokerAt == "" {
|
||||||
|
// Nothing holds the seat (or its node is not on the overlay): the hub, where the
|
||||||
|
// foundation is by convention — and only a hub that is itself on the overlay, or the
|
||||||
|
// name we hand out routes nowhere.
|
||||||
|
overlays, err := inv.Overlays(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, o := range overlays {
|
||||||
|
if o.Hub && onNetwork[o.Name] != "" {
|
||||||
|
brokerAt = onNetwork[o.Name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if brokerAt == "" {
|
||||||
|
return known.Address, nil
|
||||||
|
}
|
||||||
|
// A portless genesis address is a working configuration (amqps defaults to 5671), and
|
||||||
|
// silently keeping the public address would disable this whole path — so the port defaults
|
||||||
|
// rather than the fix dissolving (novox/hq issue 059).
|
||||||
_, port, err := net.SplitHostPort(known.Address)
|
_, port, err := net.SplitHostPort(known.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return known.Address, nil
|
port = "5671"
|
||||||
}
|
}
|
||||||
return net.JoinHostPort(overlay.InternalName(hub), port), nil
|
return net.JoinHostPort(brokerAt, port), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,23 @@ func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string)
|
|||||||
"and the mesh will not choose between them", node)
|
"and the mesh will not choose between them", node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One hub per mesh, refused rather than last-write-wins: with two flagged, which one the
|
||||||
|
// graph and the broker address pick is order-dependent — the silent-election fault ADR 0007
|
||||||
|
// exists to avoid, one flag over (novox/hq issue 059). Moving the hub is explicit: re-place
|
||||||
|
// the old one without --hub first.
|
||||||
|
if *hub {
|
||||||
|
placed, err := inv.Overlays(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, p := range placed {
|
||||||
|
if p.Hub && p.Name != node {
|
||||||
|
return fmt.Errorf("%s is already the hub; a mesh has one. Re-place %s without "+
|
||||||
|
"--hub first if the hub is moving", p.Name, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Declared, all three. The address is evidence of reachability and is not the fact, and hub
|
// Declared, all three. The address is evidence of reachability and is not the fact, and hub
|
||||||
// election by address prefix fails silently (novox/hq ADR 0007).
|
// election by address prefix fails silently (novox/hq ADR 0007).
|
||||||
if err := inv.SetPlace(ctx, node, *endpoint, *site, *hub, ""); err != nil {
|
if err := inv.SetPlace(ctx, node, *endpoint, *site, *hub, ""); err != nil {
|
||||||
@@ -108,6 +125,8 @@ func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s is at %s on the overlay\n", node, address)
|
fmt.Printf("%s is at %s on the overlay\n", node, address)
|
||||||
|
fmt.Println(" credentials issued for it before this placement keep their old broker address —" +
|
||||||
|
" `module issue` them again and push (novox/hq issue 059)")
|
||||||
switch {
|
switch {
|
||||||
case *hub:
|
case *hub:
|
||||||
fmt.Println(" the hub — every node not sharing a site routes through it")
|
fmt.Println(" the hub — every node not sharing a site routes through it")
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package catalogue
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -484,3 +486,24 @@ func TestSomethingOrdinaryAnsweredHereNeedsNothing(t *testing.T) {
|
|||||||
t.Fatalf("a shell answered on this machine produced %d need(s)", len(got.Needs))
|
t.Fatalf("a shell answered on this machine produced %d need(s)", len(got.Needs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTheControllersOwnManifestClaimsItsSeat(t *testing.T) {
|
||||||
|
// The seat test above fabricates manifests, so deleting the claim from the real module.json
|
||||||
|
// would fail nothing (novox/hq issue 059's review). This binds the one manifest this
|
||||||
|
// repository owns: the controller claims the mesh-scoped seat named after its server
|
||||||
|
// (ADR 0079), or the one-controller property is convention again.
|
||||||
|
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("the controller's own manifest is unreadable: %v", err)
|
||||||
|
}
|
||||||
|
m, err := ParseManifest(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("the controller's own manifest does not parse: %v", err)
|
||||||
|
}
|
||||||
|
for _, c := range m.Claims {
|
||||||
|
if c.Name == "mesh-controller" && c.At() == ScopeMesh {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("module.json no longer claims the mesh-scoped mesh-controller seat: %+v", m.Claims)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user