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  }