#!/usr/bin/env bats -*- bats -*- # # Tests for systemd sdnotify # load helpers # Shared throughout this module: PID of socat process, and path to its log _SOCAT_PID= _SOCAT_LOG= function setup() { skip_if_remote "systemd tests are meaningless over remote" # Skip if systemd is not running systemctl list-units &>/dev/null || skip "systemd not available" # sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just # assume that we work only with crun, nothing else. runtime=$(podman_runtime) if [[ "$runtime" != "crun" ]]; then skip "this test only works with crun, not $runtime" fi basic_setup } function teardown() { unset NOTIFY_SOCKET _stop_socat basic_teardown } ############################################################################### # BEGIN helpers # Run socat process on a socket, logging to well-known path. Each received # packet is logged with a newline appended, for ease of parsing the log file. function _start_socat() { _SOCAT_LOG="$PODMAN_TMPDIR/socat.log" # Reset socat logfile to empty rm -f $_SOCAT_LOG touch $_SOCAT_LOG # Execute in subshell so we can close fd3 (which BATS uses). # This is a superstitious ritual to try to avoid leaving processes behind, # and thus prevent CI hangs. (exec socat unix-recvfrom:"$NOTIFY_SOCKET",fork \ system:"(cat;echo) >> $_SOCAT_LOG" 3>&-) & _SOCAT_PID=$! } # Stop the socat background process and clean up logs function _stop_socat() { if [[ -n "$_SOCAT_PID" ]]; then # Kill all child processes, then the process itself. # This is a superstitious incantation to avoid leaving processes behind. # The '|| true' is because only f35 leaves behind socat processes; # f33 (and perhaps others?) behave nicely. ARGH! pkill -P $_SOCAT_PID || true kill $_SOCAT_PID fi _SOCAT_PID= if [[ -n "$_SOCAT_LOG" ]]; then rm -f $_SOCAT_LOG fi _SOCAT_LOG= } # Check that MAINPID=xxxxx points to a running conmon process function _assert_mainpid_is_conmon() { local mainpid=$(expr "$1" : ".*MAINPID=\([0-9]\+\)") test -n "$mainpid" || die "Could not parse '$1' as 'MAINPID=nnnn'" test -d /proc/$mainpid || die "sdnotify MAINPID=$mainpid - but /proc/$mainpid does not exist" # e.g. /proc/12345/exe -> /usr/bin/conmon local mainpid_bin=$(readlink /proc/$mainpid/exe) is "$mainpid_bin" ".*/conmon" "sdnotify MAINPID=$mainpid is conmon process" } # END helpers ############################################################################### # BEGIN tests themselves @test "sdnotify : ignore" { export NOTIFY_SOCKET=$PODMAN_TMPDIR/ignore.sock _start_socat run_podman create --rm --sdnotify=ignore $IMAGE printenv NOTIFY_SOCKET cid="$output" run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" is "$output" "ignore " "NOTIFY_SOCKET is not set with 'ignore' mode" run_podman 1 start --attach $cid is "$output" "" "\$NOTIFY_SOCKET in container" is "$(< $_SOCAT_LOG)" "" "nothing received on socket" _stop_socat } @test "sdnotify : conmon" { export NOTIFY_SOCKET=$PODMAN_TMPDIR/conmon.sock _start_socat run_podman run -d --name sdnotify_conmon_c \ --sdnotify=conmon \ $IMAGE \ sh -c 'printenv NOTIFY_SOCKET;echo READY;while ! test -f /stop;do sleep 0.1;done' cid="$output" wait_for_ready $cid run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" is "$output" "conmon $NOTIFY_SOCKET" run_podman container inspect sdnotify_conmon_c --format "{{.State.ConmonPid}}" mainPID="$output" run_podman logs sdnotify_conmon_c is "$output" "READY" "\$NOTIFY_SOCKET in container" # The 'echo's help us debug failed runs wait_for_file $_SOCAT_LOG run cat $_SOCAT_LOG echo "socat log:" echo "$output" is "$output" "MAINPID=$mainPID READY=1" "sdnotify sent MAINPID and READY" _assert_mainpid_is_conmon "$output" # Done. Stop container, clean up. run_podman exec $cid touch /stop run_podman wait $cid run_podman rm $cid _stop_socat } # These tests can fail in dev. environment because of SELinux. # quick fix: chcon -t container_runtime_exec_t ./bin/podman @test "sdnotify : container" { # Pull our systemd image. Retry in case of flakes. run_podman pull $SYSTEMD_IMAGE || \ run_podman pull $SYSTEMD_IMAGE || \ run_podman pull $SYSTEMD_IMAGE export NOTIFY_SOCKET=$PODMAN_TMPDIR/container.sock _start_socat run_podman run -d --sdnotify=container $SYSTEMD_IMAGE \ sh -c 'printenv NOTIFY_SOCKET; echo READY; while ! test -f /stop;do sleep 0.1;done;systemd-notify --ready' cid="$output" wait_for_ready $cid run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" is "$output" "container $NOTIFY_SOCKET" run_podman logs $cid is "${lines[0]}" "/run/notify/notify.sock" "NOTIFY_SOCKET is passed to container" run_podman container inspect $cid --format "{{.State.ConmonPid}}" mainPID="$output" # With container, READY=1 isn't necessarily the last message received; # just look for it anywhere in received messages run cat $_SOCAT_LOG # The 'echo's help us debug failed runs echo "socat log:" echo "$output" is "$output" "MAINPID=$mainPID" "Container is not ready yet, so we only know the main PID" # Done. Stop container, clean up. run_podman exec $cid touch /stop run_podman wait $cid wait_for_file $_SOCAT_LOG run cat $_SOCAT_LOG echo "socat log:" echo "$output" is "$output" "MAINPID=$mainPID READY=1" run_podman rm $cid _stop_socat } @test "sdnotify : play kube - no policies" { # Create the YAMl file yaml_source="$PODMAN_TMPDIR/test.yaml" cat >$yaml_source <$yaml_source </dev/null & # Wait for both containers to be running for i in $(seq 1 20); do run_podman "?" container wait $container_a $container_b --condition="running" if [[ $status == 0 ]]; then break fi sleep 0.5 # Just for debugging run_podman ps -a done if [[ $status != 0 ]]; then die "container $container_a and/or $container_b did not start" fi # Make sure the containers have the correct policy run_podman container inspect $container_a $container_b $service_container --format "{{.Config.SdNotifyMode}}" is "$output" "container conmon ignore" is "$(< $_SOCAT_LOG)" "" "nothing received on socket" # Make sure the container received a "proxy" socket and is not using the # one of `kube play` run_podman container inspect $container_a --format "{{.Config.SdNotifySocket}}" assert "$output" != $NOTIFY_SOCKET run_podman logs $container_a is "${lines[0]}" "/run/notify/notify.sock" "NOTIFY_SOCKET is passed to container" # Send the READY message. Doing it in an exec session helps debug # potential issues. run_podman exec --env NOTIFY_SOCKET="/run/notify/notify.sock" $container_a /usr/bin/systemd-notify --ready # Instruct the container to stop run_podman exec $container_a /bin/touch /stop run_podman container wait $container_a run_podman container inspect $container_a --format "{{.State.ExitCode}}" is "$output" "0" "container exited cleanly after sending READY message" wait_for_file $_SOCAT_LOG # The 'echo's help us debug failed runs run cat $_SOCAT_LOG echo "socat log:" echo "$output" is "$output" "MAINPID=.* READY=1" "sdnotify sent MAINPID and READY" # Make sure that Podman is the service's MainPID main_pid=$(awk -F= '{print $2}' <<< ${lines[0]}) is "$(