[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 ed09c32..2e2493e 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -9513,6 +9513,22 @@
$ echo "show table http_proxy" | socat stdio /tmp/sock1
>>> # table: http_proxy, type: ip, size:204800, used:1
+disable frontend <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 <backend>/<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 @@
This command is restricted and can only be issued on sockets configured for
level "admin".
+enable frontend <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 <backend>/<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 @@
| fgrep 'key=' | cut -d' ' -f2 | cut -d= -f2 > abusers-ip.txt
( or | awk '/key/{ print a[split($2,a,"=")]; }' )
+shutdown frontend <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 2f82397..b2e97fe 100644
--- a/include/proto/proxy.h
+++ b/include/proto/proxy.h
@@ -33,6 +33,7 @@
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 964ba83..3c21906 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -82,8 +82,9 @@
" 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 @@
si->applet.st0 = STAT_CLI_PRINT;
}
+/* Expects to find a frontend named <arg> 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 @@
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;
- return 1;
- }
-
- if (!*args[3]) {
- si->applet.ctx.cli.msg = "Frontend name expected.\n";
- si->applet.st0 = STAT_CLI_PRINT;
+ px = expect_frontend_admin(s, si, args[3]);
+ if (!px)
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";
@@ -1165,10 +1180,38 @@
}
}
+ 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) {
@@ -1213,10 +1256,65 @@
set_server_down(sv);
}
+ 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" */