#!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later # shellcheck disable=SC2016 set -eux set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh export PAGER= at_exit() { set +e machinectl status long-running &>/dev/null && machinectl kill --signal=KILL long-running mountpoint -q /var/lib/machines && timeout 10 sh -c "until umount /var/lib/machines; do sleep .5; done" [[ -n "${NSPAWN_FRAGMENT:-}" ]] && rm -f "/etc/systemd/nspawn/$NSPAWN_FRAGMENT" "/var/lib/machines/$NSPAWN_FRAGMENT" rm -f /run/systemd/nspawn/*.nspawn } trap at_exit EXIT systemctl service-log-level systemd-machined debug systemctl service-log-level systemd-importd debug # per request in https://github.com/systemd/systemd/pull/35117 systemctl edit --runtime --stdin 'systemd-nspawn@.service' --drop-in=debug.conf </var/lib/machines/long-running/sbin/init <<\EOF #!/usr/bin/bash PID=0 trap 'touch /terminate; kill 0' RTMIN+3 trap 'touch /poweroff' RTMIN+4 trap 'touch /reboot' INT trap 'touch /trap' TRAP trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & # notify that the process is ready touch /ready PID=$! while :; do wait || : done EOF long_running_machine_start() { # shellcheck disable=SC2015 machinectl status long-running &>/dev/null && return 0 || true # Ensure the service stopped. systemctl stop systemd-nspawn@long-running.service 2>/dev/null || : rm -f /var/lib/machines/long-running/ready machinectl start long-running # !!!! DO NOT REMOVE THIS TEST # The test makes sure that the long-running's init script has enough time to start and registered signal traps timeout 30 bash -c "until test -e /var/lib/machines/long-running/ready; do sleep .5; done" } long_running_machine_start machinectl machinectl --no-pager --help machinectl --version machinectl list machinectl list --no-legend --no-ask-password machinectl status long-running long-running long-running machinectl status --full long-running machinectl status --quiet --lines=1 long-running machinectl status --lines=0 --max-addresses=0 long-running machinectl status --machine=testuser@.host long-running machinectl status --output=help long-running while read -r output; do machinectl status --output="$output" long-running done < <(machinectl --output=help) machinectl show machinectl show --all machinectl show --all --machine=root@ machinectl show --all --machine=testuser@ [[ "$(machinectl show --property=PoolPath --value)" == "/var/lib/machines" ]] machinectl show long-running machinectl show long-running long-running long-running --all [[ "$(machinectl show --property=RootDirectory --value long-running)" == "/var/lib/machines/long-running" ]] machinectl enable long-running test -L /etc/systemd/system/machines.target.wants/systemd-nspawn@long-running.service machinectl enable long-running long-running long-running container1 machinectl disable long-running test ! -L /etc/systemd/system/machines.target.wants/systemd-nspawn@long-running.service machinectl disable long-running long-running long-running container1 [[ "$(machinectl shell testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "" ]] [[ "$(machinectl shell --setenv=FOO=bar testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] [[ "$(machinectl show --property=State --value long-running)" == "running" ]] # Equivalent to machinectl kill --signal=SIGRTMIN+4 --kill-whom=leader rm -f /var/lib/machines/long-running/poweroff machinectl poweroff long-running timeout 10 bash -c "until test -e /var/lib/machines/long-running/poweroff; do sleep .5; done" # Equivalent to machinectl kill --signal=SIGINT --kill-whom=leader rm -f /var/lib/machines/long-running/reboot machinectl reboot long-running timeout 10 bash -c "until test -e /var/lib/machines/long-running/reboot; do sleep .5; done" # Test for 'machinectl terminate' rm -f /var/lib/machines/long-running/terminate machinectl terminate long-running timeout 10 bash -c "until test -e /var/lib/machines/long-running/terminate; do sleep .5; done" timeout 10 bash -c "while machinectl status long-running &>/dev/null; do sleep .5; done" # Restart container long_running_machine_start # Test for 'machinectl kill' rm -f /var/lib/machines/long-running/trap machinectl kill --signal=SIGTRAP --kill-whom=leader long-running timeout 10 bash -c "until test -e /var/lib/machines/long-running/trap; do sleep .5; done" # Multiple machines at once machinectl poweroff long-running long-running long-running machinectl reboot long-running long-running long-running machinectl kill --signal=SIGTRAP --kill-whom=leader long-running long-running long-running # All used signals should've been caught by a handler [[ "$(machinectl show --property=State --value long-running)" == "running" ]] cp /etc/machine-id /tmp/foo machinectl copy-to long-running /tmp/foo /root/foo test -f /var/lib/machines/long-running/root/foo machinectl copy-from long-running /root/foo /tmp/bar diff /tmp/foo /tmp/bar rm -f /tmp/{foo,bar} # machinectl bind is covered by testcase_check_machinectl_bind() in nspawn tests machinectl list-images machinectl list-images --no-legend machinectl image-status machinectl image-status container1 machinectl image-status container1 container1 container{0..4} machinectl show-image machinectl show-image container1 machinectl show-image container1 container1 container{0..4} machinectl clone container1 clone1 machinectl show-image clone1 machinectl rename clone1 clone2 (! machinectl show-image clone1) machinectl show-image clone2 # `machinectl read-only` uses chattr (ioctl(FS_IOC_SETFLAGS)) when the container is backed by a directory, # and this operation might not be implemented on certain filesystems (i.e. tmpfs on older kernels), so check # if we have chattr support before running following tests if lsattr -d /var/lib/machines >/dev/null; then [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == no ]] machinectl read-only clone2 yes [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == yes ]] machinectl read-only clone2 no [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == no ]] fi machinectl remove clone2 for i in {0..4}; do machinectl clone container1 "clone$i" done machinectl remove clone{0..4} for i in {0..4}; do machinectl clone container1 ".hidden$i" done machinectl list-images --all test -d /var/lib/machines/.hidden1 machinectl clean test ! -d /var/lib/machines/.hidden1 # Prepare a simple raw container mkdir -p /tmp/mnt dd if=/dev/zero of=/var/tmp/container.raw bs=1M count=256 mkfs.ext4 /var/tmp/container.raw mount -o loop /var/tmp/container.raw /tmp/mnt cp -r /var/lib/machines/container1/* /tmp/mnt umount /tmp/mnt # Try to import it, run it, export it, and re-import it machinectl import-raw /var/tmp/container.raw container-raw [[ "$(machinectl show-image --property=Type --value container-raw)" == "raw" ]] machinectl start container-raw machinectl export-raw container-raw /var/tmp/container-export.raw machinectl import-raw /var/tmp/container-export.raw container-raw-reimport [[ "$(machinectl show-image --property=Type --value container-raw-reimport)" == "raw" ]] rm -f /var/tmp/container{,-export}.raw # Prepare a simple tar.gz container tar -pczf /var/tmp/container.tar.gz -C /var/lib/machines/container1 . # Try to import it, run it, export it, and re-import it machinectl import-tar /var/tmp/container.tar.gz container-tar [[ "$(machinectl show-image --property=Type --value container-tar)" =~ directory|subvolume ]] machinectl start container-tar machinectl export-tar container-tar /var/tmp/container-export.tar.gz machinectl import-tar /var/tmp/container-export.tar.gz container-tar-reimport [[ "$(machinectl show-image --property=Type --value container-tar-reimport)" =~ directory|subvolume ]] rm -f /var/tmp/container{,-export}.tar.gz # Try to import a container directory & run it cp -r /var/lib/machines/container1 /var/tmp/container.dir machinectl import-fs /var/tmp/container.dir container-dir [[ "$(machinectl show-image --property=Type --value container-dir)" =~ directory|subvolume ]] machinectl start container-dir rm -fr /var/tmp/container.dir timeout 10 bash -c "until machinectl clean --all; do sleep .5; done" NSPAWN_FRAGMENT="machinectl-test-$RANDOM.nspawn" cat >"/var/lib/machines/$NSPAWN_FRAGMENT" </tmp/fragment.nspawn </tmp/expected varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"pid\":$pid}" | diff /tmp/expected - varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"long-running\", \"pid\":$pid}" | diff /tmp/expected - (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"non-existent\", \"pid\":$pid}") (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":""}') (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":"ah@??.hmm"}') # test io.systemd.Machine.Kill # sending TRAP signal rm -f /var/lib/machines/long-running/trap varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Kill '{"name":"long-running", "whom": "leader", "signal": 5}' timeout 120 bash -c "until test -e /var/lib/machines/long-running/trap; do sleep .5; done" # test io.systemd.Machine.Terminate long_running_machine_start rm -f /var/lib/machines/long-running/terminate varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Terminate '{"name":"long-running"}' timeout 30 bash -c "until test -e /var/lib/machines/long-running/terminate; do sleep .5; done" timeout 30 bash -c "while varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{\"name\":\"long-running\"}'; do sleep 0.5; done" # test io.systemd.Machine.Register varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Register '{"name": "registered-container", "class": "container"}' timeout 30 bash -c "until varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{\"name\":\"registered-container\"}'; do sleep 0.5; done" # test io.systemd.Machine.Unregister varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Unregister '{"name": "registered-container"}' timeout 30 bash -c "while varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{\"name\":\"registered-container\"}'; do sleep 0.5; done" # test io.systemd.Machine.List with sshAddress and sshPrivateKeyPath fields varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Register '{"name": "registered-container", "class": "container", "sshAddress": "localhost", "sshPrivateKeyPath": "/non-existent"}' timeout 30 bash -c "until varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{\"name\":\"registered-container\"}'; do sleep 0.5; done" varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":"registered-container"}' | jq '.sshAddress' | grep -q 'localhost' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":"registered-container"}' | jq '.sshPrivateKeyPath' | grep -q 'non-existent' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Unregister '{"name": "registered-container"}' # test io.systemd.Machine.List with addresses, OSRelease, and UIDShift fields create_dummy_container "/var/lib/machines/container-without-os-release" cat >>/var/lib/machines/container-without-os-release/sbin/init <<\EOF ip link add hoge type dummy ip link set hoge up ip address add 192.0.2.1/24 dev hoge PID=0 trap 'kill 0' RTMIN+3 trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & # notify that the process is ready touch /ready PID=$! while :; do wait || : done EOF machinectl start "container-without-os-release" timeout 30 bash -c "until test -e /var/lib/machines/container-without-os-release/ready; do sleep .5; done" rm -f /var/lib/machines/container-without-os-release/etc/os-release /var/lib/machines/container-without-os-release/usr/lib/os-release (! varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": "container-without-os-release", "acquireMetadata": "yes"}') output=$(varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": "container-without-os-release", "acquireMetadata": "graceful"}') assert_eq "$(echo "$output" | jq --seq .name | tr -d \\036)" '"container-without-os-release"' assert_eq "$(echo "$output" | jq --seq .class | tr -d \\036)" '"container"' assert_eq "$(echo "$output" | jq --seq .service | tr -d \\036)" '"systemd-nspawn"' assert_eq "$(echo "$output" | jq --seq .rootDirectory | tr -d \\036)" '"/var/lib/machines/container-without-os-release"' assert_eq "$(echo "$output" | jq --seq .unit | tr -d \\036)" '"systemd-nspawn@container-without-os-release.service"' assert_eq "$(echo "$output" | jq --seq .addresses[0].family | tr -d \\036)" '2' assert_eq "$(echo "$output" | jq --seq .addresses[0].address[0] | tr -d \\036)" '192' assert_eq "$(echo "$output" | jq --seq .addresses[0].address[1] | tr -d \\036)" '0' assert_eq "$(echo "$output" | jq --seq .addresses[0].address[2] | tr -d \\036)" '2' assert_eq "$(echo "$output" | jq --seq .addresses[0].address[3] | tr -d \\036)" '1' # test for listing multiple machines. long_running_machine_start varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{}' varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"acquireMetadata": "no"}' varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"acquireMetadata": "graceful"}' # check if machined does not try to send anything after error message journalctl --sync TS="$(date '+%H:%M:%S')" (! varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"acquireMetadata": "yes"}') journalctl --sync (! journalctl -u systemd-machined.service --since="$TS" --grep 'Connection busy') # terminate machines machinectl terminate container-without-os-release machinectl terminate long-running # wait for the container being stopped, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. timeout 30 bash -c "while machinectl status long-running &>/dev/null; do sleep .5; done" systemctl kill --signal=KILL systemd-nspawn@long-running.service || : (ip addr show lo | grep -q 192.168.1.100) || ip address add 192.168.1.100/24 dev lo (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host"}' | grep 'addresses') varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host", "acquireMetadata": "yes"}' | grep 'addresses' (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host"}' | grep 'OSRelease') varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host", "acquireMetadata": "yes"}' | grep 'OSRelease' (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host"}' | grep 'acquireUIDShift') varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host", "acquireMetadata": "yes"}' | grep 'UIDShift' # test io.systemd.Machine.Open # Reducing log level here is to work-around check in end.service (end.sh). Read https://github.com/systemd/systemd/pull/34867 for more details systemctl service-log-level systemd-machined info (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host"}') (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": ""}') (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": null}') (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "foo"}') systemctl service-log-level systemd-machined debug varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "tty"}' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "login"}' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell"}' rm -f /tmp/none-existent-file varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done" grep -q "BAR" /tmp/none-existent-file # test io.systemd.MachineImage.List varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep 'long-running' varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep '.host' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running"}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running", "acquireMetadata": "yes"}' | grep 'OSRelease' # test io.systemd.MachineImage.Update varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.Update '{"name":"long-running", "newName": "long-running-renamed", "readOnly": true}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running-renamed"}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running-renamed"}' | jq '.readOnly' | grep 'true' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.Update '{"name":"long-running-renamed", "newName": "long-running", "readOnly": false}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running"}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running"}' | jq '.readOnly' | grep 'false' # test io.systemd.MachineImage.Clone varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.Clone '{"name":"long-running", "newName": "long-running-cloned", "readOnly": true}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running-cloned"}' varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running-cloned"}' | jq '.readOnly' | grep 'true' # test io.systemd.MachineImage.Remove varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.Remove '{"name":"long-running-cloned"}' (! varlinkctl call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{"name":"long-running-cloned"}')