From 59f50cce063fe91e198456732fdb8802b13e0943 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 22 Jan 2010 19:10:05 +0100 Subject: [PATCH] [MEDIUM] add the "force-persist" statement to force persistence on down servers 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 | 32 ++++++++++++++++++++++++-- include/types/proxy.h | 6 +++++ include/types/session.h | 2 +- src/backend.c | 3 ++- src/cfgparse.c | 51 +++++++++++++++++++++++++++++++++++++++++ src/checks.c | 3 ++- src/proto_http.c | 29 +++++++++++++++++++++-- src/session.c | 3 ++- 8 files changed, 121 insertions(+), 8 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 71f01dcf05..a66de24691 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1615,6 +1615,34 @@ errorloc303 See also : "errorfile", "errorloc", "errorloc302" +force-persist { if | unless } + 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 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 diff --git a/include/types/proxy.h b/include/types/proxy.h index 6e89945a23..67681d2c46 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/include/types/session.h b/include/types/session.h index e26033a6e3..d6e42075df 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -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 diff --git a/src/backend.c b/src/backend.c index 54b55a7472..9ad58fd900 100644 --- a/src/backend.c +++ b/src/backend.c @@ -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; diff --git a/src/cfgparse.c b/src/cfgparse.c index 076b16a1b5..d11bb22810 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/checks.c b/src/checks.c index 36df06f9bf..a7facf0da8 100644 --- a/src/checks.c +++ b/src/checks.c @@ -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. */ diff --git a/src/proto_http.c b/src/proto_http.c index cc2af32f2e..bae0412384 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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; diff --git a/src/session.c b/src/session.c index 1348aa04ff..94886771e6 100644 --- a/src/session.c +++ b/src/session.c @@ -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); -- 2.47.3