carlos / platform Public

host: edges files carry the box's data-bucket coordinates (#331)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEypt7de2X1G84aQ9rNkCf
Paul Campbell pushed by paul@keymail.dev 47ce665b6646f825eb2af761d5f36e0ce3c774f8 parent 8848271
3 files changed, +90 −38
  • internal/console/instancesplacement.go +8 −6
  • internal/host/edgesfile.go +17 −6
  • internal/host/edgesfile_test.go +65 −26
diff --git a/internal/console/instancesplacement.go b/internal/console/instancesplacement.go
index 649325f8d..9bebccaa8 100644
--- a/internal/console/instancesplacement.go
+++ b/internal/console/instancesplacement.go
@@ -33,12 +33,14 @@ type servedBy struct {
// capacity, and "where is this instance" is the question, not "who is its
// write home" (that is the home map's question, internal/edge).
type edgesFilePlacementView struct {
- V int `json:"v"`
- Box string `json:"box"`
- EIP string `json:"eip"`
- Region string `json:"region"`
- Placement string `json:"placement"`
- Hosts map[string]json.RawMessage `json:"hosts"`
+ V int `json:"v"`
+ Box string `json:"box"`
+ EIP string `json:"eip"`
+ Region string `json:"region"`
+ Placement string `json:"placement"`
+ DataBucket string `json:"data_bucket"`
+ DataBucketRegion string `json:"data_bucket_region"`
+ Hosts map[string]json.RawMessage `json:"hosts"`
}
// loadEdgesJoin folds every readable edges file into host → the boxes that
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go
index d57bbd1aa..dd4ec7685 100644
--- a/internal/host/edgesfile.go
+++ b/internal/host/edgesfile.go
@@ -85,9 +85,17 @@ type edgesFile struct {
// console's create door folds every file's Placement into the armed set
// a requested instance region is validated against — dormant until at
// least one box publishes the field, so roll order does not matter.
- Placement string `json:"placement,omitempty"`
- Generation string `json:"generation"`
- Hosts map[string]edgeHost `json:"hosts"`
+ Placement string `json:"placement,omitempty"`
+ // DataBucket/DataBucketRegion are this box's hibernate/data bucket
+ // coordinates (CARLOS_HIBERNATE_BUCKET/_REGION verbatim) — published so
+ // a move SOURCE can aim its staging mirror at the target box's bucket
+ // from the same file set everything else reads (instance-move spec).
+ // Additive + omitempty like Placement; a box with no hibernate bucket
+ // (activator disabled) publishes neither key.
+ DataBucket string `json:"data_bucket,omitempty"`
+ DataBucketRegion string `json:"data_bucket_region,omitempty"`
+ Generation string `json:"generation"`
+ Hosts map[string]edgeHost `json:"hosts"`
}
// edgesFilePass builds this box's edges file from the routes currently in
@@ -123,7 +131,7 @@ type edgesFile struct {
// once per alias per process (cleared when the alias publishes).
var withheldAliasSaid sync.Map
-func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, box, eip, region, placement string, last *[]byte) error {
+func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, box, eip, region, placement string, dataBucket, dataBucketRegion string, last *[]byte) error {
routes, err := reg.List()
if err != nil {
return fmt.Errorf("edges: list: %w", err)
@@ -257,7 +265,8 @@ func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry,
}
}
- f := edgesFile{V: 1, Box: box, EIP: eip, Region: region, Placement: placement, Hosts: hosts}
+ f := edgesFile{V: 1, Box: box, EIP: eip, Region: region, Placement: placement,
+ DataBucket: dataBucket, DataBucketRegion: dataBucketRegion, Hosts: hosts}
gate, err := json.Marshal(f)
if err != nil {
return fmt.Errorf("edges: marshal: %w", err)
@@ -372,6 +381,8 @@ func edgesFileLoop(reg *registry.Registry) {
// reconcile pass scopes by — the two vocabularies travel in one file so
// a reader can join them without a second source.
placement := strings.TrimSpace(os.Getenv("CARLOS_REGION"))
+ dataBucket := strings.TrimSpace(os.Getenv("CARLOS_HIBERNATE_BUCKET"))
+ dataBucketRegion := strings.TrimSpace(os.Getenv("CARLOS_HIBERNATE_REGION"))
log.Printf("agent: edges file loop armed (5min + jitter, %s)", edgesFileKey(box))
last := seedEdgesFileGate(ctx, store, box)
delay := time.Minute
@@ -379,7 +390,7 @@ func edgesFileLoop(reg *registry.Registry) {
time.Sleep(delay)
delay = 5*time.Minute + rand.N(time.Minute)
eip := readPublicIP()
- if err := edgesFilePass(ctx, store, reg, box, eip, region, placement, &last); err != nil {
+ if err := edgesFilePass(ctx, store, reg, box, eip, region, placement, dataBucket, dataBucketRegion, &last); err != nil {
log.Printf("agent: edges file pass: %v", err)
}
}
diff --git a/internal/host/edgesfile_test.go b/internal/host/edgesfile_test.go
index 2c219db67..db859c8e8 100644
--- a/internal/host/edgesfile_test.go
+++ b/internal/host/edgesfile_test.go
@@ -55,7 +55,7 @@ func TestEdgesFilePublishesHomedHosts(t *testing.T) {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -86,7 +86,7 @@ func TestEdgesFilePublishesWithEmptyEIP(t *testing.T) {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -112,7 +112,7 @@ func TestEdgesFileSkipsWriteWhenUnchanged(t *testing.T) {
seedRoute(t, reg, "app.example.com")
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
if s.puts != 1 {
@@ -122,7 +122,7 @@ func TestEdgesFileSkipsWriteWhenUnchanged(t *testing.T) {
// Second pass, identical registry state: a fresh generation timestamp
// would differ from the first pass's, but the change-gate excludes
// Generation, so this must not write.
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
if s.puts != 1 {
@@ -131,7 +131,7 @@ func TestEdgesFileSkipsWriteWhenUnchanged(t *testing.T) {
// A registry change makes the next pass write again.
seedRoute(t, reg, "second.example.com")
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
if s.puts != 2 {
@@ -151,12 +151,12 @@ func TestEdgesFileHoldsLastKnownGoodEIPOnTransientBlank(t *testing.T) {
seedRoute(t, reg, "app.example.com")
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
// Simulate a transient IMDS blip: this tick's probe returns "".
- if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
@@ -186,7 +186,7 @@ func TestEdgesFileExcludesNonReadyRows(t *testing.T) {
}
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -219,7 +219,7 @@ func TestEdgesFileWildcardRowPublishesVerbatim(t *testing.T) {
}
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -250,7 +250,7 @@ func TestEdgesFileRawWireBodyMatchesSchema(t *testing.T) {
seedRoute(t, reg, "app.example.com")
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
@@ -302,7 +302,7 @@ func TestEdgesFileRawWireBodyRegionField(t *testing.T) {
t.Run("unset region is omitted entirely", func(t *testing.T) {
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
@@ -321,7 +321,7 @@ func TestEdgesFileRawWireBodyRegionField(t *testing.T) {
s2, reg2 := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg2, "app.example.com")
var last []byte
- if err := edgesFilePass(context.Background(), s2, reg2, "quiet-harbour", "203.0.113.10", "eu-west-1", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s2, reg2, "quiet-harbour", "203.0.113.10", "eu-west-1", "", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s2.Get(context.Background(), "control/edges/quiet-harbour.json")
@@ -423,7 +423,7 @@ func TestEdgesFileSeedGateSkipsWriteOnUnchangedBucketState(t *testing.T) {
// A prior process (or the prior pass of this one) already published.
var priming []byte
- if err := edgesFilePass(context.Background(), real, reg, "annie", "1.2.3.4", "", "", &priming); err != nil {
+ if err := edgesFilePass(context.Background(), real, reg, "annie", "1.2.3.4", "", "", "", "", &priming); err != nil {
t.Fatal(err)
}
@@ -435,7 +435,7 @@ func TestEdgesFileSeedGateSkipsWriteOnUnchangedBucketState(t *testing.T) {
}
counting := &putCountingStore{Store: real}
- if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
if counting.puts != 0 {
@@ -454,7 +454,7 @@ func TestEdgesFileSeedHoldsEIPThroughRestartBlip(t *testing.T) {
seedRoute(t, reg, "app.example.com")
var priming []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &priming); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &priming); err != nil {
t.Fatal(err)
}
@@ -466,7 +466,7 @@ func TestEdgesFileSeedHoldsEIPThroughRestartBlip(t *testing.T) {
// This tick both hits a transient IMDS blip AND sees a real registry
// change, so the pass must write.
seedRoute(t, reg, "second.example.com")
- if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", "", "", &last); err != nil {
t.Fatal(err)
}
@@ -495,13 +495,13 @@ func TestEdgesFileUnseededLastPublishesBlankEIPOnFirstTickBlip(t *testing.T) {
seedRoute(t, reg, "app.example.com")
var priming []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &priming); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &priming); err != nil {
t.Fatal(err)
}
// The pre-fix shape: a fresh process's *last starts nil (no seed read).
var unseeded []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", &unseeded); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "", "", "", "", "", &unseeded); err != nil {
t.Fatal(err)
}
@@ -545,7 +545,7 @@ func TestEdgesFileSeedPreRegionBucketWritesOnceWhenRegionArrives(t *testing.T) {
// This build now has CARLOS_REGION set: the first pass must write, once.
counting := &putCountingStore{Store: real}
- if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "eu-west-1", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "eu-west-1", "", "", "", &last); err != nil {
t.Fatal(err)
}
if counting.puts != 1 {
@@ -565,7 +565,7 @@ func TestEdgesFileSeedPreRegionBucketWritesOnceWhenRegionArrives(t *testing.T) {
// A second, identical-region pass must not write again — the gate now
// includes region on both sides.
- if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "eu-west-1", "", &last); err != nil {
+ if err := edgesFilePass(context.Background(), counting, reg, "annie", "1.2.3.4", "eu-west-1", "", "", "", &last); err != nil {
t.Fatal(err)
}
if counting.puts != 1 {
@@ -588,7 +588,7 @@ func TestEdgesFileMarksPlatformRows(t *testing.T) {
}
}
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
body, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -654,7 +654,7 @@ func TestEdgesFilePublishesPointingAliasesOfServedPrimaries(t *testing.T) {
publish := func() map[string]hostView {
t.Helper()
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
body, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -734,7 +734,7 @@ func TestEdgesFilePublishesDelegatedWildcardParents(t *testing.T) {
}
}
var lastBody []byte
- if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", &lastBody); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", "", "", "", &lastBody); err != nil {
t.Fatal(err)
}
body, _, err := s.Get(context.Background(), "control/edges/annie.json")
@@ -797,7 +797,7 @@ func TestEdgesFileBoxNameJoinsTheStatusSnapshotLabel(t *testing.T) {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var last []byte
- if err := edgesFilePass(ctx, s, reg, box, "203.0.113.7", "eu-west-1", "", &last); err != nil {
+ if err := edgesFilePass(ctx, s, reg, box, "203.0.113.7", "eu-west-1", "", "", "", &last); err != nil {
t.Fatal(err)
}
if err := status.WriteBox(ctx, s, snapshot); err != nil {
@@ -817,7 +817,7 @@ func TestEdgesFileRawWireBodyPlacementField(t *testing.T) {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var last []byte
- if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "eu-west-2", "uk", &last); err != nil {
+ if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "eu-west-2", "uk", "", "", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
@@ -841,7 +841,7 @@ func TestEdgesFileRawWireBodyPlacementField(t *testing.T) {
s2, reg2 := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg2, "app.example.com")
var last2 []byte
- if err := edgesFilePass(context.Background(), s2, reg2, "quiet-harbour", "203.0.113.10", "", "", &last2); err != nil {
+ if err := edgesFilePass(context.Background(), s2, reg2, "quiet-harbour", "203.0.113.10", "", "", "", "", &last2); err != nil {
t.Fatal(err)
}
got2, _, err := s2.Get(context.Background(), "control/edges/quiet-harbour.json")
@@ -852,3 +852,42 @@ func TestEdgesFileRawWireBodyPlacementField(t *testing.T) {
t.Fatalf("raw wire body with no CARLOS_REGION carries a placement key: %s", got2)
}
}
+
+// Data-bucket coordinates (instance-move spec): published verbatim from the
+// hibernate env pair, absent when unset — the same additive rule as
+// placement.
+func TestEdgesFileRawWireBodyDataBucketFields(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "app.example.com")
+ var last []byte
+ if err := edgesFilePass(context.Background(), s, reg, "quiet-harbour", "203.0.113.10", "eu-west-2", "uk", "carlos-tito-uk-1", "eu-west-2", &last); err != nil {
+ t.Fatal(err)
+ }
+ got, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var probe struct {
+ DataBucket string `json:"data_bucket"`
+ DataBucketRegion string `json:"data_bucket_region"`
+ }
+ if err := json.Unmarshal(got, &probe); err != nil {
+ t.Fatal(err)
+ }
+ if probe.DataBucket != "carlos-tito-uk-1" || probe.DataBucketRegion != "eu-west-2" {
+ t.Fatalf("data bucket fields = %+v", probe)
+ }
+ s2, reg2 := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg2, "app.example.com")
+ var last2 []byte
+ if err := edgesFilePass(context.Background(), s2, reg2, "quiet-harbour", "203.0.113.10", "", "", "", "", &last2); err != nil {
+ t.Fatal(err)
+ }
+ got2, _, err := s2.Get(context.Background(), "control/edges/quiet-harbour.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(got2), "data_bucket") {
+ t.Fatalf("unset hibernate env still published a data_bucket key: %s", got2)
+ }
+}