jellyfin-qbittorrent-monitor: add webhook receiver for instant throttling
Some checks failed
Build and Deploy / deploy (push) Failing after 2m9s
Some checks failed
Build and Deploy / deploy (push) Failing after 2m9s
This commit is contained in:
@@ -6,6 +6,21 @@
|
||||
}:
|
||||
let
|
||||
jfLib = import ./jellyfin-test-lib.nix { inherit pkgs lib; };
|
||||
webhookPlugin = import ../services/jellyfin/jellyfin-webhook-plugin.nix { inherit pkgs lib; };
|
||||
configureWebhook = webhookPlugin.mkConfigureScript {
|
||||
jellyfinUrl = "http://localhost:8096";
|
||||
webhooks = [
|
||||
{
|
||||
name = "qBittorrent Monitor";
|
||||
uri = "http://127.0.0.1:9898/";
|
||||
notificationTypes = [
|
||||
"PlaybackStart"
|
||||
"PlaybackProgress"
|
||||
"PlaybackStop"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "jellyfin-qbittorrent-monitor";
|
||||
@@ -69,11 +84,30 @@ pkgs.testers.runNixOSTest {
|
||||
}
|
||||
];
|
||||
|
||||
# Create directories for qBittorrent
|
||||
# Create directories for qBittorrent.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/qbittorrent/downloads 0755 qbittorrent qbittorrent"
|
||||
"d /var/lib/qbittorrent/incomplete 0755 qbittorrent qbittorrent"
|
||||
];
|
||||
|
||||
# Install the Jellyfin Webhook plugin before Jellyfin starts, mirroring
|
||||
# the production module. Jellyfin rewrites meta.json at runtime so a
|
||||
# read-only nix-store symlink would fail — we materialise a writable copy.
|
||||
systemd.services."jellyfin-webhook-install" = {
|
||||
description = "Install Jellyfin Webhook plugin files";
|
||||
before = [ "jellyfin.service" ];
|
||||
wantedBy = [ "jellyfin.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "jellyfin";
|
||||
Group = "jellyfin";
|
||||
UMask = "0077";
|
||||
ExecStart = webhookPlugin.mkInstallScript {
|
||||
pluginsDir = "/var/lib/jellyfin/plugins";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Public test IP (RFC 5737 TEST-NET-3) so Jellyfin sees it as external
|
||||
@@ -394,6 +428,97 @@ pkgs.testers.runNixOSTest {
|
||||
local_playback["PositionTicks"] = 50000000
|
||||
server.succeed(f"curl -sf -X POST 'http://localhost:8096/Sessions/Playing/Stopped' -d '{json.dumps(local_playback)}' -H 'Content-Type:application/json' -H 'X-Emby-Authorization:{local_auth}, Token={local_token}'")
|
||||
|
||||
# === WEBHOOK TESTS ===
|
||||
#
|
||||
# Configure the Jellyfin Webhook plugin to target the monitor, then verify
|
||||
# the real Jellyfin → plugin → monitor path reacts faster than any possible
|
||||
# poll. CHECK_INTERVAL=30 rules out polling as the cause.
|
||||
|
||||
WEBHOOK_PORT = 9898
|
||||
WEBHOOK_CREDS = "/tmp/webhook-creds"
|
||||
|
||||
# Start a webhook-enabled monitor with long poll interval.
|
||||
server.succeed("systemctl stop monitor-test || true")
|
||||
time.sleep(1)
|
||||
server.succeed(f"""
|
||||
systemd-run --unit=monitor-webhook \
|
||||
--setenv=JELLYFIN_URL=http://localhost:8096 \
|
||||
--setenv=JELLYFIN_API_KEY={token} \
|
||||
--setenv=QBITTORRENT_URL=http://localhost:8080 \
|
||||
--setenv=CHECK_INTERVAL=30 \
|
||||
--setenv=STREAMING_START_DELAY=1 \
|
||||
--setenv=STREAMING_STOP_DELAY=1 \
|
||||
--setenv=TOTAL_BANDWIDTH_BUDGET=50000000 \
|
||||
--setenv=SERVICE_BUFFER=2000000 \
|
||||
--setenv=DEFAULT_STREAM_BITRATE=10000000 \
|
||||
--setenv=MIN_TORRENT_SPEED=100 \
|
||||
--setenv=WEBHOOK_PORT={WEBHOOK_PORT} \
|
||||
--setenv=WEBHOOK_BIND=127.0.0.1 \
|
||||
{python} {monitor}
|
||||
""")
|
||||
server.wait_until_succeeds(f"ss -ltn | grep -q ':{WEBHOOK_PORT}'", timeout=15)
|
||||
time.sleep(2)
|
||||
assert not is_throttled(), "Should start unthrottled"
|
||||
|
||||
# Drop the admin token where the configure script expects it (production uses agenix).
|
||||
server.succeed(f"mkdir -p {WEBHOOK_CREDS} && echo '{token}' > {WEBHOOK_CREDS}/jellyfin-api-key")
|
||||
server.succeed(
|
||||
f"systemd-run --wait --unit=webhook-configure-test "
|
||||
f"--setenv=CREDENTIALS_DIRECTORY={WEBHOOK_CREDS} "
|
||||
f"${configureWebhook}"
|
||||
)
|
||||
|
||||
with subtest("Real PlaybackStart event throttles via the plugin"):
|
||||
playback_start = {
|
||||
"ItemId": movie_id,
|
||||
"MediaSourceId": media_source_id,
|
||||
"PlaySessionId": "test-plugin-start",
|
||||
"CanSeek": True,
|
||||
"IsPaused": False,
|
||||
}
|
||||
start_cmd = f"curl -sf -X POST 'http://{server_ip}:8096/Sessions/Playing' -d '{json.dumps(playback_start)}' -H 'Content-Type:application/json' -H 'X-Emby-Authorization:{client_auth}, Token={client_token}'"
|
||||
client.succeed(start_cmd)
|
||||
server.wait_until_succeeds(
|
||||
"curl -sf http://localhost:8080/api/v2/transfer/speedLimitsMode | grep -q '^1$'",
|
||||
timeout=5,
|
||||
)
|
||||
# Let STREAMING_STOP_DELAY (1s) elapse so the upcoming stop is not swallowed by hysteresis.
|
||||
time.sleep(2)
|
||||
|
||||
with subtest("Real PlaybackStop event unthrottles via the plugin"):
|
||||
playback_stop = {
|
||||
"ItemId": movie_id,
|
||||
"MediaSourceId": media_source_id,
|
||||
"PlaySessionId": "test-plugin-start",
|
||||
"PositionTicks": 50000000,
|
||||
}
|
||||
stop_cmd = f"curl -sf -X POST 'http://{server_ip}:8096/Sessions/Playing/Stopped' -d '{json.dumps(playback_stop)}' -H 'Content-Type:application/json' -H 'X-Emby-Authorization:{client_auth}, Token={client_token}'"
|
||||
client.succeed(stop_cmd)
|
||||
server.wait_until_succeeds(
|
||||
"curl -sf http://localhost:8080/api/v2/transfer/speedLimitsMode | grep -q '^0$'",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
# Restore fast-polling monitor for the service-restart tests below.
|
||||
server.succeed("systemctl stop monitor-webhook || true")
|
||||
time.sleep(1)
|
||||
server.succeed(f"""
|
||||
systemd-run --unit=monitor-test \
|
||||
--setenv=JELLYFIN_URL=http://localhost:8096 \
|
||||
--setenv=JELLYFIN_API_KEY={token} \
|
||||
--setenv=QBITTORRENT_URL=http://localhost:8080 \
|
||||
--setenv=CHECK_INTERVAL=1 \
|
||||
--setenv=STREAMING_START_DELAY=1 \
|
||||
--setenv=STREAMING_STOP_DELAY=1 \
|
||||
--setenv=TOTAL_BANDWIDTH_BUDGET=50000000 \
|
||||
--setenv=SERVICE_BUFFER=2000000 \
|
||||
--setenv=DEFAULT_STREAM_BITRATE=10000000 \
|
||||
--setenv=MIN_TORRENT_SPEED=100 \
|
||||
{python} {monitor}
|
||||
""")
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
# === SERVICE RESTART TESTS ===
|
||||
|
||||
with subtest("qBittorrent restart during throttled state re-applies throttling"):
|
||||
|
||||
Reference in New Issue
Block a user