github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/exec.bats (about) 1 #!/usr/bin/env bats 2 3 load helpers 4 5 function setup() { 6 setup_busybox 7 } 8 9 function teardown() { 10 teardown_bundle 11 } 12 13 @test "runc exec" { 14 # run busybox detached 15 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 16 [ "$status" -eq 0 ] 17 18 runc exec test_busybox echo Hello from exec 19 [ "$status" -eq 0 ] 20 echo text echoed = "'""${output}""'" 21 [[ "${output}" == *"Hello from exec"* ]] 22 } 23 24 @test "runc exec [exit codes]" { 25 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 26 [ "$status" -eq 0 ] 27 28 runc exec test_busybox false 29 [ "$status" -eq 1 ] 30 31 runc exec test_busybox sh -c "exit 42" 32 [ "$status" -eq 42 ] 33 34 runc exec --pid-file /non-existent/directory test_busybox true 35 [ "$status" -eq 255 ] 36 37 runc exec test_busybox no-such-binary 38 [ "$status" -eq 255 ] 39 40 runc exec no_such_container true 41 [ "$status" -eq 255 ] 42 } 43 44 @test "runc exec --pid-file" { 45 # run busybox detached 46 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 47 [ "$status" -eq 0 ] 48 49 runc exec --pid-file pid.txt test_busybox echo Hello from exec 50 [ "$status" -eq 0 ] 51 echo text echoed = "'""${output}""'" 52 [[ "${output}" == *"Hello from exec"* ]] 53 54 # check pid.txt was generated 55 [ -e pid.txt ] 56 57 output=$(cat pid.txt) 58 [[ "$output" =~ [0-9]+ ]] 59 [[ "$output" != $(__runc state test_busybox | jq '.pid') ]] 60 } 61 62 @test "runc exec --pid-file with new CWD" { 63 bundle="$(pwd)" 64 # create pid_file directory as the CWD 65 mkdir pid_file 66 cd pid_file 67 68 # run busybox detached 69 runc run -d -b "$bundle" --console-socket "$CONSOLE_SOCKET" test_busybox 70 [ "$status" -eq 0 ] 71 72 runc exec --pid-file pid.txt test_busybox echo Hello from exec 73 [ "$status" -eq 0 ] 74 echo text echoed = "'""${output}""'" 75 [[ "${output}" == *"Hello from exec"* ]] 76 77 # check pid.txt was generated 78 [ -e pid.txt ] 79 80 output=$(cat pid.txt) 81 [[ "$output" =~ [0-9]+ ]] 82 [[ "$output" != $(__runc state test_busybox | jq '.pid') ]] 83 } 84 85 @test "runc exec ls -la" { 86 # run busybox detached 87 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 88 [ "$status" -eq 0 ] 89 90 runc exec test_busybox ls -la 91 [ "$status" -eq 0 ] 92 [[ ${lines[0]} == *"total"* ]] 93 [[ ${lines[1]} == *"."* ]] 94 [[ ${lines[2]} == *".."* ]] 95 } 96 97 @test "runc exec ls -la with --cwd" { 98 # run busybox detached 99 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 100 [ "$status" -eq 0 ] 101 102 runc exec --cwd /bin test_busybox pwd 103 [ "$status" -eq 0 ] 104 [[ ${output} == "/bin"* ]] 105 } 106 107 @test "runc exec --env" { 108 # run busybox detached 109 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 110 [ "$status" -eq 0 ] 111 112 runc exec --env RUNC_EXEC_TEST=true test_busybox env 113 [ "$status" -eq 0 ] 114 115 [[ ${output} == *"RUNC_EXEC_TEST=true"* ]] 116 } 117 118 @test "runc exec --user" { 119 # --user can't work in rootless containers that don't have idmap. 120 [ $EUID -ne 0 ] && requires rootless_idmap 121 122 # run busybox detached 123 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 124 [ "$status" -eq 0 ] 125 126 runc exec --user 1000:1000 test_busybox id 127 [ "$status" -eq 0 ] 128 [[ "${output}" == "uid=1000 gid=1000"* ]] 129 } 130 131 # https://github.com/opencontainers/runc/issues/3674. 132 @test "runc exec --user vs /dev/null ownership" { 133 requires root 134 135 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 136 [ "$status" -eq 0 ] 137 138 ls -l /dev/null 139 __runc exec -d --user 1000:1000 test_busybox id </dev/null 140 ls -l /dev/null 141 UG=$(stat -c %u:%g /dev/null) 142 143 # Host's /dev/null must be owned by root. 144 [ "$UG" = "0:0" ] 145 } 146 147 @test "runc exec --additional-gids" { 148 requires root 149 150 # run busybox detached 151 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 152 [ "$status" -eq 0 ] 153 154 wait_for_container 15 1 test_busybox 155 156 runc exec --user 1000:1000 --additional-gids 100 --additional-gids 65534 test_busybox id -G 157 [ "$status" -eq 0 ] 158 [ "$output" = "1000 100 65534" ] 159 } 160 161 @test "runc exec --preserve-fds" { 162 # run busybox detached 163 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 164 [ "$status" -eq 0 ] 165 166 echo hello >preserve-fds.test 167 # fd 3 is used by bats, so we use 4 168 exec 4<preserve-fds.test 169 runc exec --preserve-fds=2 test_busybox cat /proc/self/fd/4 170 [ "$status" -eq 0 ] 171 [ "${output}" = "hello" ] 172 } 173 174 function check_exec_debug() { 175 [[ "$*" == *"nsexec container setup"* ]] 176 [[ "$*" == *"child process in init()"* ]] 177 [[ "$*" == *"setns_init: about to exec"* ]] 178 } 179 180 @test "runc --debug exec" { 181 runc run -d --console-socket "$CONSOLE_SOCKET" test 182 [ "$status" -eq 0 ] 183 184 runc --debug exec test true 185 [ "$status" -eq 0 ] 186 [[ "${output}" == *"level=debug"* ]] 187 check_exec_debug "$output" 188 } 189 190 @test "runc --debug --log exec" { 191 runc run -d --console-socket "$CONSOLE_SOCKET" test 192 [ "$status" -eq 0 ] 193 194 runc --debug --log log.out exec test true 195 # check output does not include debug info 196 [[ "${output}" != *"level=debug"* ]] 197 198 cat log.out >&2 199 # check expected debug output was sent to log.out 200 output=$(cat log.out) 201 [[ "${output}" == *"level=debug"* ]] 202 check_exec_debug "$output" 203 } 204 205 @test "runc exec --cgroup sub-cgroups [v1]" { 206 requires root cgroups_v1 207 208 set_cgroups_path 209 set_cgroup_mount_writable 210 211 __runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 212 testcontainer test_busybox running 213 214 # Check we can't join parent cgroup. 215 runc exec --cgroup ".." test_busybox cat /proc/self/cgroup 216 [ "$status" -ne 0 ] 217 [[ "$output" == *" .. is not a sub cgroup path"* ]] 218 219 # Check we can't join non-existing subcgroup. 220 runc exec --cgroup nonexistent test_busybox cat /proc/self/cgroup 221 [ "$status" -ne 0 ] 222 [[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]] 223 224 # Check we can't join non-existing subcgroup (for a particular controller). 225 runc exec --cgroup cpu:nonexistent test_busybox cat /proc/self/cgroup 226 [ "$status" -ne 0 ] 227 [[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]] 228 229 # Check we can't specify non-existent controller. 230 runc exec --cgroup whaaat:/ test_busybox true 231 [ "$status" -ne 0 ] 232 [[ "$output" == *"unknown controller "* ]] 233 234 # Check we can join top-level cgroup (implicit). 235 runc exec test_busybox cat /proc/self/cgroup 236 [ "$status" -eq 0 ] 237 run ! grep -v ":$REL_CGROUPS_PATH\$" <<<"$output" 238 239 # Check we can join top-level cgroup (explicit). 240 runc exec --cgroup / test_busybox cat /proc/self/cgroup 241 [ "$status" -eq 0 ] 242 run ! grep -v ":$REL_CGROUPS_PATH\$" <<<"$output" 243 244 # Create a few subcgroups. 245 # Note that cpu,cpuacct may be mounted together or separate. 246 runc exec test_busybox sh -euc "mkdir -p /sys/fs/cgroup/memory/submem /sys/fs/cgroup/cpu/subcpu /sys/fs/cgroup/cpuacct/subcpu" 247 [ "$status" -eq 0 ] 248 249 # Check that explicit --cgroup works. 250 runc exec --cgroup memory:submem --cgroup cpu,cpuacct:subcpu test_busybox cat /proc/self/cgroup 251 [ "$status" -eq 0 ] 252 [[ "$output" == *":memory:$REL_CGROUPS_PATH/submem"* ]] 253 [[ "$output" == *":cpu"*":$REL_CGROUPS_PATH/subcpu"* ]] 254 } 255 256 @test "runc exec --cgroup subcgroup [v2]" { 257 requires root cgroups_v2 258 259 set_cgroups_path 260 set_cgroup_mount_writable 261 262 __runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 263 testcontainer test_busybox running 264 265 # Check we can't join parent cgroup. 266 runc exec --cgroup ".." test_busybox cat /proc/self/cgroup 267 [ "$status" -ne 0 ] 268 [[ "$output" == *" .. is not a sub cgroup path"* ]] 269 270 # Check we can't join non-existing subcgroup. 271 runc exec --cgroup nonexistent test_busybox cat /proc/self/cgroup 272 [ "$status" -ne 0 ] 273 [[ "$output" == *" adding pid "*"/nonexistent/cgroup.procs: no such file "* ]] 274 275 # Check we can join top-level cgroup (implicit). 276 runc exec test_busybox grep '^0::/$' /proc/self/cgroup 277 [ "$status" -eq 0 ] 278 279 # Check we can join top-level cgroup (explicit). 280 runc exec --cgroup / test_busybox grep '^0::/$' /proc/self/cgroup 281 [ "$status" -eq 0 ] 282 283 # Now move "init" to a subcgroup, and check it was moved. 284 runc exec test_busybox sh -euc "mkdir /sys/fs/cgroup/foobar \ 285 && echo 1 > /sys/fs/cgroup/foobar/cgroup.procs \ 286 && grep -w foobar /proc/1/cgroup" 287 [ "$status" -eq 0 ] 288 289 # The following part is taken from 290 # @test "runc exec (cgroup v2 + init process in non-root cgroup) succeeds" 291 292 # The init process is now in "/foo", but an exec process can still 293 # join "/" because we haven't enabled any domain controller yet. 294 runc exec test_busybox grep '^0::/$' /proc/self/cgroup 295 [ "$status" -eq 0 ] 296 297 # Turn on a domain controller (memory). 298 runc exec test_busybox sh -euc 'echo $$ > /sys/fs/cgroup/foobar/cgroup.procs; echo +memory > /sys/fs/cgroup/cgroup.subtree_control' 299 [ "$status" -eq 0 ] 300 301 # An exec process can no longer join "/" after turning on a domain 302 # controller. Check that cgroup v2 fallback to init cgroup works. 303 runc exec test_busybox sh -euc "cat /proc/self/cgroup && grep '^0::/foobar$' /proc/self/cgroup" 304 [ "$status" -eq 0 ] 305 306 # Check that --cgroup / disables the init cgroup fallback. 307 runc exec --cgroup / test_busybox true 308 [ "$status" -ne 0 ] 309 [[ "$output" == *" adding pid "*" to cgroups"*"/cgroup.procs: device or resource busy"* ]] 310 311 # Check that explicit --cgroup foobar works. 312 runc exec --cgroup foobar test_busybox grep '^0::/foobar$' /proc/self/cgroup 313 [ "$status" -eq 0 ] 314 315 # Check all processes is in foobar (this check is redundant). 316 runc exec --cgroup foobar test_busybox sh -euc '! grep -vwH foobar /proc/*/cgroup' 317 [ "$status" -eq 0 ] 318 319 # Add a second subcgroup, check we're in it. 320 runc exec --cgroup foobar test_busybox mkdir /sys/fs/cgroup/second 321 [ "$status" -eq 0 ] 322 runc exec --cgroup second test_busybox grep -w second /proc/self/cgroup 323 [ "$status" -eq 0 ] 324 } 325 326 @test "runc exec [execve error]" { 327 cat <<EOF >rootfs/run.sh 328 #!/mmnnttbb foo bar 329 sh 330 EOF 331 chmod +x rootfs/run.sh 332 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 333 runc exec -t test_busybox /run.sh 334 [ "$status" -ne 0 ] 335 336 # After the sync socket closed, we should not send error to parent 337 # process, or else we will get a unnecessary error log(#4171). 338 # Although we never close the sync socket when doing exec, 339 # but we need to keep this test to ensure this behavior is always right. 340 [ ${#lines[@]} -eq 1 ] 341 [[ ${lines[0]} = *"exec /run.sh: no such file or directory"* ]] 342 } 343 344 @test "runc exec with isolated cpus affinity temporary transition [cgroup cpuset]" { 345 requires root cgroups_cpuset 346 347 tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX") 348 349 set_cgroup_cpuset_all_cpus 350 local all_cpus 351 all_cpus="$(get_all_online_cpus)" 352 353 # set temporary isolated CPU affinity transition 354 update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "temporary"}' 355 356 runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_temporary_transition 357 [ "$status" -eq 0 ] 358 359 # set all online cpus as isolated 360 echo "nohz_full=$all_cpus" >"$tmp/cmdline" 361 362 mount --bind "$tmp/cmdline" /proc/cmdline 363 364 runc exec test_isolated_temporary_transition grep "Cpus_allowed_list:" /proc/self/status 365 366 umount /proc/cmdline 367 368 [ "$status" -eq 0 ] 369 [[ "${lines[0]}" == "Cpus_allowed_list: $all_cpus" ]] 370 } 371 372 @test "runc exec with isolated cpus affinity definitive transition [cgroup cpuset]" { 373 requires root cgroups_cpuset 374 375 tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX") 376 377 set_cgroup_cpuset_all_cpus 378 local all_cpus 379 all_cpus="$(get_all_online_cpus)" 380 381 # set definitive isolated CPU affinity transition 382 update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "definitive"}' 383 384 runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_definitive_transition 385 [ "$status" -eq 0 ] 386 387 # set all online cpus as isolated 388 echo "nohz_full=$all_cpus" >"$tmp/cmdline" 389 390 mount --bind "$tmp/cmdline" /proc/cmdline 391 392 runc exec test_isolated_definitive_transition grep "Cpus_allowed_list:" /proc/self/status 393 394 umount /proc/cmdline 395 396 [ "$status" -eq 0 ] 397 398 load /etc/os-release 399 400 # fix unbound variable in condition below 401 VERSION_ID=${VERSION_ID:-} 402 403 allowed_cpus=$all_cpus 404 # use first cpu on systems with RHEL >= 9 or systems with kernel >= 6.2 405 if [[ "${ID_LIKE:-}" =~ "rhel" && "${VERSION_ID%%.*}" -ge "9" ]] || is_kernel_gte 6.2; then 406 allowed_cpus="$(get_first_online_cpu)" 407 fi 408 409 [[ "${lines[0]}" == "Cpus_allowed_list: $allowed_cpus" ]] 410 } 411 412 @test "runc exec with isolated cpus affinity bad transition [cgroup cpuset]" { 413 requires root cgroups_cpuset 414 415 tmp=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX") 416 417 set_cgroup_cpuset_all_cpus 418 local all_cpus 419 all_cpus="$(get_all_online_cpus)" 420 421 # set a bad isolated CPU affinity transition 422 update_config '.annotations += {"org.opencontainers.runc.exec.isolated-cpu-affinity-transition": "bad"}' 423 424 runc run -d --console-socket "$CONSOLE_SOCKET" test_isolated_bad_transition 425 [ "$status" -eq 0 ] 426 427 # set all online cpus as isolated 428 echo "nohz_full=$all_cpus" >"$tmp/cmdline" 429 430 mount --bind "$tmp/cmdline" /proc/cmdline 431 432 runc exec test_isolated_bad_transition true 433 434 umount /proc/cmdline 435 436 [ "$status" -eq 255 ] 437 } 438 439 @test "runc exec with taskset affinity [cgroup cpuset]" { 440 requires root cgroups_cpuset 441 442 set_cgroup_cpuset_all_cpus 443 local all_cpus 444 all_cpus="$(get_all_online_cpus)" 445 446 taskset -p -c "$(get_first_online_cpu)" $$ 447 448 runc run -d --console-socket "$CONSOLE_SOCKET" test_with_taskset 449 [ "$status" -eq 0 ] 450 451 runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/1/status 452 [ "$status" -eq 0 ] 453 [[ "${lines[0]}" == "Cpus_allowed_list: $all_cpus" ]] 454 455 runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/self/status 456 [ "$status" -eq 0 ] 457 [[ "${lines[0]}" == "Cpus_allowed_list: $all_cpus" ]] 458 } 459 460 @test "runc exec with taskset affinity [rootless cgroups_v2]" { 461 requires rootless cgroups_v2 462 463 local all_cpus 464 all_cpus="$(get_all_online_cpus)" 465 466 taskset -p -c "$(get_first_online_cpu)" $$ 467 468 runc run -d --console-socket "$CONSOLE_SOCKET" test_with_taskset 469 [ "$status" -eq 0 ] 470 471 runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/1/status 472 [ "$status" -eq 0 ] 473 [[ "${lines[0]}" == "Cpus_allowed_list: $all_cpus" ]] 474 475 runc exec test_with_taskset grep "Cpus_allowed_list:" /proc/self/status 476 [ "$status" -eq 0 ] 477 [[ "${lines[0]}" == "Cpus_allowed_list: $all_cpus" ]] 478 }