gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/e2e/integration_runtime_test.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package integration provides end-to-end integration tests for runsc. 16 // 17 // Each test calls docker commands to start up a container, and tests that it is 18 // behaving properly, with various runsc commands. The container is killed and 19 // deleted at the end. 20 // 21 // Setup instruction in test/README.md. 22 package integration 23 24 import ( 25 "context" 26 "flag" 27 "fmt" 28 "io/ioutil" 29 "net" 30 "os" 31 "path/filepath" 32 "strconv" 33 "strings" 34 "sync" 35 "testing" 36 "time" 37 38 "github.com/docker/docker/api/types/mount" 39 "golang.org/x/sys/unix" 40 "gvisor.dev/gvisor/pkg/test/dockerutil" 41 "gvisor.dev/gvisor/pkg/test/testutil" 42 "gvisor.dev/gvisor/runsc/boot" 43 ) 44 45 const ( 46 // defaultWait is the default wait time used for tests. 47 defaultWait = time.Minute 48 49 memInfoCmd = "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'" 50 ) 51 52 func TestMain(m *testing.M) { 53 dockerutil.EnsureSupportedDockerVersion() 54 flag.Parse() 55 os.Exit(m.Run()) 56 } 57 58 func TestRlimitNoFile(t *testing.T) { 59 ctx := context.Background() 60 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-fdlimit") 61 defer d.CleanUp(ctx) 62 63 // Create a directory with a bunch of files. 64 const nfiles = 5000 65 tmpDir := testutil.TmpDir() 66 for i := 0; i < nfiles; i++ { 67 if _, err := ioutil.TempFile(tmpDir, "tmp"); err != nil { 68 t.Fatalf("TempFile(): %v", err) 69 } 70 } 71 72 // Run the container. Open a bunch of files simutaneously and sleep a bit 73 // to give time for everything to start. We should hit the FD limit and 74 // fail rather than waiting the full sleep duration. 75 cmd := `for file in /tmp/foo/*; do (cat > "${file}") & done && sleep 60` 76 got, err := d.Run(ctx, dockerutil.RunOpts{ 77 Image: "basic/ubuntu", 78 Mounts: []mount.Mount{ 79 { 80 Type: mount.TypeBind, 81 Source: tmpDir, 82 Target: "/tmp/foo", 83 }, 84 }, 85 }, "bash", "-c", cmd) 86 if err == nil { 87 t.Fatalf("docker run didn't fail: %s", got) 88 } else if strings.Contains(err.Error(), "Unknown runtime specified") { 89 t.Fatalf("docker failed because -fdlimit runtime was not installed") 90 } 91 } 92 93 func TestDentryCacheLimit(t *testing.T) { 94 ctx := context.Background() 95 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-dcache") 96 defer d.CleanUp(ctx) 97 98 // Create a directory with a bunch of files. 99 const nfiles = 5000 100 tmpDir := testutil.TmpDir() 101 for i := 0; i < nfiles; i++ { 102 if _, err := ioutil.TempFile(tmpDir, "tmp"); err != nil { 103 t.Fatalf("TempFile(): %v", err) 104 } 105 } 106 107 // Run the container. Open a bunch of files simutaneously and sleep a bit 108 // to give time for everything to start. We shouldn't hit the FD limit 109 // because the dentry cache is small. 110 cmd := `for file in /tmp/foo/*; do (cat > "${file}") & done && sleep 10` 111 got, err := d.Run(ctx, dockerutil.RunOpts{ 112 Image: "basic/ubuntu", 113 Mounts: []mount.Mount{ 114 { 115 Type: mount.TypeBind, 116 Source: tmpDir, 117 Target: "/tmp/foo", 118 }, 119 }, 120 }, "bash", "-c", cmd) 121 if err != nil { 122 t.Fatalf("docker failed: %v, %s", err, got) 123 } 124 } 125 126 // NOTE(gvisor.dev/issue/8126): Regression test. 127 func TestHostSocketConnect(t *testing.T) { 128 ctx := context.Background() 129 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-host-uds") 130 defer d.CleanUp(ctx) 131 132 tmpDir := testutil.TmpDir() 133 tmpDirFD, err := unix.Open(tmpDir, unix.O_PATH, 0) 134 if err != nil { 135 t.Fatalf("open error: %v", err) 136 } 137 defer unix.Close(tmpDirFD) 138 // Use /proc/self/fd to generate path to avoid EINVAL on large path. 139 l, err := net.Listen("unix", filepath.Join("/proc/self/fd", strconv.Itoa(tmpDirFD), "test.sock")) 140 if err != nil { 141 t.Fatalf("listen error: %v", err) 142 } 143 defer l.Close() 144 145 var wg sync.WaitGroup 146 wg.Add(1) 147 go func() { 148 defer wg.Done() 149 conn, err := l.Accept() 150 if err != nil { 151 t.Errorf("accept error: %v", err) 152 return 153 } 154 155 conn.SetReadDeadline(time.Now().Add(30 * time.Second)) 156 var buf [5]byte 157 if _, err := conn.Read(buf[:]); err != nil { 158 t.Errorf("read error: %v", err) 159 return 160 } 161 162 if want := "Hello"; string(buf[:]) != want { 163 t.Errorf("expected %s, got %v", want, string(buf[:])) 164 } 165 }() 166 167 opts := dockerutil.RunOpts{ 168 Image: "basic/integrationtest", 169 WorkDir: "/root", 170 Mounts: []mount.Mount{ 171 { 172 Type: mount.TypeBind, 173 Source: filepath.Join(tmpDir, "test.sock"), 174 Target: "/test.sock", 175 }, 176 }, 177 } 178 if _, err := d.Run(ctx, opts, "./host_connect", "/test.sock"); err != nil { 179 t.Fatalf("docker run failed: %v", err) 180 } 181 wg.Wait() 182 } 183 184 func TestOverlayNameTooLong(t *testing.T) { 185 ctx := context.Background() 186 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-overlay") 187 defer d.CleanUp(ctx) 188 189 opts := dockerutil.RunOpts{ 190 Image: "basic/ubuntu", 191 } 192 longName := strings.Repeat("a", unix.NAME_MAX+1) 193 if got, err := d.Run(ctx, opts, "bash", "-c", fmt.Sprintf("stat %s || true", longName)); err != nil { 194 t.Fatalf("docker run failed: %v", err) 195 } else if want := "File name too long"; !strings.Contains(got, want) { 196 t.Errorf("container output %q does not contain %q", got, want) 197 } 198 } 199 200 // Tests that the overlay backing host file inside the container's rootfs is 201 // hidden from the application. 202 func TestOverlayRootfsWhiteout(t *testing.T) { 203 ctx := context.Background() 204 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-overlay") 205 defer d.CleanUp(ctx) 206 207 opts := dockerutil.RunOpts{ 208 Image: "basic/ubuntu", 209 } 210 if got, err := d.Run(ctx, opts, "bash", "-c", fmt.Sprintf("ls -al / | grep %q || true", boot.SelfFilestorePrefix)); err != nil { 211 t.Fatalf("docker run failed: %s, %v", got, err) 212 } else if got != "" { 213 t.Errorf("root directory contains a file/directory whose name contains %q: output = %q", boot.SelfFilestorePrefix, got) 214 } 215 } 216 217 func TestOverlayCheckpointRestore(t *testing.T) { 218 if !testutil.IsCheckpointSupported() { 219 t.Skip("Checkpoint is not supported.") 220 } 221 dockerutil.EnsureDockerExperimentalEnabled() 222 223 dir, err := os.MkdirTemp(testutil.TmpDir(), "submount") 224 if err != nil { 225 t.Fatalf("MkdirTemp(): %v", err) 226 } 227 defer os.RemoveAll(dir) 228 229 ctx := context.Background() 230 d := dockerutil.MakeContainerWithRuntime(ctx, t, "-overlay") 231 defer d.CleanUp(ctx) 232 233 opts := dockerutil.RunOpts{ 234 Image: "basic/alpine", 235 Mounts: []mount.Mount{ 236 { 237 Type: mount.TypeBind, 238 Source: dir, 239 Target: "/submount", 240 }, 241 }, 242 } 243 if err := d.Spawn(ctx, opts, "sleep", "infinity"); err != nil { 244 t.Fatalf("docker run failed: %v", err) 245 } 246 247 // Create files in rootfs and submount. 248 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo rootfs > /file"); err != nil { 249 t.Fatalf("docker exec failed: %v", err) 250 } 251 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo submount > /submount/file"); err != nil { 252 t.Fatalf("docker exec failed: %v", err) 253 } 254 // Check that the file was not created on host. 255 if _, err := os.Stat(filepath.Join(dir, "file")); err == nil || !os.IsNotExist(err) { 256 t.Fatalf("file was created on host, expected err = ENOENT, got %v", err) 257 } 258 259 // Create a snapshot. 260 if err := d.Checkpoint(ctx, "test"); err != nil { 261 t.Fatalf("docker checkpoint failed: %v", err) 262 } 263 if err := d.WaitTimeout(ctx, defaultWait); err != nil { 264 t.Fatalf("wait failed: %v", err) 265 } 266 267 // Restore the snapshot. 268 // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. 269 if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { 270 t.Fatalf("docker restore failed: %v", err) 271 } 272 273 // Make sure the files are restored in the overlay. 274 if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/file"); err != nil || got != "rootfs\n" { 275 t.Errorf("cat /file returned: output = %q, err = %v", got, err) 276 } 277 if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/submount/file"); err != nil || got != "submount\n" { 278 t.Errorf("cat /submount/file returned: output = %q, err = %v", got, err) 279 } 280 }