Covers the previous patch ("MEDIUM: backend: report consistent-hash
LB bypass reason"):
- balance-hash-bypass-maxconn.vtc extends the existing
balance-hash-maxconn.vtc barrier-synchronized scenario (two
concurrent requests hashing to the same saturated server under
"hash-preserve-affinity maxconn") with assertions that the
"lbbypass" counter increments exactly once on the bypassed
server and on the backend, stays at 0 on the server that wasn't
bypassed, and that "txn.lb_bypass_reason" reports
"preserve-affinity-maxconn" on the bypassed request and an empty
value on the non-bypassed one.
- balance-hash-bypass-redispatch.vtc, modeled on
reg-tests/proxy/retry_on.vtc, uses a two-server hash-based
backend with one unreachable server and "option redispatch" to
verify the simpler, single-client, deterministic redispatch
case: "txn.lb_bypass_reason" reports "redispatch" and "lbbypass"
is incremented on both the backend and the unreachable server.
Both were run against the patched build with a locally built vtest
and pass; the existing reg-tests/balance/ suite (6 runnable tests,
one pre-existing "broken"-tagged test skipped) and reg-tests/log/
plus reg-tests/proxy/retry_on.vtc (17 tests) still pass unmodified.
---
.../balance/balance-hash-bypass-maxconn.vtc | 101 ++++++++++++++++++
.../balance-hash-bypass-redispatch.vtc | 71 ++++++++++++
2 files changed, 172 insertions(+)
create mode 100644 reg-tests/balance/balance-hash-bypass-maxconn.vtc
create mode 100644 reg-tests/balance/balance-hash-bypass-redispatch.vtc
diff --git a/reg-tests/balance/balance-hash-bypass-maxconn.vtc
b/reg-tests/balance/balance-hash-bypass-maxconn.vtc
new file mode 100644
index 000000000..bdc922384
--- /dev/null
+++ b/reg-tests/balance/balance-hash-bypass-maxconn.vtc
@@ -0,0 +1,101 @@
+vtest "Test for lb_bypass observability (counter + txn.lb_bypass_reason) with
hash-preserve-affinity maxconn"
+feature ignore_unknown_macro
+feature cmd "command -v socat"
+
+# Ensure c1 doesn't finish before c2
+barrier b1 cond 2
+
+# Ensure c2 only starts once c1's request is already in flight
+barrier b2 cond 2
+
+server s0 {
+ rxreq
+ barrier b2 sync
+ barrier b1 sync
+ txresp -hdr "Server: s0"
+} -start
+
+server s1 {
+ rxreq
+ barrier b1 sync
+ txresp -hdr "Server: s1"
+} -start
+
+haproxy h1 -arg "-L A" -conf {
+ global
+ .if feature(THREAD)
+ thread-groups 1
+ .endif
+ stats socket "${tmpdir}/h1/stats" level admin
+
+ defaults
+ mode http
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ listen px
+ bind "fd@${px}"
+ balance uri
+ hash-preserve-affinity maxconn
+ hash-type consistent
+ http-after-response set-header x-lb-bypass-reason
"%[txn.lb_bypass_reason]"
+
+ server srv0 ${s0_addr}:${s0_port} maxconn 1
+ server srv1 ${s1_addr}:${s1_port} maxconn 1
+
+} -start
+
+client c1 -connect ${h1_px_sock} {
+ txreq -url "/test-url"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.Server ~ s0
+ expect resp.http.x-lb-bypass-reason == ""
+} -start
+
+barrier b2 sync
+
+# srv0 is saturated, request should be assigned to srv1, and the bypass
+# should be recorded as a hash-preserve-affinity maxconn event.
+client c2 -connect ${h1_px_sock} {
+ txreq -url "/test-url"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.Server ~ s1
+ expect resp.http.x-lb-bypass-reason == "preserve-affinity-maxconn"
+} -start
+
+client c1 -wait
+client c2 -wait
+
+# c1's request was never bypassed (srv0 was the naive target and was free),
+# c2's request was bypassed exactly once, charged against srv0 (the naive
+# target it couldn't use) and against the "px" backend as a whole.
+shell {
+ stat=$(echo "show stat" | socat -t 5 "${tmpdir}/h1/stats" -)
+
+ be_bypass=$(echo "$stat" | awk -F, '
+ NR==1 { for (i=1; i<=NF; i++) if ($i=="lbbypass") c=i }
+ $1=="px" && $2=="BACKEND" { print $c }')
+ if [ "$be_bypass" != "1" ]; then
+ echo "expected 1 backend lbbypass, got '$be_bypass'"
+ exit 1
+ fi
+
+ srv0_bypass=$(echo "$stat" | awk -F, '
+ NR==1 { for (i=1; i<=NF; i++) if ($i=="lbbypass") c=i }
+ $1=="px" && $2=="srv0" { print $c }')
+ if [ "$srv0_bypass" != "1" ]; then
+ echo "expected 1 srv0 lbbypass, got '$srv0_bypass'"
+ exit 1
+ fi
+
+ srv1_bypass=$(echo "$stat" | awk -F, '
+ NR==1 { for (i=1; i<=NF; i++) if ($i=="lbbypass") c=i }
+ $1=="px" && $2=="srv1" { print $c }')
+ if [ "$srv1_bypass" != "0" ]; then
+ echo "expected 0 srv1 lbbypass, got '$srv1_bypass'"
+ exit 1
+ fi
+}
diff --git a/reg-tests/balance/balance-hash-bypass-redispatch.vtc
b/reg-tests/balance/balance-hash-bypass-redispatch.vtc
new file mode 100644
index 000000000..d746f8c6e
--- /dev/null
+++ b/reg-tests/balance/balance-hash-bypass-redispatch.vtc
@@ -0,0 +1,71 @@
+vtest "Test for lb_bypass observability (counter + txn.lb_bypass_reason) on
redispatch"
+feature ignore_unknown_macro
+feature cmd "command -v socat"
+
+# The hash-based backend only has two servers: "bad", which is unreachable,
+# and "good". Since the ring only has two candidates, a failed connect to
+# the naively-hashed "bad" server, followed by "option redispatch", must
+# land on "good" -- and the bypass must be reported as "redispatch".
+
+server s1 {
+ rxreq
+ txresp
+} -start
+
+haproxy h1 -conf {
+ global
+ .if feature(THREAD)
+ thread-groups 1
+ .endif
+ stats socket "${tmpdir}/h1/stats" level admin
+
+ defaults
+ mode http
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ frontend fe
+ bind "fd@${fe}"
+ default_backend be
+
+ http-after-response set-header x-reason "%[txn.lb_bypass_reason]"
+ http-after-response set-header x-redisp "%[txn.redispatched]"
+
+ backend be
+ timeout connect 200ms
+ balance uri
+ hash-type consistent
+ retries 1
+ option redispatch
+ server bad ${bad_backend}
+ server good ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+ txreq -url "/test-url"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-redisp == "1"
+ expect resp.http.x-reason == "redispatch"
+} -run
+
+shell {
+ stat=$(echo "show stat" | socat -t 5 "${tmpdir}/h1/stats" -)
+
+ be_bypass=$(echo "$stat" | awk -F, '
+ NR==1 { for (i=1; i<=NF; i++) if ($i=="lbbypass") c=i }
+ $1=="be" && $2=="BACKEND" { print $c }')
+ if [ "$be_bypass" != "1" ]; then
+ echo "expected 1 backend lbbypass, got '$be_bypass'"
+ exit 1
+ fi
+
+ bad_bypass=$(echo "$stat" | awk -F, '
+ NR==1 { for (i=1; i<=NF; i++) if ($i=="lbbypass") c=i }
+ $1=="be" && $2=="bad" { print $c }')
+ if [ "$bad_bypass" != "1" ]; then
+ echo "expected 1 'bad' server lbbypass, got '$bad_bypass'"
+ exit 1
+ fi
+}
--
2.52.0