diff options
Diffstat (limited to 'test')
43 files changed, 831 insertions, 27 deletions
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 5c35edf2b..bc6efc20d 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -5,8 +5,10 @@ # WORKDIR=/data ENV_WORKDIR_IMG=quay.io/libpod/testimage:20200929 +MultiTagName=localhost/test/testformultitag:tag podman pull $IMAGE &>/dev/null +podman tag $IMAGE $MultiTagName podman pull $ENV_WORKDIR_IMG &>/dev/null # Unimplemented #t POST libpod/containers/create '' 201 'sdf' @@ -216,4 +218,13 @@ t GET containers/$cid/json 200 \ t DELETE containers/$cid 204 +# when the image had multi tags, the container's Image should be correct +# Fixes https://github.com/containers/podman/issues/8547 +t POST containers/create Image=${MultiTagName} 201 \ + .Id~[0-9a-f]\\{64\\} +cid=$(jq -r '.Id' <<<"$output") +t GET containers/$cid/json 200 \ + .Image=${MultiTagName} +t DELETE containers/$cid 204 +t DELETE images/${MultiTagName}?force=true 200 # vim: filetype=sh diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at index aa167a97a..2cfca9d08 100644 --- a/test/apiv2/30-volumes.at +++ b/test/apiv2/30-volumes.at @@ -20,6 +20,18 @@ t POST libpod/volumes/create \ .Labels.testlabel=testonly \ .Options.type=tmpfs \ .Options.o=nodev,noexec +t POST libpod/volumes/create \ + '"Name":"foo3","Label":{"testlabel":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ + .Name=foo3 \ + .Labels.testlabel="" \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec +t POST libpod/volumes/create \ + '"Name":"foo4","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ + .Name=foo4 \ + .Labels.testlabel1=testonly \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec # Negative test # We have created a volume named "foo1" @@ -39,6 +51,12 @@ t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22foo1%22%5D%7D 200 length t GET libpod/volumes/json?filters=%7B%22name%22%3A%20%5B%22foo1%22%2C%20%22foo2%22%5D%7D 200 length=2 .[0].Name=foo1 .[1].Name=foo2 # -G --data-urlencode 'filters={"name":["notexist"]}' t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22notexists%22%5D%7D 200 length=0 +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=2 +# -G --data-urlencode 'filters={"label":["testlabel=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel=testonly%22%5D%7D 200 length=1 +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=1 ## inspect volume t GET libpod/volumes/foo1/json 200 \ @@ -60,6 +78,18 @@ t DELETE libpod/volumes/foo1 404 \ .message~.* \ .response=404 +## Prune volumes with label matching 'testlabel1=testonly' +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D "" 200 +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=0 + +## Prune volumes with label matching 'testlabel' +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D "" 200 +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=0 + ## Prune volumes t POST libpod/volumes/prune "" 200 #After prune volumes, there should be no volume existing diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index 0ce56ee3c..5327bd076 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -50,7 +50,13 @@ t GET networks?filters=%7B%22dangling%22%3A%5B%221%22%5D%7D 500 \ # network inspect docker t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 200 \ .Name=network1 \ -.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 +.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 \ +.Scope=local + +# network create docker +t POST networks/create '"Name":"net3","IPAM":{"Config":[]}' 201 +# network delete docker +t DELETE networks/net3 204 # clean the network t DELETE libpod/networks/network1 200 \ diff --git a/test/compose/README.md b/test/compose/README.md new file mode 100644 index 000000000..863decf2c --- /dev/null +++ b/test/compose/README.md @@ -0,0 +1,47 @@ +Tests for docker-compose +======================== + +This directory contains tests for docker-compose under podman. + +Each subdirectory must contain one docker-compose.yml file along with +all necessary infrastructure for it (e.g. Containerfile, any files +to be copied into the container, and so on. + +The `test-compose` script will, for each test subdirectory: + +* set up a fresh podman root under an empty working directory; +* run a podman server rooted therein; +* cd to the test subdirectory, and run `docker-compose up -d`; +* source `tests.sh`; +* run `docker-compose down`. + +As a special case, `setup.sh` and `teardown.sh` in the test directory +will contain commands to be executed prior to `docker-compose up` and +after `docker-compose down` respectively. + +tests.sh will probably contain commands of the form + + test_port 12345 = 'hello there' + +Where 12345 is the port to curl to; '=' checks equality, '~' uses `expr` +to check substrings; and 'hello there' is a string to look for in +the curl results. + +Usage: + + $ sudo test/compose/test-compose [pattern] + +By default, all subdirs will be run. If given a pattern, only those +subdirectories matching 'pattern' will be run. + +If `$COMPOSE_WAIT` is set, `test-compose` will pause before running +`docker-compose down`. This can be helpful for you to debug failing tests: + + $ env COMPOSE_WAIT=1 sudo --preserve-env=COMPOSE_WAIT test/compose/test-compose + +Then, in another window, + + # ls -lt /var/tmp/ + # X=/var/tmp/test-compose.tmp.XXXXXX <--- most recent results of above + # podman --root $X/root --runroot $X/runroot ps -a + # podman --root $X/root --runroot $X/runroot logs -l diff --git a/test/compose/env_and_volume/README.md b/test/compose/env_and_volume/README.md new file mode 100644 index 000000000..e7d74976b --- /dev/null +++ b/test/compose/env_and_volume/README.md @@ -0,0 +1,12 @@ +environment variable and volume +=============== + +This test creates two containers both of which are running flask. The first container has +an environment variable called PODMAN_MSG. That container pipes the contents of PODMAN_MSG +to a file on a shared volume between the containers. The second container then reads the +file are returns the PODMAN_MSG value via flask (http). + +Validation +------------ +* curl http://localhost:5000 and verify message +* curl http://localhost:5001 and verify message diff --git a/test/compose/env_and_volume/docker-compose.yml b/test/compose/env_and_volume/docker-compose.yml new file mode 100644 index 000000000..df906e170 --- /dev/null +++ b/test/compose/env_and_volume/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + writer: + environment: + - PODMAN_MSG=podman_rulez + build: write + ports: + - '5000:5000' + volumes: + - data:/data + reader: + build: read + ports: + - '5001:5000' + volumes: + - data:/data +volumes: + data: diff --git a/test/compose/env_and_volume/read/Dockerfile b/test/compose/env_and_volume/read/Dockerfile new file mode 100644 index 000000000..8d5c45401 --- /dev/null +++ b/test/compose/env_and_volume/read/Dockerfile @@ -0,0 +1,5 @@ +FROM quay.io/libpod/podman_python +WORKDIR /app +COPY . /app +ENTRYPOINT ["python3"] +CMD ["app.py"] diff --git a/test/compose/env_and_volume/read/app.py b/test/compose/env_and_volume/read/app.py new file mode 100644 index 000000000..71fbbb26a --- /dev/null +++ b/test/compose/env_and_volume/read/app.py @@ -0,0 +1,10 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + f = open("/data/message", "r") + return f.read() + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/test/compose/env_and_volume/tests.sh b/test/compose/env_and_volume/tests.sh new file mode 100644 index 000000000..a4c8bed30 --- /dev/null +++ b/test/compose/env_and_volume/tests.sh @@ -0,0 +1,4 @@ +# -*- bash -*- + +test_port 5000 = "done" +test_port 5001 = "podman_rulez" diff --git a/test/compose/env_and_volume/write/Dockerfile b/test/compose/env_and_volume/write/Dockerfile new file mode 100644 index 000000000..8d5c45401 --- /dev/null +++ b/test/compose/env_and_volume/write/Dockerfile @@ -0,0 +1,5 @@ +FROM quay.io/libpod/podman_python +WORKDIR /app +COPY . /app +ENTRYPOINT ["python3"] +CMD ["app.py"] diff --git a/test/compose/env_and_volume/write/app.py b/test/compose/env_and_volume/write/app.py new file mode 100644 index 000000000..b6ad6fe63 --- /dev/null +++ b/test/compose/env_and_volume/write/app.py @@ -0,0 +1,13 @@ +from flask import Flask +import os +app = Flask(__name__) + +@app.route('/') +def hello(): + f = open("/data/message", "w") + f.write(os.getenv("PODMAN_MSG")) + f.close() + return "done" + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/test/compose/images/README.md b/test/compose/images/README.md new file mode 100644 index 000000000..f25fbdc24 --- /dev/null +++ b/test/compose/images/README.md @@ -0,0 +1,5 @@ +images +====== + +Use these directories for images that are needed for the compose testing. These +images should be then pushed to `quay.io/libpod` for consumption. diff --git a/test/compose/images/podman-python/Containerfile b/test/compose/images/podman-python/Containerfile new file mode 100644 index 000000000..47f90afaa --- /dev/null +++ b/test/compose/images/podman-python/Containerfile @@ -0,0 +1,3 @@ +FROM alpine +WORKDIR /app +RUN apk update && apk add py3-pip && pip3 install flask && rm -fr /var/cache/apk/* diff --git a/test/compose/mount_and_label/README.md b/test/compose/mount_and_label/README.md new file mode 100644 index 000000000..623b38cac --- /dev/null +++ b/test/compose/mount_and_label/README.md @@ -0,0 +1,9 @@ +mount and label +=============== + +This test creates a container with a mount (not volume) and also adds a label to the container. + +Validation +------------ +* curl http://localhost:5000 and verify message +* inspect the container to make the label exists on it diff --git a/test/compose/mount_and_label/docker-compose.yml b/test/compose/mount_and_label/docker-compose.yml new file mode 100644 index 000000000..112d7e134 --- /dev/null +++ b/test/compose/mount_and_label/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' +services: + web: + build: frontend + ports: + - '5000:5000' + volumes: + - /tmp/data:/data:ro + labels: + - "io.podman=the_best" diff --git a/test/compose/mount_and_label/frontend/Dockerfile b/test/compose/mount_and_label/frontend/Dockerfile new file mode 100644 index 000000000..8d5c45401 --- /dev/null +++ b/test/compose/mount_and_label/frontend/Dockerfile @@ -0,0 +1,5 @@ +FROM quay.io/libpod/podman_python +WORKDIR /app +COPY . /app +ENTRYPOINT ["python3"] +CMD ["app.py"] diff --git a/test/compose/mount_and_label/frontend/app.py b/test/compose/mount_and_label/frontend/app.py new file mode 100644 index 000000000..bd2794d94 --- /dev/null +++ b/test/compose/mount_and_label/frontend/app.py @@ -0,0 +1,10 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + f = open("/data/message") + return f.read() + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/test/compose/mount_and_label/setup.sh b/test/compose/mount_and_label/setup.sh new file mode 100644 index 000000000..8633d65d5 --- /dev/null +++ b/test/compose/mount_and_label/setup.sh @@ -0,0 +1,2 @@ +mkdir -p /tmp/data +echo "Podman rulez!" > /tmp/data/message diff --git a/test/compose/mount_and_label/teardown.sh b/test/compose/mount_and_label/teardown.sh new file mode 100644 index 000000000..57867c28a --- /dev/null +++ b/test/compose/mount_and_label/teardown.sh @@ -0,0 +1 @@ +rm /tmp/data/message diff --git a/test/compose/mount_and_label/tests.sh b/test/compose/mount_and_label/tests.sh new file mode 100644 index 000000000..07ff089b5 --- /dev/null +++ b/test/compose/mount_and_label/tests.sh @@ -0,0 +1,4 @@ +# -*- bash -*- + +test_port 5000 = "Podman rulez!" +podman container inspect -l --format '{{.Config.Labels}}' | grep "the_best" diff --git a/test/compose/port_map_diff_port/README.md b/test/compose/port_map_diff_port/README.md new file mode 100644 index 000000000..13ece72ad --- /dev/null +++ b/test/compose/port_map_diff_port/README.md @@ -0,0 +1,9 @@ +port map on different port +=============== + +This test creates a container that runs flask on different ports for the container +and the host + +Validation +------------ +* curl http://localhost:5001 and verify message diff --git a/test/compose/port_map_diff_port/docker-compose.yml b/test/compose/port_map_diff_port/docker-compose.yml new file mode 100644 index 000000000..3003c52f4 --- /dev/null +++ b/test/compose/port_map_diff_port/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3' +services: + web: + build: frontend + ports: + - '5001:5000' diff --git a/test/compose/port_map_diff_port/frontend/Dockerfile b/test/compose/port_map_diff_port/frontend/Dockerfile new file mode 100644 index 000000000..8d5c45401 --- /dev/null +++ b/test/compose/port_map_diff_port/frontend/Dockerfile @@ -0,0 +1,5 @@ +FROM quay.io/libpod/podman_python +WORKDIR /app +COPY . /app +ENTRYPOINT ["python3"] +CMD ["app.py"] diff --git a/test/compose/port_map_diff_port/frontend/app.py b/test/compose/port_map_diff_port/frontend/app.py new file mode 100644 index 000000000..895556a89 --- /dev/null +++ b/test/compose/port_map_diff_port/frontend/app.py @@ -0,0 +1,9 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Podman rulez!" + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/test/compose/port_map_diff_port/tests.sh b/test/compose/port_map_diff_port/tests.sh new file mode 100644 index 000000000..5a468aadc --- /dev/null +++ b/test/compose/port_map_diff_port/tests.sh @@ -0,0 +1,3 @@ +# -*- bash -*- + +test_port 5001 = "Podman rulez!" diff --git a/test/compose/setup.sh.example b/test/compose/setup.sh.example new file mode 100644 index 000000000..9004b1e76 --- /dev/null +++ b/test/compose/setup.sh.example @@ -0,0 +1,3 @@ +# -*- bash -*- + +export ENV_PASSTHRU=$(random_string 20) diff --git a/test/compose/simple_port_map/README.md b/test/compose/simple_port_map/README.md new file mode 100644 index 000000000..f28d71c3e --- /dev/null +++ b/test/compose/simple_port_map/README.md @@ -0,0 +1,9 @@ +simple port map to host +=============== + +This test creates a container that runs flask on and maps to the same +host port + +Validation +------------ +* curl http://localhost:5000 and verify message diff --git a/test/compose/simple_port_map/docker-compose.yml b/test/compose/simple_port_map/docker-compose.yml new file mode 100644 index 000000000..e7eab1047 --- /dev/null +++ b/test/compose/simple_port_map/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3' +services: + web: + build: frontend + ports: + - '5000:5000' diff --git a/test/compose/simple_port_map/frontend/Dockerfile b/test/compose/simple_port_map/frontend/Dockerfile new file mode 100644 index 000000000..2595828ff --- /dev/null +++ b/test/compose/simple_port_map/frontend/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine +WORKDIR /app +RUN apk update && apk add py3-pip && pip3 install flask +COPY . /app +ENTRYPOINT ["python3"] +CMD ["app.py"] diff --git a/test/compose/simple_port_map/frontend/app.py b/test/compose/simple_port_map/frontend/app.py new file mode 100644 index 000000000..e4f84068c --- /dev/null +++ b/test/compose/simple_port_map/frontend/app.py @@ -0,0 +1,10 @@ +from flask import Flask +import os +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Podman rulez!" + +if __name__ == '__main__': + app.run(host='0.0.0.0') diff --git a/test/compose/simple_port_map/tests.sh b/test/compose/simple_port_map/tests.sh new file mode 100644 index 000000000..ccb2b6a3d --- /dev/null +++ b/test/compose/simple_port_map/tests.sh @@ -0,0 +1,3 @@ +# -*- bash -*- + +test_port 5000 = "Podman rulez!" diff --git a/test/compose/teardown.sh.example b/test/compose/teardown.sh.example new file mode 100644 index 000000000..3f8153fa0 --- /dev/null +++ b/test/compose/teardown.sh.example @@ -0,0 +1,4 @@ +# -*- bash -*- + +# FIXME: this is completely unnecessary; it's just an example of a teardown +unset ENV_PASSTHRU diff --git a/test/compose/test-compose b/test/compose/test-compose new file mode 100755 index 000000000..9558fbf58 --- /dev/null +++ b/test/compose/test-compose @@ -0,0 +1,345 @@ +#!/usr/bin/env bash +# +# Usage: test-compose [testname] +# +ME=$(basename $0) + +############################################################################### +# BEGIN stuff you can but probably shouldn't customize + +# Directory where this script and all subtests live +TEST_ROOTDIR=$(realpath $(dirname $0)) + +# Podman executable +PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman + +# Local path to docker socket (we will add the unix:/ prefix when we need it) +DOCKER_SOCK=/var/run/docker.sock + +# END stuff you can but probably shouldn't customize +############################################################################### +# BEGIN setup + +export TMPDIR=${TMPDIR:-/var/tmp} +WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) + +# Log of all HTTP requests and responses; always make '.log' point to latest +LOGBASE=${TMPDIR}/$ME.log +LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S') +ln -sf $LOG $LOGBASE + +# Keep track of test count and failures in files, not variables, because +# variables don't carry back up from subshells. +testcounter_file=$WORKDIR/.testcounter +failures_file=$WORKDIR/.failures + +echo 0 >$testcounter_file +echo 0 >$failures_file + +# END setup +############################################################################### +# BEGIN infrastructure code - the helper functions used in tests themselves + +######### +# die # Exit error with a message to stderr +######### +function die() { + echo "$ME: $*" >&2 + exit 1 +} + +######## +# is # Simple comparison +######## +function is() { + local actual=$1 + local expect=$2 + local testname=$3 + + if [[ $actual = $expect ]]; then + # On success, include expected value; this helps readers understand + _show_ok 1 "$testname=$expect" + return + fi + _show_ok 0 "$testname" "$expect" "$actual" +} + +########## +# like # Compare, but allowing patterns +########## +function like() { + local actual=$1 + local expect=$2 + local testname=$3 + + # "is" (equality) is a subset of "like", but one that expr fails on if + # the expected result has shell-special characters like '['. Treat it + # as a special case. + + if [[ "$actual" = "$expect" ]]; then + _show_ok 1 "$testname=$expect" + return + fi + + if expr "$actual" : ".*$expect" &>/dev/null; then + # On success, include expected value; this helps readers understand + _show_ok 1 "$testname ('$actual') ~ $expect" + return + fi + _show_ok 0 "$testname" "~ $expect" "$actual" +} + +############## +# _show_ok # Helper for is() and like(): displays 'ok' or 'not ok' +############## +function _show_ok() { + local ok=$1 + local testname=$2 + + # If output is a tty, colorize pass/fail + local red= + local green= + local reset= + local bold= + if [ -t 1 ]; then + red='\e[31m' + green='\e[32m' + reset='\e[0m' + bold='\e[1m' + fi + + _bump $testcounter_file + count=$(<$testcounter_file) + + # "skip" is a special case of "ok". Assume that our caller has included + # the magical '# skip - reason" comment string. + if [[ $ok == "skip" ]]; then + # colon-plus: replace green with yellow, but only if green is non-null + green="${green:+\e[33m}" + ok=1 + fi + if [ $ok -eq 1 ]; then + echo -e "${green}ok $count $testname${reset}" + echo "ok $count $testname" >>$LOG + return + fi + + # Failed + local expect=$3 + local actual=$4 + printf "${red}not ok $count $testname${reset}\n" + printf "${red}# expected: %s${reset}\n" "$expect" + printf "${red}# actual: ${bold}%s${reset}\n" "$actual" + + echo "not ok $count $testname" >>$LOG + echo " expected: $expect" >>$LOG + + _bump $failures_file +} + +########### +# _bump # Increment a counter in a file +########### +function _bump() { + local file=$1 + + count=$(<$file) + echo $(( $count + 1 )) >| $file +} + +############### +# test_port # Run curl against a port, check results against expectation +############### +function test_port() { + local port="$1" # e.g. 5000 + local op="$2" # '=' or '~' + local expect="$3" # what to expect from curl output + + local actual=$(curl --retry 5 --retry-connrefused -s http://127.0.0.1:$port/) + local curl_rc=$? + if [ $curl_rc -ne 0 ]; then + _show_ok 0 "$testname - curl failed with status $curl_rc" +### docker-compose down >>$logfile 2>&1 +### exit 1 + fi + + case "$op" in + '=') is "$actual" "$expect" "$testname : port $port" ;; + '~') like "$actual" "$expect" "$testname : port $port" ;; + *) die "Invalid operator '$op'" ;; + esac +} + + +################### +# start_service # Run the socket listener +################### +service_pid= +function start_service() { + test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" + + # FIXME: use ${testname} subdir but we can't: 50-char limit in runroot + rm -rf $WORKDIR/{root,runroot,cni} + mkdir --mode 0755 $WORKDIR/{root,runroot,cni} + chcon --reference=/var/lib/containers $WORKDIR/root + cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/ + + $PODMAN_BIN \ + --root $WORKDIR/root \ + --runroot $WORKDIR/runroot \ + --cgroup-manager=systemd \ + --cni-config-dir $WORKDIR/cni \ + system service \ + --time 0 unix:/$DOCKER_SOCK \ + &> $WORKDIR/server.log & + service_pid=$! + + # Wait (FIXME: how do we test the socket?) + local _timeout=5 + while [ $_timeout -gt 0 ]; do + # FIXME: should we actually try a read or write? + test -S $DOCKER_SOCK && return + sleep 1 + _timeout=$(( $_timeout - 1 )) + done + cat $WORKDIR/server.log + die "Timed out waiting for service" +} + +############ +# podman # Needed by some test scripts to invoke the actual podman binary +############ +function podman() { + echo "\$ podman $*" >>$WORKDIR/output.log + $PODMAN_BIN \ + --root $WORKDIR/root \ + --runroot $WORKDIR/runroot \ + "$@" >>$WORKDIR/output.log 2>&1 +} + +################### +# random_string # Returns a pseudorandom human-readable string +################### +function random_string() { + # Numeric argument, if present, is desired length of string + local length=${1:-10} + + head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length +} + +# END infrastructure code +############################################################################### +# BEGIN sanity checks + +for tool in curl docker-compose; do + type $tool &>/dev/null || die "$ME: Required tool '$tool' not found" +done + +# END sanity checks +############################################################################### +# BEGIN entry handler (subtest invoker) + +# Identify the tests to run. If called with args, use those as globs. +tests_to_run=() +if [ -n "$*" ]; then + shopt -s nullglob + for i; do + match=(${TEST_ROOTDIR}/*${i}*/docker-compose.yml) + if [ ${#match} -eq 0 ]; then + die "No match for $TEST_ROOTDIR/*$i*.curl" + fi + tests_to_run+=("${match[@]}") + done + shopt -u nullglob +else + tests_to_run=(${TEST_ROOTDIR}/*/docker-compose.yml) +fi + +# Too hard to precompute the number of tests; just spit it out at the end. +n_tests=0 +for t in ${tests_to_run[@]}; do + testdir="$(dirname $t)" + testname="$(basename $testdir)" + + if [ -e $test_dir/SKIP ]; then + local reason="$(<$test_dir/SKIP)" + if [ -n "$reason" ]; then + reason=" - $reason" + fi + _show_ok skip "$testname # skip$reason" + continue + fi + + start_service + + logfile=$WORKDIR/$testname.log + ( + cd $testdir || die "Cannot cd $testdir" + + # setup file may be used for creating temporary directories/files. + # We source it so that envariables defined in it will get back to us. + if [ -e setup.sh ]; then + . setup.sh + fi + if [ -e teardown.sh ]; then + trap '. teardown.sh' 0 + fi + + docker-compose up -d &> $logfile + docker_compose_rc=$? + if [[ $docker_compose_rc -ne 0 ]]; then + _show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc" + sed -e 's/^/# /' <$logfile + docker-compose down >>$logfile 2>&1 # No status check here + exit 1 + fi + _show_ok 1 "$testname - up" + + # Run tests. This is likely to be a series of 'test_port' checks + # but may also include podman commands to inspect labels, state + if [ -e tests.sh ]; then + . tests.sh + fi + # FIXME: if any tests fail, try 'podman logs' on container? + + if [ -n "$COMPOSE_WAIT" ]; then + echo -n "Pausing due to \$COMPOSE_WAIT. Press ENTER to continue: " + read keepgoing + fi + + # Done. Clean up. + docker-compose down &> $logfile + rc=$? + if [[ $rc -eq 0 ]]; then + _show_ok 1 "$testname - down" + else + _show_ok 0 "$testname - down" "[ok]" "rc=$rc" + # FIXME: show error + fi + ) + + kill $service_pid + wait $service_pid + + # FIXME: otherwise we get EBUSY + umount $WORKDIR/root/overlay &>/dev/null + + # FIXME: run 'podman ps'? +# rm -rf $WORKDIR/${testname} +done + +# END entry handler +############################################################################### + +# Clean up + +test_count=$(<$testcounter_file) +failure_count=$(<$failures_file) + +#if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then +# rm -rf $WORKDIR +#fi + +echo "1..${test_count}" + +exit $failure_count diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index b37bd584e..0c7a1bd66 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -5,9 +5,11 @@ import ( "fmt" "os" "strings" + "sync" "time" . "github.com/containers/podman/v2/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -115,10 +117,7 @@ var _ = Describe("Podman events", func() { SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) - test := podmanTest.Podman([]string{"events", "--help"}) - test.WaitWithDefaultTimeout() - fmt.Println(test.OutputToStringArray()) - result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1h"}) + result := podmanTest.Podman([]string{"events", "--stream=false", "--until", "1h"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(BeZero()) }) @@ -154,4 +153,40 @@ var _ = Describe("Podman events", func() { Expect(eventsMap).To(HaveKey("Status")) }) + + It("podman events --until future", func() { + name1 := stringid.GenerateNonCryptoID() + name2 := stringid.GenerateNonCryptoID() + name3 := stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"create", "--name", name1, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + + // wait 2 seconds to be sure events is running + time.Sleep(time.Second * 2) + session = podmanTest.Podman([]string{"create", "--name", name2, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"create", "--name", name3, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }() + + // unix timestamp in 10 seconds + until := time.Now().Add(time.Second * 10).Unix() + result := podmanTest.Podman([]string{"events", "--since", "30s", "--until", fmt.Sprint(until)}) + result.Wait(11) + Expect(result.ExitCode()).To(BeZero()) + Expect(result.OutputToString()).To(ContainSubstring(name1)) + Expect(result.OutputToString()).To(ContainSubstring(name2)) + Expect(result.OutputToString()).To(ContainSubstring(name3)) + + wg.Wait() + }) }) diff --git a/test/e2e/image_sign_test.go b/test/e2e/image_sign_test.go index c9041eaba..57739419c 100644 --- a/test/e2e/image_sign_test.go +++ b/test/e2e/image_sign_test.go @@ -1,6 +1,7 @@ package integration import ( + "io/ioutil" "os" "os/exec" "path/filepath" @@ -58,4 +59,19 @@ var _ = Describe("Podman image sign", func() { _, err = os.Stat(filepath.Join(sigDir, "library")) Expect(err).To(BeNil()) }) + + It("podman sign --all multi-arch image", func() { + cmd := exec.Command("gpg", "--import", "sign/secret-key.asc") + err := cmd.Run() + Expect(err).To(BeNil()) + sigDir := filepath.Join(podmanTest.TempDir, "test-sign-multi") + err = os.MkdirAll(sigDir, os.ModePerm) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"image", "sign", "--all", "--directory", sigDir, "--sign-by", "foo@bar.com", "docker://library/alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + fInfos, err := ioutil.ReadDir(filepath.Join(sigDir, "library")) + Expect(err).To(BeNil()) + Expect(len(fInfos) > 1).To(BeTrue()) + }) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index a749a86ff..aae6d4f02 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -355,4 +355,21 @@ var _ = Describe("Podman logs", func() { Expect(outlines[0]).To(Equal("1\r")) Expect(outlines[1]).To(Equal("2\r")) }) + + It("podman logs test stdout and stderr", func() { + cname := "log-test" + logc := podmanTest.Podman([]string{"run", "--name", cname, ALPINE, "sh", "-c", "echo stdout; echo stderr >&2"}) + logc.WaitWithDefaultTimeout() + Expect(logc).To(Exit(0)) + + wait := podmanTest.Podman([]string{"wait", cname}) + wait.WaitWithDefaultTimeout() + Expect(wait).To(Exit(0)) + + results := podmanTest.Podman([]string{"logs", cname}) + results.WaitWithDefaultTimeout() + Expect(results).To(Exit(0)) + Expect(results.OutputToString()).To(Equal("stdout")) + Expect(results.ErrorToString()).To(Equal("stderr")) + }) }) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 5ecfdd6b5..3a2387559 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -1072,7 +1072,7 @@ var _ = Describe("Podman play kube", func() { logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)}) logs.WaitWithDefaultTimeout() Expect(logs.ExitCode()).To(Equal(0)) - Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted")) + Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted")) }) It("podman play kube seccomp pod level", func() { @@ -1099,7 +1099,7 @@ var _ = Describe("Podman play kube", func() { logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)}) logs.WaitWithDefaultTimeout() Expect(logs.ExitCode()).To(Equal(0)) - Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted")) + Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted")) }) It("podman play kube with pull policy of never should be 125", func() { diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 942e00123..a6f22e007 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -49,6 +49,29 @@ var _ = Describe("Podman start", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman start --rm removed on failure", func() { + session := podmanTest.Podman([]string{"create", "--name=test", "--rm", ALPINE, "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + session = podmanTest.Podman([]string{"container", "exists", "test"}) + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman start --rm --attach removed on failure", func() { + session := podmanTest.Podman([]string{"create", "--rm", ALPINE, "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + session = podmanTest.Podman([]string{"start", "--attach", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + session = podmanTest.Podman([]string{"container", "exists", cid}) + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + It("podman container start single container by id", func() { session := podmanTest.Podman([]string{"container", "create", ALPINE, "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go index 7393b13cb..6f04ce48c 100644 --- a/test/e2e/toolbox_test.go +++ b/test/e2e/toolbox_test.go @@ -239,7 +239,7 @@ var _ = Describe("Toolbox-specific testing", func() { session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(ContainSubstring(expectedOutput)) + Expect(session.ErrorToString()).To(ContainSubstring(expectedOutput)) }) It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() { diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index c8521ebe7..a910c47a7 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -62,6 +62,66 @@ var _ = Describe("Podman volume prune", func() { podmanTest.Cleanup() }) + It("podman prune volume --filter", func() { + session := podmanTest.Podman([]string{"volume", "create", "--label", "label1=value1", "myvol1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv1", "myvol2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv2", "myvol3"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1", "myvol4"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "myvol5:/myvol5", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "myvol6:/myvol6", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(7)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=label1=value1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(6)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1=slv1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(5)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(3)) + + podmanTest.Cleanup() + }) + It("podman system prune --volume", func() { session := podmanTest.Podman([]string{"volume", "create"}) session.WaitWithDefaultTimeout() diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 3ee141f5f..23f924de2 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -415,13 +415,18 @@ json-file | f fi if [[ $driver != 'none' ]]; then - run_podman logs myctr - is "$output" "$msg" "check that podman logs works as expected" + if [[ $driver = 'journald' ]] && journald_unavailable; then + # Cannot perform check + : + else + run_podman logs myctr + is "$output" "$msg" "podman logs, with driver '$driver'" + fi else run_podman 125 logs myctr if ! is_remote; then is "$output" ".*this container is using the 'none' log driver, cannot read logs.*" \ - "podman logs does not work with none log driver" + "podman logs, with driver 'none', should fail with error" fi fi run_podman rm myctr @@ -437,14 +442,7 @@ json-file | f skip_if_remote "We cannot read journalctl over remote." # We can't use journald on RHEL as rootless, either: rhbz#1895105 - if is_rootless; then - run journalctl -n 1 - if [[ $status -ne 0 ]]; then - if [[ $output =~ permission ]]; then - skip "Cannot use rootless journald on this system" - fi - fi - fi + skip_if_journald_unavailable msg=$(random_string 20) pidfile="${PODMAN_TMPDIR}/$(random_string 20)" diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index a081a7ce1..bac153b8e 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -55,14 +55,7 @@ ${cid[0]} d" "Sequential output from logs" @test "podman logs over journald" { # We can't use journald on RHEL as rootless: rhbz#1895105 - if is_rootless; then - run journalctl -n 1 - if [[ $status -ne 0 ]]; then - if [[ $output =~ permission ]]; then - skip "Cannot use rootless journald on this system" - fi - fi - fi + skip_if_journald_unavailable msg=$(random_string 20) diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 6a7c6cc42..f782de080 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -259,6 +259,31 @@ function is_cgroupsv2() { test "$cgroup_type" = "cgroup2fs" } +# rhbz#1895105: rootless journald is unavailable except to users in +# certain magic groups; which our testuser account does not belong to +# (intentional: that is the RHEL default, so that's the setup we test). +function journald_unavailable() { + if ! is_rootless; then + # root must always have access to journal + return 1 + fi + + run journalctl -n 1 + if [[ $status -eq 0 ]]; then + return 1 + fi + + if [[ $output =~ permission ]]; then + return 0 + fi + + # This should never happen; if it does, it's likely that a subsequent + # test will fail. This output may help track that down. + echo "WEIRD: 'journalctl -n 1' failed with a non-permission error:" + echo "$output" + return 1 +} + ########################### # _add_label_if_missing # make sure skip messages include rootless/remote ########################### @@ -315,6 +340,15 @@ function skip_if_cgroupsv1() { fi } +################################## +# skip_if_journald_unavailable # rhbz#1895105: rootless journald permissions +################################## +function skip_if_journald_unavailable { + if journald_unavailable; then + skip "Cannot use rootless journald on this system" + fi +} + ######### # die # Abort with helpful message ######### |