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 "fmt"
7 "reflect"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14func counter() func() string {
15 n := 0
16 return func() string { n++; return fmt.Sprintf("id_%d", n) }
17}
18
19func servingClaim(t *testing.T, ctx context.Context, s release.Store) Observed {
20 t.Helper()
21 now := time.Date(2026, 10, 4, 1, 0, 1, 0, time.UTC)
22 reg, err := Read(ctx, s, testHost)
23 if err != nil {
24 t.Fatal(err)
25 }
26 o, err := Update(ctx, s, reg, BeginCreate(newHolder(), record, "op_c1", "console", now))
27 if err != nil {
28 t.Fatal(err)
29 }
30 if o, err = Update(ctx, s, o, CompleteCreate("op_c1", "adi-knitting")); err != nil {
31 t.Fatal(err)
32 }
33 return o // no fixture repair: CompleteCreate itself records adi-knitting in touched.boxes
34}
35
36func TestDeleteParkFree(t *testing.T) {
37 ctx := context.Background()
38 s := release.NewDirStore(t.TempDir())
39 now := time.Date(2026, 10, 4, 2, 0, 0, 0, time.UTC)
40 o := servingClaim(t, ctx, s)
41 inc, stamp := o.Claim.Holder.Incarnation, o.Claim.Holder.Stamp
42 o, err := Update(ctx, s, o, BeginDelete("op_d1", "console", `"etag1"`, now, nil, nil, ""))
43 if err != nil || o.Claim.Op.Kind != KindDeleting || o.Claim.Op.Intent["etag"] != `"etag1"` {
44 t.Fatalf("begin delete = %+v, %v", o.Claim, err)
45 }
46 if o.Claim.Op.Exec != "adi-knitting" || o.Claim.Holder.Stamp != stamp {
47 t.Fatalf("delete dropped execution authority: exec %q stamp %d", o.Claim.Op.Exec, o.Claim.Holder.Stamp)
48 }
49 o, err = Update(ctx, s, o, ToParking("op_d1", nil, nil, counter()))
50 if err != nil || o.Claim.Op.Kind != KindParking || len(o.Claim.Obligations) != 2 {
51 t.Fatalf("to parking = %+v, %v", o.Claim, err)
52 }
53 if _, err := Update(ctx, s, o, FreeAfterPark("op_d1")); !errors.Is(err, ErrWrongState) {
54 t.Fatalf("free with chores left = %v", err)
55 }
56 for _, ob := range o.Claim.Obligations {
57 if o, err = Update(ctx, s, o, CompleteObligation(ob.Op, ob.ID)); err != nil {
58 t.Fatal(err)
59 }
60 }
61 if _, err := Update(ctx, s, o, CompleteObligation("op_d1", "id_1")); !errors.Is(err, ErrWrongState) {
62 t.Fatalf("completing a chore twice = %v", err)
63 }
64 o, err = Update(ctx, s, o, FreeAfterPark("op_d1"))
65 if err != nil || !o.Claim.Free() || o.Claim.RetiredIncarnations[0] != inc {
66 t.Fatalf("free = %+v, %v", o.Claim, err)
67 }
68}
69
70func TestIncompleteTouchedParksEverywhere(t *testing.T) {
71 ctx := context.Background()
72 s := release.NewDirStore(t.TempDir())
73 now := time.Date(2026, 10, 4, 2, 0, 0, 0, time.UTC)
74 o := servingClaim(t, ctx, s)
75 o, err := Update(ctx, s, o, func(c *Claim) error { c.Touched.Complete = false; return nil })
76 if err != nil {
77 t.Fatal(err)
78 }
79 regions, boxes := []string{"ie", "uk", "us"}, []string{"b1", "b2"}
80 o, err = Update(ctx, s, o, BeginDelete("op_d1", "console", `"e"`, now, regions, boxes, ""))
81 if err != nil {
82 t.Fatal(err)
83 }
84 o, err = Update(ctx, s, o, ToParking("op_d1", regions, boxes, counter()))
85 if err != nil {
86 t.Fatal(err)
87 }
88 assertParkingTargets(t, o.Claim.Obligations, []Obligation{
89 {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "ie", Owner: "region:ie"},
90 {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "uk", Owner: "region:uk"},
91 {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "us", Owner: "region:us"},
92 {Op: "op_d1", Kind: "park-local", Box: "adi-knitting", Owner: "box:adi-knitting"},
93 {Op: "op_d1", Kind: "park-local", Box: "b1", Owner: "box:b1"},
94 {Op: "op_d1", Kind: "park-local", Box: "b2", Owner: "box:b2"},
95 })
96}
97
98func TestToParkingRefusesAPurgeFence(t *testing.T) {
99 fence := &Claim{V: 2, Host: testHost, Gen: 3, Op: Op{Kind: KindPurging, ID: "op_p"}}
100 if err := ToParking("op_p", nil, nil, counter())(fence); !errors.Is(err, ErrWrongState) {
101 t.Fatalf("purge fence = %v, want ErrWrongState", err)
102 }
103}
104
105func TestRetiredCapKeepsNewest32(t *testing.T) {
106 c := &Claim{}
107 for i := 0; i < 33; i++ {
108 retire(c, fmt.Sprintf("inc%02d", i))
109 }
110 if len(c.RetiredIncarnations) != 32 || c.RetiredIncarnations[0] != "inc32" || c.RetiredIncarnations[31] != "inc01" {
111 t.Fatalf("retired = %v", c.RetiredIncarnations)
112 }
113}
114
115func TestAppendParkObligations(t *testing.T) {
116 existing := Obligation{ID: "existing", Op: "op_old", Kind: "park-local", Box: "b0", Retirement: "retired", Owner: "box:b0"}
117 c := &Claim{Obligations: []Obligation{existing}}
118 newID := counter()
119 appendParkObligations(c, "op_park", []string{"uk", "us"}, []string{"b1", "b2"}, newID)
120 want := []Obligation{
121 existing,
122 {ID: "id_1", Op: "op_park", Kind: "park-replica", Region: "uk", Retirement: "id_2", Owner: "region:uk"},
123 {ID: "id_3", Op: "op_park", Kind: "park-replica", Region: "us", Retirement: "id_4", Owner: "region:us"},
124 {ID: "id_5", Op: "op_park", Kind: "park-local", Box: "b1", Retirement: "id_6", Owner: "box:b1"},
125 {ID: "id_7", Op: "op_park", Kind: "park-local", Box: "b2", Retirement: "id_8", Owner: "box:b2"},
126 }
127 if !reflect.DeepEqual(c.Obligations, want) {
128 t.Fatalf("obligations = %+v, want %+v", c.Obligations, want)
129 }
130 appendParkObligations(c, "op_empty", nil, nil, newID)
131 if !reflect.DeepEqual(c.Obligations, want) {
132 t.Fatalf("empty append changed obligations: %+v", c.Obligations)
133 }
134 if got := newID(); got != "id_9" {
135 t.Fatalf("next id = %q, want id_9", got)
136 }
137}
138
139func assertParkingTargets(t *testing.T, obligations, want []Obligation) {
140 t.Helper()
141 got := append([]Obligation(nil), obligations...)
142 for i := range got {
143 got[i].ID, got[i].Retirement = "", ""
144 }
145 if !reflect.DeepEqual(got, want) {
146 t.Fatalf("parking targets = %+v, want %+v", got, want)
147 }
148}
149
150func TestWriteSetIncompleteUnion(t *testing.T) {
151 c := &Claim{Touched: Touched{
152 Regions: []string{"ie", "ie", "uk"}, Boxes: []string{"B0", "B0", "B2"},
153 }}
154 regions, boxes := WriteSet(c, []string{"us", "uk", "us", "ie"}, []string{"B3", "B2", "B3", "B0"})
155 if !reflect.DeepEqual(regions, []string{"ie", "uk", "us"}) || !reflect.DeepEqual(boxes, []string{"B0", "B2", "B3"}) {
156 t.Fatalf("write set = %v, %v; want [ie uk us], [B0 B2 B3]", regions, boxes)
157 }
158}
159
160func TestIncompleteAdoptionPreservesParkingTargets(t *testing.T) {
161 ctx := context.Background()
162 s := release.NewDirStore(t.TempDir())
163 obs, err := Read(ctx, s, testHost)
164 if err != nil {
165 t.Fatal(err)
166 }
167 newID := counter()
168 o, err := Update(ctx, s, obs, Adopt(newHolder(), "B0", []string{"uk"}, []string{"B2"}, "op_adopt", newID))
169 if err != nil {
170 t.Fatal(err)
171 }
172 regions := []string{"ie"}
173 o, err = Update(ctx, s, o, BeginDelete("op_d", "console", `"e"`, time.Now(), regions, nil, ""))
174 if err != nil {
175 t.Fatal(err)
176 }
177 o, err = Update(ctx, s, o, ToParking("op_d", regions, nil, newID))
178 if err != nil {
179 t.Fatal(err)
180 }
181 assertParkingTargets(t, o.Claim.Obligations, []Obligation{
182 {ParkTarget: "instances/" + testHost, Op: "op_adopt", Kind: "park-replica", Region: "uk", Owner: "region:uk"},
183 {Op: "op_adopt", Kind: "park-local", Box: "B2", Owner: "box:B2"},
184 {ParkTarget: "instances/" + testHost, Op: "op_d", Kind: "park-replica", Region: "ie", Owner: "region:ie"},
185 {Op: "op_d", Kind: "park-local", Box: "B0", Owner: "box:B0"},
186 })
187 for _, ob := range o.Claim.Obligations {
188 if _, err := Update(ctx, s, o, FreeAfterPark("op_d")); !errors.Is(err, ErrWrongState) {
189 t.Fatalf("free with %d chores remaining = %v", len(o.Claim.Obligations), err)
190 }
191 o, err = Update(ctx, s, o, CompleteObligation(ob.Op, ob.ID))
192 if err != nil {
193 t.Fatal(err)
194 }
195 }
196 o, err = Update(ctx, s, o, FreeAfterPark("op_d"))
197 if err != nil {
198 t.Fatal(err)
199 }
200 if !o.Claim.Free() {
201 t.Fatalf("not free after all chores: %+v", o.Claim)
202 }
203}
204
205func TestOrdinaryDeleteProducesPhasedParking(t *testing.T) {
206 c := &Claim{Host: "app.example", Holder: &Holder{Incarnation: "inc", Location: "instances/app.example/inc"}, Op: Op{Kind: KindDeleting, ID: "delete"}, Touched: Touched{Complete: true, Boxes: []string{"box"}}}
207 if err := ToParking("delete", nil, nil, func() string { return "id" })(c); err != nil {
208 t.Fatal(err)
209 }
210 if c.Op.Kind != KindParking || c.Op.Phase != "park" || len(c.Obligations) != 1 || c.Obligations[0].Kind != "park-local" {
211 t.Fatalf("ordinary parking stop authority: %+v", c)
212 }
213}
214