carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "encoding/json"
8 "errors"
9 "net/http/httptest"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 execlease "github.com/carlosframework/platform/internal/hostexec"
16 "github.com/carlosframework/platform/internal/hostguard"
17 "github.com/carlosframework/platform/internal/hostops"
18 "github.com/carlosframework/platform/internal/lease/leasetest"
19 "github.com/carlosframework/platform/internal/park"
20)
21
22func TestPlan14OrdinaryGenerationParks(t *testing.T) {
23 for _, tc := range []string{"valid", "real-leases", "moves", "foreign-region", "foreign-box", "current-generation", "unknown-generation", "missing-receipt", "altered-receipt", "foreign-enrollment", "unbound-root", "changed-time", "changed-location", "unknown-obligation"} {
24 t.Run(tc, func(t *testing.T) {
25 f, _, _, in := servingRetirementFixture(t)
26 first := servingAdmit(t, f, &in, "G")
27 receipt := servingStop(t, f, first)
28 current := servingAdmit(t, f, &in, "G2")
29 current, err := hostclaim.Update(t.Context(), f.claims, current, hostclaim.PublishGeneration("", "", current.Claim.Holder.Session, "G2", "ie", f.newID))
30 if err != nil {
31 t.Fatal(err)
32 }
33 var ob hostclaim.Obligation
34 for _, x := range current.Claim.Obligations {
35 if x.Generation == "G" {
36 ob = x
37 }
38 }
39 if ob.ID == "" {
40 t.Fatal("missing publication obligation")
41 }
42 identity := handoverRecoveryIdentity(t, hostbox.Actor{Box: "box-1", Enrollment: "enrollment", Revision: 7, Binding: "7", Region: "ie", FleetHomeRegion: "ie"})
43 identity.HomeRegion = "ie"
44 if tc == "foreign-enrollment" {
45 receipt.Target.Enrollment = "another-enrollment"
46 var proof StopReceipt
47 if err := json.Unmarshal(receipt.Bytes, &proof); err != nil {
48 t.Fatal(err)
49 }
50 proof.Enrollment = receipt.Target.Enrollment
51 receipt.Bytes = mustMarshalStopReceipt(proof)
52 }
53 guard := f.guard
54 guard.FenceView = func(_ context.Context, host string, c hostguard.Captured) (hostguard.FenceView, error) {
55 receipts := []hostguard.FenceReceipt{receipt}
56 if tc == "missing-receipt" {
57 receipts = nil
58 }
59 if tc == "altered-receipt" {
60 receipts[0].Bytes = []byte("forged")
61 }
62 return hostguard.FenceView{Schema: 1, Host: host, Captured: c, Installation: hostguard.RootInstallation{Host: host, Migration: 1}, Receipts: receipts}, nil
63 }
64 data := leasetest.NewVersionedBucket()
65 data.AddBucket("data")
66 p := productionClaimRegional{Identity: identity, Local: &productionClaimLocal{assembly: &ClaimAssembly{Guard: guard, Data: data}}}
67 switch tc {
68 case "moves":
69 current.Claim.Op.Kind = hostclaim.KindMoving
70 case "foreign-region":
71 ob.Region, ob.Owner = "uk", "region:uk"
72 case "foreign-box":
73 current.Claim.Holder.Admissions[0].Box = "other"
74 case "current-generation":
75 ob.Generation = "G2"
76 case "unknown-generation":
77 ob.Generation = "unknown"
78 case "unknown-obligation":
79 ob.ID = "unknown"
80 case "unbound-root":
81 p.Local.assembly.Guard.FenceView = nil
82 case "changed-time", "changed-location":
83 scope, err := p.VerifyObligation(t.Context(), current, ob)
84 if err != nil {
85 t.Fatal(err)
86 }
87 if tc == "changed-time" {
88 scope.EvidenceAt = scope.EvidenceAt.Add(1)
89 } else {
90 scope.Location += "/other"
91 }
92 ob.Park = &scope
93
94 }
95
96 if tc == "foreign-region" || tc == "current-generation" || tc == "unknown-generation" {
97 for i := range current.Claim.Obligations {
98 if current.Claim.Obligations[i].ID == ob.ID {
99 current.Claim.Obligations[i] = ob
100 }
101 }
102 }
103 scope, err := p.VerifyObligation(t.Context(), current, ob)
104 if tc == "valid" || tc == "real-leases" {
105 if err != nil || scope.Location != current.Claim.Holder.Location || !scope.EvidenceAt.Equal(receipt.EvidenceAt) || scope.RequestLimit != hostbox.RequestLimit {
106 t.Fatal(scope, err)
107 }
108 ob.Park = &scope
109 p.Local.Identity, p.Local.lifetime = identity, t.Context()
110 p.Local.Resources.Mode = "agent"
111 p.Local.executionCall = func(context.Context, execlease.ExecutionRequest) (execlease.ExecutionResponse, error) {
112 return execlease.ExecutionResponse{}, errors.New("stopped current writer cannot execute")
113 }
114 if err := p.Authorize(t.Context(), current, ob); err != nil {
115 t.Fatal("cleanup required writer execution permission", err)
116 }
117 if _, err = p.VerifyObligation(t.Context(), current, ob); err != nil {
118 t.Fatal("bound replay", err)
119 }
120 if _, err = p.VerifyMovesSweep(t.Context(), current, nil); err == nil {
121 t.Fatal("moves sweep enabled")
122 }
123 freed := current
124 claim := *current.Claim
125 freed.Claim = &claim
126 claim.RetiredIncarnations = []string{claim.Holder.Incarnation}
127 claim.Holder, claim.Op = nil, hostclaim.Op{}
128 claim.QuietAfter = map[string]time.Time{"ie": receipt.EvidenceAt}
129 if _, err = p.VerifyMovesSweep(t.Context(), freed, nil); err == nil {
130 t.Fatal("free claim enabled moves sweep")
131 }
132 location := current.Claim.Holder.Location
133 oldSnapshot := location + "/g-G/0009/snapshot.ltx"
134 oldVersion := data.Put("data", oldSnapshot, []byte("old snapshot"))
135 data.Put("data", location+"/g-G2/0009/snapshot.ltx", []byte("current snapshot"))
136 keptFile := location + "/files/g-G/objects/0000000000000001-aa"
137 keptVersion := data.Put("data", keptFile, []byte("kept"))
138 filesManifest(t, func(k string, b []byte) { data.Put("data", k, b) }, location, "G2", "kept.txt="+keptFile)
139 marker, err := json.Marshal(map[string]any{"host": current.Host, "incarnation": current.Claim.Holder.Incarnation, "op": "G2"})
140 if err != nil {
141 t.Fatal(err)
142 }
143 data.Put("data", location+"/generation.json", marker)
144 engine := park.Engine{HomeRegion: "ie", Store: data, Claims: f.claims, Bucket: "data", Region: "ie", Leases: evLeases{}, Now: func() time.Time { return f.now.Add(park.SignatureAge + hostbox.RequestLimit + time.Second) }, NewID: f.newID, View: p.View, Authorize: p.Authorize}
145 if tc == "real-leases" {
146 handler, _ := leasetest.NewS3Server(data)
147 server := httptest.NewServer(handler)
148 defer server.Close()
149 for key, value := range map[string]string{"CARLOS_S3_ENDPOINT": server.URL, "AWS_ENDPOINT_URL_S3": server.URL, "AWS_ACCESS_KEY_ID": "test-key", "AWS_SECRET_ACCESS_KEY": "test-secret", "AWS_SESSION_TOKEN": "", "AWS_EC2_METADATA_DISABLED": "true", "AWS_CONFIG_FILE": "/nonexistent", "AWS_SHARED_CREDENTIALS_FILE": "/nonexistent"} {
150 t.Setenv(key, value)
151 }
152 engine.Leases = hostbox.AggregateLeases{Leases: hostbox.RenewingLeases{Region: "eu-west-1"}, Resolve: p.LeaseSet}
153 }
154 work := park.Work{Host: current.Host, ID: ob.ID, Op: ob.Op}
155 if err := engine.Bind(t.Context(), work, scope); err != nil {
156 t.Fatal("bind generation", err)
157 }
158 pinnedMarker, err := json.Marshal(map[string]any{"host": current.Host, "incarnation": current.Claim.Holder.Incarnation, "op": "G2", "snapshot_key": oldSnapshot, "snapshot_version": oldVersion})
159 if err != nil {
160 t.Fatal(err)
161 }
162 data.Put("data", location+"/generation.json", pinnedMarker)
163 if err := engine.Run(t.Context(), work); !errors.Is(err, park.ErrUnsafe) {
164 t.Fatal("database pin did not protect its generation", err)
165 }
166 if _, err := data.GetVersion(t.Context(), "data", oldSnapshot, oldVersion); err != nil {
167 t.Fatal("pinned database snapshot was deleted", err)
168 }
169 data.Put("data", location+"/generation.json", marker)
170 if err := engine.Run(t.Context(), work); err != nil {
171 t.Fatal("retire generation", err)
172 }
173 left, err := data.ListVersions(t.Context(), "data", location+"/g-G/")
174 if err != nil || len(left) != 0 {
175 t.Fatal("predecessor remains", left, err)
176 }
177 left, err = data.ListVersions(t.Context(), "data", location+"/g-G2/")
178 if err != nil || len(left) != 1 {
179 t.Fatal("current generation changed", left, err)
180 }
181 if body, err := data.GetVersion(t.Context(), "data", keptFile, keptVersion); err != nil || string(body) != "kept" {
182 t.Fatal("referenced file was retired with its database generation", err)
183 }
184
185 } else if err == nil {
186 t.Fatal("accepted", tc)
187 }
188 })
189 }
190}
191
192func TestPlan14OrdinaryRetirementRequiresProof(t *testing.T) {
193 t.Parallel()
194 for _, target := range []string{"holder", "empty-staging", "staging-residue", "foreign"} {
195 t.Run(target, func(t *testing.T) {
196 f, _, _, in := servingRetirementFixture(t)
197 o := servingAdmit(t, f, &in, "G")
198 identity := handoverRecoveryIdentity(t, hostbox.Actor{Box: "box-1", Enrollment: "enrollment", Revision: 7, Binding: "7", Region: "ie", FleetHomeRegion: "ie"})
199 identity.HomeRegion = "ie"
200 data := leasetest.NewVersionedBucket()
201 data.AddBucket("data")
202 ob := hostclaim.Obligation{ID: "park", Op: "delete", Kind: "park-replica", Owner: "region:ie", Region: "ie", Retirement: "retire", ParkTarget: o.Claim.Holder.Location}
203 want := hostops.ErrIdentity
204 switch target {
205 case "empty-staging", "staging-residue":
206 ob.ParkTarget = "moves/" + o.Host
207 data.Put("data", ob.ParkTarget+"/lock.json", []byte("lock"))
208 if target == "staging-residue" {
209 data.Put("data", ob.ParkTarget+"/other/g-G/object", []byte("deferred move"))
210 want = park.ErrUnsafe
211 }
212 case "foreign":
213 ob.ParkTarget += "/another"
214 want = park.ErrUnsafe
215 }
216 o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindParking, ID: "delete"}
217 o.Claim.Obligations = []hostclaim.Obligation{ob}
218 if err := o.Claim.Validate(); err != nil {
219 t.Fatal(err)
220 }
221 p := productionClaimRegional{Identity: identity, Local: &productionClaimLocal{assembly: &ClaimAssembly{Data: data, RetirementAuthority: &hostops.ProductionAuthority{}}}}
222 if err := p.authorizeOrdinaryRetirement(t.Context(), o, ob); !errors.Is(err, want) {
223 t.Fatal("unsigned retirement or deferred target accepted", err)
224 }
225 })
226 }
227}
228
229func TestPlan14OrdinaryParkView(t *testing.T) {
230 t.Parallel()
231 f, _, _, in := servingRetirementFixture(t)
232 o := servingAdmit(t, f, &in, "G")
233 identity := handoverRecoveryIdentity(t, hostbox.Actor{Box: "box-1", Enrollment: "enrollment", Revision: 7, Binding: "7", Region: "ie", FleetHomeRegion: "ie"})
234 identity.HomeRegion = "ie"
235 data := leasetest.NewVersionedBucket()
236 data.AddBucket("data")
237 p := productionClaimRegional{Identity: identity, Local: &productionClaimLocal{assembly: &ClaimAssembly{Data: data}}}
238 v, err := p.View(t.Context(), o)
239 if err != nil {
240 t.Fatal(err)
241 }
242 live, err := park.LiveGenerations(o.Claim, v, o.Claim.Holder.Location)
243 if err != nil || !live["G"] || !live[hostclaim.RootGeneration] || v.RefusedLocations["moves/"+o.Host] == nil {
244 t.Fatal(live, err)
245 }
246
247 p.Identity.HomeRegion = ""
248 v, err = p.View(t.Context(), o)
249 if err != nil || v.HomeRegion != "" || v.AuthoritativeRegion != "ie" {
250 t.Fatal("named home placement", v, err)
251 }
252 p.Identity.HomeRegion = "ie"
253 key := o.Claim.Holder.Location + "/g-G/0009/snapshot.ltx"
254 version := data.Put("data", key, []byte("retained snapshot"))
255 marker, err := json.Marshal(map[string]any{"host": o.Host, "incarnation": o.Claim.Holder.Incarnation, "op": "G", "snapshot_key": key, "snapshot_version": version})
256 if err != nil {
257 t.Fatal(err)
258 }
259 data.Put("data", o.Claim.Holder.Location+"/generation.json", marker)
260 data.Put("data", key, []byte("newer bytes"))
261 v, err = p.View(t.Context(), o)
262 if err != nil || !v.Pinned[park.Ref{Key: key, VersionID: version}] || v.MarkerGeneration != "G" {
263 t.Fatal("lost exact snapshot pin", v, err)
264 }
265 unavailable := errors.New("version read unavailable")
266 data.FailOn = func(op, key string) error {
267 if op == "get" {
268 return unavailable
269 }
270 return nil
271 }
272 if _, err := p.View(t.Context(), o); !errors.Is(err, unavailable) {
273 t.Fatal("reduced live set on failed read", err)
274 }
275 data.FailOn = nil
276 for _, kind := range []string{hostclaim.KindMoving, hostclaim.KindLanding, hostclaim.KindRelocating, hostclaim.KindRestoring} {
277 o.Claim.Op.Kind = kind
278 if _, err := p.View(t.Context(), o); err == nil {
279 t.Fatal("move view enabled", kind)
280 }
281 }
282}
283