]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http: add the track-sc* actions to http-request rules
authorWilly Tarreau <w@1wt.eu>
Wed, 25 Jun 2014 16:12:15 +0000 (18:12 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 16 Jul 2014 15:26:40 +0000 (17:26 +0200)
Add support for http-request track-sc, similar to what is done in
tcp-request for backends. A new act_prm field was added to HTTP
request rules to store the track params (table, counter). Just
like for TCP rules, the table is resolved while checking for
config validity. The code was mostly copied from the TCP code
with the exception that here we also count the HTTP request count
and rate by hand. Probably that something could be factored out in
the future.

It seems like tracking flags should be improved to mark each hook
which tracks a key so that we can have some check points where to
increase counters of the past if not done yet, a bit like is done
for TRACK_BACKEND.

doc/configuration.txt
include/proto/proto_http.h
include/types/proto_http.h
src/cfgparse.c
src/proto_http.c

index 9b833af5fb59f5f329c23ac6443e8b113b28ce87..90052e3843fe0d35d3ea37a0154183235beb354e 100644 (file)
@@ -3087,6 +3087,45 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
       with large lists! It is the equivalent of the "set map" command from the
       stats socket, but can be triggered by an HTTP request.
 
+    - { track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] :
+      enables tracking of sticky counters from current request. These rules
+      do not stop evaluation and do not change default action. Three sets of
+      counters may be simultaneously tracked by the same connection. The first
+      "track-sc0" rule executed enables tracking of the counters of the
+      specified table as the first set. The first "track-sc1" rule executed
+      enables tracking of the counters of the specified table as the second
+      set. The first "track-sc2" rule executed enables tracking of the
+      counters of the specified table as the third set. It is a recommended
+      practice to use the first set of counters for the per-frontend counters
+      and the second set for the per-backend ones. But this is just a
+      guideline, all may be used everywhere.
+
+      These actions take one or two arguments :
+        <key>   is mandatory, and is a sample expression rule as described
+                in section 7.3. It describes what elements of the incoming
+                request or connection will be analysed, extracted, combined,
+                and used to select which table entry to update the counters.
+
+        <table> is an optional table to be used instead of the default one,
+                which is the stick-table declared in the current proxy. All
+                the counters for the matches and updates for the key will
+                then be performed in that table until the session ends.
+
+      Once a "track-sc*" rule is executed, the key is looked up in the table
+      and if it is not found, an entry is allocated for it. Then a pointer to
+      that entry is kept during all the session's life, and this entry's
+      counters are updated as often as possible, every time the session's
+      counters are updated, and also systematically when the session ends.
+      Counters are only updated for events that happen after the tracking has
+      been started. As an exception, connection counters and request counters
+      are systematically updated so that they reflect useful information.
+
+      If the entry tracks concurrent connection counters, one connection is
+      counted for as long as the entry is tracked, and the entry will not
+      expire during that time. Tracking counters also provides a performance
+      advantage over just checking the keys, because only one table lookup is
+      performed for all ACL checks that make use of it.
+
   There is no limit to the number of http-request statements per instance.
 
   It is important to know that http-request rules are processed very early in
@@ -7406,7 +7445,7 @@ tcp-request connection <action> [{if | unless} <condition>]
 
     - { track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] :
         enables tracking of sticky counters from current connection. These
-        rules do not stop evaluation and do not change default action. Two sets
+        rules do not stop evaluation and do not change default action. 3 sets
         of counters may be simultaneously tracked by the same connection. The
         first "track-sc0" rule executed enables tracking of the counters of the
         specified table as the first set. The first "track-sc1" rule executed
index 8c222df48cc04c10d1dcd23021e94d57002a0ed9..3db8e10f6f7d9a59fa8e60f6446e9496fd1c94ff 100644 (file)
@@ -208,6 +208,14 @@ static inline int http_body_bytes(const struct http_msg *msg)
        return len;
 }
 
