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.
---
include/proto/dumpstats.h | 6 ++
include/types/session.h | 1 +
src/dumpstats.c | 164 ++++++++++++++++++++++++++++++++++++++++-----
src/proto_http.c | 133 +++++++++++++++++++++++++++++++++++-
4 files changed, 283 insertions(+), 21 deletions(-)
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 0fe2bbe..7de1c7c 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -52,6 +52,12 @@
#define STAT_CLI_O_SESS 6 /* dump sessions */
#define STAT_CLI_O_ERR 7 /* dump errors */
+/* 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_sock_parse_request(struct stream_interface *si, char *line);
void stats_io_handler(struct stream_interface *si);
diff --git a/include/types/session.h b/include/types/session.h
index daf2619..20d2a2c 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -213,6 +213,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 */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 6344ecb..778523d 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -972,6 +972,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
@@ -991,9 +1029,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);
+ }
}
}
@@ -1283,6 +1328,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;
}
@@ -1383,6 +1461,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"
@@ -1405,7 +1490,16 @@ 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>"
@@ -1422,11 +1516,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;
@@ -1442,9 +1532,16 @@ 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);
@@ -1602,7 +1699,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;
@@ -1776,16 +1878,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];
@@ -2116,9 +2223,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 */
@@ -2142,6 +2252,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 */
@@ -2304,7 +2415,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\">"
+ " <input type=\"submit\"
value=\"Apply\">"
+ "</form>",
+ px->id);
+ }
+
+ chunk_printf(&msg, "<p>\n");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
diff --git a/src/proto_http.c b/src/proto_http.c
index a5987f0..91d3ad1 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2821,6 +2821,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.
@@ -3026,6 +3130,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;
@@ -6774,10 +6883,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.
@@ -6791,7 +6900,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));
@@ -6835,6 +6944,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;
--
1.7.1