carlos / platform Public

console: seedRegionReady joins platform heartbeats only, logs the unconfirmed box (Plan 3 Task 5 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 5fb3dca2aaf047d9c693fad46aa81114fa7b01ce parent e6a65ae
2 files changed, +122 −8
  • internal/console/seedfloor.go +31 −7
  • internal/console/seedfloor_test.go +91 −1

CI passed — run details

diff --git a/internal/console/seedfloor.go b/internal/console/seedfloor.go
index 7d734d2e9..80ecf200e 100644
--- a/internal/console/seedfloor.go
+++ b/internal/console/seedfloor.go
@@ -18,7 +18,8 @@ import (
// console runs without claim control, the seeds floor is published, and
// every box in region has armed it, excluded or vouched for its
// carlos.prev, runs nested, and has no claim control. region is the record
-// vocabulary ("" is the home region). nil means go ahead.
+// vocabulary ("" is the home region); the home region's name is accepted
+// too. nil means go ahead.
//
// oneBox is the import door's extra rule: the swap, its journal and the
// rollback live on the one box holding the legacy row, and a second box in
@@ -31,6 +32,7 @@ import (
// box whose own local floor is unreadable publishes "" for its floor, and
// must never be read as ready.
func (s *Server) seedRegionReady(ctx context.Context, region string, oneBox bool) *doorRefusal {
+ region = s.homeRegionIn(region)
if s.cfg.HostClaim {
return &doorRefusal{http.StatusConflict, "This console runs with claim control on. Seeded instances are not supported here yet."}
}
@@ -87,24 +89,46 @@ func (s *Server) seedRegionReady(ctx context.Context, region string, oneBox bool
if len(below) > 0 {
return &doorRefusal{http.StatusConflict, fmt.Sprintf("%s in %s are below the seeds floor. Roll the platform there, then publish the seeds floor again.", boxList(below), name)}
}
- boxes, _, err := s.systemBoxes(ctx, false)
+ // The platform's own heartbeats, the set complete() vouched for. Never
+ // systemBoxes: it appends every customer fleet's heartbeats, and a fleet
+ // box sharing a label could stand in for a platform box. The join is
+ // the edges file's box against the heartbeat's Label, the pair
+ // readLiveEdgesSnapshot joins on. A label two heartbeats claim is
+ // ambiguous, so unconfirmed.
+ boxes, err := status.LoadBoxes(ctx, s.cfg.Releases)
if err != nil {
log.Printf("console: seeds readiness: boxes: %v", err)
return unconfirmed
}
- // The join is the edges file's box against the heartbeat's Label, the
- // pair readLiveEdgesSnapshot joins on. A region box with no heartbeat,
- // a stale one, or one with no layout report is unconfirmed, never
- // nested.
byLabel := map[string]status.Box{}
+ dup := map[string]bool{}
for _, b := range boxes {
+ if _, seen := byLabel[b.Label]; seen {
+ dup[b.Label] = true
+ }
byLabel[b.Label] = b
}
+ // A region box with no heartbeat, a stale one, or one with no layout
+ // report is unconfirmed, never nested. The refusal stays generic; the
+ // log names the box, since a retired box whose edges file nobody
+ // deleted refuses here until someone does.
now := time.Now()
var flat, claimed []string
for _, label := range inRegion {
b, ok := byLabel[label]
- if !ok || status.BoxStale(b, now, status.SnapshotPeriod) || b.Metrics == nil || b.Metrics.Layout == nil {
+ why := ""
+ switch {
+ case !ok:
+ why = "no heartbeat"
+ case dup[label]:
+ why = "more than one heartbeat with this label"
+ case status.BoxStale(b, now, status.SnapshotPeriod):
+ why = "stale heartbeat, generated " + b.GeneratedAt.UTC().Format(time.RFC3339)
+ case b.Metrics == nil || b.Metrics.Layout == nil:
+ why = "no layout report"
+ }
+ if why != "" {
+ log.Printf("console: seeds readiness: box %q in region %q is unconfirmed: %s", label, name, why)
return unconfirmed
}
if b.Metrics.Layout.Mode != "nested" {
diff --git a/internal/console/seedfloor_test.go b/internal/console/seedfloor_test.go
index 8379d18ed..81a472228 100644
--- a/internal/console/seedfloor_test.go
+++ b/internal/console/seedfloor_test.go
@@ -2,6 +2,7 @@ package console
import (
"context"
+ "encoding/json"
"errors"
"net/http"
"os"
@@ -10,6 +11,8 @@ import (
"testing"
"time"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/seeds"
"github.com/carlosframework/platform/internal/status"
)
@@ -39,6 +42,11 @@ func putSeedEdges(t *testing.T, fx *consoleFixture, label, body string) {
// putSeedHeartbeat writes the box's status heartbeat as of at; mode "" is
// a box with no layout report.
func putSeedHeartbeat(t *testing.T, fx *consoleFixture, label, mode string, claim bool, at time.Time) {
+ t.Helper()
+ putSeedHeartbeatIn(t, fx.rel, label, mode, claim, at)
+}
+
+func putSeedHeartbeatIn(t *testing.T, store release.Store, label, mode string, claim bool, at time.Time) {
t.Helper()
var layout *status.LayoutReport
if mode != "" {
@@ -48,7 +56,7 @@ func putSeedHeartbeat(t *testing.T, fx *consoleFixture, label, mode string, clai
// layout report would publish no metrics at all.
b := status.CollectBox(nil, status.Probes{Release: "v0.27.1", Layout: layout, HostClaimEnabled: claim}, at)
b.Label = label
- if err := status.WriteBox(context.Background(), fx.rel, b); err != nil {
+ if err := status.WriteBox(context.Background(), store, b); err != nil {
t.Fatal(err)
}
}
@@ -230,3 +238,85 @@ func TestSeedRegionReadyNamesBoxesAndRegion(t *testing.T) {
t.Fatalf("an import into three boxes: %+v", ref)
}
}
+
+// A customer fleet's heartbeat is not a platform box's: a fresh, nested,
+// claim-free fleet heartbeat under the same label cannot stand in for a
+// stale platform box.
+func TestSeedRegionReadyIgnoresFleetHeartbeats(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+ publishSeedsFloorFx(t, fx, "v0.27.0")
+ seedReadyBox(t, fx, "kass-1", "", 1, "v0.27.0", "v0.27.0", "nested", false)
+ putSeedHeartbeat(t, fx, "kass-1", "nested", false, time.Now().Add(-time.Hour))
+ reg, err := registry.Open(fx.regPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { reg.Close() })
+ if err := reg.UpsertFleet(registry.Fleet{Name: "pi", Account: fx.sqid, Bucket: "pi-bucket"}); err != nil {
+ t.Fatal(err)
+ }
+ putSeedHeartbeatIn(t, release.NewPrefixStore(fx.rel, "fleets/pi/"), "kass-1", "nested", false, time.Now())
+ if ref := fx.srv.seedRegionReady(ctx, "", false); ref == nil || ref.Status != http.StatusServiceUnavailable {
+ t.Fatalf("a fleet heartbeat covered a stale platform box: %+v", ref)
+ }
+}
+
+// Two platform heartbeats claiming one label are ambiguous: unconfirmed,
+// whichever the read happens to see last.
+func TestSeedRegionReadyRefusesADuplicateLabel(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+ publishSeedsFloorFx(t, fx, "v0.27.0")
+ seedReadyBox(t, fx, "kass-1", "", 1, "v0.27.0", "v0.27.0", "nested", false)
+ b := status.CollectBox(nil, status.Probes{Release: "v0.27.1", Layout: &status.LayoutReport{Mode: "flat"}}, time.Now())
+ b.Label = "kass-1"
+ body, err := json.Marshal(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := fx.rel.Put(ctx, "status/boxes/kass-1-old.json", body, ""); err != nil {
+ t.Fatal(err)
+ }
+ if ref := fx.srv.seedRegionReady(ctx, "", false); ref == nil || ref.Status != http.StatusServiceUnavailable {
+ t.Fatalf("two heartbeats for kass-1: %+v", ref)
+ }
+}
+
+// The floor's own min protocol binds too, when it is above the one this
+// console requires.
+func TestSeedRegionReadyHonoursTheFloorsProtocol(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+ if err := seeds.PublishFloor(ctx, fx.rel, seeds.Floor{Min: seeds.RequiredProtocol + 1, MinRelease: "v0.27.0"}); err != nil {
+ t.Fatal(err)
+ }
+ seedReadyBox(t, fx, "kass-1", "", seeds.RequiredProtocol, "v0.27.0", "v0.27.0", "nested", false)
+ want := "kass-1 in the home region are below the seeds floor. Roll the platform there, then publish the seeds floor again."
+ if ref := fx.srv.seedRegionReady(ctx, "", false); ref == nil || ref.Status != http.StatusConflict || ref.Msg != want {
+ t.Fatalf("a box below the floor's protocol: %+v", ref)
+ }
+ seedReadyBox(t, fx, "kass-1", "", seeds.RequiredProtocol+1, "v0.27.0", "v0.27.0", "nested", false)
+ if ref := fx.srv.seedRegionReady(ctx, "", false); ref != nil {
+ t.Fatalf("a box at the floor's protocol: %+v", ref)
+ }
+}
+
+// A caller may name the home region rather than pass the record's "".
+func TestSeedRegionReadyAcceptsTheHomeRegionsName(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.srv.cfg.HomeRegion = "eu"
+ ctx := context.Background()
+ publishSeedsFloorFx(t, fx, "v0.27.0")
+ seedReadyBox(t, fx, "kass-1", "", 1, "v0.27.0", "v0.27.0", "nested", false)
+ for _, region := range []string{"", "eu"} {
+ if ref := fx.srv.seedRegionReady(ctx, region, false); ref != nil {
+ t.Errorf("region %q: %+v", region, ref)
+ }
+ }
+ seedReadyBox(t, fx, "kass-1", "", 1, "v0.27.0", "v0.27.0", "flat", false)
+ want := "kass-1 in eu do not run instances nested yet. A seeded instance needs every box there nested."
+ if ref := fx.srv.seedRegionReady(ctx, "eu", false); ref == nil || ref.Msg != want {
+ t.Fatalf("region by name, a flat box: %+v", ref)
+ }
+}