v0.4.4 edcdd4c

cache_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package vm
4
5import (
6	"context"
7	"net"
8	"net/http"
9	"os"
10	"strings"
11	"testing"
12	"time"
13
14	"bnema.dev/ermite-runner/internal/sandbox"
15	"bnema.dev/ermite-runner/internal/sandbox/sandboxtest"
16)
17
18func startVM(t *testing.T, d *Driver, job string) *vmSandbox {
19	t.Helper()
20	sb, err := d.New()
21	if err != nil {
22		t.Fatal(err)
23	}
24	if _, err := sb.Start(context.Background(), sandbox.JobSpec{JobID: job}); err != nil {
25		t.Fatal(err)
26	}
27	return sb.(*vmSandbox)
28}
29
30// Each VM serves the cache on its own host-side vsock socket, <uds>_<cache
31// port> in the VM's own directory (the path Firecracker connects to when the
32// guest connects to the host on the cache port), for its own job only. Stopping
33// one VM closes its channel and leaves the other alone.
34func TestCacheVsockSocketPerVM(t *testing.T) {
35	e := newEnv(t, "")
36	svc := sandboxtest.NewCache(t)
37	e.cfg.Cache = svc
38	d := e.driver(t)
39	a, b := startVM(t, d, "job-a"), startVM(t, d, "job-b")
40	tokA, tokB := sandboxtest.CacheToken(svc, "job-a"), sandboxtest.CacheToken(svc, "job-b")
41
42	if a.cacheSocket() == b.cacheSocket() {
43		t.Fatal("two VMs share a cache socket")
44	}
45	for _, sb := range []*vmSandbox{a, b} {
46		if !strings.HasSuffix(sb.cacheSocket(), "_9089") || !strings.HasPrefix(sb.cacheSocket(), sb.lay.root+"/") {
47			t.Fatalf("cache socket %q is not <vm dir>/v.sock_9089", sb.cacheSocket())
48		}
49	}
50	for _, tc := range []struct {
51		sb   *vmSandbox
52		tok  string
53		want int
54	}{
55		{a, tokA, http.StatusNoContent},
56		{b, tokB, http.StatusNoContent},
57		{a, tokB, http.StatusForbidden},
58		{b, tokA, http.StatusForbidden},
59	} {
60		got, err := sandboxtest.CacheLookup(tc.sb.cacheSocket(), tc.tok)
61		if err != nil || got != tc.want {
62			t.Errorf("%s: status %d err %v, want %d", tc.sb.ID(), got, err, tc.want)
63		}
64	}
65
66	if err := a.Stop(context.Background()); err != nil {
67		t.Fatal(err)
68	}
69	if c, err := net.Dial("unix", a.cacheSocket()); err == nil {
70		_ = c.Close()
71		t.Fatal("A's cache socket answers after Stop")
72	}
73	if got, err := sandboxtest.CacheLookup(b.cacheSocket(), tokB); err != nil || got != http.StatusNoContent {
74		t.Fatalf("B after A's Stop: %d %v", got, err)
75	}
76	if err := b.Stop(context.Background()); err != nil {
77		t.Fatal(err)
78	}
79	if left := e.leftovers(t); len(left) != 0 {
80		t.Fatalf("leftovers: %v", left)
81	}
82}
83
84// A boot that fails after the channel was opened closes it.
85func TestCacheChannelClosedWhenBootFails(t *testing.T) {
86	e := newEnv(t, "hang") // the VMM never lets the executor connect
87	svc := sandboxtest.NewCache(t)
88	e.cfg.Cache = svc
89	d, err := New(context.Background(), e.cfg, Options{Run: e.run, Net: e.plumb, BootTimeout: 300 * time.Millisecond})
90	if err != nil {
91		t.Fatal(err)
92	}
93	t.Cleanup(func() { _ = d.Close() })
94	sb, _ := d.New()
95	if _, err := sb.Start(context.Background(), sandbox.JobSpec{JobID: "j"}); err == nil || !strings.Contains(err.Error(), "did not connect") {
96		t.Fatalf("expected a boot timeout, got %v", err)
97	}
98	_ = sb.Stop(context.Background())
99	ln, err := net.Listen("unix", t.TempDir()+"/s")
100	if err != nil {
101		t.Fatal(err)
102	}
103	if err := svc.Serve(sb.ID(), ln, "j"); err != nil {
104		t.Fatalf("the channel of the failed boot is still registered: %v", err)
105	}
106}
107
108// With no cache service the VM has no cache socket.
109func TestNoCacheSocketWithoutService(t *testing.T) {
110	e := newEnv(t, "")
111	d := e.driver(t)
112	sb := startVM(t, d, "j")
113	if _, err := os.Stat(sb.cacheSocket()); err == nil {
114		t.Fatal("a cache socket exists with no cache service")
115	}
116	_ = sb.Stop(context.Background())
117}
118
119// The socket path limit covers the cache socket too.
120func TestSocketPathsIncludeTheCacheSocket(t *testing.T) {
121	var found bool
122	for _, p := range socketPaths("/var/lib/ermite", "firecracker", true) {
123		found = found || strings.HasSuffix(p, "_9089")
124	}
125	if !found {
126		t.Fatal("the cache socket is not checked against the sun_path limit")
127	}
128}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close