From 4de9149f876cc0c63495b71a2c7a3aefc722c9c0 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 22 Jan 2010 19:10:05 +0100 Subject: [PATCH] [MINOR] 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. --- doc/configuration.txt | 32 +++++++++++++++++++++++-- include/types/proxy.h | 6 +++++ include/types/session.h | 4 ++-- src/backend.c | 3 ++- src/cfgparse.c | 53 +++++++++++++++++++++++++++++++++++++++++ src/checks.c | 3 ++- src/proto_http.c | 10 +++++--- src/session.c | 32 ++++++++++++++++++++++--- 8 files changed, 131 insertions(+), 12 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index d626ade86..8658fdc2b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1804,6 +1804,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 @@ -2935,7 +2963,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 @@ -2962,7 +2990,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 a4c99cbeb..c4fb50532 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -171,6 +171,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 list sticking_rules; /* content sticking rules (chained) */ struct list storersp_rules; /* content store response rules (chained) */ struct { /* TCP request processing */ @@ -297,6 +298,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 sticking_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 ecbb6c1ff..6c8cfa00c 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -2,7 +2,7 @@ * include/types/session.h * This file defines everything related to sessions. * - * Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu + * Copyright (C) 2000-2010 Willy Tarreau - w@1wt.eu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -47,7 +47,7 @@ #define SN_ADDR_SET 0x00000004 /* this session's server address has been set */ #define SN_BE_ASSIGNED 0x00000008 /* a backend was assigned. Conns are accounted. */ -/* unused: 0x00000010 */ +#define SN_FORCE_PRST 0x00000010 /* force persistence here, even if server is down */ #define SN_MONITOR 0x00000020 /* this session comes from a monitoring system */ #define SN_CURR_SESS 0x00000040 /* a connection is currently being counted on the server */ #define SN_FRT_ADDR_SET 0x00000080 /* set if the frontend address has been filled */ diff --git a/src/backend.c b/src/backend.c index 3d815ac01..8b8b4377b 100644 --- a/src/backend.c +++ b/src/backend.c @@ -888,7 +888,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 9aae768ab..eea9d3e7d 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -849,6 +849,7 @@ static void init_new_proxy(struct proxy *p) LIST_INIT(&p->redirect_rules); LIST_INIT(&p->mon_fail_cond); LIST_INIT(&p->switching_rules); + LIST_INIT(&p->force_persist_rules); LIST_INIT(&p->sticking_rules); LIST_INIT(&p->storersp_rules); LIST_INIT(&p->tcp_req.inspect_rules); @@ -2038,6 +2039,58 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) 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->file = file; + cond->line = linenum; + curproxy->acl_requires |= cond->requires; + 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], "stick-table")) { int myidx = 1; diff --git a/src/checks.c b/src/checks.c index 7eeced26a..50e3190a9 100644 --- a/src/checks.c +++ b/src/checks.c @@ -310,7 +310,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 47b67e899..83e051848 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3504,7 +3504,7 @@ void http_end_txn_clean_session(struct session *s) s->req->cons->flags = SI_FL_NONE; s->req->flags &= ~(BF_SHUTW|BF_SHUTW_NOW|BF_AUTO_CONNECT|BF_WRITE_ERROR|BF_STREAMER|BF_STREAMER_FAST); s->rep->flags &= ~(BF_SHUTR|BF_SHUTR_NOW|BF_READ_ATTACHED|BF_READ_ERROR|BF_READ_NOEXP|BF_STREAMER|BF_STREAMER_FAST|BF_WRITE_PARTIAL); - s->flags &= ~(SN_DIRECT|SN_ASSIGNED|SN_ADDR_SET|SN_BE_ASSIGNED); + s->flags &= ~(SN_DIRECT|SN_ASSIGNED|SN_ADDR_SET|SN_BE_ASSIGNED|SN_FORCE_PRST); s->flags &= ~(SN_CURR_SESS|SN_REDIRECTABLE); s->txn.meth = 0; http_reset_txn(s); @@ -5215,7 +5215,9 @@ void manage_client_side_appsession(struct session *t, const char *buf, int len) 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; @@ -5411,7 +5413,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 20a5886a8..36fe50de9 100644 --- a/src/session.c +++ b/src/session.c @@ -276,7 +276,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); @@ -543,12 +544,15 @@ static void sess_prepare_conn_req(struct session *s, struct stream_interface *si } /* This stream analyser checks the switching rules and changes the backend - * if appropriate. The default_backend rule is also considered. + * if appropriate. The default_backend rule is also considered, then the + * target backend's forced persistence rules are also evaluated last if any. * It returns 1 if the processing can continue on next analysers, or zero if it * either needs more data or wants to immediately abort the request. */ int process_switching_rules(struct session *s, struct buffer *req, int an_bit) { + struct force_persist_rule *prst_rule; + req->analysers &= ~an_bit; req->analyse_exp = TICK_ETERNITY; @@ -594,6 +598,26 @@ int process_switching_rules(struct session *s, struct buffer *req, int an_bit) if (s->fe == s->be) s->req->analysers &= ~AN_REQ_HTTP_PROCESS_BE; + /* 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; + } + } + return 1; sw_failed: @@ -669,7 +693,9 @@ int process_sticking_rules(struct session *s, struct buffer *req, int an_bit) struct server *srv; srv = container_of(node, struct server, conf.id); - if ((srv->state & SRV_RUNNING) || (px->options & PR_O_PERSIST)) { + if ((srv->state & SRV_RUNNING) || + (px->options & PR_O_PERSIST) || + (s->flags & SN_FORCE_PRST)) { s->flags |= SN_DIRECT | SN_ASSIGNED; s->srv = srv; }