github.com/AbhinandanKurakure/podman/v3@v3.4.10/test/apiv2/test-apiv2 (about) 1 #!/usr/bin/env bash 2 # 3 # Usage: test-apiv2 [PORT] 4 # 5 # DEVELOPER NOTE: you almost certainly don't need to play in here. See README. 6 # 7 ME=$(basename $0) 8 9 ############################################################################### 10 # BEGIN stuff you can but probably shouldn't customize 11 12 PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} 13 PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} 14 PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"alpine_labels"} 15 PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"latest"} 16 PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" 17 18 IMAGE=$PODMAN_TEST_IMAGE_FQN 19 20 REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry:2.7" 21 22 # END stuff you can but probably shouldn't customize 23 ############################################################################### 24 # BEGIN setup 25 26 TMPDIR=${TMPDIR:-/tmp} 27 WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) 28 29 # Log of all HTTP requests and responses; always make '.log' point to latest 30 LOGBASE=${TMPDIR}/$ME.log 31 LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S') 32 ln -sf $LOG $LOGBASE 33 34 HOST=localhost 35 PORT=${PODMAN_SERVICE_PORT:-8081} 36 37 # Keep track of test count and failures in files, not variables, because 38 # variables don't carry back up from subshells. 39 testcounter_file=$WORKDIR/.testcounter 40 failures_file=$WORKDIR/.failures 41 42 echo 0 >$testcounter_file 43 echo 0 >$failures_file 44 45 # Where the tests live 46 TESTS_DIR=$(realpath $(dirname $0)) 47 48 # Path to podman binary 49 PODMAN_BIN=${PODMAN:-${TESTS_DIR}/../../bin/podman} 50 51 # END setup 52 ############################################################################### 53 # BEGIN infrastructure code - the helper functions used in tests themselves 54 55 ######### 56 # die # Exit error with a message to stderr 57 ######### 58 function die() { 59 echo "$ME: $*" >&2 60 exit 1 61 } 62 63 ######## 64 # is # Simple comparison 65 ######## 66 function is() { 67 local actual=$1 68 local expect=$2 69 local testname=$3 70 71 if [ "$actual" = "$expect" ]; then 72 # On success, include expected value; this helps readers understand 73 _show_ok 1 "$testname=$expect" 74 return 75 fi 76 _show_ok 0 "$testname" "$expect" "$actual" 77 } 78 79 ########## 80 # like # Compare, but allowing patterns 81 ########## 82 function like() { 83 local actual=$1 84 local expect=$2 85 local testname=$3 86 87 if expr "$actual" : "$expect" &>/dev/null; then 88 # On success, include expected value; this helps readers understand 89 # (but don't show enormous multi-line output like 'generate kube') 90 blurb=$(head -n1 <<<"$actual") 91 _show_ok 1 "$testname ('$blurb') ~ $expect" 92 return 93 fi 94 _show_ok 0 "$testname" "~ $expect" "$actual" 95 } 96 97 ############## 98 # _show_ok # Helper for is() and like(): displays 'ok' or 'not ok' 99 ############## 100 function _show_ok() { 101 local ok=$1 102 local testname=$2 103 104 # If output is a tty, colorize pass/fail 105 local red= 106 local green= 107 local reset= 108 local bold= 109 if [ -t 1 ]; then 110 red='\e[31m' 111 green='\e[32m' 112 reset='\e[0m' 113 bold='\e[1m' 114 fi 115 116 _bump $testcounter_file 117 count=$(<$testcounter_file) 118 119 # "skip" is a special case of "ok". Assume that our caller has included 120 # the magical '# skip - reason" comment string. 121 if [[ $ok == "skip" ]]; then 122 # colon-plus: replace green with yellow, but only if green is non-null 123 green="${green:+\e[33m}" 124 ok=1 125 fi 126 if [ $ok -eq 1 ]; then 127 echo -e "${green}ok $count ${TEST_CONTEXT} $testname${reset}" 128 echo "ok $count ${TEST_CONTEXT} $testname" >>$LOG 129 return 130 fi 131 132 # Failed 133 local expect=$3 134 local actual=$4 135 echo -e "${red}not ok $count ${TEST_CONTEXT} $testname${reset}" 136 echo -e "${red}# expected: $expect${reset}" 137 echo -e "${red}# actual: ${bold}$actual${reset}" 138 139 echo "not ok $count ${TEST_CONTEXT} $testname" >>$LOG 140 echo " expected: $expect" >>$LOG 141 142 _bump $failures_file 143 } 144 145 ########### 146 # _bump # Increment a counter in a file 147 ########### 148 function _bump() { 149 local file=$1 150 151 count=$(<$file) 152 echo $(( $count + 1 )) >| $file 153 } 154 155 ############# 156 # jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"} 157 ############# 158 function jsonify() { 159 # convert each to double-quoted form 160 local -a settings_out 161 for i in "$@"; do 162 # Each argument is of the form foo=bar. Separate into left and right. 163 local lhs 164 local rhs 165 IFS='=' read lhs rhs <<<"$i" 166 167 # If right-hand side already includes double quotes, do nothing 168 if [[ ! $rhs =~ \" ]]; then 169 rhs="\"${rhs}\"" 170 fi 171 settings_out+=("\"${lhs}\":${rhs}") 172 done 173 174 # ...and wrap inside braces, with comma separator if multiple fields 175 (IFS=','; echo "{${settings_out[*]}}") 176 } 177 178 ####### 179 # t # Main test helper 180 ####### 181 function t() { 182 local method=$1; shift 183 local path=$1; shift 184 local curl_args 185 186 local testname="$method $path" 187 # POST requests may be followed by one or more key=value pairs. 188 # Slurp the command line until we see a 3-digit status code. 189 if [[ $method = "POST" ]]; then 190 local -a post_args 191 for arg; do 192 case "$arg" in 193 *=*) post_args+=("$arg"); shift ;; 194 [1-9][0-9][0-9]) break;; 195 *) die "Internal error: invalid POST arg '$arg'" ;; 196 esac 197 done 198 curl_args="-d $(jsonify ${post_args[@]})" 199 testname="$testname [$curl_args]" 200 fi 201 202 # entrypoint path can include a descriptive comment; strip it off 203 path=${path%% *} 204 205 # path may include JSONish params that curl will barf on; url-encode them 206 path="${path//'['/%5B}" 207 path="${path//']'/%5D}" 208 path="${path//'{'/%7B}" 209 path="${path//'}'/%7D}" 210 path="${path//':'/%3A}" 211 212 # curl -X HEAD but without --head seems to wait for output anyway 213 if [[ $method == "HEAD" ]]; then 214 curl_args="--head" 215 fi 216 local expected_code=$1; shift 217 218 # If given path begins with /, use it as-is; otherwise prepend /version/ 219 local url=http://$HOST:$PORT 220 if expr "$path" : "/" >/dev/null; then 221 url="$url$path" 222 else 223 url="$url/v1.40/$path" 224 fi 225 226 # Log every action we do 227 echo "-------------------------------------------------------------" >>$LOG 228 echo "\$ $testname" >>$LOG 229 rm -f $WORKDIR/curl.* 230 # -s = silent, but --write-out 'format' gives us important response data 231 response=$(curl -s -X $method ${curl_args} \ 232 -H 'Content-type: application/json' \ 233 --dump-header $WORKDIR/curl.headers.out \ 234 --write-out '%{http_code}^%{content_type}^%{time_total}' \ 235 -o $WORKDIR/curl.result.out "$url") 236 237 # Any error from curl is instant bad news, from which we can't recover 238 rc=$? 239 if [[ $rc -ne 0 ]]; then 240 echo "FATAL: curl failure ($rc) on $url - cannot continue" >&2 241 exit 1 242 fi 243 244 # Show returned headers (without trailing ^M or empty lines) in log file. 245 # Sometimes -- I can't remember why! -- we don't get headers. 246 if [[ -e $WORKDIR/curl.headers.out ]]; then 247 tr -d '\015' < $WORKDIR/curl.headers.out | egrep '.' >>$LOG 248 fi 249 250 IFS='^' read actual_code content_type time_total <<<"$response" 251 printf "X-Response-Time: ${time_total}s\n\n" >>$LOG 252 253 # Log results, if text. If JSON, filter through jq for readability. 254 if [[ $content_type =~ /octet ]]; then 255 output="[$(file --brief $WORKDIR/curl.result.out)]" 256 echo "$output" >>$LOG 257 elif [[ -e $WORKDIR/curl.result.out ]]; then 258 output=$(< $WORKDIR/curl.result.out) 259 260 if [[ $content_type =~ application/json ]] && [[ $method != "HEAD" ]]; then 261 jq . <<<"$output" >>$LOG 262 else 263 echo "$output" >>$LOG 264 fi 265 else 266 output= 267 echo "[no output]" >>$LOG 268 fi 269 270 # Test return code 271 is "$actual_code" "$expected_code" "$testname : status" 272 273 # Special case: 204/304, by definition, MUST NOT return content (rfc2616) 274 if [[ $expected_code = 204 || $expected_code = 304 ]]; then 275 if [ -n "$*" ]; then 276 die "Internal error: ${expected_code} status returns no output; fix your test." 277 fi 278 if [ -n "$output" ]; then 279 _show_ok 0 "$testname: ${expected_code} status returns no output" "''" "$output" 280 fi 281 return 282 fi 283 284 local i 285 286 # Special case: if response code does not match, dump the response body 287 # and skip all further subtests. 288 if [[ $actual_code != $expected_code ]]; then 289 echo -e "# response: $output" 290 for i; do 291 _show_ok skip "$testname: $i # skip - wrong return code" 292 done 293 return 294 fi 295 296 for i; do 297 if expr "$i" : "[^=~]\+=.*" >/dev/null; then 298 # Exact match on json field 299 json_field=$(expr "$i" : "\([^=]*\)=") 300 expect=$(expr "$i" : '[^=]*=\(.*\)') 301 actual=$(jq -r "$json_field" <<<"$output") 302 is "$actual" "$expect" "$testname : $json_field" 303 elif expr "$i" : "[^=~]\+~.*" >/dev/null; then 304 # regex match on json field 305 json_field=$(expr "$i" : "\([^~]*\)~") 306 expect=$(expr "$i" : '[^~]*~\(.*\)') 307 actual=$(jq -r "$json_field" <<<"$output") 308 like "$actual" "$expect" "$testname : $json_field" 309 else 310 # Direct string comparison 311 is "$output" "$i" "$testname : output" 312 fi 313 done 314 } 315 316 ################### 317 # start_service # Run the socket listener 318 ################### 319 service_pid= 320 function start_service() { 321 # If there's a listener on the port, nothing for us to do 322 { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null && return 323 324 test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" 325 326 if [ "$HOST" != "localhost" ]; then 327 die "Cannot start service on non-localhost ($HOST)" 328 fi 329 330 echo $WORKDIR 331 $PODMAN_BIN --root $WORKDIR/server_root --syslog=true \ 332 system service \ 333 --time 15 \ 334 tcp:127.0.0.1:$PORT \ 335 &> $WORKDIR/server.log & 336 service_pid=$! 337 338 wait_for_port $HOST $PORT 339 } 340 341 function stop_service() { 342 # Stop the server 343 if [[ -n $service_pid ]]; then 344 kill $service_pid 345 wait $service_pid 346 fi 347 } 348 349 #################### 350 # start_registry # Run a local registry 351 #################### 352 REGISTRY_PORT= 353 REGISTRY_USERNAME= 354 REGISTRY_PASSWORD= 355 function start_registry() { 356 # We can be invoked multiple times, e.g. from different subtests, but 357 # let's assume that once started we only kill it at the end of tests. 358 if [[ -n "$REGISTRY_PORT" ]]; then 359 return 360 fi 361 362 REGISTRY_PORT=$(random_port) 363 REGISTRY_USERNAME=u$(random_string 7) 364 REGISTRY_PASSWORD=p$(random_string 7) 365 366 local REGDIR=$WORKDIR/registry 367 local AUTHDIR=$REGDIR/auth 368 mkdir -p $AUTHDIR 369 370 mkdir -p ${REGDIR}/{root,runroot} 371 local PODMAN_REGISTRY_ARGS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot" 372 373 # Give it three tries, to compensate for network flakes 374 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE || 375 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE || 376 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE 377 378 # Create a local cert and credentials 379 # FIXME: is there a hidden "--quiet" flag? This is too noisy. 380 openssl req -newkey rsa:4096 -nodes -sha256 \ 381 -keyout $AUTHDIR/domain.key -x509 -days 2 \ 382 -out $AUTHDIR/domain.crt \ 383 -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \ 384 -addext subjectAltName=DNS:localhost 385 htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \ 386 > $AUTHDIR/htpasswd 387 388 # Run the registry, and wait for it to come up 389 podman ${PODMAN_REGISTRY_ARGS} run -d \ 390 -p ${REGISTRY_PORT}:5000 \ 391 --name registry \ 392 -v $AUTHDIR:/auth:Z \ 393 -e "REGISTRY_AUTH=htpasswd" \ 394 -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ 395 -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ 396 -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \ 397 -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \ 398 ${REGISTRY_IMAGE} 399 400 wait_for_port localhost $REGISTRY_PORT 401 } 402 403 function stop_registry() { 404 local REGDIR=${WORKDIR}/registry 405 if [[ -d $REGDIR ]]; then 406 local OPTS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot" 407 podman $OPTS stop -f -t 0 -a 408 409 # rm/rmi are important when running rootless: without them we 410 # get EPERMS in tmpdir cleanup because files are owned by subuids. 411 podman $OPTS rm -f -a 412 podman $OPTS rmi -f -a 413 fi 414 } 415 416 ################# 417 # random_port # Random open port; arg is range (min-max), default 5000-5999 418 ################# 419 function random_port() { 420 local range=${1:-5000-5999} 421 422 local port 423 for port in $(shuf -i ${range}); do 424 if ! { exec 5<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then 425 echo $port 426 return 427 fi 428 done 429 430 die "Could not find open port in range $range" 431 } 432 433 ################### 434 # random_string # Pseudorandom alphanumeric string of given length 435 ################### 436 function random_string() { 437 local length=${1:-10} 438 head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length 439 } 440 441 ################### 442 # wait_for_port # Returns once port is available on host 443 ################### 444 function wait_for_port() { 445 local host=$1 # Probably "localhost" 446 local port=$2 # Numeric port 447 local _timeout=${3:-5} # Optional; default to 5 seconds 448 449 # Wait 450 while [ $_timeout -gt 0 ]; do 451 { exec 3<> /dev/tcp/$host/$port; } &>/dev/null && return 452 sleep 1 453 _timeout=$(( $_timeout - 1 )) 454 done 455 die "Timed out waiting for service" 456 } 457 458 ############ 459 # podman # Needed by some test scripts to invoke the actual podman binary 460 ############ 461 function podman() { 462 echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log 463 $PODMAN_BIN --root $WORKDIR/server_root "$@" >>$WORKDIR/output.log 2>&1 464 } 465 466 #################### 467 # root, rootless # Is server rootless? 468 #################### 469 ROOTLESS= 470 function root() { 471 ! rootless 472 } 473 474 function rootless() { 475 if [[ -z $ROOTLESS ]]; then 476 ROOTLESS=$(curl -s http://$HOST:$PORT/v1.40/info | jq .Rootless) 477 fi 478 test "$ROOTLESS" = "true" 479 } 480 481 # True if cgroups v2 are enabled 482 function have_cgroupsv2() { 483 cgroup_type=$(stat -f -c %T /sys/fs/cgroup) 484 test "$cgroup_type" = "cgroup2fs" 485 } 486 487 # END infrastructure code 488 ############################################################################### 489 # BEGIN sanity checks 490 491 for tool in curl jq podman; do 492 type $tool &>/dev/null || die "$ME: Required tool '$tool' not found" 493 done 494 495 # END sanity checks 496 ############################################################################### 497 # BEGIN entry handler (subtest invoker) 498 499 # Identify the tests to run. If called with args, use those as globs. 500 tests_to_run=() 501 if [ -n "$*" ]; then 502 shopt -s nullglob 503 for i; do 504 match=(${TESTS_DIR}/*${i}*.at) 505 if [ ${#match} -eq 0 ]; then 506 die "No match for $TESTS_DIR/*$i*.at" 507 fi 508 tests_to_run+=("${match[@]}") 509 done 510 shopt -u nullglob 511 else 512 tests_to_run=($TESTS_DIR/*.at) 513 fi 514 515 start_service 516 517 for i in ${tests_to_run[@]}; do 518 TEST_CONTEXT="[$(basename $i .at)]" 519 source $i 520 done 521 522 # END entry handler 523 ############################################################################### 524 525 # Clean up 526 527 if [ -n "$service_pid" ]; then 528 # Remove any containers and images; this prevents the following warning: 529 # 'rm: cannot remove '/.../overlay': Device or resource busy 530 podman rm -a 531 podman rmi -af 532 533 stop_registry 534 stop_service 535 fi 536 537 test_count=$(<$testcounter_file) 538 failure_count=$(<$failures_file) 539 540 if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then 541 rm -rf $WORKDIR 542 fi 543 544 echo "1..${test_count}" 545 546 exit $failure_count