carlos / platform Public

#162: foldHomes ignores role replica as a decision, not an accident

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e3b5cdc9f0df45d52f46abbac582e9d08d12b02b parent 112f5e0
3 files changed, +119 −0
  • internal/edge/alias_test.go +77 −0
  • internal/edge/homemap.go +14 −0
  • internal/edge/homemap_test.go +28 −0

CI passed — run details

diff --git a/internal/edge/alias_test.go b/internal/edge/alias_test.go
index 2627c867b..d58abed21 100644
--- a/internal/edge/alias_test.go
+++ b/internal/edge/alias_test.go
@@ -1,6 +1,7 @@
package edge
import (
+ "context"
"maps"
"net/http"
"net/http/httptest"
@@ -590,3 +591,79 @@ func TestHTTPFallbackRedirectsTunnelHost(t *testing.T) {
t.Fatalf("tunnel.test: Location = %q, want https://tunnel.test/register", loc)
}
}
+
+// TestReplicaAliasServesLocallyWithoutBackhaul is the assertion the whole of
+// #162 rests on (spec §3, §4): on a replica's box, a request for the
+// ACCOUNT's public host is served by the local replica upstream, and is not
+// backhauled to the home.
+//
+// The registry holds exactly what Tasks 3 and 4 leave on a replica box — the
+// replica's own route carrying replica_of, and a route_aliases row mapping the
+// account host to it — and the home map is seeded, from a real store, with an
+// edges file in which ANOTHER box publishes that same account host as role
+// "home". Both halves are live at once, which is the point: with the alias row
+// gone this request has a dial target waiting for it (a mesh key is minted, so
+// the hop can be proven) and the local upstream would never be reached. The
+// home EIP is a refusing address so that failure is a fast 502 rather than a
+// dial timeout.
+func TestReplicaAliasServesLocallyWithoutBackhaul(t *testing.T) {
+ backend, seenHost := hostEchoBackend(t, "served by the local replica")
+ reg := openReg(t)
+ if err := reg.Upsert(registry.Route{
+ Host: "r7x2k9m4qa.go.tito.io", Kind: "service",
+ Addr: backend.Listener.Addr().String(), Status: "ready",
+ Account: "bab", App: "tito", ReplicaOf: "acme.go.tito.io",
+ }); err != nil {
+ t.Fatalf("upsert replica route: %v", err)
+ }
+ upsertAlias(t, reg, "acme.go.tito.io", "r7x2k9m4qa.go.tito.io")
+
+ s := meshTestStore(t)
+ if err := MintMeshKey(context.Background(), s); err != nil {
+ t.Fatalf("mint mesh key: %v", err)
+ }
+ homeEIP := e2eDeadEIP(t)
+ putJSON(t, s, "control/edges/ie-1.json", `{"v":1,"box":"ie-1","eip":"`+homeEIP+
+ `","hosts":{"acme.go.tito.io":{"role":"home","platform":true}}}`)
+ putJSON(t, s, "control/edges/uk-1.json",
+ `{"v":1,"box":"uk-1","eip":"203.0.113.9","hosts":{`+
+ `"acme.go.tito.io":{"role":"replica","alias":true},`+
+ `"r7x2k9m4qa.go.tito.io":{"role":"home","platform":true}}}`)
+
+ e, err := New(reg, nil)
+ if err != nil {
+ t.Fatalf("New: %v", err)
+ }
+ e.staticStore = s
+ WithBoxLabel("uk-1")(e)
+ hm := e.newHomeMap()
+ if hm == nil {
+ t.Fatal("a store-backed edge must have a home map")
+ }
+ refreshHomeMap(t, hm)
+ e.homes.Store(hm)
+
+ // The premise, asserted rather than assumed: this box's map really does
+ // home the account host somewhere else. Without it the no-backhaul claim
+ // below would be vacuous — nothing to backhaul TO.
+ if eip, box, ok := hm.Home("acme.go.tito.io"); !ok || box != "ie-1" || eip != homeEIP {
+ t.Fatalf("home map says acme.go.tito.io is homed at %s/%s (ok=%v), want the remote home box ie-1", eip, box, ok)
+ }
+
+ srv := httptest.NewServer(e)
+ defer srv.Close()
+
+ code, body := getHost(t, srv.URL, "acme.go.tito.io")
+ if code != http.StatusOK {
+ t.Fatalf("status = %d, want 200 — the account's host was not served by the local replica", code)
+ }
+ if body != "served by the local replica" {
+ t.Fatalf("body = %q, want the local replica's answer — the request was not served locally", body)
+ }
+ // And the app saw the name the buyer asked for, not the replica's platform
+ // hostname: the alias fold shares the primary's upstream but must not
+ // rewrite the Host.
+ if *seenHost != "acme.go.tito.io" {
+ t.Fatalf("the replica's upstream saw Host %q, want acme.go.tito.io", *seenHost)
+ }
+}
diff --git a/internal/edge/homemap.go b/internal/edge/homemap.go
index 4057747db..944c8edda 100644
--- a/internal/edge/homemap.go
+++ b/internal/edge/homemap.go
@@ -110,6 +110,11 @@ const edgeRoleHome = "home"
// not serve the catch-all, so the entry is no dial target.
const edgeRoleWildcard = "wildcard"
+// edgeRoleReplica is an account's public host published by a box that serves
+// it from a one-way mirror (#162). It is never a dial target for anyone:
+// see the switch in foldHomes for why the ignore is a decision.
+const edgeRoleReplica = "replica"
+
// homeMapInterval / homeMapTimeout / homeMapBootTimeout mirror the cert
// mirror's cadence and budgets exactly (certsync.go), for the same reasons: a
// home move is a placement event that converge heals, the poll runs on its own
@@ -560,6 +565,15 @@ func foldHomes(files map[string]edgesFileView, self string) map[string]homeEntry
if !h.Wildcard || !strings.HasPrefix(host, "*.") {
continue
}
+ case edgeRoleReplica:
+ // #162: that box serves the account from a one-way mirror
+ // and says so, so its own edge resolves the host locally
+ // while the FLEET goes on treating the home as the only
+ // dial target. Dropping it here keeps this map
+ // single-valued; publishing it as "home" would hand a third
+ // box two targets settled by sort order, and the account's
+ // traffic would land on a mirror.
+ continue
default:
continue
}
diff --git a/internal/edge/homemap_test.go b/internal/edge/homemap_test.go
index ce605928c..0aae1c685 100644
--- a/internal/edge/homemap_test.go
+++ b/internal/edge/homemap_test.go
@@ -111,6 +111,34 @@ func TestHomeMapIgnoresUnknownRoles(t *testing.T) {
}
}
+// Replica placements (#162): a replica box publishes the ACCOUNT's public
+// host as role "replica", so its own edge serves that host from the local
+// mirror while every other box goes on dialling the home. This map is the
+// fleet's half of that bargain — it must stay single-valued on the account
+// host, and the value must be the HOME. Marking the replica "home" instead
+// would hand a third box two dial targets settled by sort order, and some of
+// the account's traffic would land on a mirror.
+func TestFoldHomesIgnoresReplicaEntries(t *testing.T) {
+ files := map[string]edgesFileView{
+ "ie-1": {V: 1, Box: "ie-1", EIP: "198.51.100.4", Hosts: map[string]edgesHostView{
+ "acme.go.tito.io": {Role: "home", Platform: true},
+ }},
+ "uk-1": {V: 1, Box: "uk-1", EIP: "203.0.113.9", Hosts: map[string]edgesHostView{
+ "acme.go.tito.io": {Role: "replica", Alias: true},
+ "r7x2k9m4qa.go.tito.io": {Role: "home", Platform: true},
+ }},
+ }
+ got := foldHomes(files, "us-1")
+ if e, ok := got["acme.go.tito.io"]; !ok || e.box != "ie-1" || e.eip != "198.51.100.4" {
+ t.Fatalf("account host resolved to %+v, want home box ie-1: a replica entry must never win the dial", e)
+ }
+ // The replica's OWN host stays an ordinary home: that is how the home box
+ // and every other box reach the replica at all.
+ if e, ok := got["r7x2k9m4qa.go.tito.io"]; !ok || e.box != "uk-1" {
+ t.Fatalf("the replica's own host must still be dialable: %+v", e)
+ }
+}
+
// A box with no discoverable public IP publishes its hosts list with eip:""
// (Task 1's DR-runbook case). There is nothing to dial, so it must never be
// offered as a target — a "" EIP would otherwise become a dial to port 443 of