carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import (
4 "context"
5 "errors"
6 "slices"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/regionname"
11)
12
13// Main's home-region parks (park.HomeRegion, "@home") name the home's
14// chores by that token where moves queues them, while a relocation's region
15// is the roster name (ie here). Under a verified home binding the two are
16// one region (regionname.SameRegion), so a relocation in the home sees and
17// reuses the chores moves queued under the other name. Without a binding
18// the comparison stays exact, as main's own comparisons do.
19
20func TestRelocationStartGuardReadsTheHomesTwoNames(t *testing.T) {
21 serving := func(obs ...Obligation) *Claim {
22 return &Claim{Holder: &Holder{Incarnation: "i"}, Op: Op{Kind: KindServing, Exec: relSource}, Obligations: obs}
23 }
24 for _, tc := range []struct {
25 name, home, region string
26 ob Obligation
27 want error
28 }{
29 {"park-replica under @home, home bound", "ie", "ie", Obligation{Kind: "park-replica", Region: regionname.HomeRegion}, ErrPendingChore},
30 {"park-replica under @home, no binding", "", "ie", Obligation{Kind: "park-replica", Region: regionname.HomeRegion}, nil},
31 {"park-replica under the roster name, home bound", "ie", "ie", Obligation{Kind: "park-replica", Region: "ie"}, ErrPendingChore},
32 {"park-replica under @home, another region's relocation", "ie", "uk", Obligation{Kind: "park-replica", Region: regionname.HomeRegion}, nil},
33 {"park-generation under @home (L11)", "ie", "ie", Obligation{Kind: "park-generation", Region: regionname.HomeRegion}, nil},
34 } {
35 t.Run(tc.name, func(t *testing.T) {
36 if err := RelocationStartGuard(serving(tc.ob), tc.home, tc.region, relSource, relTarget, false); !errors.Is(err, tc.want) {
37 t.Fatalf("got %v, want %v", err, tc.want)
38 }
39 })
40 }
41}
42
43func TestStartRelocationCarriesTheHomeBinding(t *testing.T) {
44 ctx := context.Background()
45 t.Run("recorded as the op's home_region", func(t *testing.T) {
46 s, o := relServing(t)
47 in := relStart(ReasonDrain, nil)
48 in.HomeRegion = "ie"
49 o, err := StartRelocation(ctx, s, o, in, relAuthority{})
50 if err != nil {
51 t.Fatal(err)
52 }
53 if o.Claim.Op.Intent["home_region"] != "ie" || o.Claim.Op.Intent["region"] != "ie" {
54 t.Fatalf("intent %v", o.Claim.Op.Intent)
55 }
56 })
57 t.Run("no binding records none", func(t *testing.T) {
58 s, o := relServing(t)
59 o, err := StartRelocation(ctx, s, o, relStart(ReasonDrain, nil), relAuthority{})
60 if err != nil {
61 t.Fatal(err)
62 }
63 if _, ok := o.Claim.Op.Intent["home_region"]; ok {
64 t.Fatalf("intent %v", o.Claim.Op.Intent)
65 }
66 })
67 t.Run("a park-replica under @home refuses the start", func(t *testing.T) {
68 s, o := relServing(t)
69 o = step(t, s, o, func(c *Claim) error {
70 c.Obligations = append(c.Obligations, Obligation{ID: "lane", Op: "mv-old", Kind: "park-replica", Region: regionname.HomeRegion, Owner: "region:" + regionname.HomeRegion, Retirement: "r"})
71 return nil
72 })
73 in := relStart(ReasonDrain, nil)
74 in.HomeRegion = "ie"
75 raw := mustJSON(t, o.Claim)
76 if _, err := StartRelocation(ctx, s, o, in, relAuthority{}); !errors.Is(err, ErrPendingChore) {
77 t.Fatalf("got %v", err)
78 }
79 if stored, err := Read(ctx, s, o.Host); err != nil || stored.ETag != o.ETag || string(raw) != string(mustJSON(t, o.Claim)) {
80 t.Fatalf("refusal wrote the claim: %v", err)
81 }
82 })
83 t.Run("a home that is no placement code refuses", func(t *testing.T) {
84 s, o := relServing(t)
85 in := relStart(ReasonDrain, nil)
86 in.HomeRegion = regionname.HomeRegion
87 if _, err := StartRelocation(ctx, s, o, in, relAuthority{}); !errors.Is(err, ErrRelocation) {
88 t.Fatalf("got %v", err)
89 }
90 if stored, err := Read(ctx, s, o.Host); err != nil || stored.ETag != o.ETag {
91 t.Fatalf("refusal wrote the claim: %v", err)
92 }
93 })
94}
95
96// The completion parks an abandoned P in the relocation's region unless a
97// park-generation for it is already owed there, under either of the home's
98// names when the op is bound to the home.
99func TestRelocationCompletionReusesAParkUnderTheHomesOtherName(t *testing.T) {
100 ctx := context.Background()
101 for _, home := range []string{"ie", ""} {
102 t.Run("home "+home, func(t *testing.T) {
103 s, o := relAtPhase(t, PhaseInstalling)
104 o, err := ReplaceTarget(ctx, s, o, "rel-1", putFence(t, s, relTarget), nil, Target{relSpare, relEnrollment(relSpare)}, "P2", relAuthority{})
105 if err != nil {
106 t.Fatal(err)
107 }
108 o = step(t, s, o, func(c *Claim) error {
109 if home != "" {
110 c.Op.Intent["home_region"] = home
111 }
112 c.Obligations = append(c.Obligations, Obligation{ID: "earlier", Op: "mv-old", Kind: "park-generation", Region: regionname.HomeRegion, Owner: "region:" + regionname.HomeRegion, Generation: "P1", Retirement: "r-earlier"})
113 return nil
114 })
115 o = step(t, s, o, ack(relSpare))
116 o = step(t, s, o, ToInstalled("rel-1", relPrepared("P2", 40, 7)))
117 o = step(t, s, o, RecordLocalInstall("rel-1", localInstall(o.Claim)))
118 o = step(t, s, o, ToProbing("rel-1", relObserved))
119 o = step(t, s, o, AdmitWriter(Admission{ObservedOpID: "rel-1", ObservedPhase: PhaseRelocatingProbing, ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
120 Box: relSpare, Mode: "cold", NewGeneration: "P2", KeepStamp: true, PredecessorCertified: true, Region: "ie", PredecessorRegion: "ie"}))
121 o = step(t, s, o, RecordProbe("rel-1", Probe{Box: relSpare, Session: o.Claim.Holder.Session}))
122 o = step(t, s, o, RecordProbeStop("rel-1", ProbeStop{Box: relSpare, Session: o.Claim.Holder.Session, Receipt: "fence-1", ReceiptSHA256: strings.Repeat("a", 64)}))
123 o = step(t, s, o, CompleteRelocation("rel-1", counter()))
124 var regions []string
125 for _, ob := range o.Claim.Obligations {
126 if ob.Kind == "park-generation" && ob.Generation == "P1" {
127 regions = append(regions, ob.Region)
128 }
129 }
130 want := []string{regionname.HomeRegion}
131 if home == "" {
132 want = append(want, "ie") // exact without a binding: a second chore in the roster name
133 }
134 slices.Sort(regions)
135 slices.Sort(want)
136 if !slices.Equal(regions, want) {
137 t.Fatalf("P1's parks: %v, want %v", regions, want)
138 }
139 })
140 }
141}
142
143// J3 into the home: the move names its destination park.HomeRegion and
144// the fence binding names the roster's home (uk here). They are one region
145// only under the move's home binding; the relocation's region is then the
146// roster name, and the commit's lane park is queued in park.HomeRegion, as
147// moves' own commit queues it.
148func TestJ3CommittedLandingIntoTheHomeNeedsTheMovesHomeBinding(t *testing.T) {
149 ctx := context.Background()
150 for _, bound := range []bool{true, false} {
151 name := "bound"
152 if !bound {
153 name = "unbound"
154 }
155 t.Run(name, func(t *testing.T) {
156 s, o := j3Landing(t, PhaseLandingProbed)
157 o = step(t, s, o, func(c *Claim) error {
158 c.Op.Intent["to"] = regionname.HomeRegion
159 if bound {
160 c.Op.Intent["home_region"] = "uk"
161 }
162 return nil
163 })
164 ref := j3Fence(t, s)
165 s.puts = 0
166 after, err := ConvertCommittedLanding(ctx, s, o, moveA, j3Target, ref, j3Authority{}, &j3Guard{to: regionname.HomeRegion}, nil, counter())
167 if !bound {
168 if !errors.Is(err, ErrFence) || s.puts != 0 {
169 t.Fatalf("got %v, writes %d", err, s.puts)
170 }
171 return
172 }
173 if err != nil {
174 t.Fatal(err)
175 }
176 c := after.Claim
177 if c.Op.Intent["region"] != "uk" || c.Op.Intent["home_region"] != "uk" || c.Op.Intent["move_to_region"] != regionname.HomeRegion {
178 t.Fatalf("intent %v", c.Op.Intent)
179 }
180 if owner, err := RelocationRecoveryOwner(c); err != nil || owner != "region:uk" {
181 t.Fatalf("owner=%q error=%v", owner, err)
182 }
183 if k := obligationKinds(c); k["park-replica/ie"] != 1 || k["park-replica/"+regionname.HomeRegion] != 1 {
184 t.Fatalf("the commit's chores: %v", k)
185 }
186 })
187 }
188}
189