test: verify sysupdate invokes the notification callout directory

Extend TEST-72-SYSUPDATE with a check that, after a successful update,
systemd-sysupdate connects to every socket linked into
/run/systemd/sysupdate/notify/ and invokes
io.systemd.SysUpdate.Notify.OnCompletedUpdate(). A tiny recorder socket is
hooked into that directory; it captures the request and replies with success.
We assert the recorded call carries the expected method, version and resource
list, and that a subsequent no-op update emits no notification.
This commit is contained in:
Lennart Poettering
2026-05-28 11:42:57 +02:00
parent be1643b549
commit 89dd06505b

View File

@@ -49,6 +49,10 @@ systemctl daemon-reload
at_exit() {
set +e
systemctl stop test-sysupdate-notify-recorder.socket
rm -f /run/systemd/system/test-sysupdate-notify-recorder.socket \
/run/systemd/system/test-sysupdate-notify-recorder@.service
losetup -n --output NAME --associated "$BACKING_FILE" | while read -r loop_dev; do
losetup --detach "$loop_dev"
done
@@ -947,4 +951,85 @@ test ! -f "$CLEANUP/target/alpha-v2.bin"
rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP"
# Verify the notification callout: after a successful update, sysupdate must connect to every socket in
# /run/systemd/sysupdate/notify/ and invoke io.systemd.SysUpdate.Notify.OnCompletedUpdate(). We hook a tiny
# recorder socket into that directory that captures the request and replies with success.
NOTIFY_LOG="$WORKDIR/notify.log"
rm -f "$NOTIFY_LOG"
cat >"$WORKDIR/notify-recorder.py" <<EOF
#!/usr/bin/env python3
# Minimal Varlink server: read one NUL-terminated request, record it, reply with empty parameters.
import sys
buf = b""
while True:
c = sys.stdin.buffer.read(1)
if not c or c == b"\x00":
break
buf += c
with open("$NOTIFY_LOG", "ab") as f:
f.write(buf + b"\n")
sys.stdout.buffer.write(b'{"parameters":{}}\x00')
sys.stdout.buffer.flush()
EOF
chmod +x "$WORKDIR/notify-recorder.py"
cat >/run/systemd/system/test-sysupdate-notify-recorder.socket <<EOF
[Socket]
ListenStream=/run/systemd/sysupdate/notify/io.test.SysUpdateRecorder
Accept=yes
EOF
cat >"/run/systemd/system/test-sysupdate-notify-recorder@.service" <<EOF
[Service]
ExecStart=$WORKDIR/notify-recorder.py
StandardInput=socket
StandardOutput=socket
EOF
systemctl daemon-reload
systemctl start test-sysupdate-notify-recorder.socket
rm -rf "$CONFIGDIR" "$WORKDIR/blobs"
mkdir -p "$CONFIGDIR" "$WORKDIR/blobs"
echo "hello" >"$WORKDIR/source/notifytest-v1.bin"
(cd "$WORKDIR/source" && sha256sum notifytest-v1.bin >SHA256SUMS)
cat >"$CONFIGDIR/01-notifytest.transfer" <<EOF
[Source]
Type=url-file
Path=file://$WORKDIR/source
MatchPattern=notifytest-@v.bin
[Target]
Type=regular-file
Path=$WORKDIR/blobs
MatchPattern=notifytest-@v.bin
InstancesMax=1
EOF
# A real update must trigger exactly one notification carrying the version and the updated resources.
# The callout is synchronous (sysupdate blocks until the subscriber replied, which happens after the
# request was recorded), so the log is fully written by the time the update returns.
"$SYSUPDATE" --verify=no update
test -s "$NOTIFY_LOG" # the notification must have been recorded
notify_line="$(tail -n1 "$NOTIFY_LOG")"
echo "Recorded notification: $notify_line"
jq -e '.method == "io.systemd.SysUpdate.Notify.OnCompletedUpdate"' <<<"$notify_line" >/dev/null
jq -e '.parameters.version == "v1"' <<<"$notify_line" >/dev/null
jq -e '.parameters.resources | length >= 1' <<<"$notify_line" >/dev/null
jq -e '.parameters.resources | all(has("transfer"))' <<<"$notify_line" >/dev/null
# A no-op update ("No update needed") must NOT emit a notification.
rm -f "$NOTIFY_LOG"
"$SYSUPDATE" --verify=no update
test ! -s "$NOTIFY_LOG"
systemctl stop test-sysupdate-notify-recorder.socket
rm -f /run/systemd/system/test-sysupdate-notify-recorder.socket \
/run/systemd/system/test-sysupdate-notify-recorder@.service
systemctl daemon-reload
rm -rf "$CONFIGDIR" "$WORKDIR/blobs"
rm -f "$WORKDIR/source/notifytest-v1.bin" "$WORKDIR/source/SHA256SUMS" \
"$WORKDIR/notify-recorder.py" "$NOTIFY_LOG"
touch /testok