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 activator
4
5import (
6 "context"
7 "errors"
8 "os"
9 "path/filepath"
10 "slices"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/cgroup"
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18// Amadan #272, owed before O8: a cold wake's build-state record names the
19// build about to run against the instance's database, and completes an
20// install request (settings spec §4.2). It used to be written at step 2b,
21// before the backend's own refusals: the replica-key gate and the cgroup's
22// (a populated legacy parent, a killed cgroup kept, a limit not applied).
23// A launch refused there ran nothing, yet left a record naming its build.
24// The record is now written at the exec, so a refused launch leaves none.
25
26// execIdentityBackend is fakeBackend for the lease and the restore, with a
27// real ExecBackend for the identity, the seed and the spawn: the exec seams
28// and the refusals under test are the production ones.
29type execIdentityBackend struct {
30 *fakeBackend
31 eb *ExecBackend
32}
33
34func (b *execIdentityBackend) identityFor(registry.Route) identityBackend { return b.eb }
35
36// stageScript makes body host's live build v1, as the adopt pass lays it
37// out (<opt>/versions/v1/app, linked from <opt>/live/<host>).
38func stageScript(t *testing.T, eb *ExecBackend, host, body string) {
39 t.Helper()
40 stageLive(t, eb, host, "v1")
41 if err := os.WriteFile(filepath.Join(eb.OptDir, "versions", "v1", "app"), []byte(body), 0o755); err != nil {
42 t.Fatal(err)
43 }
44}
45
46// refusedWake runs one cold wake of rt over eb with a recording BeforeExec,
47// and returns the wake's error and the records written.
48func refusedWake(t *testing.T, eb *ExecBackend, rt registry.Route) (error, []string) {
49 t.Helper()
50 fb := &fakeBackend{restoreFresh: true}
51 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
52 if err != nil {
53 t.Fatal(err)
54 }
55 t.Cleanup(func() { reg.Close() })
56 a := New(Config{Bucket: "test-bucket", Idle: time.Hour}, &execIdentityBackend{fakeBackend: fb, eb: eb}, reg)
57 l := &execLog{}
58 a.BeforeExec = l.hook
59 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
60 defer cancel()
61 done, err := a.beginRequest(ctx, rt)
62 if err == nil {
63 done()
64 }
65 calls, _, _ := l.seen()
66 return err, calls
67}
68
69func TestARefusedLaunchLeavesNoBuildStateRecord(t *testing.T) {
70 t.Parallel()
71 const host = "shop.example.com"
72 route := func(eb *ExecBackend) registry.Route {
73 return registry.Route{Host: host, Kind: "instance", Hibernate: true, App: "app",
74 Socket: noControlSocket(t), DBPath: filepath.Join(eb.DataDir, host+".db")}
75 }
76
77 t.Run("replica seed refused by the replica-key gate", func(t *testing.T) {
78 eb := testExecBackend(t)
79 stageScript(t, eb, host, "#!/bin/sh\nexit 0\n")
80 rt := route(eb)
81 rt.Replica, rt.Seed = true, `["seed"]`
82 err, calls := refusedWake(t, eb, rt)
83 if !errors.Is(err, ErrReplicaKey) {
84 t.Fatalf("wake = %v, want the replica-key gate's refusal", err)
85 }
86 if len(calls) != 0 {
87 t.Fatalf("records %v after a refused seed, want none", calls)
88 }
89 })
90
91 t.Run("replica spawn refused by the replica-key gate", func(t *testing.T) {
92 eb := testExecBackend(t)
93 stageScript(t, eb, host, "#!/bin/sh\nexit 0\n")
94 rt := route(eb)
95 rt.Replica = true
96 err, calls := refusedWake(t, eb, rt)
97 if !errors.Is(err, ErrReplicaKey) {
98 t.Fatalf("wake = %v, want the replica-key gate's refusal", err)
99 }
100 if len(calls) != 0 {
101 t.Fatalf("records %v after a refused spawn, want none", calls)
102 }
103 })
104
105 t.Run("seed refused beside a populated legacy parent", func(t *testing.T) {
106 eb, parent := delegatedBackend(t, host)
107 eb.GenContainment = true
108 if err := os.WriteFile(filepath.Join(parent, "cgroup.procs"), []byte("4242\n"), 0o644); err != nil {
109 t.Fatal(err)
110 }
111 stageScript(t, eb, host, "#!/bin/sh\nexit 0\n")
112 rt := route(eb)
113 rt.Seed = `["seed"]`
114 err, calls := refusedWake(t, eb, rt)
115 if !errors.Is(err, cgroup.ErrLegacyParentPopulated) {
116 t.Fatalf("wake = %v, want the cgroup's refusal", err)
117 }
118 if len(calls) != 0 {
119 t.Fatalf("records %v after a refused seed, want none", calls)
120 }
121 })
122
123 // The control: a seed that runs (and fails) did execute its build
124 // against the database, so it is recorded, once, before it ran.
125 t.Run("a seed that ran is recorded", func(t *testing.T) {
126 eb := testExecBackend(t)
127 stageScript(t, eb, host, "#!/bin/sh\nexit 1\n")
128 rt := route(eb)
129 rt.Seed = `["seed"]`
130 err, calls := refusedWake(t, eb, rt)
131 if err == nil {
132 t.Fatal("a failing seed woke the instance")
133 }
134 if !slices.Equal(calls, []string{host + "@v1"}) {
135 t.Fatalf("records %v, want v1 once", calls)
136 }
137 })
138}
139