github.com/containers/podman/v5@v5.1.0-rc1/test/system/260-sdnotify.bats (about) 1 #!/usr/bin/env bats -*- bats -*- 2 # 3 # Tests for systemd sdnotify 4 # 5 6 load helpers 7 load helpers.network 8 load helpers.registry 9 10 # Shared throughout this module: PID of socat process, and path to its log 11 _SOCAT_PID= 12 _SOCAT_LOG= 13 14 function setup() { 15 skip_if_remote "systemd tests are meaningless over remote" 16 17 # Skip if systemd is not running 18 systemctl list-units &>/dev/null || skip "systemd not available" 19 20 # sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just 21 # assume that we work only with crun, nothing else. 22 runtime=$(podman_runtime) 23 if [[ "$runtime" != "crun" ]]; then 24 skip "this test only works with crun, not $runtime" 25 fi 26 27 basic_setup 28 } 29 30 function teardown() { 31 unset NOTIFY_SOCKET 32 33 _stop_socat 34 35 basic_teardown 36 } 37 38 ############################################################################### 39 # BEGIN helpers 40 41 # Run socat process on a socket, logging to well-known path. Each received 42 # packet is logged with a newline appended, for ease of parsing the log file. 43 function _start_socat() { 44 _SOCAT_LOG="$PODMAN_TMPDIR/socat.log" 45 46 # Reset socat logfile to empty 47 rm -f $_SOCAT_LOG 48 touch $_SOCAT_LOG 49 # Execute in subshell so we can close fd3 (which BATS uses). 50 # This is a superstitious ritual to try to avoid leaving processes behind, 51 # and thus prevent CI hangs. 52 (exec socat unix-recvfrom:"$NOTIFY_SOCKET",fork \ 53 system:"(cat;echo) >> $_SOCAT_LOG" 3>&-) & 54 _SOCAT_PID=$! 55 } 56 57 # Stop the socat background process and clean up logs 58 function _stop_socat() { 59 if [[ -n "$_SOCAT_PID" ]]; then 60 # Kill all child processes, then the process itself. 61 # This is a superstitious incantation to avoid leaving processes behind. 62 # The '|| true' is because only f35 leaves behind socat processes; 63 # f33 (and perhaps others?) behave nicely. ARGH! 64 pkill -P $_SOCAT_PID || true 65 kill $_SOCAT_PID 66 fi 67 _SOCAT_PID= 68 69 if [[ -n "$_SOCAT_LOG" ]]; then 70 rm -f $_SOCAT_LOG 71 fi 72 _SOCAT_LOG= 73 } 74 75 # Check that MAINPID=xxxxx points to a running conmon process 76 function _assert_mainpid_is_conmon() { 77 local mainpid=$(expr "$1" : ".*MAINPID=\([0-9]\+\)") 78 test -n "$mainpid" || die "Could not parse '$1' as 'MAINPID=nnnn'" 79 80 test -d /proc/$mainpid || die "sdnotify MAINPID=$mainpid - but /proc/$mainpid does not exist" 81 82 # e.g. /proc/12345/exe -> /usr/bin/conmon 83 local mainpid_bin=$(readlink /proc/$mainpid/exe) 84 is "$mainpid_bin" ".*/conmon" "sdnotify MAINPID=$mainpid is conmon process" 85 } 86 87 # END helpers 88 ############################################################################### 89 # BEGIN tests themselves 90 91 @test "sdnotify : ignore" { 92 export NOTIFY_SOCKET=$PODMAN_TMPDIR/ignore.sock 93 _start_socat 94 95 run_podman create --rm --sdnotify=ignore $IMAGE printenv NOTIFY_SOCKET 96 cid="$output" 97 98 run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" 99 is "$output" "ignore " "NOTIFY_SOCKET is not set with 'ignore' mode" 100 101 run_podman 1 start --attach $cid 102 is "$output" "" "\$NOTIFY_SOCKET in container" 103 104 is "$(< $_SOCAT_LOG)" "" "nothing received on socket" 105 _stop_socat 106 } 107 108 # bats test_tags=distro-integration 109 @test "sdnotify : conmon" { 110 export NOTIFY_SOCKET=$PODMAN_TMPDIR/conmon.sock 111 _start_socat 112 113 run_podman run -d --name sdnotify_conmon_c \ 114 --sdnotify=conmon \ 115 $IMAGE \ 116 sh -c 'printenv NOTIFY_SOCKET;echo READY;sleep 999' 117 cid="$output" 118 wait_for_ready $cid 119 120 run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" 121 is "$output" "conmon $NOTIFY_SOCKET" 122 123 run_podman container inspect sdnotify_conmon_c --format "{{.State.ConmonPid}}" 124 mainPID="$output" 125 126 run_podman logs sdnotify_conmon_c 127 is "$output" "READY" "\$NOTIFY_SOCKET in container" 128 129 # loop-wait for the final READY line 130 wait_for_file_content $_SOCAT_LOG "READY=1" 131 132 # ...and confirm the entire file contents 133 logcontents="$(< $_SOCAT_LOG)" 134 assert "$logcontents" = "MAINPID=$mainPID 135 READY=1" "sdnotify sent MAINPID and READY" 136 137 _assert_mainpid_is_conmon "$logcontents" 138 139 # Done. Stop container, clean up. 140 run_podman rm -f -t0 $cid 141 _stop_socat 142 } 143 144 # These tests can fail in dev. environment because of SELinux. 145 # quick fix: chcon -t container_runtime_exec_t ./bin/podman 146 # bats test_tags=distro-integration 147 @test "sdnotify : container" { 148 _prefetch $SYSTEMD_IMAGE 149 150 export NOTIFY_SOCKET=$PODMAN_TMPDIR/container.sock 151 _start_socat 152 153 run_podman run -d --sdnotify=container $SYSTEMD_IMAGE \ 154 sh -c 'trap "touch /stop" SIGUSR1;printenv NOTIFY_SOCKET; echo READY; while ! test -f /stop;do sleep 0.1;done;systemd-notify --ready' 155 cid="$output" 156 wait_for_ready $cid 157 158 run_podman container inspect $cid --format "{{.Config.SdNotifyMode}} {{.Config.SdNotifySocket}}" 159 is "$output" "container $NOTIFY_SOCKET" 160 161 run_podman logs $cid 162 is "${lines[0]}" "/run/notify/notify.sock" "NOTIFY_SOCKET is passed to container" 163 164 run_podman container inspect $cid --format "{{.State.ConmonPid}}" 165 mainPID="$output" 166 167 # Container does not send READY=1 until our signal. Until then, there must 168 # be exactly one line in the log 169 wait_for_file_content $_SOCAT_LOG "MAINPID=$mainPID" 170 # ...and that line must contain the expected PID, nothing more 171 assert "$(< $_SOCAT_LOG)" = "MAINPID=$mainPID" "Container has started, but must not indicate READY yet" 172 173 # Done. Tell container to stop itself, and clean up 174 run_podman kill -s USR1 $cid 175 run_podman wait $cid 176 177 wait_for_file_content $_SOCAT_LOG "READY=1" 178 assert "$(< $_SOCAT_LOG)" = "MAINPID=$mainPID 179 READY=1" "Container log after ready signal" 180 181 run_podman rm $cid 182 _stop_socat 183 } 184 185 # These tests can fail in dev. environment because of SELinux. 186 # quick fix: chcon -t container_runtime_exec_t ./bin/podman 187 @test "sdnotify : healthy" { 188 export NOTIFY_SOCKET=$PODMAN_TMPDIR/container.sock 189 _start_socat 190 191 wait_file="$PODMAN_TMPDIR/$(random_string).wait_for_me" 192 run_podman 125 create --sdnotify=healthy $IMAGE 193 is "$output" "Error: invalid argument: sdnotify policy \"healthy\" requires a healthcheck to be set" 194 195 # Create a container with a simple `/bin/true` healthcheck that we need to 196 # run manually. 197 ctr=$(random_string) 198 run_podman create --name $ctr \ 199 --health-cmd=/bin/true \ 200 --health-retries=1 \ 201 --health-interval=disable \ 202 --sdnotify=healthy \ 203 $IMAGE sleep infinity 204 205 # Start the container in the background which will block until the 206 # container turned healthy. After that, create the wait_file which 207 # indicates that start has returned. 208 (timeout --foreground -v --kill=5 20 $PODMAN start $ctr && touch $wait_file) & 209 210 run_podman wait --condition=running $ctr 211 212 # Make sure that the MAINPID is set but without the READY message. 213 run_podman container inspect $ctr --format "{{.State.ConmonPid}}" 214 mainPID="$output" 215 216 # Container does not send READY=1 until it runs a successful health check. 217 # Until then, there must be exactly one line in the log 218 wait_for_file_content $_SOCAT_LOG "MAINPID=" 219 # ...and that line must contain the expected PID, nothing more 220 assert "$(< $_SOCAT_LOG)" = "MAINPID=$mainPID" "Container logs after start, prior to healthcheck run" 221 222 # Now run the healthcheck and look for the READY message. 223 run_podman healthcheck run $ctr 224 is "$output" "" "output from 'podman healthcheck run'" 225 226 # Wait for start to return. At that point the READY message must have been 227 # sent. 228 wait_for_file_content $_SOCAT_LOG "READY=1" 229 assert "$(< $_SOCAT_LOG)" = "MAINPID=$mainPID 230 READY=1" "Container log after healthcheck run" 231 232 run_podman container inspect --format "{{.State.Status}}" $ctr 233 is "$output" "running" "make sure container is still running" 234 235 run_podman rm -f -t0 $ctr 236 _stop_socat 237 } 238 239 @test "sdnotify : play kube - no policies" { 240 # Create the YAMl file 241 yaml_source="$PODMAN_TMPDIR/test.yaml" 242 cat >$yaml_source <<EOF 243 apiVersion: v1 244 kind: Pod 245 metadata: 246 labels: 247 app: test 248 name: test_pod 249 spec: 250 restartPolicy: "Never" 251 containers: 252 - command: 253 - /bin/sh 254 - -c 255 - 'while :; do if test -e /rain/tears; then exit 0; fi; sleep 1; done' 256 image: $IMAGE 257 name: test 258 resources: {} 259 volumeMounts: 260 - mountPath: /rain:z 261 name: test-mountdir 262 volumes: 263 - hostPath: 264 path: $PODMAN_TMPDIR 265 type: Directory 266 name: test-mountdir 267 EOF 268 269 # The name of the service container is predictable: the first 12 characters 270 # of the hash of the YAML file followed by the "-service" suffix 271 yaml_sha=$(sha256sum $yaml_source) 272 service_container="${yaml_sha:0:12}-service" 273 274 export NOTIFY_SOCKET=$PODMAN_TMPDIR/conmon.sock 275 _start_socat 276 wait_for_file $_SOCAT_LOG 277 278 run_podman play kube --service-container=true --log-driver journald $yaml_source 279 280 # The service container is the main PID since no container has a custom 281 # sdnotify policy. 282 run_podman container inspect $service_container --format "{{.State.ConmonPid}}" 283 main_pid="$output" 284 285 # Tell pod to finish, then wait for all containers to stop 286 touch $PODMAN_TMPDIR/tears 287 run_podman container wait $service_container test_pod-test 288 289 # Make sure the containers have the correct policy. 290 run_podman container inspect test_pod-test $service_container --format "{{.Config.SdNotifyMode}}" 291 is "$output" "ignore 292 ignore" 293 294 wait_for_file_content $_SOCAT_LOG "READY=1" 295 assert "$(< $_SOCAT_LOG)" = "MAINPID=$main_pid 296 READY=1" "sdnotify sent MAINPID and READY" 297 298 _stop_socat 299 300 # Clean up pod and pause image 301 run_podman play kube --down $PODMAN_TMPDIR/test.yaml 302 run_podman rmi $(pause_image) 303 } 304 305 @test "sdnotify : play kube - with policies" { 306 skip_if_journald_unavailable 307 308 _prefetch $SYSTEMD_IMAGE 309 310 # Create the YAMl file 311 yaml_source="$PODMAN_TMPDIR/test.yaml" 312 cat >$yaml_source <<EOF 313 apiVersion: v1 314 kind: Pod 315 metadata: 316 labels: 317 app: test 318 name: test_pod 319 annotations: 320 io.containers.sdnotify: "container" 321 io.containers.sdnotify/b: "conmon" 322 spec: 323 restartPolicy: "Never" 324 containers: 325 - command: 326 - /bin/sh 327 - -c 328 - 'printenv NOTIFY_SOCKET; echo READY; while ! test -f /stop;do sleep 0.1;done' 329 image: $SYSTEMD_IMAGE 330 name: a 331 - command: 332 - /bin/sh 333 - -c 334 - 'echo READY; top' 335 image: $IMAGE 336 name: b 337 EOF 338 container_a="test_pod-a" 339 container_b="test_pod-b" 340 341 # The name of the service container is predictable: the first 12 characters 342 # of the hash of the YAML file followed by the "-service" suffix 343 yaml_sha=$(sha256sum $yaml_source) 344 service_container="${yaml_sha:0:12}-service" 345 346 export NOTIFY_SOCKET=$PODMAN_TMPDIR/conmon.sock 347 _start_socat 348 349 # Run `play kube` in the background as it will wait for all containers to 350 # send the READY=1 message. 351 timeout --foreground -v --kill=10 60 \ 352 $PODMAN play kube --service-container=true --log-driver journald $yaml_source &>/dev/null & 353 354 # Wait for both containers to be running 355 containers_running= 356 for i in $(seq 1 20); do 357 run_podman "?" container wait $container_a $container_b --condition="running" 358 if [[ $status == 0 ]]; then 359 containers_running=1 360 break 361 fi 362 sleep 0.5 363 # Just for debugging 364 run_podman ps -a 365 done 366 if [[ -z "$containers_running" ]]; then 367 die "container $container_a and/or $container_b did not start" 368 fi 369 370 wait_for_ready $container_a 371 # Make sure the containers have the correct policy 372 run_podman container inspect $container_a $container_b $service_container --format "{{.Config.SdNotifyMode}}" 373 is "$output" "container 374 conmon 375 ignore" 376 377 is "$(< $_SOCAT_LOG)" "" "nothing received on socket" 378 379 # Make sure the container received a "proxy" socket and is not using the 380 # one of `kube play` 381 run_podman container inspect $container_a --format "{{.Config.SdNotifySocket}}" 382 assert "$output" != $NOTIFY_SOCKET 383 384 run_podman logs $container_a 385 is "${lines[0]}" "/run/notify/notify.sock" "NOTIFY_SOCKET is passed to container" 386 387 # Send the READY message. Doing it in an exec session helps debug 388 # potential issues. 389 run_podman exec --env NOTIFY_SOCKET="/run/notify/notify.sock" $container_a /usr/bin/systemd-notify --ready 390 391 # Instruct the container to stop. 392 # Run detached as the `exec` session races with the cleanup process 393 # of the exiting container (see #10825). 394 run_podman exec -d $container_a /bin/touch /stop 395 396 run_podman container wait $container_a 397 run_podman container inspect $container_a --format "{{.State.ExitCode}}" 398 is "$output" "0" "container exited cleanly after sending READY message" 399 400 wait_for_file_content $_SOCAT_LOG "READY=1" 401 assert "$(< $_SOCAT_LOG)" =~ "MAINPID=.* 402 READY=1" "sdnotify sent MAINPID and READY" 403 404 # Make sure that Podman is the service's MainPID 405 main_pid=$(head -n1 $_SOCAT_LOG | awk -F= '{print $2}') 406 is "$(</proc/$main_pid/comm)" "podman" "podman is the service mainPID ($main_pid)" 407 _stop_socat 408 409 # Clean up pod and pause image 410 run_podman play kube --down $yaml_source 411 run_podman rmi $(pause_image) 412 } 413 414 function generate_exit_code_yaml { 415 local fname=$1 416 local cmd1=$2 417 local cmd2=$3 418 local sdnotify_policy=$4 419 echo " 420 apiVersion: v1 421 kind: Pod 422 metadata: 423 labels: 424 app: test 425 name: test_pod 426 annotations: 427 io.containers.sdnotify: "$sdnotify_policy" 428 spec: 429 restartPolicy: Never 430 containers: 431 - name: ctr1 432 image: $IMAGE 433 command: 434 - $cmd1 435 - name: ctr2 436 image: $IMAGE 437 command: 438 - $cmd2 439 " > $fname 440 } 441 442 # bats test_tags=distro-integration 443 @test "podman kube play - exit-code propagation" { 444 fname=$PODMAN_TMPDIR/$(random_string).yaml 445 446 # Create a test matrix with the following arguments: 447 # exit-code propagation | ctr1 command | ctr2 command | service-container exit code 448 exit_tests=" 449 all | true | true | 0 450 all | true | false | 0 451 all | false | false | 137 452 any | true | true | 0 453 any | false | true | 137 454 any | false | false | 137 455 none | true | true | 0 456 none | true | false | 0 457 none | false | false | 0 458 " 459 460 # I am sorry, this is a long test as we need to test the upper matrix 461 # twice. The first run is using the default sdnotify policy of "ignore". 462 # In this case, the service container serves as the main PID of the service 463 # to have a minimal resource footprint. The second run is using the 464 # "conmon" sdnotify policy in which case Podman needs to serve as the main 465 # PID to act as an sdnotify proxy; there Podman will wait for the service 466 # container to exit and reflects its exit code. 467 while read exit_code_prop cmd1 cmd2 exit_code; do 468 for sdnotify_policy in ignore conmon; do 469 generate_exit_code_yaml $fname $cmd1 $cmd2 $sdnotify_policy 470 yaml_sha=$(sha256sum $fname) 471 service_container="${yaml_sha:0:12}-service" 472 podman_exit=$exit_code 473 if [[ $sdnotify_policy == "ignore" ]];then 474 podman_exit=0 475 fi 476 run_podman $podman_exit kube play --service-exit-code-propagation="$exit_code_prop" --service-container $fname 477 # Make sure that there are no error logs (e.g., #19715) 478 assert "$output" !~ "error msg=" 479 run_podman container inspect --format '{{.KubeExitCodePropagation}}' $service_container 480 is "$output" "$exit_code_prop" "service container has the expected policy set in its annotations" 481 run_podman wait $service_container 482 is "$output" "$exit_code" "service container exit code (propagation: $exit_code_prop, policy: $service_policy, cmds: $cmd1 + $cmd2)" 483 run_podman kube down $fname 484 done 485 done < <(parse_table "$exit_tests") 486 487 # A final smoke test to make sure bogus policies lead to an error 488 run_podman 125 kube play --service-exit-code-propagation=bogus --service-container $fname 489 is "$output" "Error: unsupported exit-code propagation \"bogus\"" "error on unsupported exit-code propagation" 490 491 run_podman rmi $(pause_image) 492 } 493 494 @test "podman pull - EXTEND_TIMEOUT_USEC" { 495 # Make sure that Podman extends the start timeout via DBUS when running 496 # inside a systemd unit (i.e., with NOTIFY_SOCKET set). Extending the 497 # timeout works by continuously sending EXTEND_TIMEOUT_USEC; Podman does 498 # this at most 10 times, adding up to ~5min. 499 500 image_on_local_registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}/name:tag 501 registry_flags="--tls-verify=false --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}" 502 start_registry 503 504 export NOTIFY_SOCKET=$PODMAN_TMPDIR/notify.sock 505 _start_socat 506 507 run_podman push $registry_flags $IMAGE $image_on_local_registry 508 run_podman pull $registry_flags $image_on_local_registry 509 is "${lines[1]}" "Pulling image //$image_on_local_registry inside systemd: setting pull timeout to 5m0s" "NOTIFY_SOCKET is passed to container" 510 511 run cat $_SOCAT_LOG 512 # The 'echo's help us debug failed runs 513 echo "socat log:" 514 echo "$output" 515 is "$output" "EXTEND_TIMEOUT_USEC=30000000" 516 517 run_podman rmi $image_on_local_registry 518 _stop_socat 519 } 520 521 @test "podman system service" { 522 # This test makes sure that podman-system-service uses the NOTIFY_SOCKET 523 # correctly and that it unsets it after sending the expected MAINPID and 524 # READY message by making sure no EXTEND_TIMEOUT_USEC is sent on pull. 525 526 # Start a local registry and pre-populate it with an image we'll pull later on. 527 image_on_local_registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}/name:tag 528 registry_flags="--tls-verify=false --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}" 529 start_registry 530 run_podman push $registry_flags $IMAGE $image_on_local_registry 531 532 export NOTIFY_SOCKET=$PODMAN_TMPDIR/notify.sock 533 podman_socket="unix://$PODMAN_TMPDIR/podman.sock" 534 envfile=$PODMAN_TMPDIR/envfile 535 _start_socat 536 537 (timeout --foreground -v --kill=10 30 $PODMAN system service -t0 $podman_socket &) 538 539 wait_for_file $_SOCAT_LOG 540 local timeout=10 541 while [[ $timeout -gt 0 ]]; do 542 run cat $_SOCAT_LOG 543 # The 'echo's help us debug failed runs 544 echo "socat log:" 545 echo "$output" 546 547 if [[ "$output" =~ "READY=1" ]]; then 548 break 549 fi 550 timeout=$((timeout - 1)) 551 assert $timeout -gt 0 "Timed out waiting for podman-system-service to send expected data over NOTIFY_SOCKET" 552 sleep 0.5 553 done 554 555 assert "$output" =~ "MAINPID=.* 556 READY=1" "podman-system-service sends expected data over NOTIFY_SOCKET" 557 mainpid=${lines[0]:8} 558 559 # Now pull remotely and make sure that the service does _not_ extend the 560 # timeout; the NOTIFY_SOCKET should be unset at that point. 561 run_podman --url $podman_socket pull $registry_flags $image_on_local_registry 562 563 run cat $_SOCAT_LOG 564 # The 'echo's help us debug failed runs 565 echo "socat log:" 566 echo "$output" 567 assert "$output" !~ "EXTEND_TIMEOUT_USEC=" 568 569 # Give the system-service 5sec to terminate before killing it. 570 /bin/kill --timeout 5000 KILL --signal TERM $mainpid 571 run_podman rmi $image_on_local_registry 572 _stop_socat 573 } 574 # vim: filetype=sh