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 "context"
5 "encoding/base64"
6 "encoding/json"
7 "errors"
8 "io/fs"
9 "os"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/boxid"
15 "github.com/carlosframework/platform/internal/hostactors"
16 "github.com/carlosframework/platform/internal/hostfloor"
17 "github.com/carlosframework/platform/internal/status"
18)
19
20var joinNow = time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC)
21
22// joinFacts is a box, "ada-hiking", in each state the tests name. The
23// zero value has no host-actor.json and no invitation.
24type joinFacts struct {
25 enrollment *hostfloor.Enrollment
26 enrollmentErr error
27 roster hostactors.Roster
28 rosterErr error
29 guard hostfloor.Running
30 guardErr error
31 invited bool
32 invitedErr error
33 report *status.Box
34 reportErr error
35 relaunchWhy string
36 relaunchErr error
37 relaunchAsked *bool
38 marked bool
39 region string
40}
41
42func (j joinFacts) facts() modeFacts {
43 return modeFacts{
44 box: "ada-hiking",
45 enrollment: func() (hostfloor.Enrollment, error) {
46 if j.enrollmentErr != nil {
47 return hostfloor.Enrollment{}, j.enrollmentErr
48 }
49 if j.enrollment == nil {
50 return hostfloor.Enrollment{}, &fs.PathError{Op: "open", Path: hostActorPath, Err: fs.ErrNotExist}
51 }
52 return *j.enrollment, nil
53 },
54 roster: func(context.Context) (hostactors.Roster, error) { return j.roster, j.rosterErr },
55 guard: func() (hostfloor.Running, error) {
56 if j.guardErr != nil {
57 return hostfloor.Running{}, j.guardErr
58 }
59 return j.guard, nil
60 },
61 invited: func(context.Context, string) (bool, error) { return j.invited, j.invitedErr },
62 report: func(context.Context, string) (status.Box, error) {
63 if j.reportErr != nil {
64 return status.Box{}, j.reportErr
65 }
66 if j.report == nil {
67 return status.Box{}, os.ErrNotExist
68 }
69 return *j.report, nil
70 },
71 relaunch: func(context.Context, hostactors.Box) (string, error) {
72 if j.relaunchAsked != nil {
73 *j.relaunchAsked = true
74 }
75 return j.relaunchWhy, j.relaunchErr
76 },
77 marked: j.marked,
78 region: j.region,
79 now: func() time.Time { return joinNow },
80 }
81}
82
83const joinEnrollment = "0123456789abcdef0123456789abcdef"
84
85func enrolledFacts() joinFacts {
86 e := hostfloor.Enrollment{Box: "ada-hiking", Enrollment: joinEnrollment, InstanceID: "i-ada"}
87 return joinFacts{
88 enrollment: &e,
89 roster: hostactors.Roster{FloorSHA256: "floor", Boxes: map[string]hostactors.Box{"ada-hiking": {
90 Enrollment: joinEnrollment, State: "active", Cloud: hostactors.Cloud{InstanceID: "i-ada"}}}},
91 guard: hostfloor.Running{GuardReady: true, FloorSHA256: "floor", Hash: "image"},
92 }
93}
94
95// guardInstalled is a box that guard-arm has armed but the console has not
96// enrolled yet.
97func guardInstalled() joinFacts {
98 f := enrolledFacts()
99 f.roster.Boxes = map[string]hostactors.Box{}
100 return f
101}
102
103func freshReport(at time.Time) *status.Box {
104 return &status.Box{GeneratedAt: at, Metrics: &status.BoxMetrics{Enrollment: joinEnrollment, FloorGuardReady: true, FloorSHA256: "floor", BinarySHA256: "image"}}
105}
106
107func TestSelectModeHostsOnlyWhenEnrolledAndArmed(t *testing.T) {
108 d, err := selectMode(context.Background(), enrolledFacts().facts())
109 if err != nil || d.Mode != modeHosting || d.Box != "ada-hiking" {
110 t.Fatalf("enrolled and armed: %+v %v", d, err)
111 }
112 held := map[string]func(*joinFacts){
113 "the guard is not armed": func(f *joinFacts) { f.guardErr = hostfloor.ErrFloor },
114 "the guard has no policy": func(f *joinFacts) { f.guardErr = &fs.PathError{Op: "open", Path: "policy.json", Err: fs.ErrNotExist} },
115 "the guard is for another floor": func(f *joinFacts) { f.guard.FloorSHA256 = "old" },
116 "the roster entry is quarantined": func(f *joinFacts) {
117 b := f.roster.Boxes["ada-hiking"]
118 b.State = "quarantined"
119 f.roster.Boxes["ada-hiking"] = b
120 },
121 "the roster entry is another enrollment": func(f *joinFacts) {
122 b := f.roster.Boxes["ada-hiking"]
123 b.Enrollment = strings.Repeat("f", 32)
124 f.roster.Boxes["ada-hiking"] = b
125 },
126 "the roster entry is another instance": func(f *joinFacts) {
127 b := f.roster.Boxes["ada-hiking"]
128 b.Cloud.InstanceID = "i-old"
129 f.roster.Boxes["ada-hiking"] = b
130 },
131 "host-actor.json names another box": func(f *joinFacts) { f.enrollment.Box = "grace-hiking" },
132 }
133 for name, spoil := range held {
134 t.Run(name, func(t *testing.T) {
135 f := enrolledFacts()
136 spoil(&f)
137 d, err := selectMode(context.Background(), f.facts())
138 if err != nil || d.Mode != modeJoining || d.Stage != stageHeld || d.Why == "" {
139 t.Fatalf("%+v %v", d, err)
140 }
141 })
142 }
143}
144
145// A restart at each joining state resumes joining at that state (spec 2
146// §11): nothing but an active roster entry with an armed guard hosts.
147func TestSelectModeResumesEachJoiningStage(t *testing.T) {
148 for _, c := range []struct {
149 name string
150 facts joinFacts
151 want joinStage
152 }{
153 {"no invitation", joinFacts{}, stageNoInvitation},
154 {"invited", joinFacts{invited: true}, stageInvited},
155 {"guard-arm cut off after host-actor.json", func() joinFacts { f := guardInstalled(); f.guardErr = hostfloor.ErrFloor; return f }(), stageInvited},
156 {"guard installed", guardInstalled(), stageGuardInstalled},
157 {"guard-ready reported", func() joinFacts { f := guardInstalled(); f.report = freshReport(joinNow.Add(-time.Minute)); return f }(), stageGuardReady},
158 {"a stale report", func() joinFacts {
159 f := guardInstalled()
160 f.report = freshReport(joinNow.Add(-16 * time.Minute))
161 return f
162 }(), stageGuardInstalled},
163 {"a report for another image", func() joinFacts {
164 f := guardInstalled()
165 f.report = freshReport(joinNow)
166 f.report.Metrics.BinarySHA256 = "bootstrap"
167 return f
168 }(), stageGuardInstalled},
169 } {
170 t.Run(c.name, func(t *testing.T) {
171 d, err := selectMode(context.Background(), c.facts.facts())
172 if err != nil || d.Mode != modeJoining || d.Stage != c.want {
173 t.Fatalf("%+v %v, want joining at %s", d, err, c.want)
174 }
175 })
176 }
177}
178
179// A fact that cannot be read is unknown, never its negative: an enrolled
180// box whose roster read fails is not sent back to joining.
181func TestSelectModeIsUnknownWhenAFactCannotBeRead(t *testing.T) {
182 outage := errors.New("connection reset")
183 for name, f := range map[string]joinFacts{
184 "the invitation": {invitedErr: outage},
185 "host-actor.json": {enrollmentErr: hostfloor.ErrFloor},
186 "the roster": func() joinFacts { f := enrolledFacts(); f.rosterErr = outage; return f }(),
187 "the guard": func() joinFacts { f := enrolledFacts(); f.guardErr = errors.New("permission denied"); return f }(),
188 "the status report": func() joinFacts { f := guardInstalled(); f.reportErr = outage; return f }(),
189 "a roster that does not verify": func() joinFacts { f := guardInstalled(); f.rosterErr = hostactors.ErrRoster; return f }(),
190 } {
191 t.Run(name, func(t *testing.T) {
192 d, err := selectMode(context.Background(), f.facts())
193 if !errors.Is(err, errModeUnknown) || d.Mode == modeHosting {
194 t.Fatalf("%+v %v", d, err)
195 }
196 })
197 }
198}
199
200// C3: a box its region's group launched hosts only on the launch it was
201// enrolled on. Stopped and started since, it is held, never hosting on its
202// old enrollment; a launch that cannot be read is unknown. A box an
203// operator enrolled (no group) is not asked.
204func TestSelectModeHoldsAGroupBoxStartedAgain(t *testing.T) {
205 grouped := func() joinFacts {
206 f := enrolledFacts()
207 b := f.roster.Boxes["ada-hiking"]
208 b.Group, b.Region = "routers-ie", "ie"
209 f.roster.Boxes["ada-hiking"] = b
210 f.relaunchAsked = new(bool)
211 f.marked, f.region = true, "ie"
212 return f
213 }
214 f := grouped()
215 if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting || !*f.relaunchAsked {
216 t.Fatalf("same launch: %+v %v", d, err)
217 }
218 f = grouped()
219 f.relaunchWhy = "this instance was stopped and started since it was enrolled"
220 if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeJoining || d.Stage != stageHeld || d.Why != f.relaunchWhy {
221 t.Fatalf("started again: %+v %v", d, err)
222 }
223 f = grouped()
224 f.relaunchErr = errors.New("IMDS answered 500")
225 if d, err := selectMode(context.Background(), f.facts()); !errors.Is(err, errModeUnknown) || d.Mode == modeHosting {
226 t.Fatalf("unreadable launch: %+v %v", d, err)
227 }
228 f = enrolledFacts()
229 f.relaunchWhy, f.relaunchAsked = "never asked", new(bool)
230 if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting || *f.relaunchAsked {
231 t.Fatalf("an operator-enrolled box: %+v %v, asked %v", d, err, *f.relaunchAsked)
232 }
233}
234
235func TestRelaunchedComparesTheLaunchTheRosterBinds(t *testing.T) {
236 record := func(pending string) (boxid.Record, []byte) {
237 doc := `{"accountId":"123","region":"eu-west-1","instanceId":"i-ada"` + pending + `}`
238 rec := boxid.Record{V: 1, Box: "i-ada", IdentityDocument: base64.StdEncoding.EncodeToString([]byte(doc)), IdentitySignature: "c2ln"}
239 raw, _ := json.Marshal(rec)
240 return rec, raw
241 }
242 enrolledRec, enrolled := record(`,"pendingTime":"2026-10-17T09:00:00Z"`)
243 row := hostactors.Box{EvidenceSHA256: hostfloor.Digest(enrolled), Group: "routers-ie"}
244 if why := relaunched(row, enrolled, enrolledRec); why != "" {
245 t.Fatalf("the same launch: %q", why)
246 }
247 again, _ := record(`,"pendingTime":"2026-10-17T11:30:00Z"`)
248 if why := relaunched(row, enrolled, again); !strings.Contains(why, "stopped and started") {
249 t.Fatalf("started again: %q", why)
250 }
251 none, _ := record("")
252 if why := relaunched(row, enrolled, none); why == "" {
253 t.Fatal("a document with no pendingTime matched")
254 }
255 _, other := record(`,"pendingTime":"2026-10-17T09:00:00Z","x":1`)
256 if why := relaunched(row, other, enrolledRec); !strings.Contains(why, "not the one it was enrolled on") {
257 t.Fatalf("another record: %q", why)
258 }
259}
260
261// Condition 3 of census ruling region-ha-2b: the group marker and a group
262// box's CARLOS_REGION come from the parameter store, and a value that
263// disagrees with the signed roster holds the box instead of hosting it.
264func TestSelectModeHoldsAStoreValueTheRosterDisagreesWith(t *testing.T) {
265 grouped := func() joinFacts {
266 f := enrolledFacts()
267 b := f.roster.Boxes["ada-hiking"]
268 b.Group, b.Region = "routers-ie", "ie"
269 f.roster.Boxes["ada-hiking"] = b
270 f.marked, f.region = true, "ie"
271 return f
272 }
273 if d, err := selectMode(context.Background(), grouped().facts()); err != nil || d.Mode != modeHosting {
274 t.Fatalf("agreeing: %+v %v", d, err)
275 }
276 for name, spoil := range map[string]func(*joinFacts){
277 "marked, enrolled by hand": func(f *joinFacts) {
278 b := f.roster.Boxes["ada-hiking"]
279 b.Group = ""
280 f.roster.Boxes["ada-hiking"] = b
281 },
282 "enrolled from a group, not marked": func(f *joinFacts) { f.marked = false },
283 "another region": func(f *joinFacts) { f.region = "uk" },
284 "no region": func(f *joinFacts) { f.region = "" },
285 } {
286 t.Run(name, func(t *testing.T) {
287 f := grouped()
288 spoil(&f)
289 d, err := selectMode(context.Background(), f.facts())
290 if err != nil || d.Mode != modeJoining || d.Stage != stageHeld || !strings.Contains(d.Why, "does not host") {
291 t.Fatalf("%+v %v", d, err)
292 }
293 })
294 }
295 // A box an operator enrolled keeps its own region, unchecked.
296 f := enrolledFacts()
297 f.region = "anything"
298 if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting {
299 t.Fatalf("an operator-enrolled box: %+v %v", d, err)
300 }
301}
302