package hostrelocate import ( "context" "errors" "slices" "testing" "time" "github.com/carlosframework/platform/internal/boxchoice" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostops" ) // scopedChooser plugs create's chooser into the fixture's worker, scoped // to namespace, with every fixture box fresh and eligible on its edges // file, equally roomy under spec 2's policy (ยง7), and spa the most loaded. func scopedChooser(f *fixture, namespace string) RegionChooser { yes := true snap := boxchoice.Snapshot{Complete: true} generation := f.w.Now().Add(-time.Minute).UTC().Format(time.RFC3339) for _, ref := range []string{"src", "dst", "spa", "sp2"} { 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}, Headroom: &boxchoice.Headroom{MemFreeMiB: 4000, MemTotalMiB: 8000, DiskFreeMiB: 50000, DiskTotalMiB: 100000}, Generation: generation}) } loads := map[string]boxchoice.Load{"spa": {Hosts: 9, Apps: map[string]int{"bab/jam": 3}}} return RegionChooser{Records: f.s, Namespace: namespace, Region: "ie", Executors: hostops.ExecutorChooser{Directory: f.w.Directory, SecretsKeyFP: "fp", Now: f.w.Now, Edges: func(context.Context) (boxchoice.Snapshot, error) { return snap, nil }, Loads: func(context.Context) (map[string]boxchoice.Load, error) { return loads, nil }}} } // Task 2's fix: the chooser chooses only within claim Plan 5's scope, and // an unscoped one chooses nothing. With the scope set from the worker's // namespace and the record's pool, the worker picks a target: never the // source (the least loaded box, so the chooser alone would name it), never // the dead target, never a box of another namespace. func TestWorkerChoosesATargetWithinTheHostsScope(t *testing.T) { f := newFixture(t) ctx := context.Background() body := `{"host":"` + host + `","account":"bab","app":"jam","region":"ie","created_at":"2026-10-04T01:00:00Z"}` if _, err := f.s.Put(ctx, f.claim().Claim.Holder.Key, []byte(body), ""); err != nil { t.Fatal(err) } b := f.roster.Boxes["sp2"] b.Namespace = "other" f.roster.Boxes["sp2"] = b f.roster.Revision++ f.writeRoster() unscoped := scopedChooser(f, "") f.w.Choose, f.w.Eligible = unscoped.Choose, unscoped.Eligible before := f.claim().ETag if _, err := f.w.Start(ctx, host, Request{Reason: hostclaim.ReasonDrain, Source: "src", Target: "dst"}); !errors.Is(err, ErrUnsupported) { t.Fatalf("an unscoped chooser admitted a target: %v", err) } scoped := scopedChooser(f, "platform") f.w.Choose, f.w.Eligible = scoped.Choose, scoped.Eligible if _, err := f.w.Start(ctx, host, Request{Reason: hostclaim.ReasonDrain, Source: "src", Target: "sp2"}); !errors.Is(err, hostops.ErrNoEligibleBox) { t.Fatalf("started on a box of another namespace: %v", err) } if f.claim().ETag != before { t.Fatal("a refused start wrote the claim") } if _, err := f.w.Start(ctx, host, Request{Reason: hostclaim.ReasonDrain, Source: "src", Target: "dst"}); err != nil { t.Fatal(err) } f.terminate("dst") o := f.worker(nil) if o.Claim.Op.Intent["target_box"] != "spa" || o.Claim.Op.Intent["target_enrollment"] != enrollment("spa") || !slices.Contains(o.Claim.Lost, "dst") { t.Fatalf("replacement: %+v lost %v", o.Claim.Op.Intent, o.Claim.Lost) } }