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}