[MEDIUM] enable/disable servers from the stats web interface

Based on a patch provided by Judd Montgomery, it is now possible to
enable/disable servers from the stats web interface. This allows to select
several servers in a backend and apply the action to them at the same time.

Currently, there are 2 known limitations :
- The POST data are limited to one packet
  (don't alter too many servers at a time).
- Expect: 100-continue is not supported.
(cherry picked from commit 7693948766cb5647ac03b48e782cfee2b1f14491)
This commit is contained in:
Cyril Bont 2010-10-12 00:14:35 +02:00 committed by Willy Tarreau
parent d64d225e52
commit 70be45dbdf
4 changed files with 287 additions and 21 deletions

View File

@ -53,6 +53,12 @@
#define STAT_CLI_O_ERR 7 /* dump errors */
#define STAT_CLI_O_TAB 8 /* dump tables */
/* status codes (strictly 4 chars) used in the URL to display a message */
#define STAT_STATUS_UNKN "UNKN" /* an unknown error occured, shouldn't happen */
#define STAT_STATUS_DONE "DONE" /* the action is successful */
#define STAT_STATUS_NONE "NONE" /* nothing happened (no action chosen or servers state didn't change) */
#define STAT_STATUS_EXCD "EXCD" /* an error occured becayse the buffer couldn't store all data */
int stats_accept(struct session *s);
int stats_sock_parse_request(struct stream_interface *si, char *line);

View File

@ -217,6 +217,7 @@ struct session {
short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */
unsigned int flags; /* STAT_* */
int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */
const char *st_code; /* pointer to the status code returned by an action */
} stats;
struct {
struct bref bref; /* back-reference from the session being dumped */

View File

@ -1135,6 +1135,44 @@ int stats_dump_raw_to_buffer(struct session *s, struct buffer *rep)
}
/* We don't want to land on the posted stats page because a refresh will
* repost the data. We don't want this to happen on accident so we redirect
* the browse to the stats page with a GET.
*/
int stats_http_redir(struct session *s, struct buffer *rep, struct uri_auth *uri)
{
struct chunk msg;
chunk_init(&msg, trash, sizeof(trash));
switch (s->data_state) {
case DATA_ST_INIT:
chunk_printf(&msg,
"HTTP/1.0 303 See Other\r\n"
"Cache-Control: no-cache\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n"
"Location: %s;st=%s",
uri->uri_prefix, s->data_ctx.stats.st_code);
chunk_printf(&msg, "\r\n\r\n");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
s->txn.status = 303;
if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is
s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
if (!(s->flags & SN_FINST_MASK))
s->flags |= SN_FINST_R;
s->data_state = DATA_ST_FIN;
return 1;
}
return 1;
}
/* This I/O handler runs as an applet embedded in a stream interface. It is
* used to send HTTP stats over a TCP socket. The mechanism is very simple.
* si->st0 becomes non-zero once the transfer is finished. The handler
@ -1154,9 +1192,16 @@ void http_stats_io_handler(struct stream_interface *si)
si->st0 = 1;
if (!si->st0) {
if (stats_dump_http(s, res, s->be->uri_auth)) {
si->st0 = 1;
si->shutw(si);
if (s->txn.meth == HTTP_METH_POST) {
if (stats_http_redir(s, res, s->be->uri_auth)) {
si->st0 = 1;
si->shutw(si);
}
} else {
if (stats_dump_http(s, res, s->be->uri_auth)) {
si->st0 = 1;
si->shutw(si);
}
}
}
@ -1446,6 +1491,39 @@ int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri)
""
);
if (s->data_ctx.stats.st_code) {
if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_DONE) == 0) {
chunk_printf(&msg,
"<p><div class=active3>"
"<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
"Action processed successfully."
"</div>\n", uri->uri_prefix);
}
else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_NONE) == 0) {
chunk_printf(&msg,
"<p><div class=active2>"
"<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
"Nothing has changed."
"</div>\n", uri->uri_prefix);
}
else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_EXCD) == 0) {
chunk_printf(&msg,
"<p><div class=active0>"
"<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
"<b>Action not processed : the buffer couldn't store all the data.<br>"
"You should retry with less servers at a time.</b>"
"</div>\n", uri->uri_prefix);
}
else {
chunk_printf(&msg,
"<p><div class=active6>"
"<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
"Unexpected result."
"</div>\n", uri->uri_prefix);
}
chunk_printf(&msg,"<p>\n");
}
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
}
@ -1546,6 +1624,13 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
case DATA_ST_PX_TH:
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
if (px->cap & PR_CAP_BE && px->srv) {
/* A form to enable/disable this proxy servers */
chunk_printf(&msg,
"<form action=\"%s\" method=\"post\">",
uri->uri_prefix);
}
/* print a new table */
chunk_printf(&msg,
"<table class=\"tbl\" width=\"100%%\">\n"
@ -1568,7 +1653,18 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
"</tr>\n"
"</table>\n"
"<table class=\"tbl\" width=\"100%%\">\n"
"<tr class=\"titre\">"
"<tr class=\"titre\">",
(uri->flags & ST_SHLGNDS)?"<u>":"",
px->id, px->id, px->id,
(uri->flags & ST_SHLGNDS)?"</u>":"",
px->desc ? "desc" : "empty", px->desc ? px->desc : "");
if (px->cap & PR_CAP_BE && px->srv) {
/* Column heading for Enable or Disable server */
chunk_printf(&msg, "<th rowspan=2 width=1></th>");
}
chunk_printf(&msg,
"<th rowspan=2></th>"
"<th colspan=3>Queue</th>"
"<th colspan=3>Session rate</th><th colspan=5>Sessions</th>"
@ -1585,11 +1681,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
"<th>Status</th><th>LastChk</th><th>Wght</th><th>Act</th>"
"<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
"<th>Thrtle</th>\n"
"</tr>",
(uri->flags & ST_SHLGNDS)?"<u>":"",
px->id, px->id, px->id,
(uri->flags & ST_SHLGNDS)?"</u>":"",
px->desc ? "desc" : "empty", px->desc ? px->desc : "");
"</tr>");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
@ -1605,9 +1697,18 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg,
/* name, queue */
"<tr class=\"frontend\"><td class=ac>"
"<tr class=\"frontend\">");
if (px->cap & PR_CAP_BE && px->srv) {
/* Column sub-heading for Enable or Disable server */
chunk_printf(&msg, "<td></td>");
}
chunk_printf(&msg,
"<td class=ac>"
"<a name=\"%s/Frontend\"></a>"
"<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td><td colspan=3></td>"
"<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>"
"<td colspan=3></td>"
"",
px->id, px->id);
@ -1765,7 +1866,12 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
}
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, "<tr class=socket><td class=ac");
chunk_printf(&msg, "<tr class=socket>");
if (px->cap & PR_CAP_BE && px->srv) {
/* Column sub-heading for Enable or Disable server */
chunk_printf(&msg, "<td></td>");
}
chunk_printf(&msg, "<td class=ac");
if (uri->flags&ST_SHLGNDS) {
char str[INET6_ADDRSTRLEN], *fmt = NULL;
@ -1939,16 +2045,21 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
if ((sv->state & SRV_MAINTAIN) || (svs->state & SRV_MAINTAIN)) {
chunk_printf(&msg,
/* name */
"<tr class=\"maintain\"><td class=ac"
"<tr class=\"maintain\">"
);
}
else {
chunk_printf(&msg,
/* name */
"<tr class=\"%s%d\"><td class=ac",
"<tr class=\"%s%d\">",
(sv->state & SRV_BACKUP) ? "backup" : "active", sv_state);
}
chunk_printf(&msg,
"<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>"
"<td class=ac",
sv->id);
if (uri->flags&ST_SHLGNDS) {
char str[INET6_ADDRSTRLEN];
@ -2279,9 +2390,12 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
if ((px->cap & PR_CAP_BE) &&
(!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg,
/* name */
"<tr class=\"backend\"><td class=ac");
chunk_printf(&msg, "<tr class=\"backend\">");
if (px->cap & PR_CAP_BE && px->srv) {
/* Column sub-heading for Enable or Disable server */
chunk_printf(&msg, "<td></td>");
}
chunk_printf(&msg, "<td class=ac");
if (uri->flags&ST_SHLGNDS) {
/* balancing */
@ -2305,6 +2419,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
}
chunk_printf(&msg,
/* name */
">%s<a name=\"%s/Backend\"></a>"
"<a class=lfsb href=\"#%s/Backend\">Backend</a>%s</td>"
/* queue : current, max */
@ -2467,7 +2582,24 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
case DATA_ST_PX_END:
if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, "</table><p>\n");
chunk_printf(&msg, "</table>");
if (px->cap & PR_CAP_BE && px->srv) {
/* close the form used to enable/disable this proxy servers */
chunk_printf(&msg,
"Choose the action to perform on the checked servers : "
"<select name=action>"
"<option value=\"\"></option>"
"<option value=\"disable\">Disable</option>"
"<option value=\"enable\">Enable</option>"
"</select>"
"<input type=\"hidden\" name=\"b\" value=\"%s\">"
"&nbsp;<input type=\"submit\" value=\"Apply\">"
"</form>",
px->id);
}
chunk_printf(&msg, "<p>\n");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;

View File

@ -2839,6 +2839,110 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
return 0;
}
/* We reached the stats page through a POST request.
* Parse the posted data and enable/disable servers if necessary.
* Returns 0 if request was parsed.
* Returns 1 if there was a problem parsing the posted data.
*/
int http_process_req_stat_post(struct session *s, struct buffer *req)
{
struct http_txn *txn = &s->txn;
struct proxy *px;
struct server *sv;
char *backend = NULL;
int action = 0;
char *first_param, *cur_param, *next_param, *end_params;
first_param = req->data + txn->req.eoh + 2;
end_params = first_param + txn->req.hdr_content_len;
cur_param = next_param = end_params;
if (end_params >= req->data + req->size) {
/* Prevent buffer overflow */
s->data_ctx.stats.st_code = STAT_STATUS_EXCD;
return 1;
}
else if (end_params > req->data + req->l) {
/* This condition also rejects a request with Expect: 100-continue */
s->data_ctx.stats.st_code = STAT_STATUS_EXCD;
return 1;
}
*end_params = '\0';
s->data_ctx.stats.st_code = STAT_STATUS_NONE;
/*
* Parse the parameters in reverse order to only store the last value.
* From the html form, the backend and the action are at the end.
*/
while (cur_param > first_param) {
char *key, *value;
cur_param--;
if ((*cur_param == '&') || (cur_param == first_param)) {
/* Parse the key */
key = cur_param;
if (cur_param != first_param) {
/* delimit the string for the next loop */
*key++ = '\0';
}
/* Parse the value */
value = key;
while (*value != '\0' && *value != '=') {
value++;
}
if (*value == '=') {
/* Ok, a value is found, we can mark the end of the key */
*value++ = '\0';
}
/* Now we can check the key to see what to do */
if (!backend && strcmp(key, "b") == 0) {
backend = value;
}
else if (!action && strcmp(key, "action") == 0) {
if (strcmp(value, "disable") == 0) {
action = 1;
}
else if (strcmp(value, "enable") == 0) {
action = 2;
} else {
/* unknown action, no need to continue */
break;
}
}
else if (strcmp(key, "s") == 0) {
if (backend && action && get_backend_server(backend, value, &px, &sv)) {
switch (action) {
case 1:
if (! (sv->state & SRV_MAINTAIN)) {
/* Not already in maintenance, we can change the server state */
sv->state |= SRV_MAINTAIN;
set_server_down(sv);
s->data_ctx.stats.st_code = STAT_STATUS_DONE;
}
break;
case 2:
if ((sv->state & SRV_MAINTAIN)) {
/* Already in maintenance, we can change the server state */
set_server_up(sv);
s->data_ctx.stats.st_code = STAT_STATUS_DONE;
}
break;
}
}
}
next_param = cur_param;
}
}
return 0;
}
/* This stream analyser runs all HTTP request processing which is common to
* frontends and backends, which means blocking ACLs, filters, connection-close,
* reqadd, stats and redirects. This is performed for the designated proxy.
@ -3053,6 +3157,11 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
* make it follow standard rules (eg: clear req->analysers).
*/
/* Was the status page requested with a POST ? */
if (txn->meth == HTTP_METH_POST) {
http_process_req_stat_post(s, req);
}
s->logs.tv_request = now;
s->data_source = DATA_SRC_STATS;
s->data_state = DATA_ST_INIT;
@ -6943,10 +7052,10 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len)
}
/*
* In a GET or HEAD request, check if the requested URI matches the stats uri
* In a GET, HEAD or POST request, check if the requested URI matches the stats uri
* for the current backend.
*
* It is assumed that the request is either a HEAD or GET and that the
* It is assumed that the request is either a HEAD, GET, or POST and that the
* t->be->uri_auth field is valid.
*
* Returns 1 if stats should be provided, otherwise 0.
@ -6960,7 +7069,7 @@ int stats_check_uri(struct session *t, struct proxy *backend)
if (!uri_auth)
return 0;
if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD)
if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD && txn->meth != HTTP_METH_POST)
return 0;
memset(&t->data_ctx.stats, 0, sizeof(t->data_ctx.stats));
@ -7004,6 +7113,24 @@ int stats_check_uri(struct session *t, struct proxy *backend)
h++;
}
h = txn->req.sol + txn->req.sl.rq.u + uri_auth->uri_len;
while (h <= txn->req.sol + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 8) {
if (memcmp(h, ";st=", 4) == 0) {
h += 4;
if (memcmp(h, STAT_STATUS_DONE, 4) == 0)
t->data_ctx.stats.st_code = STAT_STATUS_DONE;
else if (memcmp(h, STAT_STATUS_NONE, 4) == 0)
t->data_ctx.stats.st_code = STAT_STATUS_NONE;
else if (memcmp(h, STAT_STATUS_EXCD, 4) == 0)
t->data_ctx.stats.st_code = STAT_STATUS_EXCD;
else
t->data_ctx.stats.st_code = STAT_STATUS_UNKN;
break;
}
h++;
}
t->data_ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO;
return 1;