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 cgroup
4
5import (
6 "errors"
7 "os"
8 "path/filepath"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/hostbox"
12 "github.com/carlosframework/platform/internal/hostguard"
13)
14
15// The fixture has no kernel-generated control files. Preparation therefore
16// fails after the actual mkdir and before a launch reservation can be written.
17func TestLaunchChildFailureBeforeReservationSettlesAndRetries(t *testing.T) {
18 ctx := t.Context()
19 p := strictFixture(t)
20 journalPath := filepath.Join(t.TempDir(), "launch.db")
21 j, err := hostbox.OpenLaunchJournal(journalPath)
22 if err != nil {
23 t.Fatal(err)
24 }
25 defer func() { j.Close() }()
26 target := hostguard.FenceTarget{Host: p.host, Incarnation: "inc", Stamp: 1, Session: 1, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "ctrl"}
27 parent := hostbox.LaunchGroup{RelativePath: ExecName(p.host), Device: p.identity.device, Inode: p.identity.inode}
28 if err := j.Prelaunch(ctx, hostbox.LaunchInventory{Target: target, ControllerPID: 42, ProcessStart: "start", Groups: []hostbox.LaunchGroup{parent}, NoState: true}); err != nil {
29 t.Fatal(err)
30 }
31 reservation := func(name string) hostbox.LaunchReservation {
32 return hostbox.LaunchReservation{Target: target, ID: name, Role: "main", InvocationID: name, Inputs: "inputs", Parent: parent, Group: hostbox.LaunchGroup{RelativePath: filepath.Join(parent.RelativePath, "role-"+name), InvocationID: name}, Executable: hostbox.LaunchFile{Device: 1, Inode: 2}}
33 }
34 r := reservation("first")
35 childPath := filepath.Join(p.root.Name(), r.Group.RelativePath)
36 called := false
37 child, err := p.PrepareRoleChildWithIntent("first", func(id ContainmentIdentity) error {
38 called = true
39 if id.Device != parent.Device || id.Inode != parent.Inode {
40 t.Fatal("wrong parent")
41 }
42 if _, err := os.Lstat(childPath); !errors.Is(err, os.ErrNotExist) {
43 t.Fatal("child exists before intention")
44 }
45 return j.IntendRole(ctx, r)
46 })
47 if err == nil || child != nil || !called {
48 t.Fatalf("child=%v intent=%v err=%v", child, called, err)
49 }
50 if _, err := os.Stat(childPath); err != nil {
51 t.Fatal("failure did not reach mkdir", err)
52 }
53 // Discard the writer's connection to model a crash before reservation.
54 if err := j.Close(); err != nil {
55 t.Fatal(err)
56 }
57 j, err = hostbox.OpenLaunchJournal(journalPath)
58 if err != nil {
59 t.Fatal(err)
60 }
61 stop, err := j.ReadStopKernelJournal(ctx, target)
62 if err != nil || !stop.PendingRoles || stop.Settled {
63 t.Fatalf("lost intention: %+v %v", stop, err)
64 }
65 if err := j.SettleRecovered(ctx, target); err == nil {
66 t.Fatal("blind settlement accepted intended child")
67 }
68 // Kernel files are automatic on cgroupfs; supply the observation in this
69 // filesystem fixture. Root requires emptiness before resolving the intention.
70 if err := os.WriteFile(filepath.Join(childPath, "cgroup.events"), []byte("populated 1\n"), 0600); err != nil {
71 t.Fatal(err)
72 }
73 if err := j.ResolveRoleIntentions(ctx, target, p.root.Name(), false); err == nil {
74 t.Fatal("populated child accepted")
75 }
76 if err := os.WriteFile(filepath.Join(childPath, "cgroup.events"), []byte("populated 0\n"), 0600); err != nil {
77 t.Fatal(err)
78 }
79 if err := j.ResolveRoleIntentions(ctx, target, p.root.Name(), false); err != nil {
80 t.Fatal(err)
81 }
82 stop, err = j.ReadStopKernelJournal(ctx, target)
83 if err != nil || stop.PendingRoles || !stop.Settled || len(stop.Cgroups) != 2 {
84 t.Fatalf("unsettled child: %+v %v", stop, err)
85 }
86 // Retry retains the first attempt and uses a new child and invocation.
87 next := reservation("second")
88 if err := j.IntendRole(ctx, next); err != nil {
89 t.Fatal(err)
90 }
91 nextPath := filepath.Join(p.root.Name(), next.Group.RelativePath)
92 if err := os.Mkdir(nextPath, 0700); err != nil {
93 t.Fatal(err)
94 }
95 f, err := os.Open(nextPath)
96 if err != nil {
97 t.Fatal(err)
98 }
99 id, err := strictID(f)
100 f.Close()
101 if err != nil {
102 t.Fatal(err)
103 }
104 next.Group.Device, next.Group.Inode = id.device, id.inode
105 if err := j.Reserve(ctx, next); err != nil {
106 t.Fatal("retry reservation failed", err)
107 }
108 in, err := j.Read(ctx, target)
109 if err != nil || len(in.Reservations) != 2 || in.Reservations[0].State != "settled" || in.Reservations[1].State != "reserved" {
110 t.Fatalf("retry overwrote spent attempt: %+v %v", in, err)
111 }
112}
113
114func TestLaunchChildIntentionFailureCreatesNothing(t *testing.T) {
115 p := strictFixture(t)
116 failure := errors.New("journal unavailable")
117 _, err := p.PrepareRoleChildWithIntent("first", func(ContainmentIdentity) error { return failure })
118 if !errors.Is(err, failure) {
119 t.Fatal(err)
120 }
121 if _, err := os.Stat(filepath.Join(p.dir.Name(), "role-first")); !errors.Is(err, os.ErrNotExist) {
122 t.Fatalf("child created before durable intention: %v", err)
123 }
124}
125