gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/image/image_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 image provides end-to-end image tests for runsc.
    16  
    17  // Each test calls docker commands to start up a container, and tests that it
    18  // is behaving properly, like connecting to a port or looking at the output.
    19  // The container is killed and deleted at the end.
    20  //
    21  // Setup instruction in test/README.md.
    22  package image
    23  
    24  import (
    25  	"context"
    26  	"flag"
    27  	"fmt"
    28  	"io/ioutil"
    29  	"log"
    30  	"net/http"
    31  	"os"
    32  	"strings"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/docker/docker/api/types/mount"
    37  	"gvisor.dev/gvisor/pkg/test/dockerutil"
    38  	"gvisor.dev/gvisor/pkg/test/testutil"
    39  )
    40  
    41  // defaultWait defines how long to wait for progress.
    42  //
    43  // See BUILD: This is at least a "large" test, so allow up to 1 minute for any
    44  // given "wait" step. Note that all tests are run in parallel, which may cause
    45  // individual slow-downs (but a huge speed-up in aggregate).
    46  const defaultWait = time.Minute
    47  
    48  func TestHelloWorld(t *testing.T) {
    49  	ctx := context.Background()
    50  	d := dockerutil.MakeContainer(ctx, t)
    51  	defer d.CleanUp(ctx)
    52  
    53  	// Run the basic container.
    54  	out, err := d.Run(ctx, dockerutil.RunOpts{
    55  		Image: "basic/alpine",
    56  	}, "echo", "Hello world!")
    57  	if err != nil {
    58  		t.Fatalf("docker run failed: %v", err)
    59  	}
    60  
    61  	// Check the output.
    62  	if !strings.Contains(out, "Hello world!") {
    63  		t.Fatalf("docker didn't say hello: got %s", out)
    64  	}
    65  }
    66  
    67  func TestRust(t *testing.T) {
    68  	ctx := context.Background()
    69  	d := dockerutil.MakeContainer(ctx, t)
    70  	defer d.CleanUp(ctx)
    71  
    72  	// Run the basic container.
    73  	out, err := d.Run(ctx, dockerutil.RunOpts{
    74  		Image: "basic/rust",
    75  	})
    76  	if err != nil {
    77  		t.Fatalf("docker run failed: %v", err)
    78  	}
    79  
    80  	// Check the output.
    81  	if !strings.Contains(out, "Hello, World!") {
    82  		t.Fatalf("Container didn't say Hello, World!: got %s", out)
    83  	}
    84  }
    85  
    86  func runHTTPRequest(ip string, port int) error {
    87  	url := fmt.Sprintf("http://%s:%d/not-found", ip, port)
    88  	resp, err := http.Get(url)
    89  	if err != nil {
    90  		return fmt.Errorf("error reaching http server: %v", err)
    91  	}
    92  	if want := http.StatusNotFound; resp.StatusCode != want {
    93  		return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
    94  	}
    95  
    96  	url = fmt.Sprintf("http://%s:%d/latin10k.txt", ip, port)
    97  	resp, err = http.Get(url)
    98  	if err != nil {
    99  		return fmt.Errorf("Error reaching http server: %v", err)
   100  	}
   101  	if want := http.StatusOK; resp.StatusCode != want {
   102  		return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
   103  	}
   104  
   105  	body, err := ioutil.ReadAll(resp.Body)
   106  	if err != nil {
   107  		return fmt.Errorf("Error reading http response: %v", err)
   108  	}
   109  	defer resp.Body.Close()
   110  
   111  	// READALL is the last word in the file. Ensures everything was read.
   112  	if want := "READALL"; strings.HasSuffix(string(body), want) {
   113  		return fmt.Errorf("response doesn't contain %q, resp: %q", want, body)
   114  	}
   115  	return nil
   116  }
   117  
   118  func testHTTPServer(t *testing.T, ip string, port int) {
   119  	const requests = 10
   120  	ch := make(chan error, requests)
   121  	for i := 0; i < requests; i++ {
   122  		go func() {
   123  			start := time.Now()
   124  			err := runHTTPRequest(ip, port)
   125  			log.Printf("Response time %v: %v", time.Since(start).String(), err)
   126  			ch <- err
   127  		}()
   128  	}
   129  
   130  	for i := 0; i < requests; i++ {
   131  		err := <-ch
   132  		if err != nil {
   133  			t.Errorf("testHTTPServer(%s, %d) failed: %v", ip, port, err)
   134  		}
   135  	}
   136  }
   137  
   138  func TestHttpd(t *testing.T) {
   139  	ctx := context.Background()
   140  	d := dockerutil.MakeContainer(ctx, t)
   141  	defer d.CleanUp(ctx)
   142  
   143  	// Start the container.
   144  	port := 80
   145  	opts := dockerutil.RunOpts{
   146  		Image: "basic/httpd",
   147  		Ports: []int{port},
   148  	}
   149  	d.CopyFiles(&opts, "/usr/local/apache2/htdocs", "test/image/latin10k.txt")
   150  	if err := d.Spawn(ctx, opts); err != nil {
   151  		t.Fatalf("docker run failed: %v", err)
   152  	}
   153  
   154  	// Find container IP address.
   155  	ip, err := d.FindIP(ctx, false)
   156  	if err != nil {
   157  		t.Fatalf("docker.FindIP failed: %v", err)
   158  	}
   159  
   160  	// Wait until it's up and running.
   161  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   162  		t.Errorf("WaitForHTTP() timeout: %v", err)
   163  	}
   164  
   165  	testHTTPServer(t, ip.String(), port)
   166  }
   167  
   168  func TestNginx(t *testing.T) {
   169  	ctx := context.Background()
   170  	d := dockerutil.MakeContainer(ctx, t)
   171  	defer d.CleanUp(ctx)
   172  
   173  	// Start the container.
   174  	port := 80
   175  	opts := dockerutil.RunOpts{
   176  		Image: "basic/nginx",
   177  		Ports: []int{port},
   178  	}
   179  	d.CopyFiles(&opts, "/usr/share/nginx/html", "test/image/latin10k.txt")
   180  	if err := d.Spawn(ctx, opts); err != nil {
   181  		t.Fatalf("docker run failed: %v", err)
   182  	}
   183  
   184  	// Find container IP address.
   185  	ip, err := d.FindIP(ctx, false)
   186  	if err != nil {
   187  		t.Fatalf("docker.FindIP failed: %v", err)
   188  	}
   189  
   190  	// Wait until it's up and running.
   191  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   192  		t.Errorf("WaitForHTTP() timeout: %v", err)
   193  	}
   194  
   195  	testHTTPServer(t, ip.String(), port)
   196  }
   197  
   198  func TestMysql(t *testing.T) {
   199  	ctx := context.Background()
   200  	server := dockerutil.MakeContainer(ctx, t)
   201  	defer server.CleanUp(ctx)
   202  
   203  	// Start the container.
   204  	if err := server.Spawn(ctx, dockerutil.RunOpts{
   205  		Image: "basic/mysql",
   206  		Env: []string{
   207  			"MYSQL_ROOT_PASSWORD=foobar123",
   208  			"MYSQL_ROOT_HOST=%", // Allow anyone to connect to the server.
   209  		},
   210  	}); err != nil {
   211  		t.Fatalf("docker run failed: %v", err)
   212  	}
   213  
   214  	// Wait until it's up and running.
   215  	if _, err := server.WaitForOutput(ctx, "port: 3306  MySQL Community Server", defaultWait); err != nil {
   216  		t.Fatalf("WaitForOutput() timeout: %v", err)
   217  	}
   218  
   219  	// Generate the client and copy in the SQL payload.
   220  	client := dockerutil.MakeContainer(ctx, t)
   221  	defer client.CleanUp(ctx)
   222  
   223  	// Tell mysql client to connect to the server and execute the file in
   224  	// verbose mode to verify the output.
   225  	opts := dockerutil.RunOpts{
   226  		Image: "basic/mysql",
   227  		Links: []string{server.MakeLink("mysql")},
   228  	}
   229  	client.CopyFiles(&opts, "/sql", "test/image/mysql.sql")
   230  	if _, err := client.Run(ctx, opts, "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql"); err != nil {
   231  		t.Fatalf("docker run failed: %v", err)
   232  	}
   233  
   234  	// Ensure file executed to the end and shutdown mysql.
   235  	if _, err := server.WaitForOutput(ctx, "mysqld: Shutdown complete", defaultWait); err != nil {
   236  		t.Fatalf("WaitForOutput() timeout: %v", err)
   237  	}
   238  }
   239  
   240  func TestTomcat(t *testing.T) {
   241  	ctx := context.Background()
   242  	d := dockerutil.MakeContainer(ctx, t)
   243  	defer d.CleanUp(ctx)
   244  
   245  	// Start the server.
   246  	port := 8080
   247  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   248  		Image: "basic/tomcat",
   249  		Ports: []int{port},
   250  	}); err != nil {
   251  		t.Fatalf("docker run failed: %v", err)
   252  	}
   253  
   254  	// Find container IP address.
   255  	ip, err := d.FindIP(ctx, false)
   256  	if err != nil {
   257  		t.Fatalf("docker.FindIP failed: %v", err)
   258  	}
   259  
   260  	// Wait until it's up and running.
   261  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   262  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   263  	}
   264  
   265  	// Ensure that content is being served.
   266  	url := fmt.Sprintf("http://%s:%d", ip.String(), port)
   267  	resp, err := http.Get(url)
   268  	if err != nil {
   269  		t.Errorf("Error reaching http server: %v", err)
   270  	}
   271  	if want := http.StatusOK; resp.StatusCode != want {
   272  		t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
   273  	}
   274  }
   275  
   276  func TestRuby(t *testing.T) {
   277  	ctx := context.Background()
   278  	d := dockerutil.MakeContainer(ctx, t)
   279  	defer d.CleanUp(ctx)
   280  
   281  	// Execute the ruby workload.
   282  	port := 8080
   283  	opts := dockerutil.RunOpts{
   284  		Image: "basic/ruby",
   285  		Ports: []int{port},
   286  	}
   287  	d.CopyFiles(&opts, "/src", "test/image/ruby.rb", "test/image/ruby.sh")
   288  	if err := d.Spawn(ctx, opts, "/src/ruby.sh"); err != nil {
   289  		t.Fatalf("docker run failed: %v", err)
   290  	}
   291  
   292  	// Find container IP address.
   293  	ip, err := d.FindIP(ctx, false)
   294  	if err != nil {
   295  		t.Fatalf("docker.FindIP failed: %v", err)
   296  	}
   297  
   298  	// Wait until it's up and running, 'gem install' can take some time.
   299  	if err := testutil.WaitForHTTP(ip.String(), port, time.Minute); err != nil {
   300  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   301  	}
   302  
   303  	// Ensure that content is being served.
   304  	url := fmt.Sprintf("http://%s:%d", ip.String(), port)
   305  	resp, err := http.Get(url)
   306  	if err != nil {
   307  		t.Errorf("error reaching http server: %v", err)
   308  	}
   309  	if want := http.StatusOK; resp.StatusCode != want {
   310  		t.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
   311  	}
   312  	body, err := ioutil.ReadAll(resp.Body)
   313  	if err != nil {
   314  		t.Fatalf("error reading body: %v", err)
   315  	}
   316  	if got, want := string(body), "Hello World"; !strings.Contains(got, want) {
   317  		t.Errorf("invalid body content, got: %q, want: %q", got, want)
   318  	}
   319  }
   320  
   321  func TestStdio(t *testing.T) {
   322  	ctx := context.Background()
   323  	d := dockerutil.MakeContainer(ctx, t)
   324  	defer d.CleanUp(ctx)
   325  
   326  	wantStdout := "hello stdout"
   327  	wantStderr := "bonjour stderr"
   328  	cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr)
   329  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   330  		Image: "basic/alpine",
   331  	}, "/bin/sh", "-c", cmd); err != nil {
   332  		t.Fatalf("docker run failed: %v", err)
   333  	}
   334  
   335  	for _, want := range []string{wantStdout, wantStderr} {
   336  		if _, err := d.WaitForOutput(ctx, want, defaultWait); err != nil {
   337  			t.Fatalf("docker didn't get output %q : %v", want, err)
   338  		}
   339  	}
   340  }
   341  
   342  func TestDockerOverlay(t *testing.T) {
   343  	testDocker(t, true)
   344  }
   345  
   346  func TestDocker(t *testing.T) {
   347  	// Overlayfs can't be built on top of another overlayfs, so docket has
   348  	// to fall back to the vfs driver.
   349  	testDocker(t, false)
   350  }
   351  
   352  func testDocker(t *testing.T, overlay bool) {
   353  	if testutil.IsRunningWithHostNet() {
   354  		t.Skip("docker doesn't work with hostinet")
   355  	}
   356  	ctx := context.Background()
   357  	d := dockerutil.MakeContainer(ctx, t)
   358  	defer d.CleanUp(ctx)
   359  
   360  	// Start the container.
   361  	opts := dockerutil.RunOpts{
   362  		Image:      "basic/docker",
   363  		Privileged: true,
   364  	}
   365  	if overlay {
   366  		opts.Mounts = []mount.Mount{
   367  			{
   368  				Target: "/var/lib/docker",
   369  				Type:   mount.TypeTmpfs,
   370  			},
   371  		}
   372  	}
   373  	if err := d.Spawn(ctx, opts); err != nil {
   374  		t.Fatalf("docker run failed: %v", err)
   375  	}
   376  
   377  	if overlay {
   378  		// Docker creates tmpfs mounts with the noexec flag.
   379  		output, err := d.Exec(ctx,
   380  			dockerutil.ExecOpts{Privileged: true},
   381  			"mount", "-o", "remount,exec", "/var/lib/docker",
   382  		)
   383  		if err != nil {
   384  			t.Fatalf("docker exec failed: %v\n%s", err, output)
   385  		}
   386  	}
   387  	// Wait for the docker daemon.
   388  	for i := 0; i < 10; i++ {
   389  		output, err := d.Exec(ctx, dockerutil.ExecOpts{}, "docker", "info")
   390  		t.Logf("== docker info ==\n%s", output)
   391  		if err != nil {
   392  			t.Logf("docker exec failed: %v", err)
   393  			time.Sleep(5 * time.Second)
   394  			continue
   395  		}
   396  		break
   397  	}
   398  	p, err := d.ExecProcess(ctx, dockerutil.ExecOpts{},
   399  		"docker", "run", "--network", "host", "--rm", "alpine", "echo", "Hello World")
   400  	if err != nil {
   401  		t.Fatalf("docker exec failed: %v", err)
   402  	}
   403  	stdout, stderr, err := p.Read()
   404  	t.Logf("Container output: == stdout ==\n%s\n== stderr ==\n%s", stdout, stderr)
   405  	if err != nil {
   406  		t.Errorf("failed to read process output: %s", err)
   407  	}
   408  	if stdout != "Hello World\n" {
   409  		t.Errorf("Unexpected output: %#v", stdout)
   410  	}
   411  }
   412  
   413  func TestMain(m *testing.M) {
   414  	dockerutil.EnsureSupportedDockerVersion()
   415  	flag.Parse()
   416  	os.Exit(m.Run())
   417  }