#!/usr/bin/env bash # T34: hc-mock FSM (OPEN/DRAINING/CLOSING/CLOSED) + illegal-transition guard + # DRAIN_TIMEOUT forced-advance, reproducing gt__src-w2-hc-fsm C4-C7. set -euo pipefail NS=istio-vt-t34 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MANIFEST="$SCRIPT_DIR/manifest.yaml" cleanup() { echo "--- cleanup: deleting namespace $NS ---" kubectl delete namespace "$NS" --wait=false --ignore-not-found } trap cleanup EXIT echo "=== create namespace ===" kubectl create namespace "$NS" kubectl label namespace "$NS" istio-injection=enabled echo "=== apply manifest ===" kubectl apply -f "$MANIFEST" echo "=== wait for workloads ready ===" kubectl -n "$NS" wait --for=condition=Ready pod/client --timeout=120s kubectl -n "$NS" wait --for=condition=Ready pod/hc-mock --timeout=120s kubectl -n "$NS" wait --for=condition=available deploy/echo --timeout=120s kubectl -n "$NS" wait --for=condition=available deploy/slow-echo --timeout=120s hc() { # $1 = method(GET/POST) $2 = path if [ "$1" = "POST" ]; then kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null -w '%{http_code}\n' -X POST \ "http://hc-mock.$NS.svc.homelab.local:18180/$2" else kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null -w '%{http_code}\n' \ "http://hc-mock.$NS.svc.homelab.local:18180/$2" fi } restart_hc_mock() { kubectl -n "$NS" delete pod hc-mock --wait=true kubectl apply -f "$MANIFEST" kubectl -n "$NS" wait --for=condition=Ready pod/hc-mock --timeout=60s sleep 5 # let endpoints propagate } echo "=== illegal transition: OPEN -> close-lb directly must be 409 ===" hc POST close-lb echo "=== 2-step: drain then close-lb must succeed (200) ===" hc POST drain hc POST close-lb echo "=== reset to clean OPEN, walk state table ===" restart_hc_mock for ep in health_check.html health live; do echo -n "OPEN $ep="; hc GET "$ep" done hc POST drain for ep in health_check.html health live; do echo -n "DRAINING $ep="; hc GET "$ep" done echo "=== reopen from DRAINING must immediately return to OPEN ===" hc POST reopen echo -n "OPEN_after_reopen health_check.html="; hc GET health_check.html hc POST drain hc POST close-lb for ep in health_check.html health live; do echo -n "CLOSING $ep="; hc GET "$ep" done hc POST close for ep in health_check.html health live; do echo -n "CLOSED $ep="; hc GET "$ep" done echo "=== DRAIN_TIMEOUT forced-advance under real in-mesh load ===" restart_hc_mock hc POST drain # Sustain overlapping in-mesh traffic to slow-echo across the poll window # (a single 125s-delay request does not work: kennethreitz/httpbin classic # image caps /delay/ at min(n,10) regardless of requested n). for i in $(seq 1 25); do kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null \ "http://slow-echo.$NS.svc.homelab.local/delay/10" & sleep 6 done & TRAFFIC_PID=$! POLL_INTERVAL=2 DRAIN_TIMEOUT=120 START=$(date +%s) while true; do ACTIVE=$(kubectl -n "$NS" exec deploy/slow-echo -c istio-proxy -- \ curl -s 'localhost:15000/stats?filter=downstream_rq_active' 2>/dev/null \ | awk -F': ' '{sum+=$2} END{print sum+0}') NOW=$(date +%s); EL=$((NOW-START)) echo "t=${EL}s active=${ACTIVE}" if [ "$EL" -ge "$DRAIN_TIMEOUT" ]; then echo "DRAIN_TIMEOUT reached -- forcing close-lb regardless of active" hc POST close-lb break fi sleep "$POLL_INTERVAL" done wait "$TRAFFIC_PID" 2>/dev/null || true echo "=== done ==="