Stream object transfers with shared byte throttling

This commit is contained in:
ms2k 2026-07-18 20:48:26 +09:00
commit 4b044b1456

View file

@ -362,6 +362,374 @@ spec:
exit 1
fi
if [ "${NUTHATCH_STREAMING_MODE:-}" = large ]; then
large_bytes="${NUTHATCH_STREAMING_LARGE_BYTES:?}"
printf 'Preparing %s-byte streaming object\n' "$large_bytes"
large_digest="$(
dd if=/dev/zero bs=1048576 \
count="$((large_bytes / 1048576))" status=none |
sha256sum |
cut -d' ' -f1
)"
printf 'Uploading constrained-memory streaming object\n'
status="$(
dd if=/dev/zero bs=1048576 \
count="$((large_bytes / 1048576))" status=none |
curl --silent --show-error \
--output /tmp/large-upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $large_bytes" \
--header "X-Content-SHA256: $large_digest" \
--header "X-CSRF-Token: $csrf" \
--request POST \
--upload-file - \
"$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000001"
)"
printf 'Large upload status=%s\n' "$status"
test "$status" = 200
grep -F '"result":"succeeded"' /tmp/large-upload.json
printf 'Downloading constrained-memory streaming object\n'
download_digest="$(
curl --fail --silent --show-error \
--cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin" |
sha256sum |
cut -d' ' -f1
)"
printf 'Large download digest expected=%s actual=%s\n' \
"$large_digest" "$download_digest"
test "$download_digest" = "$large_digest"
printf x >/tmp/tiny
tiny_digest="$(sha256sum /tmp/tiny | cut -d' ' -f1)"
for attempt in first replay; do
status="$(
curl --silent --show-error \
--output "/tmp/prior-desired-$attempt.json" \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $large_bytes" \
--header "X-Content-SHA256: $large_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000007"
)"
test "$status" = 200
grep -F '"result":"succeeded"' \
"/tmp/prior-desired-$attempt.json"
done
status="$(
curl --silent --show-error \
--output /tmp/size-mismatch.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 2' \
--header "X-Content-SHA256: $tiny_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=size-mismatch.bin&operation_id=70000000-0000-4000-8000-000000000002"
)"
printf 'Size mismatch status=%s body=' "$status"
cat /tmp/size-mismatch.json
printf '\n'
test "$status" = 400
grep -F '"error":"size_mismatch"' /tmp/size-mismatch.json
status="$(
curl --silent --show-error \
--output /tmp/identity-mismatch.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1' \
--header "X-Content-SHA256: $(printf '%064d' 0)" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=identity-mismatch.bin&operation_id=70000000-0000-4000-8000-000000000003"
)"
printf 'Identity mismatch status=%s body=' "$status"
cat /tmp/identity-mismatch.json
printf '\n'
test "$status" = 400
grep -F '"error":"content_identity_mismatch"' \
/tmp/identity-mismatch.json
status="$(
set +o pipefail
dd if=/dev/zero bs=1048576 count=160 status=none |
curl --silent --show-error \
--max-time 5 \
--limit-rate 64k \
--output /tmp/too-large.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $NUTHATCH_STREAMING_OVER_LIMIT_BYTES" \
--header "X-Content-SHA256: $tiny_digest" \
--header "X-CSRF-Token: $csrf" \
--request POST \
--upload-file - \
"$base/upload?root=personal&path=too-large.bin&operation_id=70000000-0000-4000-8000-000000000004"
)"
printf 'Over-limit status=%s body=' "$status"
cat /tmp/too-large.json
printf '\n'
test "$status" = 413
grep -F '"error":"object_too_large"' /tmp/too-large.json
if dd if=/dev/zero bs=1048576 \
count="$((large_bytes / 1048576))" status=none |
curl --silent --show-error \
--max-time 1 \
--limit-rate 64k \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $large_bytes" \
--header "X-Content-SHA256: $large_digest" \
--header "X-CSRF-Token: $csrf" \
--request POST \
--upload-file - \
"$base/upload?root=personal&path=aborted-upload.bin&operation_id=70000000-0000-4000-8000-000000000005" \
>/tmp/aborted-upload.json; then
exit 1
fi
aborted_upload_status="$(
curl --silent --output /dev/null \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$base/download?root=personal&path=aborted-upload.bin"
)"
printf 'Aborted upload lookup status=%s\n' \
"$aborted_upload_status"
test "$aborted_upload_status" = 404
for retry in $(seq 1 20); do
status="$(
curl --silent --show-error \
--output /tmp/aborted-upload-replay.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $large_bytes" \
--header "X-Content-SHA256: $large_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=aborted-upload.bin&operation_id=70000000-0000-4000-8000-000000000005"
)"
test "$status" = 200
if grep -F '"result":"failed"' \
/tmp/aborted-upload-replay.json >/dev/null; then
break
fi
sleep 0.25
done
grep -F '"result":"failed"' /tmp/aborted-upload-replay.json
if dd if=/dev/zero bs=1048576 \
count="$((large_bytes / 1048576))" status=none |
curl --silent --show-error \
--max-time 1 \
--limit-rate 64k \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $large_bytes" \
--header "X-Content-SHA256: $(printf '%064d' 0)" \
--header "X-CSRF-Token: $csrf" \
--request POST \
--upload-file - \
"$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000006" \
>/tmp/aborted-overwrite.json; then
exit 1
fi
preserved_digest="$(
curl --fail --silent --show-error \
--cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin" |
sha256sum |
cut -d' ' -f1
)"
test "$preserved_digest" = "$large_digest"
if curl --silent --show-error \
--max-time 1 \
--limit-rate 64k \
--cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin" \
>/tmp/aborted-download; then
exit 1
fi
aborted_bytes="$(wc -c </tmp/aborted-download | tr -d ' ')"
test "$aborted_bytes" -gt 0
test "$aborted_bytes" -lt "$large_bytes"
curl --fail --silent --show-error "$base/readyz" |
grep -F '"ready":true'
printf 'Large streaming and abort probes passed\n'
exit 0
fi
if [ "${NUTHATCH_STREAMING_MODE:-}" = throttle ]; then
csrf_b="$(login "$NUTHATCH_OIDC_USER_B" b)"
dd if=/dev/zero of=/tmp/joint-upload bs=65536 \
count=1 status=none
joint_digest="$(
sha256sum /tmp/joint-upload | cut -d' ' -f1
)"
status="$(
curl --silent --show-error \
--output /tmp/joint-upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 65536' \
--header "X-Content-SHA256: $joint_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/joint-upload \
"$base/upload?root=personal&path=joint-throttle.bin&operation_id=70000000-0000-4000-8000-000000000013"
)"
test "$status" = 200
grep -F '"result":"succeeded"' /tmp/joint-upload.json
dd if=/dev/zero of=/tmp/throttle-upload bs=1048576 \
count=1 status=none
upload_digest="$(
sha256sum /tmp/throttle-upload | cut -d' ' -f1
)"
status="$(
curl --silent --show-error \
--output /tmp/throttle-upload.json \
--write-out '%{http_code}' \
--cookie /tmp/cookies-b \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1048576' \
--header "X-Content-SHA256: $upload_digest" \
--header "X-CSRF-Token: $csrf_b" \
--data-binary @/tmp/throttle-upload \
"$base/upload?root=personal&path=throttled-upload.bin&operation_id=71000000-0000-4000-8000-000000000001"
)"
test "$status" = 429
grep -F '"error":"bandwidth_exceeded"' /tmp/throttle-upload.json
printf x >/tmp/tiny
test "$(
curl --silent --output /tmp/prestart-upload.json \
--write-out '%{http_code}' \
--cookie /tmp/cookies-b \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1' \
--header "X-Content-SHA256: $upload_digest" \
--header "X-CSRF-Token: $csrf_b" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=prestart.bin&operation_id=71000000-0000-4000-8000-000000000002"
)" = 429
curl --silent --show-error --cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin" \
>/tmp/parallel-a &
first_pid=$!
curl --silent --show-error --cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin" \
>/tmp/parallel-b &
second_pid=$!
first_ok=true
second_ok=true
wait "$first_pid" || first_ok=false
wait "$second_pid" || second_ok=false
test "$first_ok" = false
test "$second_ok" = false
parallel_bytes="$(
wc -c /tmp/parallel-a /tmp/parallel-b |
awk 'END {print $1}'
)"
printf 'Parallel downloads moved %s bytes with burst %s\n' \
"$parallel_bytes" \
"$NUTHATCH_STREAMING_THROTTLE_BURST_BYTES"
test "$parallel_bytes" -gt 0
joint_limit="$(
expr "$NUTHATCH_STREAMING_THROTTLE_BURST_BYTES" - 65536 + 4096
)"
test "$parallel_bytes" -le "$joint_limit"
prestart_status="$(
curl --silent --output /tmp/prestart-download.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin"
)"
printf 'Post-exhaustion download status=%s\n' "$prestart_status"
test "$prestart_status" = 429
printf 'Shared incremental throttle probes passed\n'
exit 0
fi
if [ "${NUTHATCH_STREAMING_MODE:-}" = refill ]; then
dd if=/dev/zero of=/tmp/refill-upload bs=65536 \
count=1 status=none
refill_digest="$(
sha256sum /tmp/refill-upload | cut -d' ' -f1
)"
for suffix in initial refilled; do
if [ "$suffix" = refilled ]; then
sleep 2
refill_operation=72000000-0000-4000-8000-000000000002
else
refill_operation=72000000-0000-4000-8000-000000000001
fi
status="$(
curl --silent --show-error \
--output "/tmp/refill-$suffix.json" \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 65536' \
--header "X-Content-SHA256: $refill_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/refill-upload \
"$base/upload?root=personal&path=refill-$suffix.bin&operation_id=$refill_operation"
)"
test "$status" = 200
grep -F '"result":"succeeded"' "/tmp/refill-$suffix.json"
if [ "$suffix" = initial ]; then
test "$(
curl --silent --output /tmp/refill-exhausted.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 65536' \
--header "X-Content-SHA256: $refill_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/refill-upload \
"$base/upload?root=personal&path=refill-exhausted.bin&operation_id=72000000-0000-4000-8000-000000000003"
)" = 429
fi
done
printf 'Token-bucket exhaustion and refill probes passed\n'
exit 0
fi
if [ "${NUTHATCH_STREAMING_MODE:-}" = unavailable ]; then
printf x >/tmp/tiny
tiny_digest="$(sha256sum /tmp/tiny | cut -d' ' -f1)"
test "$(
curl --silent --output /tmp/unavailable-upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1' \
--header "X-Content-SHA256: $tiny_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/tiny \
"$base/upload?root=personal&path=unavailable.bin&operation_id=72000000-0000-4000-8000-000000000001"
)" = 503
grep -F '"error":"throttle_unavailable"' \
/tmp/unavailable-upload.json
test "$(
curl --silent --output /tmp/unavailable-download.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$base/download?root=personal&path=streaming-large.bin"
)" = 503
grep -F '"error":"throttle_unavailable"' \
/tmp/unavailable-download.json
printf 'Unavailable coordination probes passed\n'
exit 0
fi
exercise_root() {
root="$1"
suffix="$2"
@ -563,6 +931,30 @@ spec:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_REDIRECT_URI
- name: NUTHATCH_STREAMING_MODE
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_STREAMING_MODE
optional: true
- name: NUTHATCH_STREAMING_LARGE_BYTES
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_STREAMING_LARGE_BYTES
optional: true
- name: NUTHATCH_STREAMING_OVER_LIMIT_BYTES
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_STREAMING_OVER_LIMIT_BYTES
optional: true
- name: NUTHATCH_STREAMING_THROTTLE_BURST_BYTES
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_STREAMING_THROTTLE_BURST_BYTES
optional: true
- name: NUTHATCH_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef: