carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "path/filepath"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/hostguard"
9)
10
11func TestLaunchJournalDurableExactReservations(t *testing.T) {
12 ctx := context.Background()
13 file := filepath.Join(t.TempDir(), "launch.db")
14 j, err := OpenLaunchJournal(file)
15 if err != nil {
16 t.Fatal(err)
17 }
18 target := hostguard.FenceTarget{Host: "app.example.test", Incarnation: "inc", Stamp: 1, Session: 1, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "controller"}
19 if err = j.Create(ctx, LaunchInventory{ControllerPID: 123, ProcessStart: "456", Target: target, Groups: []LaunchGroup{{RelativePath: "host", Device: 1, Inode: 1}}, NoState: true}); err != nil {
20 t.Fatal(err)
21 }
22 reservation := LaunchReservation{ID: "launch", Role: "main", InvocationID: "invocation", Inputs: "inputs", Target: target, Group: LaunchGroup{RelativePath: "host/main", Device: 1, Inode: 2, InvocationID: "invocation"}, Executable: LaunchFile{Device: 1, Inode: 3}}
23 if err = j.Reserve(ctx, reservation); err != nil {
24 t.Fatal(err)
25 }
26 if err = j.Close(); err != nil {
27 t.Fatal(err)
28 }
29 j, err = OpenLaunchJournal(file)
30 if err != nil {
31 t.Fatal(err)
32 }
33 defer j.Close()
34 got, err := j.Read(ctx, target)
35 if err != nil || len(got.Reservations) != 1 || got.Reservations[0].State != "reserved" {
36 t.Fatalf("recovery=%+v %v", got, err)
37 }
38 wrong := reservation
39 wrong.Executable.Inode++
40 if err = j.Started(ctx, wrong, 123, "12345"); err == nil {
41 t.Fatal("different executable accepted")
42 }
43 if err = j.Reserve(ctx, reservation); err == nil {
44 t.Fatal("launch ID reused")
45 }
46 if err = j.Started(ctx, reservation, 123, "12345"); err != nil {
47 t.Fatal(err)
48 }
49 if err = j.Settled(ctx, reservation); err != nil {
50 t.Fatal(err)
51 }
52 target.Session++
53 if _, err = j.Read(ctx, target); err == nil {
54 t.Fatal("successor read predecessor inventory")
55 }
56}
57
58func TestLaunchJournalPrelaunchRecordIsRecoverableWithoutEffects(t *testing.T) {
59 ctx := context.Background()
60 file := filepath.Join(t.TempDir(), "launch.db")
61 j, err := OpenLaunchJournal(file)
62 if err != nil {
63 t.Fatal(err)
64 }
65 former := hostguard.FenceTarget{Host: "app.example.test", Incarnation: "inc", Stamp: 7, Session: 7, Box: "box", Enrollment: "enroll", BootID: "boot-1", ControllerID: "controller-1"}
66 parent := LaunchGroup{RelativePath: "carlos/claim-parent", Device: 1, Inode: 9}
67 for _, bad := range []LaunchInventory{
68 {ControllerPID: 41, ProcessStart: "start-1", Target: former, Groups: []LaunchGroup{parent}, Files: []LaunchFile{{Device: 1, Inode: 2}}},
69 {ControllerPID: 41, ProcessStart: "start-1", Target: former, Groups: []LaunchGroup{parent}, NoState: true, ProducerIDs: []string{"replicator"}},
70 {ControllerPID: 41, ProcessStart: "start-1", Target: former, Groups: []LaunchGroup{parent, {RelativePath: "carlos/claim-parent/role", Device: 1, Inode: 10}}, NoState: true},
71 {ControllerPID: 41, ProcessStart: "start-1", Target: former, NoState: true},
72 } {
73 if err = j.Prelaunch(ctx, bad); err == nil {
74 t.Fatalf("pre-launch record accepted effects or missing containment: %+v", bad)
75 }
76 }
77 if err = j.Prelaunch(ctx, LaunchInventory{ControllerPID: 41, ProcessStart: "start-1", Target: former, Groups: []LaunchGroup{parent}, NoState: true}); err != nil {
78 t.Fatal(err)
79 }
80 successor := former
81 successor.Session, successor.Stamp, successor.BootID, successor.ControllerID = 8, 8, "boot-2", "controller-2"
82 if err = j.Prelaunch(ctx, LaunchInventory{ControllerPID: 42, ProcessStart: "start-2", Target: successor, Groups: []LaunchGroup{{RelativePath: "carlos/claim-successor", Device: 1, Inode: 11}}, NoState: true}); err != nil {
83 t.Fatal(err)
84 }
85 if err = j.Close(); err != nil {
86 t.Fatal(err)
87 }
88 // A restarted controller reopens the journal and finds the former tuple.
89 j, err = OpenLaunchJournal(file)
90 if err != nil {
91 t.Fatal(err)
92 }
93 defer j.Close()
94 found, err := j.SessionLaunches(ctx, former.Host, former.Incarnation, former.Stamp, former.Session, former.Box, former.Enrollment)
95 if err != nil || len(found) != 1 || !SameLaunchOwner(found[0].Target, former) || found[0].ControllerPID != 41 || found[0].ProcessStart != "start-1" {
96 t.Fatalf("session records = %+v, %v", found, err)
97 }
98 stop, err := j.ReadStopKernelJournal(ctx, former)
99 if err != nil || !stop.Settled || len(stop.Cgroups) != 1 || len(stop.Files) != 0 || len(stop.ProducerIDs) != 0 {
100 t.Fatalf("pre-launch stop journal = %+v, %v", stop, err)
101 }
102 // The record is not an installation effect, so cold handover still accepts it.
103 if effects, err := j.HasInstallationEffects(ctx, former.Host, former.Incarnation, former.Stamp); err != nil || effects {
104 t.Fatalf("pre-launch record counted as installation effect: %v %v", effects, err)
105 }
106 if err = j.SettleRecovered(ctx, former); err != nil {
107 t.Fatal(err)
108 }
109 other, err := j.Read(ctx, successor)
110 if err != nil || len(other.Reservations) != 0 || !other.NoState {
111 t.Fatalf("recovering the former tuple changed its successor: %+v %v", other, err)
112 }
113 if found, err = j.SessionLaunches(ctx, former.Host, former.Incarnation, former.Stamp, 99, former.Box, former.Enrollment); err != nil || len(found) != 0 {
114 t.Fatalf("unrelated session matched: %+v %v", found, err)
115 }
116 if err = j.RecordFile(ctx, former, LaunchFile{Device: 1, Inode: 12}); err != nil {
117 t.Fatal(err)
118 }
119 if got, err := j.Read(ctx, former); err != nil || got.NoState || len(got.Files) != 1 {
120 t.Fatalf("launch files after pre-launch = %+v %v", got, err)
121 }
122}
123
124func TestClaimReviewCompletedProducersAreInstallationEffects(t *testing.T) {
125 j, err := OpenLaunchJournal(filepath.Join(t.TempDir(), "launch.db"))
126 if err != nil {
127 t.Fatal(err)
128 }
129 defer j.Close()
130 target := hostguard.FenceTarget{Host: "app.test", Incarnation: "inc", Stamp: 2, Session: 2, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "controller"}
131 if err := j.Prelaunch(t.Context(), LaunchInventory{ControllerPID: 123, ProcessStart: "456", Target: target, Groups: []LaunchGroup{{RelativePath: "host", Device: 1, Inode: 1}}, NoState: true}); err != nil {
132 t.Fatal(err)
133 }
134 if err := j.RecordProducer(t.Context(), target, "delivery"); err != nil {
135 t.Fatal(err)
136 }
137 if _, err := j.CompleteProducer(t.Context(), target, "delivery", ProducerCompletion{}); err != nil {
138 t.Fatal(err)
139 }
140 if effects, err := j.HasInstallationEffects(t.Context(), target.Host, target.Incarnation, target.Stamp); err != nil || !effects {
141 t.Fatalf("completed producer lost from handover check: effects=%v err=%v", effects, err)
142 }
143 if _, err := j.db.Exec(`UPDATE launches SET body=json_set(body,'$.Completed.Digest','broken')`); err != nil {
144 t.Fatal(err)
145 }
146 if _, err := j.HasInstallationEffects(t.Context(), target.Host, target.Incarnation, target.Stamp); err == nil {
147 t.Fatal("invalid completion summary accepted")
148 }
149}
150