+/* for an http-request action HTTP_REQ_ACT_TRK_*, return a tracking index
+ * starting at zero for SC0. Unknown actions also return zero.
+ */
+static inline int http_req_trk_idx(int trk_action)
+{
+       return trk_action - HTTP_REQ_ACT_TRK_SC0;
+}
+
 /* for debugging, reports the HTTP message state name */
 static inline const char *http_msg_state_str(int msg_state)
 {
index c53c7fdd4b813c14b9e812e30a0aab428ef72d44..95bf59d6d4dad8b8ad0bcb991ee16e6837898f16 100644 (file)
@@ -28,6 +28,7 @@
 #include <common/regex.h>
 
 #include <types/hdr_idx.h>
+#include <types/stick_table.h>
 
 /* These are the flags that are found in txn->flags */
 
@@ -261,6 +262,9 @@ enum {
        HTTP_REQ_ACT_SET_MAP,
        HTTP_REQ_ACT_CUSTOM_STOP,
        HTTP_REQ_ACT_CUSTOM_CONT,
+       HTTP_REQ_ACT_TRK_SC0,
+       /* SC1, SC2, ... SCn */
+       HTTP_REQ_ACT_TRK_SCMAX = HTTP_REQ_ACT_TRK_SC0 + MAX_SESS_STKCTR - 1,
        HTTP_REQ_ACT_MAX /* must always be last */
 };
 
@@ -435,6 +439,10 @@ struct http_req_rule {
                        struct list value;     /* pattern to retrieve MAP value */
                } map;
        } arg;                                 /* arguments used by some actions */
+
+       union {
+               struct track_ctr_prm trk_ctr;
+       } act_prm;
 };
 
 struct http_res_rule {
index 16cf717b22e4b291b72fc208b9832dc6376d143d..42c1790ce6386461e02ccc4d13dda3c96fd5e376 100644 (file)
@@ -6024,6 +6024,7 @@ int check_config_validity()
                struct server_rule *srule;
                struct sticking_rule *mrule;
                struct tcp_rule *trule;
+               struct http_req_rule *hrqrule;
                struct listener *listener;
                unsigned int next_id;
                int nbproc;
@@ -6500,6 +6501,45 @@ int check_config_validity()
                        }
                }
 
+               /* find the target table for 'http-request' layer 7 rules */
+               list_for_each_entry(hrqrule, &curproxy->http_req_rules, list) {
+                       struct proxy *target;
+
+                       if (hrqrule->action < HTTP_REQ_ACT_TRK_SC0 || hrqrule->action > HTTP_REQ_ACT_TRK_SCMAX)
+                               continue;
+
+                       if (hrqrule->act_prm.trk_ctr.table.n)
+                               target = findproxy(hrqrule->act_prm.trk_ctr.table.n, 0);
+                       else
+                               target = curproxy;
+
+                       if (!target) {
+                               Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
+                                     curproxy->id, hrqrule->act_prm.trk_ctr.table.n,
+                                     http_req_trk_idx(hrqrule->action));
+                               cfgerr++;
+                       }
+                       else if (target->table.size == 0) {
+                               Alert("Proxy '%s': table '%s' used but not configured.\n",
+                                     curproxy->id, hrqrule->act_prm.trk_ctr.table.n ? hrqrule->act_prm.trk_ctr.table.n : curproxy->id);
+                               cfgerr++;
+                       }
+                       else if (!stktable_compatible_sample(hrqrule->act_prm.trk_ctr.expr,  target->table.type)) {
+                               Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n",
+                                     curproxy->id, hrqrule->act_prm.trk_ctr.table.n ? hrqrule->act_prm.trk_ctr.table.n : curproxy->id,
+                                     http_req_trk_idx(hrqrule->action));
+                               cfgerr++;
+                       }
+                       else {
+                               free(hrqrule->act_prm.trk_ctr.table.n);
+                               hrqrule->act_prm.trk_ctr.table.t = &target->table;
+                               /* Note: if we decide to enhance the track-sc syntax, we may be able
+                                * to pass a list of counters to track and allocate them right here using
+                                * stktable_alloc_data_type().
+                                */
+                       }
+               }
+
                /* move any "block" rules at the beginning of the http-request rules */
                if (!LIST_ISEMPTY(&curproxy->block_rules)) {
                        /* insert block_rules into http_req_rules at the beginning */
index 9e50796dcba22d9f914554212a3fc8333c0f091c..e67970117c4b8473a543601583edef5c0c95a21e 100644 (file)
@@ -3498,6 +3498,39 @@ http_req_get_intercept_rule(struct proxy *px, struct list *rules, struct session
                case HTTP_REQ_ACT_CUSTOM_STOP:
                        rule->action_ptr(rule, px, s, txn);
                        return HTTP_RULE_RES_DONE;
+
+               case HTTP_REQ_ACT_TRK_SC0 ... HTTP_REQ_ACT_TRK_SCMAX:
+                       /* Note: only the first valid tracking parameter of each
+                        * applies.
+                        */
+
+                       if (stkctr_entry(&s->stkctr[http_req_trk_idx(rule->action)]) == NULL) {
+                               struct stktable *t;
+                               struct stksess *ts;
+                               struct stktable_key *key;
+                               void *ptr;
+
+                               t = rule->act_prm.trk_ctr.table.t;
+                               key = stktable_fetch_key(t, s->be, s, &s->txn, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL);
+
+                               if (key && (ts = stktable_get_entry(t, key))) {
+                                       session_track_stkctr(&s->stkctr[http_req_trk_idx(rule->action)], t, ts);
+
+                                       /* let's count a new HTTP request as it's the first time we do it */
+                                       ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
+                                       if (ptr)
+                                               stktable_data_cast(ptr, http_req_cnt)++;
+
+                                       ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
+                                       if (ptr)
+                                               update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
+                                                                      t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
+
+                                       stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
+                                       if (s->fe != s->be)
+                                               stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
+                               }
+                       }
                }
        }
 
