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 }