github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/gvisor/pkg/test/dockerutil"
    37  	"github.com/SagerNet/gvisor/pkg/test/testutil"
    38  )
    39  
    40  // defaultWait defines how long to wait for progress.
    41  //
    42  // See BUILD: This is at least a "large" test, so allow up to 1 minute for any
    43  // given "wait" step. Note that all tests are run in parallel, which may cause
    44  // individual slow-downs (but a huge speed-up in aggregate).
    45  const defaultWait = time.Minute
    46  
    47  func TestHelloWorld(t *testing.T) {
    48  	ctx := context.Background()
    49  	d := dockerutil.MakeContainer(ctx, t)
    50  	defer d.CleanUp(ctx)
    51  
    52  	// Run the basic container.
    53  	out, err := d.Run(ctx, dockerutil.RunOpts{
    54  		Image: "basic/alpine",
    55  	}, "echo", "Hello world!")
    56  	if err != nil {
    57  		t.Fatalf("docker run failed: %v", err)
    58  	}
    59  
    60  	// Check the output.
    61  	if !strings.Contains(out, "Hello world!") {
    62  		t.Fatalf("docker didn't say hello: got %s", out)
    63  	}
    64  }
    65  
    66  func runHTTPRequest(ip string, port int) error {
    67  	url := fmt.Sprintf("http://%s:%d/not-found", ip, port)
    68  	resp, err := http.Get(url)
    69  	if err != nil {
    70  		return fmt.Errorf("error reaching http server: %v", err)
    71  	}
    72  	if want := http.StatusNotFound; resp.StatusCode != want {
    73  		return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
    74  	}
    75  
    76  	url = fmt.Sprintf("http://%s:%d/latin10k.txt", ip, port)
    77  	resp, err = http.Get(url)
    78  	if err != nil {
    79  		return fmt.Errorf("Error reaching http server: %v", err)
    80  	}
    81  	if want := http.StatusOK; resp.StatusCode != want {
    82  		return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
    83  	}
    84  
    85  	body, err := ioutil.ReadAll(resp.Body)
    86  	if err != nil {
    87  		return fmt.Errorf("Error reading http response: %v", err)
    88  	}
    89  	defer resp.Body.Close()
    90  
    91  	// READALL is the last word in the file. Ensures everything was read.
    92  	if want := "READALL"; strings.HasSuffix(string(body), want) {
    93  		return fmt.Errorf("response doesn't contain %q, resp: %q", want, body)
    94  	}
    95  	return nil
    96  }
    97  
    98  func testHTTPServer(t *testing.T, ip string, port int) {
    99  	const requests = 10
   100  	ch := make(chan error, requests)
   101  	for i := 0; i < requests; i++ {
   102  		go func() {
   103  			start := time.Now()
   104  			err := runHTTPRequest(ip, port)
   105  			log.Printf("Response time %v: %v", time.Since(start).String(), err)
   106  			ch <- err
   107  		}()
   108  	}
   109  
   110  	for i := 0; i < requests; i++ {
   111  		err := <-ch
   112  		if err != nil {
   113  			t.Errorf("testHTTPServer(%s, %d) failed: %v", ip, port, err)
   114  		}
   115  	}
   116  }
   117  
   118  func TestHttpd(t *testing.T) {
   119  	ctx := context.Background()
   120  	d := dockerutil.MakeContainer(ctx, t)
   121  	defer d.CleanUp(ctx)
   122  
   123  	// Start the container.
   124  	port := 80
   125  	opts := dockerutil.RunOpts{
   126  		Image: "basic/httpd",
   127  		Ports: []int{port},
   128  	}
   129  	d.CopyFiles(&opts, "/usr/local/apache2/htdocs", "test/image/latin10k.txt")
   130  	if err := d.Spawn(ctx, opts); err != nil {
   131  		t.Fatalf("docker run failed: %v", err)
   132  	}
   133  
   134  	// Find container IP address.
   135  	ip, err := d.FindIP(ctx, false)
   136  	if err != nil {
   137  		t.Fatalf("docker.FindIP failed: %v", err)
   138  	}
   139  
   140  	// Wait until it's up and running.
   141  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   142  		t.Errorf("WaitForHTTP() timeout: %v", err)
   143  	}
   144  
   145  	testHTTPServer(t, ip.String(), port)
   146  }
   147  
   148  func TestNginx(t *testing.T) {
   149  	ctx := context.Background()
   150  	d := dockerutil.MakeContainer(ctx, t)
   151  	defer d.CleanUp(ctx)
   152  
   153  	// Start the container.
   154  	port := 80
   155  	opts := dockerutil.RunOpts{
   156  		Image: "basic/nginx",
   157  		Ports: []int{port},
   158  	}
   159  	d.CopyFiles(&opts, "/usr/share/nginx/html", "test/image/latin10k.txt")
   160  	if err := d.Spawn(ctx, opts); err != nil {
   161  		t.Fatalf("docker run failed: %v", err)
   162  	}
   163  
   164  	// Find container IP address.
   165  	ip, err := d.FindIP(ctx, false)
   166  	if err != nil {
   167  		t.Fatalf("docker.FindIP failed: %v", err)
   168  	}
   169  
   170  	// Wait until it's up and running.
   171  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   172  		t.Errorf("WaitForHTTP() timeout: %v", err)
   173  	}
   174  
   175  	testHTTPServer(t, ip.String(), port)
   176  }
   177  
   178  func TestMysql(t *testing.T) {
   179  	ctx := context.Background()
   180  	server := dockerutil.MakeContainer(ctx, t)
   181  	defer server.CleanUp(ctx)
   182  
   183  	// Start the container.
   184  	if err := server.Spawn(ctx, dockerutil.RunOpts{
   185  		Image: "basic/mysql",
   186  		Env: []string{
   187  			"MYSQL_ROOT_PASSWORD=foobar123",
   188  			"MYSQL_ROOT_HOST=%", // Allow anyone to connect to the server.
   189  		},
   190  	}); err != nil {
   191  		t.Fatalf("docker run failed: %v", err)
   192  	}
   193  
   194  	// Wait until it's up and running.
   195  	if _, err := server.WaitForOutput(ctx, "port: 3306  MySQL Community Server", defaultWait); err != nil {
   196  		t.Fatalf("WaitForOutput() timeout: %v", err)
   197  	}
   198  
   199  	// Generate the client and copy in the SQL payload.
   200  	client := dockerutil.MakeContainer(ctx, t)
   201  	defer client.CleanUp(ctx)
   202  
   203  	// Tell mysql client to connect to the server and execute the file in
   204  	// verbose mode to verify the output.
   205  	opts := dockerutil.RunOpts{
   206  		Image: "basic/mysql",
   207  		Links: []string{server.MakeLink("mysql")},
   208  	}
   209  	client.CopyFiles(&opts, "/sql", "test/image/mysql.sql")
   210  	if _, err := client.Run(ctx, opts, "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql"); err != nil {
   211  		t.Fatalf("docker run failed: %v", err)
   212  	}
   213  
   214  	// Ensure file executed to the end and shutdown mysql.
   215  	if _, err := server.WaitForOutput(ctx, "mysqld: Shutdown complete", defaultWait); err != nil {
   216  		t.Fatalf("WaitForOutput() timeout: %v", err)
   217  	}
   218  }
   219  
   220  func TestTomcat(t *testing.T) {
   221  	ctx := context.Background()
   222  	d := dockerutil.MakeContainer(ctx, t)
   223  	defer d.CleanUp(ctx)
   224  
   225  	// Start the server.
   226  	port := 8080
   227  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   228  		Image: "basic/tomcat",
   229  		Ports: []int{port},
   230  	}); err != nil {
   231  		t.Fatalf("docker run failed: %v", err)
   232  	}
   233  
   234  	// Find container IP address.
   235  	ip, err := d.FindIP(ctx, false)
   236  	if err != nil {
   237  		t.Fatalf("docker.FindIP failed: %v", err)
   238  	}
   239  
   240  	// Wait until it's up and running.
   241  	if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil {
   242  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   243  	}
   244  
   245  	// Ensure that content is being served.
   246  	url := fmt.Sprintf("http://%s:%d", ip.String(), port)
   247  	resp, err := http.Get(url)
   248  	if err != nil {
   249  		t.Errorf("Error reaching http server: %v", err)
   250  	}
   251  	if want := http.StatusOK; resp.StatusCode != want {
   252  		t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
   253  	}
   254  }
   255  
   256  func TestRuby(t *testing.T) {
   257  	ctx := context.Background()
   258  	d := dockerutil.MakeContainer(ctx, t)
   259  	defer d.CleanUp(ctx)
   260  
   261  	// Execute the ruby workload.
   262  	port := 8080
   263  	opts := dockerutil.RunOpts{
   264  		Image: "basic/ruby",
   265  		Ports: []int{port},
   266  	}
   267  	d.CopyFiles(&opts, "/src", "test/image/ruby.rb", "test/image/ruby.sh")
   268  	if err := d.Spawn(ctx, opts, "/src/ruby.sh"); err != nil {
   269  		t.Fatalf("docker run failed: %v", err)
   270  	}
   271  
   272  	// Find container IP address.
   273  	ip, err := d.FindIP(ctx, false)
   274  	if err != nil {
   275  		t.Fatalf("docker.FindIP failed: %v", err)
   276  	}
   277  
   278  	// Wait until it's up and running, 'gem install' can take some time.
   279  	if err := testutil.WaitForHTTP(ip.String(), port, time.Minute); err != nil {
   280  		t.Fatalf("WaitForHTTP() timeout: %v", err)
   281  	}
   282  
   283  	// Ensure that content is being served.
   284  	url := fmt.Sprintf("http://%s:%d", ip.String(), port)
   285  	resp, err := http.Get(url)
   286  	if err != nil {
   287  		t.Errorf("error reaching http server: %v", err)
   288  	}
   289  	if want := http.StatusOK; resp.StatusCode != want {
   290  		t.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
   291  	}
   292  	body, err := ioutil.ReadAll(resp.Body)
   293  	if err != nil {
   294  		t.Fatalf("error reading body: %v", err)
   295  	}
   296  	if got, want := string(body), "Hello World"; !strings.Contains(got, want) {
   297  		t.Errorf("invalid body content, got: %q, want: %q", got, want)
   298  	}
   299  }
   300  
   301  func TestStdio(t *testing.T) {
   302  	ctx := context.Background()
   303  	d := dockerutil.MakeContainer(ctx, t)
   304  	defer d.CleanUp(ctx)
   305  
   306  	wantStdout := "hello stdout"
   307  	wantStderr := "bonjour stderr"
   308  	cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr)
   309  	if err := d.Spawn(ctx, dockerutil.RunOpts{
   310  		Image: "basic/alpine",
   311  	}, "/bin/sh", "-c", cmd); err != nil {
   312  		t.Fatalf("docker run failed: %v", err)
   313  	}
   314  
   315  	for _, want := range []string{wantStdout, wantStderr} {
   316  		if _, err := d.WaitForOutput(ctx, want, defaultWait); err != nil {
   317  			t.Fatalf("docker didn't get output %q : %v", want, err)
   318  		}
   319  	}
   320  }
   321  
   322  func TestMain(m *testing.M) {
   323  	dockerutil.EnsureSupportedDockerVersion()
   324  	flag.Parse()
   325  	os.Exit(m.Run())
   326  }