carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "crypto/ed25519"
5 "encoding/json"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/hostactors"
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostfloor"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19// bindingRosterFixture is a signed one-box roster in a release.DirStore, with
20// a published floor and the matching local floor policy, read through the
21// production roster adapter. Roster writes go through the real
22// hostactors.Directory writers unless a step says otherwise.
23type bindingRosterFixture struct {
24 t *testing.T
25 store release.Store
26 signer release.Signer
27 dir hostactors.Directory
28 floorDir string
29 box string
30 churn int
31}
32
33const bindingFixtureBucket = "deployment"
34
35func newBindingRosterFixture(t *testing.T, box, region string) *bindingRosterFixture {
36 t.Helper()
37 key := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize))
38 s := release.NewDirStore(t.TempDir())
39 f := &bindingRosterFixture{t: t, store: s, signer: release.RootSigner(key), dir: hostactors.Directory{Store: s, PublicKey: key.Public().(ed25519.PublicKey), Bucket: bindingFixtureBucket}, floorDir: t.TempDir(), box: box}
40 if err := hostfloor.Publish(t.Context(), s, f.signer, bindingFixtureBucket, hostfloor.Floor{Min: 1, MinRelease: "v0.1.0"}); err != nil {
41 t.Fatalf("newBindingRosterFixture: %v", err)
42 }
43 raw, _, err := s.Get(t.Context(), hostfloor.Key)
44 if err != nil {
45 t.Fatalf("newBindingRosterFixture: %v", err)
46 }
47 auth, _, err := s.Get(t.Context(), hostfloor.AuthKey)
48 if err != nil {
49 t.Fatalf("newBindingRosterFixture: %v", err)
50 }
51 body, _ := json.Marshal(hostfloor.Policy{Schema: 1, DeploymentBucket: bindingFixtureBucket, Mode: "enforced", Floor: raw, Authorization: auth})
52 if err := os.WriteFile(filepath.Join(f.floorDir, "policy.json"), body, 0600); err != nil {
53 t.Fatalf("newBindingRosterFixture: %v", err)
54 }
55 if err := f.dir.Initialize(t.Context(), f.signer); err != nil {
56 t.Fatalf("newBindingRosterFixture: %v", err)
57 }
58 o := f.read()
59 next := hostactors.Clone(o.Roster)
60 next.RequestLimitNS, next.Publication, next.FloorSHA256 = int64(hostbox.RequestLimit), "published", hostfloor.Digest(raw)
61 // A region HA group, so enrollmentAttempt can run the real writer.
62 next.Regions = map[string]hostactors.Region{region: {Namespaces: []string{"platform"}, Pools: []string{"default"}, Account: "111122223333", AWSRegion: "eu-west-1", GroupName: "group", GroupARN: "arn:aws:autoscaling:eu-west-1:111122223333:autoScalingGroup:x:autoScalingGroupName/group"}}
63 next.Boxes[box] = hostactors.Box{BoxID: box, Enrollment: strings.Repeat("a", 32), Namespace: "platform", Label: box, Region: region, Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "111122223333", Region: "eu-west-1", InstanceID: "i-0abc"}, EvidenceSHA256: strings.Repeat("c", 64), State: "active", LossIDs: []string{}}
64 if _, err := f.dir.Replace(t.Context(), f.signer, o, next); err != nil {
65 t.Fatalf("newBindingRosterFixture: %v", err)
66 }
67 return f
68}
69
70func (f *bindingRosterFixture) read() hostactors.Observed {
71 f.t.Helper()
72 o, err := f.dir.Read(f.t.Context())
73 if err != nil {
74 f.t.Fatalf("read: %v", err)
75 }
76 return o
77}
78
79func (f *bindingRosterFixture) row() hostactors.Box { return f.read().Roster.Boxes[f.box] }
80
81// identity is the box's production identity: signed cloud evidence for the
82// row's instance and productionClaimRoster over this roster.
83func (f *bindingRosterFixture) identity() hostbox.IdentityProvider {
84 f.t.Helper()
85 row := f.row()
86 identity := handoverRecoveryIdentity(f.t, hostbox.Actor{Box: f.box, Enrollment: row.Enrollment, Region: row.Region, ControlBucket: bindingFixtureBucket, Revision: 1})
87 identity.Roster = productionClaimRoster{Directory: f.dir, DataBucket: identity.Bucket, FloorDir: f.floorDir}
88 return identity
89}
90
91func (f *bindingRosterFixture) binding() string {
92 f.t.Helper()
93 b, ok := hostactors.BoxBinding(f.read().Roster, f.box)
94 if !ok {
95 f.t.Fatal("box not in roster")
96 }
97 return b
98}
99
100// holdChurn is one console create's roster traffic: a real hold Reserve, then
101// the claim it names moves on and ReleaseFinishedHold drops the hold. The
102// roster write counter moves by two; this box's binding does not.
103func (f *bindingRosterFixture) holdChurn() {
104 f.t.Helper()
105 before := f.read().Roster.Revision
106 f.releaseHold(f.reserveHold())
107 after := f.read()
108 if after.Roster.Revision != before+2 || len(after.Roster.Holds) != 0 {
109 f.t.Fatalf("hold churn: revision %d -> %d, holds %d", before, after.Roster.Revision, len(after.Roster.Holds))
110 }
111}
112
113// reserveHold is a create's real hold Reserve naming this box, left held.
114func (f *bindingRosterFixture) reserveHold() hostactors.Reservation {
115 f.t.Helper()
116 ctx := f.t.Context()
117 f.churn++
118 host := fmt.Sprintf("churn-%d.example.com", f.churn)
119 before := f.read()
120 claim, err := hostclaim.Read(ctx, f.store, host)
121 if err != nil {
122 f.t.Fatalf("reserveHold: %v", err)
123 }
124 actor := "box:" + f.box
125 enrollment := before.Roster.Boxes[f.box].Enrollment
126 h := hostactors.Reservation{ID: fmt.Sprintf("hold_%d", f.churn), Host: host, OpID: "create", ExpectedClaimETag: claim.ETag, FloorSHA256: before.Roster.FloorSHA256, Actors: map[string]string{actor: enrollment}, Releases: map[string]hostactors.Report{actor: {Enrollment: enrollment}}}
127 if _, err := f.dir.Reserve(ctx, f.signer, before, h, false); err != nil {
128 f.t.Fatalf("reserveHold: %v", err)
129 }
130 return h
131}
132
133// releaseHold moves the hold's claim on, then ReleaseFinishedHold drops it.
134func (f *bindingRosterFixture) releaseHold(h hostactors.Reservation) {
135 f.t.Helper()
136 ctx := f.t.Context()
137 claim, err := hostclaim.Read(ctx, f.store, h.Host)
138 if err != nil {
139 f.t.Fatalf("releaseHold: %v", err)
140 }
141 if _, err := hostclaim.Update(ctx, f.store, claim, func(*hostclaim.Claim) error { return nil }); err != nil {
142 f.t.Fatalf("releaseHold: %v", err)
143 }
144 if err := f.dir.ReleaseFinishedHold(ctx, f.signer, h.ID); err != nil {
145 f.t.Fatalf("releaseHold: %v", err)
146 }
147}
148
149// bindCensusKey binds key's public half as the box's census key through the
150// real BindCensusKey writer, with the box's proof of possession.
151func (f *bindingRosterFixture) bindCensusKey(key ed25519.PrivateKey) {
152 f.t.Helper()
153 binding := hostactors.CensusKeyBinding{Bucket: bindingFixtureBucket, Box: f.box, Enrollment: f.row().Enrollment, PublicKey: key.Public().(ed25519.PublicKey)}
154 raw, err := json.Marshal(binding)
155 if err != nil {
156 f.t.Fatal(err)
157 }
158 if _, err := f.dir.BindCensusKey(f.t.Context(), f.signer, f.read(), binding, ed25519.Sign(key, raw)); err != nil {
159 f.t.Fatal("bind census key:", err)
160 }
161}
162
163// quarantine is a real Directory.Quarantine of the box.
164func (f *bindingRosterFixture) quarantine(loss string) {
165 f.t.Helper()
166 o := f.read()
167 if _, err := f.dir.Quarantine(f.t.Context(), f.signer, o, f.box, o.Roster.Boxes[f.box].Enrollment, loss); err != nil {
168 f.t.Fatalf("quarantine: %v", err)
169 }
170}
171
172// readmit returns the quarantined row to active by a DIRECT SIGNED WRITE.
173// Directory.Replace admits a quarantined -> active change on a published
174// roster only with a fresh box-signed census report (hostactors
175// checkMembership), which this fixture has no box to produce. The written
176// roster is exactly what hostresolve's readmission writes: the row's state
177// alone changes, its quarantine revision and loss ids stay, and Revision
178// moves by one.
179func (f *bindingRosterFixture) readmit() {
180 f.t.Helper()
181 o := f.read()
182 next := hostactors.Clone(o.Roster)
183 b := next.Boxes[f.box]
184 if b.State != "quarantined" {
185 f.t.Fatalf("readmit: state %q", b.State)
186 }
187 b.State = "active"
188 next.Boxes[f.box] = b
189 next.Revision++
190 wire, err := release.SignRootEnvelope(f.signer, next)
191 if err != nil {
192 f.t.Fatalf("readmit: %v", err)
193 }
194 if _, err := f.store.Put(f.t.Context(), hostactors.Key, wire, o.ETag); err != nil {
195 f.t.Fatalf("readmit: %v", err)
196 }
197}
198
199// activationChurn is one platform promote's roster traffic: a real activation
200// Reserve (with a prepared system publication) and CancelActivation.
201func (f *bindingRosterFixture) activationChurn() {
202 f.t.Helper()
203 ctx := f.t.Context()
204 f.churn++
205 id := fmt.Sprintf("activation_%d", f.churn)
206 platform := release.AdoptPlatform()
207 rel := release.SystemRelease{Version: "v2.0.0", ReleaseTag: "v2.0.0", MovesProtocol: 1, Artifacts: map[string]release.SystemArtifact{platform: {Key: release.SystemArtifactKey("v2.0.0", platform), SHA256: strings.Repeat("a", 64)}}}
208 pub, err := release.PrepareSystemPromotion(ctx, f.store, f.signer, "router", "stable", rel, release.SystemPromoteOptions{Actor: "operator"}, id)
209 if err != nil {
210 f.t.Fatalf("activationChurn: %v", err)
211 }
212 before := f.read()
213 actor := "box:" + f.box
214 enrollment := before.Roster.Boxes[f.box].Enrollment
215 h := hostactors.Reservation{ID: id, Publication: &pub, RequestSHA256: strings.Repeat("e", 64), UpdateMode: "promote", FloorSHA256: before.Roster.FloorSHA256, Actors: map[string]string{actor: enrollment}, Releases: map[string]hostactors.Report{actor: {Enrollment: enrollment}}, Hashes: map[string][]string{actor: {"a", "b", "c"}}}
216 if _, err := f.dir.Reserve(ctx, f.signer, before, h, true); err != nil {
217 f.t.Fatalf("activationChurn: %v", err)
218 }
219 if err := f.dir.CancelActivation(ctx, f.signer, id); err != nil {
220 f.t.Fatalf("activationChurn: %v", err)
221 }
222 if after := f.read(); after.Roster.Revision != before.Roster.Revision+2 || len(after.Roster.Activations) != 0 {
223 f.t.Fatalf("activation churn: revision %d -> %d", before.Roster.Revision, after.Roster.Revision)
224 }
225}
226
227// enrollmentAttempt is region HA autoscaling's real BeginEnrollmentAttempt
228// for another instance in the box's region.
229func (f *bindingRosterFixture) enrollmentAttempt() {
230 f.t.Helper()
231 f.churn++
232 o := f.read()
233 ref := fmt.Sprintf("replacement-%d", f.churn)
234 if _, _, err := f.dir.BeginEnrollmentAttempt(f.t.Context(), f.signer, o, hostactors.EnrollmentRequest{Reference: ref, Region: o.Roster.Boxes[f.box].Region}, []byte("evidence-"+ref), "i-0"+ref); err != nil {
235 f.t.Fatalf("enrollmentAttempt: %v", err)
236 }
237}
238
239// roleChange is the operator's real SetBoxRole on this box.
240func (f *bindingRosterFixture) roleChange(role string) {
241 f.t.Helper()
242 if _, err := f.dir.SetBoxRole(f.t.Context(), f.signer, f.read(), f.box, role); err != nil {
243 f.t.Fatalf("roleChange: %v", err)
244 }
245}
246
247// sixWrites is the design's L2 sequence: hold Reserve and ReleaseFinishedHold,
248// activation Reserve and CancelActivation, BeginEnrollmentAttempt and
249// SetBoxRole. The roster write counter moves by exactly six and this box's
250// binding does not move at all.
251func (f *bindingRosterFixture) sixWrites() {
252 f.t.Helper()
253 before, binding := f.read().Roster.Revision, f.binding()
254 f.holdChurn()
255 f.activationChurn()
256 f.enrollmentAttempt()
257 role := hostactors.RoleLegacy
258 if f.row().Role == role {
259 role = hostactors.RoleDefault
260 }
261 f.roleChange(role)
262 if after := f.read().Roster.Revision; after != before+6 {
263 f.t.Fatalf("six roster writes moved the revision %d -> %d", before, after)
264 }
265 if f.binding() != binding {
266 f.t.Fatal("unrelated roster writes moved the box binding")
267 }
268}
269