carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "os"
8 "path/filepath"
9 "slices"
10 "strings"
11 "testing"
12
13 "github.com/carlosframework/platform/internal/instancectl"
14 "github.com/carlosframework/platform/internal/instancekey"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/registry"
17)
18
19// envlessProc is a generation handle that kept no launch environment.
20type envlessProc struct{ Proc }
21
22// A config swap is queued: config sync has delivered a new revision, and
23// the serving generation still runs with the one it launched with. A run
24// gets the serving generation's environment, not the delivered files
25// (merge ruling M3).
26func TestARunGetsTheServingGenerationsEnvironment(t *testing.T) {
27 ctl := &fakeControl{inv: "inv1", cmdInv: "inv2"}
28 unit := &fakeUnit{}
29 b, rt := nestedBackend(t, ctl, unit)
30 b.layoutFn = func(registry.Route) (Layout, string) { return LayoutNested, "" }
31 paths := b.pathsFor(rt)
32 if err := os.MkdirAll(filepath.Dir(paths.configEnv), 0o700); err != nil {
33 t.Fatal(err)
34 }
35 deliver := func(rev string) {
36 t.Helper()
37 if err := os.WriteFile(paths.configEnv, []byte("APP_KEY="+rev+"\n"), 0o600); err != nil {
38 t.Fatal(err)
39 }
40 }
41 deliver("old")
42 var stopServing func()
43 ctl.onStart = func() { stopServing = serveOK(t, rt.Socket) }
44 defer func() {
45 if stopServing != nil {
46 stopServing()
47 }
48 }()
49 unit.set("active", "inv1", true, true)
50 p, _, err := b.Start(context.Background(), rt)
51 if err != nil {
52 t.Fatalf("Start: %v", err)
53 }
54 lp, ok := p.(launchEnvProc)
55 if !ok || lp.LaunchEnv()["APP_KEY"] != "old" {
56 t.Fatalf("the generation's handle kept %v, want its launch environment", p)
57 }
58 deliver("new") // delivered, not activated: the config swap is queued
59
60 dir := filepath.Join(provision.InstanceDir(b.DataDir, rt.Host), CmdRunDirName, "run_a")
61 spec := CmdSpec{RunID: "run_a", RunDir: dir, Stdout: &syncBuffer{}, Stderr: &syncBuffer{}, genEnv: lp.LaunchEnv()}
62 u, err := b.StartCmd(context.Background(), rt, spec)
63 if err != nil {
64 t.Fatalf("StartCmd: %v", err)
65 }
66 u.(*cmdUnit).out.Close()
67 u.(*cmdUnit).errs.Close()
68 raw, err := os.ReadFile(filepath.Join(provision.InstanceDir(b.DataDir, rt.Host), "launch.cmd.json"))
69 if err != nil {
70 t.Fatal(err)
71 }
72 var l instancectl.Launch
73 if err := json.Unmarshal(raw, &l); err != nil {
74 t.Fatal(err)
75 }
76 if !slices.Contains(l.Env, "APP_KEY=old") || slices.Contains(l.Env, "APP_KEY=new") {
77 t.Fatalf("the run's env %q, want the serving generation's APP_KEY=old", l.Env)
78 }
79 token := lp.LaunchEnv()["CARLOS_ADMIN_TOKEN"]
80 if token == "" || !slices.Contains(l.Env, "CARLOS_ADMIN_TOKEN="+token) {
81 t.Fatal("the run did not get the serving generation's admin token")
82 }
83 // The instance key the launch minted is in the generation's snapshot,
84 // and the command gets exactly it: the command opens the instance's
85 // sealed data and must never be left to mint a key of its own.
86 key := lp.LaunchEnv()[instancekey.Var]
87 if key == "" || !slices.Contains(l.Env, instancekey.Var+"="+key) {
88 t.Fatalf("the run's env lacks the serving generation's %s", instancekey.Var)
89 }
90 if kl, ok := p.(launchKeyLayerProc); !ok || kl.LaunchKeyLayer() != instancekey.LayerOwn {
91 t.Fatalf("the generation's handle did not keep its key's layer")
92 }
93 for _, kv := range l.Env {
94 if strings.HasPrefix(kv, "CARLOS_CONTROL_SOCKET=") || strings.HasPrefix(kv, "CARLOS_PURGE=") {
95 t.Errorf("a run's env carries %s", kv)
96 }
97 }
98
99 // A run with no generation environment is refused before anything is
100 // laid out or started: the delivered files are never its fallback.
101 before := ctl.joined()
102 if _, err := b.StartCmd(context.Background(), rt, CmdSpec{RunID: "run_b", RunDir: filepath.Join(filepath.Dir(dir), "run_b")}); !errors.Is(err, errNoGenEnv) {
103 t.Fatalf("StartCmd without a generation environment = %v, want errNoGenEnv", err)
104 }
105 if ctl.joined() != before {
106 t.Fatalf("the refused run reached the control service:\n%s", ctl.joined())
107 }
108}
109
110// RunCmd hands StartCmd the environment the current generation's handle
111// kept, and nothing for a handle that kept none, which StartCmd refuses
112// (merge ruling M3).
113func TestRunCmdHandsOverTheGenerationsLaunchEnvironment(t *testing.T) {
114 a, cb, _ := testCmdActivator(t, Config{})
115 rt := testRoute("c.example.com")
116 var got []map[string]string
117 cb.startCmd = func(_ context.Context, _ registry.Route, spec CmdSpec) (CmdUnit, error) {
118 got = append(got, spec.genEnv)
119 if spec.genEnv == nil {
120 return nil, errNoGenEnv
121 }
122 return cb.unit, nil
123 }
124 arrangeAwake(t, a, rt)
125 a.mu.Lock()
126 e := a.entries[rt.Host]
127 e.proc.(*fakeProc).launchEnv = map[string]string{"APP_KEY": "serving"}
128 a.mu.Unlock()
129 h, err := a.RunCmd(context.Background(), rt, CmdSpec{RunID: "run_a"})
130 if err != nil {
131 t.Fatal(err)
132 }
133 if err := h.Stop(context.Background()); err != nil {
134 t.Fatal(err)
135 }
136 h.Release()
137 a.mu.Lock()
138 e.proc = envlessProc{e.proc}
139 a.mu.Unlock()
140 if _, err := a.RunCmd(context.Background(), rt, CmdSpec{RunID: "run_b"}); !errors.Is(err, errNoGenEnv) {
141 t.Fatalf("a run on a handle with no launch environment = %v, want errNoGenEnv", err)
142 }
143 if len(got) != 2 || got[0]["APP_KEY"] != "serving" || got[1] != nil {
144 t.Fatalf("StartCmd got %v, want the handle's environment, then none", got)
145 }
146}
147
148// A run's command on a replica row passes the replica gate like every
149// other launch (instance-key rulings 26 to 29), on the environment it is
150// given: no key and no control socket is refused, a key that is not the
151// home's is refused, and the home's own key starts. A refusal reaches
152// neither the control service nor the instance's directory.
153func TestStartCmdPassesTheReplicaKeyGate(t *testing.T) {
154 const key = "aG9tZS1rZXktaG9tZS1rZXktaG9tZS1rZXktaG9tZSE="
155 for _, c := range []struct {
156 name string
157 env map[string]string
158 layer instancekey.Layer
159 listed string
160 ok bool
161 }{
162 {"no key", map[string]string{}, instancekey.LayerNone, instancekey.Digest(key), false},
163 {"not the home's key", map[string]string{instancekey.Var: key}, instancekey.LayerOwn, instancekey.Digest("another"), false},
164 {"the home's key", map[string]string{instancekey.Var: key}, instancekey.LayerOwn, instancekey.Digest(key), true},
165 } {
166 t.Run(c.name, func(t *testing.T) {
167 ctl := &fakeControl{cmdInv: "inv1"}
168 b, rt := nestedBackend(t, ctl, &fakeUnit{})
169 b.layoutFn = func(registry.Route) (Layout, string) { return LayoutNested, "" }
170 rt.Replica = true
171 b.ReplicaHomeKeys = func(string) (string, bool) { return c.listed, true }
172 dir := filepath.Join(provision.InstanceDir(b.DataDir, rt.Host), CmdRunDirName, "run_a")
173 spec := CmdSpec{RunID: "run_a", RunDir: dir, Stdout: &syncBuffer{}, Stderr: &syncBuffer{}, genEnv: c.env, genKeyLayer: c.layer}
174 u, err := b.StartCmd(context.Background(), rt, spec)
175 if c.ok {
176 if err != nil {
177 t.Fatalf("StartCmd: %v", err)
178 }
179 u.(*cmdUnit).out.Close()
180 u.(*cmdUnit).errs.Close()
181 return
182 }
183 if !errors.Is(err, ErrReplicaKey) || u != nil {
184 t.Fatalf("StartCmd = %v, %v; want ErrReplicaKey and no unit", u, err)
185 }
186 if strings.Contains(ctl.joined(), "cmd ") {
187 t.Fatalf("a refused replica command reached the control service:\n%s", ctl.joined())
188 }
189 if _, err := os.Stat(filepath.Join(provision.InstanceDir(b.DataDir, rt.Host), "launch.cmd.json")); !os.IsNotExist(err) {
190 t.Fatalf("a refused replica command left a launch file (%v)", err)
191 }
192 })
193 }
194}
195