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// The dedicated engine box and ingress (spec §5.9 "The engine box is
10// dedicated"; §5.1 "The edge is the only ingress").
11//
12// I1 keeps a declaring build from starting before the edge refuses the
13// public, but the edge is not the only way to a process on its box. These
14// rules close the others:
15//
16// - ports-excluded: a release declaring doc_engine and ports is refused at
17// ship, and the box renders no ListenStream for an internal group. The
18// capability pass removes an existing one whenever the group's mark
19// closes (writeMark calls dropListeners), so it is gone before any fence
20// of that write completes.
21// - dedicated-launch-check: a declaring launch is refused on a box with a
22// proxying row outside its group (the gate, before it writes or waits).
23// - dedicated-edge-refusal: while an internal group is homed on the box,
24// the edge gives every proxying row outside that group the internal
25// refusal.
26// - ingress-precondition: the operator's firewall admits connections from
27// every source, public or private (the deployment's other boxes too),
28// only on the edge's ports and the ports the operator granted. It is a
29// world flag, not a box mechanism: the platform cannot see it.
30//
31// A proxying row is an instance or service row with a backend: the edge
32// proxies it to a process. Static rows (and the status host, the tunnel and
33// steering handlers, which the model does not have) are answered by the edge
34// itself. Alias rows and path maps resolve to their final row, and the edge
35// checks that one.
36
37// ---------- addresses ----------
38
39// A TCP backend is on a box by its subnet, 10.0.<box>.<n>:<port>; a socket
40// path or 127.0.0.1 is reachable on its own box only.
41
42// boxAddr is box b's address.
43func boxAddr(b *box) string { return fmt.Sprintf("10.0.%d.1", b.ID) }
44
45// addrBox is the box an address backend is on, and its port; nil for a
46// socket path, loopback, or an address on no box.
47func (w *world) addrBox(backend string) (*box, string) {
48 i := strings.LastIndex(backend, ":")
49 if strings.HasPrefix(backend, "/") || i < 0 {
50 return nil, ""
51 }
52 for _, b := range w.boxes {
53 if strings.HasPrefix(backend[:i], fmt.Sprintf("10.0.%d.", b.ID)) {
54 return b, backend[i+1:]
55 }
56 }
57 return nil, ""
58}
59
60// ownPort is the port process p binds on its own, if it does (OwnPort).
61func ownPort(p *proc) string { return fmt.Sprint(20000 + p.ID) }
62
63// bindOwnPort is process p binding a port of its own on every interface:
64// the unit template allows it and the capability validator leaves high
65// ports to the app (spec §5.9, codex R12-1).
66func (w *world) bindOwnPort(b *box, p *proc) {
67 p.OwnPort = true
68 w.logf("box%d: process %d (%s v%d) binds port %s on every interface", b.ID, p.ID, p.Group, p.Ver, ownPort(p))
69}
70
71// newest is group g's newest process on b, or nil.
72func (w *world) newest(b *box, g string) *proc {
73 var p *proc
74 for _, x := range b.Procs {
75 if x.Group == g {
76 p = x
77 }
78 }
79 return p
80}
81
82// ---------- service rows ----------
83
84// proxying: the edge proxies the row to a process.
85func proxying(r *row) bool {
86 return (r.Kind == "instance" || r.Kind == "service") && r.Backend != ""
87}
88
89// addServiceRow is `carlos add` of a service row on box b whose upstream is
90// backend: a socket path or address on b, or, from box 1, box 0's address.
91// A service row is 0 by construction.
92func (w *world) addServiceRow(b *box, host, backend string) {
93 b.Rows[host] = &row{Host: host, Kind: "service", Backend: backend, PathTo: map[string]string{}}
94 b.Mark[host] = markPublic
95 b.Wrote++
96 w.logf("box%d: registry change %d: service row %s -> %s", b.ID, b.Wrote, host, backend)
97}
98
99// deleteRow removes a service row. A request admitted through it keeps the
100// proxy it holds (the fence's business, fence-box-wide).
101func (w *world) deleteRow(b *box, host string) {
102 delete(b.Rows, host)
103 delete(b.Mark, host)
104 b.Wrote++
105 w.logf("box%d: registry change %d: row %s deleted", b.ID, b.Wrote, host)
106}
107
108// removeGroup is the operator removing instance route g from b, as the
109// dedicated-box refusal's log line tells them to: its processes stop, its
110// rows and the rows minted from it go, and so do its listeners. The app's
111// staged versions stay on the box (/opt/carlos/versions is per app): any
112// recorded under g pass to another instance of the app.
113func (w *world) removeGroup(b *box, g string) {
114 others := []string{}
115 for _, h := range w.appGroups(b, g) {
116 if h != g {
117 others = append(others, h)
118 }
119 }
120 if len(others) > 0 {
121 for _, v := range b.Staged[g] {
122 b.Staged[others[0]] = append(b.Staged[others[0]], v)
123 }
124 }
125 w.stopGroup(b, g)
126 for _, h := range hostsOf(b) {
127 if r := b.Rows[h]; h == g || (r.Platform && r.Source == g) {
128 delete(b.Rows, h)
129 delete(b.Mark, h)
130 }
131 }
132 for port, lg := range b.Listen {
133 if lg == g {
134 delete(b.Listen, port)
135 }
136 }
137 delete(b.Live, g)
138 delete(b.Good, g)
139 delete(b.Staged, g)
140 delete(b.SetAt, g)
141 delete(b.PortGrant, g)
142 b.Wrote++
143 w.logf("box%d: registry change %d: route %s removed with its minted rows", b.ID, b.Wrote, g)
144}
145
146// ---------- the dedicated box ----------
147
148// dedicatedLaunchCheck is the launch gate's check before it writes or waits
149// for anything: a declaring launch in group g is refused while any proxying
150// row of the box's registry belongs to another group, and each such row is
151// named with the move or remove command.
152func (w *world) dedicatedLaunchCheck(b *box, g string, ver int, path string) bool {
153 if !on("dedicated-launch-check") {
154 return true
155 }
156 var outside []string
157 for _, h := range hostsOf(b) {
158 if r := b.Rows[h]; proxying(r) && w.group(b, h) != g {
159 outside = append(outside, h)
160 }
161 }
162 for _, h := range outside {
163 w.logf("box%d: gate(%s) %s v%d: %s row %s proxies outside the group on the box that would home an engine; move or remove it", b.ID, path, g, ver, b.Rows[h].Kind, h)
164 }
165 if len(outside) > 0 {
166 w.logf("box%d: gate(%s) %s v%d: refused: the box is not dedicated", b.ID, path, g, ver)
167 return false
168 }
169 return true
170}
171
172// edgeHomed is every internal group homed on b, as the edge's map has it: a
173// group whose own route the edge serves 1 or NULL. The edge refuses NULL like
174// 1 (classify-null-refused), and so does this rule, release or not: a gate
175// writing 1 over a NULL group finds it fenced already and bumps no epoch, so
176// a request admitted through a row beside a NULL route would outlive a
177// declaring launch there (TestNullRouteHomesTheBox). The cost: a NULL route
178// on a shared box (an upgrade that cannot read its live manifest, a
179// provisioned route whose declaring first adoption the launch check refused)
180// takes every proxying neighbour out of service until it is classified.
181func (w *world) edgeHomed(b *box) map[string]bool {
182 homed := map[string]bool{}
183 for h, r := range b.EdgeRows {
184 if r.Kind != "instance" || r.Platform {
185 continue
186 }
187 if m := b.Serving[h]; m == markInternal || (m == markNull && on("classify-null-refused")) {
188 homed[h] = true
189 }
190 }
191 return homed
192}
193
194// dedicatedRefusal is the edge's refusal of final, the row a public request
195// resolved to: on a box that homes an internal group, a proxying row of any
196// other group gets the internal refusal. It returns why, or "" when the row
197// is served.
198func (w *world) dedicatedRefusal(b *box, final row) string {
199 if !on("dedicated-edge-refusal") || !proxying(&final) {
200 return ""
201 }
202 homed := w.edgeHomed(b)
203 if len(homed) == 0 || homed[edgeGroup(b, final.Host)] {
204 return ""
205 }
206 return fmt.Sprintf("%s row %s proxies outside %v, the internal group homed on the box; move or remove it", final.Kind, final.Host, sortedKeys(homed))
207}
208
209func sortedKeys(m map[string]bool) []string {
210 out := make([]string, 0, len(m))
211 for k := range m {
212 out = append(out, k)
213 }
214 sort.Strings(out)
215 return out
216}
217
218// ---------- ports ----------
219
220// ship is `carlos ship` of build ver's release for group g, its capability
221// block declaring ports or not, then its adoption on b. A ports release
222// records the operator's grant (a port) and the capability pass runs.
223func (w *world) ship(b *box, g string, ver int, ports bool) bool {
224 if ports && w.builds[ver].Declares && on("ports-excluded") {
225 w.logf("ship %s v%d: refused: the release declares doc_engine and ports together", g, ver)
226 return false
227 }
228 if !w.adopt(b, g, ver, nil) {
229 return false
230 }
231 if ports && b.PortGrant[g] == "" {
232 w.nextID++
233 b.PortGrant[g] = fmt.Sprint(40000 + w.nextID)
234 w.logf("box%d: %s holds a ports grant: port %s", b.ID, g, b.PortGrant[g])
235 }
236 w.capsPass(b)
237 return true
238}
239
240// capsPass is the agent's capability pass: it renders each granted port's
241// ListenStream, except for a route of an internal group, whose listener it
242// removes. It fails closed: the route's own mark or any row of its group
243// being 1 or NULL is enough.
244func (w *world) capsPass(b *box) {
245 gs := make([]string, 0, len(b.PortGrant))
246 for g := range b.PortGrant {
247 gs = append(gs, g)
248 }
249 sort.Strings(gs)
250 for _, g := range gs {
251 if on("ports-excluded") && (b.Mark[g] != markPublic || w.closed(b, g)) {
252 w.dropListeners(b, g)
253 continue
254 }
255 if port := b.PortGrant[g]; b.Listen[port] != g {
256 b.Listen[port] = g
257 w.logf("box%d: capability pass renders ListenStream=%s for %s", b.ID, port, g)
258 }
259 }
260}
261
262// dropListeners removes group g's ListenStream sockets, stopping their
263// socket units, and logs the grant it is ignoring. writeMark calls it on
264// every write of 1 or NULL.
265func (w *world) dropListeners(b *box, g string) {
266 if !on("ports-excluded") {
267 return
268 }
269 ports := make([]string, 0, len(b.Listen))
270 for port, lg := range b.Listen {
271 if lg == g {
272 ports = append(ports, port)
273 }
274 }
275 sort.Strings(ports)
276 for _, port := range ports {
277 delete(b.Listen, port)
278 w.logf("box%d: %s is internal: ListenStream=%s removed, its ports grant ignored (revoke it with carlos caps)", b.ID, g, port)
279 }
280}
281
282// ---------- ingress ----------
283
284// directConnect is a connection to port on box b that does not go through
285// b's edge: from the internet, or from another box (a service row there
286// aimed at b's address). A ports listener takes it whatever the firewall
287// flag (the operator granted that port, so the firewall admits it); a stopped
288// unit is socket-activated from its live link. Otherwise it reaches a process
289// only if the ingress precondition is violated: one bound to the port on its
290// own, or a TCP route's backend.
291func (w *world) directConnect(b *box, port string, public bool) *proc {
292 return w.connect(b, port, public, true)
293}
294
295// connect is directConnect, or with firewalled false the box's own traffic
296// (a local row's proxy to an address on its own box), which no firewall sees.
297func (w *world) connect(b *box, port string, public, firewalled bool) *proc {
298 src := "private"
299 if public {
300 src = "public"
301 }
302 if g, ok := b.Listen[port]; ok {
303 w.logf("box%d: %s connection to port %s: the ports listener of %s", b.ID, src, port, g)
304 w.socketActivate(b, g)
305 return w.reach(b, w.newest(b, g), public, "the ports listener on "+port)
306 }
307 if firewalled && on("ingress-precondition") {
308 w.logf("box%d: %s connection to port %s: refused by the firewall (the edge's ports only)", b.ID, src, port)
309 return nil
310 }
311 return w.reach(b, w.portProc(b, port), public, "port "+port)
312}
313
314// portProc is the process listening on port on b: one bound to it on its
315// own, else the newest process of the TCP route whose backend it is.
316func (w *world) portProc(b *box, port string) *proc {
317 for i := len(b.Procs) - 1; i >= 0; i-- {
318 if p := b.Procs[i]; p.OwnPort && ownPort(p) == port {
319 return p
320 }
321 }
322 for _, h := range hostsOf(b) {
323 r := b.Rows[h]
324 if r.Kind != "instance" || r.Platform {
325 continue
326 }
327 if tb, pt := w.addrBox(r.Backend); tb == b && pt == port {
328 return w.newest(b, w.group(b, h))
329 }
330 }
331 return nil
332}
333
334// reach records a connection past the edge reaching process p. INV-P, as in
335// deliver: a public connection never reaches a declaring build, nor a group
336// holding a loaded doc-engine grant.
337func (w *world) reach(b *box, p *proc, public bool, how string) *proc {
338 if p == nil {
339 w.logf("box%d: %s: nothing listens", b.ID, how)
340 return nil
341 }
342 w.logf("box%d: %s reached process %d (%s v%d) past the edge", b.ID, how, p.ID, p.Group, p.Ver)
343 w.events["delivered"]++
344 // a ports listener exists only because the group is public: a path the
345 // upgrade's 0 opens; any other way past the edge is the ingress rule's
346 listener := strings.HasPrefix(how, "the ports listener")
347 if public && w.builds[p.Ver].Declares {
348 w.failProc(b, p, "INV-P", listener, "box%d: a public connection through %s reached process %d of %s running v%d, which declares doc_engine, past the edge", b.ID, how, p.ID, p.Group, p.Ver)
349 } else if g := w.grantLoaded(b, p.Group); public && g != "" {
350 w.failProc(b, p, "INV-P", listener, "box%d: a public connection through %s reached process %d of %s running v%d, whose group holds the doc-engine grant (on %s), past the edge", b.ID, how, p.ID, p.Group, p.Ver, g)
351 }
352 return p
353}
354
355// deliverDirect is the proxy of request r on b whose backend is an address
356// no instance row of b carries: on another box, a direct connection there
357// (the firewall decides); on b itself, the process bound to that port. False
358// when the backend is no such address.
359func (w *world) deliverDirect(b *box, r *req) bool {
360 tb, port := w.addrBox(r.Backend)
361 if tb == nil {
362 return false
363 }
364 w.logf("box%d: request %d proxies to %s, box%d's address", b.ID, r.ID, r.Backend, tb.ID)
365 if p := w.connect(tb, port, r.Public, tb != b); p != nil {
366 r.Reached = p.ID
367 }
368 return true
369}
370
371// ---------- INV-AV ----------
372
373// A proxying row outside the internal group homed on its box is refused by
374// the dedicated-box rule on purpose (spec §5.9): INV-AV does not count it.
375// The row's true group decides, not the edge's resolution.
376func init() {
377 avExemptions = append(avExemptions, func(w *world, b *box, host string) string {
378 r := b.Rows[host]
379 if r == nil || !proxying(r) {
380 return ""
381 }
382 homed := w.edgeHomed(b)
383 if len(homed) == 0 || homed[truthGroup(b, host)] {
384 return ""
385 }
386 return fmt.Sprintf("a proxying row outside %v, the internal group homed on the box: refused by design (§5.9)", sortedKeys(homed))
387 })
388}
389
390// ---------- the random walk ----------
391
392// addressablePorts is every port on b a direct connection can name: TCP
393// routes' backends, ports processes bound on their own, ports listeners.
394func (w *world) addressablePorts(b *box) []string {
395 set := map[string]bool{}
396 for _, h := range hostsOf(b) {
397 if r := b.Rows[h]; r.Kind == "instance" && !r.Platform {
398 if tb, port := w.addrBox(r.Backend); tb == b {
399 set[port] = true
400 }
401 }
402 }
403 for _, p := range b.Procs {
404 if p.OwnPort {
405 set[ownPort(p)] = true
406 }
407 }
408 for port := range b.Listen {
409 set[port] = true
410 }
411 return sortedKeys(set)
412}
413
414// localBackends is every upstream a local service row on b can name: its
415// instance routes' backends and the ports on b's own address.
416func (w *world) localBackends(b *box) []string {
417 set := map[string]bool{"127.0.0.1:1": true} // nothing listens
418 for _, h := range hostsOf(b) {
419 if r := b.Rows[h]; r.Kind == "instance" && !r.Platform && r.Backend != "" {
420 set[r.Backend] = true
421 }
422 }
423 for _, port := range w.addressablePorts(b) {
424 set[boxAddr(b)+":"+port] = true
425 }
426 return sortedKeys(set)
427}
428
429func init() {
430 actions = append(actions,
431 action{name: "add-service-row", weight: 2, run: func(w *world) bool {
432 b := w.pickBox()
433 if len(b.Rows) >= maxRows {
434 return false
435 }
436 w.nextID++
437 host := fmt.Sprintf("svc%d.example", w.nextID)
438 if b.ID == 1 && w.rng.Intn(2) == 0 {
439 // aimed at box 0's address (codex R13-1)
440 ports := append(w.addressablePorts(w.boxes[0]), "1")
441 w.addServiceRow(b, host, boxAddr(w.boxes[0])+":"+ports[w.rng.Intn(len(ports))])
442 return true
443 }
444 bs := w.localBackends(b)
445 w.addServiceRow(b, host, bs[w.rng.Intn(len(bs))])
446 return true
447 }},
448 action{name: "delete-service-row", weight: 2, run: func(w *world) bool {
449 type at struct {
450 b *box
451 host string
452 }
453 var rows []at
454 for _, b := range w.boxes {
455 for _, h := range hostsOf(b) {
456 if b.Rows[h].Kind == "service" {
457 rows = append(rows, at{b, h})
458 }
459 }
460 }
461 if len(rows) == 0 {
462 return false
463 }
464 x := rows[w.rng.Intn(len(rows))]
465 w.deleteRow(x.b, x.host)
466 return true
467 }},
468 action{name: "own-port", weight: 1, run: func(w *world) bool {
469 b := w.pickBox()
470 if len(b.Procs) == 0 {
471 return false
472 }
473 w.bindOwnPort(b, b.Procs[w.rng.Intn(len(b.Procs))])
474 return true
475 }},
476 action{name: "direct-connect", weight: 2, run: func(w *world) bool {
477 b := w.pickBox()
478 ports := append(w.addressablePorts(b), "1")
479 w.directConnect(b, ports[w.rng.Intn(len(ports))], true)
480 return true
481 }},
482 action{name: "ship-ports", weight: 1, run: func(w *world) bool {
483 b := w.pickBox()
484 g := w.pickGroup(b)
485 if g == "" {
486 return false
487 }
488 w.ship(b, g, w.pickVer(), true)
489 return true
490 }},
491 action{name: "caps-pass", weight: 1, run: func(w *world) bool { w.capsPass(w.pickBox()); return true }},
492 action{name: "remove-route", weight: 1, run: func(w *world) bool {
493 // an instance route the walk added (the starting eng and shop stay)
494 b := w.pickBox()
495 var gs []string
496 for _, g := range w.groups(b) {
497 if g != eng && g != "shop" {
498 gs = append(gs, g)
499 }
500 }
501 if len(gs) == 0 {
502 return false
503 }
504 w.removeGroup(b, gs[w.rng.Intn(len(gs))])
505 return true
506 }},
507 )
508}
509