| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "github.com/carlosframework/platform/internal/hostclaim" |
| 6 | "github.com/carlosframework/platform/internal/lease" |
| 7 | "github.com/carlosframework/platform/internal/park" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | ) |
| 12 | |
| 13 | func plan12Retired(t *testing.T) hostclaim.Observed { |
| 14 | t.Helper() |
| 15 | t.Setenv("CARLOS_HOST_CLAIM_RETIRED_LOCATIONS", "1") |
| 16 | f := newRecoveryFixture(t) |
| 17 | o := f.read() |
| 18 | var err error |
| 19 | o, err = hostclaim.Update(t.Context(), f.claims, o, func(c *hostclaim.Claim) error { |
| 20 | c.Holder.Location = "instances/" + o.Host |
| 21 | c.Touched = hostclaim.Touched{Complete: true} |
| 22 | return hostclaim.BeginDelete("delete", "console", "record", f.now, nil, nil, "")(c) |
| 23 | }) |
| 24 | if err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | o, err = hostclaim.Update(t.Context(), f.claims, o, hostclaim.ToParking("delete", nil, nil, f.newID)) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | o, err = hostclaim.Update(t.Context(), f.claims, o, hostclaim.FreeAfterPark("delete")) |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | return o |
| 36 | } |
| 37 | func TestPlan12ArchivedIncarnationCensus(t *testing.T) { |
| 38 | for _, mode := range []string{"legacy", "old-incarnation", "missing-history", "qualified-beyond-history", "ambiguous-legacy"} { |
| 39 | t.Run(mode, func(t *testing.T) { |
| 40 | o := plan12Retired(t) |
| 41 | inc := o.Claim.RetiredIncarnations[0] |
| 42 | loc := "instances/" + o.Host |
| 43 | if mode == "old-incarnation" { |
| 44 | loc += "/" + inc |
| 45 | } |
| 46 | if mode == "missing-history" { |
| 47 | o.Claim.RetiredLocations = nil |
| 48 | } |
| 49 | if mode == "qualified-beyond-history" { |
| 50 | loc += "/" + strings.Repeat("b", 64) |
| 51 | } |
| 52 | if mode == "ambiguous-legacy" { |
| 53 | other := strings.Repeat("b", 64) |
| 54 | o.Claim.RetiredIncarnations = append(o.Claim.RetiredIncarnations, other) |
| 55 | o.Claim.RetiredLocations[other] = loc |
| 56 | } |
| 57 | versions := []lease.ObjectVersion{{Key: loc + "/0000/db", VersionID: "v1"}} |
| 58 | labels, err := ClaimRetirementCensus(o, versions) |
| 59 | if mode == "qualified-beyond-history" { |
| 60 | inc = strings.Repeat("b", 64) |
| 61 | } |
| 62 | if mode == "legacy" || mode == "old-incarnation" || mode == "qualified-beyond-history" { |
| 63 | if err != nil || labels[loc] != inc { |
| 64 | t.Fatal(labels, err) |
| 65 | } |
| 66 | } else { |
| 67 | var dependency *ClaimDependencyError |
| 68 | if !errors.As(err, &dependency) { |
| 69 | t.Fatal("not a typed dependency", err) |
| 70 | } |
| 71 | } |
| 72 | }) |
| 73 | } |
| 74 | } |
| 75 | func TestPlan12LegacyAndMovesHaveIndependentLabels(t *testing.T) { |
| 76 | o := plan12Retired(t) |
| 77 | legacy := o.Claim.RetiredIncarnations[0] |
| 78 | move := strings.Repeat("c", 64) |
| 79 | versions := []lease.ObjectVersion{{Key: "instances/" + o.Host + "/0000/db", VersionID: "v1"}, {Key: "moves/" + o.Host + "/op/replica/db", VersionID: "v2"}} |
| 80 | view := park.View{ClaimETag: o.ETag, Region: "ie", ArchivedIncarnations: claimArchivedLocations(o.Claim)} |
| 81 | d, err := park.Decide(o, view, versions, move) |
| 82 | if err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | if len(d.Candidates) != 2 { |
| 86 | t.Fatal(d) |
| 87 | } |
| 88 | for _, c := range d.Candidates { |
| 89 | want := move |
| 90 | if strings.HasPrefix(c.Location, "instances/") { |
| 91 | want = legacy |
| 92 | } |
| 93 | if c.Incarnation != want { |
| 94 | t.Fatal(c) |
| 95 | } |
| 96 | } |
| 97 | p := productionClaimRegional{Now: func() time.Time { return time.Unix(100, 0) }} |
| 98 | label, at, err := p.retirement(t.Context(), o, versions[:1]) |
| 99 | if err == nil || label != "" || !at.IsZero() { |
| 100 | t.Fatal(label, at, err) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | func TestPlan12HolderResidueInOtherRegion(t *testing.T) { |
| 105 | t.Parallel() |
| 106 | f := newRecoveryFixture(t) |
| 107 | o := f.read() |
| 108 | c := o.Claim |
| 109 | c.Holder.Location = "instances/" + o.Host |
| 110 | c.Holder.Generation = "live" |
| 111 | loc := c.Holder.Location |
| 112 | versions := []lease.ObjectVersion{{Key: loc + "/live/db", VersionID: "v1"}} |
| 113 | labels, err := ClaimRetirementCensus(o, versions) |
| 114 | if err != nil || labels[loc] != c.Holder.Incarnation { |
| 115 | t.Fatal(labels, err) |
| 116 | } |
| 117 | view := park.View{ClaimETag: o.ETag, Region: "uk", AuthoritativeRegion: "ie", ArchivedIncarnations: labels} |
| 118 | d, err := park.Decide(o, view, versions, "") |
| 119 | if err != nil || len(d.Candidates) != 1 || d.Candidates[0].Incarnation != c.Holder.Incarnation { |
| 120 | t.Fatal(d, err) |
| 121 | } |
| 122 | view.Region = "ie" |
| 123 | view.MarkerKnown = true |
| 124 | view.MarkerGeneration = "live" |
| 125 | view.GenerationOf = func(park.Ref) (string, bool) { return "live", true } |
| 126 | d, err = park.Decide(o, view, versions, "") |
| 127 | if err != nil || len(d.Candidates) != 0 { |
| 128 | t.Fatal("live generation swept", d, err) |
| 129 | } |
| 130 | } |
| 131 | func TestPlan12CensusNamespacesAndFreeLanes(t *testing.T) { |
| 132 | o := plan12Retired(t) |
| 133 | o.Claim.QuietAfter = map[string]time.Time{"ie": time.Unix(10, 0), "uk": time.Unix(20, 0)} |
| 134 | proof, err := claimFreeMoveProof(o) |
| 135 | if err != nil || proof.Check(o, o.Claim.RetiredIncarnations[0], time.Unix(21, 0)) != nil { |
| 136 | t.Fatal(proof, err) |
| 137 | } |
| 138 | if proof.Check(o, proof.Incarnation, time.Unix(20, 0)) == nil { |
| 139 | t.Fatal("accepted listing before retirement") |
| 140 | } |
| 141 | delete(o.Claim.RetiredLocations, o.Claim.RetiredIncarnations[0]) |
| 142 | v := []lease.ObjectVersion{{Key: "instances/" + o.Host + "/0000/db", VersionID: "one"}, {Key: "moves/" + o.Host + "/move/db", VersionID: "two"}} |
| 143 | p := productionClaimRegional{Now: func() time.Time { return time.Unix(30, 0) }} |
| 144 | label, _, err := p.retirement(t.Context(), o, v) |
| 145 | if err != nil || label != proof.Incarnation { |
| 146 | t.Fatal("legacy refusal blocked lanes", err) |
| 147 | } |
| 148 | view := park.View{ClaimETag: o.ETag, Region: "ie", RefusedLocations: map[string]error{"instances/" + o.Host: claimRetirementDependency()}} |
| 149 | d, err := park.Decide(o, view, v, label) |
| 150 | if err != nil || len(d.Candidates) != 1 || d.Candidates[0].Location != "moves/"+o.Host { |
| 151 | t.Fatal(d, err) |
| 152 | } |
| 153 | o.Claim.QuietAfter = nil |
| 154 | if _, err := claimFreeMoveProof(o); err == nil { |
| 155 | t.Fatal("invented retirement bound") |
| 156 | } |
| 157 | } |
| 158 | func TestPlan12QualifiedIdentityBeyondHistoryCap(t *testing.T) { |
| 159 | o := plan12Retired(t) |
| 160 | loc := "instances/" + o.Host + "/" + strings.Repeat("e", 64) |
| 161 | labels, err := ClaimRetirementCensus(o, []lease.ObjectVersion{{Key: loc + "/g/db", VersionID: "v"}}) |
| 162 | if err != nil || labels[loc] != strings.Repeat("e", 64) { |
| 163 | t.Fatal(labels, err) |
| 164 | } |
| 165 | } |
| 166 | |