From: Willy Tarreau Date: Wed, 7 Sep 2011 20:37:44 +0000 (+0200) Subject: [MEDIUM] stats: add the ability to enable/disable/shutdown a frontend at runtime X-Git-Tag: v1.5-dev8~116 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=532a450ebcf3a5177471cbc86722fd0f483b6337;p=thirdparty%2Fhaproxy.git [MEDIUM] stats: add the ability to enable/disable/shutdown a frontend at runtime The stats socket now allows the admin to disable, enable or shutdown a frontend. This can be used when a bug is discovered in a configuration and it's desirable to fix it but the rules in place don't allow to change a running config. Thus it becomes possible to kill the frontend to release the port and start a new one in a separate process. This can also be used to temporarily make haproxy return TCP resets to incoming requests to pretend the service is not bound. For instance, this may be useful to quickly flush a very deep SYN backlog. The frontend check and lookup code was factored with the "set maxconn" usage. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index ed09c32e05..2e2493e9f3 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -9513,6 +9513,22 @@ clear table [ data. ] | [ key ] $ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:1 +disable frontend + Mark the frontend as temporarily stopped. This corresponds to the mode which + is used during a soft restart : the frontend releases the port but can be + enabled again if needed. This should be used with care as some non-Linux OSes + are unable to enable it back. This is intended to be used in environments + where stopping a proxy is not even imaginable but a misconfigured proxy must + be fixed. That way it's possible to release the port and bind it into another + process to restore operations. The frontend will appear with status "STOP" + on the stats page. + + The frontend may be specified either by its name or by its numeric ID, + prefixed with a sharp ('#'). + + This command is restricted and can only be issued on sockets configured for + level "admin". + disable server / Mark the server DOWN for maintenance. In this mode, no more checks will be performed on the server until it leaves maintenance. @@ -9528,6 +9544,19 @@ disable server / This command is restricted and can only be issued on sockets configured for level "admin". +enable frontend + Resume a frontend which was temporarily stopped. It is possible that some of + the listening ports won't be able to bind anymore (eg: if another process + took them since the 'disable frontend' operation). If this happens, an error + is displayed. Some operating systems might not be able to resume a frontend + which was disabled. + + The frontend may be specified either by its name or by its numeric ID, + prefixed with a sharp ('#'). + + This command is restricted and can only be issued on sockets configured for + level "admin". + enable server / If the server was previously marked as DOWN for maintenance, this marks the server UP and checks are re-enabled. @@ -9784,6 +9813,21 @@ show table [ data. ] | [ key ] | fgrep 'key=' | cut -d' ' -f2 | cut -d= -f2 > abusers-ip.txt ( or | awk '/key/{ print a[split($2,a,"=")]; }' ) +shutdown frontend + Completely delete the specified frontend. All the ports it was bound to will + be released. It will not be possible to enable the frontend anymore after + this operation. This is intended to be used in environments where stopping a + proxy is not even imaginable but a misconfigured proxy must be fixed. That + way it's possible to release the port and bind it into another process to + restore operations. The frontend will not appear at all on the stats page + once it is terminated. + + The frontend may be specified either by its name or by its numeric ID, + prefixed with a sharp ('#'). + + This command is restricted and can only be issued on sockets configured for + level "admin". + /* * Local variables: * fill-column: 79 diff --git a/include/proto/proxy.h b/include/proto/proxy.h index 2f82397e74..b2e97fe7de 100644 --- a/include/proto/proxy.h +++ b/include/proto/proxy.h @@ -33,6 +33,7 @@ int start_proxies(int verbose); struct task *manage_proxy(struct task *t); void soft_stop(void); int pause_proxy(struct proxy *p); +int resume_proxy(struct proxy *p); void stop_proxy(struct proxy *p); void pause_proxies(void); void resume_proxies(void); diff --git a/src/dumpstats.c b/src/dumpstats.c index 964ba83b36..3c21906917 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -82,8 +82,9 @@ static const char stats_sock_usage_msg[] = " get weight : report a server's current weight\n" " set weight : change a server's weight\n" " set timeout : change a timeout setting\n" - " disable server : set a server in maintenance mode\n" - " enable server : re-enable a server that was previously in maintenance mode\n" + " disable : put a server or frontend in maintenance mode\n" + " enable : re-enable a server or frontend which is in maintenance mode\n" + " shutdown : irreversibly stop a frontend (eg: to release listening ports)\n" " set maxconn : change a maxconn setting\n" " set rate-limit : change a rate limiting value\n" ""; @@ -662,6 +663,35 @@ err_args: si->applet.st0 = STAT_CLI_PRINT; } +/* Expects to find a frontend named and returns it, otherwise displays various + * adequate error messages and returns NULL. This function also expects the session + * level to be admin. + */ +static struct proxy *expect_frontend_admin(struct session *s, struct stream_interface *si, const char *arg) +{ + struct proxy *px; + + if (s->listener->perm.ux.level < ACCESS_LVL_ADMIN) { + si->applet.ctx.cli.msg = stats_permission_denied_msg; + si->applet.st0 = STAT_CLI_PRINT; + return NULL; + } + + if (!*arg) { + si->applet.ctx.cli.msg = "A frontend name is expected.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return NULL; + } + + px = findproxy(arg, PR_CAP_FE); + if (!px) { + si->applet.ctx.cli.msg = "No such frontend.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return NULL; + } + return px; +} + /* Processes the stats interpreter on the statistics socket. This function is * called from an applet running in a stream interface. The function returns 1 * if the request was understood, otherwise zero. It sets si->applet.st0 to a value @@ -974,24 +1004,9 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) struct listener *l; int v; - if (s->listener->perm.ux.level < ACCESS_LVL_ADMIN) { - si->applet.ctx.cli.msg = stats_permission_denied_msg; - si->applet.st0 = STAT_CLI_PRINT; + px = expect_frontend_admin(s, si, args[3]); + if (!px) return 1; - } - - if (!*args[3]) { - si->applet.ctx.cli.msg = "Frontend name expected.\n"; - si->applet.st0 = STAT_CLI_PRINT; - return 1; - } - - px = findproxy(args[3], PR_CAP_FE); - if (!px) { - si->applet.ctx.cli.msg = "No such frontend.\n"; - si->applet.st0 = STAT_CLI_PRINT; - return 1; - } if (!*args[4]) { si->applet.ctx.cli.msg = "Integer value expected.\n"; @@ -1167,8 +1182,36 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) return 1; } + else if (strcmp(args[1], "frontend") == 0) { + struct proxy *px; + + px = expect_frontend_admin(s, si, args[2]); + if (!px) + return 1; + + if (px->state == PR_STSTOPPED) { + si->applet.ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + + if (px->state != PR_STPAUSED) { + si->applet.ctx.cli.msg = "Frontend is already enabled.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + + if (!resume_proxy(px)) { + si->applet.ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + return 1; + } else { /* unknown "enable" parameter */ - return 0; + si->applet.ctx.cli.msg = "'enable' only supports 'frontend' and 'server'.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; } } else if (strcmp(args[0], "disable") == 0) { @@ -1215,8 +1258,63 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) return 1; } + else if (strcmp(args[1], "frontend") == 0) { + struct proxy *px; + + px = expect_frontend_admin(s, si, args[2]); + if (!px) + return 1; + + if (px->state == PR_STSTOPPED) { + si->applet.ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + + if (px->state == PR_STPAUSED) { + si->applet.ctx.cli.msg = "Frontend is already disabled.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + + if (!pause_proxy(px)) { + si->applet.ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + return 1; + } else { /* unknown "disable" parameter */ - return 0; + si->applet.ctx.cli.msg = "'disable' only supports 'frontend' and 'server'.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + } + else if (strcmp(args[0], "shutdown") == 0) { + if (strcmp(args[1], "frontend") == 0) { + struct proxy *px; + + px = expect_frontend_admin(s, si, args[2]); + if (!px) + return 1; + + if (px->state == PR_STSTOPPED) { + si->applet.ctx.cli.msg = "Frontend was already shut down.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; + } + + Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", + px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn); + send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", + px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn); + stop_proxy(px); + return 1; + } + else { /* unknown "disable" parameter */ + si->applet.ctx.cli.msg = "'shutdown' only supports 'frontend'.\n"; + si->applet.st0 = STAT_CLI_PRINT; + return 1; } } else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */