jellyfin-qbittorrent-monitor: take into account soulseek
This commit is contained in:
@@ -808,5 +808,169 @@ pkgs.testers.runNixOSTest {
|
||||
# After Jellyfin comes back, sessions are gone - should unthrottle
|
||||
time.sleep(3)
|
||||
assert not is_throttled(), "Should unthrottle after Jellyfin returns with no sessions"
|
||||
|
||||
# === SLSKD UPLOAD BANDWIDTH TESTS ===
|
||||
|
||||
# Spin up a mock slskd API server that simulates active uploads. The
|
||||
# monitor should query it and subtract upload bandwidth from the torrent
|
||||
# budget alongside Jellyfin streaming.
|
||||
|
||||
mock_slskd_port = 9999
|
||||
|
||||
|
||||
# Start a mock slskd server. It checks the X-API-Key header to verify
|
||||
# the monitor sends credentials correctly, and returns 401 if they mismatch.
|
||||
mock_slskd_key = "test-slskd-api-key"
|
||||
server.succeed(
|
||||
f"{pkgs.python3}/bin/python -c \""
|
||||
"import json, http.server, threading;"
|
||||
f"EXPECTED_KEY='{mock_slskd_key}';"
|
||||
"class H(http.server.BaseHTTPRequestHandler):"
|
||||
" uploads_response=[];"
|
||||
" def do_GET(s):"
|
||||
" if s.path=='/api/v0/transfers/uploads':"
|
||||
" if s.headers.get('X-API-Key')!=EXPECTED_KEY:"
|
||||
" s.send_error(401);"
|
||||
" return;"
|
||||
" s.send_response(200);"
|
||||
" s.send_header('Content-Type','application/json');"
|
||||
" s.end_headers();"
|
||||
" s.wfile.write(json.dumps(H.uploads_response).encode());"
|
||||
" else: s.send_error(404);"
|
||||
" def log_message(*a): pass;"
|
||||
f"server=http.server.HTTPServer(('127.0.0.1',{mock_slskd_port}),H);"
|
||||
"threading.Thread(target=server.serve_forever,daemon=True).start();"
|
||||
"import signal; signal.pause()\" &"
|
||||
)
|
||||
server.wait_for_open_port(mock_slskd_port)
|
||||
server.succeed(f"curl -sf http://127.0.0.1:{mock_slskd_port}/api/v0/transfers/uploads")
|
||||
|
||||
# Stop the normal monitor and start one pointed at the mock slskd
|
||||
server.succeed("systemctl stop monitor-test || true")
|
||||
time.sleep(1)
|
||||
server.succeed(f"""
|
||||
systemd-run --unit=monitor-slskd \
|
||||
--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 \
|
||||
--setenv=SLSKD_URL=http://127.0.0.1:{mock_slskd_port} \
|
||||
--setenv=SLSKD_API_KEY={mock_slskd_key} \
|
||||
{python} {monitor}
|
||||
""")
|
||||
time.sleep(2)
|
||||
assert not is_throttled(), "Should start unthrottled (slskd reports no uploads)"
|
||||
|
||||
with subtest("Slskd uploads reduce torrent bandwidth budget"):
|
||||
# Re-authenticate to get fresh token after previous tests
|
||||
client_auth_result = json.loads(client.succeed(
|
||||
f"curl -sf -X POST 'http://{server_ip}:8096/Users/AuthenticateByName' "
|
||||
f"-d '@${jfLib.payloads.auth}' "
|
||||
"-H 'Content-Type:application/json' "
|
||||
f"-H 'X-Emby-Authorization:{client_auth}'"
|
||||
))
|
||||
client_token = client_auth_result["AccessToken"]
|
||||
|
||||
# Start a single Jellyfin stream to establish a baseline torrent limit
|
||||
playback_start = {{
|
||||
"ItemId": movie_id,
|
||||
"MediaSourceId": media_source_id,
|
||||
"PlaySessionId": "test-play-session-slskd",
|
||||
"CanSeek": True,
|
||||
"IsPaused": False,
|
||||
}}
|
||||
start_cmd = (
|
||||
f"curl -sf -X POST 'http://{server_ip}:8096/Sessions/Playing' "
|
||||
f"-d '{json.dumps(playback_start)}' "
|
||||
"-H 'Content-Type:application/json' "
|
||||
f"-H 'X-Emby-Authorization:{client_auth}, Token={client_token}'"
|
||||
)
|
||||
client.succeed(start_cmd)
|
||||
time.sleep(3)
|
||||
|
||||
assert is_throttled(), "Should throttle with streaming"
|
||||
baseline_dl = get_alt_dl_limit()
|
||||
|
||||
# Now simulate active slskd uploads consuming 15 Mbps (1,875,000 bytes/s)
|
||||
# by restarting the mock with two in-progress uploads at 937,500 bytes/s each.
|
||||
server.succeed("pkill -f 'H(http' || true")
|
||||
time.sleep(1)
|
||||
|
||||
# Restart the mock server with upload data; still validates the API key.
|
||||
server.succeed(
|
||||
f"{pkgs.python3}/bin/python -c \""
|
||||
"import json, http.server, threading;"
|
||||
f"EXPECTED_KEY='{mock_slskd_key}';"
|
||||
"class H(http.server.BaseHTTPRequestHandler):"
|
||||
" uploads_response=[{'username':'peer1','directories':[{'directory':'music',"
|
||||
"'fileCount':2,'files':["
|
||||
"{'averageSpeed':937500,'state':'InProgress','filename':'t1.flac','size':30000000,'username':'peer1'},"
|
||||
"{'averageSpeed':937500,'state':'InProgress','filename':'t2.flac','size':25000000,'username':'peer1'}"
|
||||
"]}]];"
|
||||
" def do_GET(s):"
|
||||
" if s.path=='/api/v0/transfers/uploads':"
|
||||
" if s.headers.get('X-API-Key')!=EXPECTED_KEY:"
|
||||
" s.send_error(401);"
|
||||
" return;"
|
||||
" s.send_response(200);"
|
||||
" s.send_header('Content-Type','application/json');"
|
||||
" s.end_headers();"
|
||||
" s.wfile.write(json.dumps(H.uploads_response).encode());"
|
||||
" else: s.send_error(404);"
|
||||
" def log_message(*a): pass;"
|
||||
f"server=http.server.HTTPServer(('127.0.0.1',{mock_slskd_port}),H);"
|
||||
"threading.Thread(target=server.serve_forever,daemon=True).start();"
|
||||
"import signal; signal.pause()\" &"
|
||||
)
|
||||
server.wait_for_open_port(mock_slskd_port)
|
||||
time.sleep(4) # Let monitor poll and adjust
|
||||
|
||||
slskd_dl = get_alt_dl_limit()
|
||||
# With 15 Mbps of slskd uploads consuming the shared budget, the
|
||||
# remaining bandwidth for torrents must be lower than baseline.
|
||||
assert slskd_dl < baseline_dl, \
|
||||
f"Slskd uploads should reduce torrent budget. baseline={baseline_dl}, with_slskd={slskd_dl}"
|
||||
|
||||
# Stop Jellyfin playback
|
||||
playback_stop = {{
|
||||
"ItemId": movie_id,
|
||||
"MediaSourceId": media_source_id,
|
||||
"PlaySessionId": "test-play-session-slskd",
|
||||
"PositionTicks": 50000000,
|
||||
}}
|
||||
stop_cmd = (
|
||||
f"curl -sf -X POST 'http://{server_ip}:8096/Sessions/Playing/Stopped' "
|
||||
f"-d '{json.dumps(playback_stop)}' "
|
||||
"-H 'Content-Type:application/json' "
|
||||
f"-H 'X-Emby-Authorization:{client_auth}, Token={client_token}'"
|
||||
)
|
||||
client.succeed(stop_cmd)
|
||||
time.sleep(3)
|
||||
|
||||
# Clean up: stop mock slskd and restore normal monitor
|
||||
server.succeed("pkill -f 'H(http' || true")
|
||||
server.succeed("systemctl stop monitor-slskd || 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)
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user