[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.
This commit is contained in:
Willy Tarreau 2011-09-07 22:37:44 +02:00
parent c03ebbfca4
commit 532a450ebc
3 changed files with 164 additions and 21 deletions

View File

@ -9513,6 +9513,22 @@ clear table <table> [ data.<type> <operator> <value> ] | [ key <key> ]
$ 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 @@ disable server <backend>/<server>
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 @@ show table <name> [ data.<type> <operator> <value> ] | [ key <key> ]
| 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

View File

@ -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);

View File

@ -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 <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 @@ 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" */