carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "errors"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/rootstate"
11)
12
13func generationFixture(t *testing.T) (*RootGenerations, *healthyRetryStore, GenerationBinding) {
14 t.Helper()
15 s := &healthyRetryStore{dir: t.TempDir(), fault: errors.New("directory sync failed")}
16 b := GenerationBinding{Binding: HistoryBinding{Host: "shop.example", Account: "bab", App: "shop"},
17 Installation: "installation", Record: "record", RecordETag: "etag", Attempt: "attempt:main", Inputs: "inputs", Invocation: "invocation",
18 Target: GenerationOwner{Host: "shop.example", Incarnation: "inc", Stamp: 1, Session: 2, Box: "box", Enrollment: "enrollment", BootID: "boot", ControllerID: "controller"},
19 ControllerPID: 42, ControllerStart: "start", Version: "v1", Digest: strings.Repeat("a", 64),
20 Path: "/opt/carlos/versions/v1/bab/shop", Tree: "/opt/carlos/versions/v1/bab",
21 Executable: rootstate.FileIdentity{Device: 1, Inode: 2}, TreeIdentity: rootstate.FileIdentity{Device: 1, Inode: 3}}
22 return &RootGenerations{Store: s}, s, b
23}
24
25func TestPlan14GenerationReservationDurability(t *testing.T) {
26 g, s, b := generationFixture(t)
27 s.fail = 1
28 if _, err := g.Reserve(b); err == nil {
29 t.Fatal("directory sync failure granted a reservation")
30 }
31 raw, err := s.Read(generationKey(b))
32 if err != nil {
33 t.Fatal(err)
34 }
35 s.fail = 1
36 if _, err := g.Reserve(b); err == nil {
37 t.Fatal("visible reservation bypassed directory sync retry")
38 }
39 r, err := g.Reserve(b)
40 if err != nil || r.ID == "" || r.Binding != b || r.Revision != 1 {
41 t.Fatalf("reserve: %+v %v", r, err)
42 }
43 after, err := s.Read(generationKey(b))
44 if err != nil || string(raw) != string(after) {
45 t.Fatalf("retry changed reservation identity: %v", err)
46 }
47 for _, attack := range []string{"empty", "missing", "rename"} {
48 t.Run(attack, func(t *testing.T) {
49 dir := t.TempDir()
50 path := GenerationsPath(dir)
51 if err := WriteGenerations(path, Generations{Hosts: map[string][]string{b.Binding.Host: {b.Version}}}); err != nil {
52 t.Fatal(err)
53 }
54 switch attack {
55 case "empty":
56 if err := WriteGenerations(path, Generations{}); err != nil {
57 t.Fatal(err)
58 }
59 case "missing":
60 if err := os.Remove(path); err != nil {
61 t.Fatal(err)
62 }
63 if err := os.Remove(GenerationsSeqPath(path)); err != nil {
64 t.Fatal(err)
65 }
66 case "rename":
67 if err := os.Rename(filepath.Dir(path), filepath.Dir(path)+".old"); err != nil {
68 t.Fatal(err)
69 }
70 }
71 got, err := g.Read(b)
72 if err != nil || got != r {
73 t.Fatalf("legacy edit changed root reservation: %+v %v", got, err)
74 }
75 })
76 }
77}
78
79func TestPlan14GenerationReservationConflicts(t *testing.T) {
80 g, _, b := generationFixture(t)
81 r, err := g.Reserve(b)
82 if err != nil {
83 t.Fatal(err)
84 }
85 for name, change := range map[string]func(*GenerationBinding){
86 "account": func(b *GenerationBinding) { b.Binding.Account = "bap" },
87 "app": func(b *GenerationBinding) { b.Binding.App = "other" },
88 "version": func(b *GenerationBinding) { b.Version = "v2" },
89 "digest": func(b *GenerationBinding) { b.Digest = strings.Repeat("b", 64) },
90 "inode": func(b *GenerationBinding) { b.Executable.Inode++ },
91 "tree": func(b *GenerationBinding) { b.TreeIdentity.Inode++ },
92 "installation": func(b *GenerationBinding) { b.Installation = "replacement" },
93 "process": func(b *GenerationBinding) { b.ControllerStart = "replacement" },
94 "inputs": func(b *GenerationBinding) { b.Inputs = "replacement" },
95 } {
96 t.Run(name, func(t *testing.T) {
97 other := b
98 change(&other)
99 if _, err := g.Reserve(other); err == nil {
100 t.Fatal("rebound existing reservation")
101 }
102 got, err := g.Read(b)
103 if err != nil || got != r {
104 t.Fatalf("conflict changed old pin: %+v %v", got, err)
105 }
106 })
107 }
108}
109
110func TestPlan14GenerationObservationKeepsReservation(t *testing.T) {
111 g, s, b := generationFixture(t)
112 r, err := g.Reserve(b)
113 if err != nil {
114 t.Fatal(err)
115 }
116 o := GenerationProcess{PID: 51, Start: "51", TenantPID: 52, TenantStart: "52", Group: "system.slice/run.service", GroupIdentity: rootstate.FileIdentity{Device: 2, Inode: 3}, Invocation: "unit-invocation"}
117 s.fail = 1
118 if err := g.Started(r, o); err == nil {
119 t.Fatal("unsynced started record succeeded")
120 }
121 if err := g.Started(r, o); err != nil {
122 t.Fatal(err)
123 }
124 got, err := g.Read(b)
125 if err != nil || got.Binding != b || got.ID != r.ID || got.Process != o || got.Revision != 2 {
126 t.Fatalf("started: %+v %v", got, err)
127 }
128 o.PID++
129 if err := g.Started(r, o); err == nil {
130 t.Fatal("changed running lifetime")
131 }
132 if _, err := g.Reserve(b); err == nil {
133 t.Fatal("running reservation authorized another launch")
134 }
135}
136
137func TestPlan14GenerationRecordRefusals(t *testing.T) {
138 for _, attack := range []string{"missing", "malformed", "unknown field", "oversize"} {
139 t.Run(attack, func(t *testing.T) {
140 g, s, b := generationFixture(t)
141 if _, err := g.Reserve(b); err != nil {
142 t.Fatal(err)
143 }
144 key := generationKey(b)
145 raw, err := s.Read(key)
146 if err != nil {
147 t.Fatal(err)
148 }
149 switch attack {
150 case "missing":
151 if err := os.Remove(filepath.Join(s.dir, key)); err != nil {
152 t.Fatal(err)
153 }
154 case "malformed":
155 raw = []byte("{")
156 case "unknown field":
157 raw = append(raw[:len(raw)-1], []byte(`,"complete":true}`)...)
158 case "oversize":
159 raw = []byte(strings.Repeat(" ", rootstate.MaxRecord+1))
160 }
161 if attack != "missing" {
162 if err := s.Write(key, raw); err != nil {
163 t.Fatal(err)
164 }
165 }
166 if _, err := g.Read(b); err == nil {
167 t.Fatal("unavailable reservation granted a launch")
168 }
169 })
170 }
171}
172
173func TestPlan14GenerationOldProcessStillParses(t *testing.T) {
174 g, s, b := generationFixture(t)
175 r, err := g.Reserve(b)
176 if err != nil {
177 t.Fatal(err)
178 }
179 p := GenerationProcess{PID: 100, TenantPID: 200, Start: "one", TenantStart: "two", Group: "unit", Invocation: "invocation", GroupIdentity: rootstate.FileIdentity{Device: 1, Inode: 2}}
180 if err := g.Started(r, p); err != nil {
181 t.Fatal(err)
182 }
183 raw, err := s.Read(generationKey(b))
184 if err != nil {
185 t.Fatal(err)
186 }
187 if strings.Contains(string(raw), "LauncherIdentity") || strings.Contains(string(raw), "Containment") {
188 t.Fatal("old process wire shape changed")
189 }
190 got, err := g.Read(b)
191 if err != nil || got.Process != p {
192 t.Fatalf("old process: %+v %v", got, err)
193 }
194}
195