#!/usr/bin/env bash # T33: in-flight long-lived request vs forceful (--grace-period=0) pod kill. # # Deviations from the raw spec, discovered empirically while running this test # and preserved here so re-runs are trustworthy: # 1. Timing-sensitive steps (background curl launch -> sleep N -> force-kill # pod -> collect result) MUST run inside ONE shell process. Splitting them # across separate invocations introduces unknown/uncontrolled latency # between "launch" and "kill" and can make an already-finished request # look like it survived the kill. Each case below is one atomic script. # 2. Case 2 originally targeted httpbin's /stream/60. That endpoint emits all # chunks near-instantly (no per-chunk delay) on kennethreitz/httpbin, so # it always completes before a "sleep 2" kill can land mid-transfer. It is # replaced with /drip?duration=10&numbytes=1000, which throttles the body # over a real wall-clock duration and genuinely exercises "kill mid-body". # 3. Plain h2c (--http2-prior-knowledge) requests through the sidecar to this # httpbin backend were observed to intermittently hang for the full # client timeout with NO response at all, independent of any pod kill # (~3/5 in a baseline probe). This is pre-existing protocol flakiness in # this environment, not a kill-signal; it adds noise but does not change # the qualitative Case 1 vs Case 2 conclusion recorded in verdict.json. set -euo pipefail NS=istio-vt-t33 MANIFEST="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/manifest.yaml" cleanup() { echo "[cleanup] deleting namespace $NS" kubectl delete namespace "$NS" --wait=false --ignore-not-found } trap cleanup EXIT echo "[setup] create namespace $NS with istio-injection=enabled" kubectl create namespace "$NS" kubectl label namespace "$NS" istio-injection=enabled echo "[setup] apply manifest" kubectl apply -f "$MANIFEST" echo "[setup] wait for deployments + client pod" kubectl -n "$NS" wait --for=condition=available deploy/killobs-single deploy/killobs-double --timeout=90s kubectl -n "$NS" wait --for=condition=Ready pod -l app=client --timeout=120s echo echo "=== CASE 1: HTTP/1.1 long request (/delay/10) vs forceful kill ===" date -u +"T0=%H:%M:%S.%3N kickoff" ( kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null \ -w 'h1_result=%{http_code} exit_marker=OK\n' --max-time 15 \ "http://killobs-single.$NS.svc.homelab.local/delay/10" \ > /tmp/killobs_h1.log 2>&1; echo "h1_curl_exit=$?" >> /tmp/killobs_h1.log ) & sleep 2 POD1=$(kubectl -n "$NS" get pod -l app=killobs-single -o jsonpath='{.items[0].metadata.name}') kubectl -n "$NS" delete pod "$POD1" --grace-period=0 --force wait cat /tmp/killobs_h1.log kubectl -n "$NS" wait --for=condition=available deploy/killobs-single --timeout=60s echo echo "=== CASE 2: HTTP/2 (h2c prior-knowledge) throttled /drip stream vs forceful kill ===" date -u +"T0=%H:%M:%S.%3N kickoff" ( kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null --http2-prior-knowledge \ -w 'h2_result=%{http_code} exit_marker=OK\n' --max-time 15 \ "http://killobs-single.$NS.svc.homelab.local/drip?duration=10&numbytes=1000" \ > /tmp/killobs_h2.log 2>&1; echo "h2_curl_exit=$?" >> /tmp/killobs_h2.log ) & sleep 2 POD1B=$(kubectl -n "$NS" get pod -l app=killobs-single -o jsonpath='{.items[0].metadata.name}') kubectl -n "$NS" delete pod "$POD1B" --grace-period=0 --force wait cat /tmp/killobs_h2.log kubectl -n "$NS" wait --for=condition=available deploy/killobs-single --timeout=60s echo echo "=== CASE 3: replicas=2 masking -- kill ONE replica while a request loop runs against the Service ===" ( kubectl -n "$NS" exec client -c curl -- sh -c \ 'for i in $(seq 1 60); do curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 '"http://killobs-double.$NS.svc.homelab.local/get"'; sleep 0.2; done' \ > /tmp/killobs_double.log 2>&1 ) & LOOP_PID=$! sleep 1 POD2=$(kubectl -n "$NS" get pod -l app=killobs-double -o jsonpath='{.items[0].metadata.name}') kubectl -n "$NS" delete pod "$POD2" --grace-period=0 --force wait "$LOOP_PID" echo "--- result distribution (expect overwhelmingly 200) ---" sort /tmp/killobs_double.log | uniq -c