github.com/AbhinandanKurakure/podman/v3@v3.4.10/test/system/helpers.bash (about) 1 # -*- bash -*- 2 3 # Podman command to run; may be podman-remote 4 PODMAN=${PODMAN:-podman} 5 6 # Standard image to use for most tests 7 PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} 8 PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} 9 PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"testimage"} 10 PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20210610"} 11 PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" 12 PODMAN_TEST_IMAGE_ID= 13 14 # Remote image that we *DO NOT* fetch or keep by default; used for testing pull 15 # This has changed in 2021, from 0 through 3, various iterations of getting 16 # multiarch to work. It should change only very rarely. 17 PODMAN_NONLOCAL_IMAGE_TAG=${PODMAN_NONLOCAL_IMAGE_TAG:-"00000003"} 18 PODMAN_NONLOCAL_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_NONLOCAL_IMAGE_TAG" 19 20 # Because who wants to spell that out each time? 21 IMAGE=$PODMAN_TEST_IMAGE_FQN 22 23 # Default timeout for a podman command. 24 PODMAN_TIMEOUT=${PODMAN_TIMEOUT:-120} 25 26 # Prompt to display when logging podman commands; distinguish root/rootless 27 _LOG_PROMPT='$' 28 if [ $(id -u) -eq 0 ]; then 29 _LOG_PROMPT='#' 30 fi 31 32 ############################################################################### 33 # BEGIN setup/teardown tools 34 35 # Provide common setup and teardown functions, but do not name them such! 36 # That way individual tests can override with their own setup/teardown, 37 # while retaining the ability to include these if they so desire. 38 39 # Some CI systems set this to runc, overriding the default crun. 40 # Although it would be more elegant to override options in run_podman(), 41 # we instead override $PODMAN itself because some tests (170-run-userns) 42 # have to invoke $PODMAN directly. 43 if [[ -n $OCI_RUNTIME ]]; then 44 if [[ -z $CONTAINERS_CONF ]]; then 45 # FIXME: BATS provides no mechanism for end-of-run cleanup[1]; how 46 # can we avoid leaving this file behind when we finish? 47 # [1] https://github.com/bats-core/bats-core/issues/39 48 export CONTAINERS_CONF=$(mktemp --tmpdir=${BATS_TMPDIR:-/tmp} podman-bats-XXXXXXX.containers.conf) 49 cat >$CONTAINERS_CONF <<EOF 50 [engine] 51 runtime="$OCI_RUNTIME" 52 EOF 53 fi 54 fi 55 56 # Setup helper: establish a test environment with exactly the images needed 57 function basic_setup() { 58 # Clean up all containers 59 run_podman rm --all --force 60 61 # ...including external (buildah) ones 62 run_podman ps --all --external --format '{{.ID}} {{.Names}}' 63 for line in "${lines[@]}"; do 64 set $line 65 echo "# setup(): removing stray external container $1 ($2)" >&3 66 run_podman rm $1 67 done 68 69 # Clean up all images except those desired 70 found_needed_image= 71 run_podman images --all --format '{{.Repository}}:{{.Tag}} {{.ID}}' 72 for line in "${lines[@]}"; do 73 set $line 74 if [ "$1" == "$PODMAN_TEST_IMAGE_FQN" ]; then 75 if [[ -z "$PODMAN_TEST_IMAGE_ID" ]]; then 76 # This will probably only trigger the 2nd time through setup 77 PODMAN_TEST_IMAGE_ID=$2 78 fi 79 found_needed_image=1 80 else 81 # Always remove image that doesn't match by name 82 echo "# setup(): removing stray image $1" >&3 83 run_podman rmi --force "$1" >/dev/null 2>&1 || true 84 85 # Tagged image will have same IID as our test image; don't rmi it. 86 if [[ $2 != "$PODMAN_TEST_IMAGE_ID" ]]; then 87 echo "# setup(): removing stray image $2" >&3 88 run_podman rmi --force "$2" >/dev/null 2>&1 || true 89 fi 90 fi 91 done 92 93 # Make sure desired images are present 94 if [ -z "$found_needed_image" ]; then 95 run_podman pull "$PODMAN_TEST_IMAGE_FQN" 96 fi 97 98 # Argh. Although BATS provides $BATS_TMPDIR, it's just /tmp! 99 # That's bloody worthless. Let's make our own, in which subtests 100 # can write whatever they like and trust that it'll be deleted 101 # on cleanup. 102 # TODO: do this outside of setup, so it carries across tests? 103 PODMAN_TMPDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} podman_bats.XXXXXX) 104 105 # In the unlikely event that a test runs is() before a run_podman() 106 MOST_RECENT_PODMAN_COMMAND= 107 } 108 109 # Basic teardown: remove all pods and containers 110 function basic_teardown() { 111 echo "# [teardown]" >&2 112 run_podman '?' pod rm --all --force 113 run_podman '?' rm --all --force 114 115 command rm -rf $PODMAN_TMPDIR 116 } 117 118 119 # Provide the above as default methods. 120 function setup() { 121 basic_setup 122 } 123 124 function teardown() { 125 basic_teardown 126 } 127 128 129 # Helpers useful for tests running rmi 130 function archive_image() { 131 local image=$1 132 133 # FIXME: refactor? 134 archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) 135 archive=$BATS_TMPDIR/$archive_basename.tar 136 137 run_podman save -o $archive $image 138 } 139 140 function restore_image() { 141 local image=$1 142 143 archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) 144 archive=$BATS_TMPDIR/$archive_basename.tar 145 146 run_podman restore $archive 147 } 148 149 # END setup/teardown tools 150 ############################################################################### 151 # BEGIN podman helpers 152 153 ################ 154 # run_podman # Invoke $PODMAN, with timeout, using BATS 'run' 155 ################ 156 # 157 # This is the preferred mechanism for invoking podman: first, it 158 # invokes $PODMAN, which may be 'podman-remote' or '/some/path/podman'. 159 # 160 # Second, we use 'timeout' to abort (with a diagnostic) if something 161 # takes too long; this is preferable to a CI hang. 162 # 163 # Third, we log the command run and its output. This doesn't normally 164 # appear in BATS output, but it will if there's an error. 165 # 166 # Next, we check exit status. Since the normal desired code is 0, 167 # that's the default; but the first argument can override: 168 # 169 # run_podman 125 nonexistent-subcommand 170 # run_podman '?' some-other-command # let our caller check status 171 # 172 # Since we use the BATS 'run' mechanism, $output and $status will be 173 # defined for our caller. 174 # 175 function run_podman() { 176 # Number as first argument = expected exit code; default 0 177 expected_rc=0 178 case "$1" in 179 [0-9]) expected_rc=$1; shift;; 180 [1-9][0-9]) expected_rc=$1; shift;; 181 [12][0-9][0-9]) expected_rc=$1; shift;; 182 '?') expected_rc= ; shift;; # ignore exit code 183 esac 184 185 # Remember command args, for possible use in later diagnostic messages 186 MOST_RECENT_PODMAN_COMMAND="podman $*" 187 188 # stdout is only emitted upon error; this echo is to help a debugger 189 echo "$_LOG_PROMPT $PODMAN $*" 190 # BATS hangs if a subprocess remains and keeps FD 3 open; this happens 191 # if podman crashes unexpectedly without cleaning up subprocesses. 192 run timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN $_PODMAN_TEST_OPTS "$@" 3>/dev/null 193 # without "quotes", multiple lines are glommed together into one 194 if [ -n "$output" ]; then 195 echo "$output" 196 fi 197 if [ "$status" -ne 0 ]; then 198 echo -n "[ rc=$status "; 199 if [ -n "$expected_rc" ]; then 200 if [ "$status" -eq "$expected_rc" ]; then 201 echo -n "(expected) "; 202 else 203 echo -n "(** EXPECTED $expected_rc **) "; 204 fi 205 fi 206 echo "]" 207 fi 208 209 if [ "$status" -eq 124 ]; then 210 if expr "$output" : ".*timeout: sending" >/dev/null; then 211 # It's possible for a subtest to _want_ a timeout 212 if [[ "$expected_rc" != "124" ]]; then 213 echo "*** TIMED OUT ***" 214 false 215 fi 216 fi 217 fi 218 219 if [ -n "$expected_rc" ]; then 220 if [ "$status" -ne "$expected_rc" ]; then 221 die "exit code is $status; expected $expected_rc" 222 fi 223 fi 224 } 225 226 227 # Wait for certain output from a container, indicating that it's ready. 228 function wait_for_output { 229 local sleep_delay=5 230 local how_long=$PODMAN_TIMEOUT 231 local expect= 232 local cid= 233 234 # Arg processing. A single-digit number is how long to sleep between 235 # iterations; a 2- or 3-digit number is the total time to wait; all 236 # else are, in order, the string to expect and the container name/ID. 237 local i 238 for i in "$@"; do 239 if expr "$i" : '[0-9]\+$' >/dev/null; then 240 if [ $i -le 9 ]; then 241 sleep_delay=$i 242 else 243 how_long=$i 244 fi 245 elif [ -z "$expect" ]; then 246 expect=$i 247 else 248 cid=$i 249 fi 250 done 251 252 [ -n "$cid" ] || die "FATAL: wait_for_output: no container name/ID in '$*'" 253 254 t1=$(expr $SECONDS + $how_long) 255 while [ $SECONDS -lt $t1 ]; do 256 run_podman logs $cid 257 logs=$output 258 if expr "$logs" : ".*$expect" >/dev/null; then 259 return 260 fi 261 262 # Barf if container is not running 263 run_podman inspect --format '{{.State.Running}}' $cid 264 if [ $output != "true" ]; then 265 run_podman inspect --format '{{.State.ExitCode}}' $cid 266 exitcode=$output 267 die "Container exited (status: $exitcode) before we saw '$expect': $logs" 268 fi 269 270 sleep $sleep_delay 271 done 272 273 die "timed out waiting for '$expect' from $cid" 274 } 275 276 # Shortcut for the lazy 277 function wait_for_ready { 278 wait_for_output 'READY' "$@" 279 } 280 281 ###################### 282 # random_free_port # Pick an available port within a specified range 283 ###################### 284 function random_free_port() { 285 local range=${1:-5000-5999} 286 287 local port 288 for port in $(shuf -i ${range}); do 289 if ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then 290 echo $port 291 return 292 fi 293 done 294 295 die "Could not find open port in range $range" 296 } 297 298 ################### 299 # wait_for_port # Returns once port is available on host 300 ################### 301 function wait_for_port() { 302 local host=$1 # Probably "localhost" 303 local port=$2 # Numeric port 304 local _timeout=${3:-5} # Optional; default to 5 seconds 305 306 # Wait 307 while [ $_timeout -gt 0 ]; do 308 { exec {unused_fd}<> /dev/tcp/$host/$port; } &>/dev/null && return 309 sleep 1 310 _timeout=$(( $_timeout - 1 )) 311 done 312 313 die "Timed out waiting for $host:$port" 314 } 315 316 # END podman helpers 317 ############################################################################### 318 # BEGIN miscellaneous tools 319 320 # Shortcuts for common needs: 321 function is_rootless() { 322 [ "$(id -u)" -ne 0 ] 323 } 324 325 function is_remote() { 326 [[ "$PODMAN" =~ -remote ]] 327 } 328 329 function is_cgroupsv1() { 330 # WARNING: This will break if there's ever a cgroups v3 331 ! is_cgroupsv2 332 } 333 334 # True if cgroups v2 are enabled 335 function is_cgroupsv2() { 336 cgroup_type=$(stat -f -c %T /sys/fs/cgroup) 337 test "$cgroup_type" = "cgroup2fs" 338 } 339 340 # Returns the OCI runtime *basename* (typically crun or runc). Much as we'd 341 # love to cache this result, we probably shouldn't. 342 function podman_runtime() { 343 # This function is intended to be used as '$(podman_runtime)', i.e. 344 # our caller wants our output. run_podman() messes with output because 345 # it emits the command invocation to stdout, hence the redirection. 346 run_podman info --format '{{ .Host.OCIRuntime.Name }}' >/dev/null 347 basename "${output:-[null]}" 348 } 349 350 # rhbz#1895105: rootless journald is unavailable except to users in 351 # certain magic groups; which our testuser account does not belong to 352 # (intentional: that is the RHEL default, so that's the setup we test). 353 function journald_unavailable() { 354 if ! is_rootless; then 355 # root must always have access to journal 356 return 1 357 fi 358 359 run journalctl -n 1 360 if [[ $status -eq 0 ]]; then 361 return 1 362 fi 363 364 if [[ $output =~ permission ]]; then 365 return 0 366 fi 367 368 # This should never happen; if it does, it's likely that a subsequent 369 # test will fail. This output may help track that down. 370 echo "WEIRD: 'journalctl -n 1' failed with a non-permission error:" 371 echo "$output" 372 return 1 373 } 374 375 ########################### 376 # _add_label_if_missing # make sure skip messages include rootless/remote 377 ########################### 378 function _add_label_if_missing() { 379 local msg="$1" 380 local want="$2" 381 382 if [ -z "$msg" ]; then 383 echo 384 elif expr "$msg" : ".*$want" &>/dev/null; then 385 echo "$msg" 386 else 387 echo "[$want] $msg" 388 fi 389 } 390 391 ###################### 392 # skip_if_rootless # ...with an optional message 393 ###################### 394 function skip_if_rootless() { 395 if is_rootless; then 396 local msg=$(_add_label_if_missing "$1" "rootless") 397 skip "${msg:-not applicable under rootless podman}" 398 fi 399 } 400 401 #################### 402 # skip_if_remote # ...with an optional message 403 #################### 404 function skip_if_remote() { 405 if is_remote; then 406 local msg=$(_add_label_if_missing "$1" "remote") 407 skip "${msg:-test does not work with podman-remote}" 408 fi 409 } 410 411 ######################## 412 # skip_if_no_selinux # 413 ######################## 414 function skip_if_no_selinux() { 415 if [ ! -e /usr/sbin/selinuxenabled ]; then 416 skip "selinux not available" 417 elif ! /usr/sbin/selinuxenabled; then 418 skip "selinux disabled" 419 fi 420 } 421 422 ####################### 423 # skip_if_cgroupsv1 # ...with an optional message 424 ####################### 425 function skip_if_cgroupsv1() { 426 if ! is_cgroupsv2; then 427 skip "${1:-test requires cgroupsv2}" 428 fi 429 } 430 431 ################################## 432 # skip_if_journald_unavailable # rhbz#1895105: rootless journald permissions 433 ################################## 434 function skip_if_journald_unavailable { 435 if journald_unavailable; then 436 skip "Cannot use rootless journald on this system" 437 fi 438 } 439 440 ######### 441 # die # Abort with helpful message 442 ######### 443 function die() { 444 # FIXME: handle multi-line output 445 echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 446 echo "#| FAIL: $*" >&2 447 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 448 false 449 } 450 451 452 ######## 453 # is # Compare actual vs expected string; fail w/diagnostic if mismatch 454 ######## 455 # 456 # Compares given string against expectations, using 'expr' to allow patterns. 457 # 458 # Examples: 459 # 460 # is "$actual" "$expected" "descriptive test name" 461 # is "apple" "orange" "name of a test that will fail in most universes" 462 # is "apple" "[a-z]\+" "this time it should pass" 463 # 464 function is() { 465 local actual="$1" 466 local expect="$2" 467 local testname="${3:-${MOST_RECENT_PODMAN_COMMAND:-[no test name given]}}" 468 469 if [ -z "$expect" ]; then 470 if [ -z "$actual" ]; then 471 return 472 fi 473 expect='[no output]' 474 elif expr "$actual" : "$expect" >/dev/null; then 475 return 476 fi 477 478 # This is a multi-line message, which may in turn contain multi-line 479 # output, so let's format it ourself, readably 480 local -a actual_split 481 readarray -t actual_split <<<"$actual" 482 printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 483 printf "#| FAIL: $testname\n" >&2 484 printf "#| expected: '%s'\n" "$expect" >&2 485 printf "#| actual: '%s'\n" "${actual_split[0]}" >&2 486 local line 487 for line in "${actual_split[@]:1}"; do 488 printf "#| > '%s'\n" "$line" >&2 489 done 490 printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 491 false 492 } 493 494 495 ############ 496 # dprint # conditional debug message 497 ############ 498 # 499 # Set PODMAN_TEST_DEBUG to the name of one or more functions you want to debug 500 # 501 # Examples: 502 # 503 # $ PODMAN_TEST_DEBUG=parse_table bats . 504 # $ PODMAN_TEST_DEBUG="test_podman_images test_podman_run" bats . 505 # 506 function dprint() { 507 test -z "$PODMAN_TEST_DEBUG" && return 508 509 caller="${FUNCNAME[1]}" 510 511 # PODMAN_TEST_DEBUG is a space-separated list of desired functions 512 # e.g. "parse_table test_podman_images" (or even just "table") 513 for want in $PODMAN_TEST_DEBUG; do 514 # Check if our calling function matches any of the desired strings 515 if expr "$caller" : ".*$want" >/dev/null; then 516 echo "# ${FUNCNAME[1]}() : $*" >&3 517 return 518 fi 519 done 520 } 521 522 523 ################# 524 # parse_table # Split a table on '|' delimiters; return space-separated 525 ################# 526 # 527 # See sample .bats scripts for examples. The idea is to list a set of 528 # tests in a table, then use simple logic to iterate over each test. 529 # Columns are separated using '|' (pipe character) because sometimes 530 # we need spaces in our fields. 531 # 532 function parse_table() { 533 while read line; do 534 test -z "$line" && continue 535 536 declare -a row=() 537 while read col; do 538 dprint "col=<<$col>>" 539 row+=("$col") 540 done < <(echo "$line" | sed -E -e 's/(^|\s)\|(\s|$)/\n /g' | sed -e 's/^ *//' -e 's/\\/\\\\/g') 541 # the above seds: 542 # 1) Convert '|' to newline, but only if bracketed by spaces or 543 # at beginning/end of line (this allows 'foo|bar' in tests); 544 # 2) then remove leading whitespace; 545 # 3) then double-escape all backslashes 546 547 printf "%q " "${row[@]}" 548 printf "\n" 549 done <<<"$1" 550 } 551 552 553 ################### 554 # random_string # Returns a pseudorandom human-readable string 555 ################### 556 # 557 # Numeric argument, if present, is desired length of string 558 # 559 function random_string() { 560 local length=${1:-10} 561 562 head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length 563 } 564 565 566 ########################### 567 # random_rfc1918_subnet # 568 ########################### 569 # 570 # Use the class B set, because much of our CI environment (Google, RH) 571 # already uses up much of the class A, and it's really hard to test 572 # if a block is in use. 573 # 574 # This returns THREE OCTETS! It is up to our caller to append .0/24, .255, &c. 575 # 576 function random_rfc1918_subnet() { 577 local retries=1024 578 579 while [ "$retries" -gt 0 ];do 580 local cidr=172.$(( 16 + $RANDOM % 16 )).$(( $RANDOM & 255 )) 581 582 in_use=$(ip route list | fgrep $cidr) 583 if [ -z "$in_use" ]; then 584 echo "$cidr" 585 return 586 fi 587 588 retries=$(( retries - 1 )) 589 done 590 591 die "Could not find a random not-in-use rfc1918 subnet" 592 } 593 594 595 ######################### 596 # find_exec_pid_files # Returns nothing or exec_pid hash files 597 ######################### 598 # 599 # Return exec_pid hash files if exists, otherwise, return nothing 600 # 601 function find_exec_pid_files() { 602 run_podman info --format '{{.Store.RunRoot}}' 603 local storage_path="$output" 604 if [ -d $storage_path ]; then 605 find $storage_path -type f -iname 'exec_pid_*' 606 fi 607 } 608 609 610 ############################# 611 # remove_same_dev_warning # Filter out useless warning from output 612 ############################# 613 # 614 # On some CI systems, 'podman run --privileged' emits a useless warning: 615 # 616 # WARNING: The same type, major and minor should not be used for multiple devices. 617 # 618 # This obviously screws us up when we look at output results. 619 # 620 # This function removes the warning from $output and $lines. We don't 621 # do a full string match because there's another variant of that message: 622 # 623 # WARNING: Creating device "/dev/null" with same type, major and minor as existing "/dev/foodevdir/null". 624 # 625 # (We should never again see that precise error ever again, but we could 626 # see variants of it). 627 # 628 function remove_same_dev_warning() { 629 # No input arguments. We operate in-place on $output and $lines 630 631 local i=0 632 local -a new_lines=() 633 while [[ $i -lt ${#lines[@]} ]]; do 634 if expr "${lines[$i]}" : 'WARNING: .* same type, major' >/dev/null; then 635 : 636 else 637 new_lines+=("${lines[$i]}") 638 fi 639 i=$(( i + 1 )) 640 done 641 642 lines=("${new_lines[@]}") 643 output=$(printf '%s\n' "${lines[@]}") 644 } 645 646 # run 'podman help', parse the output looking for 'Available Commands'; 647 # return that list. 648 function _podman_commands() { 649 dprint "$@" 650 run_podman help "$@" | 651 awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' | 652 grep . 653 "$output" 654 } 655 656 # END miscellaneous tools 657 ###############################################################################