package hostops import ( "context" "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "strings" "testing" "time" "github.com/carlosframework/platform/internal/boxchoice" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/park" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/termfence" ) func chooserFixture(t *testing.T, boxes ...string) (ExecutorChooser, release.Store, *boxchoice.Snapshot, map[string]boxchoice.Load) { t.Helper() return chooserFixtureWith(t, nil, boxes...) } // chooserFixtureWith is chooserFixture with edit applied to the roster // before it is signed (another namespace or pool for some box). func chooserFixtureWith(t *testing.T, edit func(*hostactors.Roster), boxes ...string) (ExecutorChooser, release.Store, *boxchoice.Snapshot, map[string]boxchoice.Load) { t.Helper() s := release.NewDirStore(t.TempDir()) pub, key, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } roster := hostactors.Roster{V: 1, DeploymentBucket: "bucket", Revision: 1, Regions: map[string]hostactors.Region{"ie": {Account: "123", AWSRegion: "eu-west-1"}}, Boxes: map[string]hostactors.Box{}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}} snap := &boxchoice.Snapshot{Complete: true} yes := true for i, ref := range boxes { roster.Boxes[ref] = hostactors.Box{Enrollment: strings.Repeat(string("abcdef"[i]), 32), Namespace: "platform", BoxID: ref, Label: ref, Region: "ie", Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-" + ref}, EvidenceSHA256: strings.Repeat("c", 64), State: "active"} snap.Boxes = append(snap.Boxes, boxchoice.Box{BoxID: ref, EIP: "203.0.113.1", Region: "eu-west-1", Placement: "ie", Fresh: true, HibernationConfigured: &yes, ServesSharedPool: &yes, AcceptsInbound: &yes, SecretsKeyFP: "fp", Capabilities: []string{boxchoice.Capability}, // Equal, roomy headroom from a file a minute old: spec 2's // policy (§7) admits every box, and the choice falls through // to the hash unless a test changes it. Headroom: &boxchoice.Headroom{MemFreeMiB: 4000, MemTotalMiB: 8000, DiskFreeMiB: 50000, DiskTotalMiB: 100000}, Generation: "2026-10-07T08:59:00Z"}) } if edit != nil { edit(&roster) } wire, err := release.SignRootEnvelope(release.RootSigner(key), roster) if err != nil { t.Fatal(err) } if _, err := s.Put(context.Background(), hostactors.Key, wire, ""); err != nil { t.Fatal(err) } loads := map[string]boxchoice.Load{} x := ExecutorChooser{Directory: hostactors.Directory{Store: s, PublicKey: pub, Bucket: "bucket"}, SecretsKeyFP: "fp", Namespace: "platform", Edges: func(context.Context) (boxchoice.Snapshot, error) { return *snap, nil }, Loads: func(context.Context) (map[string]boxchoice.Load, error) { return loads, nil }, Now: func() time.Time { return time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC) }} return x, s, snap, loads } func chooserRecord() Record { return Record{Holder: hostclaim.NewHolder{Family: "instance", Account: "bab", App: "jam", Host: "shop.bab.oncarlos.com", Region: "ie"}, Body: []byte(`{"host":"shop.bab.oncarlos.com","account":"bab","app":"jam","region":"ie"}`)} } func TestExecutorChooserSpreadsThenPrefersMemoryThenHashes(t *testing.T) { ctx := context.Background() x, _, snap, loads := chooserFixture(t, "box-a", "box-b") absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} first, err := x.Choose(ctx, absent, chooserRecord()) if err != nil { t.Fatal(err) } other := map[string]string{"box-a": "box-b", "box-b": "box-a"}[first.Box] if first.Enrollment != map[string]string{"box-a": strings.Repeat("a", 32), "box-b": strings.Repeat("b", 32)}[first.Box] { t.Fatalf("enrollment not the roster's: %+v", first) } // The hash winner already runs this app: spread prefers the other box. loads[first.Box] = boxchoice.Load{Hosts: 1, Apps: map[string]int{"bab/jam": 1}} if got, _ := x.Choose(ctx, absent, chooserRecord()); got.Box != other { t.Fatalf("soft spread: got %s, want %s", got.Box, other) } // Both run the app: the most free memory wins (spec 2 §7), whatever // the host counts say. loads[other] = boxchoice.Load{Hosts: 5, Apps: map[string]int{"bab/jam": 1}} for i := range snap.Boxes { if snap.Boxes[i].BoxID == other { snap.Boxes[i].Headroom.MemFreeMiB = 6000 } } if got, _ := x.Choose(ctx, absent, chooserRecord()); got.Box != other { t.Fatalf("headroom: got %s, want %s", got.Box, other) } } // withRole sets one box's roster role before the roster is signed. func withRole(ref, role string) func(*hostactors.Roster) { return func(r *hostactors.Roster) { b := r.Boxes[ref] b.Role = role r.Boxes[ref] = b } } // Region HA spec 2 §7: the chooser, and so creates and relocation // targets, applies the roster's roles and the policy's thresholds after // Eligible. Permits answers the same set, and Capacity counts it. func TestExecutorChooserAppliesRolesAndHeadroom(t *testing.T) { ctx := context.Background() absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} cases := []struct { name string roster func(*hostactors.Roster) edges func(*boxchoice.Box) }{ {"legacy role", withRole("box-a", hostactors.RoleLegacy), nil}, {"platform role", withRole("box-a", hostactors.RolePlatform), nil}, {"memory under 15%", nil, func(b *boxchoice.Box) { b.Headroom.MemFreeMiB = 1199 }}, {"disk under 10%", nil, func(b *boxchoice.Box) { b.Headroom.DiskFreeMiB = 9999 }}, {"edges file 16 minutes old", nil, func(b *boxchoice.Box) { b.Generation = "2026-10-07T08:44:00Z" }}, {"no headroom", nil, func(b *boxchoice.Box) { b.Headroom = nil }}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { x, _, snap, _ := chooserFixtureWith(t, tc.roster, "box-a", "box-b") for i := range snap.Boxes { if snap.Boxes[i].BoxID == "box-a" && tc.edges != nil { tc.edges(&snap.Boxes[i]) } } for range 4 { got, err := x.Choose(ctx, absent, chooserRecord()) if err != nil || got.Box != "box-b" { t.Fatalf("chose %+v %v, want box-b", got, err) } } if err := x.Permits(ctx, absent, chooserRecord(), "box-a"); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("Permits(box-a) = %v, want ErrNoEligibleBox", err) } if err := x.Permits(ctx, absent, chooserRecord(), "box-b"); err != nil { t.Fatalf("Permits(box-b) = %v", err) } if n, aws, err := x.Capacity(ctx, "ie"); err != nil || n != 1 || aws != "eu-west-1" { t.Fatalf("Capacity = %d in %q, %v; want 1 in eu-west-1", n, aws, err) } if boxes, err := x.PlaceableBoxes(ctx, "ie"); err != nil || len(boxes) != 1 || boxes[0] != "box-b" { t.Fatalf("PlaceableBoxes = %v, %v; want [box-b]", boxes, err) } }) } x, _, _, _ := chooserFixture(t, "box-a", "box-b") if n, aws, err := x.Capacity(ctx, "ie"); err != nil || n != 2 || aws != "eu-west-1" { t.Fatalf("Capacity with both placeable = %d in %q, %v; want 2 in eu-west-1", n, aws, err) } if _, _, err := x.Capacity(ctx, "us"); !errors.Is(err, ErrContract) { t.Fatalf("Capacity of a region the roster lacks = %v, want ErrContract", err) } } // Round 18 finding 2: with equal loads the winner is the greatest // SHA-256 of host + "\x00" + box id (claim P5-D2), computed here // independently of boxchoice. The two hostnames are chosen so that their // winners differ: a chooser that hashed anything but the record's host // would get at least one of them wrong. func TestExecutorChooserHashesTheRecordsHostOnEqualLoads(t *testing.T) { ctx := context.Background() winner := func(host string) string { a := sha256.Sum256([]byte(host + "\x00box-a")) b := sha256.Sum256([]byte(host + "\x00box-b")) if hex.EncodeToString(a[:]) > hex.EncodeToString(b[:]) { return "box-a" } return "box-b" } hosts := []string{"shop.bab.oncarlos.com", "cafe.bab.oncarlos.com"} if winner(hosts[0]) == winner(hosts[1]) { t.Fatal("the fixture's hostnames must choose opposite boxes") } for _, host := range hosts { x, _, _, _ := chooserFixture(t, "box-a", "box-b") r := chooserRecord() r.Holder.Host = host got, err := x.Choose(ctx, hostclaim.Observed{Host: host, Absent: true}, r) if err != nil || got.Box != winner(host) { t.Fatalf("%s: chose %+v %v, want %s", host, got, err, winner(host)) } } } func TestExecutorChooserNeverNamesAFencedLostOrUnknownBox(t *testing.T) { ctx := context.Background() absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} t.Run("lost", func(t *testing.T) { x, _, _, _ := chooserFixture(t, "box-a") obs := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Claim: &hostclaim.Claim{Lost: []string{"box-a"}}} if _, err := x.Choose(ctx, obs, chooserRecord()); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("chose a lost box: %v", err) } }) t.Run("owing a local retirement", func(t *testing.T) { // Spec 1 §2: a box whose old installation of the host is not yet // retired (park-local) is never chosen; the clean one is. x, _, _, _ := chooserFixture(t, "box-a", "box-b") obs := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Claim: &hostclaim.Claim{Obligations: []hostclaim.Obligation{{ID: "old", Op: "old-op", Kind: "park-local", Box: "box-a", Retirement: "r", Owner: "box:box-a"}}}} for range 4 { if got, err := x.Choose(ctx, obs, chooserRecord()); err != nil || got.Box != "box-b" { t.Fatalf("got %+v %v, want the box with no chore", got, err) } } if err := x.Permits(ctx, obs, chooserRecord(), "box-a"); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("permitted a box owing park-local: %v", err) } }) t.Run("fenced", func(t *testing.T) { x, s, _, _ := chooserFixture(t, "box-a", "box-w") f := termfence.Record{V: 1, Box: "box-a", Enrollment: strings.Repeat("a", 32), State: "terminated", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-box-a"}, ObservedAt: "2026-10-07T08:00:00Z", Via: termfence.ViaDescribe, RequestID: "req", WrittenBy: termfence.WrittenBy{Box: "box-w", Enrollment: strings.Repeat("b", 32)}} raw, _ := json.Marshal(f) if _, err := s.Put(ctx, "control/box-fences/"+strings.Repeat("a", 32)+".json", raw, ""); err != nil { t.Fatal(err) } got, err := x.Choose(ctx, absent, chooserRecord()) if err != nil || got.Box != "box-w" { t.Fatalf("got %+v %v, want the unfenced box", got, err) } }) t.Run("an edges file for a box the roster does not hold", func(t *testing.T) { x, _, snap, _ := chooserFixture(t) yes := true snap.Boxes = append(snap.Boxes, boxchoice.Box{BoxID: "stray", EIP: "203.0.113.9", Region: "eu-west-1", Placement: "ie", Fresh: true, HibernationConfigured: &yes, ServesSharedPool: &yes, AcceptsInbound: &yes, SecretsKeyFP: "fp", Capabilities: []string{boxchoice.Capability}}) if _, err := x.Choose(ctx, absent, chooserRecord()); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("chose an unenrolled box: %v", err) } }) t.Run("an incomplete snapshot", func(t *testing.T) { // Region HA 2a Task 11 ruling: a partial read is not "no room". x, _, snap, _ := chooserFixture(t, "box-a") snap.Complete = false if _, err := x.Choose(ctx, absent, chooserRecord()); !errors.Is(err, ErrEvidenceIncomplete) || errors.Is(err, ErrNoEligibleBox) { t.Fatalf("chose from a partial read: %v, want ErrEvidenceIncomplete", err) } }) t.Run("an unreadable fence", func(t *testing.T) { x, s, _, _ := chooserFixture(t, "box-a") if _, err := s.Put(ctx, "control/box-fences/"+strings.Repeat("a", 32)+".json", []byte("{"), ""); err != nil { t.Fatal(err) } if _, err := x.Choose(ctx, absent, chooserRecord()); err == nil || errors.Is(err, ErrNoEligibleBox) { t.Fatalf("an unreadable fence must refuse, not skip: %v", err) } }) } func TestExecutorChooserPermitsOnlyTheEligibleSet(t *testing.T) { ctx := context.Background() absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} for _, tc := range []struct { name string change func(*boxchoice.Box) }{ {"a stale edges file", func(b *boxchoice.Box) { b.Fresh = false }}, {"no region-ha-1 capability", func(b *boxchoice.Box) { b.Capabilities = nil }}, {"another secrets key", func(b *boxchoice.Box) { b.SecretsKeyFP = "other" }}, {"a draining box", func(b *boxchoice.Box) { b.Draining = true }}, } { t.Run(tc.name, func(t *testing.T) { x, _, snap, _ := chooserFixture(t, "box-a", "box-b") if err := x.Permits(ctx, absent, chooserRecord(), "box-a"); err != nil { t.Fatal(err) } tc.change(&snap.Boxes[0]) if err := x.Permits(ctx, absent, chooserRecord(), "box-a"); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("permitted: %v", err) } if err := x.Permits(ctx, absent, chooserRecord(), "box-b"); err != nil { t.Fatalf("the other box: %v", err) } }) } t.Run("an unknown box", func(t *testing.T) { x, _, _, _ := chooserFixture(t, "box-a") if err := x.Permits(ctx, absent, chooserRecord(), "box-z"); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("permitted: %v", err) } }) } func TestClaimLoadsCountsExecutorsPerBoxAndApp(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) created := time.Date(2026, 10, 4, 1, 0, 0, 0, time.UTC) for i, tc := range []struct{ host, app, box string }{ {"a.bab.oncarlos.com", "jam", "box-a"}, {"b.bab.oncarlos.com", "jam", "box-a"}, {"c.bab.oncarlos.com", "tea", "box-b"}, } { nh := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/" + tc.app + "/instances/" + tc.host + ".json", Account: "bab", App: tc.app, Host: tc.host, Region: "ie", RecordRegion: "ie", CreatedAt: created} body := []byte(`{"host":"` + tc.host + `","account":"bab","app":"` + tc.app + `","region":"ie","created_at":"2026-10-04T01:00:00Z"}`) o, _ := hostclaim.Read(ctx, s, tc.host) o, err := hostclaim.Update(ctx, s, o, hostclaim.BeginCreateOn(nh, body, "c", "console", created, hostclaim.ExecutorSelection{Box: tc.box, Enrollment: "e"})) if err != nil { t.Fatal(err) } if i < 2 { // the third is still creating: it has no executor yet if _, err := hostclaim.Update(ctx, s, o, hostclaim.CompleteCreate("c", tc.box)); err != nil { t.Fatal(err) } } } if _, err := s.Put(ctx, "control/hosts/broken.bab.oncarlos.com.json", []byte("{"), ""); err != nil { t.Fatal(err) } loads, err := ClaimLoads(ctx, s) if err != nil { t.Fatal(err) } if loads["box-a"].Hosts != 2 || loads["box-a"].Apps["bab/jam"] != 2 || loads["box-b"].Hosts != 0 { t.Fatalf("loads: %+v", loads) } } func TestProductionAuthoritySelectsOnlyWithAChooser(t *testing.T) { // No chooser (CARLOS_REGION_HA off): no selection and no error, so the // create is today's. sel, selected, err := (ProductionAuthority{}).SelectExecutor(context.Background(), hostclaim.Observed{}, Record{}) if err != nil || selected || sel != (hostclaim.ExecutorSelection{}) { t.Fatalf("no chooser: %+v %v %v", sel, selected, err) } // With a chooser the selection is the chooser's, read from its // evidence: box-a is draining, so box-b with its roster enrollment; // with both draining, no box and an error, never a selection. x, _, snap, _ := chooserFixture(t, "box-a", "box-b") snap.Boxes[0].Draining = true absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} sel, selected, err = (ProductionAuthority{Executors: &x, Namespace: "platform"}).SelectExecutor(context.Background(), absent, chooserRecord()) if err != nil || !selected || sel.Box != "box-b" || sel.Enrollment != strings.Repeat("b", 32) { t.Fatalf("with a chooser: %+v %v %v", sel, selected, err) } snap.Boxes[1].Draining = true if sel, selected, err = (ProductionAuthority{Executors: &x, Namespace: "platform"}).SelectExecutor(context.Background(), absent, chooserRecord()); !errors.Is(err, ErrNoEligibleBox) || selected { t.Fatalf("no eligible box: %+v %v %v", sel, selected, err) } } // Main's production create (claim Plan 5's StartCreate) selects through // selectExecutor. Without a chooser it is Plan 5's choice over the roster // alone; with one the choice is the chooser's, read from the edges // evidence, and Plan 5's scope (this console's namespace, the record's // pool) still binds it: a chosen box outside that scope refuses. func TestProductionCreateSelectsThroughTheChooser(t *testing.T) { ctx := context.Background() x, s, snap, _ := chooserFixture(t, "box-a", "box-b") r := chooserRecord() absent := hostclaim.Observed{Host: r.Holder.Host, Absent: true} a := ProductionAuthority{Store: s, Directory: x.Directory, Namespace: "platform", Now: x.Now} off, _, pool, err := a.selectExecutor(ctx, absent, r) if err != nil || pool != "default" { t.Fatalf("no chooser: %s %s %v", off, pool, err) } // The roster-only choice is draining: Plan 5 does not read the edges, // so it still names it; the chooser names the other box. other := map[string]string{"box-a": "box-b", "box-b": "box-a"}[off] for i := range snap.Boxes { snap.Boxes[i].Draining = snap.Boxes[i].BoxID == off } if again, _, _, err := a.selectExecutor(ctx, absent, r); err != nil || again != off { t.Fatalf("no chooser, after a drain: %s %v, want %s", again, err, off) } a.Executors = &x box, enrollment, pool, err := a.selectExecutor(ctx, absent, r) if err != nil || box != other || enrollment != strings.Repeat(map[string]string{"box-a": "a", "box-b": "b"}[other], 32) || pool != "default" { t.Fatalf("with a chooser: %s %s %s %v, want %s", box, enrollment, pool, err, other) } // Another console namespace: the chooser chooses within it, and it // holds no box. a.Namespace = "elsewhere" if box, _, _, err := a.selectExecutor(ctx, absent, r); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("a box outside the namespace: %s %v", box, err) } // Every box draining: the chooser's refusal, never Plan 5's fallback. a.Namespace = "platform" snap.Boxes[0].Draining, snap.Boxes[1].Draining = true, true if box, _, _, err := a.selectExecutor(ctx, absent, r); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("no eligible box: %s %v", box, err) } } // Fix round 1 (review Important 1): the chooser chooses within claim Plan // 5's scope, so a region that also holds another namespace's or another // pool's boxes places on the in-scope eligible box, never refusing because // an out-of-scope box would have won. func TestProductionCreateWithAChooserPlacesWithinPlan5sScope(t *testing.T) { ctx := context.Background() absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} // The console's chooser carries no scope of its own: the authority // supplies its namespace and the record's pool. authority := func(x *ExecutorChooser, s release.Store) ProductionAuthority { x.Namespace, x.Pool = "", "" return ProductionAuthority{Store: s, Directory: x.Directory, Namespace: "platform", Now: x.Now, Executors: x} } t.Run("another namespace's box would win on load", func(t *testing.T) { x, s, _, loads := chooserFixtureWith(t, func(r *hostactors.Roster) { b := r.Boxes["box-b"] b.Namespace = "other" r.Boxes["box-b"] = b }, "box-a", "box-b") loads["box-a"] = boxchoice.Load{Hosts: 5, Apps: map[string]int{"bab/jam": 1}} box, enrollment, pool, err := authority(&x, s).selectExecutor(ctx, absent, chooserRecord()) if err != nil || box != "box-a" || enrollment != strings.Repeat("a", 32) || pool != "default" { t.Fatalf("got %s %s %s %v, want the in-scope box-a", box, enrollment, pool, err) } }) t.Run("a named pool", func(t *testing.T) { // box-b serves only pool acme: its edges file does not serve the // shared pool. box-a is the shared pool's, and idle. x, s, snap, loads := chooserFixtureWith(t, func(r *hostactors.Roster) { b := r.Boxes["box-b"] b.Pool = "acme" r.Boxes["box-b"] = b }, "box-a", "box-b") no := false snap.Boxes[1].ServesSharedPool = &no loads["box-b"] = boxchoice.Load{Hosts: 5} named := chooserRecord() named.Body = []byte(`{"host":"shop.bab.oncarlos.com","account":"bab","app":"jam","region":"ie","pool":"acme"}`) box, enrollment, pool, err := authority(&x, s).selectExecutor(ctx, absent, named) if err != nil || box != "box-b" || enrollment != strings.Repeat("b", 32) || pool != "acme" { t.Fatalf("named pool: got %s %s %s %v, want box-b in acme", box, enrollment, pool, err) } // The shared pool still places on its own box, whatever the load. loads["box-a"] = boxchoice.Load{Hosts: 50} if box, _, pool, err := authority(&x, s).selectExecutor(ctx, absent, chooserRecord()); err != nil || box != "box-a" || pool != "default" { t.Fatalf("shared pool: got %s %s %v, want box-a", box, pool, err) } // The named pool's box must still be eligible on its own evidence. snap.Boxes[1].Draining = true if box, _, _, err := authority(&x, s).selectExecutor(ctx, absent, named); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("a draining named-pool box: %s %v", box, err) } }) t.Run("an unreadable fence on another namespace's box", func(t *testing.T) { x, s, _, _ := chooserFixtureWith(t, func(r *hostactors.Roster) { b := r.Boxes["box-b"] b.Namespace = "other" r.Boxes["box-b"] = b }, "box-a", "box-b") if _, err := s.Put(ctx, "control/box-fences/"+strings.Repeat("b", 32)+".json", []byte("{"), ""); err != nil { t.Fatal(err) } if box, _, _, err := authority(&x, s).selectExecutor(ctx, absent, chooserRecord()); err != nil || box != "box-a" { t.Fatalf("got %s %v, want box-a: an out-of-scope fence never blocks", box, err) } // In scope, the same unreadable fence still refuses (absence is // never evidence). if _, err := s.Put(ctx, "control/box-fences/"+strings.Repeat("a", 32)+".json", []byte("{"), ""); err != nil { t.Fatal(err) } if _, _, _, err := authority(&x, s).selectExecutor(ctx, absent, chooserRecord()); err == nil || errors.Is(err, ErrNoEligibleBox) { t.Fatalf("an unreadable in-scope fence must refuse: %v", err) } }) } // Main's home-region parks: a record placed in the home with no home name // configured names it park.HomeRegion. With the home binding (HomeName, // the create door's ProductionAuthority.HomeRegion) the chooser places it // in the home's roster region, as main's StartCreate maps it; with none, // park.HomeRegion is no roster region and the create is refused. func TestExecutorChooserPlacesAHomeRecordUnderTheHomeBinding(t *testing.T) { ctx := context.Background() r := chooserRecord() r.Holder.Region = park.HomeRegion absent := hostclaim.Observed{Host: r.Holder.Host, Absent: true} x, _, _, _ := chooserFixture(t, "box-a") x.HomeName = "ie" if got, err := x.Choose(ctx, absent, r); err != nil || got.Box != "box-a" { t.Fatalf("bound: %+v %v", got, err) } x.HomeName = "" if _, err := x.Choose(ctx, absent, r); !errors.Is(err, ErrContract) { t.Fatalf("unbound: %v", err) } } // Region HA spec 2 §7 (H27): a door that chose before registering holds // the create to its choice, for that host only; Plan 5's scope still // binds it (TestProductionCreateSelectsThroughTheChooser). func TestSelectExecutorHonoursTheDoorsChoice(t *testing.T) { ctx := context.Background() x, _, snap, _ := chooserFixture(t, "box-a", "box-b") a := ProductionAuthority{Namespace: "platform", Executors: &x} r := chooserRecord() absent := hostclaim.Observed{Host: r.Holder.Host, Absent: true} chosen := hostclaim.ExecutorSelection{Box: "box-b", Enrollment: strings.Repeat("b", 32)} // Every box is now ineligible, yet the door's earlier choice stands. for i := range snap.Boxes { snap.Boxes[i].Draining = true } sel, ok, err := a.SelectExecutor(WithChosenExecutor(ctx, r.Holder.Host, chosen), absent, r) if err != nil || !ok || sel != chosen { t.Fatalf("with the door's choice: %+v %v %v", sel, ok, err) } // Another host's choice is not this create's. if _, _, err := a.SelectExecutor(WithChosenExecutor(ctx, "other.bab.oncarlos.com", chosen), absent, r); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("another host's choice was used: %v", err) } // No chooser (region HA off): no selection, whatever the context says. if _, ok, err := (ProductionAuthority{}).SelectExecutor(WithChosenExecutor(ctx, r.Holder.Host, chosen), absent, r); ok || err != nil { t.Fatalf("region HA off selected: %v %v", ok, err) } } // Region HA 2a Task 11 ruling: an incomplete placement read (an unreadable // or malformed edges file, an unread status listing, a fresh box with no // edges file: boxchoice.Snapshot.Complete false) is ErrEvidenceIncomplete // from every chooser question, never ErrNoEligibleBox or an empty set, so // a door never answers or pages a partial read as "no room". The same // placeable box is chosen, permitted and counted once the read completes. func TestExecutorChooserAnIncompleteReadIsNotNoRoom(t *testing.T) { ctx := context.Background() x, _, snap, _ := chooserFixture(t, "box-a") absent := hostclaim.Observed{Host: "shop.bab.oncarlos.com", Absent: true} snap.Complete = false incomplete := func(what string, err error) { t.Helper() if !errors.Is(err, ErrEvidenceIncomplete) || errors.Is(err, ErrNoEligibleBox) { t.Fatalf("%s on a partial read = %v, want ErrEvidenceIncomplete", what, err) } } _, err := x.Choose(ctx, absent, chooserRecord()) incomplete("Choose", err) incomplete("Permits", x.Permits(ctx, absent, chooserRecord(), "box-a")) boxes, err := x.PlaceableBoxes(ctx, "ie") incomplete("PlaceableBoxes", err) if boxes != nil { t.Fatalf("PlaceableBoxes named %v from a partial read", boxes) } _, _, err = x.Capacity(ctx, "ie") incomplete("Capacity", err) _, _, err = (ProductionAuthority{Namespace: "platform", Executors: &x}).SelectExecutor(ctx, absent, chooserRecord()) incomplete("SelectExecutor", err) snap.Complete = true if sel, err := x.Choose(ctx, absent, chooserRecord()); err != nil || sel.Box != "box-a" { t.Fatalf("Choose on a complete read: %+v %v", sel, err) } if err := x.Permits(ctx, absent, chooserRecord(), "box-a"); err != nil { t.Fatalf("Permits on a complete read: %v", err) } if n, aws, err := x.Capacity(ctx, "ie"); err != nil || n != 1 || aws != "eu-west-1" { t.Fatalf("Capacity on a complete read: %d in %q %v", n, aws, err) } // A complete read with no placeable box is still no room. snap.Boxes[0].Fresh = false if _, err := x.Choose(ctx, absent, chooserRecord()); !errors.Is(err, ErrNoEligibleBox) { t.Fatalf("Choose with every box stale: %v, want ErrNoEligibleBox", err) } if n, aws, err := x.Capacity(ctx, "ie"); err != nil || n != 0 || aws != "eu-west-1" { t.Fatalf("Capacity with every box stale: %d in %q %v", n, aws, err) } }