package host import ( "os" "path/filepath" "testing" ) // TestListCarlosUnitsFindsFlatAndNestedUnits pins the cgroup layout systemd // actually produces: the platform's fixed units sit directly under // system.slice, but an INSTANTIATED unit (carlos-app@.service, and // internal/host/units/carlos-app@.service sets no Slice=) is placed by // systemd inside an implicit per-template slice — // system.slice/system-carlos\x2dapp.slice/carlos-app@.service. Reading // only the top level would find every platform unit and not one tenant, which // is exactly the set the per-unit tables and owner metrics exist for. A // non-CARLOS directory at either level must stay invisible. func TestListCarlosUnitsFindsFlatAndNestedUnits(t *testing.T) { root := t.TempDir() mk := func(parts ...string) string { t.Helper() dir := filepath.Join(append([]string{root}, parts...)...) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } return dir } flatDir := mk("carlos-edge.service") nestedDir := mk(`system-carlos\x2dapp.slice`, "carlos-app@x.example.com.service") // Decoys: a non-CARLOS unit at each level, and a nested slice that holds // nothing of ours. mk("sshd.service") mk(`system-carlos\x2dapp.slice`, "sshd.service") mk("system-getty.slice", "getty@tty1.service") got := listCarlosUnits(root) dirs := map[string]string{} for _, u := range got { if _, dup := dirs[u.name]; dup { t.Fatalf("unit %q discovered twice: %+v", u.name, got) } dirs[u.name] = u.dir } if len(got) != 2 { t.Fatalf("discovered %d units, want 2 (one flat, one nested): %+v", len(got), got) } if dirs["carlos-edge.service"] != flatDir { t.Errorf("carlos-edge.service dir = %q, want %q", dirs["carlos-edge.service"], flatDir) } if dirs["carlos-app@x.example.com.service"] != nestedDir { t.Errorf("carlos-app@x.example.com.service dir = %q, want %q (the per-template slice)", dirs["carlos-app@x.example.com.service"], nestedDir) } } // TestListStampedUnits pins the second discovery walk: units named by the // registry's Unit column (#110) rather than by the carlos- convention. The // same two systemd levels are in play — a bespoke flat service, and a // template instance inside its implicit per-template slice — because the // stamped name says nothing about where systemd put its cgroup. A unit // present on disk but NOT stamped stays invisible (the convention walk owns // what it owns), and a stamped unit with no cgroup dir yields nothing at all: // absence is the honest report for a unit that isn't running. func TestListStampedUnits(t *testing.T) { root := t.TempDir() mk := func(parts ...string) string { t.Helper() dir := filepath.Join(append([]string{root}, parts...)...) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } return dir } // Flat bespoke unit. flatDir := mk("console.service") // Template instance inside its implicit per-template slice. The literal // directory name carries systemd's \x2d escaping of the dash. nestedDir := mk(`system-titogo\x2dcanary.slice`, "titogo-canary@demo.service") // Present on disk but NOT stamped — must not be returned. mk("postgres.service") got := listStampedUnits(root, []string{"console.service", "titogo-canary@demo.service", "absent@x.service"}) want := map[string]string{"console.service": flatDir, "titogo-canary@demo.service": nestedDir} if len(got) != 2 { t.Fatalf("got %d units, want 2 (the stamped ones that exist): %+v", len(got), got) } for _, u := range got { dir, ok := want[u.name] if !ok { t.Fatalf("unexpected unit %q", u.name) } if u.dir != dir { t.Fatalf("unit %q dir = %q, want %q (the real cgroup dir)", u.name, u.dir, dir) } if _, err := os.Stat(u.dir); err != nil { t.Fatalf("dir %q not readable: %v", u.dir, err) } } if out := listStampedUnits(root, nil); out != nil { t.Fatalf("no stamped units must scan nothing, got %+v", out) } } // TestReadUnitRawReadsFromUnitDir pins that a unit's cgroup files are read // from the directory listCarlosUnits reported for it — the nested case, since // a reader that rebuilt the path as / would find nothing there. func TestReadUnitRawReadsFromUnitDir(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, `system-carlos\x2dapp.slice`, "carlos-app@x.example.com.service") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } write := func(name, body string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil { t.Fatal(err) } } write("cpu.stat", "usage_usec 12345\nuser_usec 1\n") write("memory.current", "4096\n") // memory.peak and io.stat deliberately absent: each read fails on its own. units := listCarlosUnits(root) if len(units) != 1 { t.Fatalf("discovered %d units, want 1: %+v", len(units), units) } raw := readUnitRaw(units[0].dir) if raw.cpuUsec == nil || *raw.cpuUsec != 12345 { t.Errorf("cpuUsec = %v, want 12345", raw.cpuUsec) } if raw.memBytes == nil || *raw.memBytes != 4096 { t.Errorf("memBytes = %v, want 4096", raw.memBytes) } if raw.memPeakBytes != nil || raw.readBytes != nil || raw.writeBytes != nil { t.Errorf("absent files must stay nil, got peak=%v r=%v w=%v", raw.memPeakBytes, raw.readBytes, raw.writeBytes) } } // TestListExecCgroupsFindsPerHostDirs pins the delegated subtree's scan: // carlos-exec@ dirs with live members come out with their names // VERBATIM as sample keys (deliberately not dressed up as .service units — // an operator who takes a dashboard row to systemctl must not be told a // unit exists that doesn't). The agent's own supervise leaf and foreign // names are invisible; so is a MEMBER-LESS dir (empty or missing // cgroup.procs) — a failed seed's leftover must read as absent, never as a // live instance at zero. An empty root — the no-delegation state — scans // nothing. func TestListExecCgroupsFindsPerHostDirs(t *testing.T) { root := t.TempDir() mk := func(name, procs string) string { t.Helper() dir := filepath.Join(root, name) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if procs != "" { if err := os.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(procs), 0o644); err != nil { t.Fatal(err) } } return dir } execDir := mk("carlos-exec@keymail.dev", "1234\n") mk("supervise", "1\n") mk("carlos-exec@", "99\n") // no host — not one of ours mk("random-dir", "77\n") // foreign name mk("carlos-exec@failed.example.com", "\n") // member-less: whitespace-only procs mk("carlos-exec@aborted.example.com", "") // member-less: no procs file at all got := listExecCgroups(root) if len(got) != 1 { t.Fatalf("discovered %d cgroups, want 1: %+v", len(got), got) } if got[0].name != "carlos-exec@keymail.dev" || got[0].dir != execDir { t.Fatalf("got %+v, want name carlos-exec@keymail.dev dir %s", got[0], execDir) } if out := listExecCgroups(""); out != nil { t.Fatalf("empty root must scan nothing, got %+v", out) } } // TestListCarlosUnitsFindsUnitsUnderCarlosSlice pins the review fix: once // carlos-edge.service moved into carlos.slice (isolated-exec design §4.3), // realUnitReadings must scan cgroupCarlosSliceDir as a second root or the // edge's metrics row silently vanishes. listCarlosUnits itself is root- // agnostic (it takes root as an argument precisely so callers can point it // at either slice), so this pins that a fixture shaped like the real // carlos.slice — a flat carlos-edge.service leaf holding memory.current, the // same file readUnitRaw actually reads — is discovered from that second // root, and that scanning it does not also resurface anything from a // system.slice-shaped root scanned separately. func TestListCarlosUnitsFindsUnitsUnderCarlosSlice(t *testing.T) { root := t.TempDir() carlosSlice := filepath.Join(root, "carlos.slice") edgeDir := filepath.Join(carlosSlice, "carlos-edge.service") if err := os.MkdirAll(edgeDir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(edgeDir, "memory.current"), []byte("12345\n"), 0o644); err != nil { t.Fatal(err) } got := listCarlosUnits(carlosSlice) if len(got) != 1 { t.Fatalf("discovered %d units under carlos.slice, want 1: %+v", len(got), got) } if got[0].name != "carlos-edge.service" || got[0].dir != edgeDir { t.Fatalf("got %+v, want name carlos-edge.service dir %s", got[0], edgeDir) } // A system.slice-shaped root scanned on its own must not also produce // the edge: the two roots are independent, so an edge found only under // carlos.slice must not be double-reported when both scans are combined // the way realUnitReadings combines them. systemSlice := filepath.Join(root, "system.slice") if err := os.MkdirAll(systemSlice, 0o755); err != nil { t.Fatal(err) } systemGot := listCarlosUnits(systemSlice) if len(systemGot) != 0 { t.Fatalf("system.slice root found %+v, want none: the edge only lives under carlos.slice now", systemGot) } combined := append(append([]unitCgroup{}, systemGot...), got...) seen := map[string]bool{} for _, u := range combined { if seen[u.name] { t.Fatalf("unit %q double-reported when combining both scan roots: %+v", u.name, combined) } seen[u.name] = true } if !seen["carlos-edge.service"] { t.Fatalf("carlos-edge.service missing from the combined scan: %+v", combined) } } // TestDBFileBytes: the main file plus its -wal, nil (unknown) for a database // that does not exist, and the main file alone once a checkpoint has removed // the WAL. func TestDBFileBytes(t *testing.T) { dir := t.TempDir() db := filepath.Join(dir, "h.test.db") if got := dbFileBytes(db); got != nil { t.Fatalf("missing db = %d, want nil", *got) } if err := os.WriteFile(db, make([]byte, 4096), 0o600); err != nil { t.Fatal(err) } if got := dbFileBytes(db); got == nil || *got != 4096 { t.Fatalf("db alone = %v, want 4096", got) } if err := os.WriteFile(db+"-wal", make([]byte, 1000), 0o600); err != nil { t.Fatal(err) } if got := dbFileBytes(db); got == nil || *got != 5096 { t.Fatalf("db + wal = %v, want 5096", got) } if got := dbFileBytes(dir); got != nil { t.Fatalf("a directory = %d, want nil", *got) } } // A generation parent (zero-downtime spec §7.3) holds no process of its // own, so its cgroup.procs is empty while its generations run. It must // still be sampled, under its own name: its memory, cpu, io and pids // files already count the whole subtree. func TestListExecCgroupsCountsAGenerationParent(t *testing.T) { root := t.TempDir() mk := func(rel, file, body string) { t.Helper() dir := filepath.Join(root, rel) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if file != "" { if err := os.WriteFile(filepath.Join(dir, file), []byte(body), 0o644); err != nil { t.Fatal(err) } } } mk("carlos-exec@flat.example.com", "cgroup.procs", "1234\n") mk("carlos-exec@gens.example.com", "cgroup.procs", "") mk("carlos-exec@gens.example.com", "cgroup.events", "populated 1\nfrozen 0\n") mk("carlos-exec@gens.example.com/g7", "cgroup.procs", "5678\n") mk("carlos-exec@empty.example.com", "cgroup.procs", "") mk("carlos-exec@empty.example.com", "cgroup.events", "populated 0\nfrozen 0\n") got := listExecCgroups(root) names := map[string]bool{} for _, u := range got { names[u.name] = true } if len(got) != 2 || !names["carlos-exec@flat.example.com"] || !names["carlos-exec@gens.example.com"] { t.Fatalf("sampled %+v, want the flat host and the populated generation parent", got) } }