| index 7f24c1031..0602e5654 100644 |
| --- a/internal/host/fleetbind_test.go |
| +++ b/internal/host/fleetbind_test.go |
| @@ -49,7 +49,7 @@ func TestPublishedBindingBoxMatchesTunnelChannelName(t *testing.T) { |
| |
| ctx := context.Background() |
| s := release.NewDirStore(t.TempDir()) |
| - hook := fleetBindingHook(s, id.Fleet, id.Label) |
| + hook := fleetBindingHook(s, id.Fleet, id.Label, testRegistry(t)) |
| hook("one.example.com") |
| |
| body, _, err := s.Get(ctx, fleetBindingKey(id.Fleet, "one.example.com")) |
| @@ -533,7 +533,7 @@ func TestFleetBindingHookLogsErrorWithoutPanicking(t *testing.T) { |
| defer log.SetOutput(prev) |
| |
| store := errPutStore{Store: release.NewDirStore(t.TempDir())} |
| - hook := fleetBindingHook(store, "pi", "amber-gliding") |
| + hook := fleetBindingHook(store, "pi", "amber-gliding", testRegistry(t)) |
| |
| hook("one.example.com") // must not panic even though every Put fails |
| |
| @@ -545,7 +545,7 @@ func TestFleetBindingHookLogsErrorWithoutPanicking(t *testing.T) { |
| func TestFleetBindingHookWritesBinding(t *testing.T) { |
| ctx := context.Background() |
| s := release.NewDirStore(t.TempDir()) |
| - hook := fleetBindingHook(s, "pi", "amber-gliding") |
| + hook := fleetBindingHook(s, "pi", "amber-gliding", testRegistry(t)) |
| |
| hook("woken.example.com") |
| |
| @@ -576,7 +576,7 @@ func TestFleetBindingHookTakesOverFromAnotherBox(t *testing.T) { |
| t.Fatalf("seed old-box's binding: %v", err) |
| } |
| |
| - hook := fleetBindingHook(s, "pi", "new-box") |
| + hook := fleetBindingHook(s, "pi", "new-box", testRegistry(t)) |
| hook("one.example.com") |
| |
| body, _, err := s.Get(ctx, fleetBindingKey("pi", "one.example.com")) |
| @@ -631,3 +631,89 @@ func TestPublishResidentFleetBindingsSkipsSidecars(t *testing.T) { |
| t.Fatalf("a sidecar binding was published — a box must never advertise a sidecar as served") |
| } |
| } |
| + |
| +// --- Fix round 1: the other two ends of the same chain ------------------------ |
| + |
| +// TestFleetBindingHookNeverPublishesSidecar covers the WAKE publish path, |
| +// which the startup-pass filter does not reach. It matters more than the |
| +// startup pass, not less: this hook writes with takeover=true, so an errant |
| +// sidecar binding here does not merely appear — it claims the host away from |
| +// whichever box legitimately held it. |
| +// |
| +// Deliberately not resting on "the activator never wakes a sidecar": that is |
| +// true of the activator today and it is a fact a later task on this same |
| +// branch removes, at which point the hole would open silently. An ordinary |
| +// instance host must still be published, so this asserts both directions. |
| +func TestFleetBindingHookNeverPublishesSidecar(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + reg := testRegistry(t) |
| + if err := reg.Upsert(registry.Route{Host: "app.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { |
| + t.Fatalf("upsert instance: %v", err) |
| + } |
| + if err := reg.Upsert(registry.Route{Host: "bot.eleven.bdm.sidecar.internal", Kind: "sidecar", Region: "fleet/pi"}); err != nil { |
| + t.Fatalf("upsert sidecar: %v", err) |
| + } |
| + |
| + hook := fleetBindingHook(s, "pi", "box1", reg) |
| + hook("app.example.com") |
| + hook("bot.eleven.bdm.sidecar.internal") |
| + |
| + if _, _, err := s.Get(ctx, fleetBindingKey("pi", "app.example.com")); err != nil { |
| + t.Fatalf("instance binding missing from the wake publish: %v", err) |
| + } |
| + if _, _, err := s.Get(ctx, fleetBindingKey("pi", "bot.eleven.bdm.sidecar.internal")); err == nil { |
| + t.Fatalf("the wake hook published a sidecar binding — with takeover semantics, no less") |
| + } |
| +} |
| + |
| +// TestReconcileFleetBindingsNeverStampsSidecar covers the READING side. A |
| +// binding is an untrusted claim written by another machine that upgrades on |
| +// its own schedule: through a mixed-version roll, a fleet box still on an |
| +// older binary keeps publishing sidecar bindings, and this platform box is |
| +// what acts on them. SetFleetBox is the act that hands a fleet-placed route to |
| +// the edge's fleet dispatch arm, so it must refuse on kind independently of |
| +// whoever wrote the object. |
| +// |
| +// Both routes here are placed on the SAME fleet as their binding, so the |
| +// pre-existing placement check passes for both — the kind guard is the only |
| +// thing that can distinguish them. |
| +func TestReconcileFleetBindingsNeverStampsSidecar(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + reg := testRegistry(t) |
| + const sidecarHost = "bot.eleven.bdm.sidecar.internal" |
| + if err := reg.Upsert(registry.Route{Host: "app.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { |
| + t.Fatalf("upsert instance: %v", err) |
| + } |
| + if err := reg.Upsert(registry.Route{Host: sidecarHost, Kind: "sidecar", Region: "fleet/pi"}); err != nil { |
| + t.Fatalf("upsert sidecar: %v", err) |
| + } |
| + // Both bindings written directly, as an older box's publisher would. |
| + for _, host := range []string{"app.example.com", sidecarHost} { |
| + if err := WriteFleetBinding(ctx, s, FleetBinding{ |
| + Host: host, Fleet: "pi", Box: "box1", BoundAt: 100, |
| + }, true); err != nil { |
| + t.Fatalf("seed binding for %s: %v", host, err) |
| + } |
| + } |
| + |
| + if err := ReconcileFleetBindings(ctx, s, reg); err != nil { |
| + t.Fatalf("ReconcileFleetBindings: %v", err) |
| + } |
| + |
| + rt, err := reg.Get("app.example.com") |
| + if err != nil || rt == nil { |
| + t.Fatalf("get instance route: %v", err) |
| + } |
| + if rt.FleetBox != "box1" { |
| + t.Fatalf("instance FleetBox = %q, want box1 — ordinary routes must still be stamped", rt.FleetBox) |
| + } |
| + sc, err := reg.Get(sidecarHost) |
| + if err != nil || sc == nil { |
| + t.Fatalf("get sidecar route: %v", err) |
| + } |
| + if sc.FleetBox != "" { |
| + t.Fatalf("sidecar FleetBox = %q, want empty — stamping it hands the row to the edge's fleet arm", sc.FleetBox) |
| + } |
| +} |