gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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  	"bytes"
    26  	"context"
    27  	"flag"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"net"
    31  	"net/http"
    32  	"os"
    33  	"os/exec"
    34  	"path/filepath"
    35  	"regexp"
    36  	"strconv"
    37  	"strings"
    38  	"testing"
    39  	"time"
    40  
    41  	"github.com/docker/docker/api/types/mount"
    42  	"golang.org/x/sys/unix"
    43  	"gvisor.dev/gvisor/pkg/test/dockerutil"
    44  	"gvisor.dev/gvisor/pkg/test/testutil"
    45  )
    46  
    47  const (
    48  	// defaultWait is the default wait time used for tests.
    49  	defaultWait = time.Minute
    50  
    51  	memInfoCmd = "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'"
    52  )
    53  
    54  func TestMain(m *testing.M) {
    55  	dockerutil.EnsureSupportedDockerVersion()
    56  	flag.Parse()
    57  	os.Exit(m.Run())
    58  }
    59  
    60  // httpRequestSucceeds sends a request to a given url and checks that the status is OK.
    61  func httpRequestSucceeds(client http.Client, server string, port int) error {
    62  	url := fmt.Sprintf("http://%s:%d", server, port)
    63  	// Ensure that content is being served.
    64  	resp, err := client.Get(url)
    65  	if err != nil {
    66  		return fmt.Errorf("error reaching http server: %v", err)
    67  	}
    68  	if want := http.StatusOK; resp.StatusCode != want {
    69  		return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
    70  	}
    71  	return nil
    72  }
    73  
    74  // TestLifeCycle tests a basic Create/Start/Stop docker container life cycle.
    75  func TestLifeCycle(t *testing.T) {
    76  	ctx := context.Background()
    77  	d := dockerutil.MakeContainer(ctx, t)
    78  	defer d.CleanUp(ctx)
    79  
    80  	// Start the container.
    81  	port := 80
    82  	if err := d.Create(ctx, dockerutil.RunOpts{
    83  		Image: "basic/nginx",
    84  		Ports: []int{port},
    85  	}); err != nil {
    86  		t.Fatalf("docker create failed: %v", err)
    87  	}
    88  	if err := d.Start(ctx); err != nil {
    89  		t.Fatalf("docker start failed: %v", err)
    90  	}
    91  
    92  	ip, err := d.FindIP(ctx, false)
    93  	if err != nil {
    94  		t.Fatalf("docker.FindIP failed: %v", err)
    95  	}
    96  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
    97  		t.Fatalf("WaitForHTTP() timeout: %v", err)
    98  	}
    99  	client := http.Client{Timeout: defaultWait}
   100  	if err := httpRequestSucceeds(client, ip.String(), port); err != nil {
   101  		t.Errorf("http request failed: %v", err)
   102  	}
   103  
   104  	if err := d.Stop(ctx); err != nil {
   105  		t.Fatalf("docker stop failed: %v", err)
   106  	}
   107  	if err := d.Remove(ctx); err != nil {
   108  		t.Fatalf("docker rm failed: %v", err)
   109  	}
   110  }
   111  
   112  func TestPauseResume(t *testing.T) {
   113  	if !testutil.IsCheckpointSupported() {
   114  		t.Skip("Checkpoint is not supported.")
   115  	}
   116  
   117  	ctx := context.Background()
   118  	d := dockerutil.MakeContainer(ctx, t)
   119  	defer d.CleanUp(ctx)
   120  
   121  	// Start the container.
   122  	port := 8080
   123  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   124  		Image: "basic/python",
   125  		Ports: []int{port}, // See Dockerfile.
   126  	}); err != nil {
   127  		t.Fatalf("docker run failed: %v", err)
   128  	}
   129  
   130  	// Find container IP address.
   131  	ip, err := d.FindIP(ctx, false)
   132  	if err != nil {
   133  		t.Fatalf("docker.FindIP failed: %v", err)
   134  	}
   135  
   136  	// Wait until it's up and running.
   137  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   138  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   139  	}
   140  
   141  	// Check that container is working.
   142  	client := http.Client{Timeout: defaultWait}
   143  	if err := httpRequestSucceeds(client, ip.String(), port); err != nil {
   144  		t.Error("http request failed:", err)
   145  	}
   146  
   147  	if err := d.Pause(ctx); err != nil {
   148  		t.Fatalf("docker pause failed: %v", err)
   149  	}
   150  
   151  	// Check if container is paused.
   152  	client = http.Client{Timeout: 10 * time.Millisecond} // Don't wait a minute.
   153  	switch _, err := client.Get(fmt.Sprintf("http://%s:%d", ip.String(), port)); v := err.(type) {
   154  	case nil:
   155  		t.Errorf("http req expected to fail but it succeeded")
   156  	case net.Error:
   157  		if !v.Timeout() {
   158  			t.Errorf("http req got error %v, wanted timeout", v)
   159  		}
   160  	default:
   161  		t.Errorf("http req got unexpected error %v", v)
   162  	}
   163  
   164  	if err := d.Unpause(ctx); err != nil {
   165  		t.Fatalf("docker unpause failed: %v", err)
   166  	}
   167  
   168  	// Wait until it's up and running.
   169  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   170  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   171  	}
   172  
   173  	// Check if container is working again.
   174  	client = http.Client{Timeout: defaultWait}
   175  	if err := httpRequestSucceeds(client, ip.String(), port); err != nil {
   176  		t.Error("http request failed:", err)
   177  	}
   178  }
   179  
   180  func TestCheckpointRestore(t *testing.T) {
   181  	if !testutil.IsCheckpointSupported() {
   182  		t.Skip("Checkpoint is not supported.")
   183  	}
   184  	dockerutil.EnsureDockerExperimentalEnabled()
   185  
   186  	ctx := context.Background()
   187  	d := dockerutil.MakeContainer(ctx, t)
   188  	defer d.CleanUp(ctx)
   189  
   190  	// Start the container.
   191  	port := 8080
   192  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   193  		Image: "basic/python",
   194  		Ports: []int{port}, // See Dockerfile.
   195  	}); err != nil {
   196  		t.Fatalf("docker run failed: %v", err)
   197  	}
   198  
   199  	// Create a snapshot.
   200  	if err := d.Checkpoint(ctx, "test"); err != nil {
   201  		t.Fatalf("docker checkpoint failed: %v", err)
   202  	}
   203  	if err := d.WaitTimeout(ctx, defaultWait); err != nil {
   204  		t.Fatalf("wait failed: %v", err)
   205  	}
   206  
   207  	// TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed.
   208  	if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil {
   209  		t.Fatalf("docker restore failed: %v", err)
   210  	}
   211  
   212  	// Find container IP address.
   213  	ip, err := d.FindIP(ctx, false)
   214  	if err != nil {
   215  		t.Fatalf("docker.FindIP failed: %v", err)
   216  	}
   217  
   218  	// Wait until it's up and running.
   219  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   220  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   221  	}
   222  
   223  	// Check if container is working again.
   224  	client := http.Client{Timeout: defaultWait}
   225  	if err := httpRequestSucceeds(client, ip.String(), port); err != nil {
   226  		t.Error("http request failed:", err)
   227  	}
   228  }
   229  
   230  // Create client and server that talk to each other using the local IP.
   231  func TestConnectToSelf(t *testing.T) {
   232  	ctx := context.Background()
   233  	d := dockerutil.MakeContainer(ctx, t)
   234  	defer d.CleanUp(ctx)
   235  
   236  	// Creates server that replies "server" and exists. Sleeps at the end because
   237  	// 'docker exec' gets killed if the init process exists before it can finish.
   238  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   239  		Image: "basic/ubuntu",
   240  	}, "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil {
   241  		t.Fatalf("docker run failed: %v", err)
   242  	}
   243  
   244  	// Finds IP address for host.
   245  	ip, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'")
   246  	if err != nil {
   247  		t.Fatalf("docker exec failed: %v", err)
   248  	}
   249  	ip = strings.TrimRight(ip, "\n")
   250  
   251  	// Runs client that sends "client" to the server and exits.
   252  	reply, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip))
   253  	if err != nil {
   254  		t.Fatalf("docker exec failed: %v", err)
   255  	}
   256  
   257  	// Ensure both client and server got the message from each other.
   258  	if want := "server\n"; reply != want {
   259  		t.Errorf("Error on server, want: %q, got: %q", want, reply)
   260  	}
   261  	if _, err := d.WaitForOutput(ctx, "^client\n$", defaultWait); err != nil {
   262  		t.Fatalf("docker.WaitForOutput(client) timeout: %v", err)
   263  	}
   264  }
   265  
   266  func TestMemory(t *testing.T) {
   267  	// Find total amount of memory in the host.
   268  	host, err := exec.Command("sh", "-c", memInfoCmd).CombinedOutput()
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	want, err := strconv.ParseUint(strings.TrimSpace(string(host)), 10, 64)
   273  	if err != nil {
   274  		t.Fatalf("failed to parse %q: %v", host, err)
   275  	}
   276  
   277  	ctx := context.Background()
   278  	d := dockerutil.MakeContainer(ctx, t)
   279  	defer d.CleanUp(ctx)
   280  
   281  	out, err := d.Run(ctx, dockerutil.RunOpts{Image: "basic/alpine"}, "sh", "-c", memInfoCmd)
   282  	if err != nil {
   283  		t.Fatalf("docker run failed: %v", err)
   284  	}
   285  
   286  	// Get memory from inside the container and ensure it matches the host.
   287  	got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
   288  	if err != nil {
   289  		t.Fatalf("failed to parse %q: %v", out, err)
   290  	}
   291  	if got != want {
   292  		t.Errorf("MemTotal got: %d, want: %d", got, want)
   293  	}
   294  }
   295  
   296  func TestMemLimit(t *testing.T) {
   297  	ctx := context.Background()
   298  	d := dockerutil.MakeContainer(ctx, t)
   299  	defer d.CleanUp(ctx)
   300  
   301  	allocMemoryKb := 128 * 1024
   302  	opts := dockerutil.RunOpts{
   303  		Image:  "basic/alpine",
   304  		Memory: allocMemoryKb * 1024, // In bytes.
   305  	}
   306  	out, err := d.Run(ctx, opts, "sh", "-c", memInfoCmd)
   307  	if err != nil {
   308  		t.Fatalf("docker run failed: %v", err)
   309  	}
   310  
   311  	// Remove warning message that swap isn't present.
   312  	if strings.HasPrefix(out, "WARNING") {
   313  		lines := strings.Split(out, "\n")
   314  		if len(lines) != 3 {
   315  			t.Fatalf("invalid output: %s", out)
   316  		}
   317  		out = lines[1]
   318  	}
   319  
   320  	// Ensure the memory matches what we want.
   321  	got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
   322  	if err != nil {
   323  		t.Fatalf("failed to parse %q: %v", out, err)
   324  	}
   325  	if want := uint64(allocMemoryKb); got != want {
   326  		t.Errorf("MemTotal got: %d, want: %d", got, want)
   327  	}
   328  }
   329  
   330  func TestNumCPU(t *testing.T) {
   331  	ctx := context.Background()
   332  	d := dockerutil.MakeContainer(ctx, t)
   333  	defer d.CleanUp(ctx)
   334  
   335  	// Read how many cores are in the container.
   336  	out, err := d.Run(ctx, dockerutil.RunOpts{
   337  		Image:      "basic/alpine",
   338  		CpusetCpus: "0",
   339  	}, "sh", "-c", "cat /proc/cpuinfo | grep 'processor.*:' | wc -l")
   340  	if err != nil {
   341  		t.Fatalf("docker run failed: %v", err)
   342  	}
   343  
   344  	// Ensure it matches what we want.
   345  	got, err := strconv.Atoi(strings.TrimSpace(out))
   346  	if err != nil {
   347  		t.Fatalf("failed to parse %q: %v", out, err)
   348  	}
   349  	if want := 1; got != want {
   350  		t.Errorf("MemTotal got: %d, want: %d", got, want)
   351  	}
   352  }
   353  
   354  // TestJobControl tests that job control characters are handled properly.
   355  func TestJobControl(t *testing.T) {
   356  	ctx := context.Background()
   357  	d := dockerutil.MakeContainer(ctx, t)
   358  	defer d.CleanUp(ctx)
   359  
   360  	// Start the container with an attached PTY.
   361  	p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{
   362  		Image: "basic/alpine",
   363  	}, "sh", "-c", "sleep 100 | cat")
   364  	if err != nil {
   365  		t.Fatalf("docker run failed: %v", err)
   366  	}
   367  	// Give shell a few seconds to start executing the sleep.
   368  	time.Sleep(2 * time.Second)
   369  
   370  	if _, err := p.Write(time.Second, []byte{0x03}); err != nil {
   371  		t.Fatalf("error exit: %v", err)
   372  	}
   373  
   374  	if err := d.WaitTimeout(ctx, 3*time.Second); err != nil {
   375  		t.Fatalf("WaitTimeout failed: %v", err)
   376  	}
   377  
   378  	want := 130
   379  	got, err := p.WaitExitStatus(ctx)
   380  	if err != nil {
   381  		t.Fatalf("wait for exit failed with: %v", err)
   382  	} else if got != want {
   383  		t.Fatalf("got: %d want: %d", got, want)
   384  	}
   385  }
   386  
   387  // TestWorkingDirCreation checks that working dir is created if it doesn't exit.
   388  func TestWorkingDirCreation(t *testing.T) {
   389  	for _, tc := range []struct {
   390  		name       string
   391  		workingDir string
   392  	}{
   393  		{name: "root", workingDir: "/foo"},
   394  		{name: "tmp", workingDir: "/tmp/foo"},
   395  	} {
   396  		for _, readonly := range []bool{true, false} {
   397  			name := tc.name
   398  			if readonly {
   399  				name += "-readonly"
   400  			}
   401  			t.Run(name, func(t *testing.T) {
   402  				ctx := context.Background()
   403  				d := dockerutil.MakeContainer(ctx, t)
   404  				defer d.CleanUp(ctx)
   405  
   406  				opts := dockerutil.RunOpts{
   407  					Image:    "basic/alpine",
   408  					WorkDir:  tc.workingDir,
   409  					ReadOnly: readonly,
   410  				}
   411  				got, err := d.Run(ctx, opts, "sh", "-c", "echo ${PWD}")
   412  				if err != nil {
   413  					t.Fatalf("docker run failed: %v", err)
   414  				}
   415  				if want := tc.workingDir + "\n"; want != got {
   416  					t.Errorf("invalid working dir, want: %q, got: %q", want, got)
   417  				}
   418  			})
   419  		}
   420  	}
   421  }
   422  
   423  // TestTmpFile checks that files inside '/tmp' are not overridden.
   424  func TestTmpFile(t *testing.T) {
   425  	ctx := context.Background()
   426  	d := dockerutil.MakeContainer(ctx, t)
   427  	defer d.CleanUp(ctx)
   428  
   429  	opts := dockerutil.RunOpts{Image: "basic/tmpfile"}
   430  	got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt")
   431  	if err != nil {
   432  		t.Fatalf("docker run failed: %v", err)
   433  	}
   434  	if want := "123\n"; want != got {
   435  		t.Errorf("invalid file content, want: %q, got: %q", want, got)
   436  	}
   437  }
   438  
   439  // TestTmpMount checks that mounts inside '/tmp' are not overridden.
   440  func TestTmpMount(t *testing.T) {
   441  	dir, err := ioutil.TempDir(testutil.TmpDir(), "tmp-mount")
   442  	if err != nil {
   443  		t.Fatalf("TempDir(): %v", err)
   444  	}
   445  	const want = "123"
   446  	if err := ioutil.WriteFile(filepath.Join(dir, "file.txt"), []byte("123"), 0666); err != nil {
   447  		t.Fatalf("WriteFile(): %v", err)
   448  	}
   449  	ctx := context.Background()
   450  	d := dockerutil.MakeContainer(ctx, t)
   451  	defer d.CleanUp(ctx)
   452  
   453  	opts := dockerutil.RunOpts{
   454  		Image: "basic/alpine",
   455  		Mounts: []mount.Mount{
   456  			{
   457  				Type:   mount.TypeBind,
   458  				Source: dir,
   459  				Target: "/tmp/foo",
   460  			},
   461  		},
   462  	}
   463  	got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt")
   464  	if err != nil {
   465  		t.Fatalf("docker run failed: %v", err)
   466  	}
   467  	if want != got {
   468  		t.Errorf("invalid file content, want: %q, got: %q", want, got)
   469  	}
   470  }
   471  
   472  // Test that it is allowed to mount a file on top of /dev files, e.g.
   473  // /dev/random.
   474  func TestMountOverDev(t *testing.T) {
   475  	random, err := ioutil.TempFile(testutil.TmpDir(), "random")
   476  	if err != nil {
   477  		t.Fatal("ioutil.TempFile() failed:", err)
   478  	}
   479  	const want = "123"
   480  	if _, err := random.WriteString(want); err != nil {
   481  		t.Fatalf("WriteString() to %q: %v", random.Name(), err)
   482  	}
   483  
   484  	ctx := context.Background()
   485  	d := dockerutil.MakeContainer(ctx, t)
   486  	defer d.CleanUp(ctx)
   487  
   488  	opts := dockerutil.RunOpts{
   489  		Image: "basic/alpine",
   490  		Mounts: []mount.Mount{
   491  			{
   492  				Type:   mount.TypeBind,
   493  				Source: random.Name(),
   494  				Target: "/dev/random",
   495  			},
   496  		},
   497  	}
   498  	cmd := "dd count=1 bs=5 if=/dev/random 2> /dev/null"
   499  	got, err := d.Run(ctx, opts, "sh", "-c", cmd)
   500  	if err != nil {
   501  		t.Fatalf("docker run failed: %v", err)
   502  	}
   503  	if want != got {
   504  		t.Errorf("invalid file content, want: %q, got: %q", want, got)
   505  	}
   506  }
   507  
   508  // TestSyntheticDirs checks that submounts can be created inside a readonly
   509  // mount even if the target path does not exist.
   510  func TestSyntheticDirs(t *testing.T) {
   511  	ctx := context.Background()
   512  	d := dockerutil.MakeContainer(ctx, t)
   513  	defer d.CleanUp(ctx)
   514  
   515  	opts := dockerutil.RunOpts{
   516  		Image: "basic/alpine",
   517  		// Make the root read-only to force use of synthetic dirs
   518  		// inside the root gofer mount.
   519  		ReadOnly: true,
   520  		Mounts: []mount.Mount{
   521  			// Mount inside read-only gofer-backed root.
   522  			{
   523  				Type:   mount.TypeTmpfs,
   524  				Target: "/foo/bar/baz",
   525  			},
   526  			// Mount inside sysfs, which always uses synthetic dirs
   527  			// for submounts.
   528  			{
   529  				Type:   mount.TypeTmpfs,
   530  				Target: "/sys/foo/bar/baz",
   531  			},
   532  		},
   533  	}
   534  	// Make sure the directories exist.
   535  	if _, err := d.Run(ctx, opts, "ls", "/foo/bar/baz", "/sys/foo/bar/baz"); err != nil {
   536  		t.Fatalf("docker run failed: %v", err)
   537  	}
   538  
   539  }
   540  
   541  // TestHostOverlayfsCopyUp tests that the --overlayfs-stale-read option causes
   542  // runsc to hide the incoherence of FDs opened before and after overlayfs
   543  // copy-up on the host.
   544  func TestHostOverlayfsCopyUp(t *testing.T) {
   545  	runIntegrationTest(t, nil, "./test_copy_up")
   546  }
   547  
   548  // TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory
   549  // stream to refer to the current state of the corresponding directory, as a
   550  // call to opendir() would have done" as required by POSIX, when the directory
   551  // in question is host overlayfs.
   552  //
   553  // This test specifically targets host overlayfs because, per POSIX, "if a file
   554  // is removed from or added to the directory after the most recent call to
   555  // opendir() or rewinddir(), whether a subsequent call to readdir() returns an
   556  // entry for that file is unspecified"; the host filesystems used by other
   557  // automated tests yield newly-added files from readdir() even if the fsgofer
   558  // does not explicitly rewinddir(), but overlayfs does not.
   559  func TestHostOverlayfsRewindDir(t *testing.T) {
   560  	runIntegrationTest(t, nil, "./test_rewinddir")
   561  }
   562  
   563  // Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it
   564  // cannot use tricks like userns as root. For this reason, run a basic link test
   565  // to ensure some coverage.
   566  func TestLink(t *testing.T) {
   567  	runIntegrationTest(t, nil, "./link_test")
   568  }
   569  
   570  // This test ensures we can run ping without errors.
   571  func TestPing4Loopback(t *testing.T) {
   572  	runIntegrationTest(t, nil, "./ping4.sh")
   573  }
   574  
   575  // This test ensures we can enable ipv6 on loopback and run ping6 without
   576  // errors.
   577  func TestPing6Loopback(t *testing.T) {
   578  	if testutil.IsRunningWithHostNet() {
   579  		// TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable
   580  		// this test.
   581  		t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.")
   582  	}
   583  
   584  	// The CAP_NET_ADMIN capability is required to use the `ip` utility, which
   585  	// we use to enable ipv6 on loopback.
   586  	//
   587  	// By default, ipv6 loopback is not enabled by runsc, because docker does
   588  	// not assign an ipv6 address to the test container.
   589  	runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh")
   590  }
   591  
   592  // This test checks that the owner of the sticky directory can delete files
   593  // inside it belonging to other users. It also checks that the owner of a file
   594  // can always delete its file when the file is inside a sticky directory owned
   595  // by another user.
   596  func TestStickyDir(t *testing.T) {
   597  	runIntegrationTest(t, nil, "./test_sticky")
   598  }
   599  
   600  func TestHostFD(t *testing.T) {
   601  	t.Run("regular", func(t *testing.T) {
   602  		runIntegrationTest(t, nil, "./host_fd")
   603  	})
   604  	t.Run("tty", func(t *testing.T) {
   605  		ctx := context.Background()
   606  		d := dockerutil.MakeContainer(ctx, t)
   607  		defer d.CleanUp(ctx)
   608  
   609  		// Start the container with an attached PTY.
   610  		p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{
   611  			Image:   "basic/integrationtest",
   612  			WorkDir: "/root",
   613  		}, "./host_fd", "-t")
   614  		if err != nil {
   615  			t.Fatalf("docker run failed: %v", err)
   616  		}
   617  
   618  		if err := d.Wait(ctx); err != nil {
   619  			t.Fatalf("Wait failed: %v", err)
   620  		}
   621  		if out, err := p.Logs(); err != nil {
   622  			t.Fatal(err)
   623  		} else if len(out) > 0 {
   624  			t.Errorf("test failed:\n%s", out)
   625  		}
   626  	})
   627  }
   628  
   629  func runIntegrationTest(t *testing.T, capAdd []string, args ...string) {
   630  	ctx := context.Background()
   631  	d := dockerutil.MakeContainer(ctx, t)
   632  	defer d.CleanUp(ctx)
   633  
   634  	opts := dockerutil.RunOpts{
   635  		Image:   "basic/integrationtest",
   636  		WorkDir: "/root",
   637  		CapAdd:  capAdd,
   638  	}
   639  	if got, err := d.Run(ctx, opts, args...); err != nil {
   640  		t.Fatalf("docker run failed: %v", err)
   641  	} else if got != "" {
   642  		t.Errorf("test failed:\n%s", got)
   643  	}
   644  }
   645  
   646  // Test that UDS can be created using overlay when parent directory is in lower
   647  // layer only (b/134090485).
   648  //
   649  // Prerequisite: the directory where the socket file is created must not have
   650  // been open for write before bind(2) is called.
   651  func TestBindOverlay(t *testing.T) {
   652  	ctx := context.Background()
   653  	d := dockerutil.MakeContainer(ctx, t)
   654  	defer d.CleanUp(ctx)
   655  
   656  	// Run the container.
   657  	got, err := d.Run(ctx, dockerutil.RunOpts{
   658  		Image: "basic/ubuntu",
   659  	}, "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")
   660  	if err != nil {
   661  		t.Fatalf("docker run failed: %v", err)
   662  	}
   663  
   664  	// Check the output contains what we want.
   665  	if want := "foobar-asdf"; !strings.Contains(got, want) {
   666  		t.Fatalf("docker run output is missing %q: %s", want, got)
   667  	}
   668  }
   669  
   670  func TestStdios(t *testing.T) {
   671  	ctx := context.Background()
   672  	d := dockerutil.MakeContainer(ctx, t)
   673  	defer d.CleanUp(ctx)
   674  
   675  	testStdios(t, func(user string, args ...string) (string, error) {
   676  		defer d.CleanUp(ctx)
   677  		opts := dockerutil.RunOpts{
   678  			Image: "basic/alpine",
   679  			User:  user,
   680  		}
   681  		return d.Run(ctx, opts, args...)
   682  	})
   683  }
   684  
   685  func TestStdiosExec(t *testing.T) {
   686  	ctx := context.Background()
   687  	d := dockerutil.MakeContainer(ctx, t)
   688  	defer d.CleanUp(ctx)
   689  
   690  	runOpts := dockerutil.RunOpts{Image: "basic/alpine"}
   691  	if err := d.Spawn(ctx, runOpts, "sleep", "100"); err != nil {
   692  		t.Fatalf("docker run failed: %v", err)
   693  	}
   694  
   695  	testStdios(t, func(user string, args ...string) (string, error) {
   696  		opts := dockerutil.ExecOpts{User: user}
   697  		return d.Exec(ctx, opts, args...)
   698  	})
   699  }
   700  
   701  func testStdios(t *testing.T, run func(string, ...string) (string, error)) {
   702  	const cmd = "stat -L /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 | grep 'Uid:'"
   703  	got, err := run("123", "/bin/sh", "-c", cmd)
   704  	if err != nil {
   705  		t.Fatalf("docker exec failed: %v", err)
   706  	}
   707  	if len(got) == 0 {
   708  		t.Errorf("Unexpected empty output from %q", cmd)
   709  	}
   710  	re := regexp.MustCompile(`Uid: \(\s*(\w+)\/.*\)`)
   711  	for _, line := range strings.SplitN(got, "\n", 3) {
   712  		t.Logf("stat -L: %s", line)
   713  		matches := re.FindSubmatch([]byte(line))
   714  		if len(matches) != 2 {
   715  			t.Fatalf("wrong output format: %q: matches: %v", line, matches)
   716  		}
   717  		if want, got := "123", string(matches[1]); want != got {
   718  			t.Errorf("wrong user, want: %q, got: %q", want, got)
   719  		}
   720  	}
   721  
   722  	// Check that stdout and stderr can be open and written to. This checks
   723  	// that ownership and permissions are correct inside gVisor.
   724  	got, err = run("456", "/bin/sh", "-c", "echo foobar | tee /proc/self/fd/1 > /proc/self/fd/2")
   725  	if err != nil {
   726  		t.Fatalf("docker run failed: %v", err)
   727  	}
   728  	t.Logf("echo foobar: %q", got)
   729  	// Check it repeats twice, once for stdout and once for stderr.
   730  	if want := "foobar\nfoobar\n"; want != got {
   731  		t.Errorf("Wrong echo output, want: %q, got: %q", want, got)
   732  	}
   733  
   734  	// Check that timestamps can be changed. Setting timestamps require an extra
   735  	// write check _after_ the file was opened, and may fail if the underlying
   736  	// host file is not setup correctly.
   737  	if _, err := run("789", "touch", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil {
   738  		t.Fatalf("docker run failed: %v", err)
   739  	}
   740  }
   741  
   742  func TestStdiosChown(t *testing.T) {
   743  	ctx := context.Background()
   744  	d := dockerutil.MakeContainer(ctx, t)
   745  	defer d.CleanUp(ctx)
   746  
   747  	opts := dockerutil.RunOpts{Image: "basic/alpine"}
   748  	if _, err := d.Run(ctx, opts, "chown", "123", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil {
   749  		t.Fatalf("docker run failed: %v", err)
   750  	}
   751  }
   752  
   753  func TestUnmount(t *testing.T) {
   754  	ctx := context.Background()
   755  	d := dockerutil.MakeContainer(ctx, t)
   756  	defer d.CleanUp(ctx)
   757  
   758  	dir, err := ioutil.TempDir(testutil.TmpDir(), "sub-mount")
   759  	if err != nil {
   760  		t.Fatalf("TempDir(): %v", err)
   761  	}
   762  	opts := dockerutil.RunOpts{
   763  		Image:      "basic/alpine",
   764  		Privileged: true, // Required for umount
   765  		Mounts: []mount.Mount{
   766  			{
   767  				Type:   mount.TypeBind,
   768  				Source: dir,
   769  				Target: "/foo",
   770  			},
   771  		},
   772  	}
   773  	if _, err := d.Run(ctx, opts, "umount", "/foo"); err != nil {
   774  		t.Fatalf("docker run failed: %v", err)
   775  	}
   776  }
   777  
   778  func TestDeleteInterface(t *testing.T) {
   779  	ctx := context.Background()
   780  	d := dockerutil.MakeContainer(ctx, t)
   781  	defer d.CleanUp(ctx)
   782  
   783  	opts := dockerutil.RunOpts{
   784  		Image:  "basic/alpine",
   785  		CapAdd: []string{"NET_ADMIN"},
   786  	}
   787  	if err := d.Spawn(ctx, opts, "sleep", "1000"); err != nil {
   788  		t.Fatalf("docker run failed: %v", err)
   789  	}
   790  
   791  	// We should be able to remove eth0.
   792  	output, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev eth0")
   793  	if err != nil {
   794  		t.Fatalf("failed to remove eth0: %s, output: %s", err, output)
   795  	}
   796  	// Verify that eth0 is no longer there.
   797  	output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show")
   798  	if err != nil {
   799  		t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output)
   800  	}
   801  	if strings.Contains(output, "eth0") {
   802  		t.Fatalf("failed to remove eth0")
   803  	}
   804  
   805  	// Loopback device can't be removed.
   806  	output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev lo")
   807  	if err == nil {
   808  		t.Fatalf("should not remove the loopback device: %v", output)
   809  	}
   810  	// Verify that lo is still there.
   811  	output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show")
   812  	if err != nil {
   813  		t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output)
   814  	}
   815  	if !strings.Contains(output, "lo") {
   816  		t.Fatalf("loopback interface is removed")
   817  	}
   818  }
   819  
   820  func TestProductName(t *testing.T) {
   821  	want, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_name")
   822  	if err != nil {
   823  		t.Fatal(err)
   824  	}
   825  
   826  	ctx := context.Background()
   827  	d := dockerutil.MakeContainer(ctx, t)
   828  	defer d.CleanUp(ctx)
   829  
   830  	opts := dockerutil.RunOpts{Image: "basic/alpine"}
   831  	got, err := d.Run(ctx, opts, "cat", "/sys/devices/virtual/dmi/id/product_name")
   832  	if err != nil {
   833  		t.Fatalf("docker run failed: %v", err)
   834  	}
   835  	if string(want) != got {
   836  		t.Errorf("invalid product name, want: %q, got: %q", want, got)
   837  	}
   838  }
   839  
   840  // TestRevalidateSymlinkChain tests that when a symlink in the middle of chain
   841  // gets updated externally, the change is noticed and the internal cache is
   842  // updated accordingly.
   843  func TestRevalidateSymlinkChain(t *testing.T) {
   844  	ctx := context.Background()
   845  	d := dockerutil.MakeContainer(ctx, t)
   846  	defer d.CleanUp(ctx)
   847  
   848  	// Create the following structure:
   849  	// dir
   850  	//  + gen1
   851  	//  |  + file [content: 123]
   852  	//  |
   853  	//  + gen2
   854  	//  |  + file [content: 456]
   855  	//  |
   856  	//  + file -> sym1/file
   857  	//  + sym1 -> sym2
   858  	//  + sym2 -> gen1
   859  	//
   860  	dir, err := ioutil.TempDir(testutil.TmpDir(), "sub-mount")
   861  	if err != nil {
   862  		t.Fatalf("TempDir(): %v", err)
   863  	}
   864  	if err := os.Mkdir(filepath.Join(dir, "gen1"), 0777); err != nil {
   865  		t.Fatal(err)
   866  	}
   867  	if err := os.Mkdir(filepath.Join(dir, "gen2"), 0777); err != nil {
   868  		t.Fatal(err)
   869  	}
   870  	if err := os.WriteFile(filepath.Join(dir, "gen1", "file"), []byte("123"), 0666); err != nil {
   871  		t.Fatal(err)
   872  	}
   873  	if err := os.WriteFile(filepath.Join(dir, "gen2", "file"), []byte("456"), 0666); err != nil {
   874  		t.Fatal(err)
   875  	}
   876  	if err := os.Symlink("sym1/file", filepath.Join(dir, "file")); err != nil {
   877  		t.Fatal(err)
   878  	}
   879  	if err := os.Symlink("sym2", filepath.Join(dir, "sym1")); err != nil {
   880  		t.Fatal(err)
   881  	}
   882  	if err := os.Symlink("gen1", filepath.Join(dir, "sym2")); err != nil {
   883  		t.Fatal(err)
   884  	}
   885  
   886  	// Mount dir inside the container so that external changes are propagated to
   887  	// the container.
   888  	opts := dockerutil.RunOpts{
   889  		Image:      "basic/alpine",
   890  		Privileged: true, // Required for umount
   891  		Mounts: []mount.Mount{
   892  			{
   893  				Type:   mount.TypeBind,
   894  				Source: dir,
   895  				Target: "/foo",
   896  			},
   897  		},
   898  	}
   899  	if err := d.Create(ctx, opts, "sleep", "1000"); err != nil {
   900  		t.Fatalf("docker run failed: %v", err)
   901  	}
   902  	if err := d.Start(ctx); err != nil {
   903  		t.Fatalf("docker run failed: %v", err)
   904  	}
   905  
   906  	// Read and cache symlinks pointing to gen1/file.
   907  	got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/foo/file")
   908  	if err != nil {
   909  		t.Fatalf("docker run failed: %v", err)
   910  	}
   911  	if want := "123"; got != want {
   912  		t.Fatalf("Read wrong file, want: %q, got: %q", want, got)
   913  	}
   914  
   915  	// Change the symlink to point to gen2 file.
   916  	if err := os.Remove(filepath.Join(dir, "sym2")); err != nil {
   917  		t.Fatal(err)
   918  	}
   919  	if err := os.Symlink("gen2", filepath.Join(dir, "sym2")); err != nil {
   920  		t.Fatal(err)
   921  	}
   922  
   923  	// Read symlink chain again and check that it got updated to gen2/file.
   924  	got, err = d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/foo/file")
   925  	if err != nil {
   926  		t.Fatalf("docker run failed: %v", err)
   927  	}
   928  	if want := "456"; got != want {
   929  		t.Fatalf("Read wrong file, want: %q, got: %q", want, got)
   930  	}
   931  }
   932  
   933  // TestTmpMountWithSize checks when 'tmpfs' is mounted
   934  // with size option the limit is not exceeded.
   935  func TestTmpMountWithSize(t *testing.T) {
   936  	ctx := context.Background()
   937  	d := dockerutil.MakeContainer(ctx, t)
   938  	defer d.CleanUp(ctx)
   939  
   940  	opts := dockerutil.RunOpts{
   941  		Image: "basic/alpine",
   942  		Mounts: []mount.Mount{
   943  			{
   944  				Type:   mount.TypeTmpfs,
   945  				Target: "/tmp/foo",
   946  				TmpfsOptions: &mount.TmpfsOptions{
   947  					SizeBytes: 4096,
   948  				},
   949  			},
   950  		},
   951  	}
   952  	if err := d.Create(ctx, opts, "sleep", "1000"); err != nil {
   953  		t.Fatalf("docker create failed: %v", err)
   954  	}
   955  	if err := d.Start(ctx); err != nil {
   956  		t.Fatalf("docker start failed: %v", err)
   957  	}
   958  
   959  	if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo hello > /tmp/foo/test1.txt"); err != nil {
   960  		t.Fatalf("docker exec failed: %v", err)
   961  	}
   962  	echoOutput, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo world > /tmp/foo/test2.txt")
   963  	if err == nil {
   964  		t.Fatalf("docker exec size check unexpectedly succeeded (output: %v)", echoOutput)
   965  	}
   966  	wantErr := "No space left on device"
   967  	if !strings.Contains(echoOutput, wantErr) {
   968  		t.Errorf("unexpected echo error:Expected: %v, Got: %v", wantErr, echoOutput)
   969  	}
   970  }
   971  
   972  // NOTE(b/236028361): Regression test. Check we can handle a working directory
   973  // without execute permissions. See comment in
   974  // pkg/sentry/kernel/kernel.go:CreateProcess() for more context.
   975  func TestNonSearchableWorkingDirectory(t *testing.T) {
   976  	dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount")
   977  	if err != nil {
   978  		t.Fatalf("MkdirTemp() failed: %v", err)
   979  	}
   980  	defer os.RemoveAll(dir)
   981  
   982  	// The container will run as a non-root user. Make dir not searchable by
   983  	// others by removing execute bit for others.
   984  	if err := os.Chmod(dir, 0766); err != nil {
   985  		t.Fatalf("Chmod() failed: %v", err)
   986  	}
   987  	ctx := context.Background()
   988  	d := dockerutil.MakeContainer(ctx, t)
   989  	defer d.CleanUp(ctx)
   990  
   991  	targetMount := "/foo"
   992  	opts := dockerutil.RunOpts{
   993  		Image: "basic/alpine",
   994  		Mounts: []mount.Mount{
   995  			{
   996  				Type:   mount.TypeBind,
   997  				Source: dir,
   998  				Target: targetMount,
   999  			},
  1000  		},
  1001  		WorkDir: targetMount,
  1002  		User:    "nobody",
  1003  	}
  1004  
  1005  	echoPhrase := "All izz well"
  1006  	got, err := d.Run(ctx, opts, "sh", "-c", "echo "+echoPhrase+" && (ls || true)")
  1007  	if err != nil {
  1008  		t.Fatalf("docker run failed: %v", err)
  1009  	}
  1010  	if !strings.Contains(got, echoPhrase) {
  1011  		t.Errorf("echo output not found, want: %q, got: %q", echoPhrase, got)
  1012  	}
  1013  	if wantErrorMsg := "Permission denied"; !strings.Contains(got, wantErrorMsg) {
  1014  		t.Errorf("ls error message not found, want: %q, got: %q", wantErrorMsg, got)
  1015  	}
  1016  }
  1017  
  1018  func TestCharDevice(t *testing.T) {
  1019  	if testutil.IsRunningWithOverlay() {
  1020  		t.Skip("files are not available outside the sandbox with overlay.")
  1021  	}
  1022  
  1023  	ctx := context.Background()
  1024  	d := dockerutil.MakeContainer(ctx, t)
  1025  	defer d.CleanUp(ctx)
  1026  
  1027  	dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount")
  1028  	if err != nil {
  1029  		t.Fatalf("MkdirTemp() failed: %v", err)
  1030  	}
  1031  	defer os.RemoveAll(dir)
  1032  
  1033  	opts := dockerutil.RunOpts{
  1034  		Image: "basic/alpine",
  1035  		Mounts: []mount.Mount{
  1036  			{
  1037  				Type:   mount.TypeBind,
  1038  				Source: "/dev/zero",
  1039  				Target: "/test/zero",
  1040  			},
  1041  			{
  1042  				Type:   mount.TypeBind,
  1043  				Source: dir,
  1044  				Target: "/out",
  1045  			},
  1046  		},
  1047  	}
  1048  
  1049  	const size = 1024 * 1024
  1050  
  1051  	// `docker logs` encodes the string, making it hard to compare. Write the
  1052  	// result to a file that is available to the test.
  1053  	cmd := fmt.Sprintf("head -c %d /test/zero > /out/result", size)
  1054  	if _, err := d.Run(ctx, opts, "sh", "-c", cmd); err != nil {
  1055  		t.Fatalf("docker run failed: %v", err)
  1056  	}
  1057  	got, err := os.ReadFile(filepath.Join(dir, "result"))
  1058  	if err != nil {
  1059  		t.Fatal(err)
  1060  	}
  1061  	if want := [size]byte{}; !bytes.Equal(want[:], got) {
  1062  		t.Errorf("Wrong bytes, want: [all zeros], got: %v", got)
  1063  	}
  1064  }
  1065  
  1066  func TestBlockHostUds(t *testing.T) {
  1067  	ctx := context.Background()
  1068  	d := dockerutil.MakeContainer(ctx, t)
  1069  	defer d.CleanUp(ctx)
  1070  
  1071  	dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount")
  1072  	if err != nil {
  1073  		t.Fatalf("MkdirTemp() failed: %v", err)
  1074  	}
  1075  	defer os.RemoveAll(dir)
  1076  
  1077  	dirFD, err := unix.Open(dir, unix.O_PATH, 0)
  1078  	if err != nil {
  1079  		t.Fatalf("failed to open %s: %v", dir, err)
  1080  	}
  1081  	defer unix.Close(dirFD)
  1082  	// Use /proc/self/fd to generate path to avoid EINVAL on large path.
  1083  	l, err := net.Listen("unix", filepath.Join("/proc/self/fd", strconv.Itoa(dirFD), "test.sock"))
  1084  	if err != nil {
  1085  		t.Fatalf("listen error: %v", err)
  1086  	}
  1087  	defer l.Close()
  1088  
  1089  	opts := dockerutil.RunOpts{
  1090  		Image:   "basic/integrationtest",
  1091  		WorkDir: "/root",
  1092  		Mounts: []mount.Mount{
  1093  			{
  1094  				Type:   mount.TypeBind,
  1095  				Source: dir,
  1096  				Target: "/dir",
  1097  			},
  1098  		},
  1099  	}
  1100  	if err := d.Spawn(ctx, opts, "sleep", "infinity"); err != nil {
  1101  		t.Fatalf("docker run failed: %v", err)
  1102  	}
  1103  	// Application should be able to walk/stat the UDS ...
  1104  	if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "stat", "/dir/test.sock"); err != nil {
  1105  		t.Fatalf("stat(2)-ing the UDS failed: output = %q, err = %v", got, err)
  1106  	}
  1107  	// ... but not connect to it.
  1108  	const want = "connect: Connection refused"
  1109  	if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "./host_connect", "/dir/test.sock"); err == nil || !strings.Contains(got, want) {
  1110  		t.Errorf("err should be non-nil and output should contain %q, but got err = %v and output = %q", want, err, got)
  1111  	}
  1112  }
  1113  
  1114  func readLogs(logs string, position int) (int, error) {
  1115  	if len(logs) == 0 {
  1116  		return 0, fmt.Errorf("error no content was read")
  1117  	}
  1118  
  1119  	nums := strings.Split(logs, "\n")
  1120  	if position >= len(nums) {
  1121  		return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums)
  1122  	}
  1123  	if position == -1 {
  1124  		// Expectation of newline at the end of last position.
  1125  		position = len(nums) - 2
  1126  	}
  1127  	num, err := strconv.Atoi(nums[position])
  1128  	if err != nil {
  1129  		return 0, fmt.Errorf("error getting number from file: %v", err)
  1130  	}
  1131  
  1132  	return num, nil
  1133  }
  1134  
  1135  func checkLogs(logs string, oldPos int) error {
  1136  	if len(logs) == 0 {
  1137  		return fmt.Errorf("error no content was read")
  1138  	}
  1139  
  1140  	nums := strings.Split(logs, "\n")
  1141  	// Expectation of newline at the end of last position.
  1142  	if oldPos >= len(nums)-2 {
  1143  		return fmt.Errorf("oldPos %v is not within the length of content %v", oldPos, nums)
  1144  	}
  1145  	for i := oldPos + 1; i < len(nums)-1; i++ {
  1146  		num, err := strconv.Atoi(nums[i])
  1147  		if err != nil {
  1148  			return fmt.Errorf("error getting number from file: %v", err)
  1149  		}
  1150  		if num != oldPos+1 {
  1151  			return fmt.Errorf("error in save/resume, numbers not in order, previous: %d, next: %d", oldPos, num)
  1152  		}
  1153  		oldPos++
  1154  	}
  1155  	return nil
  1156  }
  1157  
  1158  // Checkpoint the container and continue running.
  1159  func TestCheckpointResume(t *testing.T) {
  1160  	if !testutil.IsCheckpointSupported() {
  1161  		t.Skip("Checkpoint is not supported.")
  1162  	}
  1163  	dockerutil.EnsureDockerExperimentalEnabled()
  1164  
  1165  	ctx := context.Background()
  1166  	d := dockerutil.MakeContainer(ctx, t)
  1167  	defer d.CleanUp(ctx)
  1168  
  1169  	// Start the container.
  1170  	if err := d.Spawn(ctx, dockerutil.RunOpts{
  1171  		Image: "basic/alpine",
  1172  	}, "sh", "-c", "i=0; while true; do echo \"$i\"; i=\"$(expr \"$i\" + 1)\"; sleep .01; done"); err != nil {
  1173  		t.Fatalf("docker run failed: %v", err)
  1174  	}
  1175  
  1176  	time.Sleep(2 * time.Second)
  1177  
  1178  	// Get the logs before checkpointing.
  1179  	logs, err := d.Logs(ctx)
  1180  	if err != nil {
  1181  		t.Fatalf("docker logs failed: %v", err)
  1182  	}
  1183  
  1184  	// Get the last position of the logs printed.
  1185  	pos, err := readLogs(logs, -1)
  1186  	if err != nil {
  1187  		t.Fatalf("readLogs failed: %v", err)
  1188  	}
  1189  
  1190  	// Create a snapshot and continue running.
  1191  	if err := d.CheckpointResume(ctx, "test"); err != nil {
  1192  		t.Fatalf("docker checkpoint failed: %v", err)
  1193  	}
  1194  
  1195  	var newLogs string
  1196  	// Wait for the container to resume running and print new logs.
  1197  	if err := testutil.Poll(func() error {
  1198  		// Get the logs after checkpointing to check if the container resumed.
  1199  		newLogs, err = d.Logs(ctx)
  1200  		if err != nil {
  1201  			t.Fatalf("docker logs failed: %v", err)
  1202  		}
  1203  		return nil
  1204  	}, defaultWait); err != nil {
  1205  		t.Fatalf("container read logs failed after resume: %v", err)
  1206  	}
  1207  
  1208  	if err := checkLogs(newLogs, pos); err != nil {
  1209  		t.Fatalf("checkLogs failed: %v", err)
  1210  	}
  1211  	if err := d.Kill(ctx); err != nil {
  1212  		t.Fatalf("docker kill failed: %v", err)
  1213  	}
  1214  }