]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] enable/disable servers from the stats web interface
authorCyril Bonté <cyril.bonte@free.fr>
Mon, 11 Oct 2010 22:14:35 +0000 (00:14 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 30 Oct 2010 17:04:34 +0000 (19:04 +0200)
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)

include/proto/dumpstats.h
include/types/session.h
src/dumpstats.c
src/proto_http.c

index 56d8abbe70fe9a5bb5cbdc7b86af323130f042f0..7038f469ec7289c7de15145f7971aae75ae7350d 100644 (file)
 #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);
index d1e54acff4a23a4d0aa11831f68e94018be6e067..0bbb9bf2f831197a04513c0c3b2ec02a598c3858 100644 (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 */
index 59607f95b7433b15b60d63dadef567f088619c89..5195d8c1e596cdb138c474ddbb4f0805191e794d 100644 (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;
index 859b7fcbbf0944c83e475a99096bb722fd0640e9..3d408e13b243b3e120e04b1bd6cba4f41151e3e9 100644 (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;