carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/hostguard"
14)
15
16type seedAbsentJournal struct {
17 rebootJournalFixture
18 raw hostbox.LaunchInventory
19 after func()
20}
21
22func (j *seedAbsentJournal) Read(context.Context, hostguard.FenceTarget) (hostbox.LaunchInventory, error) {
23 return j.raw, nil
24}
25func (j *seedAbsentJournal) SettleRecovered(context.Context, hostguard.FenceTarget) error {
26 j.j.Settled = true
27 if j.after != nil {
28 j.after()
29 }
30 return nil
31}
32
33func TestFilesSeedKilledUnlaunchedRecovery(t *testing.T) {
34 for _, fault := range []string{"none", "live-controller", "reused-parent", "reappears", "role", "boot-change", "descriptor", "foreign-parent", "no-journal"} {
35 t.Run(fault, func(t *testing.T) {
36 root, proc := t.TempDir(), t.TempDir()
37 os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0700)
38 os.WriteFile(filepath.Join(proc, "sys/kernel/random/boot_id"), []byte("boot\n"), 0600)
39 target := hostguard.FenceTarget{Host: "seed.example", Incarnation: "inc", Stamp: 11, Session: 13, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "controller", FenceID: "fence"}
40 agent := "/sys/fs/cgroup/system.slice/carlos-edge.service"
41 parent, err := claimContainmentParent(agent, target)
42 if err != nil {
43 t.Fatal(err)
44 }
45 group := hostbox.StopCgroupIdentity{RelativePath: parent, Device: 1, Inode: 2}
46 j := &seedAbsentJournal{rebootJournalFixture: rebootJournalFixture{j: hostbox.StopKernelJournal{V: 1, Target: target, ControllerPID: 42, ProcessStart: "123", Cgroups: []hostbox.StopCgroupIdentity{group}}}, raw: hostbox.LaunchInventory{Target: target, Groups: []hostbox.LaunchGroup{{RelativePath: parent, Device: 1, Inode: 2}}, NoState: true}}
47 state := t.TempDir()
48 k := LinuxStopKernel{AgentCgroup: func(context.Context) (string, error) { return agent, nil }, Journal: j, CgroupRoot: root, ProcRoot: proc, ManagedRoots: map[string]string{"data": state}}
49 switch fault {
50 case "live-controller":
51 os.MkdirAll(filepath.Join(proc, "42"), 0700)
52 os.WriteFile(filepath.Join(proc, "42/stat"), []byte("42 (fixture) S "+strings.Repeat("0 ", 18)+"123 0"), 0600)
53 case "reused-parent":
54 os.MkdirAll(filepath.Join(root, parent), 0700)
55 case "reappears":
56 j.after = func() { os.MkdirAll(filepath.Join(root, parent), 0700) }
57 case "role":
58 j.j.UnitReservationIDs = []string{"main"}
59 case "boot-change":
60 os.WriteFile(filepath.Join(proc, "sys/kernel/random/boot_id"), []byte("other"), 0600)
61 case "descriptor":
62 os.MkdirAll(filepath.Join(proc, "42/fd"), 0700)
63 os.WriteFile(filepath.Join(proc, "42/stat"), []byte("42 (fixture) S "+strings.Repeat("0 ", 18)+"456 0"), 0600)
64 os.Symlink(filepath.Join(state, "data.db")+" (deleted)", filepath.Join(proc, "42/fd/3"))
65 case "foreign-parent":
66 j.j.Cgroups[0].RelativePath = "other/group"
67 case "no-journal":
68 k.Journal = &j.rebootJournalFixture
69 }
70 in := StopInventory{Complete: true, Entries: []hostbox.StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: "data.db"}}}
71 evidence, _, err := k.recoverUnlaunchedAbsent(t.Context(), target, in, j.j, j)
72 if fault == "none" {
73 if err != nil || !j.j.Settled || evidence.ObservedBootID != "boot" {
74 t.Fatal(err, evidence)
75 }
76 return
77 }
78 if err == nil {
79 t.Fatal("unsafe recovery accepted", fault)
80 }
81 if fault != "reappears" && j.j.Settled {
82 t.Fatal("refusal settled journal")
83 }
84 })
85 }
86}
87