novox/hq ADR 0118: the prefix is the reservation rule, so a module declaring any mesh-* name is refused and there is no reserved-names list to drift. Ten seats renamed in the table, the manifests that claim them, the controller's own shipped manifests, and the tests. Not the migration 0118 expected: a holding is derived at resolution from manifests and never stored, so nothing recorded points at an old name. A kept rename table tells a manifest written against one what it became — kept rather than retired, because a module lives in its own repository and may be registered long after the catalogue stopped using it. **A seat is not the interface it delivers.** The git seat became mesh-git and the git provision did not; likewise the package registry. A blanket replace renamed both, and the failure read "the package registry is served on <nil>", which does not say "you renamed an interface". A test now pins every seat against what it delivers, and that neither name is also the other.
105 lines
4.5 KiB
Go
105 lines
4.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The mesh's bus is one per mesh, read from the catalogue beside this checkout.
|
|
//
|
|
// **This is step 2's claim, and it is checked here rather than in a bed** (novox/hq ADR 0116):
|
|
// adoption puts the NATS server into the `mesh-broker` seat on a mesh that is already running,
|
|
// and the property that matters is that a second one anywhere is refused *when it is assigned*,
|
|
// not discovered later as two servers holding different halves of the mesh's traffic. A second
|
|
// bus is not a degraded mesh; it is two meshes that both believe they are the one.
|
|
func TestASecondMeshBusAnywhereIsRefusedByName(t *testing.T) {
|
|
nats := catalogueManifest(t, "nats")
|
|
|
|
if _, err := Resolve(shelf(nats), []string{"nats"}, workstation(), World{}); err != nil {
|
|
t.Fatalf("the bus alone does not resolve: %v", err)
|
|
}
|
|
|
|
elsewhere := World{Held: []Held{{Claim: "mesh-broker", Scope: ScopeMesh,
|
|
Node: "anchor", Module: "nats"}}}
|
|
other := workstation()
|
|
other.Name = "laptop"
|
|
_, err := Resolve(shelf(nats), []string{"nats"}, other, elsewhere)
|
|
if err == nil {
|
|
t.Fatal("a second bus was accepted on another machine")
|
|
}
|
|
if !strings.Contains(err.Error(), "mesh-broker") || !strings.Contains(err.Error(), "one per mesh") {
|
|
t.Fatalf("refused without naming the seat: %v", err)
|
|
}
|
|
}
|
|
|
|
// The seat is the server's role, not the product's name (novox/hq ADR 0079). A different
|
|
// implementation of the bus claims the same seat, and the mesh refuses it for the same reason —
|
|
// which is the property that lets the bus be replaced at all.
|
|
func TestTheSeatRefusesADifferentBusToo(t *testing.T) {
|
|
nats := catalogueManifest(t, "nats")
|
|
held := World{Held: []Held{{Claim: "mesh-broker", Scope: ScopeMesh,
|
|
Node: "anchor", Module: "some-other-broker"}}}
|
|
other := workstation()
|
|
other.Name = "laptop"
|
|
if _, err := Resolve(shelf(nats), []string{"nats"}, other, held); err == nil {
|
|
t.Fatal("the seat admitted a second holder because the module's name differed")
|
|
}
|
|
}
|
|
|
|
// The old broker no longer claims the seat: it is an ordinary provider of `amqp`
|
|
// (novox/hq ADR 0119), so it can sit on the same mesh as the bus without contending for it.
|
|
func TestTheAmqpBrokerDoesNotContendForTheSeat(t *testing.T) {
|
|
lavinmq := catalogueManifest(t, "lavinmq")
|
|
for _, c := range lavinmq.Claims {
|
|
if c.Name == "mesh-broker" {
|
|
t.Fatal("the amqp broker still claims mesh-broker; it is a provider, not foundation")
|
|
}
|
|
}
|
|
busHeld := World{Held: []Held{{Claim: "mesh-broker", Scope: ScopeMesh,
|
|
Node: "anchor", Module: "nats"}}}
|
|
other := workstation()
|
|
other.Name = "laptop"
|
|
if _, err := Resolve(shelf(lavinmq), []string{"lavinmq"}, other, busHeld); err != nil {
|
|
t.Fatalf("the amqp broker was refused beside the mesh bus: %v", err)
|
|
}
|
|
}
|
|
|
|
// **A seat and the interface it delivers are different names, and renaming one must not rename
|
|
// the other** (novox/hq ADR 0118). This nearly went wrong: the seats were renamed to the `mesh-*`
|
|
// prefix, and a blanket search-and-replace also renamed `npm-package-registry` and `git` where
|
|
// they are *provisions* — which a consumer requires and a provider offers. The tests failed with
|
|
// "the package registry is served on <nil>", which does not say "you renamed an interface".
|
|
func TestRenamingASeatDidNotRenameTheInterfaceItDelivers(t *testing.T) {
|
|
for _, pair := range []struct{ seat, delivers string }{
|
|
{"mesh-git", "git"},
|
|
{"mesh-npm-package-registry", "npm-package-registry"},
|
|
{"mesh-artifact-store", "artifact-store"},
|
|
{"mesh-store", "postgres-database"},
|
|
{"mesh-broker", "mesh-bus"},
|
|
} {
|
|
s, known := SeatNamed(pair.seat)
|
|
if !known {
|
|
t.Fatalf("%q is not a seat", pair.seat)
|
|
}
|
|
if s.Delivers != pair.delivers {
|
|
t.Errorf("the %s seat delivers %q, expected %q — renaming the seat moved the "+
|
|
"interface with it, and every consumer requiring it would stop resolving",
|
|
pair.seat, s.Delivers, pair.delivers)
|
|
}
|
|
if _, isSeat := SeatNamed(pair.delivers); isSeat {
|
|
t.Errorf("%q is both an interface and a seat name; one of the renames was incomplete",
|
|
pair.delivers)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And a manifest written against an old seat name is told what it became, rather than refused as
|
|
// unknown — the courtesy the `needs`/`own-secrets` rename already sets.
|
|
func TestAnOldSeatNameSaysWhatItBecame(t *testing.T) {
|
|
m := Manifest{Module: "old", Claims: []Claim{{Name: "the-catalogue", Scope: ScopeMesh}}}
|
|
got := strings.Join(claimProblems(m), "; ")
|
|
if !strings.Contains(got, "the-catalogue") || !strings.Contains(got, "mesh-catalog") {
|
|
t.Fatalf("the refusal does not name both the old and the new: %q", got)
|
|
}
|
|
}
|