carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package engines
2
3import (
4 "fmt"
5 "sort"
6 "strings"
7)
8
9// Classification, minted groups and the operator clear (spec §5.1 "The
10// column", "Platform-minted rows", "Becoming public is never automatic",
11// "Classification before serving"; §6.3).
12
13// ---------- minted groups ----------
14
15// followsOwner: the row is a platform-minted row that takes its execution
16// owner's mark and barrier. Only minted instance rows do: a minted static or
17// service row is 0 by construction (static-minted-public).
18func followsOwner(r *row) bool {
19 if !r.Platform || !on("minted-follow-owner") {
20 return false
21 }
22 return r.Kind == "instance" || (r.Kind == "static" && !on("static-minted-public"))
23}
24
25// resolveOwner is the edge's (and the agent's) owner for a minted row, under
26// the rules in force.
27func (b *box) resolveOwner(r *row) (string, bool) {
28 return b.resolveOwnerWith(r, on("owner-nonempty-unique"))
29}
30
31// resolveOwnerWith: the recorded owner, if its row exists; else the
32// non-platform instance row with the same account and app and the same
33// backend. strict is the spec's fallback: empty values never match, and a
34// match that is not unique counts as no match. Without it, empty values
35// match and the first match wins.
36func (b *box) resolveOwnerWith(r *row, strict bool) (string, bool) {
37 if r.Owner != "" {
38 if o := b.Rows[r.Owner]; o != nil && !o.Platform {
39 return r.Owner, true
40 }
41 return "", false
42 }
43 var found []string
44 for _, h := range hostsOf(b) {
45 c := b.Rows[h]
46 if c.Platform || c.Kind != "instance" || c.App != r.App {
47 continue
48 }
49 cs, ca, rs, ra := socketOf(c), addrOf(c), socketOf(r), addrOf(r)
50 if strict && ((cs != "" && cs == rs) || (ca != "" && ca == ra)) ||
51 !strict && (cs == rs || ca == ra) {
52 found = append(found, h)
53 }
54 }
55 if len(found) == 1 || (!strict && len(found) > 0) {
56 return found[0], true
57 }
58 return "", false
59}
60
61// socketOf and addrOf split a backend into the registry's Socket and Addr.
62func socketOf(r *row) string {
63 if strings.HasPrefix(r.Backend, "/") {
64 return r.Backend
65 }
66 return ""
67}
68
69func addrOf(r *row) string {
70 if r.Backend != "" && !strings.HasPrefix(r.Backend, "/") {
71 return r.Backend
72 }
73 return ""
74}
75
76func hostsOf(b *box) []string {
77 hosts := make([]string, 0, len(b.Rows))
78 for h := range b.Rows {
79 hosts = append(hosts, h)
80 }
81 sort.Strings(hosts)
82 return hosts
83}
84
85// truthGroup is the group a row really serves: a minted row's source, else
86// its own host. The oracle reads it; the protocol never does.
87func truthGroup(b *box, host string) string {
88 if r := b.Rows[host]; r != nil && r.Platform && r.Source != "" {
89 return r.Source
90 }
91 return host
92}
93
94// mint is the domains reconciler minting a platform row from src: it copies
95// the source's kind, backend and app (no version), and, being a row of the
96// same group, the group's mark. recordOwner is whether it writes
97// routes.owner (rows minted before the column was added have none until the
98// reconciler's next pass).
99func (w *world) mint(b *box, src, host string, recordOwner bool) {
100 s := b.Rows[src]
101 m := w.groupMark(b, w.group(b, src))
102 r := &row{Host: host, Kind: s.Kind, App: s.App, Backend: s.Backend, Platform: true, Source: src, PathTo: map[string]string{}}
103 if recordOwner {
104 r.Owner = src
105 }
106 b.Rows[host] = r
107 b.Mark[host] = m
108 b.Wrote++
109 w.logf("box%d: registry change %d: minted %s %s from %s (owner %q, mark %v)", b.ID, b.Wrote, r.Kind, host, src, r.Owner, m)
110}
111
112// reconcileOwners is the reconciler's backfill pass: it records the owner of
113// every minted instance row that has none. The row takes its owner group's
114// mark (one group, one mark). It reports whether it recorded any.
115func (w *world) reconcileOwners(b *box) bool {
116 did := false
117 for _, h := range hostsOf(b) {
118 r := b.Rows[h]
119 if !r.Platform || r.Kind != "instance" || r.Owner != "" || b.Rows[r.Source] == nil {
120 continue
121 }
122 m := w.groupMark(b, w.group(b, r.Source))
123 r.Owner = r.Source
124 b.Mark[h] = m
125 b.Wrote++
126 did = true
127 w.logf("box%d: registry change %d: owner of %s recorded as %s (mark %v)", b.ID, b.Wrote, h, r.Owner, m)
128 }
129 return did
130}
131
132// provision creates instance route host (a new route: NULL, unclassified),
133// then its first adoption classifies it before it is served: a declaring
134// build through the gate, a readable plain one as 0 (spec §5.1, "A route the
135// agent creates later ... is classified by its first adoption").
136func (w *world) provision(b *box, host, app, backend string, ver int) bool {
137 b.Rows[host] = &row{Host: host, Kind: "instance", App: app, Backend: backend, PathTo: map[string]string{}}
138 b.Wrote++
139 w.logf("box%d: registry change %d: provisioned %s (%s, %s), NULL until its first adoption", b.ID, b.Wrote, host, app, backend)
140 ok := w.adopt(b, host, ver, nil)
141 if w.groupMark(b, host) == markNull {
142 if m := w.classify(b, host); m != markNull {
143 w.writeMark(b, host, m)
144 }
145 }
146 return ok
147}
148
149// addStaticApp creates static app name with its minted platform row and a
150// static canary on its own channel (domains/reconcile.go:368).
151func (w *world) addStaticApp(b *box, name string) (minted, canary string) {
152 b.Rows[name] = &row{Host: name, Kind: "static", App: "acct/" + name, PathTo: map[string]string{}}
153 b.Mark[name] = markPublic // 0 by construction
154 b.Wrote++
155 w.logf("box%d: registry change %d: static app %s", b.ID, b.Wrote, name)
156 minted, canary = name+".acct.apps.example", "canary-"+name+".acct.apps.example"
157 w.mint(b, name, minted, true)
158 w.mint(b, name, canary, true)
159 return minted, canary
160}
161
162// ---------- grants ----------
163
164// groupHasGrant: a row of group g holds the doc-engine grant. With
165// grant-marks-group, any row of the group; without it, only the owner's own
166// row (a grant on a minted row is ignored, R6-1).
167func (w *world) groupHasGrant(b *box, g string) bool {
168 if !on("grant-marks-group") {
169 r := b.Rows[g]
170 return r != nil && r.Grant
171 }
172 for _, h := range w.groupHosts(b, g) {
173 if b.Rows[h].Grant {
174 return true
175 }
176 }
177 return false
178}
179
180// grantLoaded is the oracle's grant: a row whose true group is g holds the
181// grant and the edge has loaded the change that recorded it. It returns that
182// row, or "".
183func (w *world) grantLoaded(b *box, g string) string {
184 for _, h := range hostsOf(b) {
185 r := b.Rows[h]
186 if r.Grant && truthGroup(b, h) == g && b.Fenced >= r.GrantAt {
187 return h
188 }
189 }
190 return ""
191}
192
193// grant is `carlos route --host <host> --grant doc-engine`: it records the
194// grant, writes 1 on the group if the group now holds it, and returns once
195// the edge has loaded the change (or says it timed out). On a minted row
196// whose owner cannot be resolved it is refused: the CLI cannot find the
197// group to mark, and the row itself stays NULL and refused.
198func (w *world) grant(b *box, host string) bool {
199 r := b.Rows[host]
200 if r == nil || r.Kind != "instance" || r.Grant {
201 return false
202 }
203 if followsOwner(r) {
204 if _, ok := b.resolveOwner(r); !ok {
205 w.logf("box%d: --grant doc-engine on %s: refused: the row's owner is not recorded and cannot be found uniquely; the row stays NULL and refused", b.ID, host)
206 return false
207 }
208 }
209 b.Wrote++
210 r.Grant, r.GrantAt = true, b.Wrote
211 g := w.group(b, host)
212 w.logf("box%d: registry change %d: --grant doc-engine on %s (group %s)", b.ID, b.Wrote, host, g)
213 if w.groupHasGrant(b, g) && w.groupMark(b, g) != markInternal {
214 w.writeMark(b, g, markInternal)
215 }
216 want := b.Wrote
217 for i := 0; i < fenceWaitTicks && b.Fenced < want; i++ {
218 w.reload(b)
219 }
220 if b.Fenced < want {
221 w.logf("box%d: --grant on %s: the edge has not loaded it; timed out", b.ID, host)
222 return false
223 }
224 return true
225}
226
227// revoke is --revoke doc-engine on any row of a group: the grant goes and
228// the group's mark stays (becoming public is never automatic; only --public
229// writes 0). Like --grant it runs the barrier for the group: it returns once
230// the edge has loaded the change, so the group's mark is fenced, or says it
231// timed out. Refused on a minted row whose owner cannot be resolved.
232func (w *world) revoke(b *box, host string) bool {
233 r := b.Rows[host]
234 if r == nil || !r.Grant {
235 return false
236 }
237 if followsOwner(r) {
238 if _, ok := b.resolveOwner(r); !ok {
239 w.logf("box%d: --revoke doc-engine on %s: refused: the row's owner cannot be resolved", b.ID, host)
240 return false
241 }
242 }
243 g := w.group(b, host)
244 b.Wrote++
245 r.Grant = false
246 want := b.Wrote
247 w.logf("box%d: registry change %d: --revoke doc-engine on %s (group %s); the mark stays %v", b.ID, b.Wrote, host, g, w.groupMark(b, g))
248 for i := 0; i < fenceWaitTicks && b.Fenced < want; i++ {
249 w.reload(b)
250 }
251 if b.Fenced < want {
252 w.logf("box%d: --revoke on %s: the edge has not loaded it; timed out", b.ID, host)
253 return false
254 }
255 return true
256}
257
258// ---------- classification before serving ----------
259
260// classify is the upgrade's classification of group g: 1 if a row of the
261// group holds the grant, the live manifest declares, or the manifest of any
262// other version of the app staged on the box declares, whichever instance of
263// the app staged it (classify-staged); 0 if the live manifest is readable and
264// none of those holds; else NULL.
265func (w *world) classify(b *box, g string) mark {
266 if w.groupHasGrant(b, g) {
267 return markInternal
268 }
269 live, liveOK := w.builds[b.Live[g]]
270 liveOK = liveOK && b.Live[g] != 0 && live.ManifestOK
271 if liveOK && live.Declares {
272 return markInternal
273 }
274 if on("classify-staged") {
275 for _, v := range w.stagedForApp(b, g) {
276 if bd := w.builds[v]; bd.ManifestOK && bd.Declares {
277 return markInternal
278 }
279 }
280 }
281 if liveOK {
282 return markPublic
283 }
284 return markNull
285}
286
287// classifiedGroups is every group with a row the classification covers: an
288// instance row, or a row that follows an owner.
289func (w *world) classifiedGroups(b *box) []string {
290 seen := map[string]bool{}
291 var out []string
292 for _, h := range hostsOf(b) {
293 r := b.Rows[h]
294 if r.Kind != "instance" && !followsOwner(r) {
295 continue
296 }
297 if g := w.group(b, h); !seen[g] {
298 seen[g] = true
299 out = append(out, g)
300 }
301 }
302 return out
303}
304
305// upgrade is the agent's first start on the build that adds routes.internal.
306// The column arrives NULL on every instance route; before any edge listener
307// opens, the agent classifies every NULL group. The edge then starts on the
308// classified map: in-flight requests did not survive the restart, and the
309// fence covers everything written. Running processes are untouched.
310func (w *world) upgrade(b *box) {
311 if w.unwrittenManifestAcrossUpgrade {
312 if why, procs := w.upgradeResidual(b); why != "" {
313 for _, p := range procs {
314 p.Residual = "unwrittenManifestAcrossUpgrade"
315 }
316 w.logf("box%d: upgrade: the stated residual (%s); its processes' breaches are counted as residual", b.ID, why)
317 }
318 }
319 gs := w.classifiedGroups(b)
320 w.logf("box%d: upgrade: routes.internal arrives NULL on %v", b.ID, gs)
321 for _, g := range gs {
322 w.writeMark(b, g, markNull)
323 }
324 for _, g := range gs {
325 if m := w.classify(b, g); m != markNull {
326 w.writeMark(b, g, m)
327 } else {
328 w.logf("box%d: upgrade: %s stays NULL (live manifest unreadable): refused publicly, named every pass", b.ID, g)
329 }
330 }
331 b.loadEdge()
332 b.Epoch++
333 if len(b.Admitted) > 0 {
334 w.logf("box%d: upgrade: %d in-flight requests ended with the restart", b.ID, len(b.Admitted))
335 }
336 b.Admitted = nil
337 b.Fenced = b.Wrote
338 // the restart loaded every group's mark and drained the whole box: a
339 // fence for every closed group, NULL included
340 for _, g := range gs {
341 if w.closed(b, g) && !w.recorded(b, g) {
342 b.SetAt[g] = b.Wrote
343 }
344 }
345 w.logf("box%d: upgrade: listeners open on the classified map, epoch %d, fenced %d", b.ID, b.Epoch, b.Fenced)
346}
347
348// residualAtUpgrade names the spec's stated residual if an upgrade of b now
349// would produce it: a running declaring build whose manifest cannot be read,
350// in a group the (correct) classification would mark 0 because the live
351// manifest reads and does not declare. The default world does not generate
352// it; Task 5's flag (unwrittenManifestAcrossUpgrade) does.
353func (w *world) residualAtUpgrade(b *box) string {
354 why, _ := w.upgradeResidual(b)
355 return why
356}
357
358// upgradeResidual is residualAtUpgrade with the processes it names: every
359// running unreadable declaring process of each group it applies to.
360func (w *world) upgradeResidual(b *box) (string, []*proc) {
361 first := ""
362 var procs []*proc
363 for _, g := range w.groups(b) {
364 var unreadable *proc
365 var group []*proc
366 for _, p := range b.Procs {
367 if bd := w.builds[p.Ver]; p.Group == g && bd.Declares && !bd.ManifestOK {
368 unreadable = p
369 group = append(group, p)
370 }
371 }
372 if unreadable == nil {
373 continue
374 }
375 granted := false
376 for _, h := range hostsOf(b) {
377 granted = granted || (b.Rows[h].Grant && truthGroup(b, h) == g)
378 }
379 live, ok := w.builds[b.Live[g]]
380 if granted || b.Live[g] == 0 || !ok || !live.ManifestOK || live.Declares {
381 continue
382 }
383 staged := false
384 for _, v := range w.stagedForApp(b, g) {
385 if bd := w.builds[v]; bd.ManifestOK && bd.Declares {
386 staged = true
387 }
388 }
389 if !staged {
390 if first == "" {
391 first = fmt.Sprintf("%s runs v%d (declares, manifest unreadable) while live v%d reads as plain", g, unreadable.Ver, b.Live[g])
392 }
393 procs = append(procs, group...)
394 }
395 }
396 return first, procs
397}
398
399// ---------- the operator clear ----------
400
401// operatorPublic is `carlos route --host <host> --public` on group g (spec
402// §5.1, "Becoming public is never automatic"). Holding the group's lock for
403// its whole run (adoption's repoint honours it, public-holds-lock; so does
404// every launch through the gate, clear-launch-lock):
405// 1. refuse unless the live manifest is readable and does not declare
406// (public-checks-live), and no row of the group holds the grant;
407// 2. stop the whole group (public-stops-group);
408// 3. refuse unless every cgroup of the group is empty (public-confirms-empty);
409// 4. write 0 and release the lock.
410//
411// Step 3 is what catches a stop that did not complete (w.clearStopMisses).
412//
413// w.clearHook, if set, runs after step 1 ("locked"), after step 2
414// ("stopped") and after step 3 ("confirmed").
415func (w *world) operatorPublic(b *box, g string) bool {
416 w.logf("box%d: --public %s: taking the group's lock", b.ID, g)
417 b.Locked[g] = true
418 defer delete(b.Locked, g)
419 if on("public-checks-live") {
420 live, ok := w.builds[b.Live[g]]
421 if b.Live[g] == 0 || !ok || !live.ManifestOK || live.Declares {
422 w.logf("box%d: --public %s: refused: the live release v%d is unreadable or declares doc_engine", b.ID, g, b.Live[g])
423 return false
424 }
425 }
426 if w.groupHasGrant(b, g) {
427 w.logf("box%d: --public %s: refused: a row of the group holds the doc-engine grant", b.ID, g)
428 return false
429 }
430 w.hook(b, g, "locked")
431 if on("public-stops-group") {
432 w.clearStop(b, g)
433 }
434 w.hook(b, g, "stopped")
435 if on("public-confirms-empty") && w.running(b, g) {
436 w.logf("box%d: --public %s: refused: a cgroup of the group is not empty", b.ID, g)
437 return false
438 }
439 w.hook(b, g, "confirmed")
440 w.writeMark(b, g, markPublic)
441 w.logf("box%d: --public %s: 0 written; the lock is released", b.ID, g)
442 return true
443}
444
445// clearStop is step 2: stop every process of group g, except, when
446// w.clearStopMisses is set, the first one, whose stop did not complete.
447func (w *world) clearStop(b *box, g string) {
448 var left *proc
449 if w.clearStopMisses {
450 for _, p := range b.Procs {
451 if p.Group == g {
452 left = p
453 break
454 }
455 }
456 }
457 w.stopGroup(b, g)
458 if left != nil {
459 b.Procs = append(b.Procs, left)
460 w.logf("box%d: --public %s: the stop did not complete: process %d (v%d) still runs", b.ID, g, left.ID, left.Ver)
461 return
462 }
463 w.logf("box%d: --public %s: the group is stopped", b.ID, g)
464}
465
466func (w *world) hook(b *box, g, stage string) {
467 if w.clearHook != nil {
468 w.clearHook(b, g, stage)
469 }
470}
471
472// ---------- the random walk ----------
473
474// pickInstanceRow is any instance row of b, minted included, or "".
475func (w *world) pickInstanceRow(b *box) string {
476 var hs []string
477 for _, h := range hostsOf(b) {
478 if b.Rows[h].Kind == "instance" {
479 hs = append(hs, h)
480 }
481 }
482 if len(hs) == 0 {
483 return ""
484 }
485 return hs[w.rng.Intn(len(hs))]
486}
487
488// maxRows bounds a box's table in the walk.
489const maxRows = 24
490
491func init() {
492 actions = append(actions,
493 action{name: "upgrade", weight: 1, run: func(w *world) bool {
494 b := w.pickBox()
495 if why := w.residualAtUpgrade(b); why != "" && !w.unwrittenManifestAcrossUpgrade {
496 w.logf("box%d: upgrade not generated: the stated residual (%s)", b.ID, why)
497 return false
498 }
499 w.upgrade(b)
500 return true
501 }},
502 action{name: "grant", weight: 1, run: func(w *world) bool {
503 b := w.pickBox()
504 h := w.pickInstanceRow(b)
505 if h == "" || b.Rows[h].Grant {
506 return false
507 }
508 w.grant(b, h)
509 return true
510 }},
511 action{name: "revoke", weight: 1, run: func(w *world) bool {
512 b := w.pickBox()
513 h := w.pickInstanceRow(b)
514 if h == "" || !b.Rows[h].Grant {
515 return false
516 }
517 w.revoke(b, h)
518 return true
519 }},
520 action{name: "operator-public", weight: 2, run: func(w *world) bool {
521 b := w.pickBox()
522 g := w.pickGroup(b)
523 if g == "" {
524 return false
525 }
526 switch w.rng.Intn(5) {
527 case 1: // an adoption tries to run during the clear
528 w.clearHook = func(hb *box, hg, stage string) {
529 if stage == "locked" {
530 w.adopt(hb, hg, w.pickVer(), nil)
531 }
532 }
533 case 2: // an awake repoint tries to run during the clear
534 w.clearHook = func(hb *box, hg, stage string) {
535 if stage == "locked" {
536 w.repointAwake(hb, hg, w.pickVer())
537 }
538 }
539 case 3: // a swap's incoming generation races the stop
540 w.clearHook = func(hb *box, hg, stage string) {
541 if stage == "stopped" {
542 w.launch(hb, hg, w.pickVer(), "swap")
543 }
544 }
545 case 4: // a launch races the write of 0, after the empty check
546 w.clearHook = func(hb *box, hg, stage string) {
547 if stage == "confirmed" {
548 w.launch(hb, hg, w.pickVer(), []string{"cmd", "recovery", "seed"}[w.rng.Intn(3)])
549 }
550 }
551 }
552 w.clearStopMisses = w.rng.Intn(4) == 0
553 w.operatorPublic(b, g)
554 w.clearHook, w.clearStopMisses = nil, false
555 return true
556 }},
557 action{name: "adopt-awake", weight: 2, run: func(w *world) bool {
558 b := w.pickBox()
559 g := w.pickGroup(b)
560 if g == "" {
561 return false
562 }
563 w.repointAwake(b, g, w.pickVer())
564 return true
565 }},
566 action{name: "add-tcp-instance", weight: 1, run: func(w *world) bool {
567 b := w.pickBox()
568 if len(b.Rows) >= maxRows {
569 return false
570 }
571 w.nextID++
572 host := fmt.Sprintf("t%d", w.nextID)
573 app := []string{"acct/shop", "acct/tcp"}[w.rng.Intn(2)]
574 addr := fmt.Sprintf("10.0.%d.1:%d", b.ID, 9100+w.nextID) // unique: no ambiguous owner in the walk
575 w.provision(b, host, app, addr, w.pickVer())
576 w.mint(b, host, host+".acct.apps.example", w.rng.Intn(2) == 0)
577 return true
578 }},
579 action{name: "add-static-app", weight: 1, run: func(w *world) bool {
580 b := w.pickBox()
581 if len(b.Rows) >= maxRows {
582 return false
583 }
584 w.nextID++
585 w.addStaticApp(b, fmt.Sprintf("s%d", w.nextID))
586 return true
587 }},
588 action{name: "reconcile-owners", weight: 1, run: func(w *world) bool {
589 return w.reconcileOwners(w.pickBox())
590 }},
591 )
592}
593