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 "encoding/json"
5 "errors"
6 "io"
7 "net/http"
8 "os"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/instancectl"
12 "github.com/carlosframework/platform/internal/instanceupdates"
13 "github.com/carlosframework/platform/internal/release"
14)
15
16// updatesGet asks GET /updates on a control socket with bearer ("" sends none).
17func updatesGet(t *testing.T, ctl, bearer string) (int, instanceupdates.Report) {
18 t.Helper()
19 req, err := http.NewRequest("GET", "http://carlos/updates?host=other.example.com", nil)
20 if err != nil {
21 t.Fatal(err)
22 }
23 if bearer != "" {
24 req.Header.Set("Authorization", "Bearer "+bearer)
25 }
26 resp, err := socketClient(ctl).Do(req)
27 if err != nil {
28 t.Fatalf("dial control socket %s: %v", ctl, err)
29 }
30 defer resp.Body.Close()
31 body, _ := io.ReadAll(resp.Body)
32 var rep instanceupdates.Report
33 _ = json.Unmarshal(body, &rep)
34 return resp.StatusCode, rep
35}
36
37func fakeUpdatesReader(host string) (instanceupdates.Report, error) {
38 return instanceupdates.Report{Settings: release.UpdateSettings{Updates: release.UpdatesManual}, Installed: "v1.2.0"}, nil
39}
40
41// With UpdatesReader set, GET /updates is served on the spawned instance's
42// control socket behind the minted token, for the host the spawn was for (a
43// ?host= on the request changes nothing); with Timers left nil the schedule
44// verbs are still absent.
45func TestExecBackendStartMountsUpdatesOnControlSocket(t *testing.T) {
46 b := testExecBackend(t) // b.Timers nil
47 b.UpdatesReader = fakeUpdatesReader
48 ctl, token, stop := startAndReachControlSocket(t, b, "upd.example.com")
49 defer stop()
50
51 code, rep := updatesGet(t, ctl, token)
52 if code != http.StatusOK || rep.Host != "upd.example.com" || rep.Installed != "v1.2.0" || rep.Settings.Updates != release.UpdatesManual {
53 t.Fatalf("GET /updates with the minted token = %d %+v", code, rep)
54 }
55 if code, _ := updatesGet(t, ctl, "not-the-token"); code != http.StatusUnauthorized {
56 t.Fatalf("wrong bearer = %d, want 401", code)
57 }
58 if code, _ := updatesGet(t, ctl, ""); code != http.StatusUnauthorized {
59 t.Fatalf("no bearer = %d, want 401", code)
60 }
61 req, _ := http.NewRequest("GET", "http://carlos/schedule", nil)
62 req.Header.Set("Authorization", "Bearer "+token)
63 resp, err := socketClient(ctl).Do(req)
64 if err != nil {
65 t.Fatal(err)
66 }
67 resp.Body.Close()
68 if resp.StatusCode != http.StatusNotFound {
69 t.Fatalf("GET /schedule with nil Timers = %d, want 404", resp.StatusCode)
70 }
71}
72
73// A reader that errors (a host the registry has no row for) is a 503 on the
74// wire, not an empty 200.
75func TestExecBackendStartUpdatesReaderErrorIs503(t *testing.T) {
76 b := testExecBackend(t)
77 b.UpdatesReader = func(string) (instanceupdates.Report, error) { return instanceupdates.Report{}, errors.New("no route") }
78 ctl, token, stop := startAndReachControlSocket(t, b, "upd503.example.com")
79 defer stop()
80 if code, _ := updatesGet(t, ctl, token); code != http.StatusServiceUnavailable {
81 t.Fatalf("GET /updates = %d, want 503", code)
82 }
83}
84
85// With UpdatesReader nil, even with the schedule verbs mounted, /updates is
86// not served.
87func TestExecBackendStartWithNilUpdatesReaderMountsNoUpdates(t *testing.T) {
88 b := testExecBackend(t)
89 b.Timers = &fakeTimerStore{}
90 ctl, token, stop := startAndReachControlSocket(t, b, "noupd.example.com")
91 defer stop()
92 if code, _ := updatesGet(t, ctl, token); code != http.StatusNotFound {
93 t.Fatalf("GET /updates with nil UpdatesReader = %d, want 404", code)
94 }
95}
96
97// The nested layout's listener (prepareAt) mounts the same verb.
98func TestPreparedControlListenerMountsUpdates(t *testing.T) {
99 b, rt := nestedBackend(t, &fakeControl{}, &fakeUnit{})
100 b.GenContainment = true
101 b.UpdatesReader = fakeUpdatesReader
102 p0 := b.pathsFor(rt)
103 if err := os.MkdirAll(p0.runDir, 0o750); err != nil {
104 t.Fatal(err)
105 }
106 sock := nestedGenSocketIn(p0.runDir, 7)
107 p, ctl, logs, _, err := b.prepareAt(rt, instancectl.RoleRun, sock, 7)
108 if err != nil {
109 t.Fatalf("prepareAt: %v", err)
110 }
111 if logs != nil {
112 defer logs.Close()
113 }
114 if ctl == nil {
115 t.Fatal("prepareAt bound no control listener")
116 }
117 defer ctl.Close()
118 env, err := readEnvFileBeneath(p.dataDir, rt.Host+".env")
119 if err != nil {
120 t.Fatal(err)
121 }
122 code, rep := updatesGet(t, sock+".control", env["CARLOS_ADMIN_TOKEN"])
123 if code != http.StatusOK || rep.Host != rt.Host || rep.Installed != "v1.2.0" {
124 t.Fatalf("GET /updates on the prepared listener = %d %+v", code, rep)
125 }
126}
127