]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: stream: Support dynamic changes of the number of connection retries
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 25 Sep 2024 13:10:08 +0000 (15:10 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 30 Sep 2024 14:55:53 +0000 (16:55 +0200)
Thanks to the previous patch, it is now possible to add an action to
dynamically change the maxumum number of connection retires for a stream.
"set-retries" action may now be used to do so, from a "tcp-request content"
or a "http-request" rule. This action accepts an expression or an integer
between 0 and 100. The integer value is checked during the configuration
parsing and leads to an error if it is not in the expected range. However,
for the expression, the value is retrieve at runtime. So, invalid value are
just ignored.

Too high value is forbidden to avoid any trouble. 100 retries seems already
be an amazingly hight value. In addition, the option is only available on
backend or listen sections.

Because the max retries is limited to 100 at most, it can be stored as a
unsigned short. This save some space in the stream structure.

doc/configuration.txt
include/haproxy/stream-t.h
src/stream.c

index 8799e945a470a4d2584c0bbfc2772c72a62f7b06..699796a9955534a9e746c2957161e75755617934 100644 (file)
@@ -14595,6 +14595,7 @@ set-priority-class             -           -     -     X     -            X   -
 set-priority-offset            -           -     -     X     -            X   -   -
 --keyword---------------QUIC--Ini---TCP--RqCon-RqSes-RqCnt-RsCnt---HTTP--Req-Res-Aft-
 set-query                      -           -     -     -     -            X   -   -
+set-retries                    -           -     -     X     -            X   -   -
 set-src                        -           X     X     X     -            X   -   -
 set-src-port                   -           X     X     X     -            X   -   -
 set-status                     -           -     -     -     -            -   X   X
@@ -15772,6 +15773,22 @@ set-query <fmt>
     http-request set-query %[query,regsub(%3D,=,g)]
 
 
+set-retries <int> | <epxr>
+  Usable in:  QUIC Ini|    TCP RqCon| RqSes| RqCnt| RsCnt|    HTTP Req| Res| Aft
+                    - |          -  |   -  |   X  |   -  |          X |  - |  -
+
+  This action overrides the specified "retries" value for the current stream
+  only. It can be an integer value, in the range [0, 100], or an expression
+  which must return a integer in the range [0, 100].
+
+  Note that this action is only relevant on the backend side and thus this rule
+  is only available for the proxies with backend capability. It is also not
+  allowed in "defaults" sections.
+
+  Example:
+    tcp-request content set-retries 3
+    http-request set-retries var(txn.retries)
+
 set-src <expr>
   Usable in:  QUIC Ini|    TCP RqCon| RqSes| RqCnt| RsCnt|    HTTP Req| Res| Aft
                     - |          X  |   X  |   X  |   -  |          X |  - |  -
index 43353c367fb708de6bc9a2b4ea2b7f8eaf4f144f..ad79f5decee16f0d53537b2468c2cd875c276998 100644 (file)
@@ -234,7 +234,6 @@ struct stream {
                                         * This is a bit field of TASK_WOKEN_* */
        int conn_retries;               /* number of connect retries performed */
        unsigned int conn_exp;          /* wake up time for connect, queue, turn-around, ... */
-       unsigned int max_retries;     /* Maximum number of connection retried (=0 is backend is not set) */
        unsigned int conn_err_type;     /* first error detected, one of STRM_ET_* */
 
        struct stream *parent;          /* Pointer to the parent stream, if any. NULL most of time */
@@ -248,8 +247,8 @@ struct stream {
        uint64_t cpu_time;              /* total CPU time consumed */
        struct freq_ctr call_rate;      /* stream task call rate without making progress */
 
+       unsigned short max_retries;     /* Maximum number of connection retried (=0 is backend is not set) */
        short store_count;
-       /* 2 unused bytes here */
 
        struct {
                struct stksess *ts;
index d0623a9adbcbdc8ef2eaa761d14e141b25f7e31c..e151b2c956a7c27787c6fa42fc2edad68b1b1b32 100644 (file)
@@ -430,7 +430,8 @@ struct stream *stream_new(struct session *sess, struct stconn *sc, struct buffer
 
        s->task = t;
        s->pending_events = 0;
-       s->conn_retries = s->max_retries = 0;
+       s->conn_retries = 0;
+       s->max_retries = 0;
        s->conn_exp = TICK_ETERNITY;
        s->conn_err_type = STRM_ET_NONE;
        s->prev_conn_state = SC_ST_INI;
@@ -1123,7 +1124,7 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an
 
        }
 
-       /* Se the max connection retries for the stream. */
+       /* Se the max connection retries for the stream. may be overwriten later */
        s->max_retries = s->be->conn_retries;
 
        /* we don't want to run the TCP or HTTP filters again if the backend has not changed */
@@ -2893,6 +2894,87 @@ struct ist stream_generate_unique_id(struct stream *strm, struct lf_expr *format
 /************************************************************************/
 /*           All supported ACL keywords must be declared here.          */
 /************************************************************************/
+static enum act_return stream_action_set_retries(struct act_rule *rule, struct proxy *px,
+                                                  struct session *sess, struct stream *s, int flags)
+{
+       struct sample *smp;
+
+       if (!rule->arg.expr_int.expr)
+               s->max_retries = rule->arg.expr_int.value;
+       else  {
+               smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr_int.expr, SMP_T_SINT);
+               if (!smp || smp->data.u.sint < 0 || smp->data.u.sint > 100)
+                       goto end;
+               s->max_retries = smp->data.u.sint;
+       }
+
+  end:
+       return ACT_RET_CONT;
+}
+
+
+/* Parse a "set-retries" action. It takes the level value as argument. It
+ * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret stream_parse_set_retries(const char **args, int *cur_arg, struct proxy *px,
+                                                  struct act_rule *rule, char **err)
+{
+       struct sample_expr *expr;
+       char *endp;
+       unsigned int where;
+
+       if (!*args[*cur_arg]) {
+         bad_retries:
+               memprintf(err, "expects exactly 1 argument (an expression or an integer between 1 and 100)");
+               return ACT_RET_PRS_ERR;
+       }
+       if (!(px->cap & PR_CAP_BE)) {
+               memprintf(err, "'%s' only available in backend or listen section", args[0]);
+               return ACT_RET_PRS_ERR;
+       }
+       if (px->cap & PR_CAP_DEF) {
+               memprintf(err, "'%s' is not allowed in 'defaults' sections", args[0]);
+               return ACT_RET_PRS_ERR;
+       }
+
+       /* value may be either an unsigned integer or an expression */
+       rule->arg.expr_int.expr = NULL;
+       rule->arg.expr_int.value = strtol(args[*cur_arg], &endp, 0);
+       if (*endp == '\0') {
+               if (rule->arg.expr_int.value < 0  || rule->arg.expr_int.value > 100) {
+                       memprintf(err, "expects an expression or an integer between 1 and 100");
+                       return ACT_RET_PRS_ERR;
+               }
+               /* valid unsigned integer */
+               (*cur_arg)++;
+       }
+       else {          /* invalid unsigned integer, fallback to expr */
+               expr = sample_parse_expr((char **)args, cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
+               if (!expr)
+                       return ACT_RET_PRS_ERR;
+               where = 0;
+               if (px->cap & PR_CAP_FE)
+                       where |= SMP_VAL_FE_HRQ_HDR;
+               if (px->cap & PR_CAP_BE)
+                       where |= SMP_VAL_BE_HRQ_HDR;
+
+               if (!(expr->fetch->val & where)) {
+                       memprintf(err,
+                                 "fetch method '%s' extracts information from '%s', none of which is available here",
+                                 args[*cur_arg-1], sample_src_names(expr->fetch->use));
+                       free(expr);
+                       return ACT_RET_PRS_ERR;
+               }
+               rule->arg.expr_int.expr = expr;
+       }
+
+       /* Register processing function. */
+       rule->action = ACT_CUSTOM;
+       rule->action_ptr = stream_action_set_retries;
+       rule->release_ptr = release_expr_int_action;
+       return ACT_RET_PRS_OK;
+}
+
 static enum act_return stream_action_set_log_level(struct act_rule *rule, struct proxy *px,
                                                   struct session *sess, struct stream *s, int flags)
 {
@@ -3907,6 +3989,7 @@ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
 
 /* main configuration keyword registration. */
 static struct action_kw_list stream_tcp_req_keywords = { ILH, {
+       { "set-retries",   stream_parse_set_retries },
        { "set-log-level", stream_parse_set_log_level },
        { "set-nice",      stream_parse_set_nice },
        { "switch-mode",   stream_parse_switch_mode },
@@ -3926,6 +4009,7 @@ static struct action_kw_list stream_tcp_res_keywords = { ILH, {
 INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &stream_tcp_res_keywords);
 
 static struct action_kw_list stream_http_req_keywords = { ILH, {
+       { "set-retries",   stream_parse_set_retries },
        { "set-log-level", stream_parse_set_log_level },
        { "set-nice",      stream_parse_set_nice },
        { "use-service",   stream_parse_use_service },