github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/e2e/integration_test.go (about) 1 // Copyright 2018 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 "net/http" 31 "os" 32 "path/filepath" 33 "strconv" 34 "strings" 35 "testing" 36 "time" 37 38 "github.com/docker/docker/api/types/mount" 39 "github.com/SagerNet/gvisor/pkg/test/dockerutil" 40 "github.com/SagerNet/gvisor/pkg/test/testutil" 41 ) 42 43 // defaultWait is the default wait time used for tests. 44 const defaultWait = time.Minute 45 46 // httpRequestSucceeds sends a request to a given url and checks that the status is OK. 47 func httpRequestSucceeds(client http.Client, server string, port int) error { 48 url := fmt.Sprintf("http://%s:%d", server, port) 49 // Ensure that content is being served. 50 resp, err := client.Get(url) 51 if err != nil { 52 return fmt.Errorf("error reaching http server: %v", err) 53 } 54 if want := http.StatusOK; resp.StatusCode != want { 55 return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) 56 } 57 return nil 58 } 59 60 // TestLifeCycle tests a basic Create/Start/Stop docker container life cycle. 61 func TestLifeCycle(t *testing.T) { 62 ctx := context.Background() 63 d := dockerutil.MakeContainer(ctx, t) 64 defer d.CleanUp(ctx) 65 66 // Start the container. 67 port := 80 68 if err := d.Create(ctx, dockerutil.RunOpts{ 69 Image: "basic/nginx", 70 Ports: []int{port}, 71 }); err != nil { 72 t.Fatalf("docker create failed: %v", err) 73 } 74 if err := d.Start(ctx); err != nil { 75 t.Fatalf("docker start failed: %v", err) 76 } 77 78 ip, err := d.FindIP(ctx, false) 79 if err != nil { 80 t.Fatalf("docker.FindIP failed: %v", err) 81 } 82 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 83 t.Fatalf("WaitForHTTP() timeout: %v", err) 84 } 85 client := http.Client{Timeout: defaultWait} 86 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 87 t.Errorf("http request failed: %v", err) 88 } 89 90 if err := d.Stop(ctx); err != nil { 91 t.Fatalf("docker stop failed: %v", err) 92 } 93 if err := d.Remove(ctx); err != nil { 94 t.Fatalf("docker rm failed: %v", err) 95 } 96 } 97 98 func TestPauseResume(t *testing.T) { 99 if !testutil.IsCheckpointSupported() { 100 t.Skip("Checkpoint is not supported.") 101 } 102 103 ctx := context.Background() 104 d := dockerutil.MakeContainer(ctx, t) 105 defer d.CleanUp(ctx) 106 107 // Start the container. 108 port := 8080 109 if err := d.Spawn(ctx, dockerutil.RunOpts{ 110 Image: "basic/python", 111 Ports: []int{port}, // See Dockerfile. 112 }); err != nil { 113 t.Fatalf("docker run failed: %v", err) 114 } 115 116 // Find container IP address. 117 ip, err := d.FindIP(ctx, false) 118 if err != nil { 119 t.Fatalf("docker.FindIP failed: %v", err) 120 } 121 122 // Wait until it's up and running. 123 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 124 t.Fatalf("WaitForHTTP() timeout: %v", err) 125 } 126 127 // Check that container is working. 128 client := http.Client{Timeout: defaultWait} 129 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 130 t.Error("http request failed:", err) 131 } 132 133 if err := d.Pause(ctx); err != nil { 134 t.Fatalf("docker pause failed: %v", err) 135 } 136 137 // Check if container is paused. 138 client = http.Client{Timeout: 10 * time.Millisecond} // Don't wait a minute. 139 switch _, err := client.Get(fmt.Sprintf("http://%s:%d", ip.String(), port)); v := err.(type) { 140 case nil: 141 t.Errorf("http req expected to fail but it succeeded") 142 case net.Error: 143 if !v.Timeout() { 144 t.Errorf("http req got error %v, wanted timeout", v) 145 } 146 default: 147 t.Errorf("http req got unexpected error %v", v) 148 } 149 150 if err := d.Unpause(ctx); err != nil { 151 t.Fatalf("docker unpause failed: %v", err) 152 } 153 154 // Wait until it's up and running. 155 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 156 t.Fatalf("WaitForHTTP() timeout: %v", err) 157 } 158 159 // Check if container is working again. 160 client = http.Client{Timeout: defaultWait} 161 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 162 t.Error("http request failed:", err) 163 } 164 } 165 166 func TestCheckpointRestore(t *testing.T) { 167 if !testutil.IsCheckpointSupported() { 168 t.Skip("Pause/resume is not supported.") 169 } 170 171 ctx := context.Background() 172 d := dockerutil.MakeContainer(ctx, t) 173 defer d.CleanUp(ctx) 174 175 // Start the container. 176 port := 8080 177 if err := d.Spawn(ctx, dockerutil.RunOpts{ 178 Image: "basic/python", 179 Ports: []int{port}, // See Dockerfile. 180 }); err != nil { 181 t.Fatalf("docker run failed: %v", err) 182 } 183 184 // Create a snapshot. 185 if err := d.Checkpoint(ctx, "test"); err != nil { 186 t.Fatalf("docker checkpoint failed: %v", err) 187 } 188 if err := d.WaitTimeout(ctx, defaultWait); err != nil { 189 t.Fatalf("wait failed: %v", err) 190 } 191 192 // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. 193 if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { 194 t.Fatalf("docker restore failed: %v", err) 195 } 196 197 // Find container IP address. 198 ip, err := d.FindIP(ctx, false) 199 if err != nil { 200 t.Fatalf("docker.FindIP failed: %v", err) 201 } 202 203 // Wait until it's up and running. 204 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 205 t.Fatalf("WaitForHTTP() timeout: %v", err) 206 } 207 208 // Check if container is working again. 209 client := http.Client{Timeout: defaultWait} 210 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 211 t.Error("http request failed:", err) 212 } 213 } 214 215 // Create client and server that talk to each other using the local IP. 216 func TestConnectToSelf(t *testing.T) { 217 ctx := context.Background() 218 d := dockerutil.MakeContainer(ctx, t) 219 defer d.CleanUp(ctx) 220 221 // Creates server that replies "server" and exists. Sleeps at the end because 222 // 'docker exec' gets killed if the init process exists before it can finish. 223 if err := d.Spawn(ctx, dockerutil.RunOpts{ 224 Image: "basic/ubuntu", 225 }, "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil { 226 t.Fatalf("docker run failed: %v", err) 227 } 228 229 // Finds IP address for host. 230 ip, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'") 231 if err != nil { 232 t.Fatalf("docker exec failed: %v", err) 233 } 234 ip = strings.TrimRight(ip, "\n") 235 236 // Runs client that sends "client" to the server and exits. 237 reply, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip)) 238 if err != nil { 239 t.Fatalf("docker exec failed: %v", err) 240 } 241 242 // Ensure both client and server got the message from each other. 243 if want := "server\n"; reply != want { 244 t.Errorf("Error on server, want: %q, got: %q", want, reply) 245 } 246 if _, err := d.WaitForOutput(ctx, "^client\n$", defaultWait); err != nil { 247 t.Fatalf("docker.WaitForOutput(client) timeout: %v", err) 248 } 249 } 250 251 func TestMemLimit(t *testing.T) { 252 ctx := context.Background() 253 d := dockerutil.MakeContainer(ctx, t) 254 defer d.CleanUp(ctx) 255 256 allocMemoryKb := 50 * 1024 257 out, err := d.Run(ctx, dockerutil.RunOpts{ 258 Image: "basic/alpine", 259 Memory: allocMemoryKb * 1024, // In bytes. 260 }, "sh", "-c", "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'") 261 if err != nil { 262 t.Fatalf("docker run failed: %v", err) 263 } 264 265 // Remove warning message that swap isn't present. 266 if strings.HasPrefix(out, "WARNING") { 267 lines := strings.Split(out, "\n") 268 if len(lines) != 3 { 269 t.Fatalf("invalid output: %s", out) 270 } 271 out = lines[1] 272 } 273 274 // Ensure the memory matches what we want. 275 got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) 276 if err != nil { 277 t.Fatalf("failed to parse %q: %v", out, err) 278 } 279 if want := uint64(allocMemoryKb); got != want { 280 t.Errorf("MemTotal got: %d, want: %d", got, want) 281 } 282 } 283 284 func TestNumCPU(t *testing.T) { 285 ctx := context.Background() 286 d := dockerutil.MakeContainer(ctx, t) 287 defer d.CleanUp(ctx) 288 289 // Read how many cores are in the container. 290 out, err := d.Run(ctx, dockerutil.RunOpts{ 291 Image: "basic/alpine", 292 CpusetCpus: "0", 293 }, "sh", "-c", "cat /proc/cpuinfo | grep 'processor.*:' | wc -l") 294 if err != nil { 295 t.Fatalf("docker run failed: %v", err) 296 } 297 298 // Ensure it matches what we want. 299 got, err := strconv.Atoi(strings.TrimSpace(out)) 300 if err != nil { 301 t.Fatalf("failed to parse %q: %v", out, err) 302 } 303 if want := 1; got != want { 304 t.Errorf("MemTotal got: %d, want: %d", got, want) 305 } 306 } 307 308 // TestJobControl tests that job control characters are handled properly. 309 func TestJobControl(t *testing.T) { 310 ctx := context.Background() 311 d := dockerutil.MakeContainer(ctx, t) 312 defer d.CleanUp(ctx) 313 314 // Start the container with an attached PTY. 315 p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{ 316 Image: "basic/alpine", 317 }, "sh", "-c", "sleep 100 | cat") 318 if err != nil { 319 t.Fatalf("docker run failed: %v", err) 320 } 321 // Give shell a few seconds to start executing the sleep. 322 time.Sleep(2 * time.Second) 323 324 if _, err := p.Write(time.Second, []byte{0x03}); err != nil { 325 t.Fatalf("error exit: %v", err) 326 } 327 328 if err := d.WaitTimeout(ctx, 3*time.Second); err != nil { 329 t.Fatalf("WaitTimeout failed: %v", err) 330 } 331 332 want := 130 333 got, err := p.WaitExitStatus(ctx) 334 if err != nil { 335 t.Fatalf("wait for exit failed with: %v", err) 336 } else if got != want { 337 t.Fatalf("got: %d want: %d", got, want) 338 } 339 } 340 341 // TestWorkingDirCreation checks that working dir is created if it doesn't exit. 342 func TestWorkingDirCreation(t *testing.T) { 343 for _, tc := range []struct { 344 name string 345 workingDir string 346 }{ 347 {name: "root", workingDir: "/foo"}, 348 {name: "tmp", workingDir: "/tmp/foo"}, 349 } { 350 for _, readonly := range []bool{true, false} { 351 name := tc.name 352 if readonly { 353 name += "-readonly" 354 } 355 t.Run(name, func(t *testing.T) { 356 ctx := context.Background() 357 d := dockerutil.MakeContainer(ctx, t) 358 defer d.CleanUp(ctx) 359 360 opts := dockerutil.RunOpts{ 361 Image: "basic/alpine", 362 WorkDir: tc.workingDir, 363 ReadOnly: readonly, 364 } 365 got, err := d.Run(ctx, opts, "sh", "-c", "echo ${PWD}") 366 if err != nil { 367 t.Fatalf("docker run failed: %v", err) 368 } 369 if want := tc.workingDir + "\n"; want != got { 370 t.Errorf("invalid working dir, want: %q, got: %q", want, got) 371 } 372 }) 373 } 374 } 375 } 376 377 // TestTmpFile checks that files inside '/tmp' are not overridden. 378 func TestTmpFile(t *testing.T) { 379 ctx := context.Background() 380 d := dockerutil.MakeContainer(ctx, t) 381 defer d.CleanUp(ctx) 382 383 opts := dockerutil.RunOpts{Image: "basic/tmpfile"} 384 got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") 385 if err != nil { 386 t.Fatalf("docker run failed: %v", err) 387 } 388 if want := "123\n"; want != got { 389 t.Errorf("invalid file content, want: %q, got: %q", want, got) 390 } 391 } 392 393 // TestTmpMount checks that mounts inside '/tmp' are not overridden. 394 func TestTmpMount(t *testing.T) { 395 dir, err := ioutil.TempDir(testutil.TmpDir(), "tmp-mount") 396 if err != nil { 397 t.Fatalf("TempDir(): %v", err) 398 } 399 const want = "123" 400 if err := ioutil.WriteFile(filepath.Join(dir, "file.txt"), []byte("123"), 0666); err != nil { 401 t.Fatalf("WriteFile(): %v", err) 402 } 403 ctx := context.Background() 404 d := dockerutil.MakeContainer(ctx, t) 405 defer d.CleanUp(ctx) 406 407 opts := dockerutil.RunOpts{ 408 Image: "basic/alpine", 409 Mounts: []mount.Mount{ 410 { 411 Type: mount.TypeBind, 412 Source: dir, 413 Target: "/tmp/foo", 414 }, 415 }, 416 } 417 got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") 418 if err != nil { 419 t.Fatalf("docker run failed: %v", err) 420 } 421 if want != got { 422 t.Errorf("invalid file content, want: %q, got: %q", want, got) 423 } 424 } 425 426 // Test that it is allowed to mount a file on top of /dev files, e.g. 427 // /dev/random. 428 func TestMountOverDev(t *testing.T) { 429 if usingVFS2, err := dockerutil.UsingVFS2(); !usingVFS2 { 430 t.Skip("VFS1 doesn't allow /dev/random to be mounted.") 431 } else if err != nil { 432 t.Fatalf("Failed to read config for runtime %s: %v", dockerutil.Runtime(), err) 433 } 434 435 random, err := ioutil.TempFile(testutil.TmpDir(), "random") 436 if err != nil { 437 t.Fatal("ioutil.TempFile() failed:", err) 438 } 439 const want = "123" 440 if _, err := random.WriteString(want); err != nil { 441 t.Fatalf("WriteString() to %q: %v", random.Name(), err) 442 } 443 444 ctx := context.Background() 445 d := dockerutil.MakeContainer(ctx, t) 446 defer d.CleanUp(ctx) 447 448 opts := dockerutil.RunOpts{ 449 Image: "basic/alpine", 450 Mounts: []mount.Mount{ 451 { 452 Type: mount.TypeBind, 453 Source: random.Name(), 454 Target: "/dev/random", 455 }, 456 }, 457 } 458 cmd := "dd count=1 bs=5 if=/dev/random 2> /dev/null" 459 got, err := d.Run(ctx, opts, "sh", "-c", cmd) 460 if err != nil { 461 t.Fatalf("docker run failed: %v", err) 462 } 463 if want != got { 464 t.Errorf("invalid file content, want: %q, got: %q", want, got) 465 } 466 } 467 468 // TestSyntheticDirs checks that submounts can be created inside a readonly 469 // mount even if the target path does not exist. 470 func TestSyntheticDirs(t *testing.T) { 471 ctx := context.Background() 472 d := dockerutil.MakeContainer(ctx, t) 473 defer d.CleanUp(ctx) 474 475 opts := dockerutil.RunOpts{ 476 Image: "basic/alpine", 477 // Make the root read-only to force use of synthetic dirs 478 // inside the root gofer mount. 479 ReadOnly: true, 480 Mounts: []mount.Mount{ 481 // Mount inside read-only gofer-backed root. 482 { 483 Type: mount.TypeTmpfs, 484 Target: "/foo/bar/baz", 485 }, 486 // Mount inside sysfs, which always uses synthetic dirs 487 // for submounts. 488 { 489 Type: mount.TypeTmpfs, 490 Target: "/sys/foo/bar/baz", 491 }, 492 }, 493 } 494 // Make sure the directories exist. 495 if _, err := d.Run(ctx, opts, "ls", "/foo/bar/baz", "/sys/foo/bar/baz"); err != nil { 496 t.Fatalf("docker run failed: %v", err) 497 } 498 499 } 500 501 // TestHostOverlayfsCopyUp tests that the --overlayfs-stale-read option causes 502 // runsc to hide the incoherence of FDs opened before and after overlayfs 503 // copy-up on the host. 504 func TestHostOverlayfsCopyUp(t *testing.T) { 505 runIntegrationTest(t, nil, "./test_copy_up") 506 } 507 508 // TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory 509 // stream to refer to the current state of the corresponding directory, as a 510 // call to opendir() would have done" as required by POSIX, when the directory 511 // in question is host overlayfs. 512 // 513 // This test specifically targets host overlayfs because, per POSIX, "if a file 514 // is removed from or added to the directory after the most recent call to 515 // opendir() or rewinddir(), whether a subsequent call to readdir() returns an 516 // entry for that file is unspecified"; the host filesystems used by other 517 // automated tests yield newly-added files from readdir() even if the fsgofer 518 // does not explicitly rewinddir(), but overlayfs does not. 519 func TestHostOverlayfsRewindDir(t *testing.T) { 520 runIntegrationTest(t, nil, "./test_rewinddir") 521 } 522 523 // Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it 524 // cannot use tricks like userns as root. For this reason, run a basic link test 525 // to ensure some coverage. 526 func TestLink(t *testing.T) { 527 runIntegrationTest(t, nil, "./link_test") 528 } 529 530 // This test ensures we can run ping without errors. 531 func TestPing4Loopback(t *testing.T) { 532 if testutil.IsRunningWithHostNet() { 533 // TODO(github.com/SagerNet/issue/5011): support ICMP sockets in hostnet and enable 534 // this test. 535 t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.") 536 } 537 538 runIntegrationTest(t, nil, "./ping4.sh") 539 } 540 541 // This test ensures we can enable ipv6 on loopback and run ping6 without 542 // errors. 543 func TestPing6Loopback(t *testing.T) { 544 if testutil.IsRunningWithHostNet() { 545 // TODO(github.com/SagerNet/issue/5011): support ICMP sockets in hostnet and enable 546 // this test. 547 t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.") 548 } 549 550 // The CAP_NET_ADMIN capability is required to use the `ip` utility, which 551 // we use to enable ipv6 on loopback. 552 // 553 // By default, ipv6 loopback is not enabled by runsc, because docker does 554 // not assign an ipv6 address to the test container. 555 runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh") 556 } 557 558 // This test checks that the owner of the sticky directory can delete files 559 // inside it belonging to other users. It also checks that the owner of a file 560 // can always delete its file when the file is inside a sticky directory owned 561 // by another user. 562 func TestStickyDir(t *testing.T) { 563 if vfs2Used, err := dockerutil.UsingVFS2(); err != nil { 564 t.Fatalf("failed to read config for runtime %s: %v", dockerutil.Runtime(), err) 565 } else if !vfs2Used { 566 t.Skip("sticky bit test fails on VFS1.") 567 } 568 569 runIntegrationTest(t, nil, "./test_sticky") 570 } 571 572 func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { 573 ctx := context.Background() 574 d := dockerutil.MakeContainer(ctx, t) 575 defer d.CleanUp(ctx) 576 577 if got, err := d.Run(ctx, dockerutil.RunOpts{ 578 Image: "basic/integrationtest", 579 WorkDir: "/root", 580 CapAdd: capAdd, 581 }, args...); err != nil { 582 t.Fatalf("docker run failed: %v", err) 583 } else if got != "" { 584 t.Errorf("test failed:\n%s", got) 585 } 586 } 587 588 // Test that UDS can be created using overlay when parent directory is in lower 589 // layer only (b/134090485). 590 // 591 // Prerequisite: the directory where the socket file is created must not have 592 // been open for write before bind(2) is called. 593 func TestBindOverlay(t *testing.T) { 594 ctx := context.Background() 595 d := dockerutil.MakeContainer(ctx, t) 596 defer d.CleanUp(ctx) 597 598 // Run the container. 599 got, err := d.Run(ctx, dockerutil.RunOpts{ 600 Image: "basic/ubuntu", 601 }, "bash", "-c", "nc -q -1 -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -q 0 -U /var/run/sock && wait $p") 602 if err != nil { 603 t.Fatalf("docker run failed: %v", err) 604 } 605 606 // Check the output contains what we want. 607 if want := "foobar-asdf"; !strings.Contains(got, want) { 608 t.Fatalf("docker run output is missing %q: %s", want, got) 609 } 610 } 611 612 func TestMain(m *testing.M) { 613 dockerutil.EnsureSupportedDockerVersion() 614 flag.Parse() 615 os.Exit(m.Run()) 616 }