| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 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 | |
| 16 | type seedAbsentJournal struct { |
| 17 | rebootJournalFixture |
| 18 | raw hostbox.LaunchInventory |
| 19 | after func() |
| 20 | } |
| 21 | |
| 22 | func (j *seedAbsentJournal) Read(context.Context, hostguard.FenceTarget) (hostbox.LaunchInventory, error) { |
| 23 | return j.raw, nil |
| 24 | } |
| 25 | func (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 | |
| 33 | func 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 | |