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