| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostclaim" |
| 10 | "github.com/carlosframework/platform/internal/moves" |
| 11 | "github.com/carlosframework/platform/internal/park" |
| 12 | "github.com/carlosframework/platform/internal/provision" |
| 13 | ) |
| 14 | |
| 15 | // Home-region parks, part 1 (Paul, 2026-10-11): a move out of the home |
| 16 | // placement finishes its cleanup on its source box. The home's placement |
| 17 | // code is ""; its engine region is park.HomeRegion, mapped once where a |
| 18 | // placement code becomes an engine region (park.PlacementRegion). |
| 19 | |
| 20 | // TestMoveFromHomeClosesThroughTheSourcePark: the whole move out of home, |
| 21 | // through the real paths. The target commits through Plan 3's committed arm, |
| 22 | // which queues the source's location park in HomeRegion; the source box, |
| 23 | // whose placement is "", runs it through the real park engine and the real |
| 24 | // park view seen from home, retiring the pre-move generation from its |
| 25 | // bucket; and the move closes. |
| 26 | func TestMoveFromHomeClosesThroughTheSourcePark(t *testing.T) { |
| 27 | t.Parallel() |
| 28 | h := newCloseHarness(t, fromHome) |
| 29 | h.cfg.ParkView = movesParkView(MovesRecordsFrom(h.s), h.vs, "data-ie", park.HomeRegion) |
| 30 | h.cfg.ParkAuthorize = allowParkAuthorize |
| 31 | if rec := h.read(t, p1Host); rec.Move.From != "" || h.cfg.Region != "" { |
| 32 | t.Fatalf("fixture: not a move out of home: from %q, source region %q", rec.Move.From, h.cfg.Region) |
| 33 | } |
| 34 | h.land(t) |
| 35 | h.committedAndOpen(t) |
| 36 | src := h.chore(t, park.HomeRegion, false) |
| 37 | if src == nil || src.Owner != "region:"+park.HomeRegion || src.Park.Location != cutRoot { |
| 38 | t.Fatalf("the commit queued the source's location park as %+v, want it in %s", src, park.HomeRegion) |
| 39 | } |
| 40 | for _, o := range h.claim(t).Obligations { |
| 41 | if o.Region == "" || (o.Kind == "park-replica" && o.Region != park.HomeRegion && o.Region != "uk") { |
| 42 | t.Fatalf("the commit queued %+v", o) |
| 43 | } |
| 44 | } |
| 45 | // The target's probe recorded the home generation's region as home's, |
| 46 | // and the commit's location park covered it. |
| 47 | covered := false |
| 48 | for _, a := range h.claim(t).Holder.Admissions { |
| 49 | if a.Region == "uk" && a.Predecessor == "cur" { |
| 50 | if a.PredecessorRegion != park.HomeRegion || !a.Covered { |
| 51 | t.Fatalf("the probe's admission %+v: the home generation is not home's, or not covered", a) |
| 52 | } |
| 53 | covered = true |
| 54 | } |
| 55 | } |
| 56 | if !covered { |
| 57 | t.Fatalf("no admission retires the home generation: %+v", h.claim(t).Holder.Admissions) |
| 58 | } |
| 59 | h.clock.Advance(24 * time.Hour) |
| 60 | h.land(t) |
| 61 | if h.chore(t, "uk", true) != nil { |
| 62 | t.Fatal("the staging park was not acknowledged") |
| 63 | } |
| 64 | h.notClosed(t, "the source has not parked") |
| 65 | h.sourcePass(t) |
| 66 | h.done(t) |
| 67 | for k := range h.f.buckets["data-ie"] { |
| 68 | if strings.HasPrefix(k, cutRoot+"/") { |
| 69 | t.Fatalf("still at the home location: %s", k) |
| 70 | } |
| 71 | } |
| 72 | pre := 0 |
| 73 | for k := range h.f.buckets["data-ie"] { |
| 74 | if strings.HasPrefix(k, "parked/"+h.inc+"/"+p1Host+"/"+src.Retirement+"/instances/") && strings.Contains(k, "/g-cur/") { |
| 75 | pre++ |
| 76 | } |
| 77 | } |
| 78 | if pre == 0 { |
| 79 | t.Fatal("the pre-move generation was not archived under the location park's retirement") |
| 80 | } |
| 81 | q := h.claim(t).QuietAfter |
| 82 | if _, ok := q[park.HomeRegion]; !ok { |
| 83 | t.Fatalf("quiet_after %v names no %s", q, park.HomeRegion) |
| 84 | } |
| 85 | if _, ok := q[""]; ok { |
| 86 | t.Fatalf("quiet_after %v names the unset region", q) |
| 87 | } |
| 88 | if m := h.read(t, p1Host).LastMove; m == nil || m.Outcome != "done" { |
| 89 | t.Fatalf("the move did not close: %+v", m) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // TestHomeParkViewNamesHomeAsARegion: the real park view for a move out of |
| 94 | // home names HomeRegion where the spec's table names the source, seen from |
| 95 | // home and from elsewhere; before the commit, home stays authoritative on |
| 96 | // home (J2/F4). An unset region, a named region spelled "home" and a legacy |
| 97 | // home holder with no admission still refuse. |
| 98 | func TestHomeParkViewNamesHomeAsARegion(t *testing.T) { |
| 99 | type row struct { |
| 100 | name, seen string |
| 101 | claim func(f *vwFixture) *hostclaim.Claim |
| 102 | state string |
| 103 | want string // "" wants a refusal |
| 104 | } |
| 105 | op := func(kind, phase string) hostclaim.Op { |
| 106 | return hostclaim.Op{Kind: kind, ID: vwMove, Phase: phase, Exec: "box"} |
| 107 | } |
| 108 | home := park.HomeRegion |
| 109 | rows := []row{ |
| 110 | {"moving, a legacy holder, seen from home", home, func(f *vwFixture) *hostclaim.Claim { |
| 111 | return &hostclaim.Claim{Holder: f.holder(""), Op: op(hostclaim.KindMoving, hostclaim.PhaseMoving)} |
| 112 | }, provision.MoveCutting, home}, |
| 113 | {"moving, admitted at home, seen from us", "us", func(f *vwFixture) *hostclaim.Claim { |
| 114 | return &hostclaim.Claim{Holder: f.holder("cur", "cur@"+home), Op: op(hostclaim.KindMoving, hostclaim.PhaseMoving)} |
| 115 | }, provision.MoveCutting, home}, |
| 116 | {"reverting, seen from home", home, func(f *vwFixture) *hostclaim.Claim { |
| 117 | return &hostclaim.Claim{Holder: f.holder("cur", "cur@"+home), Op: op(hostclaim.KindReverting, "")} |
| 118 | }, provision.MoveReverting, home}, |
| 119 | {"landing, the target's probe admitted, seen from home", home, func(f *vwFixture) *hostclaim.Claim { |
| 120 | return &hostclaim.Claim{Holder: f.holder(vwMove, "cur@"+home, vwMove+"@uk"), Op: op(hostclaim.KindLanding, provision.LandProbing)} |
| 121 | }, provision.MoveLanding, home}, |
| 122 | {"landing, the target's probe admitted, seen from us", "us", func(f *vwFixture) *hostclaim.Claim { |
| 123 | return &hostclaim.Claim{Holder: f.holder(vwMove, "cur@"+home, vwMove+"@uk"), Op: op(hostclaim.KindLanding, provision.LandProbing)} |
| 124 | }, provision.MoveLanding, "uk"}, |
| 125 | {"landing committed, seen from home", home, func(f *vwFixture) *hostclaim.Claim { |
| 126 | return &hostclaim.Claim{Holder: f.holder(vwMove, "cur@"+home, vwMove+"@uk"), Op: op(hostclaim.KindLanding, hostclaim.PhaseCommitted)} |
| 127 | }, provision.MoveCommitted, "uk"}, |
| 128 | {"restoring before any admission", home, func(f *vwFixture) *hostclaim.Claim { |
| 129 | return &hostclaim.Claim{Holder: f.holder(""), Op: op(hostclaim.KindRestoring, hostclaim.PhaseReleasing)} |
| 130 | }, provision.MoveRollingBack, home}, |
| 131 | {"moving, admitted in a named region spelled home", home, func(f *vwFixture) *hostclaim.Claim { |
| 132 | return &hostclaim.Claim{Holder: f.holder("cur", "cur@home"), Op: op(hostclaim.KindMoving, hostclaim.PhaseMoving)} |
| 133 | }, provision.MoveCutting, ""}, |
| 134 | {"moving, seen from the unset region", "", func(f *vwFixture) *hostclaim.Claim { |
| 135 | return &hostclaim.Claim{Holder: f.holder(""), Op: op(hostclaim.KindMoving, hostclaim.PhaseMoving)} |
| 136 | }, provision.MoveCutting, ""}, |
| 137 | {"serving, a legacy home holder with no admission", home, func(f *vwFixture) *hostclaim.Claim { |
| 138 | return &hostclaim.Claim{Holder: f.holder(""), Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: "box"}} |
| 139 | }, "", ""}, |
| 140 | } |
| 141 | for _, r := range rows { |
| 142 | t.Run(r.name, func(t *testing.T) { |
| 143 | f := newVwFixture(t) |
| 144 | f.rec.Region = "" |
| 145 | if r.state != "" { |
| 146 | f.rec.Move = &provision.MoveState{ID: vwMove, Incarnation: f.inc, From: "", To: "uk", State: r.state} |
| 147 | } |
| 148 | obs := vwObs(r.claim(f)) |
| 149 | v, err := movesParkView(f.records(), f.bucket, "data", r.seen)(t.Context(), obs) |
| 150 | if r.want == "" { |
| 151 | if !errors.Is(err, park.ErrUnsafe) { |
| 152 | t.Fatalf("view %+v, %v; want a refusal", v, err) |
| 153 | } |
| 154 | return |
| 155 | } |
| 156 | if err != nil { |
| 157 | t.Fatal(err) |
| 158 | } |
| 159 | if v.AuthoritativeRegion != r.want || v.Region != r.seen { |
| 160 | t.Fatalf("view seen from %q names %q authoritative, want %q", v.Region, v.AuthoritativeRegion, r.want) |
| 161 | } |
| 162 | if (r.want == r.seen) != v.MarkerKnown { |
| 163 | t.Fatalf("the marker was read %v, seen from %s with %s authoritative", v.MarkerKnown, r.seen, r.want) |
| 164 | } |
| 165 | }) |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // TestHomeLaneIsArmed: the lane park's arming takes the home's "" as a |
| 170 | // region (the lane predicate), and refuses a code that is no placement. |
| 171 | func TestHomeLaneIsArmed(t *testing.T) { |
| 172 | c := newRevertHarness(t).laneCfg() |
| 173 | for code, armed := range map[string]bool{"uk": true, "": true, "home": true, "IE": false, park.HomeRegion: false, "a/b": false} { |
| 174 | c.Region = code |
| 175 | if revertLaneArmed(c) != armed { |
| 176 | t.Fatalf("lane armed for %q: %v, want %v", code, !armed, armed) |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // TestMovesClaimPolicyRecoveryFromHome: a rollback's or a revert's recovery |
| 182 | // on a home source is granted to the source's selection in park.HomeRegion, |
| 183 | // the claim's engine region for home, and refused to one in a named region, |
| 184 | // one named "home" among them. |
| 185 | func TestMovesClaimPolicyRecoveryFromHome(t *testing.T) { |
| 186 | type base struct { |
| 187 | claim func() *hostclaim.Claim |
| 188 | record func() *provision.Record |
| 189 | } |
| 190 | for name, b := range map[string]base{ |
| 191 | "restoring probing": {func() *hostclaim.Claim { return rpRestoringClaim(rpClaimPhase(provision.RbSourceProbing)) }, |
| 192 | func() *provision.Record { return rpRestoringRecord(provision.RbSourceProbing) }}, |
| 193 | "reverting probing": {func() *hostclaim.Claim { return rpRevertingClaim(hostclaim.PhaseProbing) }, rpRevertingRecord}, |
| 194 | } { |
| 195 | for region, granted := range map[string]bool{park.HomeRegion: true, "home": false, "ie": false, "uk": false} { |
| 196 | c, rec := b.claim(), b.record() |
| 197 | rec.Move.From = "" |
| 198 | s := rpSelection(c, "spawn") |
| 199 | s.Region = region |
| 200 | err := MovesClaimPolicy((&mpReader{rec: rec}).read)(t.Context(), c, s, rpIdentity("spawn")) |
| 201 | if (err == nil) != granted { |
| 202 | t.Fatalf("%s from home, selection in %q: %v, want granted %v", name, region, err, granted) |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // TestHomeLaneStagingLeaseKey: the staging lease of a lane in home (a move |
| 209 | // out of home's return lane, taken with the record's From) is keyed by |
| 210 | // park.HomeRegion, never an empty segment, and apart from every named |
| 211 | // region, one named "home" among them; a code that is no placement refuses. |
| 212 | func TestHomeLaneStagingLeaseKey(t *testing.T) { |
| 213 | const host = "a.go.example.com" |
| 214 | home, err := stagingLeaseKey("", host) |
| 215 | if err != nil || home != "control/staging-leases/"+park.HomeRegion+"/"+host || strings.Contains(home, "//") { |
| 216 | t.Fatalf("the home lane's staging lease is %q, %v", home, err) |
| 217 | } |
| 218 | for _, code := range []string{"uk", "home", "none"} { |
| 219 | if got, err := stagingLeaseKey(code, host); err != nil || got == home || got != moves.StagingLeasePrefix(code, host) { |
| 220 | t.Fatalf("%s's staging lease is %q, %v (home's %q)", code, got, err, home) |
| 221 | } |
| 222 | } |
| 223 | for _, code := range []string{"IE", park.HomeRegion, "a/b"} { |
| 224 | if got, err := stagingLeaseKey(code, host); !errors.Is(err, park.ErrUnsafe) { |
| 225 | t.Fatalf("invalid code %q: %q, %v", code, got, err) |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |