github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/helpers.bash (about) 1 #!/bin/bash 2 3 set -u 4 5 bats_require_minimum_version 1.5.0 6 7 # Root directory of integration tests. 8 INTEGRATION_ROOT=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") 9 10 # Download images, get *_IMAGE variables. 11 IMAGES=$("${INTEGRATION_ROOT}"/get-images.sh) 12 eval "$IMAGES" 13 unset IMAGES 14 15 : "${RUNC:="${INTEGRATION_ROOT}/../../runc"}" 16 RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty" 17 SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper" 18 SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent" 19 FS_IDMAP="${INTEGRATION_ROOT}/../../contrib/cmd/fs-idmap/fs-idmap" 20 PIDFD_KILL="${INTEGRATION_ROOT}/../../contrib/cmd/pidfd-kill/pidfd-kill" 21 REMAP_ROOTFS="${INTEGRATION_ROOT}/../../contrib/cmd/remap-rootfs/remap-rootfs" 22 23 # Some variables may not always be set. Set those to empty value, 24 # if unset, to avoid "unbound variable" error. 25 : "${ROOTLESS_FEATURES:=}" 26 27 # Test data path. 28 # shellcheck disable=SC2034 29 TESTDATA="${INTEGRATION_ROOT}/testdata" 30 31 # Kernel version 32 KERNEL_VERSION="$(uname -r)" 33 KERNEL_MAJOR="${KERNEL_VERSION%%.*}" 34 KERNEL_MINOR="${KERNEL_VERSION#"$KERNEL_MAJOR".}" 35 KERNEL_MINOR="${KERNEL_MINOR%%.*}" 36 37 ARCH=$(uname -m) 38 39 # Seccomp agent socket. 40 SECCCOMP_AGENT_SOCKET="$BATS_TMPDIR/seccomp-agent.sock" 41 42 # Wrapper for runc. 43 function runc() { 44 run __runc "$@" 45 46 # Some debug information to make life easier. bats will only print it if the 47 # test failed, in which case the output is useful. 48 # shellcheck disable=SC2154 49 echo "$(basename "$RUNC") $* (status=$status):" >&2 50 # shellcheck disable=SC2154 51 echo "$output" >&2 52 } 53 54 # Raw wrapper for runc. 55 function __runc() { 56 "$RUNC" ${RUNC_USE_SYSTEMD+--systemd-cgroup} \ 57 ${ROOT:+--root "$ROOT/state"} "$@" 58 } 59 60 # Wrapper for runc spec. 61 function runc_spec() { 62 local rootless="" 63 [ $EUID -ne 0 ] && rootless="--rootless" 64 65 runc spec $rootless 66 67 # Always add additional mappings if we have idmaps. 68 if [[ $EUID -ne 0 && "$ROOTLESS_FEATURES" == *"idmap"* ]]; then 69 runc_rootless_idmap 70 fi 71 } 72 73 # Helper function to reformat config.json file. Input uses jq syntax. 74 function update_config() { 75 jq "$@" "./config.json" | awk 'BEGIN{RS="";getline<"-";print>ARGV[1]}' "./config.json" 76 } 77 78 # Shortcut to add additional uids and gids, based on the values set as part of 79 # a rootless configuration. 80 function runc_rootless_idmap() { 81 update_config ' .mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .) 82 | .linux.uidMappings += [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}] 83 | .linux.gidMappings += [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}] 84 | .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 10))"', "containerID": 1, "size": 20}] 85 | .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 100))"', "containerID": 1000, "size": '"$((ROOTLESS_GIDMAP_LENGTH - 1000))"'}]' 86 } 87 88 # Returns systemd version as a number (-1 if systemd is not enabled/supported). 89 function systemd_version() { 90 if [ -v RUNC_USE_SYSTEMD ]; then 91 systemctl --version | awk '/^systemd / {print $2; exit}' 92 return 93 fi 94 95 echo "-1" 96 } 97 98 function init_cgroup_paths() { 99 # init once 100 [[ -v CGROUP_V1 || -v CGROUP_V2 ]] && return 101 102 if stat -f -c %t /sys/fs/cgroup | grep -qFw 63677270; then 103 CGROUP_V2=yes 104 local controllers="/sys/fs/cgroup/cgroup.controllers" 105 # For rootless + systemd case, controllers delegation is required, 106 # so check the controllers that the current user has, not the top one. 107 # NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04). 108 if [[ $EUID -ne 0 && -v RUNC_USE_SYSTEMD ]]; then 109 controllers="/sys/fs/cgroup/user.slice/user-${UID}.slice/user@${UID}.service/cgroup.controllers" 110 fi 111 112 # "pseudo" controllers do not appear in /sys/fs/cgroup/cgroup.controllers. 113 # - devices (since kernel 4.15) we must assume to be supported because 114 # it's quite hard to test. 115 # - freezer (since kernel 5.2) we can auto-detect by looking for the 116 # "cgroup.freeze" file a *non-root* cgroup. 117 CGROUP_SUBSYSTEMS=$( 118 cat "$controllers" 119 echo devices 120 ) 121 CGROUP_BASE_PATH=/sys/fs/cgroup 122 123 # Find any cgroup.freeze files... 124 if [ -n "$(find "$CGROUP_BASE_PATH" -type f -name "cgroup.freeze" -print -quit)" ]; then 125 CGROUP_SUBSYSTEMS+=" freezer" 126 fi 127 else 128 if stat -f -c %t /sys/fs/cgroup/unified 2>/dev/null | grep -qFw 63677270; then 129 CGROUP_HYBRID=yes 130 fi 131 CGROUP_V1=yes 132 CGROUP_SUBSYSTEMS=$(awk '!/^#/ {print $1}' /proc/cgroups) 133 local g base_path 134 for g in ${CGROUP_SUBSYSTEMS}; do 135 # This uses gawk-specific feature (\< ... \>). 136 base_path=$(gawk '$(NF-2) == "cgroup" && $NF ~ /\<'"${g}"'\>/ { print $5; exit }' /proc/self/mountinfo) 137 test -z "$base_path" && continue 138 eval CGROUP_"${g^^}"_BASE_PATH="${base_path}" 139 done 140 fi 141 } 142 143 function create_parent() { 144 if [ -v RUNC_USE_SYSTEMD ]; then 145 [ ! -v SD_PARENT_NAME ] && return 146 "$SD_HELPER" --parent machine.slice start "$SD_PARENT_NAME" 147 else 148 [ ! -v REL_PARENT_PATH ] && return 149 if [ -v CGROUP_V2 ]; then 150 mkdir "/sys/fs/cgroup$REL_PARENT_PATH" 151 else 152 local subsys 153 for subsys in ${CGROUP_SUBSYSTEMS}; do 154 # Have to ignore EEXIST (-p) as some subsystems 155 # are mounted together (e.g. cpu,cpuacct), so 156 # the path is created more than once. 157 mkdir -p "/sys/fs/cgroup/$subsys$REL_PARENT_PATH" 158 done 159 fi 160 fi 161 } 162 163 function remove_parent() { 164 if [ -v RUNC_USE_SYSTEMD ]; then 165 [ ! -v SD_PARENT_NAME ] && return 166 "$SD_HELPER" --parent machine.slice stop "$SD_PARENT_NAME" 167 else 168 [ ! -v REL_PARENT_PATH ] && return 169 if [ -v CGROUP_V2 ]; then 170 rmdir "/sys/fs/cgroup/$REL_PARENT_PATH" 171 else 172 local subsys 173 for subsys in ${CGROUP_SUBSYSTEMS} systemd; do 174 rmdir "/sys/fs/cgroup/$subsys/$REL_PARENT_PATH" 175 done 176 fi 177 fi 178 unset SD_PARENT_NAME 179 unset REL_PARENT_PATH 180 } 181 182 function set_parent_systemd_properties() { 183 [ ! -v SD_PARENT_NAME ] && return 184 local user="" 185 [ $EUID -ne 0 ] && user="--user" 186 systemctl set-property $user "$SD_PARENT_NAME" "$@" 187 } 188 189 # Randomize cgroup path(s), and update cgroupsPath in config.json. 190 # This function also sets a few cgroup-related variables that are used 191 # by other cgroup-related functions. 192 # 193 # If this function is not called (and cgroupsPath is not set in config), 194 # runc uses default container's cgroup path derived from the container's name 195 # (except for rootless containers, that have no default cgroup path). 196 # 197 # Optional parameter $1 is a pod/parent name. If set, a parent/pod cgroup is 198 # created, and variables $REL_PARENT_PATH and $SD_PARENT_NAME can be used to 199 # refer to it. 200 function set_cgroups_path() { 201 init_cgroup_paths 202 local pod dash_pod="" slash_pod="" pod_slice="" 203 if [ "$#" -ne 0 ] && [ "$1" != "" ]; then 204 # Set up a parent/pod cgroup. 205 pod="$1" 206 dash_pod="-$pod" 207 slash_pod="/$pod" 208 SD_PARENT_NAME="machine-${pod}.slice" 209 pod_slice="/$SD_PARENT_NAME" 210 fi 211 212 local rnd="$RANDOM" 213 if [ -v RUNC_USE_SYSTEMD ]; then 214 SD_UNIT_NAME="runc-cgroups-integration-test-${rnd}.scope" 215 if [ $EUID -eq 0 ]; then 216 REL_PARENT_PATH="/machine.slice${pod_slice}" 217 OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}" 218 else 219 REL_PARENT_PATH="/user.slice/user-${UID}.slice/user@${UID}.service/machine.slice${pod_slice}" 220 # OCI path doesn't contain "/user.slice/user-${UID}.slice/user@${UID}.service/" prefix 221 OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}" 222 fi 223 REL_CGROUPS_PATH="$REL_PARENT_PATH/$SD_UNIT_NAME" 224 else 225 REL_PARENT_PATH="/runc-cgroups-integration-test${slash_pod}" 226 REL_CGROUPS_PATH="$REL_PARENT_PATH/test-cgroup-${rnd}" 227 OCI_CGROUPS_PATH=$REL_CGROUPS_PATH 228 fi 229 230 # Absolute path to container's cgroup v2. 231 if [ -v CGROUP_V2 ]; then 232 CGROUP_V2_PATH=${CGROUP_BASE_PATH}${REL_CGROUPS_PATH} 233 fi 234 235 [ -v pod ] && create_parent 236 237 update_config '.linux.cgroupsPath |= "'"${OCI_CGROUPS_PATH}"'"' 238 } 239 240 # Get a path to cgroup directory, based on controller name. 241 # Parameters: 242 # $1: controller name (like "pids") or a file name (like "pids.max"). 243 function get_cgroup_path() { 244 if [ -v CGROUP_V2 ]; then 245 echo "$CGROUP_V2_PATH" 246 return 247 fi 248 249 local var cgroup 250 var=${1%%.*} # controller name (e.g. memory) 251 var=CGROUP_${var^^}_BASE_PATH # variable name (e.g. CGROUP_MEMORY_BASE_PATH) 252 eval cgroup=\$"${var}${REL_CGROUPS_PATH}" 253 echo "$cgroup" 254 } 255 256 # Get a value from a cgroup file. 257 function get_cgroup_value() { 258 local cgroup 259 cgroup="$(get_cgroup_path "$1")" 260 cat "$cgroup/$1" 261 } 262 263 # Helper to check a if value in a cgroup file matches the expected one. 264 function check_cgroup_value() { 265 local current 266 current="$(get_cgroup_value "$1")" 267 local expected=$2 268 269 echo "current $current !? $expected" 270 [ "$current" = "$expected" ] 271 } 272 273 # Helper to check a value in systemd. 274 function check_systemd_value() { 275 [ ! -v RUNC_USE_SYSTEMD ] && return 276 local source="$1" 277 [ "$source" = "unsupported" ] && return 278 local expected="$2" 279 local expected2="${3:-}" 280 local user="" 281 [ $EUID -ne 0 ] && user="--user" 282 283 current=$(systemctl show $user --property "$source" "$SD_UNIT_NAME" | awk -F= '{print $2}') 284 echo "systemd $source: current $current !? $expected $expected2" 285 [ "$current" = "$expected" ] || [[ -n "$expected2" && "$current" = "$expected2" ]] 286 } 287 288 function check_cpu_quota() { 289 local quota=$1 290 local period=$2 291 local sd_quota=$3 292 293 if [ -v CGROUP_V2 ]; then 294 if [ "$quota" = "-1" ]; then 295 quota="max" 296 fi 297 check_cgroup_value "cpu.max" "$quota $period" 298 else 299 check_cgroup_value "cpu.cfs_quota_us" "$quota" 300 check_cgroup_value "cpu.cfs_period_us" "$period" 301 fi 302 # systemd values are the same for v1 and v2 303 check_systemd_value "CPUQuotaPerSecUSec" "$sd_quota" 304 305 # CPUQuotaPeriodUSec requires systemd >= v242 306 [ "$(systemd_version)" -lt 242 ] && return 307 308 local sd_period=$((period / 1000))ms 309 [ "$sd_period" = "1000ms" ] && sd_period="1s" 310 local sd_infinity="" 311 # 100ms is the default value, and if not set, shown as infinity 312 [ "$sd_period" = "100ms" ] && sd_infinity="infinity" 313 check_systemd_value "CPUQuotaPeriodUSec" $sd_period $sd_infinity 314 } 315 316 function check_cpu_burst() { 317 local burst=$1 318 if [ -v CGROUP_V2 ]; then 319 burst=$((burst / 1000)) 320 check_cgroup_value "cpu.max.burst" "$burst" 321 else 322 check_cgroup_value "cpu.cfs_burst_us" "$burst" 323 fi 324 } 325 326 # Works for cgroup v1 and v2, accepts v1 shares as an argument. 327 function check_cpu_shares() { 328 local shares=$1 329 330 if [ -v CGROUP_V2 ]; then 331 local weight=$((1 + ((shares - 2) * 9999) / 262142)) 332 check_cpu_weight "$weight" 333 else 334 check_cgroup_value "cpu.shares" "$shares" 335 check_systemd_value "CPUShares" "$shares" 336 fi 337 } 338 339 # Works only for cgroup v2, accept v2 weight. 340 function check_cpu_weight() { 341 local weight=$1 342 343 check_cgroup_value "cpu.weight" "$weight" 344 check_systemd_value "CPUWeight" "$weight" 345 } 346 347 # Helper function to set a resources limit 348 function set_resources_limit() { 349 update_config '.linux.resources.pids.limit |= 100' 350 } 351 352 # Helper function to make /sys/fs/cgroup writable 353 function set_cgroup_mount_writable() { 354 update_config '.mounts |= map((select(.type == "cgroup") | .options -= ["ro"]) // .)' 355 } 356 357 # Helper function to get all online cpus. 358 function get_all_online_cpus() { 359 cat /sys/devices/system/cpu/online 360 } 361 362 # Helper function to get the first online cpu. 363 function get_first_online_cpu() { 364 [[ $(get_all_online_cpus) =~ [^0-9]*([0-9]+)([-,][0-9]+)? ]] && echo "${BASH_REMATCH[1]}" 365 } 366 367 # Helper function to set all cpus/mems in container cgroup cpuset. 368 function set_cgroup_cpuset_all_cpus() { 369 update_config ".linux.resources.cpu.cpus = \"$(get_all_online_cpus)\"" 370 371 local mems 372 mems="$(cat /sys/devices/system/node/online 2>/dev/null || true)" 373 if [[ -n $mems ]]; then 374 update_config ".linux.resources.cpu.mems = \"$mems\"" 375 fi 376 } 377 378 # Fails the current test, providing the error given. 379 function fail() { 380 echo "$@" >&2 381 exit 1 382 } 383 384 # Check whether rootless runc can use cgroups. 385 function rootless_cgroup() { 386 [[ "$ROOTLESS_FEATURES" == *"cgroup"* || -v RUNC_USE_SYSTEMD ]] 387 } 388 389 # Check if criu is available and working. 390 function have_criu() { 391 command -v criu &>/dev/null || return 1 392 393 # Workaround for https://github.com/opencontainers/runc/issues/3532. 394 local ver 395 ver=$(rpm -q criu 2>/dev/null || true) 396 run ! grep -q '^criu-3\.17-[123]\.el9' <<<"$ver" 397 } 398 399 # Allows a test to specify what things it requires. If the environment can't 400 # support it, the test is skipped with a message. 401 function requires() { 402 for var in "$@"; do 403 local skip_me 404 case $var in 405 criu) 406 if ! have_criu; then 407 skip_me=1 408 fi 409 ;; 410 criu_feature_*) 411 var=${var#criu_feature_} 412 if ! criu check --feature "$var"; then 413 skip "requires CRIU feature ${var}" 414 fi 415 ;; 416 root) 417 if [ $EUID -ne 0 ]; then 418 skip_me=1 419 fi 420 ;; 421 rootless) 422 if [ $EUID -eq 0 ]; then 423 skip_me=1 424 fi 425 ;; 426 rootless_idmap) 427 if [[ "$ROOTLESS_FEATURES" != *"idmap"* ]]; then 428 skip_me=1 429 fi 430 ;; 431 rootless_cgroup) 432 if ! rootless_cgroup; then 433 skip_me=1 434 fi 435 ;; 436 rootless_no_cgroup) 437 if rootless_cgroup; then 438 skip_me=1 439 fi 440 ;; 441 rootless_no_features) 442 if [ -n "$ROOTLESS_FEATURES" ]; then 443 skip_me=1 444 fi 445 ;; 446 cgroups_rt) 447 init_cgroup_paths 448 if [ ! -e "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us" ]; then 449 skip_me=1 450 fi 451 ;; 452 cgroups_swap) 453 init_cgroup_paths 454 if [ -v CGROUP_V1 ] && [ ! -e "${CGROUP_MEMORY_BASE_PATH}/memory.memsw.limit_in_bytes" ]; then 455 skip_me=1 456 fi 457 ;; 458 cgroups_cpu_idle) 459 local p 460 init_cgroup_paths 461 [ -v CGROUP_V1 ] && p="$CGROUP_CPU_BASE_PATH" 462 [ -v CGROUP_V2 ] && p="$CGROUP_BASE_PATH" 463 if [ -z "$(find "$p" -name cpu.idle -print -quit)" ]; then 464 skip_me=1 465 fi 466 ;; 467 cgroups_cpu_burst) 468 local p f 469 init_cgroup_paths 470 if [ -v CGROUP_V1 ]; then 471 p="$CGROUP_CPU_BASE_PATH" 472 f="cpu.cfs_burst_us" 473 elif [ -v CGROUP_V2 ]; then 474 # https://github.com/torvalds/linux/commit/f4183717b370ad28dd0c0d74760142b20e6e7931 475 requires_kernel 5.14 476 p="$CGROUP_BASE_PATH" 477 f="cpu.max.burst" 478 fi 479 if [ -z "$(find "$p" -name "$f" -print -quit)" ]; then 480 skip_me=1 481 fi 482 ;; 483 cgroups_io_weight) 484 local p f1 f2 485 init_cgroup_paths 486 if [ -v CGROUP_V1 ]; then 487 p="$CGROUP_CPU_BASE_PATH" 488 f1="blkio.weight" 489 f2="blkio.bfq.weight" 490 elif [ -v CGROUP_V2 ]; then 491 p="$CGROUP_BASE_PATH" 492 f1="io.weight" 493 f2="io.bfq.weight" 494 fi 495 if [ -z "$(find "$p" -type f \( -name "$f1" -o -name "$f2" \) -print -quit)" ]; then 496 skip_me=1 497 fi 498 ;; 499 cgroupns) 500 if [ ! -e "/proc/self/ns/cgroup" ]; then 501 skip_me=1 502 fi 503 ;; 504 timens) 505 if [ ! -e "/proc/self/ns/time" ]; then 506 skip_me=1 507 fi 508 ;; 509 cgroups_v1) 510 init_cgroup_paths 511 if [ ! -v CGROUP_V1 ]; then 512 skip_me=1 513 fi 514 ;; 515 cgroups_v2) 516 init_cgroup_paths 517 if [ ! -v CGROUP_V2 ]; then 518 skip_me=1 519 fi 520 ;; 521 cgroups_hybrid) 522 init_cgroup_paths 523 if [ ! -v CGROUP_HYBRID ]; then 524 skip_me=1 525 fi 526 ;; 527 cgroups_*) 528 init_cgroup_paths 529 var=${var#cgroups_} 530 if [[ "$CGROUP_SUBSYSTEMS" != *"$var"* ]]; then 531 skip_me=1 532 fi 533 ;; 534 smp) 535 local cpus 536 cpus=$(grep -c '^processor' /proc/cpuinfo) 537 if [ "$cpus" -lt 2 ]; then 538 skip_me=1 539 fi 540 ;; 541 systemd) 542 if [ ! -v RUNC_USE_SYSTEMD ]; then 543 skip_me=1 544 fi 545 ;; 546 systemd_v*) 547 var=${var#systemd_v} 548 if [ "$(systemd_version)" -lt "$var" ]; then 549 skip "requires systemd >= v${var}" 550 fi 551 ;; 552 no_systemd) 553 if [ -v RUNC_USE_SYSTEMD ]; then 554 skip_me=1 555 fi 556 ;; 557 arch_x86_64) 558 if [ "$ARCH" != "x86_64" ]; then 559 skip_me=1 560 fi 561 ;; 562 more_than_8_core) 563 local cpus 564 cpus=$(grep -c '^processor' /proc/cpuinfo) 565 if [ "$cpus" -le 8 ]; then 566 skip_me=1 567 fi 568 ;; 569 psi) 570 # If PSI is not compiled in the kernel, the file will not exist. 571 # If PSI is compiled, but not enabled, read will fail with ENOTSUPP. 572 if ! cat /sys/fs/cgroup/cpu.pressure &>/dev/null; then 573 skip_me=1 574 fi 575 ;; 576 *) 577 fail "BUG: Invalid requires $var." 578 ;; 579 esac 580 if [ -v skip_me ]; then 581 skip "test requires $var" 582 fi 583 done 584 } 585 586 # Allow a test to specify that it will not work properly on a given OS. The 587 # fingerprint for the OS used for this test is $ID-$VERSION_ID, using the 588 # variables in /etc/os-release. The arguments are regular expressions, and any 589 # match will cause the test to be skipped. 590 function exclude_os() { 591 local host 592 host="$(sh -c '. /etc/os-release ; echo "$ID-$VERSION_ID"')" 593 for bad_os in "$@"; do 594 if [[ "$host" =~ ^$bad_os$ ]]; then 595 skip "test doesn't work on $bad_os" 596 fi 597 done 598 } 599 600 # Retry a command $1 times until it succeeds. Wait $2 seconds between retries. 601 function retry() { 602 local attempts=$1 603 shift 604 local delay=$1 605 shift 606 local i 607 608 for ((i = 0; i < attempts; i++)); do 609 run "$@" 610 if [[ "$status" -eq 0 ]]; then 611 return 0 612 fi 613 sleep "$delay" 614 done 615 616 echo "Command \"$*\" failed $attempts times. Output: $output" 617 false 618 } 619 620 # retry until the given container has state 621 function wait_for_container() { 622 if [ $# -eq 3 ]; then 623 retry "$1" "$2" __runc state "$3" 624 elif [ $# -eq 4 ]; then 625 retry "$1" "$2" eval "__runc state $3 | grep -qw $4" 626 else 627 echo "Usage: wait_for_container ATTEMPTS DELAY ID [STATUS]" 1>&2 628 return 1 629 fi 630 } 631 632 function testcontainer() { 633 # test state of container 634 runc state "$1" 635 if [ "$2" = "checkpointed" ]; then 636 [ "$status" -eq 1 ] 637 return 638 fi 639 [ "$status" -eq 0 ] 640 [[ "${output}" == *"$2"* ]] 641 } 642 643 # Check that all the listed processes are gone. Use after kill/stop etc. 644 function wait_pids_gone() { 645 if [ $# -lt 3 ]; then 646 echo "Usage: wait_pids_gone ITERATIONS SLEEP PID [PID ...]" 647 return 1 648 fi 649 local iter=$1 650 shift 651 local sleep=$1 652 shift 653 local pids=("$@") 654 655 while true; do 656 for i in "${!pids[@]}"; do 657 # Check if the pid is there; if not, remove it from the list. 658 kill -0 "${pids[i]}" 2>/dev/null || unset "pids[i]" 659 done 660 [ ${#pids[@]} -eq 0 ] && return 0 661 # Rebuild pids array to avoid sparse array issues. 662 pids=("${pids[@]}") 663 664 ((--iter > 0)) || break 665 666 sleep "$sleep" 667 done 668 669 echo "Expected all PIDs to be gone, but some are still there:" "${pids[@]}" 1>&2 670 return 1 671 } 672 673 function setup_recvtty() { 674 [ ! -v ROOT ] && return 1 # must not be called without ROOT set 675 local dir="$ROOT/tty" 676 677 mkdir "$dir" 678 export CONSOLE_SOCKET="$dir/sock" 679 680 # We need to start recvtty in the background, so we double fork in the shell. 681 ("$RECVTTY" --pid-file "$dir/pid" --mode null "$CONSOLE_SOCKET" &) & 682 } 683 684 function teardown_recvtty() { 685 [ ! -v ROOT ] && return 0 # nothing to teardown 686 local dir="$ROOT/tty" 687 688 # When we kill recvtty, the container will also be killed. 689 if [ -f "$dir/pid" ]; then 690 kill -9 "$(cat "$dir/pid")" 691 fi 692 693 # Clean up the files that might be left over. 694 rm -rf "$dir" 695 } 696 697 function setup_seccompagent() { 698 ("${SECCOMP_AGENT}" -socketfile="$SECCCOMP_AGENT_SOCKET" -pid-file "$BATS_TMPDIR/seccompagent.pid" &) & 699 } 700 701 function teardown_seccompagent() { 702 if [ -f "$BATS_TMPDIR/seccompagent.pid" ]; then 703 kill -9 "$(cat "$BATS_TMPDIR/seccompagent.pid")" 704 fi 705 rm -f "$BATS_TMPDIR/seccompagent.pid" 706 rm -f "$SECCCOMP_AGENT_SOCKET" 707 } 708 709 function setup_bundle() { 710 local image="$1" 711 712 # Root for various container directories (state, tty, bundle). 713 ROOT=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX") 714 mkdir -p "$ROOT/state" "$ROOT/bundle/rootfs" 715 716 # Directories created by mktemp -d have 0700 permission bits. Tests 717 # running inside userns (see userns.bats) need to access the directory 718 # as a different user to mount the rootfs. Since kernel v5.12, parent 719 # directories are also checked. Give a+x for these tests to work. 720 chmod a+x "$ROOT" "$BATS_RUN_TMPDIR" 721 722 setup_recvtty 723 cd "$ROOT/bundle" || return 724 725 tar --exclude './dev/*' -C rootfs -xf "$image" 726 727 runc_spec 728 } 729 730 function setup_busybox() { 731 setup_bundle "$BUSYBOX_IMAGE" 732 } 733 734 function setup_debian() { 735 setup_bundle "$DEBIAN_IMAGE" 736 } 737 738 function teardown_bundle() { 739 [ ! -v ROOT ] && return 0 # nothing to teardown 740 741 cd "$INTEGRATION_ROOT" || return 742 teardown_recvtty 743 local ct 744 for ct in $(__runc list -q); do 745 __runc delete -f "$ct" 746 done 747 rm -rf "$ROOT" 748 remove_parent 749 } 750 751 function remap_rootfs() { 752 [ ! -v ROOT ] && return 0 # nothing to remap 753 754 "$REMAP_ROOTFS" "$ROOT/bundle" 755 } 756 757 function is_kernel_gte() { 758 local major_required minor_required 759 major_required=$(echo "$1" | cut -d. -f1) 760 minor_required=$(echo "$1" | cut -d. -f2) 761 [[ "$KERNEL_MAJOR" -gt $major_required || ("$KERNEL_MAJOR" -eq $major_required && "$KERNEL_MINOR" -ge $minor_required) ]] 762 } 763 764 function requires_kernel() { 765 if ! is_kernel_gte "$@"; then 766 skip "requires kernel >= $1" 767 fi 768 } 769 770 function requires_idmap_fs() { 771 local fs 772 fs=$1 773 774 # We need to "|| true" it to avoid CI failure as this binary may return with 775 # something different than 0. 776 stderr=$($FS_IDMAP "$fs" 2>&1 >/dev/null || true) 777 778 case $stderr in 779 *invalid\ argument) 780 skip "$fs underlying file system does not support ID map mounts" 781 ;; 782 *operation\ not\ permitted) 783 if uname -r | grep -q el9; then 784 # centos kernel 5.14.0-200 does not permit using ID map mounts due to a 785 # specific patch added to their sources: 786 # https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/131 787 # 788 # There doesn't seem to be any technical reason behind 789 # it, none was provided in numerous examples, like: 790 # https://lore.kernel.org/lkml/20210213130042.828076-1-christian.brauner@ubuntu.com/T/#m3a9df31aa183e8797c70bc193040adfd601399ad 791 # https://lore.kernel.org/lkml/20210213130042.828076-1-christian.brauner@ubuntu.com/T/#m59cdad9630d5a279aeecd0c1f117115144bc15eb 792 # https://lore.kernel.org/lkml/m1r1ifzf8x.fsf@fess.ebiederm.org 793 # https://lore.kernel.org/lkml/20210510125147.tkgeurcindldiwxg@wittgenstein 794 # 795 # So, sadly we just need to skip this on centos. 796 # 797 # TODO Nonetheless, there are ongoing works to revert the patch 798 # deactivating ID map mounts: 799 # https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/2179/diffs?commit_id=06f4fe946394cb94d2cf274aa7f3091d8f8469dc 800 # Once this patch is merge, we should be able to remove the below skip 801 # if the revert is backported or if CI centos kernel is upgraded. 802 skip "sadly, centos kernel 5.14 does not permit using ID map mounts" 803 fi 804 ;; 805 esac 806 # If we have another error, the integration test will fail and report it. 807 } 808 809 # setup_pidfd_kill runs pidfd-kill process in background and receives the 810 # SIGTERM as signal to send the given signal to init process. 811 function setup_pidfd_kill() { 812 local signal=$1 813 814 [ ! -v ROOT ] && return 1 815 local dir="${ROOT}/pidfd" 816 817 mkdir "${dir}" 818 export PIDFD_SOCKET="${dir}/sock" 819 820 ("${PIDFD_KILL}" --pid-file "${dir}/pid" --signal "${signal}" "${PIDFD_SOCKET}" &) & 821 822 # ensure socket is ready 823 retry 10 1 stat "${PIDFD_SOCKET}" 824 } 825 826 # teardown_pidfd_kill cleanups all the resources related to pidfd-kill. 827 function teardown_pidfd_kill() { 828 [ ! -v ROOT ] && return 0 829 830 local dir="${ROOT}/pidfd" 831 832 if [ -f "${dir}/pid" ]; then 833 kill -9 "$(cat "${dir}/pid")" 834 fi 835 836 rm -rf "${dir}" 837 } 838 839 # pidfd_kill sends the signal to init process. 840 function pidfd_kill() { 841 [ ! -v ROOT ] && return 0 842 843 local dir="${ROOT}/pidfd" 844 845 if [ -f "${dir}/pid" ]; then 846 kill "$(cat "${dir}/pid")" 847 fi 848 }