[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)
This commit is contained in:
Willy Tarreau 2010-01-22 19:10:05 +01:00
parent 32efff0c67
commit 59f50cce06
8 changed files with 121 additions and 8 deletions

View 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

View 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 */

View 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

View 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;

View 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;

View 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.
*/

View 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;

View 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);