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 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/registry"
12 "github.com/carlosframework/platform/internal/rootstate"
13)
14
15func TestPlan14GenerationRetirementReleasesExactPin(t *testing.T) {
16 f := newReuseFixture(t)
17 g, r := generationPinFixture(t, f.opt, "bab", "myapp", "v2")
18 pending := r.Binding
19 pending.Attempt, pending.Invocation = "pending-v3", "pending-v3"
20 pending.Version = "v3"
21 pending.Path = VersionPath(f.opt, "bap", "v3", "other")
22 pending.Binding.Account, pending.Binding.App = "bap", "other"
23 pending.Tree = filepath.Dir(pending.Path)
24 if err := StageArtifact(f.opt, "bap", "v3", "other", []byte("pending")); err != nil {
25 t.Fatal(err)
26 }
27 for path, dst := range map[string]*rootstate.FileIdentity{pending.Path: &pending.Executable, pending.Tree: &pending.TreeIdentity} {
28 file, err := os.Open(path)
29 if err != nil {
30 t.Fatal(err)
31 }
32 *dst, err = rootstate.Identity(file)
33 file.Close()
34 if err != nil {
35 t.Fatal(err)
36 }
37 }
38 p, err := g.Reserve(pending)
39 if err != nil {
40 t.Fatal(err)
41 }
42 closed, verified := 0, 0
43 err = g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { closed++; return nil }, func(_ context.Context, got GenerationReservation) (string, error) {
44 verified++
45 if !got.Closed || got.ID != r.ID {
46 t.Fatal("kernel checked before durable close")
47 }
48 if err := g.Check(r); err == nil {
49 t.Fatal("closed authority can launch")
50 }
51 return strings.Repeat("a", 64), nil
52 })
53 if err != nil || closed != 1 || verified != 1 {
54 t.Fatalf("retire: %v %d %d", err, closed, verified)
55 }
56 source := RootGenerationPins{Ledger: g, Verify: func(context.Context, []GenerationReservation) error { return nil }}
57 pins, err := source.StagedPins(t.Context(), f.opt)
58 if err != nil || pins.Has(r.Binding.Path) || !pins.Has(p.Binding.Path) {
59 t.Fatalf("wrong pins: %v %v", pins, err)
60 }
61 if got, err := g.Read(p.Binding); err != nil || got != p {
62 t.Fatalf("pending reservation changed: %+v %v", got, err)
63 }
64 if _, err := g.Reserve(r.Binding); err == nil {
65 t.Fatal("retired request replay authorized launch")
66 }
67 if err := g.Started(r, GenerationProcess{PID: 51, TenantPID: 52, Start: "51", TenantStart: "52", Group: "system.slice/run.service", Invocation: "unit", GroupIdentity: rootstate.FileIdentity{Device: 1, Inode: 2}}); err == nil {
68 t.Fatal("retired reservation recorded a late launch")
69 }
70 f.pass(t, WithRootGenerationPins(source))
71 if got := f.stagedV2(t); got != "new v2" {
72 t.Fatalf("exact retirement failed to release restage: %q", got)
73 }
74}
75
76func TestPlan14GenerationRetirementRefusals(t *testing.T) {
77 for _, attack := range []string{"join", "kernel", "close-sync", "retire-sync", "wrong-id", "wrong-binding", "no-close", "no-kernel", "bad-evidence"} {
78 t.Run(attack, func(t *testing.T) {
79 g, s, b := generationFixture(t)
80 r, err := g.Reserve(b)
81 if err != nil {
82 t.Fatal(err)
83 }
84 expected := r
85 close := func(context.Context, GenerationReservation) error { return nil }
86 verify := func(context.Context, GenerationReservation) (string, error) { return strings.Repeat("b", 64), nil }
87 switch attack {
88 case "join":
89 close = func(context.Context, GenerationReservation) error { return os.ErrPermission }
90 case "kernel":
91 verify = func(context.Context, GenerationReservation) (string, error) { return "", os.ErrPermission }
92 case "close-sync":
93 close = func(context.Context, GenerationReservation) error { s.fail = 1; return nil }
94 case "retire-sync":
95 verify = func(context.Context, GenerationReservation) (string, error) {
96 s.fail = 1
97 return strings.Repeat("b", 64), nil
98 }
99 case "wrong-id":
100 expected.ID = strings.Repeat("c", 64)
101 case "wrong-binding":
102 expected.Binding.ControllerStart = "other"
103 case "no-close":
104 close = nil
105 case "no-kernel":
106 verify = nil
107 case "bad-evidence":
108 verify = func(context.Context, GenerationReservation) (string, error) { return "complete", nil }
109 }
110 if err := g.Retire(t.Context(), expected, close, verify); err == nil {
111 t.Fatal("retirement accepted incomplete evidence")
112 }
113 // A failed final sync may have made the evidence visible. It must be
114 // recommitted before a reader can release its pin.
115 if attack == "retire-sync" {
116 s.fail = 1
117 if _, err := g.Read(b); err == nil {
118 t.Fatal("uncommitted retirement granted read")
119 }
120 }
121 got, err := g.Read(b)
122 if err != nil {
123 t.Fatal(err)
124 }
125 if attack != "retire-sync" && got.Retirement != "" {
126 t.Fatal("unverified retirement released pin")
127 }
128 if got.ID != r.ID || got.Binding != r.Binding {
129 t.Fatal("retirement changed identity")
130 }
131 if got.Closed && !errors.Is(g.Check(r), os.ErrPermission) {
132 t.Fatal("closed request remained launchable")
133 }
134 })
135 }
136}
137
138func TestPlan14GenerationRetirementKeepsIndependentReference(t *testing.T) {
139 f := newReuseFixture(t)
140 g, r := generationPinFixture(t, f.opt, "bab", "myapp", "v2")
141 b := r.Binding
142 b.Attempt = "independent"
143 b.Invocation = "independent"
144 other, err := g.Reserve(b)
145 if err != nil {
146 t.Fatal(err)
147 }
148 if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) { return strings.Repeat("a", 64), nil }); err != nil {
149 t.Fatal(err)
150 }
151 source := RootGenerationPins{Ledger: g, Verify: func(context.Context, []GenerationReservation) error { return nil }}
152 f.pass(t, WithRootGenerationPins(source))
153 if got := f.stagedV2(t); got != "old v2" {
154 t.Fatalf("independent reference overwritten: %q", got)
155 }
156 if got, err := g.Read(other.Binding); err != nil || got != other {
157 t.Fatal("other reference changed", err)
158 }
159 source.Verify = nil
160 if _, err := source.StagedPins(t.Context(), f.opt); err == nil {
161 t.Fatal("retirement supplied completeness")
162 }
163}
164
165func TestPlan14GenerationClaimsOffRestageMatchesMain(t *testing.T) {
166 for _, pinned := range []bool{false, true} {
167 type result struct {
168 bytes, live, version, previous string
169 restarts int
170 }
171 run := func(withRoot bool) result {
172 f := newReuseFixture(t)
173 if pinned {
174 if err := f.reg.Upsert(registry.Route{Host: "pending.example", Account: "bab", App: "myapp", Version: "v2", Status: "creating"}); err != nil {
175 t.Fatal(err)
176 }
177 }
178 var opts []PassOption
179 if withRoot {
180 opts = append(opts, WithRootGenerationPins(PinSourceFunc(func(context.Context, string) (StagedPaths, error) { return StagedPaths{}, nil })))
181 }
182 restarts := 0
183 if err := Pass(t.Context(), f.reg, f.s, f.pub, f.opt, func(string) error { restarts++; return nil }, opts...); err != nil {
184 t.Fatal(err)
185 }
186 live, err := LiveVersion(f.opt, "h1.example.com")
187 if err != nil {
188 t.Fatal(err)
189 }
190 row, err := f.reg.Get("h1.example.com")
191 if err != nil || row == nil {
192 t.Fatal(err)
193 }
194 return result{f.stagedV2(t), live, row.Version, row.PrevVersion, restarts}
195 }
196 main, branch := run(false), run(true)
197 if main != branch {
198 t.Fatalf("claims-off changed pinned=%v: main=%+v branch=%+v", pinned, main, branch)
199 }
200 if !pinned && (branch.bytes != "new v2" || branch.live != "v2" || branch.restarts != 1) {
201 t.Fatalf("positive restage refused: %+v", branch)
202 }
203 }
204}
205
206func TestPlan14GenerationClosedReplayRefused(t *testing.T) {
207 for _, started := range []bool{false, true} {
208 g, _, b := generationFixture(t)
209 r, err := g.Reserve(b)
210 if err != nil {
211 t.Fatal(err)
212 }
213 process := GenerationProcess{PID: 51, TenantPID: 52, Start: "51", TenantStart: "52", Group: "system.slice/run.service", Invocation: "unit", GroupIdentity: rootstate.FileIdentity{Device: 1, Inode: 2}}
214 if started {
215 if err := g.Started(r, process); err != nil {
216 t.Fatal(err)
217 }
218 r, err = g.Read(b)
219 if err != nil {
220 t.Fatal(err)
221 }
222 }
223 if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(_ context.Context, closed GenerationReservation) (string, error) {
224 if err := g.Check(closed); err == nil {
225 t.Fatal("closed reservation granted a spawn")
226 }
227 if err := g.Started(r, process); err == nil {
228 t.Fatal("closed reservation accepted a started replay")
229 }
230 return strings.Repeat("a", 64), nil
231 }); err != nil {
232 t.Fatal(err)
233 }
234 }
235}
236