| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "slices" |
| 8 | "testing" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/hostbox" |
| 11 | "github.com/carlosframework/platform/internal/hostclaim" |
| 12 | "github.com/carlosframework/platform/internal/park" |
| 13 | "github.com/carlosframework/platform/internal/provision" |
| 14 | "github.com/carlosframework/platform/internal/release" |
| 15 | ) |
| 16 | |
| 17 | // Moves into the home region, part 2, Task 2: the fixtures for a home |
| 18 | // target box. intoHome mirrors fromHome for the target: the source is |
| 19 | // ca-box in ca, the target home-box with placement "", whose roster name is |
| 20 | // ie and whose fleet designates ie the home. The witnesses of Tasks 3 to 5 |
| 21 | // stand on these fixtures, so this test pins what each one is. |
| 22 | |
| 23 | var _ release.Store = p1FilesStore{} |
| 24 | |
| 25 | // TestIntoHomeFixtureIsAHomeTarget: the record's move names the home's |
| 26 | // code, the target's edges file publishes no placement, the claim's op |
| 27 | // names the placement-derived @home with home_region ie, the target's held |
| 28 | // environment sets no CARLOS_REGION, and the close harness's production |
| 29 | // probe mode admits on home-box in the claim engine's region ie. |
| 30 | func TestIntoHomeFixtureIsAHomeTarget(t *testing.T) { |
| 31 | ctx := context.Background() |
| 32 | |
| 33 | t.Run("the move, the edges file, the claim and the held env", func(t *testing.T) { |
| 34 | h := newPhase1Harness(t, intoHome) |
| 35 | rec := h.start(t, "manual") |
| 36 | if rec.Move == nil || rec.Move.To != "" || rec.Move.From != "ca" || rec.Region != "ca" { |
| 37 | t.Fatalf("the record: region %q move %+v", rec.Region, rec.Move) |
| 38 | } |
| 39 | body, _, err := h.s.Get(ctx, "control/edges/home-box.json") |
| 40 | if err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | var edges map[string]any |
| 44 | if err := json.Unmarshal(body, &edges); err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | if _, ok := edges["placement"]; ok || edges["data_bucket"] != "data-home" || edges["box"] != "home-box" { |
| 48 | t.Fatalf("the home box's edges file: %s", body) |
| 49 | } |
| 50 | if _, _, err := h.s.Get(ctx, "control/edges/uk-box.json"); err == nil { |
| 51 | t.Fatal("a uk-box edges file is still published beside the home box") |
| 52 | } |
| 53 | c := h.claim(t) |
| 54 | if c.Op.Kind != hostclaim.KindMoving || c.Op.Intent["to"] != park.HomeRegion || c.Op.Intent["home_region"] != "ie" || |
| 55 | c.Op.Intent["source"] != "ca-box" || c.Op.Intent["target"] != "home-box" || c.Op.Exec != "ca-box" { |
| 56 | t.Fatalf("the claim's op: %+v", c.Op) |
| 57 | } |
| 58 | if c.Op.Intent["source_enrollment"] != p1SrcEnrollment || c.Op.Intent["target_enrollment"] != p1DstEnrollment || c.Op.Intent["admission_id"] != p1ID { |
| 59 | t.Fatalf("the op was not begun as the claim-mode door begins it: %+v", c.Op.Intent) |
| 60 | } |
| 61 | a := h.roster[h.dstBox] |
| 62 | if a.Region != "ie" || a.FleetHomeRegion != "ie" || a.HomeRegion != "ie" || h.roster[h.srcBox].Region != "ca" || h.roster[h.srcBox].HomeRegion != "" { |
| 63 | t.Fatalf("the roster: %+v", h.roster) |
| 64 | } |
| 65 | tc := h.targetCfg() |
| 66 | if tc.Region != "" || tc.Box != "home-box" || tc.OwnBucket != "data-home" || tc.OwnBucketRegion != "eu-west-1" || tc.ParkHomeRegion != "ie" { |
| 67 | t.Fatalf("the target's configuration: region %q box %q bucket %q/%q park home %q", tc.Region, tc.Box, tc.OwnBucket, tc.OwnBucketRegion, tc.ParkHomeRegion) |
| 68 | } |
| 69 | env, _, _, _, err := tc.ComposeHeldEnv(ctx, h.s, rec, nil) |
| 70 | if err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | if _, ok := env["CARLOS_REGION"]; ok { |
| 74 | t.Fatal("the home target's held env sets CARLOS_REGION") |
| 75 | } |
| 76 | ch := newCutHarness(t, intoHome) |
| 77 | env, _, _, _, err = ch.target().ComposeHeldEnv(ctx, ch.s, rec, nil) |
| 78 | if err != nil { |
| 79 | t.Fatal(err) |
| 80 | } |
| 81 | if _, ok := env["CARLOS_REGION"]; ok { |
| 82 | t.Fatal("the cut harness's home target sets CARLOS_REGION") |
| 83 | } |
| 84 | // The default fixture is unchanged: uk sets its own region. |
| 85 | named := newPhase1Harness(t) |
| 86 | env, _, _, _, err = named.targetCfg().ComposeHeldEnv(ctx, named.s, rec, nil) |
| 87 | if err != nil || env["CARLOS_REGION"] != "uk" { |
| 88 | t.Fatalf("the named target's held env: %v %q", err, env["CARLOS_REGION"]) |
| 89 | } |
| 90 | }) |
| 91 | |
| 92 | t.Run("retained state in either spelling", func(t *testing.T) { |
| 93 | for _, spelling := range []string{"ie", park.HomeRegion} { |
| 94 | h := newPhase1Harness(t, intoHome, retainTouched(spelling), retainQuiet(spelling), retainChore("ie")) |
| 95 | h.start(t, "manual") |
| 96 | c := h.claim(t) |
| 97 | if !containsString(c.Touched.Regions, spelling) || !containsString(c.Touched.Regions, "ca") { |
| 98 | t.Fatalf("%s: touched %+v", spelling, c.Touched) |
| 99 | } |
| 100 | if _, ok := c.QuietAfter[spelling]; !ok || len(c.QuietAfter) != 1 { |
| 101 | t.Fatalf("%s: quiet_after %v", spelling, c.QuietAfter) |
| 102 | } |
| 103 | var chore *hostclaim.Obligation |
| 104 | for _, o := range c.Obligations { |
| 105 | if o.Op == retainedChoreOp { |
| 106 | chore = &o |
| 107 | } |
| 108 | } |
| 109 | if chore == nil || chore.Region != "ie" || chore.Owner != "region:ie" || chore.Kind != "park-generation" || chore.Generation != retainedChoreGeneration || |
| 110 | chore.Park == nil || chore.Park.Location != c.Holder.Location || chore.Park.Incarnation != h.inc { |
| 111 | t.Fatalf("%s: the earlier residue chore: %+v", spelling, c.Obligations) |
| 112 | } |
| 113 | if c.Op.Intent["to"] != park.HomeRegion { |
| 114 | t.Fatalf("%s: the op's to: %+v", spelling, c.Op.Intent) |
| 115 | } |
| 116 | } |
| 117 | }) |
| 118 | |
| 119 | t.Run("the production probe admits on home-box in ie", func(t *testing.T) { |
| 120 | h := newCloseHarness(t, intoHome, realLaunchEnv) |
| 121 | if m := h.read(t, p1Host).Move; m.To != "" || m.State != provision.MoveLanding || m.LandingPhase != provision.LandRestored { |
| 122 | t.Fatalf("the close harness: %+v", m) |
| 123 | } |
| 124 | p := h.productionProbe(t) |
| 125 | h.land(t) |
| 126 | if len(p.admissions) != 1 { |
| 127 | t.Fatalf("admissions through the claim runtime: %+v", p.admissions) |
| 128 | } |
| 129 | a := p.admissions[0] |
| 130 | if a.Box != "home-box" || a.Region != "ie" || a.PredecessorRegion != "ca" || a.Mode != "cold" || a.Generation != p1ID || a.Coverage != "sealed" { |
| 131 | t.Fatalf("the probe's admission: %+v", a) |
| 132 | } |
| 133 | if p.owners.Box != "home-box" || p.owners.Region != "ie" || p.owners.HomeRegion != "ie" || p.owners.FleetHomeRegion != "ie" { |
| 134 | t.Fatalf("the owners' identity: box %q region %q home %q fleet %q", p.owners.Box, p.owners.Region, p.owners.HomeRegion, p.owners.FleetHomeRegion) |
| 135 | } |
| 136 | c := h.claim(t) |
| 137 | if c.Holder.WriterBox != "home-box" || c.Op.Intent["to"] != park.HomeRegion { |
| 138 | t.Fatalf("the claim after the probe: writer %q to %q", c.Holder.WriterBox, c.Op.Intent["to"]) |
| 139 | } |
| 140 | // The writer's evidence was the owners': asked once, for the one |
| 141 | // admission. |
| 142 | if p.evidence != 1 { |
| 143 | t.Fatalf("the writer's admission evidence ran %d times, want 1", p.evidence) |
| 144 | } |
| 145 | // Materialize ran the production owners' held steps, in order. |
| 146 | if want := []string{"render", "select:held-config", "select:held-release", "release:v7"}; len(p.order) < len(want) || !slices.Equal(p.order[:len(want)], want) { |
| 147 | t.Fatalf("the owners' held steps: %v", p.order) |
| 148 | } |
| 149 | h.committedAndOpen(t) |
| 150 | }) |
| 151 | |
| 152 | t.Run("rejected admission evidence admits nothing", func(t *testing.T) { |
| 153 | h := newCloseHarness(t, intoHome, realLaunchEnv) |
| 154 | p := h.productionProbe(t) |
| 155 | before := h.claim(t).Holder |
| 156 | p.rejectEvidence = hostbox.ErrChanged |
| 157 | h.land(t) |
| 158 | if p.evidence == 0 { |
| 159 | t.Fatal("the writer never asked for admission evidence") |
| 160 | } |
| 161 | after := h.claim(t).Holder |
| 162 | if len(p.admissions) != 0 || len(after.Admissions) != len(before.Admissions) || after.Session != before.Session || after.WriterBox == "home-box" { |
| 163 | t.Fatalf("an admission went through rejected evidence: %+v (before %+v)", after, before) |
| 164 | } |
| 165 | if m := h.read(t, p1Host).Move; m.State != provision.MoveLanding || m.LandingPhase != provision.LandProbing { |
| 166 | t.Fatalf("the landing moved past probing: %+v", m) |
| 167 | } |
| 168 | // The owners' own evidence refuses an admission Select never made: |
| 169 | // the dispatch the writer calls is the owners'. |
| 170 | p.rejectEvidence = nil |
| 171 | o, err := hostclaim.Read(t.Context(), h.s, p1Host) |
| 172 | if err != nil { |
| 173 | t.Fatal(err) |
| 174 | } |
| 175 | in, err := p.owners.Select(t.Context(), h.route, o, poProbe) |
| 176 | if err != nil { |
| 177 | t.Fatal(err) |
| 178 | } |
| 179 | if err := p.writerEvidence(t.Context(), o, in.Admission, in.Baseline); err != nil { |
| 180 | t.Fatalf("the writer's evidence refused the owners' own admission: %v", err) |
| 181 | } |
| 182 | bad := in.Admission |
| 183 | bad.Box = "ca-box" |
| 184 | if err := p.writerEvidence(t.Context(), o, bad, in.Baseline); !errors.Is(err, hostbox.ErrChanged) { |
| 185 | t.Fatalf("the writer's evidence accepted an admission on %s: %v", bad.Box, err) |
| 186 | } |
| 187 | }) |
| 188 | |
| 189 | t.Run("the probe and recovery owners' fixtures", func(t *testing.T) { |
| 190 | f := newPOFixture(t, poIntoHome) |
| 191 | in, err := f.owners.Select(t.Context(), f.route, f.read(), poProbe) |
| 192 | if err != nil { |
| 193 | t.Fatal(err) |
| 194 | } |
| 195 | if a := in.Admission; a.Box != "home-box" || a.Region != "ie" || a.PredecessorRegion != "ca" || a.Mode != "cold" { |
| 196 | t.Fatalf("the probe owners' admission: %+v", a) |
| 197 | } |
| 198 | if err := f.owners.AdmissionEvidence(t.Context(), f.read(), in.Admission, in.Baseline); err != nil { |
| 199 | t.Fatal(err) |
| 200 | } |
| 201 | if o := f.read(); o.Claim.Op.Intent["to"] != park.HomeRegion || o.Claim.Op.Intent["home_region"] != "ie" || f.rec.Move.To != "" { |
| 202 | t.Fatalf("the probe owners' fixture: %+v %+v", o.Claim.Op.Intent, f.rec.Move) |
| 203 | } |
| 204 | for _, kind := range []string{rcClean, rcDirty, rcRevert} { |
| 205 | f := newRCFixture(t, kind, poIntoHome) |
| 206 | o := f.read() |
| 207 | in := f.selectAdmission(o) |
| 208 | if a := in.Admission; a.Box != "ca-box" || a.Region != "ca" { |
| 209 | t.Fatalf("%s: the recovery owners' admission: %+v", kind, a) |
| 210 | } |
| 211 | if err := f.owners.AdmissionEvidence(t.Context(), o, in.Admission, in.Baseline); err != nil { |
| 212 | t.Fatalf("%s: %v", kind, err) |
| 213 | } |
| 214 | if o.Claim.Op.Intent["to"] != park.HomeRegion || o.Claim.Op.Intent["target"] != "home-box" { |
| 215 | t.Fatalf("%s: the recovery claim: %+v", kind, o.Claim.Op) |
| 216 | } |
| 217 | } |
| 218 | }) |
| 219 | |
| 220 | t.Run("the revert fixture", func(t *testing.T) { |
| 221 | for _, from := range []string{"stopping", "stopped"} { |
| 222 | h := newRevertHarness(t, intoHome) |
| 223 | if from == "stopping" { |
| 224 | h.fromStopping(t) |
| 225 | h.ev.stopped = true |
| 226 | } else { |
| 227 | h.fromStopped(t) |
| 228 | } |
| 229 | h.arm(t) |
| 230 | h.drive(t) |
| 231 | h.closed(t) |
| 232 | if rec := h.read(t, p1Host); rec.Region != "ca" || rec.LastMove == nil || rec.LastMove.Outcome != "rolled_back" { |
| 233 | t.Fatalf("%s: the record: %+v", from, rec) |
| 234 | } |
| 235 | if keys := h.laneKeys(); len(keys) != 0 { |
| 236 | t.Fatalf("%s: the home bucket's lane still holds %v", from, keys) |
| 237 | } |
| 238 | } |
| 239 | }) |
| 240 | } |
| 241 | |