From: Christopher Faulet Date: Thu, 1 Oct 2026 05:26:44 +0000 (+0200) Subject: MINOR: filters: Expose global filter ordering through the CLI X-Git-Tag: v3.5-dev9~78 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=286a264aee7cf4deec6520b6acb25d1d58c4a1e7;p=haproxy.git MINOR: filters: Expose global filter ordering through the CLI Accept global for show filter classes and show filter instances, and show all three orders when no direction is selected. Include global in single instance details. Traverse unflattened class references for defaults as well as flat lists for regular proxies. Share class dumping across the three orders and instance dumping across flat lists and class-reference trees. Reset separators for each order and emit one complete line per order, including empty lists. Extend the regression test to check the global registry order, independent request/response orders, reordered and inherited instances, defaults, lookup without double counting, enabled state and invalid CLI directions. --- diff --git a/reg-tests/filters/global-order.vtc b/reg-tests/filters/global-order.vtc index d4a606d87..dd8164767 100644 --- a/reg-tests/filters/global-order.vtc +++ b/reg-tests/filters/global-order.vtc @@ -1,12 +1,14 @@ varnishtest "Global filter instance order is independent of request/response orders" feature ignore_unknown_macro +feature cmd "$HAPROXY_PROGRAM -cc 'feature(LUA)'" haproxy h1 -conf { global .if feature(THREAD) thread-groups 1 .endif + chroot / cache store total-max-size 1 @@ -56,6 +58,55 @@ haproxy h1 -conf { http-request return status 200 } -start +haproxy h1 -cli { + send "show filter classes global" + expect ~ "^global: trace > cache-store > decomp-req > decomp-res > spoe > lua > http-comp-req > http-comp-res > bwlim-in > bwlim-out > fcgi\n" + + send "show filter classes request" + expect ~ "^request: trace > decomp-req > lua > spoe > http-comp-res > decomp-res > http-comp-req > bwlim-in > fcgi\n" + + send "show filter classes response" + expect ~ "^response: fcgi > trace > decomp-res > cache-store > spoe > lua > http-comp-res > decomp-req > bwlim-out\n" + + send "show filter instances baseline" + expect ~ "^global: trace/b > trace/a > cache-store/store > bwlim-in/upload > bwlim-out/download\nrequest: trace/b > trace/a > bwlim-in/upload\nresponse: trace/b > trace/a > cache-store/store > bwlim-out/download\n" + + send "show filter instances reordered global" + expect ~ "^global: bwlim-out/download > cache-store/store > bwlim-in/upload > trace/a > trace/b\n" + + send "show filter instances reordered request" + expect ~ "^request: trace/b > trace/a > bwlim-in/upload\n" + + send "show filter instances reordered response" + expect ~ "^response: trace/b > cache-store/store > trace/a > bwlim-out/download\n" + + # Defaults retain class references instead of flat lists. + send "show filter instances filters global" + expect ~ "^global: trace/b > trace/a > cache-store/store > bwlim-in/upload > bwlim-out/download\n" + + send "show filter instance reordered trace/a" + expect ~ "state: enabled, inherited\nsides: global request response\n" + + send "show filter instance reordered trace/b" + expect ~ "state: disabled, inherited\nsides: global request response\n" + + # A bidirectional instance must not be counted twice during lookup. + send "show filter instance single trace" + expect ~ "id: only\n" + + send "show filter instance reordered bwlim-in" + expect ~ "id: upload\n" + + send "show filter instances empty" + expect ~ "^global: \nrequest: \nresponse: \n" + + send "show filter instances baseline invalid" + expect ~ "'global', 'request' or 'response' expected" + + send "show filter classes invalid" + expect ~ "'global', 'request' or 'response' expected" +} + # Inherited global sequences are applied to the child's instances, including # local overrides, without changing either channel's parsing/class order. haproxy h2 -conf { @@ -63,6 +114,7 @@ haproxy h2 -conf { .if feature(THREAD) thread-groups 1 .endif + chroot / defaults ordered mode http @@ -84,6 +136,20 @@ haproxy h2 -conf { http-request return status 200 } -start +haproxy h2 -cli { + send "show filter instances fe" + expect ~ "^global: trace/b > trace/a > trace/c\nrequest: trace/a > trace/b > trace/c\nresponse: trace/a > trace/b > trace/c\n" + + send "show filter instance fe trace/b" + expect ~ "state: enabled\nsides: global request response\n" + + send "show filter instances single global" + expect ~ "^global: trace/b > trace/a\n" + + send "show filter instance single trace" + expect ~ "several instances of this class, an id is required" +} + # Reject self-references in each order. haproxy h4 -conf-BAD {a loop is detected} { global diff --git a/src/filters.c b/src/filters.c index 3acdfbab1..d9272bc87 100644 --- a/src/filters.c +++ b/src/filters.c @@ -2739,100 +2739,87 @@ static int flt_precheck_instances_all() return 0; } -/* Dumps the request classes. The classes inserted in the before/after lists are - * inserted around the class they are attached to. - */ -static void cli_dump_flt_req_classes(struct buffer *out, struct list *classes) +/* Dump classes in global registration order or in a channel's placement tree. */ +static void cli_dump_flt_classes(struct buffer *out, struct list *classes, unsigned int side, int *first) { struct filter_class *cls; - int first = 1; - - list_for_each_entry(cls, classes, req.list) { - if (!first) - chunk_appendf(out, " > "); - if (!LIST_ISEMPTY(&cls->req.before)) { - cli_dump_flt_req_classes(out, &cls->req.before); - chunk_appendf(out, " > "); - } - chunk_appendf(out, cls->name); - if (!LIST_ISEMPTY(&cls->req.after)) { - chunk_appendf(out, " > "); - cli_dump_flt_req_classes(out, &cls->req.after); - } - first = 0; - } - chunk_appendf(&trash, "\n"); -} + struct list *entry; -/* Dumps the response classes. The classes inserted in the before/after lists are - * inserted around the class they are attached to. - */ -static void cli_dump_flt_res_classes(struct buffer *out, struct list *classes) -{ - struct filter_class *cls; - int first = 1; - - list_for_each_entry(cls, classes, res.list) { - if (!first) - chunk_appendf(out, " > "); - if (!LIST_ISEMPTY(&cls->res.before)) { - cli_dump_flt_res_classes(out, &cls->res.before); - chunk_appendf(out, " > "); - } - chunk_appendf(out, cls->name); - if (!LIST_ISEMPTY(&cls->res.after)) { - chunk_appendf(out, " > "); - cli_dump_flt_res_classes(out, &cls->res.after); - } - first = 0; + for (entry = classes->n; entry != classes; entry = entry->n) { + cls = flt_class_from_list(entry, side); + if (side == FLT_SIDE_REQ) + cli_dump_flt_classes(out, &cls->req.before, side, first); + else if (side == FLT_SIDE_RES) + cli_dump_flt_classes(out, &cls->res.before, side, first); + chunk_appendf(out, "%s%s", *first ? "" : " > ", cls->name); + *first = 0; + if (side == FLT_SIDE_REQ) + cli_dump_flt_classes(out, &cls->req.after, side, first); + else if (side == FLT_SIDE_RES) + cli_dump_flt_classes(out, &cls->res.after, side, first); } - chunk_appendf(&trash, "\n"); } -/* Parses "show filter classes [request|response]" */ +/* Parses "show filter classes [global|request|response]". No side means all three. */ static int cli_parse_show_flt_classes(char **args, char *payload, struct appctx *appctx, void *private) { + const unsigned int sides[] = { FLT_SIDE_GLOBAL, FLT_SIDE_REQ, FLT_SIDE_RES }; + struct list *classes; + unsigned int selected; + int i, first; + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) return 1; + selected = flt_parse_side(args[3]); + if (*args[3] && !selected) + return cli_err(appctx, "'global', 'request' or 'response' expected\n"); chunk_reset(&trash); - - if (!*args[3] || strcmp(args[3], "request") == 0) { - chunk_appendf(&trash, "request: "); - cli_dump_flt_req_classes(&trash, &req_filter_classes); - } - if (!*args[3] || strcmp(args[3], "response") == 0) { - chunk_appendf(&trash, "response: "); - cli_dump_flt_res_classes(&trash, &res_filter_classes); + for (i = 0; i < sizeof(sides) / sizeof(sides[0]); i++) { + if (selected && selected != sides[i]) + continue; + classes = (sides[i] == FLT_SIDE_REQ ? &req_filter_classes : + sides[i] == FLT_SIDE_RES ? &res_filter_classes : &filter_classes); + first = 1; + chunk_appendf(&trash, "%s: ", flt_side_name(sides[i])); + cli_dump_flt_classes(&trash, classes, sides[i], &first); + if (first) + chunk_appendf(&trash, ""); + chunk_appendf(&trash, "\n"); } - if (*args[3] && strcmp(args[3], "request") != 0 && strcmp(args[3], "response") != 0) - return cli_err(appctx, "'request' or 'response' expected\n"); - return cli_msg(appctx, LOG_INFO, trash.area); } -/* Dumps the filter instance as "[:]", prefixed by " > " - * unless is set. - */ -static void cli_dump_flat_flt_def(struct buffer *out, struct filter_instance *inst, int *first) +struct cli_flt_instances_ctx { + struct buffer *out; + int first; +}; + +/* Dump a instance from either a flat order or unflattened class references. */ +static int cli_dump_flt_instance_cb(struct filter_instance *inst, void *data) { - chunk_appendf(out, "%s%s", *first ? "" : " > ", inst->class->name); + struct cli_flt_instances_ctx *ctx = data; + + chunk_appendf(ctx->out, "%s%s", ctx->first ? "" : " > ", inst->class->name); if (inst->id) - chunk_appendf(out, ":%s", inst->id); - *first = 0; + chunk_appendf(ctx->out, "/%s", inst->id); + ctx->first = 0; + return 0; } -/* Parse "show filter instances [request|response]". Displays the filter - * instances of the proxy , one line per side (both sides if no side is - * selected). +/* Parse "show filter instances [global|request|response]". Display one + * line per order, including defaults' unflattened instances. No side means + * all three orders. */ static int cli_parse_show_flt_instances(char **args, char *payload, struct appctx *appctx, void *private) { + const unsigned int sides[] = { FLT_SIDE_GLOBAL, FLT_SIDE_REQ, FLT_SIDE_RES }; + struct cli_flt_instances_ctx ctx = { .out = &trash }; struct proxy *px; - struct filter_instance *inst; - int first = 1; + unsigned int selected; + int i; if (!cli_has_level(appctx, ACCESS_LVL_OPER)) return 1; @@ -2846,26 +2833,22 @@ static int cli_parse_show_flt_instances(char **args, char *payload, struct appct if (!px) return cli_err(appctx, "unknown proxy\n"); - if (*args[4] && strcmp(args[4], "request") != 0 && strcmp(args[4], "response") != 0) - return cli_err(appctx, "'request' or 'response' expected\n"); + selected = flt_parse_side(args[4]); + if (*args[4] && !selected) + return cli_err(appctx, "'global', 'request' or 'response' expected\n"); chunk_reset(&trash); - - if (!*args[4] || strcmp(args[4], "request") == 0) { - chunk_appendf(&trash, "request: "); - list_for_each_entry(inst, &px->filter_req_instances, req.list) - cli_dump_flat_flt_def(&trash, inst, &first); - } - if (!*args[4] || strcmp(args[4], "response") == 0) { - chunk_appendf(&trash, "response: "); - list_for_each_entry(inst, &px->filter_res_instances, res.list) - cli_dump_flat_flt_def(&trash, inst, &first); + for (i = 0; i < sizeof(sides) / sizeof(sides[0]); i++) { + if (selected && selected != sides[i]) + continue; + ctx.first = 1; + chunk_appendf(&trash, "%s: ", flt_side_name(sides[i])); + flt_foreach_instance(flt_instances(px, sides[i]), sides[i], cli_dump_flt_instance_cb, &ctx); + flt_foreach_instance_side(px, sides[i], cli_dump_flt_instance_cb, &ctx); + if (ctx.first) + chunk_appendf(&trash, ""); + chunk_appendf(&trash, "\n"); } - - if (first) - chunk_appendf(&trash, ""); - chunk_appendf(&trash, "\n"); - return cli_msg(appctx, LOG_INFO, trash.area); } @@ -2920,7 +2903,7 @@ static int cli_parse_show_flt_instance(char **args, char *payload, struct appctx found->enabled ? "enabled" : "disabled", (found->flags & FLT_INST_F_IMPLICIT) ? ", implicit" : "", (found->flags & FLT_INST_F_INHERITED) ? ", inherited" : ""); - chunk_appendf(&trash, "sides: %s%s\n", + chunk_appendf(&trash, "sides: global%s%s\n", LIST_INLIST(&found->class->req.list) ? " request" : "", LIST_INLIST(&found->class->res.list) ? " response" : ""); chunk_appendf(&trash, "defined at: %s:%d\n", found->conf.file, found->conf.line); @@ -2937,9 +2920,9 @@ static int cli_parse_show_flt_instance(char **args, char *payload, struct appctx } static struct cli_kw_list cli_kws = {ILH, { - {{ "show", "filter", "classes", NULL }, "show filter classes [request|response] : display all filter classes", cli_parse_show_flt_classes, NULL, NULL, NULL }, + {{ "show", "filter", "classes", NULL }, "show filter classes [global|request|response] : display all filter classes", cli_parse_show_flt_classes, NULL, NULL, NULL }, {{ "show", "filter", "instance", NULL }, "show filter instance : display a filter instance of a proxy", cli_parse_show_flt_instance, NULL, NULL, NULL }, - {{ "show", "filter", "instances", NULL }, "show filter instances [side] : display the filter instances of a proxy", cli_parse_show_flt_instances, NULL, NULL, NULL }, + {{ "show", "filter", "instances", NULL }, "show filter instances [global|request|response] : display the filter instances of a proxy", cli_parse_show_flt_instances, NULL, NULL, NULL }, {{},} }};