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 && rootrunner
2
3package host
4
5import (
6 "bytes"
7 "context"
8 "encoding/json"
9 "io"
10 "net/http"
11 "net/http/httptest"
12 "os"
13 "os/exec"
14 "os/signal"
15 "runtime"
16 "strings"
17 "sync"
18 "syscall"
19 "testing"
20 "time"
21
22 "github.com/carlosframework/platform/internal/hostbox"
23 "github.com/carlosframework/platform/internal/hostclaim"
24 execlease "github.com/carlosframework/platform/internal/hostexec"
25 "github.com/carlosframework/platform/internal/hostguard"
26 "github.com/carlosframework/platform/internal/instancectl"
27 "github.com/carlosframework/platform/internal/park"
28)
29
30type lifecycleControlTrace struct {
31 ClaimRootWithLaunch
32 t *testing.T
33}
34
35type lifecycleStopTrace struct {
36 claimUnitStopKernel
37 t *testing.T
38 once sync.Once
39}
40
41func (k *lifecycleStopTrace) VerifyStopped(ctx context.Context, target hostguard.FenceTarget, inv StopInventory, ack DrainAcknowledgement) (StopKernelEvidence, error) {
42 evidence, err := k.claimUnitStopKernel.VerifyStopped(ctx, target, inv, ack)
43 if err != nil {
44 k.once.Do(func() {
45 if _, descriptor, ok := strings.Cut(err.Error(), "descriptor remains at "); ok {
46 name, linkErr := os.Readlink(descriptor)
47 info, infoErr := os.ReadFile(strings.Replace(descriptor, "/fd/", "/fdinfo/", 1))
48 k.t.Logf("retained descriptor %s target=%s link=%v info=%s read=%v", descriptor, name, linkErr, info, infoErr)
49 }
50 journal, readErr := k.Journal.Read(ctx, target)
51 k.t.Logf("stop kernel refusal: %v; journal=%+v read=%v acknowledgement=%+v inventory=%+v", err, journal, readErr, ack, inv)
52 })
53 }
54 return evidence, err
55}
56
57type lifecycleResponse struct {
58 http.ResponseWriter
59 status int
60 body bytes.Buffer
61}
62
63func (w *lifecycleResponse) Unwrap() http.ResponseWriter { return w.ResponseWriter }
64func (w *lifecycleResponse) WriteHeader(status int) {
65 w.status = status
66 w.ResponseWriter.WriteHeader(status)
67}
68func (w *lifecycleResponse) Write(body []byte) (int, error) {
69 if w.body.Len() < 64<<10 {
70 w.body.Write(body)
71 }
72 return w.ResponseWriter.Write(body)
73}
74func (h lifecycleControlTrace) ServeHTTP(w http.ResponseWriter, r *http.Request) {
75 var q hostbox.RoleLaunchRequest
76 var execution execlease.ExecutionRequest
77 if r.URL.Path == "/claim/launch" || r.URL.Path == "/claim/execution" {
78 raw, _ := io.ReadAll(r.Body)
79 r.Body = io.NopCloser(bytes.NewReader(raw))
80 if r.URL.Path == "/claim/launch" {
81 json.Unmarshal(raw, &q)
82 } else {
83 json.Unmarshal(raw, &execution)
84 }
85 }
86 started := time.Now()
87 response := &lifecycleResponse{ResponseWriter: w}
88 h.ClaimRootWithLaunch.ServeHTTP(response, r)
89 if response.status >= 400 || bytes.Contains(response.body.Bytes(), []byte(`"Failed":true`)) {
90 if execution.Host != "" {
91 raw, err := h.Root.executionRuntime.state.Read(execution.Host + ".json")
92 diagnostic, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), time.Second)
93 a, authorityErr := h.Root.executionAuthorityWithMigration(diagnostic, h.Root.executionRuntime.state, execution, execlease.ControllerProcess{})
94 cancel()
95 h.t.Logf("execution current authority=%+v err=%v", a, authorityErr)
96 h.t.Logf("execution failure request=%+v retained=%s read=%v", execution, raw, err)
97 }
98 h.t.Logf("root RPC %s status=%d reply=%s", r.URL.Path, response.status, response.body.String())
99 h.t.Log("root RPC elapsed", time.Since(started))
100 }
101 if q.Op == hostbox.RoleLaunchStop && q.Reservation != nil {
102 in, err := h.Launch.Journal.Read(r.Context(), q.Reservation.Target)
103 h.t.Logf("stop record err=%v request=%+v record=%+v containment=%v", err, *q.Reservation, in, validatePersistedUnitContainment(r.Context(), in, h.Launch.Units))
104 }
105}
106
107type lifecycleKernelTrace struct {
108 ClaimLaunchKernel
109 t *testing.T
110}
111
112func (k lifecycleKernelTrace) Observe(s instancectl.ClaimUnitStarted) (ClaimLaunchObserved, error) {
113 got, err := k.ClaimLaunchKernel.Observe(s)
114 k.t.Logf("launch observation: started=%+v observed=%+v error=%v", s, got, err)
115 return got, err
116}
117func (k lifecycleKernelTrace) VerifyDigest(ctx context.Context, path string, id hostbox.LaunchFile, digest string) error {
118 err := k.ClaimLaunchKernel.VerifyDigest(ctx, path, id, digest)
119 k.t.Log("launch digest", err)
120 return err
121}
122
123type lifecycleUnitsTrace struct {
124 ClaimLaunchUnits
125 t *testing.T
126}
127
128func (u lifecycleUnitsTrace) Placement(host string) (string, string, error) {
129 return claimLaunchPlacement(u.ClaimLaunchUnits, host)
130}
131func (u lifecycleUnitsTrace) StopInvocation(ctx context.Context, host, inv string) error {
132 err := claimStopInvocation(ctx, u.ClaimLaunchUnits, host, inv)
133 u.t.Log("stop invocation", inv, err)
134 return err
135}
136func (u lifecycleUnitsTrace) Gone(ctx context.Context, host, inv string) error {
137 err := u.ClaimLaunchUnits.Gone(ctx, host, inv)
138 u.t.Log("gone", inv, err)
139 return err
140}
141func (u lifecycleUnitsTrace) StartReserved(ctx context.Context, in instancectl.ClaimUnit, l instancectl.Launch, p instancectl.ClaimExecPermit, check func() error) (instancectl.ClaimUnitStarted, error) {
142 got, err := u.ClaimLaunchUnits.StartReserved(ctx, in, l, p, func() error { e := check(); u.t.Log("unit start revalidation", e); return e })
143 u.t.Log("start reserved", got, err)
144 if err != nil {
145 raw, _ := exec.Command("journalctl", "-u", instancectl.UnitName(instancectl.RoleRun, in.Host), "--no-pager", "-n", "60").CombinedOutput()
146 u.t.Log(string(raw))
147 }
148 return got, err
149}
150
151type lifecycleRegionalTrace struct {
152 hostbox.RegionalEvidence
153 t *testing.T
154 once sync.Once
155}
156
157func (r *lifecycleRegionalTrace) VerifyObligation(ctx context.Context, o hostclaim.Observed, ob hostclaim.Obligation) (hostclaim.ParkScope, error) {
158 scope, err := r.RegionalEvidence.(hostbox.RegionalObligationEvidence).VerifyObligation(ctx, o, ob)
159 if err != nil {
160 r.once.Do(func() { r.t.Logf("regional evidence: %v; obligation=%+v holder=%+v", err, ob, o.Claim.Holder) })
161 }
162 return scope, err
163}
164
165func lifecycleTraceRegional(t *testing.T, p *ClaimProviders) {
166 worker := p.Regional.(hostbox.RegionalWorker)
167 worker.Regional.Engine.Now = func() time.Time { return time.Now().Add(park.SignatureAge + hostbox.RequestLimit + time.Second) }
168 worker.Evidence = &lifecycleRegionalTrace{RegionalEvidence: worker.Evidence, t: t}
169 view, authorize := worker.Regional.Engine.View, worker.Regional.Engine.Authorize
170 var viewOnce, authorizeOnce sync.Once
171 worker.Regional.Engine.View = func(ctx context.Context, o hostclaim.Observed) (park.View, error) {
172 v, err := view(ctx, o)
173 if err != nil {
174 viewOnce.Do(func() { t.Log("regional view", err) })
175 }
176 return v, err
177 }
178 worker.Regional.Engine.Authorize = func(ctx context.Context, o hostclaim.Observed, ob hostclaim.Obligation) error {
179 err := authorize(ctx, o, ob)
180 if err != nil {
181 authorizeOnce.Do(func() { t.Log("regional authorization", err) })
182 }
183 return err
184 }
185 p.Regional = worker
186}
187
188func lifecycleTraceParkState(t *testing.T, p ClaimProviders, o hostclaim.Observed) {
189 engine := p.Regional.(hostbox.RegionalWorker).Regional.Engine
190 v, err := engine.View(t.Context(), o)
191 t.Logf("park view=%+v err=%v holder=%+v", v, err, o.Claim.Holder)
192 if err != nil || o.Claim.Holder == nil {
193 return
194 }
195 live, err := park.LiveGenerations(o.Claim, v, o.Claim.Holder.Location)
196 t.Log("park live", live, err)
197 versions, err := engine.Store.ListVersions(t.Context(), engine.Bucket, o.Claim.Holder.Location+"/")
198 t.Log("park listing", err)
199 for _, version := range versions {
200 if strings.HasSuffix(version.Key, "/lock.json") {
201 continue
202 }
203 generation, known := v.GenerationOf(park.Ref{Key: version.Key, VersionID: version.VersionID})
204 t.Logf("park version=%+v generation=%q known=%v", version, generation, known)
205 }
206}
207
208func TestPlan14LifecycleTraceStreams(t *testing.T) {
209 sink := httptest.NewRecorder()
210 w := &lifecycleResponse{ResponseWriter: sink}
211 if _, err := w.Write([]byte("fence")); err != nil {
212 t.Fatal(err)
213 }
214 if err := http.NewResponseController(w).Flush(); err != nil {
215 t.Fatal(err)
216 }
217 if !sink.Flushed || sink.Body.String() != "fence" {
218 t.Fatal("drain frame buffered")
219 }
220}
221
222func lifecycleStackTrace(t *testing.T) {
223 signals := make(chan os.Signal, 1)
224 signal.Notify(signals, syscall.SIGUSR1)
225 done := make(chan struct{})
226 t.Cleanup(func() { signal.Stop(signals); close(done) })
227 go func() {
228 for {
229 select {
230 case <-done:
231 return
232 case <-signals:
233 buf := make([]byte, 2<<20)
234 n := runtime.Stack(buf, true)
235 t.Logf("lifecycle goroutines:\n%s", buf[:n])
236 }
237 }
238 }()
239}
240
241func lifecycleCrashDiagnostic(t *testing.T) {
242 for _, unit := range []string{"carlos-edge.service", "carlos-instance-control.service"} {
243 out, err := exec.Command("systemctl", "kill", "--kill-who=main", "--signal=SIGUSR1", unit).CombinedOutput()
244 t.Logf("stack signal %s: %s %v", unit, out, err)
245 }
246 time.Sleep(time.Second)
247 out, err := exec.Command("journalctl", "-u", "carlos-edge.service", "--no-pager", "-o", "cat").CombinedOutput()
248 t.Logf("full edge log: %s %v", out, err)
249}
250