]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add the "force-persist" statement to force persistence on down servers
authorWilly Tarreau <w@1wt.eu>
Fri, 22 Jan 2010 18:10:05 +0000 (19:10 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 28 Jan 2010 22:16:57 +0000 (23:16 +0100)
This is used to force access to down servers for some requests. This
is useful when validating that a change on a server correctly works
before enabling the server again.

(cherry picked from commit 4de9149f876cc0c63495b71a2c7a3aefc722c9c0)

doc/configuration.txt
include/types/proxy.h
include/types/session.h
src/backend.c
src/cfgparse.c
src/checks.c
src/proto_http.c
src/session.c

index 71f01dcf05aa2113f434b3eb583b1c42699a3720..a66de24691f237f160c2329d5cec67b65b7abe59 100644 (file)
@@ -1615,6 +1615,34 @@ errorloc303 <code> <url>
   See also : "errorfile", "errorloc", "errorloc302"
 
 
+force-persist { if | unless } <condition>
+  Declare a condition to force persistence on down servers
+  May be used in sections:    defaults | frontend | listen | backend
+                                  no   |    yes   |   yes  |   yes
+
+  By default, requests are not dispatched to down servers. It is possible to
+  force this using "option persist", but it is unconditional and redispatches
+  to a valid server if "option redispatch" is set. That leaves with very little
+  possibilities to force some requests to reach a server which is artificially
+  marked down for maintenance operations.
+
+  The "force-persist" statement allows one to declare various ACL-based
+  conditions which, when met, will cause a request to ignore the down status of
+  a server and still try to connect to it. That makes it possible to start a
+  server, still replying an error to the health checks, and run a specially
+  configured browser to test the service. Among the handy methods, one could
+  use a specific source IP address, or a specific cookie. The cookie also has
+  the advantage that it can easily be added/removed on the browser from a test
+  page. Once the service is validated, it is then possible to open the service
+  to the world by returning a valid response to health checks.
+
+  The forced persistence is enabled when an "if" condition is met, or unless an
+  "unless" condition is met. The final redispatch is always disabled when this
+  is used.
+
+  See also : "option redispatch", "persist", and section 7 about ACL usage.
+
+
 fullconn <conns>
   Specify at what backend load the servers will reach their maxconn
   May be used in sections :   defaults | frontend | listen | backend
@@ -2617,7 +2645,7 @@ no option persist
   If this option has been enabled in a "defaults" section, it can be disabled
   in a specific instance by prepending the "no" keyword before it.
 
-  See also : "option redispatch", "retries"
+  See also : "option redispatch", "retries", "force-persist"
 
 
 option redispatch
@@ -2644,7 +2672,7 @@ no option redispatch
   If this option has been enabled in a "defaults" section, it can be disabled
   in a specific instance by prepending the "no" keyword before it.
 
-  See also : "redispatch", "retries"
+  See also : "redispatch", "retries", "force-persist"
 
 
 option smtpchk
index 6e89945a23dc29caeb2794f477510532e7e69ef5..67681d2c460f05d3c66112ba1f5b0aceabf854f3 100644 (file)
@@ -160,6 +160,7 @@ struct proxy {
        struct list block_cond;                 /* early blocking conditions (chained) */
        struct list redirect_rules;             /* content redirecting rules (chained) */
        struct list switching_rules;            /* content switching rules (chained) */
+       struct list force_persist_rules;        /* 'force-persist' rules (chained) */
        struct {                                /* TCP request processing */
                unsigned int inspect_delay;     /* inspection delay */
                struct list inspect_rules;      /* inspection rules */
@@ -305,6 +306,11 @@ struct switching_rule {
        } be;
 };
 
+struct force_persist_rule {
+       struct list list;                       /* list linked to from the proxy */
+       struct acl_cond *cond;                  /* acl condition to meet */
+};
+
 struct redirect_rule {
        struct list list;                       /* list linked to from the proxy */
        struct acl_cond *cond;                  /* acl condition to meet */
index e26033a6e3916536950855e92a7a734e5c085e65..d6e42075df066950afb0a5a814097507fe3e8192 100644 (file)
@@ -52,7 +52,7 @@
 #define SN_REDISP      0x00000100      /* set if this session was redispatched from one server to another */
 #define SN_CONN_TAR    0x00000200      /* set if this session is turning around before reconnecting */
 #define SN_REDIRECTABLE        0x00000400      /* set if this session is redirectable (GET or HEAD) */
-/* unused:              0x00000800 */
+#define SN_FORCE_PRST  0x00000800      /* force persistence here, even if server is down */
 
 /* session termination conditions, bits values 0x1000 to 0x7000 (0-7 shift 12) */
 #define SN_ERR_NONE     0x00000000
index 54b55a74721b18d5420dd2b41a331c1b3b6bd6fe..9ad58fd900b125dcef2fa7980f4c4fc38e63c7bc 100644 (file)
@@ -2019,7 +2019,8 @@ int srv_redispatch_connect(struct session *t)
                 * would bring us on the same server again. Note that t->srv is set in
                 * this case.
                 */
-               if ((t->flags & SN_DIRECT) && (t->be->options & PR_O_REDISP)) {
+               if (((t->flags & (SN_DIRECT|SN_FORCE_PRST)) == SN_DIRECT) &&
+                   (t->be->options & PR_O_REDISP)) {
                        t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
                        t->prev_srv = t->srv;
                        goto redispatch;
index 076b16a1b5d5ff879969f6e218561bd07bb3a8ec..d11bb22810a8df49c5a3950090c963596c0a93b9 100644 (file)
@@ -849,6 +849,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                LIST_INIT(&curproxy->mon_fail_cond);
                LIST_INIT(&curproxy->switching_rules);
                LIST_INIT(&curproxy->tcp_req.inspect_rules);
+               LIST_INIT(&curproxy->force_persist_rules);
 
                /* Timeouts are defined as -1, so we cannot use the zeroed area
                 * as a default value.
@@ -1728,6 +1729,56 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
                LIST_INIT(&rule->list);
                LIST_ADDQ(&curproxy->switching_rules, &rule->list);
        }
+       else if (!strcmp(args[0], "force-persist")) {
+               int pol = ACL_COND_NONE;
+               struct acl_cond *cond;
+               struct force_persist_rule *rule;
+
+               if (curproxy == &defproxy) {
+                       Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+
+               if (warnifnotcap(curproxy, PR_CAP_FE|PR_CAP_BE, file, linenum, args[0], NULL))
+                       err_code |= ERR_WARN;
+
+               if (!strcmp(args[1], "if"))
+                       pol = ACL_COND_IF;
+               else if (!strcmp(args[1], "unless"))
+                       pol = ACL_COND_UNLESS;
+
+               if (pol == ACL_COND_NONE) {
+                       Alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n",
+                             file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+
+               if ((cond = parse_acl_cond((const char **)args + 2, &curproxy->acl, pol)) == NULL) {
+                       Alert("parsing [%s:%d] : error detected while parsing a 'force-persist' rule.\n",
+                             file, linenum);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+
+               cond->line = linenum;
+               if (cond->requires & ACL_USE_RTR_ANY) {
+                       struct acl *acl;
+                       const char *name;
+
+                       acl = cond_find_require(cond, ACL_USE_RTR_ANY);
+                       name = acl ? acl->name : "(unknown)";
+                       Warning("parsing [%s:%d] : acl '%s' involves some response-only criteria which will be ignored.\n",
+                               file, linenum, name);
+                       err_code |= ERR_WARN;
+               }
+
+               rule = (struct force_persist_rule *)calloc(1, sizeof(*rule));
+               rule->cond = cond;
+               LIST_INIT(&rule->list);
+               LIST_ADDQ(&curproxy->force_persist_rules, &rule->list);
+       }
        else if (!strcmp(args[0], "stats")) {
                if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
                        err_code |= ERR_WARN;
index 36df06f9bfd8534780cdc27717fe404bb2375736..a7facf0da8fc6dc3bf586a8ff412bd0bf286030c 100644 (file)
@@ -65,7 +65,8 @@ static int redistribute_pending(struct server *s)
 
        FOREACH_ITEM_SAFE(pc, pc_bck, &s->pendconns, pc_end, struct pendconn *, list) {
                struct session *sess = pc->sess;
-               if (sess->be->options & PR_O_REDISP) {
+               if ((sess->be->options & (PR_O_REDISP|PR_O_PERSIST)) == PR_O_REDISP &&
+                   !(sess->flags & SN_FORCE_PRST)) {
                        /* The REDISP option was specified. We will ignore
                         * cookie and force to balance or use the dispatcher.
                         */
index cc2af32f2efce9f95c1da6b90c045e5755ed3a5a..bae0412384aafa57c62c95996ff4750c004b1acf 100644 (file)
@@ -1540,6 +1540,7 @@ int http_process_request(struct session *s, struct buffer *req)
        struct http_txn *txn = &s->txn;
        struct http_msg *msg = &txn->req;
        struct proxy *cur_proxy;
+       struct force_persist_rule *prst_rule;
 
        DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
                now_ms, __FUNCTION__,
@@ -2124,6 +2125,26 @@ int http_process_request(struct session *s, struct buffer *req)
                s->flags |= SN_BE_ASSIGNED;
        }
 
+       /* as soon as we know the backend, we must check if we have a matching forced
+        * persistence rule, and report that in the session.
+        */
+       list_for_each_entry(prst_rule, &s->be->force_persist_rules, list) {
+               int ret = 1;
+
+               if (prst_rule->cond) {
+                       ret = acl_exec_cond(prst_rule->cond, s->be, s, &s->txn, ACL_DIR_REQ);
+                       ret = acl_pass(ret);
+                       if (prst_rule->cond->pol == ACL_COND_UNLESS)
+                               ret = !ret;
+               }
+
+               if (ret) {
+                       /* no rule, or the rule matches */
+                       s->flags |= SN_FORCE_PRST;
+                       break;
+               }
+       }
+
        /*
         * Right now, we know that we have processed the entire headers
         * and that unwanted requests have been filtered out. We can do
@@ -3428,7 +3449,9 @@ void manage_client_side_appsession(struct session *t, const char *buf) {
                        struct server *srv = t->be->srv;
                        while (srv) {
                                if (strcmp(srv->id, asession->serverid) == 0) {
-                                       if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) {
+                                       if ((srv->state & SRV_RUNNING) ||
+                                           (t->be->options & PR_O_PERSIST) ||
+                                           (t->flags & SN_FORCE_PRST)) {
                                                /* we found the server and it's usable */
                                                txn->flags &= ~TX_CK_MASK;
                                                txn->flags |= TX_CK_VALID;
@@ -3624,7 +3647,9 @@ void manage_client_side_cookies(struct session *t, struct buffer *req)
                                        while (srv) {
                                                if (srv->cookie && (srv->cklen == delim - p3) &&
                                                    !memcmp(p3, srv->cookie, delim - p3)) {
-                                                       if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) {
+                                                       if ((srv->state & SRV_RUNNING) ||
+                                                           (t->be->options & PR_O_PERSIST) ||
+                                                           (t->flags & SN_FORCE_PRST)) {
                                                                /* we found the server and it's usable */
                                                                txn->flags &= ~TX_CK_MASK;
                                                                txn->flags |= TX_CK_VALID;
index 1348aa04ffedef7ce8504c046f47a3550af96694..94886771e66982d9a260bc9a37498467f97c66bc 100644 (file)
@@ -280,7 +280,8 @@ int sess_update_st_cer(struct session *s, struct stream_interface *si)
         * bit to ignore any persistence cookie. We won't count a retry nor a
         * redispatch yet, because this will depend on what server is selected.
         */
-       if (s->srv && s->conn_retries == 0 && s->be->options & PR_O_REDISP) {
+       if (s->srv && s->conn_retries == 0 &&
+           s->be->options & PR_O_REDISP && !(s->flags & SN_FORCE_PRST)) {
                if (may_dequeue_tasks(s->srv, s->be))
                        process_srv_queue(s->srv);