| 1 | package hostops |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/hostclaim" |
| 8 | "github.com/carlosframework/platform/internal/moves" |
| 9 | "github.com/carlosframework/platform/internal/park" |
| 10 | "github.com/carlosframework/platform/internal/provision" |
| 11 | ) |
| 12 | |
| 13 | func TestMoveRecoveryCommitUsesSharedSealValidation(t *testing.T) { |
| 14 | for _, unknownField := range []bool{false, true} { |
| 15 | t.Run(map[bool]string{false: "valid", true: "unknown-seal-field"}[unknownField], func(t *testing.T) { |
| 16 | w := newLossWorld(t, "landing", func(c *hostclaim.Claim, r *provision.Record, _ *AppAdmission) { |
| 17 | c.Op.Phase = hostclaim.PhaseLandingProbed |
| 18 | r.Move.State = provision.MoveCommitted |
| 19 | r.Move.CommittedAt = r.Move.FlippedAt |
| 20 | if unknownField { |
| 21 | raw := c.Op.Journal[hostclaim.JournalSeal] |
| 22 | c.Op.Journal[hostclaim.JournalSeal] = `{"unknown":true,` + raw[1:] |
| 23 | } |
| 24 | }) |
| 25 | before := w.claim(t) |
| 26 | rec, tag := w.record(t) |
| 27 | err := w.control.RunRecovery(context.Background(), func(ctx context.Context) error { |
| 28 | return runMoveRecovery(ctx, before, func(ctx context.Context, g *MoveRecoveryGrant) error { |
| 29 | return g.Claim(ctx, RecoveryMoveCommit) |
| 30 | }) |
| 31 | }) |
| 32 | after := w.claim(t) |
| 33 | if unknownField { |
| 34 | if err == nil || after.ETag != before.ETag { |
| 35 | t.Fatalf("unsupported seal changed claim: err=%v, before=%s, after=%s", err, before.ETag, after.ETag) |
| 36 | } |
| 37 | return |
| 38 | } |
| 39 | if err != nil || after.Claim.Op.Phase != hostclaim.PhaseCommitted { |
| 40 | t.Fatalf("commit: %v, claim=%+v", err, after.Claim) |
| 41 | } |
| 42 | source, staging, err := moves.CommitScopes(before.Claim, rec.Move.FlippedAt) |
| 43 | if err != nil { |
| 44 | t.Fatal(err) |
| 45 | } |
| 46 | found := map[string]bool{} |
| 47 | for _, chore := range after.Claim.Obligations { |
| 48 | if chore.Kind == "park-replica" && chore.Park != nil { |
| 49 | if *chore.Park == source || *chore.Park == staging { |
| 50 | found[chore.Park.Location] = true |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | if !found[source.Location] || !found[staging.Location] { |
| 55 | t.Fatalf("commit parks differ from shared scopes: %+v", after.Claim.Obligations) |
| 56 | } |
| 57 | if _, nextTag := w.record(t); nextTag != tag { |
| 58 | t.Fatal("claim commit wrote the record") |
| 59 | } |
| 60 | }) |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // Home-region parks, part 1: the console's commit of a move out of home |
| 65 | // queues the source's location park in park.HomeRegion, exactly as the |
| 66 | // box's commit does (K9), and covers the home generation's admission. The |
| 67 | // record names home by its code, "", which the claim would refuse. |
| 68 | func TestMoveRecoveryCommitFromHome(t *testing.T) { |
| 69 | w := newLossWorld(t, "landing", func(c *hostclaim.Claim, r *provision.Record, _ *AppAdmission) { |
| 70 | c.Op.Phase = hostclaim.PhaseLandingProbed |
| 71 | r.Move.State = provision.MoveCommitted |
| 72 | r.Move.CommittedAt = r.Move.FlippedAt |
| 73 | r.Move.From = "" |
| 74 | for i := range c.Holder.Admissions { |
| 75 | a := &c.Holder.Admissions[i] |
| 76 | if a.Region == "ie" { |
| 77 | a.Region = park.HomeRegion |
| 78 | } |
| 79 | if a.PredecessorRegion == "ie" { |
| 80 | a.PredecessorRegion = park.HomeRegion |
| 81 | } |
| 82 | } |
| 83 | }) |
| 84 | before := w.claim(t) |
| 85 | err := w.control.RunRecovery(context.Background(), func(ctx context.Context) error { |
| 86 | return runMoveRecovery(ctx, before, func(ctx context.Context, g *MoveRecoveryGrant) error { |
| 87 | return g.Claim(ctx, RecoveryMoveCommit) |
| 88 | }) |
| 89 | }) |
| 90 | after := w.claim(t) |
| 91 | if err != nil || after.Claim.Op.Phase != hostclaim.PhaseCommitted { |
| 92 | t.Fatalf("commit: %v, claim=%+v", err, after.Claim.Op) |
| 93 | } |
| 94 | found := false |
| 95 | for _, o := range after.Claim.Obligations { |
| 96 | if o.Region == "" { |
| 97 | t.Fatalf("a chore in the unset region: %+v", o) |
| 98 | } |
| 99 | if o.Kind == "park-replica" && o.Park != nil && o.Park.Location == "instances/"+lossHost { |
| 100 | if o.Region != park.HomeRegion || o.Owner != "region:"+park.HomeRegion { |
| 101 | t.Fatalf("the source's location park %+v, want it in %s", o, park.HomeRegion) |
| 102 | } |
| 103 | found = true |
| 104 | } |
| 105 | } |
| 106 | if !found { |
| 107 | t.Fatalf("no location park: %+v", after.Claim.Obligations) |
| 108 | } |
| 109 | for _, a := range after.Claim.Holder.Admissions { |
| 110 | if a.PredecessorRegion == park.HomeRegion && !a.Covered { |
| 111 | t.Fatalf("the home generation's admission is not covered: %+v", a) |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | // Home part 2, Task 3 (the inventory's witness for this Commit site): the |
| 117 | // console's commit of a move into home, from clean claim state. The record |
| 118 | // names the target by the home's code, ""; the start CAS wrote the op's to |
| 119 | // as @home with home_region uk, the roster name of uk-box, whose admissions |
| 120 | // keep their engine region uk. The commit queues the lane park in @home, |
| 121 | // owned by region:@home, and the source's location park in ie; nothing is |
| 122 | // queued under uk or "". |
| 123 | func TestMoveRecoveryCommitIntoHome(t *testing.T) { |
| 124 | w := newLossWorld(t, "landing", func(c *hostclaim.Claim, r *provision.Record, _ *AppAdmission) { |
| 125 | c.Op.Phase = hostclaim.PhaseLandingProbed |
| 126 | c.Op.Intent["to"], c.Op.Intent["home_region"] = park.HomeRegion, "uk" |
| 127 | r.Move.State = provision.MoveCommitted |
| 128 | r.Move.CommittedAt = r.Move.FlippedAt |
| 129 | r.Move.To, r.Region = "", "" |
| 130 | }) |
| 131 | // The console names the same home (CONSOLE_HOME_REGION), as the |
| 132 | // operation's binding requires. |
| 133 | w.control.homeRegion = "uk" |
| 134 | before := w.claim(t) |
| 135 | if len(before.Claim.Obligations) != 0 { |
| 136 | t.Fatalf("fixture: the claim already owes %+v", before.Claim.Obligations) |
| 137 | } |
| 138 | rec, _ := w.record(t) |
| 139 | err := w.control.RunRecovery(context.Background(), func(ctx context.Context) error { |
| 140 | return runMoveRecovery(ctx, before, func(ctx context.Context, g *MoveRecoveryGrant) error { |
| 141 | return g.Claim(ctx, RecoveryMoveCommit) |
| 142 | }) |
| 143 | }) |
| 144 | after := w.claim(t) |
| 145 | if err != nil || after.Claim.Op.Phase != hostclaim.PhaseCommitted { |
| 146 | t.Fatalf("commit: %v, claim=%+v", err, after.Claim.Op) |
| 147 | } |
| 148 | source, staging, err := moves.CommitScopes(before.Claim, rec.Move.FlippedAt) |
| 149 | if err != nil { |
| 150 | t.Fatal(err) |
| 151 | } |
| 152 | var lane, loc bool |
| 153 | for _, o := range after.Claim.Obligations { |
| 154 | if o.Region == "" || o.Region == "uk" { |
| 155 | t.Fatalf("a chore named %q: %+v", o.Region, o) |
| 156 | } |
| 157 | switch { |
| 158 | case o.Kind == "park-replica" && o.Park != nil && *o.Park == staging: |
| 159 | if o.Region != park.HomeRegion || o.Owner != "region:"+park.HomeRegion { |
| 160 | t.Fatalf("the lane park %+v, want it in %s", o, park.HomeRegion) |
| 161 | } |
| 162 | lane = true |
| 163 | case o.Kind == "park-replica" && o.Park != nil && *o.Park == source: |
| 164 | if o.Region != "ie" || o.Owner != "region:ie" { |
| 165 | t.Fatalf("the source's location park %+v, want it in ie", o) |
| 166 | } |
| 167 | loc = true |
| 168 | } |
| 169 | } |
| 170 | if !lane || !loc { |
| 171 | t.Fatalf("the commit's parks: lane %v location %v in %+v", lane, loc, after.Claim.Obligations) |
| 172 | } |
| 173 | if after.Claim.Op.Intent["to"] != park.HomeRegion { |
| 174 | t.Fatalf("the op's to after the commit: %+v", after.Claim.Op.Intent) |
| 175 | } |
| 176 | } |
| 177 | |