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 }