carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// `carlos status --layout` (isolated-exec design §8.4, plan 4 task 5): which
2// layout each instance is on, box by box — the operator's console-backed
3// view of the layout pass's own on-box classification (internal/layout's
4// Pass.Classify), rather than a bucket walk with bucket credentials.
5//
6// GATE: the identical posture handleAPIStatusInstances already has
7// (apistatusinstances.go): cliUser, then s.store.IsOperator, then a bare
8// byte-identical 404 for anyone else. Registered through cliRoute with an
9// EMPTY role set — this is an operator's view across the whole fleet, not a
10// workload's, and no service credential of any role reaches it.
11package console
12
13import (
14 "log"
15 "net/http"
16 "sort"
17 "time"
18
19 "github.com/carlosframework/platform/internal/status"
20)
21
22// apiStatusLayoutRow is one instance-kind route's layout, as the box's own
23// pass classified it — see status.RowLayout, which this mirrors on the
24// wire.
25type apiStatusLayoutRow struct {
26 Host string `json:"host"`
27 Layout string `json:"layout"`
28 Reason string `json:"reason,omitempty"`
29 // Note is the pass's last non-refusal event for the host (what a move
30 // quarantined or kept aside). Additive; an old CLI ignores it.
31 Note string `json:"note,omitempty"`
32}
33
34// apiStatusLayoutBox is one box's layout report: the mode it last converged
35// to, whether its snapshot is stale (status.BoxStale — the same rule every
36// other private reader of status/boxes/ uses), and its rows.
37type apiStatusLayoutBox struct {
38 Box string `json:"box"`
39 Mode string `json:"mode"`
40 Stale bool `json:"stale"`
41 Rows []apiStatusLayoutRow `json:"rows"`
42 // Mixed is set when the box has a flat, foreign or external row
43 // beside a nested one, or under a nested intent. Each of those runs as the agent's uid in the
44 // agent's mount namespace, so it reaches every nested sibling's raw
45 // directory: the nested rows are not isolated until none remains on
46 // the box. Additive; an old CLI ignores it.
47 Mixed bool `json:"mixed,omitempty"`
48 // Default is set when the box is nested by the deployment's default
49 // rather than a recorded intent; DefaultBlocked is why the default is
50 // not in force on a box whose mode is none (design §8.6). Additive.
51 Default bool `json:"default,omitempty"`
52 DefaultBlocked string `json:"default_blocked,omitempty"`
53}
54
55// layoutMixed is the Mixed rule: a row that runs as the agent's uid with
56// no namespace, which is a flat row, a foreign one (paths the pass leaves
57// alone, spawned the flat way) or an external one (another supervisor's
58// process, also uid carlos), on a box that is nested, either by a nested
59// row beside it or by the box's intent (mode) being nested. The second
60// half (plan 5) keeps the warning loud for a box that has gone nested but
61// whose only remaining rows are the ones the pass cannot move: with no
62// nested row left to stand beside, they would otherwise read as clean.
63// Fenced and conflict rows are stopped and count for neither side.
64func layoutMixed(mode string, rows []apiStatusLayoutRow) bool {
65 nested, unisolated := mode == "nested", false
66 for _, r := range rows {
67 switch r.Layout {
68 case "nested":
69 nested = true
70 case "flat", "foreign", "external":
71 unisolated = true
72 }
73 }
74 return nested && unisolated
75}
76
77func (s *Server) handleAPIStatusLayout(w http.ResponseWriter, r *http.Request) {
78 addr, ok := s.cliUser(w, r)
79 if !ok {
80 return
81 }
82 isOp, err := s.store.IsOperator(addr)
83 if err != nil {
84 log.Printf("console: api status layout: is-operator: %v", err)
85 http.NotFound(w, r)
86 return
87 }
88 if !isOp {
89 http.NotFound(w, r)
90 return
91 }
92 // systemBoxes is systemapply's own reader (apisystemupdate.go): the
93 // platform's own status.LoadBoxes plus every customer fleet's boxes
94 // under fleets/<name>/status/boxes/ — the same union carlos system
95 // status already answers with, rather than a second, narrower one that
96 // would miss fleet boxes (finding S14).
97 boxes, _, err := s.systemBoxes(r.Context(), false)
98 if err != nil {
99 log.Printf("console: api status layout: boxes: %v", err)
100 jsonError(w, http.StatusBadGateway, "could not read the fleet's box snapshots — try again")
101 return
102 }
103 now := time.Now()
104 out := make([]apiStatusLayoutBox, 0, len(boxes))
105 for _, b := range boxes {
106 if b.Metrics == nil || b.Metrics.Layout == nil {
107 // A box that predates layout reporting, or has no layout pass
108 // armed yet (no activator running): nothing to add.
109 continue
110 }
111 rows := make([]apiStatusLayoutRow, 0, len(b.Metrics.Layout.Rows))
112 for host, rl := range b.Metrics.Layout.Rows {
113 rows = append(rows, apiStatusLayoutRow{Host: host, Layout: rl.Layout, Reason: rl.Reason, Note: rl.Note})
114 }
115 sort.Slice(rows, func(i, j int) bool { return rows[i].Host < rows[j].Host })
116 out = append(out, apiStatusLayoutBox{
117 Box: b.Label,
118 Mode: b.Metrics.Layout.Mode,
119 Stale: status.BoxStale(b, now, status.SnapshotPeriod),
120 Rows: rows,
121 Mixed: layoutMixed(b.Metrics.Layout.Mode, rows),
122 Default: b.Metrics.Layout.Default,
123 DefaultBlocked: b.Metrics.Layout.DefaultBlocked,
124 })
125 }
126 sort.Slice(out, func(i, j int) bool { return out[i].Box < out[j].Box })
127 writeJSON(w, http.StatusOK, map[string]any{"boxes": out})
128}
129