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\">"
+                                       "&nbsp;<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


Reply via email to