421 lines
15 KiB
Bash
Executable File
421 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
source "$(dirname "$0")/lib.sh"
|
|
|
|
bundle_hash=547cec172aab2f4d418d73d4f5fc0d2794e971b7
|
|
bundle_torrent=ZDQ6aW5mb2Q1OmZpbGVzbGQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsOTphbHBoYS5iaW5lZWQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsNjpuZXN0ZWQ5OmJyYXZvLmJpbmVlZDY6bGVuZ3RoaTE2Mzg0ZTQ6cGF0aGw2Om5lc3RlZDExOmNoYXJsaWUuYmluZWVkNjpsZW5ndGhpMTYzODRlNDpwYXRobDk6ZGVsdGEuYmluZWVlNDpuYW1lNjpidW5kbGUxMjpwaWVjZSBsZW5ndGhpMTYzODRlNjpwaWVjZXM4MDp9tnus7uyzpCC/N6e+ykpFGF+PPHmZb79ZCq2dp84v4EgIIzMZE7bwEMQCvl+7qybWEDECinYwiKcGKn82sWFjoNungr001vTmtGeLhKBGd2Vl
|
|
legacy_bundle_hash=305af159425d76140eea48d20cfcfbb3f8dccf46
|
|
shared_hash=03fbcf663bd3fe80c298ca54a55780a6b1d4b43b
|
|
shared_torrent=ZDQ6aW5mb2Q1OmZpbGVzbGQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsNjpuZXN0ZWQ5OmJyYXZvLmJpbmVlZTQ6bmFtZTY6YnVuZGxlMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6cGllY2VzMjA6eZlvv1kKrZ2nzi/gSAgjMxkTtvBlZQ==
|
|
cancel_hash=330be0cb7c2201135a2de63b28e77993745ff688
|
|
cancel_torrent=ZDQ6aW5mb2Q2Omxlbmd0aGkzMWU0Om5hbWUxMTpmaXh0dXJlLmJpbjEyOnBpZWNlIGxlbmd0aGkxNjM4NGU2OnBpZWNlczIwOlFlB05MtIUU4oem2MNz5LJW/GI6ZWU=
|
|
|
|
control_scenario() {
|
|
compose_control exec -T control \
|
|
python /e2e/scenarios/complex_jobs.py "$@"
|
|
}
|
|
|
|
qb_endpoint() {
|
|
case "$1" in
|
|
cache-1) printf '%s' http://qb-cache-1:8080/api/v2 ;;
|
|
cache-2) printf '%s' http://qb-cache-2:8080/api/v2 ;;
|
|
archive-1) printf '%s' http://qb-archive-1:8080/api/v2 ;;
|
|
archive-2) printf '%s' http://qb-archive-2:8080/api/v2 ;;
|
|
*) printf 'unknown E2E node: %s\n' "$1" >&2; return 2 ;;
|
|
esac
|
|
}
|
|
|
|
wait_qb_present() {
|
|
local node=$1
|
|
local info_hash=$2
|
|
local deadline=$((SECONDS + 30))
|
|
while (( SECONDS < deadline )); do
|
|
local records
|
|
records=$(compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
"http://127.0.0.1:8080/api/v2/torrents/info?hashes=$info_hash")
|
|
if [[ "$records" != "[]" ]]; then
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
printf 'timed out waiting for torrent %s on %s\n' \
|
|
"$info_hash" "$node" >&2
|
|
return 1
|
|
}
|
|
|
|
delete_torrent() {
|
|
local node=$1
|
|
local info_hash=$2
|
|
compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/delete \
|
|
--data-urlencode "hashes=$info_hash" \
|
|
--data-urlencode "deleteFiles=true" >/dev/null || true
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint "$node")" \
|
|
--info-hash "$info_hash" \
|
|
--absent >/dev/null
|
|
}
|
|
|
|
install_torrent() {
|
|
local node=$1
|
|
local info_hash=$2
|
|
local encoded=$3
|
|
local save_path=$4
|
|
local selected=$5
|
|
local all_indices=$6
|
|
compose_node "$node" exec -T qbittorrent /bin/sh -c \
|
|
"printf '%s' '$encoded' | base64 -d > /tmp/e2e.torrent"
|
|
compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/add \
|
|
-F torrents=@/tmp/e2e.torrent \
|
|
-F "savepath=$save_path" \
|
|
-F stopped=true >/dev/null
|
|
wait_qb_present "$node" "$info_hash"
|
|
compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
|
|
--data-urlencode "hash=$info_hash" \
|
|
--data-urlencode "id=$all_indices" \
|
|
--data-urlencode "priority=0" >/dev/null
|
|
compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
|
|
--data-urlencode "hash=$info_hash" \
|
|
--data-urlencode "id=$selected" \
|
|
--data-urlencode "priority=1" >/dev/null
|
|
compose_node "$node" exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
|
|
--data-urlencode "hashes=$info_hash" >/dev/null
|
|
}
|
|
|
|
assert_qb() {
|
|
local node=$1
|
|
local info_hash=$2
|
|
local selected=$3
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint "$node")" \
|
|
--info-hash "$info_hash" \
|
|
--selected "$selected" >/dev/null
|
|
}
|
|
|
|
archive2_download_mode=
|
|
restore_archive2_download_mode() {
|
|
if [[ -n "$archive2_download_mode" ]]; then
|
|
compose_node archive-2 exec -T qbittorrent \
|
|
chmod "$archive2_download_mode" /downloads >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap restore_archive2_download_mode EXIT
|
|
|
|
for node in cache-1 cache-2 archive-1 archive-2; do
|
|
delete_torrent "$node" "$legacy_bundle_hash"
|
|
delete_torrent "$node" "$bundle_hash"
|
|
delete_torrent "$node" "$shared_hash"
|
|
delete_torrent "$node" "$cancel_hash"
|
|
compose_node "$node" exec -T qbittorrent /bin/sh -c \
|
|
'rm -f /downloads/bundle/alpha.bin \
|
|
/downloads/bundle/nested/bravo.bin \
|
|
/downloads/bundle/nested/charlie.bin \
|
|
/downloads/bundle/delta.bin \
|
|
/downloads/bundle/unknown.keep \
|
|
/downloads/fixture.bin; \
|
|
rmdir /downloads/bundle/nested /downloads/bundle 2>/dev/null || true'
|
|
compose_node "$node" exec -T qbittorrent /bin/sh -c \
|
|
'chown -R 1001:1001 /downloads/bundle 2>/dev/null || true'
|
|
done
|
|
|
|
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'mkdir -p /downloads/bundle/nested; \
|
|
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" A \
|
|
> /downloads/bundle/alpha.bin; \
|
|
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" C \
|
|
> /downloads/bundle/nested/charlie.bin'
|
|
install_torrent cache-1 "$bundle_hash" "$bundle_torrent" \
|
|
/downloads '0|2' '0|1|2|3'
|
|
assert_qb cache-1 "$bundle_hash" '0,2'
|
|
|
|
compose_node cache-2 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'mkdir -p /downloads/bundle/nested; \
|
|
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" B \
|
|
> /downloads/bundle/nested/bravo.bin; \
|
|
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" D \
|
|
> /downloads/bundle/delta.bin'
|
|
install_torrent cache-2 "$bundle_hash" "$bundle_torrent" \
|
|
/downloads '1|3' '0|1|2|3'
|
|
assert_qb cache-2 "$bundle_hash" '1,3'
|
|
|
|
control_scenario stale-preview \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-1 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '0' >/dev/null
|
|
|
|
control_scenario transfer \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-1 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '0,2' \
|
|
--expected-selection '0,2' \
|
|
--expected-delta '0,2'
|
|
assert_qb archive-1 "$bundle_hash" '0,2'
|
|
|
|
control_scenario preview-error \
|
|
--operation evict_cache \
|
|
--cache-client cache-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--status 400 \
|
|
--contains 'missing file indices'
|
|
|
|
control_scenario transfer \
|
|
--operation archive \
|
|
--source-client cache-2 \
|
|
--target-client archive-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '1,3' \
|
|
--expected-selection '1,3' \
|
|
--expected-delta '1,3'
|
|
assert_qb archive-2 "$bundle_hash" '1,3'
|
|
|
|
compose_node cache-2 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" A \
|
|
> /downloads/bundle/alpha.bin; \
|
|
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" C \
|
|
> /downloads/bundle/nested/charlie.bin'
|
|
compose_node cache-2 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
|
|
--data-urlencode "hash=$bundle_hash" \
|
|
--data-urlencode 'id=0|1|2|3' \
|
|
--data-urlencode priority=1 >/dev/null
|
|
compose_node cache-2 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
|
|
--data-urlencode "hashes=$bundle_hash" >/dev/null
|
|
assert_qb cache-2 "$bundle_hash" '0,1,2,3'
|
|
|
|
control_scenario evict \
|
|
--cache-client cache-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--minimum-coverage-proofs 2
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint cache-2)" \
|
|
--info-hash "$bundle_hash" \
|
|
--absent >/dev/null
|
|
|
|
control_scenario transfer \
|
|
--operation unarchive \
|
|
--source-client archive-1 \
|
|
--target-client cache-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '0,2' \
|
|
--expected-selection '0,2' \
|
|
--expected-delta '0,2'
|
|
assert_qb cache-2 "$bundle_hash" '0,2'
|
|
|
|
control_scenario transfer \
|
|
--operation unarchive \
|
|
--source-client archive-2 \
|
|
--target-client cache-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '1,3' \
|
|
--expected-selection '1,3' \
|
|
--expected-delta '1,3'
|
|
assert_qb cache-2 "$bundle_hash" '0,1,2,3'
|
|
|
|
control_scenario transfer \
|
|
--operation archive \
|
|
--source-client cache-2 \
|
|
--target-client archive-1 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '1,3' \
|
|
--expected-selection '1,3' \
|
|
--expected-delta '1,3'
|
|
assert_qb archive-1 "$bundle_hash" '0,1,2,3'
|
|
|
|
control_scenario transfer \
|
|
--operation archive \
|
|
--source-client cache-2 \
|
|
--target-client archive-2 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '0,2' \
|
|
--expected-selection '0,2' \
|
|
--expected-delta '0,2'
|
|
assert_qb archive-2 "$bundle_hash" '0,1,2,3'
|
|
|
|
control_scenario preview-error \
|
|
--operation archive \
|
|
--source-client cache-2 \
|
|
--target-client archive-1 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '0' \
|
|
--status 400 \
|
|
--contains 'already contains'
|
|
|
|
control_scenario evict \
|
|
--cache-client cache-1 \
|
|
--info-hash "$bundle_hash"
|
|
control_scenario transfer \
|
|
--operation unarchive \
|
|
--source-client archive-1 \
|
|
--target-client cache-1 \
|
|
--info-hash "$bundle_hash" \
|
|
--selected '1' \
|
|
--expected-selection '1' \
|
|
--expected-delta '1'
|
|
assert_qb cache-1 "$bundle_hash" '1'
|
|
|
|
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'printf "preserve-unknown\n" > /downloads/bundle/unknown.keep'
|
|
install_torrent cache-1 "$shared_hash" "$shared_torrent" \
|
|
/downloads '0' '0'
|
|
assert_qb cache-1 "$shared_hash" '0'
|
|
control_scenario evict \
|
|
--cache-client cache-1 \
|
|
--info-hash "$bundle_hash"
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint cache-1)" \
|
|
--info-hash "$bundle_hash" \
|
|
--absent >/dev/null
|
|
assert_qb cache-1 "$shared_hash" '0'
|
|
compose_node cache-1 exec -T qbittorrent test -f \
|
|
/downloads/bundle/nested/bravo.bin
|
|
compose_node cache-1 exec -T qbittorrent test -f \
|
|
/downloads/bundle/unknown.keep
|
|
|
|
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'printf "archive-control-e2e-happy-path\n" > /downloads/fixture.bin'
|
|
install_torrent cache-1 "$cancel_hash" "$cancel_torrent" \
|
|
/downloads '0' '0'
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
|
|
control_scenario queued-cancel \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-1 \
|
|
--info-hash "$cancel_hash"
|
|
compose_node cache-1 exec -T qbittorrent test -f /downloads/fixture.bin
|
|
|
|
cancel_job=$(control_scenario drive \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash" \
|
|
--through target_materialize)
|
|
control_scenario cancel --job-id "$cancel_job"
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint archive-2)" \
|
|
--info-hash "$cancel_hash" \
|
|
--absent >/dev/null
|
|
if compose_node archive-2 exec -T qbittorrent \
|
|
test -e /downloads/fixture.bin; then
|
|
printf 'precommit cancellation left a materialized target file\n' >&2
|
|
exit 1
|
|
fi
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
|
|
restart_job=$(control_scenario drive \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-1 \
|
|
--info-hash "$cancel_hash" \
|
|
--through source_stage)
|
|
compose_control restart control
|
|
sleep 2
|
|
control_scenario resume --job-id "$restart_job"
|
|
assert_qb archive-1 "$cancel_hash" '0'
|
|
|
|
delete_torrent archive-2 "$cancel_hash"
|
|
replay_job=$(control_scenario drive \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash" \
|
|
--through source_stage)
|
|
compose_node archive-2 stop client
|
|
sleep 1
|
|
control_scenario advance \
|
|
--job-id "$replay_job" \
|
|
--action syncthing_transfer >/dev/null
|
|
compose_node archive-2 start client
|
|
control_scenario resume --job-id "$replay_job"
|
|
control_scenario assert-qb \
|
|
--endpoint "$(qb_endpoint archive-2)" \
|
|
--info-hash "$cancel_hash" \
|
|
--selected '0' \
|
|
--no-downloaded >/dev/null
|
|
|
|
delete_torrent archive-2 "$cancel_hash"
|
|
stale_source_job=$(control_scenario create-paused \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash")
|
|
compose_node cache-1 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
|
|
--data-urlencode "hash=$cancel_hash" \
|
|
--data-urlencode id=0 \
|
|
--data-urlencode priority=0 >/dev/null
|
|
control_scenario expect-failure \
|
|
--job-id "$stale_source_job" \
|
|
--contains 'source resource changed after job confirmation'
|
|
compose_node cache-1 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
|
|
--data-urlencode "hash=$cancel_hash" \
|
|
--data-urlencode id=0 \
|
|
--data-urlencode priority=1 >/dev/null
|
|
compose_node cache-1 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
|
|
--data-urlencode "hashes=$cancel_hash" >/dev/null
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
|
|
target_appeared_job=$(control_scenario create-paused \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash")
|
|
compose_node archive-2 exec -T qbittorrent /bin/sh -c \
|
|
"printf '%s' '$cancel_torrent' | base64 -d > /tmp/appeared.torrent"
|
|
compose_node archive-2 exec -T qbittorrent curl -fsS \
|
|
-X POST http://127.0.0.1:8080/api/v2/torrents/add \
|
|
-F torrents=@/tmp/appeared.torrent \
|
|
-F savepath=/downloads \
|
|
-F stopped=true >/dev/null
|
|
wait_qb_present archive-2 "$cancel_hash"
|
|
control_scenario expect-failure \
|
|
--job-id "$target_appeared_job" \
|
|
--contains 'target resource appeared after job confirmation'
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
delete_torrent archive-2 "$cancel_hash"
|
|
|
|
target_collision_job=$(control_scenario create-paused \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash")
|
|
compose_node archive-2 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
|
|
'printf "wrong-size\n" > /downloads/fixture.bin'
|
|
control_scenario expect-failure \
|
|
--job-id "$target_collision_job" \
|
|
--contains 'pre-existing destination size does not match'
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
compose_node archive-2 exec -T qbittorrent rm -f /downloads/fixture.bin
|
|
|
|
permission_job=$(control_scenario create-paused \
|
|
--operation archive \
|
|
--source-client cache-1 \
|
|
--target-client archive-2 \
|
|
--info-hash "$cancel_hash")
|
|
archive2_download_mode=$(compose_node archive-2 exec -T qbittorrent \
|
|
stat -c '%a' /downloads)
|
|
compose_node archive-2 exec -T qbittorrent chmod 0555 /downloads
|
|
control_scenario expect-failure \
|
|
--job-id "$permission_job" \
|
|
--contains 'Permission denied'
|
|
restore_archive2_download_mode
|
|
archive2_download_mode=
|
|
assert_qb cache-1 "$cancel_hash" '0'
|
|
|
|
printf '%s\n' \
|
|
'complex matrix passed: selective merges, union eviction, hostile retention,' \
|
|
'queued/partial cancellation, restart/replay, hostile state changes,' \
|
|
'target collisions, and permission denial'
|