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 "os"
5 "path/filepath"
6 "testing"
7)
8
9// TestListCarlosUnitsFindsFlatAndNestedUnits pins the cgroup layout systemd
10// actually produces: the platform's fixed units sit directly under
11// system.slice, but an INSTANTIATED unit (carlos-app@<host>.service, and
12// internal/host/units/carlos-app@.service sets no Slice=) is placed by
13// systemd inside an implicit per-template slice —
14// system.slice/system-carlos\x2dapp.slice/carlos-app@<host>.service. Reading
15// only the top level would find every platform unit and not one tenant, which
16// is exactly the set the per-unit tables and owner metrics exist for. A
17// non-CARLOS directory at either level must stay invisible.
18func TestListCarlosUnitsFindsFlatAndNestedUnits(t *testing.T) {
19 root := t.TempDir()
20 mk := func(parts ...string) string {
21 t.Helper()
22 dir := filepath.Join(append([]string{root}, parts...)...)
23 if err := os.MkdirAll(dir, 0o755); err != nil {
24 t.Fatal(err)
25 }
26 return dir
27 }
28
29 flatDir := mk("carlos-edge.service")
30 nestedDir := mk(`system-carlos\x2dapp.slice`, "carlos-app@x.example.com.service")
31 // Decoys: a non-CARLOS unit at each level, and a nested slice that holds
32 // nothing of ours.
33 mk("sshd.service")
34 mk(`system-carlos\x2dapp.slice`, "sshd.service")
35 mk("system-getty.slice", "getty@tty1.service")
36
37 got := listCarlosUnits(root)
38 dirs := map[string]string{}
39 for _, u := range got {
40 if _, dup := dirs[u.name]; dup {
41 t.Fatalf("unit %q discovered twice: %+v", u.name, got)
42 }
43 dirs[u.name] = u.dir
44 }
45
46 if len(got) != 2 {
47 t.Fatalf("discovered %d units, want 2 (one flat, one nested): %+v", len(got), got)
48 }
49 if dirs["carlos-edge.service"] != flatDir {
50 t.Errorf("carlos-edge.service dir = %q, want %q", dirs["carlos-edge.service"], flatDir)
51 }
52 if dirs["carlos-app@x.example.com.service"] != nestedDir {
53 t.Errorf("carlos-app@x.example.com.service dir = %q, want %q (the per-template slice)", dirs["carlos-app@x.example.com.service"], nestedDir)
54 }
55}
56
57// TestListStampedUnits pins the second discovery walk: units named by the
58// registry's Unit column (#110) rather than by the carlos- convention. The
59// same two systemd levels are in play — a bespoke flat service, and a
60// template instance inside its implicit per-template slice — because the
61// stamped name says nothing about where systemd put its cgroup. A unit
62// present on disk but NOT stamped stays invisible (the convention walk owns
63// what it owns), and a stamped unit with no cgroup dir yields nothing at all:
64// absence is the honest report for a unit that isn't running.
65func TestListStampedUnits(t *testing.T) {
66 root := t.TempDir()
67 mk := func(parts ...string) string {
68 t.Helper()
69 dir := filepath.Join(append([]string{root}, parts...)...)
70 if err := os.MkdirAll(dir, 0o755); err != nil {
71 t.Fatal(err)
72 }
73 return dir
74 }
75
76 // Flat bespoke unit.
77 flatDir := mk("console.service")
78 // Template instance inside its implicit per-template slice. The literal
79 // directory name carries systemd's \x2d escaping of the dash.
80 nestedDir := mk(`system-titogo\x2dcanary.slice`, "titogo-canary@demo.service")
81 // Present on disk but NOT stamped — must not be returned.
82 mk("postgres.service")
83
84 got := listStampedUnits(root, []string{"console.service", "titogo-canary@demo.service", "absent@x.service"})
85 want := map[string]string{"console.service": flatDir, "titogo-canary@demo.service": nestedDir}
86 if len(got) != 2 {
87 t.Fatalf("got %d units, want 2 (the stamped ones that exist): %+v", len(got), got)
88 }
89 for _, u := range got {
90 dir, ok := want[u.name]
91 if !ok {
92 t.Fatalf("unexpected unit %q", u.name)
93 }
94 if u.dir != dir {
95 t.Fatalf("unit %q dir = %q, want %q (the real cgroup dir)", u.name, u.dir, dir)
96 }
97 if _, err := os.Stat(u.dir); err != nil {
98 t.Fatalf("dir %q not readable: %v", u.dir, err)
99 }
100 }
101
102 if out := listStampedUnits(root, nil); out != nil {
103 t.Fatalf("no stamped units must scan nothing, got %+v", out)
104 }
105}
106
107// TestReadUnitRawReadsFromUnitDir pins that a unit's cgroup files are read
108// from the directory listCarlosUnits reported for it — the nested case, since
109// a reader that rebuilt the path as <root>/<unit> would find nothing there.
110func TestReadUnitRawReadsFromUnitDir(t *testing.T) {
111 root := t.TempDir()
112 dir := filepath.Join(root, `system-carlos\x2dapp.slice`, "carlos-app@x.example.com.service")
113 if err := os.MkdirAll(dir, 0o755); err != nil {
114 t.Fatal(err)
115 }
116 write := func(name, body string) {
117 t.Helper()
118 if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
119 t.Fatal(err)
120 }
121 }
122 write("cpu.stat", "usage_usec 12345\nuser_usec 1\n")
123 write("memory.current", "4096\n")
124 // memory.peak and io.stat deliberately absent: each read fails on its own.
125
126 units := listCarlosUnits(root)
127 if len(units) != 1 {
128 t.Fatalf("discovered %d units, want 1: %+v", len(units), units)
129 }
130 raw := readUnitRaw(units[0].dir)
131 if raw.cpuUsec == nil || *raw.cpuUsec != 12345 {
132 t.Errorf("cpuUsec = %v, want 12345", raw.cpuUsec)
133 }
134 if raw.memBytes == nil || *raw.memBytes != 4096 {
135 t.Errorf("memBytes = %v, want 4096", raw.memBytes)
136 }
137 if raw.memPeakBytes != nil || raw.readBytes != nil || raw.writeBytes != nil {
138 t.Errorf("absent files must stay nil, got peak=%v r=%v w=%v", raw.memPeakBytes, raw.readBytes, raw.writeBytes)
139 }
140}
141
142// TestListExecCgroupsFindsPerHostDirs pins the delegated subtree's scan:
143// carlos-exec@<host> dirs with live members come out with their names
144// VERBATIM as sample keys (deliberately not dressed up as .service units —
145// an operator who takes a dashboard row to systemctl must not be told a
146// unit exists that doesn't). The agent's own supervise leaf and foreign
147// names are invisible; so is a MEMBER-LESS dir (empty or missing
148// cgroup.procs) — a failed seed's leftover must read as absent, never as a
149// live instance at zero. An empty root — the no-delegation state — scans
150// nothing.
151func TestListExecCgroupsFindsPerHostDirs(t *testing.T) {
152 root := t.TempDir()
153 mk := func(name, procs string) string {
154 t.Helper()
155 dir := filepath.Join(root, name)
156 if err := os.MkdirAll(dir, 0o755); err != nil {
157 t.Fatal(err)
158 }
159 if procs != "" {
160 if err := os.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(procs), 0o644); err != nil {
161 t.Fatal(err)
162 }
163 }
164 return dir
165 }
166 execDir := mk("carlos-exec@keymail.dev", "1234\n")
167 mk("supervise", "1\n")
168 mk("carlos-exec@", "99\n") // no host — not one of ours
169 mk("random-dir", "77\n") // foreign name
170 mk("carlos-exec@failed.example.com", "\n") // member-less: whitespace-only procs
171 mk("carlos-exec@aborted.example.com", "") // member-less: no procs file at all
172
173 got := listExecCgroups(root)
174 if len(got) != 1 {
175 t.Fatalf("discovered %d cgroups, want 1: %+v", len(got), got)
176 }
177 if got[0].name != "carlos-exec@keymail.dev" || got[0].dir != execDir {
178 t.Fatalf("got %+v, want name carlos-exec@keymail.dev dir %s", got[0], execDir)
179 }
180
181 if out := listExecCgroups(""); out != nil {
182 t.Fatalf("empty root must scan nothing, got %+v", out)
183 }
184}
185
186// TestListCarlosUnitsFindsUnitsUnderCarlosSlice pins the review fix: once
187// carlos-edge.service moved into carlos.slice (isolated-exec design §4.3),
188// realUnitReadings must scan cgroupCarlosSliceDir as a second root or the
189// edge's metrics row silently vanishes. listCarlosUnits itself is root-
190// agnostic (it takes root as an argument precisely so callers can point it
191// at either slice), so this pins that a fixture shaped like the real
192// carlos.slice — a flat carlos-edge.service leaf holding memory.current, the
193// same file readUnitRaw actually reads — is discovered from that second
194// root, and that scanning it does not also resurface anything from a
195// system.slice-shaped root scanned separately.
196func TestListCarlosUnitsFindsUnitsUnderCarlosSlice(t *testing.T) {
197 root := t.TempDir()
198 carlosSlice := filepath.Join(root, "carlos.slice")
199 edgeDir := filepath.Join(carlosSlice, "carlos-edge.service")
200 if err := os.MkdirAll(edgeDir, 0o755); err != nil {
201 t.Fatal(err)
202 }
203 if err := os.WriteFile(filepath.Join(edgeDir, "memory.current"), []byte("12345\n"), 0o644); err != nil {
204 t.Fatal(err)
205 }
206
207 got := listCarlosUnits(carlosSlice)
208 if len(got) != 1 {
209 t.Fatalf("discovered %d units under carlos.slice, want 1: %+v", len(got), got)
210 }
211 if got[0].name != "carlos-edge.service" || got[0].dir != edgeDir {
212 t.Fatalf("got %+v, want name carlos-edge.service dir %s", got[0], edgeDir)
213 }
214
215 // A system.slice-shaped root scanned on its own must not also produce
216 // the edge: the two roots are independent, so an edge found only under
217 // carlos.slice must not be double-reported when both scans are combined
218 // the way realUnitReadings combines them.
219 systemSlice := filepath.Join(root, "system.slice")
220 if err := os.MkdirAll(systemSlice, 0o755); err != nil {
221 t.Fatal(err)
222 }
223 systemGot := listCarlosUnits(systemSlice)
224 if len(systemGot) != 0 {
225 t.Fatalf("system.slice root found %+v, want none: the edge only lives under carlos.slice now", systemGot)
226 }
227
228 combined := append(append([]unitCgroup{}, systemGot...), got...)
229 seen := map[string]bool{}
230 for _, u := range combined {
231 if seen[u.name] {
232 t.Fatalf("unit %q double-reported when combining both scan roots: %+v", u.name, combined)
233 }
234 seen[u.name] = true
235 }
236 if !seen["carlos-edge.service"] {
237 t.Fatalf("carlos-edge.service missing from the combined scan: %+v", combined)
238 }
239}
240
241// TestDBFileBytes: the main file plus its -wal, nil (unknown) for a database
242// that does not exist, and the main file alone once a checkpoint has removed
243// the WAL.
244func TestDBFileBytes(t *testing.T) {
245 dir := t.TempDir()
246 db := filepath.Join(dir, "h.test.db")
247 if got := dbFileBytes(db); got != nil {
248 t.Fatalf("missing db = %d, want nil", *got)
249 }
250 if err := os.WriteFile(db, make([]byte, 4096), 0o600); err != nil {
251 t.Fatal(err)
252 }
253 if got := dbFileBytes(db); got == nil || *got != 4096 {
254 t.Fatalf("db alone = %v, want 4096", got)
255 }
256 if err := os.WriteFile(db+"-wal", make([]byte, 1000), 0o600); err != nil {
257 t.Fatal(err)
258 }
259 if got := dbFileBytes(db); got == nil || *got != 5096 {
260 t.Fatalf("db + wal = %v, want 5096", got)
261 }
262 if got := dbFileBytes(dir); got != nil {
263 t.Fatalf("a directory = %d, want nil", *got)
264 }
265}
266
267// A generation parent (zero-downtime spec §7.3) holds no process of its
268// own, so its cgroup.procs is empty while its generations run. It must
269// still be sampled, under its own name: its memory, cpu, io and pids
270// files already count the whole subtree.
271func TestListExecCgroupsCountsAGenerationParent(t *testing.T) {
272 root := t.TempDir()
273 mk := func(rel, file, body string) {
274 t.Helper()
275 dir := filepath.Join(root, rel)
276 if err := os.MkdirAll(dir, 0o755); err != nil {
277 t.Fatal(err)
278 }
279 if file != "" {
280 if err := os.WriteFile(filepath.Join(dir, file), []byte(body), 0o644); err != nil {
281 t.Fatal(err)
282 }
283 }
284 }
285 mk("carlos-exec@flat.example.com", "cgroup.procs", "1234\n")
286 mk("carlos-exec@gens.example.com", "cgroup.procs", "")
287 mk("carlos-exec@gens.example.com", "cgroup.events", "populated 1\nfrozen 0\n")
288 mk("carlos-exec@gens.example.com/g7", "cgroup.procs", "5678\n")
289 mk("carlos-exec@empty.example.com", "cgroup.procs", "")
290 mk("carlos-exec@empty.example.com", "cgroup.events", "populated 0\nfrozen 0\n")
291
292 got := listExecCgroups(root)
293 names := map[string]bool{}
294 for _, u := range got {
295 names[u.name] = true
296 }
297 if len(got) != 2 || !names["carlos-exec@flat.example.com"] || !names["carlos-exec@gens.example.com"] {
298 t.Fatalf("sampled %+v, want the flat host and the populated generation parent", got)
299 }
300}
301