]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: filters: Expose global filter ordering through the CLI
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 1 Oct 2026 05:26:44 +0000 (07:26 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 9 Oct 2026 14:17:35 +0000 (16:17 +0200)
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.

reg-tests/filters/global-order.vtc
src/filters.c

index d4a606d8728820328d7ea43e450c5067f2e74780..dd81647676a3f18532d6ee8a243a61201ed9022b 100644 (file)
@@ -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: <none>\nrequest: <none>\nresponse: <none>\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
index 3acdfbab1b14302a56e05611f166183003a1a62e..d9272bc870a3453263144ed1999b0d54bac2b167 100644 (file)
@@ -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, "<none>");
+               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 <inst> as "<cls>[:<id>]", prefixed by " > "
- * unless <first> 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 <px> [request|response]". Displays the filter
- * instances of the proxy <px>, one line per side (both sides if no side is
- * selected).
+/* Parse "show filter instances <px> [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, "<none>");
+               chunk_appendf(&trash, "\n");
        }
-
-       if (first)
-               chunk_appendf(&trash, "<none>");
-       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 <px> <class[/id]> : display a filter instance of a proxy", cli_parse_show_flt_instance, NULL, NULL, NULL },
-       {{ "show", "filter", "instances", NULL }, "show filter instances <px> [side]     : display the filter instances of a proxy", cli_parse_show_flt_instances, NULL, NULL, NULL },
+       {{ "show", "filter", "instances", NULL }, "show filter instances <px> [global|request|response] : display the filter instances of a proxy", cli_parse_show_flt_instances, NULL, NULL, NULL },
        {{},}
 }};