@@ -8975,6 +9008,53 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
                proxy->conf.lfs_file = strdup(proxy->conf.args.file);
                proxy->conf.lfs_line = proxy->conf.args.line;
                cur_arg += 1;
+       } else if (strncmp(args[0], "track-sc", 8) == 0 &&
+                args[0][9] == '\0' && args[0][8] >= '0' &&
+                args[0][8] <= '0' + MAX_SESS_STKCTR) { /* track-sc 0..9 */
+               struct sample_expr *expr;
+               unsigned int where;
+               char *err = NULL;
+
+               cur_arg = 1;
+               proxy->conf.args.ctx = ARGC_TRK;
+
+               expr = sample_parse_expr((char **)args, &cur_arg, file, linenum, &err, &proxy->conf.args);
+               if (!expr) {
+                       Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : %s.\n",
+                             file, linenum, proxy_type_str(proxy), proxy->id, args[0], err);
+                       free(err);
+                       goto out_err;
+               }
+
+               where = 0;
+               if (proxy->cap & PR_CAP_FE)
+                       where |= SMP_VAL_FE_HRQ_HDR;
+               if (proxy->cap & PR_CAP_BE)
+                       where |= SMP_VAL_BE_HRQ_HDR;
+
+               if (!(expr->fetch->val & where)) {
+                       Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule :"
+                             " fetch method '%s' extracts information from '%s', none of which is available here.\n",
+                             file, linenum, proxy_type_str(proxy), proxy->id, args[0],
+                             args[cur_arg-1], sample_src_names(expr->fetch->use));
+                       free(expr);
+                       goto out_err;
+               }
+
+               if (strcmp(args[cur_arg], "table") == 0) {
+                       cur_arg++;
+                       if (!args[cur_arg]) {
+                               Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : missing table name.\n",
+                                     file, linenum, proxy_type_str(proxy), proxy->id, args[0]);
+                               free(expr);
+                               goto out_err;
+                       }
+                       /* we copy the table name for now, it will be resolved later */
+                       rule->act_prm.trk_ctr.table.n = strdup(args[cur_arg]);
+                       cur_arg++;
+               }
+               rule->act_prm.trk_ctr.expr = expr;
+               rule->action = HTTP_REQ_ACT_TRK_SC0 + args[0][8] - '0';
        } else if (strcmp(args[0], "redirect") == 0) {
                struct redirect_rule *redir;
                char *errmsg = NULL;