diff options
Diffstat (limited to 'test')
39 files changed, 1111 insertions, 195 deletions
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index 4fd954e37..3ffc6f738 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -227,16 +227,36 @@ t GET libpod/images/quay.io/libpod/busybox:latest/exists 204 CONTAINERFILE_WITH_ERR_TAR="${TMPD}/containerfile.tar" cat > $TMPD/containerfile << EOF -FROM quay.io/fedora/fedora +FROM $IMAGE RUN echo 'some error' >&2 EOF tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_WITH_ERR_TAR} containerfile &> /dev/null -t POST "build?q=1&dockerfile=containerfile" $CONTAINERFILE_WITH_ERR_TAR 200 -response_output=$(cat "$WORKDIR/curl.result.out") -if [[ ${response_output} == *"some error"* ]];then - _show_ok 0 "compat quiet build" "~ $response_output" "found output from stderr in API" +t POST "/build?q=1&dockerfile=containerfile" $CONTAINERFILE_WITH_ERR_TAR 200 +if [[ $output == *"some error"* ]];then + _show_ok 0 "compat quiet build" "[should not contain 'some error']" "$output" +else + _show_ok 1 "compat quiet build" fi cleanBuildTest +# compat API vs libpod API event differences: +# on image removal, libpod produces 'remove' events. +# compat produces 'delete' events. +podman image build -t test:test -<<EOF +from $IMAGE +EOF + +START=$(date +%s) + +t DELETE libpod/images/test:test 200 +# HACK HACK HACK There is a race around events being added to the journal +# This sleep seems to avoid the race. +# If it fails and begins to flake, investigate a retry loop. +sleep 1 +t GET "libpod/events?stream=false&since=$START" 200 \ + 'select(.status | contains("remove")).Action=remove' +t GET "events?stream=false&since=$START" 200 \ + 'select(.status | contains("delete")).Action=delete' + # vim: filetype=sh diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at index be56152f1..eb58b8377 100644 --- a/test/apiv2/12-imagesMore.at +++ b/test/apiv2/12-imagesMore.at @@ -3,9 +3,6 @@ # Tests for more image-related endpoints # -red='\e[31m' -nc='\e[0m' - start_registry podman pull -q $IMAGE @@ -63,7 +60,9 @@ podman pull -q $IMAGE # test podman image SCP # ssh needs to work so we can validate that the failure is past argument parsing -podman system connection add --default test ssh://$USER@localhost/run/user/$MYUID/podman/podman.sock +conn=apiv2test-temp-connection +podman system connection add --default $conn \ + ssh://$USER@localhost/run/user/$UID/podman/podman.sock # should fail but need to check the output... # status 125 here means that the save/load fails due to # cirrus weirdness with exec.Command. All of the args have been parsed successfully. @@ -72,4 +71,7 @@ t POST "libpod/images/scp/$IMAGE?destination=QA::" 500 \ t DELETE libpod/images/$IMAGE 200 \ .ExitCode=0 +# Clean up +podman system connection rm $conn + stop_registry diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 655462f16..9ace46b8b 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -309,7 +309,9 @@ 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} + .Config.Image=${MultiTagName} \ + .Image~sha256:[0-9a-f]\\{64\\} + t DELETE containers/$cid 204 t DELETE images/${MultiTagName} 200 # vim: filetype=sh @@ -545,6 +547,21 @@ t GET libpod/containers/$cname/json 200 \ .ImageName=$IMAGE \ .Name=$cname +if root; then + podman run -dt --name=updateCtr alpine + echo '{"Memory":{"Limit":500000}, "CPU":{"Shares":123}}' >${TMPD}/update.json + t POST libpod/containers/updateCtr/update ${TMPD}/update.json 201 + + # Verify + echo '{ "AttachStdout":true,"Cmd":["cat","/sys/fs/cgroup/cpu.weight"]}' >${TMPD}/exec.json + t POST containers/updateCtr/exec ${TMPD}/exec.json 201 .Id~[0-9a-f]\\{64\\} + eid=$(jq -r '.Id' <<<"$output") + # 002 is the byte length + t POST exec/$eid/start 200 $'\001\0025' + + podman rm -f updateCtr +fi + rm -rf $TMPD podman container rm -fa diff --git a/test/apiv2/23-containersArchive.at b/test/apiv2/23-containersArchive.at index 3ff4465b9..c1b936e3a 100644 --- a/test/apiv2/23-containersArchive.at +++ b/test/apiv2/23-containersArchive.at @@ -3,9 +3,6 @@ # test more container-related endpoints # -red='\e[31m' -nc='\e[0m' - podman pull $IMAGE &>/dev/null # Ensure clean slate diff --git a/test/apiv2/26-containersWait.at b/test/apiv2/26-containersWait.at index 55bcd4592..41938d567 100644 --- a/test/apiv2/26-containersWait.at +++ b/test/apiv2/26-containersWait.at @@ -3,9 +3,6 @@ # test more container-related endpoints # -red='\e[31m' -nc='\e[0m' - podman pull "${IMAGE}" &>/dev/null # Ensure clean slate @@ -21,29 +18,29 @@ t POST "containers/${CTR}/wait?condition=non-existent-cond" 400 t POST "containers/${CTR}/wait?condition=not-running" 200 +# Test waiting for EXIT (need to start a background trigger first) +(sleep 2;podman start "${CTR}") & +child_pid=$! + +# This will block until the background job completes t POST "containers/${CTR}/wait?condition=next-exit" 200 \ .StatusCode=0 \ - .Error=null & -child_pid=$! -podman start "${CTR}" + .Error=null wait "${child_pid}" - -# check if headers are sent in advance before body -WAIT_TEST_ERROR="" -curl -I -X POST "http://$HOST:$PORT/containers/${CTR}/wait?condition=next-exit" &> "/dev/null" & -child_pid=$! -sleep 0.5 -if kill -2 "${child_pid}" 2> "/dev/null"; then - echo -e "${red}NOK: Failed to get response headers immediately.${nc}" 1>&2; - WAIT_TEST_ERROR="1" +# Test that headers are sent before body. (We should actually never get a body) +APIV2_TEST_EXPECT_TIMEOUT=2 t POST "containers/${CTR}/wait?condition=next-exit" 999 +like "$(<$WORKDIR/curl.headers.out)" ".*HTTP.* 200 OK.*" \ + "Received headers from /wait" +if [[ -e $WORKDIR/curl.result.out ]]; then + _show_ok 0 "UNEXPECTED: curl on /wait returned results" fi -t POST "containers/${CTR}/wait?condition=removed" 200 & +# Test waiting for REMOVE. Like above, start a background trigger. +(sleep 2;podman container rm "${CTR}") & child_pid=$! -podman container rm "${CTR}" -wait "${child_pid}" -if [[ "${WAIT_TEST_ERROR}" ]] ; then - exit 1; -fi +t POST "containers/${CTR}/wait?condition=removed" 200 \ + .StatusCode=0 \ + .Error=null +wait "${child_pid}" diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at index d21b3d1a9..0e0f1cb18 100644 --- a/test/apiv2/40-pods.at +++ b/test/apiv2/40-pods.at @@ -134,23 +134,17 @@ t GET libpod/pods/json?filters='{"label":["testl' 400 \ t DELETE libpod/pods/foo 200 t DELETE "libpod/pods/foo (pod has already been deleted)" 404 -t_timeout 5 GET "libpod/pods/stats?stream=true&delay=1" 200 +# Expect this to time out +APIV2_TEST_EXPECT_TIMEOUT=5 t GET "libpod/pods/stats?stream=true&delay=1" 999 podman pod create --name=specgen TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX) -podman generate spec -f ${TMPD}/input.txt -c specgen +podman generate spec -f ${TMPD}/myspec.json -c specgen -curl -XPOST -o ${TMPD}/response.txt --dump-header ${TMPD}/headers.txt -H content-type:application/json http://$HOST:$PORT/v4.0.0/libpod/pods/create -d "@${TMPD}/input.txt" - -if ! grep -q '201 Created' "${TMPD}/headers.txt"; then - cat "${TMPD}/headers.txt" - cat "${TMPD}/response.txt" - echo -e "${red}NOK: pod create failed" - rm -rf $TMPD - exit 1 -fi +t POST libpod/pods/create ${TMPD}/myspec.json 201 \ + .Id~[0-9a-f]\\{64\\} rm -rf $TMPD diff --git a/test/apiv2/70-short-names.at b/test/apiv2/70-short-names.at index bd7f8e7bd..952dd2ad1 100644 --- a/test/apiv2/70-short-names.at +++ b/test/apiv2/70-short-names.at @@ -9,7 +9,7 @@ t POST "images/create?fromImage=quay.io/libpod/alpine:latest" 200 .error~null .s # 14291 - let a short-name resolve to a *local* non Docker-Hub image. t POST containers/create Image=alpine 201 .Id~[0-9a-f]\\{64\\} cid=$(jq -r '.Id' <<<"$output") -t GET containers/$cid/json 200 .Image="quay.io/libpod/alpine:latest" +t GET containers/$cid/json 200 .Config.Image="quay.io/libpod/alpine:latest" .Image~sha256:[0-9a-f]\\{64\\} podman rm -f $cid ########## TAG @@ -33,18 +33,8 @@ RUN touch /foo EOF tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_TAR} containerfile &> /dev/null - curl -XPOST --data-binary @<(cat $CONTAINERFILE_TAR) \ - -H "content-type: application/x-tar" \ - --dump-header "${TMPD}/headers.txt" \ - -o "${TMPD}/response.txt" \ - "http://$HOST:$PORT/build?dockerfile=containerfile&t=$tag" &> /dev/null - - if ! grep -q '200 OK' "${TMPD}/headers.txt"; then - cat "${TMPD}/headers.txt" - cat "${TMPD}/response.txt" - echo -e "${red}NOK: Image build from tar failed response was not 200 OK (application/x-tar)" - exit 1 - fi + t POST "/build?dockerfile=containerfile&t=$tag" $CONTAINERFILE_TAR 200 \ + .stream~".*Successfully tagged .*" rm -rf $TMPD t DELETE "images/$fqn" 200 diff --git a/test/apiv2/README.md b/test/apiv2/README.md index 63d1f5b13..712124d1b 100644 --- a/test/apiv2/README.md +++ b/test/apiv2/README.md @@ -46,6 +46,9 @@ with POST parameters if present, and compares return status and | +----------- POST params +--------------------------------- note the missing slash +Never, ever, ever, seriously _EVER_ `exit` from a test. Just don't. +That skips cleanup, and leaves the system in a broken state. + Notes: * If the endpoint has a leading slash (`/_ping`), `t` leaves it unchanged. @@ -61,14 +64,19 @@ of POST parameters in the form 'key=value', separated by spaces: `t` will convert the param list to JSON form for passing to the server. A numeric status code terminates processing of POST parameters. ** As a special case, when one POST argument is a string ending in `.tar`, -`t` will invoke `curl` with `--data-binary @PATH` and -set `Content-type: application/x-tar`. This is useful for `build` endpoints. +`.yaml`, or `.json`, `t` will invoke `curl` with `--data-binary @PATH` and +set `Content-type` as appropriate. This is useful for `build` endpoints. (To override `Content-type`, simply pass along an extra string argument matching `application/*`): t POST myentrypoint /mytmpdir/myfile.tar application/foo 400 +** Like above, when using PUT, `t` does `--upload-time` instead of +`--data-binary` * The final arguments are one or more expected string results. If an argument starts with a dot, `t` will invoke `jq` on the output to fetch that field, and will compare it to the right-hand side of the argument. If the separator is `=` (equals), `t` will require an exact match; if `~` (tilde), `t` will use `expr` to compare. + +* If your test expects `curl` to time out: + APIV2_TEST_EXPECT_TIMEOUT=5 t POST /foo 999 diff --git a/test/apiv2/python/rest_api/test_v2_0_0_container.py b/test/apiv2/python/rest_api/test_v2_0_0_container.py index a6cd93a1a..25596a9b7 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_container.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_container.py @@ -359,8 +359,6 @@ class ContainerTestCase(APITestCase): self.assertEqual(2000, out["HostConfig"]["MemorySwap"]) self.assertEqual(1000, out["HostConfig"]["Memory"]) - - def execute_process(cmd): return subprocess.run( cmd, diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index 0eb2d1b30..aca7db0dd 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -23,8 +23,6 @@ REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry ############################################################################### # BEGIN setup -USER=$PODMAN_ROOTLESS_USER -MYUID=$PODMAN_ROOTLESS_UID TMPDIR=${TMPDIR:-/tmp} WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX) @@ -56,9 +54,6 @@ fi # Path to podman binary PODMAN_BIN=${PODMAN:-${CONTAINERS_HELPER_BINARY_DIR}/podman} -# Timeout for streamed responses -CURL_TIMEOUT=0 - # Cleanup handlers clean_up_server() { if [ -n "$service_pid" ]; then @@ -221,21 +216,6 @@ function jsonify() { } ####### -# t_timeout # Timeout wrapper for test helper -####### -function t_timeout() { - CURL_TIMEOUT=$1; shift - local min_runtime=$((CURL_TIMEOUT - 1)) - start=`date +%s` - t $@ - local end=`date +%s` - local runtime=$((end-start)) - if ! [[ "$runtime" -ge "$min_runtime" ]]; then - die "Error: Streaming time should be greater or equal to '$min_runtime'" - fi -} - -####### # t # Main test helper ####### function t() { @@ -246,11 +226,6 @@ function t() { local testname="$method $path" - if [[ $CURL_TIMEOUT != 0 ]]; then - local c_timeout=$CURL_TIMEOUT - curl_args+=("-m $CURL_TIMEOUT") - CURL_TIMEOUT=0 # 'consume' timeout - fi # POST and PUT requests may be followed by one or more key=value pairs. # Slurp the command line until we see a 3-digit status code. if [[ $method = "POST" || $method == "PUT" || $method = "DELETE" ]]; then @@ -312,6 +287,11 @@ function t() { curl_args+=("--head") fi + # If this is set, we're *expecting* curl to time out + if [[ -n "$APIV2_TEST_EXPECT_TIMEOUT" ]]; then + curl_args+=("-m" $APIV2_TEST_EXPECT_TIMEOUT) + fi + local expected_code=$1; shift # Log every action we do @@ -327,8 +307,19 @@ function t() { --write-out '%{http_code}^%{content_type}^%{time_total}' \ -o $WORKDIR/curl.result.out "$url"); rc=$?; } || : + # Special case: this means we *expect and want* a timeout + if [[ -n "$APIV2_TEST_EXPECT_TIMEOUT" ]]; then + # Hardcoded. See curl(1) for list of exit codes + if [[ $rc -eq 28 ]]; then + _show_ok 1 "$testname: curl timed out (expected)" + else + _show_ok 0 "$testname: expected curl to time out; it did not" + fi + return + fi + # Any error from curl is instant bad news, from which we can't recover - if [[ $rc -ne 0 ]] && [[ $c_timeout -eq 0 ]]; then + if [[ $rc -ne 0 ]]; then die "curl failure ($rc) on $url - cannot continue" fi diff --git a/test/buildah-bud/apply-podman-deltas b/test/buildah-bud/apply-podman-deltas index 8ce58b06d..999f36bf9 100755 --- a/test/buildah-bud/apply-podman-deltas +++ b/test/buildah-bud/apply-podman-deltas @@ -235,6 +235,10 @@ skip_if_remote "envariables do not automatically work with -remote." \ skip_if_remote "FIXME FIXME FIXME: does this test make sense in remote?" \ "build-test with OCI prestart hook" +# 2022-08-17 buildah PR 4190 +skip_if_remote "Explicit request in buildah PR 4190 to skip this on remote" \ + "build: test race in updating image name while performing parallel commits" + ############################################################################### # BEGIN tests which are skipped due to actual podman or podman-remote bugs. diff --git a/test/buildah-bud/buildah-tests.diff b/test/buildah-bud/buildah-tests.diff index 399042240..bf119421e 100644 --- a/test/buildah-bud/buildah-tests.diff +++ b/test/buildah-bud/buildah-tests.diff @@ -1,4 +1,4 @@ -From 6508e3df2a129554fdf8336d8a6f0cdcc6fd4832 Mon Sep 17 00:00:00 2001 +From d22e44c8fb1c87afb90391188733f7ce8fea005d Mon Sep 17 00:00:00 2001 From: Ed Santiago <santiago@redhat.com> Date: Tue, 9 Feb 2021 17:28:05 -0700 Subject: [PATCH] tweaks for running buildah tests under podman @@ -9,10 +9,10 @@ Signed-off-by: Ed Santiago <santiago@redhat.com> 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/tests/helpers.bash b/tests/helpers.bash -index e3087063..178a486e 100644 +index 8cb93c3a..72c1c797 100644 --- a/tests/helpers.bash +++ b/tests/helpers.bash -@@ -51,6 +51,23 @@ EOF +@@ -52,6 +52,23 @@ EOF ROOTDIR_OPTS="--root ${TEST_SCRATCH_DIR}/root --runroot ${TEST_SCRATCH_DIR}/runroot --storage-driver ${STORAGE_DRIVER}" BUILDAH_REGISTRY_OPTS="--registries-conf ${TEST_SOURCES}/registries.conf --registries-conf-dir ${TEST_SCRATCH_DIR}/registries.d --short-name-alias-conf ${TEST_SCRATCH_DIR}/cache/shortnames.conf" PODMAN_REGISTRY_OPTS="--registries-conf ${TEST_SOURCES}/registries.conf" @@ -36,7 +36,7 @@ index e3087063..178a486e 100644 } function starthttpd() { -@@ -94,6 +111,12 @@ function teardown_tests() { +@@ -95,6 +112,12 @@ function teardown_tests() { stop_git_daemon stop_registry @@ -49,7 +49,7 @@ index e3087063..178a486e 100644 # Workaround for #1991 - buildah + overlayfs leaks mount points. # Many tests leave behind /var/tmp/.../root/overlay and sub-mounts; # let's find those and clean them up, otherwise 'rm -rf' fails. -@@ -186,6 +209,10 @@ function podman() { +@@ -187,6 +210,10 @@ function podman() { command ${PODMAN_BINARY:-podman} ${PODMAN_REGISTRY_OPTS} ${ROOTDIR_OPTS} "$@" } @@ -60,7 +60,7 @@ index e3087063..178a486e 100644 # There are various scenarios where we would like to execute `tests` as rootless user, however certain commands like `buildah mount` # do not work in rootless session since a normal user cannot mount a filesystem unless they're in a user namespace along with its # own mount namespace. In order to run such specific commands from a rootless session we must perform `buildah unshare`. -@@ -247,8 +274,36 @@ function run_buildah() { +@@ -248,8 +275,36 @@ function run_buildah() { --retry) retry=3; shift;; # retry network flakes esac @@ -98,7 +98,7 @@ index e3087063..178a486e 100644 # If session is rootless and `buildah mount` is invoked, perform unshare, # since normal user cannot mount a filesystem unless they're in a user namespace along with its own mount namespace. -@@ -262,8 +317,8 @@ function run_buildah() { +@@ -263,8 +318,8 @@ function run_buildah() { retry=$(( retry - 1 )) # stdout is only emitted upon error; this echo is to help a debugger @@ -109,7 +109,7 @@ index e3087063..178a486e 100644 # without "quotes", multiple lines are glommed together into one if [ -n "$output" ]; then echo "$output" -@@ -595,6 +650,15 @@ function skip_if_no_docker() { +@@ -596,6 +651,15 @@ function skip_if_no_docker() { fi } @@ -126,5 +126,5 @@ index e3087063..178a486e 100644 daemondir=${TEST_SCRATCH_DIR}/git-daemon mkdir -p ${daemondir}/repo -- -2.35.3 +2.36.1 diff --git a/test/e2e/build/Containerfile.userns-auto b/test/e2e/build/Containerfile.userns-auto new file mode 100644 index 000000000..921610982 --- /dev/null +++ b/test/e2e/build/Containerfile.userns-auto @@ -0,0 +1,2 @@ +FROM alpine +RUN cat /proc/self/uid_map diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 2d7c47a7f..8fe89f32e 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -571,7 +571,7 @@ func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectCont func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData { var i define.InspectPodData err := jsoniter.Unmarshal(s.Out.Contents(), &i) - Expect(err).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) return i } diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index 528fa143d..d54265558 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -42,10 +42,7 @@ var _ = Describe("Podman events", func() { // Perhaps a future version of this test would put events in a go func and send output back over a channel // while events occur. - // These tests are only known to work on Fedora ATM. Other distributions - // will be skipped. It("podman events", func() { - SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false"}) @@ -54,7 +51,6 @@ var _ = Describe("Podman events", func() { }) It("podman events with an event filter", func() { - SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start"}) @@ -81,7 +77,6 @@ var _ = Describe("Podman events", func() { }) It("podman events with a type and filter container=id", func() { - SkipIfNotFedora() _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod", "--filter", fmt.Sprintf("container=%s", cid)}) @@ -91,7 +86,6 @@ var _ = Describe("Podman events", func() { }) It("podman events with a type", func() { - SkipIfNotFedora() setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobarpod", ALPINE, "top"}) setup.WaitWithDefaultTimeout() stop := podmanTest.Podman([]string{"pod", "stop", "foobarpod"}) @@ -110,7 +104,6 @@ var _ = Describe("Podman events", func() { }) It("podman events --since", func() { - SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1m"}) @@ -119,7 +112,6 @@ var _ = Describe("Podman events", func() { }) It("podman events --until", func() { - SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--until", "1h"}) @@ -128,7 +120,6 @@ var _ = Describe("Podman events", func() { }) It("podman events format", func() { - SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) @@ -153,6 +144,13 @@ var _ = Describe("Podman events", func() { event = events.Event{} err = json.Unmarshal([]byte(jsonArr[0]), &event) Expect(err).ToNot(HaveOccurred()) + + test = podmanTest.Podman([]string{"events", "--stream=false", "--filter=type=container", "--format", "ID: {{.ID}}"}) + test.WaitWithDefaultTimeout() + Expect(test).To(Exit(0)) + arr := test.OutputToStringArray() + Expect(len(arr)).To(BeNumerically(">", 1)) + Expect(arr[0]).To(MatchRegexp("ID: [a-fA-F0-9]{64}")) }) It("podman events --until future", func() { diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index e7ceaf2d2..d8308aeea 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -3,6 +3,7 @@ package integration import ( "io/ioutil" "os" + "os/user" "path/filepath" "strconv" "strings" @@ -71,6 +72,10 @@ var _ = Describe("Podman generate kube", func() { Expect(pod.Spec.Containers[0]).To(HaveField("WorkingDir", "")) Expect(pod.Spec.Containers[0].Env).To(BeNil()) Expect(pod).To(HaveField("Name", "top-pod")) + enableServiceLinks := false + Expect(pod.Spec).To(HaveField("EnableServiceLinks", &enableServiceLinks)) + automountServiceAccountToken := false + Expect(pod.Spec).To(HaveField("AutomountServiceAccountToken", &automountServiceAccountToken)) numContainers := 0 for range pod.Spec.Containers { @@ -165,6 +170,10 @@ var _ = Describe("Podman generate kube", func() { err := yaml.Unmarshal(kube.Out.Contents(), pod) Expect(err).To(BeNil()) Expect(pod.Spec).To(HaveField("HostNetwork", false)) + enableServiceLinks := false + Expect(pod.Spec).To(HaveField("EnableServiceLinks", &enableServiceLinks)) + automountServiceAccountToken := false + Expect(pod.Spec).To(HaveField("AutomountServiceAccountToken", &automountServiceAccountToken)) numContainers := 0 for range pod.Spec.Containers { @@ -262,6 +271,39 @@ var _ = Describe("Podman generate kube", func() { Expect(numContainers).To(Equal(1)) }) + It("podman generate kube on pod with user namespace", func() { + u, err := user.Current() + Expect(err).To(BeNil()) + name := u.Name + if name == "root" { + name = "containers" + } + content, err := ioutil.ReadFile("/etc/subuid") + if err != nil { + Skip("cannot read /etc/subuid") + } + if !strings.Contains(string(content), name) { + Skip("cannot find mappings for the current user") + } + podSession := podmanTest.Podman([]string{"pod", "create", "--name", "testPod", "--userns=auto"}) + podSession.WaitWithDefaultTimeout() + Expect(podSession).Should(Exit(0)) + + session := podmanTest.Podman([]string{"create", "--name", "topcontainer", "--pod", "testPod", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", "testPod"}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + pod := new(v1.Pod) + err = yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + expected := false + Expect(pod.Spec).To(HaveField("HostUsers", &expected)) + }) + It("podman generate kube on pod with host network", func() { podSession := podmanTest.Podman([]string{"pod", "create", "--name", "testHostNetwork", "--network", "host"}) podSession.WaitWithDefaultTimeout() @@ -497,7 +539,7 @@ var _ = Describe("Podman generate kube", func() { Expect(podSession).Should(Exit(0)) ctr1Name := "ctr1" - ctr1Session := podmanTest.Podman([]string{"create", "--name", ctr1Name, "--pod", podName, "--memory", "10Mi", ALPINE, "top"}) + ctr1Session := podmanTest.Podman([]string{"create", "--name", ctr1Name, "--pod", podName, "--memory", "10M", ALPINE, "top"}) ctr1Session.WaitWithDefaultTimeout() Expect(ctr1Session).Should(Exit(0)) @@ -550,6 +592,11 @@ var _ = Describe("Podman generate kube", func() { It("podman generate kube on pod with ports", func() { podName := "test" + + lock4 := GetPortLock("4000") + defer lock4.Unlock() + lock5 := GetPortLock("5000") + defer lock5.Unlock() podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName, "-p", "4000:4000", "-p", "5000:5000"}) podSession.WaitWithDefaultTimeout() Expect(podSession).Should(Exit(0)) @@ -710,7 +757,7 @@ var _ = Describe("Podman generate kube", func() { pod := new(v1.Pod) err = yaml.Unmarshal(b, pod) Expect(err).To(BeNil()) - Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix+vol1, HaveSuffix("z"))) + Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1+":"+"z")) rm := podmanTest.Podman([]string{"pod", "rm", "-t", "0", "-f", "test1"}) rm.WaitWithDefaultTimeout() diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 45a2f1f86..f47abbc13 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -600,4 +600,75 @@ var _ = Describe("Podman generate systemd", func() { Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(" --label key={{someval}}")) }) + + It("podman generate systemd --env", func() { + session := podmanTest.RunTopContainer("test") + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "hoge=fuga", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Environment=foo=bar")) + Expect(session.OutputToString()).To(ContainSubstring("Environment=hoge=fuga")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "=bar", "-e", "hoge=fuga", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("invalid environment variable")) + + // Use -e/--env option with --new option + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "hoge=fuga", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Environment=foo=bar")) + Expect(session.OutputToString()).To(ContainSubstring("Environment=hoge=fuga")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "=fuga", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("invalid environment variable")) + + // Escape systemd arguments + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "BAR=my test", "-e", "USER=%a", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("\"BAR=my test\"")) + Expect(session.OutputToString()).To(ContainSubstring("USER=%%a")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "BAR=my test", "-e", "USER=%a", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("\"BAR=my test\"")) + Expect(session.OutputToString()).To(ContainSubstring("USER=%%a")) + + // Specify the environment variables without a value + os.Setenv("FOO1", "BAR1") + os.Setenv("FOO2", "BAR2") + os.Setenv("FOO3", "BAR3") + defer os.Unsetenv("FOO1") + defer os.Unsetenv("FOO2") + defer os.Unsetenv("FOO3") + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO1", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("BAR1")) + Expect(session.OutputToString()).NotTo(ContainSubstring("BAR2")) + Expect(session.OutputToString()).NotTo(ContainSubstring("BAR3")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO*", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("BAR1")) + Expect(session.OutputToString()).To(ContainSubstring("BAR2")) + Expect(session.OutputToString()).To(ContainSubstring("BAR3")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO*", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("BAR1")) + Expect(session.OutputToString()).To(ContainSubstring("BAR2")) + Expect(session.OutputToString()).To(ContainSubstring("BAR3")) + }) }) diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index ece1b519d..30a5c6482 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -157,7 +157,7 @@ var _ = Describe("Podman network connect and disconnect", func() { Expect(con.ErrorToString()).To(ContainSubstring(`"slirp4netns" is not supported: invalid network mode`)) }) - It("podman connect on a container that already is connected to the network should error", func() { + It("podman connect on a container that already is connected to the network should error after init", func() { netName := "aliasTest" + stringid.GenerateNonCryptoID() session := podmanTest.Podman([]string{"network", "create", netName}) session.WaitWithDefaultTimeout() @@ -177,7 +177,15 @@ var _ = Describe("Podman network connect and disconnect", func() { con := podmanTest.Podman([]string{"network", "connect", netName, "test"}) con.WaitWithDefaultTimeout() - Expect(con).Should(ExitWithError()) + Expect(con).Should(Exit(0)) + + init := podmanTest.Podman([]string{"init", "test"}) + init.WaitWithDefaultTimeout() + Expect(init).Should(Exit(0)) + + con2 := podmanTest.Podman([]string{"network", "connect", netName, "test"}) + con2.WaitWithDefaultTimeout() + Expect(con2).Should(ExitWithError()) }) It("podman network connect", func() { diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 1b4eefd45..baa74cb51 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -380,6 +380,9 @@ spec: restartPolicy: {{ .RestartPolicy }} hostname: {{ .Hostname }} hostNetwork: {{ .HostNetwork }} +{{ if .HostUsers }} + hostUsers: {{ .HostUsers }} +{{ end }} hostAliases: {{ range .HostAliases }} - hostnames: @@ -509,6 +512,9 @@ spec: volumes: {{ range . }} - name: {{ .Name }} + {{- if (eq .VolumeType "EmptyDir") }} + emptyDir: {} + {{- end }} {{- if (eq .VolumeType "HostPath") }} hostPath: path: {{ .HostPath.Path }} @@ -841,6 +847,7 @@ type Pod struct { RestartPolicy string Hostname string HostNetwork bool + HostUsers *bool HostAliases []HostAlias Ctrs []*Ctr InitCtrs []*Ctr @@ -965,6 +972,12 @@ func withHostNetwork() podOption { } } +func withHostUsers(val bool) podOption { + return func(pod *Pod) { + pod.HostUsers = &val + } +} + // Deployment describes the options a kube yaml can be configured at deployment level type Deployment struct { Name string @@ -1242,12 +1255,15 @@ type ConfigMap struct { Optional bool } +type EmptyDir struct{} + type Volume struct { VolumeType string Name string HostPath PersistentVolumeClaim ConfigMap + EmptyDir } // getHostPathVolume takes a type and a location for a HostPath @@ -1289,6 +1305,14 @@ func getConfigMapVolume(vName string, items []map[string]string, optional bool) } } +func getEmptyDirVolume() *Volume { + return &Volume{ + VolumeType: "EmptyDir", + Name: defaultVolName, + EmptyDir: EmptyDir{}, + } +} + type Env struct { Name string Value string @@ -2482,7 +2506,7 @@ spec: It("podman play kube test with network portbindings", func() { ip := "127.0.0.100" - port := "5000" + port := "8087" ctr := getCtr(withHostIP(ip, port), withImage(BB)) pod := getPod(withCtr(ctr)) @@ -2496,7 +2520,7 @@ spec: inspect := podmanTest.Podman([]string{"port", getCtrNameInPod(pod)}) inspect.WaitWithDefaultTimeout() Expect(inspect).Should(Exit(0)) - Expect(inspect.OutputToString()).To(Equal("5000/tcp -> 127.0.0.100:5000")) + Expect(inspect.OutputToString()).To(Equal("8087/tcp -> 127.0.0.100:8087")) }) It("podman play kube test with nonexistent empty HostPath type volume", func() { @@ -2762,6 +2786,43 @@ VOLUME %s`, ALPINE, hostPathDir+"/") Expect(kube).Should(Exit(0)) }) + It("podman play kube with emptyDir volume", func() { + podName := "test-pod" + ctrName1 := "vol-test-ctr" + ctrName2 := "vol-test-ctr-2" + ctr1 := getCtr(withVolumeMount("/test-emptydir", false), withImage(BB), withName(ctrName1)) + ctr2 := getCtr(withVolumeMount("/test-emptydir-2", false), withImage(BB), withName(ctrName2)) + pod := getPod(withPodName(podName), withVolume(getEmptyDirVolume()), withCtr(ctr1), withCtr(ctr2)) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + emptyDirCheck1 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName1, "ls", "/test-emptydir"}) + emptyDirCheck1.WaitWithDefaultTimeout() + Expect(emptyDirCheck1).Should(Exit(0)) + + emptyDirCheck2 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName2, "ls", "/test-emptydir-2"}) + emptyDirCheck2.WaitWithDefaultTimeout() + Expect(emptyDirCheck2).Should(Exit(0)) + + volList1 := podmanTest.Podman([]string{"volume", "ls", "-q"}) + volList1.WaitWithDefaultTimeout() + Expect(volList1).Should(Exit(0)) + Expect(volList1.OutputToString()).To(Equal(defaultVolName)) + + remove := podmanTest.Podman([]string{"pod", "rm", "-f", podName}) + remove.WaitWithDefaultTimeout() + Expect(remove).Should(Exit(0)) + + volList2 := podmanTest.Podman([]string{"volume", "ls", "-q"}) + volList2.WaitWithDefaultTimeout() + Expect(volList2).Should(Exit(0)) + Expect(volList2.OutputToString()).To(Equal("")) + }) + It("podman play kube applies labels to pods", func() { var numReplicas int32 = 5 expectedLabelKey := "key1" @@ -3732,8 +3793,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q Expect((inspect.InspectContainerToJSON()[0]).HostConfig.LogConfig.Tag).To(Equal("{{.ImageName}}")) }) - // Check that --userns=auto creates a user namespace - It("podman play kube --userns=auto", func() { + It("podman play kube using a user namespace", func() { u, err := user.Current() Expect(err).To(BeNil()) name := u.Name @@ -3780,6 +3840,26 @@ ENV OPENJ9_JAVA_OPTIONS=%q usernsInCtr.WaitWithDefaultTimeout() Expect(usernsInCtr).Should(Exit(0)) Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) + + // Now try with hostUsers in the pod spec + for _, hostUsers := range []bool{true, false} { + pod = getPod(withHostUsers(hostUsers)) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube = podmanTest.PodmanNoCache([]string{"play", "kube", "--replace", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + usernsInCtr = podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/proc/self/uid_map"}) + usernsInCtr.WaitWithDefaultTimeout() + Expect(usernsInCtr).Should(Exit(0)) + if hostUsers { + Expect(string(usernsInCtr.Out.Contents())).To(Equal(string(initialUsernsConfig))) + } else { + Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) + } + } }) // Check the block devices are exposed inside container diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 351317cc5..cefdee40a 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -118,4 +118,21 @@ var _ = Describe("Podman pod inspect", func() { Expect(inspectOut.OutputToString()).To(ContainSubstring(macAddr)) }) + + It("podman inspect two pods", func() { + _, ec, podid1 := podmanTest.CreatePod(nil) + Expect(ec).To(Equal(0)) + + _, ec, podid2 := podmanTest.CreatePod(nil) + Expect(ec).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"pod", "inspect", podid1, podid2}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(BeValidJSON()) + podData := inspect.InspectPodArrToJSON() + Expect(podData).To(HaveLen(2)) + Expect(podData[0]).To(HaveField("ID", podid1)) + Expect(podData[1]).To(HaveField("ID", podid2)) + }) }) diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index dd0070f54..9df884292 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -228,7 +228,7 @@ var _ = Describe("Podman restart", func() { Expect(beforeRestart.OutputToString()).To(Equal(afterRestart.OutputToString())) }) - It("podman restart all stoped containers with --all", func() { + It("podman restart all stopped containers with --all", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index e57eb3b26..19bb735ff 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -138,4 +138,20 @@ var _ = Describe("Podman run cpu", func() { result.WaitWithDefaultTimeout() Expect(result).To(ExitWithError()) }) + + It("podman run invalid cpu-rt-period with cgroupsv2", func() { + SkipIfCgroupV1("testing options that only work in cgroup v2") + result := podmanTest.Podman([]string{"run", "--rm", "--cpu-rt-period=5000", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(result.ErrorToString()).To(ContainSubstring("Realtime period not supported on cgroups V2 systems")) + }) + + It("podman run invalid cpu-rt-runtime with cgroupsv2", func() { + SkipIfCgroupV1("testing options that only work in cgroup v2") + result := podmanTest.Podman([]string{"run", "--rm", "--cpu-rt-runtime=5000", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(result.ErrorToString()).To(ContainSubstring("Realtime runtime not supported on cgroups V2 systems")) + }) }) diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index 8207f6d0b..12783cd29 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -105,6 +105,13 @@ var _ = Describe("Podman run with --ip flag", func() { result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) + // We need to set "no_proxy" in proxy environment + if env, found := os.LookupEnv("no_proxy"); found { + defer os.Setenv("no_proxy", env) + } else { + defer os.Unsetenv("no_proxy") + } + os.Setenv("no_proxy", ip) for retries := 20; retries > 0; retries-- { response, err := http.Get(fmt.Sprintf("http://%s", ip)) if err == nil && response.StatusCode == http.StatusOK { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 1f6b6fa3d..3b10fdff3 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -945,7 +945,7 @@ echo -n %s >%s session := podmanTest.Podman([]string{"run", "--rm", "--user=1234", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToString()).To(Equal("uid=1234(1234) gid=0(root)")) + Expect(session.OutputToString()).To(Equal("uid=1234(1234) gid=0(root) groups=0(root)")) }) It("podman run with user (integer, in /etc/passwd)", func() { @@ -966,14 +966,14 @@ echo -n %s >%s session := podmanTest.Podman([]string{"run", "--rm", "--user=mail:21", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToString()).To(Equal("uid=8(mail) gid=21(ftp)")) + Expect(session.OutputToString()).To(Equal("uid=8(mail) gid=21(ftp) groups=21(ftp)")) }) It("podman run with user:group (integer:groupname)", func() { session := podmanTest.Podman([]string{"run", "--rm", "--user=8:ftp", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToString()).To(Equal("uid=8(mail) gid=21(ftp)")) + Expect(session.OutputToString()).To(Equal("uid=8(mail) gid=21(ftp) groups=21(ftp)")) }) It("podman run with user, verify caps dropped", func() { @@ -984,6 +984,14 @@ echo -n %s >%s Expect("0000000000000000").To(Equal(capEff[1])) }) + It("podman run with user, verify group added", func() { + session := podmanTest.Podman([]string{"run", "--rm", "--user=1000:1000", ALPINE, "grep", "Groups:", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + groups := strings.Split(session.OutputToString(), " ")[1] + Expect("1000").To(Equal(groups)) + }) + It("podman run with attach stdin outputs container ID", func() { session := podmanTest.Podman([]string{"run", "--attach", "stdin", ALPINE, "printenv"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index f247b2dac..016f67bf6 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -8,6 +8,7 @@ import ( "strings" . "github.com/containers/podman/v4/test/utils" + "github.com/containers/storage" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -42,6 +43,33 @@ var _ = Describe("Podman UserNS support", func() { }) + // Note: Lot of tests for build with --userns=auto are already there in buildah + // but they are skipped in podman CI because bud tests are executed in rootfull + // environment ( where mappings for the `containers` user is not present in /etc/subuid ) + // causing them to skip hence this is a redundant test for sanity to make sure + // we don't break this feature for podman-remote. + It("podman build with --userns=auto", func() { + u, err := user.Current() + Expect(err).To(BeNil()) + name := u.Name + if name == "root" { + name = "containers" + } + content, err := ioutil.ReadFile("/etc/subuid") + if err != nil { + Skip("cannot read /etc/subuid") + } + if !strings.Contains(string(content), name) { + Skip("cannot find mappings for the current user") + } + session := podmanTest.Podman([]string{"build", "-f", "build/Containerfile.userns-auto", "-t", "test", "--userns=auto"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + // `1024` is the default size or length of the range of user IDs + // that is mapped between the two user namespaces by --userns=auto. + Expect(session.OutputToString()).To(ContainSubstring(fmt.Sprintf("%d", storage.AutoUserNsMinSize))) + }) + It("podman uidmapping and gidmapping", func() { session := podmanTest.Podman([]string{"run", "--uidmap=0:100:5000", "--gidmap=0:200:5000", "alpine", "echo", "hello"}) session.WaitWithDefaultTimeout() @@ -85,6 +113,16 @@ var _ = Describe("Podman UserNS support", func() { Expect(session).Should(Exit(0)) uid := fmt.Sprintf("%d", os.Geteuid()) Expect(session.OutputToString()).To(ContainSubstring(uid)) + + session = podmanTest.Podman([]string{"run", "--userns=keep-id:uid=10,gid=12", "alpine", "sh", "-c", "echo $(id -u):$(id -g)"}) + session.WaitWithDefaultTimeout() + if os.Geteuid() == 0 { + Expect(session).Should(Exit(125)) + return + } + + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("10:12")) }) It("podman --userns=keep-id check passwd", func() { @@ -157,6 +195,8 @@ var _ = Describe("Podman UserNS support", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) l := session.OutputToString() + // `1024` is the default size or length of the range of user IDs + // that is mapped between the two user namespaces by --userns=auto. Expect(l).To(ContainSubstring("1024")) m[l] = l } diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 94c363dd4..afb723a63 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -153,6 +153,9 @@ var _ = Describe("Podman save", func() { defer os.Setenv("GNUPGHOME", origGNUPGHOME) port := 5000 + portlock := GetPortLock(strconv.Itoa(port)) + defer portlock.Unlock() + session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", strings.Join([]string{strconv.Itoa(port), strconv.Itoa(port)}, ":"), REGISTRY_IMAGE}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index c6d5e6391..902f422bd 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -200,27 +200,33 @@ var _ = Describe("Podman secret", func() { list := podmanTest.Podman([]string{"secret", "ls", "--filter", fmt.Sprintf("name=%s", secret1)}) list.WaitWithDefaultTimeout() Expect(list).Should(Exit(0)) - Expect(list.OutputToStringArray()).To(HaveLen(2), ContainSubstring(secret1)) + Expect(list.OutputToStringArray()).To(HaveLen(2)) + Expect(list.OutputToStringArray()[1]).To(ContainSubstring(secret1)) list = podmanTest.Podman([]string{"secret", "ls", "--filter", fmt.Sprintf("name=%s", secret2)}) list.WaitWithDefaultTimeout() Expect(list).Should(Exit(0)) - Expect(list.OutputToStringArray()).To(HaveLen(2), ContainSubstring(secret2)) + Expect(list.OutputToStringArray()).To(HaveLen(2)) + Expect(list.OutputToStringArray()[1]).To(ContainSubstring(secret2)) list = podmanTest.Podman([]string{"secret", "ls", "--filter", fmt.Sprintf("id=%s", secrID1)}) list.WaitWithDefaultTimeout() Expect(list).Should(Exit(0)) - Expect(list.OutputToStringArray()).To(HaveLen(2), ContainSubstring(secrID1)) + Expect(list.OutputToStringArray()).To(HaveLen(2)) + Expect(list.OutputToStringArray()[1]).To(ContainSubstring(secrID1)) list = podmanTest.Podman([]string{"secret", "ls", "--filter", fmt.Sprintf("id=%s", secrID2)}) list.WaitWithDefaultTimeout() Expect(list).Should(Exit(0)) - Expect(list.OutputToStringArray()).To(HaveLen(2), ContainSubstring(secrID2)) + Expect(list.OutputToStringArray()).To(HaveLen(2)) + Expect(list.OutputToStringArray()[1]).To(ContainSubstring(secrID2)) list = podmanTest.Podman([]string{"secret", "ls", "--filter", fmt.Sprintf("name=%s,name=%s", secret1, secret2)}) list.WaitWithDefaultTimeout() Expect(list).Should(Exit(0)) - Expect(list.OutputToStringArray()).To(HaveLen(3), ContainSubstring(secret1), ContainSubstring(secret2)) + Expect(list.OutputToStringArray()).To(HaveLen(3)) + Expect(list.OutputToString()).To(ContainSubstring(secret1)) + Expect(list.OutputToString()).To(ContainSubstring(secret2)) }) It("podman secret ls with Go template", func() { diff --git a/test/e2e/update_test.go b/test/e2e/update_test.go new file mode 100644 index 000000000..97dadd04c --- /dev/null +++ b/test/e2e/update_test.go @@ -0,0 +1,200 @@ +package integration + +import ( + "github.com/containers/common/pkg/cgroupv2" + . "github.com/containers/podman/v4/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman update", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + Expect(err).To(BeNil()) + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman update container all options v1", func() { + SkipIfCgroupV2("testing flags that only work in cgroup v1") + SkipIfRootless("many of these handlers are not enabled while rootless in CI") + session := podmanTest.Podman([]string{"run", "-dt", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + commonArgs := []string{ + "update", + "--cpus", "5", + "--cpuset-cpus", "0", + "--cpu-shares", "123", + "--cpuset-mems", "0", + "--memory", "1G", + "--memory-swap", "2G", + "--memory-reservation", "2G", + "--memory-swappiness", "50", ctrID} + + session = podmanTest.Podman(commonArgs) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + // checking cpu quota from --cpus + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + + // checking cpuset-cpus + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.cpus"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking cpuset-mems + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.mems"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking memory limit + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + // checking memory-swap + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("2147483648")) + + // checking cpu-shares + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("123")) + + }) + + It("podman update container all options v2", func() { + SkipIfCgroupV1("testing flags that only work in cgroup v2") + SkipIfRootless("many of these handlers are not enabled while rootless in CI") + session := podmanTest.Podman([]string{"run", "-dt", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + commonArgs := []string{ + "update", + "--cpus", "5", + "--cpuset-cpus", "0", + "--cpu-shares", "123", + "--cpuset-mems", "0", + "--memory", "1G", + "--memory-swap", "2G", + "--memory-reservation", "2G", + "--blkio-weight", "123", + "--device-read-bps", "/dev/zero:10mb", + "--device-write-bps", "/dev/zero:10mb", + "--device-read-iops", "/dev/zero:1000", + "--device-write-iops", "/dev/zero:1000", + ctrID} + + session = podmanTest.Podman(commonArgs) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID = session.OutputToString() + + // checking cpu quota and period + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + + // checking blkio weight + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/io.bfq.weight"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("123")) + + // checking device-read/write-bps/iops + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/io.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("rbps=10485760 wbps=10485760 riops=1000 wiops=1000")) + + // checking cpuset-cpus + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.cpus"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking cpuset-mems + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.mems"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking memory limit + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + // checking memory-swap + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory.swap.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + // checking cpu-shares + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.weight"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("5")) + }) + + It("podman update keep original resources if not overridden", func() { + SkipIfRootless("many of these handlers are not enabled while rootless in CI") + session := podmanTest.Podman([]string{"run", "-dt", "--cpus", "5", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{ + "update", + "--memory", "1G", + session.OutputToString(), + }) + + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + if v2, _ := cgroupv2.Enabled(); v2 { + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) + } else { + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) + } + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + }) +}) diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 584511388..e3302bec3 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -29,6 +29,12 @@ function setup() { local built=$(expr "$output" : ".*Built: \+\(.*\)" | head -n1) local built_t=$(date --date="$built" +%s) assert "$built_t" -gt 1546300800 "Preposterous 'Built' time in podman version" + + run_podman -v + is "$output" "podman.*version \+" "'Version line' in output" + + run_podman --config foobar version + is "$output" ".*The --config flag is ignored by Podman. Exists for Docker compatibility\+" "verify warning for --config option" } @test "podman info" { @@ -190,7 +196,9 @@ See 'podman version --help'" "podman version --remote" run_podman --log-level=error info run_podman --log-level=fatal info run_podman --log-level=panic info + # docker compat run_podman --debug info + run_podman -D info run_podman 1 --debug --log-level=panic info is "$output" "Setting --log-level and --debug is not allowed" } diff --git a/test/system/090-events.bats b/test/system/090-events.bats index ceb53ae73..cd1bf327b 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -64,7 +64,7 @@ load helpers run_podman --events-backend=file tag $IMAGE $tag run_podman --events-backend=file untag $IMAGE $tag run_podman --events-backend=file tag $IMAGE $tag - run_podman --events-backend=file rmi $tag + run_podman --events-backend=file rmi -f $imageID run_podman --events-backend=file events --stream=false --filter type=image --since $t0 is "$output" ".*image push $imageID dir:$pushedDir @@ -74,7 +74,8 @@ load helpers .*image tag $imageID $tag .*image untag $imageID $tag:latest .*image tag $imageID $tag -.*image remove $imageID $tag.*" \ +.*image untag $imageID $tag:latest +.*image remove $imageID $imageID" \ "podman events" } @@ -193,3 +194,22 @@ EOF is "$(wc -l <$eventsFile)" "$(wc -l <<<$output)" "all events are returned" is "${lines[-2]}" ".* log-rotation $eventsFile" } + +# Prior to #15633, container labels would not appear in 'die' log events +@test "events - labels included in container die" { + skip_if_remote "remote does not support --events-backend" + local cname=c$(random_string 15) + local lname=l$(random_string 10) + local lvalue="v$(random_string 10) $(random_string 5)" + + run_podman 17 --events-backend=file run --rm \ + --name=$cname \ + --label=$lname="$lvalue" \ + $IMAGE sh -c 'exit 17' + run_podman --events-backend=file events \ + --filter=container=$cname \ + --filter=status=died \ + --stream=false \ + --format="{{.Attributes.$lname}}" + assert "$output" = "$lvalue" "podman-events output includes container label" +} diff --git a/test/system/150-login.bats b/test/system/150-login.bats index b57bb44ab..b85007f0b 100644 --- a/test/system/150-login.bats +++ b/test/system/150-login.bats @@ -122,7 +122,7 @@ function setup() { --password-stdin \ $registry <<< "x${PODMAN_LOGIN_PASS}" is "$output" \ - "Error: error logging into \"$registry\": invalid username/password" \ + "Error: logging into \"$registry\": invalid username/password" \ 'output from podman login' } diff --git a/test/system/220-healthcheck.bats b/test/system/220-healthcheck.bats index c502ad669..00ec1dd79 100644 --- a/test/system/220-healthcheck.bats +++ b/test/system/220-healthcheck.bats @@ -20,44 +20,8 @@ function _check_health { done } - @test "podman healthcheck" { - # Create an image with a healthcheck script; said script will - # pass until the file /uh-oh gets created (by us, via exec) - cat >${PODMAN_TMPDIR}/healthcheck <<EOF -#!/bin/sh - -if test -e /uh-oh; then - echo "Uh-oh on stdout!" - echo "Uh-oh on stderr!" >&2 - exit 1 -else - echo "Life is Good on stdout" - echo "Life is Good on stderr" >&2 - exit 0 -fi -EOF - - cat >${PODMAN_TMPDIR}/entrypoint <<EOF -#!/bin/sh - -while :; do - sleep 1 -done -EOF - - cat >${PODMAN_TMPDIR}/Containerfile <<EOF -FROM $IMAGE - -COPY healthcheck /healthcheck -COPY entrypoint /entrypoint - -RUN chmod 755 /healthcheck /entrypoint - -CMD ["/entrypoint"] -EOF - - run_podman build -t healthcheck_i ${PODMAN_TMPDIR} + _build_health_check_image healthcheck_i # Run that healthcheck image. run_podman run -d --name healthcheck_c \ @@ -66,6 +30,9 @@ EOF --health-retries 3 \ healthcheck_i + run_podman inspect healthcheck_c --format "{{.Config.HealthcheckOnFailureAction}}" + is "$output" "none" "default on-failure action is none" + # We can't check for 'starting' because a 1-second interval is too # short; it could run healthcheck before we get to our first check. # @@ -109,4 +76,59 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\" run_podman rmi healthcheck_i } +@test "podman healthcheck --health-on-failure" { + run_podman 125 create --health-on-failure=kill $IMAGE + is "$output" "Error: cannot set on-failure action to kill without a health check" + + ctr="healthcheck_c" + img="healthcheck_i" + + for policy in none kill restart stop;do + if [[ $policy == "none" ]];then + # Do not remove the /uh-oh file for `none` as we want to + # demonstrate that no action was taken + _build_health_check_image $img + else + _build_health_check_image $img cleanfile + fi + + # Run that healthcheck image. + run_podman run -d --name $ctr \ + --health-cmd /healthcheck \ + --health-on-failure=$policy \ + $img + + # healthcheck should succeed + run_podman healthcheck run $ctr + + # Now cause the healthcheck to fail + run_podman exec $ctr touch /uh-oh + + # healthcheck should now fail, with exit status 1 and 'unhealthy' output + run_podman 1 healthcheck run $ctr + # FIXME: #15691 - `healthcheck run` may emit an error log that the timer already exists + is "$output" ".*unhealthy.*" "output from 'podman healthcheck run'" + + run_podman inspect $ctr --format "{{.State.Status}} {{.Config.HealthcheckOnFailureAction}}" + if [[ $policy == "restart" ]];then + # Container has been restarted and health check works again + is "$output" "running $policy" "container has been restarted" + run_podman healthcheck run $ctr + elif [[ $policy == "none" ]];then + # Container is still running and health check still broken + is "$output" "running $policy" "container continued running" + run_podman 1 healthcheck run $ctr + # FIXME: #15691 - `healthcheck run` may emit an error log that the timer already exists + is "$output" ".*unhealthy.*" "output from 'podman healthcheck run'" + else + # kill and stop yield the container into a non-running state + is "$output" ".* $policy" "container was stopped/killed" + assert "$output" != "running $policy" + fi + + run_podman rm -f -t0 $ctr + run_podman rmi -f $img + done +} + # vim: filetype=sh diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats index 9a91501dd..3f6296b36 100644 --- a/test/system/250-systemd.bats +++ b/test/system/250-systemd.bats @@ -33,7 +33,11 @@ function teardown() { # Helper to start a systemd service running a container function service_setup() { - run_podman generate systemd --new $cname + run_podman generate systemd \ + -e http_proxy -e HTTP_PROXY \ + -e https_proxy -e HTTPS_PROXY \ + -e no_proxy -e NO_PROXY \ + --new $cname echo "$output" > "$UNIT_FILE" run_podman rm $cname @@ -73,6 +77,11 @@ function service_cleanup() { # These tests can fail in dev. environment because of SELinux. # quick fix: chcon -t container_runtime_exec_t ./bin/podman @test "podman generate - systemd - basic" { + # Flakes with "ActiveState=failed (expected =inactive)" + if is_ubuntu; then + skip "FIXME: 2022-09-01: requires conmon-2.1.4, ubuntu has 2.1.3" + fi + cname=$(random_string) # See #7407 for --pull=always. run_podman create --pull=always --name $cname --label "io.containers.autoupdate=registry" $IMAGE \ @@ -295,25 +304,68 @@ LISTEN_FDNAMES=listen_fdnames" | sort) run_podman network rm -f $netname } -@test "podman-kube@.service template" { - # If running from a podman source directory, build and use the source - # version of the play-kube-@ unit file - unit_name="podman-kube@.service" - unit_file="contrib/systemd/system/${unit_name}" - if [[ -e ${unit_file}.in ]]; then - echo "# [Building & using $unit_name from source]" >&3 - # Force regenerating unit file (existing one may have /usr/bin path) - rm -f $unit_file - BINDIR=$(dirname $PODMAN) make $unit_file - cp $unit_file $UNIT_DIR/$unit_name - fi +@test "podman create --health-on-failure=kill" { + img="healthcheck_i" + _build_health_check_image $img + + cname=$(random_string) + run_podman create --name $cname \ + --health-cmd /healthcheck \ + --health-on-failure=kill \ + --restart=on-failure \ + $img + + # run container in systemd unit + service_setup + + run_podman container inspect $cname --format "{{.ID}}" + oldID="$output" + + run_podman healthcheck run $cname + + # Now cause the healthcheck to fail + run_podman exec $cname touch /uh-oh + + # healthcheck should now fail, with exit status 1 and 'unhealthy' output + run_podman 1 healthcheck run $cname + is "$output" "unhealthy" "output from 'podman healthcheck run'" + # What is expected to happen now: + # 1) The container gets killed as the health check has failed + # 2) Systemd restarts the service as the restart policy is set to "on-failure" + # 3) The /uh-oh file is gone and $cname has another ID + + # Wait at most 10 seconds for the service to be restarted + local timeout=10 + while [[ $timeout -gt 1 ]]; do + run_podman '?' container inspect $cname + if [[ $status == 0 ]]; then + if [[ "$output" != "$oldID" ]]; then + break + fi + fi + sleep 1 + let timeout=$timeout-1 + done + + run_podman healthcheck run $cname + + # stop systemd container + service_cleanup + run_podman rmi -f $img +} + +@test "podman-kube@.service template" { + install_kube_template # Create the YAMl file yaml_source="$PODMAN_TMPDIR/test.yaml" cat >$yaml_source <<EOF apiVersion: v1 kind: Pod metadata: + annotations: + io.containers.autoupdate: "local" + io.containers.autoupdate/b: "registry" labels: app: test name: test_pod @@ -322,8 +374,11 @@ spec: - command: - top image: $IMAGE - name: test - resources: {} + name: a + - command: + - top + image: $IMAGE + name: b EOF # Dispatch the YAML file @@ -344,6 +399,12 @@ EOF run_podman 125 container rm $service_container is "$output" "Error: container .* is the service container of pod(s) .* and cannot be removed without removing the pod(s)" + # Add a simple `auto-update --dry-run` test here to avoid too much redundancy + # with 255-auto-update.bats + run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}" + is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,local.*" "global auto-update policy gets applied" + is "$output" ".*$service_name,.* (test_pod-b),$IMAGE,false,registry.*" "container-specified auto-update policy gets applied" + # Kill the pod and make sure the service is not running. # The restart policy is set to "never" since there is no # design yet for propagating exit codes up to the service diff --git a/test/system/255-auto-update.bats b/test/system/255-auto-update.bats index c6f9600b6..a106914fe 100644 --- a/test/system/255-auto-update.bats +++ b/test/system/255-auto-update.bats @@ -266,8 +266,6 @@ EOF # Generate a healthy image that will run correctly. run_podman build -t quay.io/libpod/$image -f $dockerfile1 - podman image inspect --format "{{.ID}}" $image - oldID="$output" generate_service $image local /runme --sdnotify=container noTag _wait_service_ready container-$cname.service @@ -277,7 +275,7 @@ EOF # Generate an unhealthy image that will fail. run_podman build -t quay.io/libpod/$image -f $dockerfile2 - podman image inspect --format "{{.ID}}" $image + run_podman image inspect --format "{{.ID}}" $image newID="$output" run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}" @@ -375,6 +373,12 @@ After=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/podman auto-update +Environment="http_proxy=${http_proxy}" +Environment="HTTP_PROXY=${HTTP_PROXY}" +Environment="https_proxy=${https_proxy}" +Environment="HTTPS_PROXY=${HTTPS_PROXY}" +Environment="no_proxy=${no_proxy}" +Environment="NO_PROXY=${NO_PROXY}" [Install] WantedBy=default.target @@ -409,4 +413,97 @@ EOF _confirm_update $cname $ori_image } +@test "podman-kube@.service template with rollback" { + # sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just + # assume that we work only with crun, nothing else. + # [copied from 260-sdnotify.bats] + runtime=$(podman_runtime) + if [[ "$runtime" != "crun" ]]; then + skip "this test only works with crun, not $runtime" + fi + + install_kube_template + + dockerfile1=$PODMAN_TMPDIR/Dockerfile.1 + cat >$dockerfile1 <<EOF +FROM quay.io/libpod/fedora:31 +RUN echo -e "#!/bin/sh\n\ +printenv NOTIFY_SOCKET; echo READY; systemd-notify --ready;\n\ +trap 'echo Received SIGTERM, finishing; exit' SIGTERM; echo WAITING; while :; do sleep 0.1; done" \ +>> /runme +RUN chmod +x /runme +EOF + + dockerfile2=$PODMAN_TMPDIR/Dockerfile.2 + cat >$dockerfile2 <<EOF +FROM quay.io/libpod/fedora:31 +RUN echo -e "#!/bin/sh\n\ +exit 1" >> /runme +RUN chmod +x /runme +EOF + local_image=localhost/image:$(random_string 10) + + # Generate a healthy image that will run correctly. + run_podman build -t $local_image -f $dockerfile1 + run_podman image inspect --format "{{.ID}}" $local_image + oldID="$output" + + # Create the YAMl file + yaml_source="$PODMAN_TMPDIR/test.yaml" + cat >$yaml_source <<EOF +apiVersion: v1 +kind: Pod +metadata: + annotations: + io.containers.autoupdate: "registry" + io.containers.autoupdate/b: "local" + io.containers.sdnotify/b: "container" + labels: + app: test + name: test_pod +spec: + containers: + - command: + - top + image: $IMAGE + name: a + - command: + - /runme + image: $local_image + name: b +EOF + + # Dispatch the YAML file + service_name="podman-kube@$(systemd-escape $yaml_source).service" + systemctl start $service_name + systemctl is-active $service_name + + # Make sure the containers are properly configured + run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}" + is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,registry.*" "global auto-update policy gets applied" + is "$output" ".*$service_name,.* (test_pod-b),$local_image,false,local.*" "container-specified auto-update policy gets applied" + + # Generate a broken image that will fail. + run_podman build -t $local_image -f $dockerfile2 + run_podman image inspect --format "{{.ID}}" $local_image + newID="$output" + + assert "$oldID" != "$newID" "broken image really is a new one" + + # Make sure container b sees the new image + run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}" + is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,registry.*" "global auto-update policy gets applied" + is "$output" ".*$service_name,.* (test_pod-b),$local_image,pending,local.*" "container b sees the new image" + + # Now update and check for the rollback + run_podman auto-update --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}" + is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,rolled back,registry.*" "container a was rolled back as the update of b failed" + is "$output" ".*$service_name,.* (test_pod-b),$local_image,rolled back,local.*" "container b was rolled back as its update has failed" + + # Clean up + systemctl stop $service_name + run_podman rmi -f $(pause_image) $local_image $newID $oldID + rm -f $UNIT_DIR/$unit_name +} + # vim: filetype=sh diff --git a/test/system/280-update.bats b/test/system/280-update.bats new file mode 100644 index 000000000..c7037c286 --- /dev/null +++ b/test/system/280-update.bats @@ -0,0 +1,130 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Tests for podman update +# + +load helpers + +LOOPDEVICE= + +function teardown() { + if [[ -n "$LOOPDEVICE" ]]; then + losetup -d $LOOPDEVICE + LOOPDEVICE= + fi + basic_teardown +} + + +@test "podman update - test all options" { + + local cgv=1 + if is_cgroupsv2; then + cgv=2; + fi + + # Need a block device for blkio-weight-device testing + local pass_loop_device= + if ! is_rootless; then + if is_cgroupsv2; then + lofile=${PODMAN_TMPDIR}/disk.img + fallocate -l 1k ${lofile} + LOOPDEVICE=$(losetup --show -f $lofile) + pass_loop_device="--device $LOOPDEVICE" + + # Get maj:min (tr needed because losetup seems to use %2d) + lomajmin=$(losetup -l --noheadings --output MAJ:MIN $LOOPDEVICE | tr -d ' ') + fi + fi + + # Shortcuts to make the table narrower + local -a gig=(0 1073741824 2147483648 3221225472) + local devicemax="1:5 rbps=10485760 wbps=31457280 riops=2000 wiops=4000" + local mm=memory/memory + + # Format: + # --<option> = <value> | rootless? | check: cgroups v1 | check: cgroups v2 + # + # Requires very wide window to read. Sorry. + # + # FIXMEs: + # cpu-rt-period (cgv1 only, on cpu/cpu.rt_period_us) works on RHEL8 but not on Ubuntu + # cpu-rt-runtime (cgv1 only, on cpu/cpu.rt_runtime_us) fails: error setting cgroup config for procHooks ... + tests=" +cpu-shares = 512 | - | cpu/cpu.shares = 512 | cpu.weight = 20 +cpus = 5 | - | cpu/cpu.cfs_quota_us = 500000 | cpu.max = 500000 100000 +cpuset-cpus = 0 | - | cpuset/cpuset.cpus = 0 | cpuset.cpus = 0 +cpuset-mems = 0 | - | cpuset/cpuset.mems = 0 | cpuset.mems = 0 + +memory = 1G | 2 | $mm.limit_in_bytes = ${gig[1]} | memory.max = ${gig[1]} +memory-swap = 3G | 2 | $mm.memsw.limit_in_bytes = ${gig[3]} | memory.swap.max = ${gig[2]} +memory-reservation = 400M | 2 | $mm.soft_limit_in_bytes = 419430400 | memory.low = 419430400 + +blkio-weight = 321 | - | - | io.bfq.weight = default 321 $lomajmin 98 +blkio-weight-device = $LOOPDEVICE:98 | - | - | io.bfq.weight = default 321 $lomajmin 98 + +device-read-bps = /dev/zero:10mb | - | - | io.max = $devicemax +device-read-iops = /dev/zero:2000 | - | - | io.max = $devicemax +device-write-bps = /dev/zero:30mb | - | - | io.max = $devicemax +device-write-iops = /dev/zero:4000 | - | - | io.max = $devicemax +" + + # Run a container + run_podman run ${pass_loop_device} -d $IMAGE sleep infinity + cid="$output" + + # Pass 1: read the table above, gather up the options applicable + # to this test environment (root/rootless, cgroups v1/v2) + local -a opts + local -A check + while read opt works_rootless cgv1 cgv2; do + if is_rootless; then + local skipping="skipping --$opt : does not work rootless" + if [[ $works_rootless = '-' ]]; then + echo "[ $skipping ]" + continue + fi + if [[ ! $works_rootless =~ $cgv ]]; then + echo "[ $skipping on cgroups v$cgv ]" + continue + fi + fi + + tuple=$cgv1 + if is_cgroupsv2; then + tuple=$cgv2 + fi + if [[ $tuple = '-' ]]; then + echo "[ skipping --$opt : N/A on cgroups v$cgv ]" + continue + fi + + # OK: setting is applicable. Preserve it. (First removing whitespace) + opt=${opt// /} + opts+=("--$opt") + check["--$opt"]=$tuple + done < <(parse_table "$tests") + + # Now do the update in one fell swoop + run_podman update "${opts[@]}" $cid + + # ...and check one by one + for opt in "${opts[@]}"; do + read path op expect <<<"${check[$opt]}" + run_podman exec $cid cat /sys/fs/cgroup/$path + + # Magic echo of unquoted-output converts newlines to spaces; + # important for otherwise multiline blkio file. + updated="$(echo $output)" + assert "$updated" $op "$expect" "$opt ($path)" + done + + # Clean up + run_podman rm -f -t0 $cid + if [[ -n "$LOOPDEVICE" ]]; then + losetup -d $LOOPDEVICE + LOOPDEVICE= + fi +} + +# vim: filetype=sh diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 5da7523f3..862bc285c 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -61,9 +61,9 @@ load helpers is "$output" "$random_2" "curl 127.0.0.1:/index2.txt" # Verify http contents: wget from a second container - run_podman run --rm --net=host $IMAGE wget -qO - $SERVER/index.txt + run_podman run --rm --net=host --http-proxy=false $IMAGE wget -qO - $SERVER/index.txt is "$output" "$random_1" "podman wget /index.txt" - run_podman run --rm --net=host $IMAGE wget -qO - $SERVER/index2.txt + run_podman run --rm --net=host --http-proxy=false $IMAGE wget -qO - $SERVER/index2.txt is "$output" "$random_2" "podman wget /index2.txt" # Tests #4889 - two-argument form of "podman ports" was broken diff --git a/test/system/710-kube.bats b/test/system/710-kube.bats index 58e42148a..c446ff65f 100644 --- a/test/system/710-kube.bats +++ b/test/system/710-kube.bats @@ -78,11 +78,6 @@ status | = | null assert "$actual" $op "$expect" ".$key" done < <(parse_table "$expect") - if ! is_remote; then - count=$(egrep -c "$kubernetes_63" <<<"$output") - assert "$count" = 1 "1 instance of the Kubernetes-63-char warning" - fi - run_podman rm $cname } @@ -157,12 +152,6 @@ status | = | {} assert "$actual" $op "$expect" ".$key" done < <(parse_table "$expect") - # Why 4? Maybe two for each container? - if ! is_remote; then - count=$(egrep -c "$kubernetes_63" <<<"$output") - assert "$count" = 4 "instances of the Kubernetes-63-char warning" - fi - run_podman rm $cname1 $cname2 run_podman pod rm $pname run_podman rmi $(pause_image) diff --git a/test/system/helpers.bash b/test/system/helpers.bash index f2eb3016c..b0d4b526a 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -894,5 +894,59 @@ function _podman_commands() { awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' <<<"$output" | grep . } +############################### +# _build_health_check_image # Builds a container image with a configured health check +############################### +# +# The health check will fail once the /uh-oh file exists. +# +# First argument is the desired name of the image +# Second argument, if present and non-null, forces removal of the /uh-oh file once the check failed; this way the container can be restarted +# + +function _build_health_check_image { + local imagename="$1" + local cleanfile="" + + if [[ ! -z "$2" ]]; then + cleanfile="rm -f /uh-oh" + fi + # Create an image with a healthcheck script; said script will + # pass until the file /uh-oh gets created (by us, via exec) + cat >${PODMAN_TMPDIR}/healthcheck <<EOF +#!/bin/sh + +if test -e /uh-oh; then + echo "Uh-oh on stdout!" + echo "Uh-oh on stderr!" >&2 + ${cleanfile} + exit 1 +else + echo "Life is Good on stdout" + echo "Life is Good on stderr" >&2 + exit 0 +fi +EOF + + cat >${PODMAN_TMPDIR}/entrypoint <<EOF +#!/bin/sh + +trap 'echo Received SIGTERM, finishing; exit' SIGTERM; echo WAITING; while :; do sleep 0.1; done +EOF + + cat >${PODMAN_TMPDIR}/Containerfile <<EOF +FROM $IMAGE + +COPY healthcheck /healthcheck +COPY entrypoint /entrypoint + +RUN chmod 755 /healthcheck /entrypoint + +CMD ["/entrypoint"] +EOF + + run_podman build -t $imagename ${PODMAN_TMPDIR} +} + # END miscellaneous tools ############################################################################### diff --git a/test/system/helpers.systemd.bash b/test/system/helpers.systemd.bash index d9abc087d..afbab6e08 100644 --- a/test/system/helpers.systemd.bash +++ b/test/system/helpers.systemd.bash @@ -32,3 +32,17 @@ journalctl() { systemd-run() { command systemd-run $_DASHUSER "$@"; } + +install_kube_template() { + # If running from a podman source directory, build and use the source + # version of the play-kube-@ unit file + unit_name="podman-kube@.service" + unit_file="contrib/systemd/system/${unit_name}" + if [[ -e ${unit_file}.in ]]; then + echo "# [Building & using $unit_name from source]" >&3 + # Force regenerating unit file (existing one may have /usr/bin path) + rm -f $unit_file + BINDIR=$(dirname $PODMAN) make $unit_file + cp $unit_file $UNIT_DIR/$unit_name + fi +} |