github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run_linux_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "errors" 24 "fmt" 25 "io" 26 "net/http" 27 "os" 28 "path/filepath" 29 "strconv" 30 "strings" 31 "syscall" 32 "testing" 33 "time" 34 35 "github.com/containerd/nerdctl/pkg/rootlessutil" 36 "github.com/containerd/nerdctl/pkg/strutil" 37 "github.com/containerd/nerdctl/pkg/testutil" 38 "gotest.tools/v3/assert" 39 "gotest.tools/v3/icmd" 40 ) 41 42 func TestRunCustomRootfs(t *testing.T) { 43 testutil.DockerIncompatible(t) 44 base := testutil.NewBase(t) 45 rootfs := prepareCustomRootfs(base, testutil.AlpineImage) 46 defer os.RemoveAll(rootfs) 47 base.Cmd("run", "--rm", "--rootfs", rootfs, "/bin/cat", "/proc/self/environ").AssertOutContains("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") 48 base.Cmd("run", "--rm", "--entrypoint", "/bin/echo", "--rootfs", rootfs, "echo", "foo").AssertOutExactly("echo foo\n") 49 } 50 51 func prepareCustomRootfs(base *testutil.Base, imageName string) string { 52 base.Cmd("pull", imageName).AssertOK() 53 tmpDir, err := os.MkdirTemp(base.T.TempDir(), "test-save") 54 assert.NilError(base.T, err) 55 defer os.RemoveAll(tmpDir) 56 archiveTarPath := filepath.Join(tmpDir, "a.tar") 57 base.Cmd("save", "-o", archiveTarPath, imageName).AssertOK() 58 rootfs, err := os.MkdirTemp(base.T.TempDir(), "rootfs") 59 assert.NilError(base.T, err) 60 err = extractDockerArchive(archiveTarPath, rootfs) 61 assert.NilError(base.T, err) 62 return rootfs 63 } 64 65 func TestRunShmSize(t *testing.T) { 66 t.Parallel() 67 base := testutil.NewBase(t) 68 const shmSize = "32m" 69 70 base.Cmd("run", "--rm", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k") 71 } 72 73 func TestRunPidHost(t *testing.T) { 74 t.Parallel() 75 base := testutil.NewBase(t) 76 pid := os.Getpid() 77 78 base.Cmd("run", "--rm", "--pid=host", testutil.AlpineImage, "ps", "auxw").AssertOutContains(strconv.Itoa(pid)) 79 } 80 81 func TestRunUtsHost(t *testing.T) { 82 t.Parallel() 83 base := testutil.NewBase(t) 84 85 // Was thinking of os.ReadLink("/proc/1/ns/uts") 86 // but you'd get EPERM for rootless. Just validate the 87 // hostname is the same. 88 hostName, err := os.Hostname() 89 assert.NilError(base.T, err) 90 91 base.Cmd("run", "--rm", "--uts=host", testutil.AlpineImage, "hostname").AssertOutContains(hostName) 92 // Validate we can't provide a hostname with uts=host 93 base.Cmd("run", "--rm", "--uts=host", "--hostname=foobar", testutil.AlpineImage, "hostname").AssertFail() 94 } 95 96 func TestRunPidContainer(t *testing.T) { 97 t.Parallel() 98 base := testutil.NewBase(t) 99 100 sharedContainerResult := base.Cmd("run", "-d", testutil.AlpineImage, "sleep", "infinity").Run() 101 baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout()) 102 defer base.Cmd("rm", "-f", baseContainerID).Run() 103 104 base.Cmd("run", "--rm", fmt.Sprintf("--pid=container:%s", baseContainerID), 105 testutil.AlpineImage, "ps", "ax").AssertOutContains("sleep infinity") 106 } 107 108 func TestRunIpcHost(t *testing.T) { 109 t.Parallel() 110 base := testutil.NewBase(t) 111 testFilePath := filepath.Join("/dev/shm", 112 fmt.Sprintf("%s-%d-%s", testutil.Identifier(t), os.Geteuid(), base.Target)) 113 err := os.WriteFile(testFilePath, []byte(""), 0644) 114 assert.NilError(base.T, err) 115 defer os.Remove(testFilePath) 116 117 base.Cmd("run", "--rm", "--ipc=host", testutil.AlpineImage, "ls", testFilePath).AssertOK() 118 } 119 120 func TestRunAddHost(t *testing.T) { 121 // Not parallelizable (https://github.com/containerd/nerdctl/issues/1127) 122 base := testutil.NewBase(t) 123 base.Cmd("run", "--rm", "--add-host", "testing.example.com:10.0.0.1", testutil.AlpineImage, "cat", "/etc/hosts").AssertOutWithFunc(func(stdout string) error { 124 var found bool 125 sc := bufio.NewScanner(bytes.NewBufferString(stdout)) 126 for sc.Scan() { 127 //removing spaces and tabs separating items 128 line := strings.ReplaceAll(sc.Text(), " ", "") 129 line = strings.ReplaceAll(line, "\t", "") 130 if strings.Contains(line, "10.0.0.1testing.example.com") { 131 found = true 132 } 133 } 134 if !found { 135 return errors.New("host was not added") 136 } 137 return nil 138 }) 139 base.Cmd("run", "--rm", "--add-host", "test:10.0.0.1", "--add-host", "test1:10.0.0.1", testutil.AlpineImage, "cat", "/etc/hosts").AssertOutWithFunc(func(stdout string) error { 140 var found int 141 sc := bufio.NewScanner(bytes.NewBufferString(stdout)) 142 for sc.Scan() { 143 //removing spaces and tabs separating items 144 line := strings.ReplaceAll(sc.Text(), " ", "") 145 line = strings.ReplaceAll(line, "\t", "") 146 if strutil.InStringSlice([]string{"10.0.0.1test", "10.0.0.1test1"}, line) { 147 found++ 148 } 149 } 150 if found != 2 { 151 return fmt.Errorf("host was not added, found %d", found) 152 } 153 return nil 154 }) 155 base.Cmd("run", "--rm", "--add-host", "10.0.0.1:testing.example.com", testutil.AlpineImage, "cat", "/etc/hosts").AssertFail() 156 157 response := "This is the expected response for --add-host special IP test." 158 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 159 io.WriteString(w, response) 160 }) 161 const hostPort = 8081 162 s := http.Server{Addr: fmt.Sprintf(":%d", hostPort), Handler: nil, ReadTimeout: 30 * time.Second} 163 go s.ListenAndServe() 164 defer s.Shutdown(context.Background()) 165 base.Cmd("run", "--rm", "--add-host", "test:host-gateway", testutil.NginxAlpineImage, "curl", fmt.Sprintf("test:%d", hostPort)).AssertOutExactly(response) 166 } 167 168 func TestRunAddHostWithCustomHostGatewayIP(t *testing.T) { 169 // Not parallelizable (https://github.com/containerd/nerdctl/issues/1127) 170 base := testutil.NewBase(t) 171 testutil.DockerIncompatible(t) 172 base.Cmd("run", "--rm", "--host-gateway-ip", "192.168.5.2", "--add-host", "test:host-gateway", testutil.AlpineImage, "cat", "/etc/hosts").AssertOutWithFunc(func(stdout string) error { 173 var found bool 174 sc := bufio.NewScanner(bytes.NewBufferString(stdout)) 175 for sc.Scan() { 176 //removing spaces and tabs separating items 177 line := strings.ReplaceAll(sc.Text(), " ", "") 178 line = strings.ReplaceAll(line, "\t", "") 179 if strings.Contains(line, "192.168.5.2test") { 180 found = true 181 } 182 } 183 if !found { 184 return errors.New("host was not added") 185 } 186 return nil 187 }) 188 } 189 190 func TestRunUlimit(t *testing.T) { 191 t.Parallel() 192 base := testutil.NewBase(t) 193 ulimit := "nofile=622:622" 194 ulimit2 := "nofile=622:722" 195 196 base.Cmd("run", "--rm", "--ulimit", ulimit, testutil.AlpineImage, "sh", "-c", "ulimit -Sn").AssertOutExactly("622\n") 197 base.Cmd("run", "--rm", "--ulimit", ulimit, testutil.AlpineImage, "sh", "-c", "ulimit -Hn").AssertOutExactly("622\n") 198 199 base.Cmd("run", "--rm", "--ulimit", ulimit2, testutil.AlpineImage, "sh", "-c", "ulimit -Sn").AssertOutExactly("622\n") 200 base.Cmd("run", "--rm", "--ulimit", ulimit2, testutil.AlpineImage, "sh", "-c", "ulimit -Hn").AssertOutExactly("722\n") 201 } 202 203 func TestRunWithInit(t *testing.T) { 204 t.Parallel() 205 testutil.DockerIncompatible(t) 206 base := testutil.NewBase(t) 207 208 container := testutil.Identifier(t) 209 base.Cmd("run", "-d", "--name", container, testutil.AlpineImage, "sleep", "infinity").AssertOK() 210 defer base.Cmd("rm", "-f", container).Run() 211 212 base.Cmd("stop", "--time=3", container).AssertOK() 213 // Unable to handle TERM signal, be killed when timeout 214 assert.Equal(t, base.InspectContainer(container).State.ExitCode, 137) 215 216 // Test with --init-path 217 container1 := container + "-1" 218 base.Cmd("run", "-d", "--name", container1, "--init-binary", "tini-custom", 219 testutil.AlpineImage, "sleep", "infinity").AssertOK() 220 defer base.Cmd("rm", "-f", container1).Run() 221 222 base.Cmd("stop", "--time=3", container1).AssertOK() 223 assert.Equal(t, base.InspectContainer(container1).State.ExitCode, 143) 224 225 // Test with --init 226 container2 := container + "-2" 227 base.Cmd("run", "-d", "--name", container2, "--init", 228 testutil.AlpineImage, "sleep", "infinity").AssertOK() 229 defer base.Cmd("rm", "-f", container2).Run() 230 231 base.Cmd("stop", "--time=3", container2).AssertOK() 232 assert.Equal(t, base.InspectContainer(container2).State.ExitCode, 143) 233 } 234 235 func TestRunTTY(t *testing.T) { 236 t.Parallel() 237 base := testutil.NewBase(t) 238 if testutil.GetTarget() == testutil.Nerdctl { 239 testutil.RequireDaemonVersion(base, ">= 1.6.0-0") 240 } 241 242 const sttyPartialOutput = "speed 38400 baud" 243 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 244 // unbuffer(1) can be installed with `apt-get install expect`. 245 unbuffer := []string{"unbuffer"} 246 base.CmdWithHelper(unbuffer, "run", "--rm", "-it", testutil.CommonImage, "stty").AssertOutContains(sttyPartialOutput) 247 base.CmdWithHelper(unbuffer, "run", "--rm", "-t", testutil.CommonImage, "stty").AssertOutContains(sttyPartialOutput) 248 base.Cmd("run", "--rm", "-i", testutil.CommonImage, "stty").AssertFail() 249 base.Cmd("run", "--rm", testutil.CommonImage, "stty").AssertFail() 250 251 // tests pipe works 252 res := icmd.RunCmd(icmd.Command("unbuffer", "/bin/sh", "-c", fmt.Sprintf("%q run --rm -it %q echo hi | grep hi", base.Binary, testutil.CommonImage))) 253 assert.Equal(t, 0, res.ExitCode, res.Combined()) 254 } 255 256 func runSigProxy(t *testing.T, args ...string) (string, bool, bool) { 257 t.Parallel() 258 base := testutil.NewBase(t) 259 testContainerName := testutil.Identifier(t) 260 defer base.Cmd("rm", "-f", testContainerName).Run() 261 262 fullArgs := []string{"run"} 263 fullArgs = append(fullArgs, args...) 264 fullArgs = append(fullArgs, 265 "--name", 266 testContainerName, 267 testutil.CommonImage, 268 "sh", 269 "-c", 270 testutil.SigProxyTestScript, 271 ) 272 273 result := base.Cmd(fullArgs...).Start() 274 process := result.Cmd.Process 275 276 // Waits until we reach the trap command in the shell script, then sends SIGINT. 277 time.Sleep(3 * time.Second) 278 syscall.Kill(process.Pid, syscall.SIGINT) 279 280 // Waits until SIGINT is sent and responded to, then kills process to avoid timeout 281 time.Sleep(3 * time.Second) 282 process.Kill() 283 284 sigIntRecieved := strings.Contains(result.Stdout(), testutil.SigProxyTrueOut) 285 timedOut := strings.Contains(result.Stdout(), testutil.SigProxyTimeoutMsg) 286 287 return result.Stdout(), sigIntRecieved, timedOut 288 } 289 290 func TestRunSigProxy(t *testing.T) { 291 292 type testCase struct { 293 name string 294 args []string 295 want bool 296 expectedOut string 297 } 298 testCases := []testCase{ 299 { 300 name: "SigProxyDefault", 301 args: []string{}, 302 want: true, 303 expectedOut: testutil.SigProxyTrueOut, 304 }, 305 { 306 name: "SigProxyTrue", 307 args: []string{"--sig-proxy=true"}, 308 want: true, 309 expectedOut: testutil.SigProxyTrueOut, 310 }, 311 { 312 name: "SigProxyFalse", 313 args: []string{"--sig-proxy=false"}, 314 want: false, 315 expectedOut: "", 316 }, 317 } 318 319 for _, tc := range testCases { 320 tc := tc 321 t.Run(tc.name, func(t *testing.T) { 322 stdout, sigIntRecieved, timedOut := runSigProxy(t, tc.args...) 323 errorMsg := fmt.Sprintf("%s failed;\nExpected: '%s'\nActual: '%s'", tc.name, tc.expectedOut, stdout) 324 assert.Equal(t, false, timedOut, errorMsg) 325 assert.Equal(t, tc.want, sigIntRecieved, errorMsg) 326 }) 327 } 328 } 329 330 func TestRunWithFluentdLogDriver(t *testing.T) { 331 base := testutil.NewBase(t) 332 tempDirectory := t.TempDir() 333 err := os.Chmod(tempDirectory, 0777) 334 assert.NilError(t, err) 335 336 containerName := testutil.Identifier(t) 337 base.Cmd("run", "-d", "--name", containerName, "-p", "24224:24224", 338 "-v", fmt.Sprintf("%s:/fluentd/log", tempDirectory), testutil.FluentdImage).AssertOK() 339 defer base.Cmd("rm", "-f", containerName).AssertOK() 340 time.Sleep(3 * time.Second) 341 342 testContainerName := containerName + "test" 343 base.Cmd("run", "-d", "--log-driver", "fluentd", "--name", testContainerName, testutil.CommonImage, 344 "sh", "-c", "echo test").AssertOK() 345 defer base.Cmd("rm", "-f", testContainerName).AssertOK() 346 347 inspectedContainer := base.InspectContainer(testContainerName) 348 matches, err := filepath.Glob(tempDirectory + "/" + "data.*.log") 349 assert.NilError(t, err) 350 assert.Equal(t, 1, len(matches)) 351 352 data, err := os.ReadFile(matches[0]) 353 assert.NilError(t, err) 354 logData := string(data) 355 assert.Equal(t, true, strings.Contains(logData, "test")) 356 assert.Equal(t, true, strings.Contains(logData, inspectedContainer.ID)) 357 } 358 359 func TestRunWithFluentdLogDriverWithLogOpt(t *testing.T) { 360 base := testutil.NewBase(t) 361 tempDirectory := t.TempDir() 362 err := os.Chmod(tempDirectory, 0777) 363 assert.NilError(t, err) 364 365 containerName := testutil.Identifier(t) 366 base.Cmd("run", "-d", "--name", containerName, "-p", "24225:24224", 367 "-v", fmt.Sprintf("%s:/fluentd/log", tempDirectory), testutil.FluentdImage).AssertOK() 368 defer base.Cmd("rm", "-f", containerName).AssertOK() 369 time.Sleep(3 * time.Second) 370 371 testContainerName := containerName + "test" 372 base.Cmd("run", "-d", "--log-driver", "fluentd", "--log-opt", "fluentd-address=127.0.0.1:24225", 373 "--name", testContainerName, testutil.CommonImage, "sh", "-c", "echo test2").AssertOK() 374 defer base.Cmd("rm", "-f", testContainerName).AssertOK() 375 376 inspectedContainer := base.InspectContainer(testContainerName) 377 matches, err := filepath.Glob(tempDirectory + "/" + "data.*.log") 378 assert.NilError(t, err) 379 assert.Equal(t, 1, len(matches)) 380 381 data, err := os.ReadFile(matches[0]) 382 assert.NilError(t, err) 383 logData := string(data) 384 assert.Equal(t, true, strings.Contains(logData, "test2")) 385 assert.Equal(t, true, strings.Contains(logData, inspectedContainer.ID)) 386 } 387 388 func TestRunWithOOMScoreAdj(t *testing.T) { 389 if rootlessutil.IsRootless() { 390 t.Skip("test skipped for rootless containers.") 391 } 392 t.Parallel() 393 base := testutil.NewBase(t) 394 var score = "-42" 395 396 base.Cmd("run", "--rm", "--oom-score-adj", score, testutil.AlpineImage, "cat", "/proc/self/oom_score_adj").AssertOutContains(score) 397 } 398 399 func TestRunWithDetachKeys(t *testing.T) { 400 t.Parallel() 401 402 if testutil.GetTarget() == testutil.Docker { 403 t.Skip("When detaching from a container, for a session started with 'docker attach'" + 404 ", it prints 'read escape sequence', but for one started with 'docker (run|start)', it prints nothing." + 405 " However, the flag is called '--detach-keys' in all cases" + 406 ", so nerdctl prints 'read detach keys' for all cases" + 407 ", and that's why this test is skipped for Docker.") 408 } 409 410 base := testutil.NewBase(t) 411 containerName := testutil.Identifier(t) 412 opts := []func(*testutil.Cmd){ 413 testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2}))), // https://www.physics.udel.edu/~watson/scen103/ascii.html 414 } 415 defer base.Cmd("container", "rm", "-f", containerName).AssertOK() 416 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 417 // unbuffer(1) can be installed with `apt-get install expect`. 418 // 419 // "-p" is needed because we need unbuffer to read from stdin, and from [1]: 420 // "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations. 421 // To use unbuffer in a pipeline, use the -p flag." 422 // 423 // [1] https://linux.die.net/man/1/unbuffer 424 base.CmdWithHelper([]string{"unbuffer", "-p"}, "run", "-it", "--detach-keys=ctrl-a,ctrl-b", "--name", containerName, testutil.CommonImage). 425 CmdOption(opts...).AssertOutContains("read detach keys") 426 container := base.InspectContainer(containerName) 427 assert.Equal(base.T, container.State.Running, true) 428 }