From: Christopher Faulet Date: Fri, 2 Oct 2026 19:10:36 +0000 (+0200) Subject: REGTESTS: filters: Exercise Lua inheritance and state selection X-Git-Tag: v3.5-dev9~63 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D/doc/static/gitweb.js?a=commitdiff_plain;h=2081c43b8f23e3b6827666f84edacac39c78fff4;p=haproxy.git REGTESTS: filters: Exercise Lua inheritance and state selection Add reusable Lua probes exposing construction order, channel order, encoding visibility and request payload counts. Keep callback state on the stream instance, and support declined attachment, bounded payload forwarding and resumable callbacks for subsequent tests. Exercise named defaults derived from plain defaults, independent frontend and backend inheritance, local instance replacement and sibling isolation. Check class-wide and id-specific enabling/disabling, last-match precedence, directives preceding instance replacement and independent global/request/response sequences. Also forward traffic through a pair of proxies with all instances disabled. Check behavior on the wire and inspect selected instance states through the CLI, rather than relying on configuration acceptance alone. --- diff --git a/reg-tests/filters/lua-defaults.vtc b/reg-tests/filters/lua-defaults.vtc new file mode 100644 index 000000000..073df7d77 --- /dev/null +++ b/reg-tests/filters/lua-defaults.vtc @@ -0,0 +1,147 @@ +varnishtest "Lua filter defaults, overrides and state directives remain proxy-local" + +feature cmd "$HAPROXY_PROGRAM -cc 'feature(LUA)'" +feature ignore_unknown_macro + +server s1 { + rxreq + expect req.http.x-probe-order == "F1.local-a;F.base-b;F1.local-c;BA.local-d;BA.local-c;" + expect req.http.x-probe-errors == + expect req.body == "ping" + txresp -body "pong" +} -start + +server s2 { + rxreq + expect req.http.x-probe-order == "F.base-a;B.base-c;B.base-b;" + expect req.http.x-probe-errors == + txresp -body "sibling" +} -start + +server s3 { + rxreq + expect req.http.x-probe-order == + expect req.http.x-probe-instances == + txresp -body "no filters" +} -start + +haproxy h1 -conf { + global + chroot / + nbthread 1 + tune.lua.bool-sample-conversion normal + lua-load "${testdir}/lua-probe.lua" + + defaults base + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults frontend_filters from base + filter-config lua id probe-a F base-a + filter-config lua id probe-b enabled F base-b + filter-config lua id probe-c F base-c + filter-config lua id probe-d enabled F base-d + filter-disable lua + filter-enable lua/probe-b lua/probe-d + filter-sequence request lua/probe-d:before(lua/probe-b) + filter-sequence response lua/probe-b:after(lua/probe-d) + + frontend first from frontend_filters + bind "fd@${first}" + # State directives are applied after parsing instances, including + # replacements declared below. The last matching directive wins. + filter-enable lua + filter-disable lua/probe-d + filter-config lua id probe-a enabled F1 local-a + filter-config lua id probe-c F1 local-c + filter-sequence global lua/probe-c:before(lua/probe-b) + filter-sequence request lua/probe-a:before(lua/probe-b) + filter-sequence response lua/probe-c:before(lua/probe-a) + default_backend first_be + + frontend sibling from frontend_filters + bind "fd@${sibling}" + filter-disable lua + filter-enable lua/probe-a + filter-config lua id probe-b F2 local-b + filter-config lua id probe-d enabled F2 local-d + default_backend sibling_be + + frontend all_off from frontend_filters + bind "fd@${all_off}" + filter-disable lua + default_backend all_off_be + + defaults backend_filters from base + filter-config lua id probe-a enabled B base-a + filter-config lua id probe-b B base-b + filter-config lua id probe-c enabled B base-c + filter-config lua id probe-d B base-d + filter-enable lua/probe-b + filter-disable lua/probe-a + filter-sequence request lua/probe-c:before(lua/probe-b) + filter-sequence response lua/probe-b:before(lua/probe-c) + + backend first_be from backend_filters + filter-config lua id probe-c BA local-c + filter-config lua id probe-d enabled BA local-d + filter-enable lua/probe-c + filter-disable lua/probe-b + filter-sequence global lua/probe-c:after(lua/probe-d) + filter-sequence request lua/probe-d:before(lua/probe-c) + filter-sequence response lua/probe-d:after(lua/probe-c) + server s1 ${s1_addr}:${s1_port} + + backend sibling_be from backend_filters + filter-enable lua + filter-disable lua/probe-a lua/probe-d + server s2 ${s2_addr}:${s2_port} + + backend all_off_be from backend_filters + filter-disable lua + server s3 ${s3_addr}:${s3_port} +} -start + +client c1 -connect ${h1_first_sock} { + txreq -req POST -body "ping" + rxresp + expect resp.status == 200 + expect resp.http.x-probe-order == "F.base-b;F1.local-c;F1.local-a;BA.local-c;BA.local-d;" + expect resp.http.x-probe-errors == + expect resp.body == "pong" +} -run + +# Overriding arguments, state and sequencing on the first pair must not modify +# the defaults or the instances copied into a sibling proxy. +client c2 -connect ${h1_sibling_sock} { + txreq + rxresp + expect resp.status == 200 + expect resp.http.x-probe-order == "F.base-a;B.base-b;B.base-c;" + expect resp.http.x-probe-errors == + expect resp.body == "sibling" +} -run + +client c3 -connect ${h1_all_off_sock} { + txreq + rxresp + expect resp.status == 200 + expect resp.http.x-probe-order == + expect resp.http.x-probe-instances == + expect resp.body == "no filters" +} -run + +haproxy h1 -cli { + send "show filter instance first lua/probe-c" + expect ~ "state: enabled\n" + send "show filter instance sibling lua/probe-a" + expect ~ "state: enabled, inherited\n" + send "show filter instance first_be lua/probe-b" + expect ~ "state: disabled, inherited\n" + send "show filter instance all_off lua/probe-d" + expect ~ "state: disabled, inherited\n" + send "show filter instance frontend_filters lua/probe-a" + expect ~ "args: F base-a\n" +} diff --git a/reg-tests/filters/lua-probe.lua b/reg-tests/filters/lua-probe.lua new file mode 100644 index 000000000..d50207587 --- /dev/null +++ b/reg-tests/filters/lua-probe.lua @@ -0,0 +1,101 @@ +-- Reusable HTTP probes. Arguments: scope, tag, mode (headers/data/wait/decline), +-- optional maximum bytes forwarded per payload callback. Only new() mutates +-- the construction counters; all callback state belongs to the stream instance. +local counters = {} + +local function append(msg, header, value) + local values = msg:get_headers()[header] + msg:set_header(header, (values and values[0] or "") .. value .. ";") +end + +local function register(name) + local F = { + id = "lua-probe-" .. name, + flags = filter.FLT_CFG_FL_HTX, + } + + function F:new() + if self.mode == "decline" then + return nil + end + counters[self.scope] = (counters[self.scope] or 0) + 1 + return setmetatable({ + serial = counters[self.scope], + started = {}, + headers_seen = {}, + start_waited = {}, + headers_waited = {}, + bytes = { 0, 0 }, + }, { __index = self }) + end + + function F:start_analyze(txn, chn) + local side = chn:is_resp() and 2 or 1 + if self.mode == "wait" and not self.start_waited[side] then + self.start_waited[side] = true + filter.wake_time(1) + return filter.WAIT + end + self.started[side] = true + self.headers_seen[side] = false + self.bytes[side] = 0 + if self.mode == "data" or self.limit > 0 then + filter.register_data_filter(self, chn) + end + return filter.CONTINUE + end + + function F:http_headers(txn, msg) + local side = msg:is_resp() and 2 or 1 + if self.mode == "wait" and not self.headers_waited[side] then + self.headers_waited[side] = true + filter.wake_time(1) + return filter.WAIT + end + local tag = self.scope .. "." .. self.tag + if not self.started[side] then + append(msg, "x-probe-errors", tag .. ":missing-start") + end + if self.headers_seen[side] then + append(msg, "x-probe-errors", tag .. ":duplicate-headers") + end + self.headers_seen[side] = true + append(msg, "x-probe-order", tag) + append(msg, "x-probe-instances", tag .. ":" .. self.serial) + local encoding = msg:get_headers()["content-encoding"] + append(msg, "x-probe-encodings", tag .. ":" .. (encoding and encoding[0] or "identity")) + if side == 2 and (self.mode == "data" or self.limit > 0) then + append(msg, "x-probe-request-bytes", tag .. ":" .. self.bytes[1]) + end + return filter.CONTINUE + end + + function F:http_payload(txn, msg) + local side = msg:is_resp() and 2 or 1 + local count = msg:input() + if self.limit > 0 and count > self.limit then + count = self.limit + end + self.bytes[side] = self.bytes[side] + #(msg:body(0, count) or "") + return count + end + + function F:http_end(txn, msg) + return filter.CONTINUE + end + + core.register_filter(name, F, function(conf, args) + assert(args[1] and args[2], "probe scope and tag are required") + conf.scope = args[1] + conf.tag = args[2] + conf.mode = args[3] or "headers" + conf.limit = tonumber(args[4]) or 0 + assert(conf.limit >= 0, "probe payload limit must not be negative") + return conf + end) +end + +register("probe-a") +register("probe-b") +register("probe-c") +register("probe-d")