MINOR: cli/wait: add a condition to wait on a server to become unused

The "wait" command now supports a condition, "srv-unused", which waits
for the designated server to become totally unused, indicating that it
is removable. Upon each wakeup it calls srv_check_for_deletion() to
verify if conditions are met, if not if it's recoverable, or if it's
not recoverable, and proceeds according to this, never waiting for a
final decision longer than the configured delay.

The purpose is to make it possible to remove servers from the CLI after
waiting for their sessions to be terminated:

  $ socat -t5 /path/to/socket - <<< "
        disable server px/srv1
        shutdown sessions server px/srv1
        wait 2s srv-unused px/srv1
        del server px/srv1"

Or even wait for connections to terminate themselves:

  $ socat -t70 /path/to/socket - <<< "
        disable server px/srv1
        wait 1m srv-unused px/srv1
        del server px/srv1"
This commit is contained in:
Willy Tarreau 2024-02-09 20:35:52 +01:00
parent 66989ff426
commit 613e959c7b
3 changed files with 79 additions and 12 deletions

View File

@ -3997,15 +3997,36 @@ update ssl ocsp-response <certfile>
local tree, its contents will be displayed on the standard output. The format
is the same as the one described in "show ssl ocsp-response".
wait { -h | <delay> }
This simply waits for the requested delay before continuing. This can be used
to collect metrics around a specific interval. The default unit for the delay
is milliseconds, though other units are accepted if suffixed with the usual
timer units (us, ms, s, m, h, d). When used with the 'socat' utility, do not
forget to extend socat's close timeout to cover the wait time. Passing "-h"
as the first or second argument provides the command's usage.
wait { -h | <delay> } [<condition> [<args>...]]
In its simplest form without any condition, this simply waits for the
requested delay before continuing. This can be used to collect metrics around
a specific interval.
With a condition and optional arguments, the command will wait for the
specified condition to be satisfied, to unrecoverably fail, or to remain
unsatisfied for the whole <delay> duration. The supported conditions are:
- srv-unused <proxy>/<server> : this will wait for the specified server to be
removable, i.e. be in maintenance and no longer have any connection on it.
Some conditions will never be accepted (e.g. not in maintenance) and will
cause the report of a specific error message indicating what condition is
not met. The server might even have been removed in parallel and no longer
exit. If everything is OK before the delay, a success is returned and the
operation is terminated.
The default unit for the delay is milliseconds, though other units are
accepted if suffixed with the usual timer units (us, ms, s, m, h, d). When
used with the 'socat' utility, do not forget to extend socat's close timeout
to cover the wait time. Passing "-h" as the first or second argument provides
the command's usage.
Example:
$ socat -t20 /path/to/socket <<< "show activity; wait 10s; show activity"
$ socat -t20 /path/to/socket - <<< "show activity; wait 10s; show activity"
$ socat -t5 /path/to/socket - <<< "
disable server px/srv1
shutdown sessions server px/srv1
wait 2s srv-unused px/srv1
del server px/srv1"
9.4. Master CLI

View File

@ -95,6 +95,7 @@ enum cli_wait_err {
enum cli_wait_cond {
CLI_WAIT_COND_NONE, // no condition to wait on
CLI_WAIT_COND_SRV_UNUSED,// wait for server to become unused
};
struct cli_wait_ctx {

View File

@ -2035,14 +2035,40 @@ static int cli_parse_wait(char **args, char *payload, struct appctx *appctx, voi
return cli_err(appctx, "Invalid duration.\n");
}
if (*args[2]) {
if (strcmp(args[2], "srv-unused") == 0) {
struct ist be_name, sv_name;
if (!*args[3])
return cli_err(appctx, "Missing server name (<backend>/<server>).\n");
sv_name = ist(args[3]);
be_name = istsplit(&sv_name, '/');
if (!istlen(sv_name))
return cli_err(appctx, "Require 'backend/server'.\n");
be_name = istdup(be_name);
sv_name = istdup(sv_name);
if (!isttest(be_name) || !isttest(sv_name)) {
free(istptr(be_name));
free(istptr(sv_name));
return cli_err(appctx, "Out of memory trying to clone the server name.\n");
}
ctx->args[0] = ist0(be_name);
ctx->args[1] = ist0(sv_name);
ctx->cond = CLI_WAIT_COND_SRV_UNUSED;
}
else if (*args[2]) {
/* show the command's help either upon request (-h) or error */
err = "Usage: wait {-h|<duration>} [condition [args...]]\n"
" - '-h' displays this help\n"
" - <duration> is the maximum wait time, optionally suffixed by the unit among\n"
" 'us', 'ms', 's', 'm', 'h', and 'd'. ; the default unit is milliseconds.\n"
" - <condition> indicates what to wait for. By default, no events aborts the\n"
" operation, which makes it reliably pause for the specified duration.\n";
" - <condition> indicates what to wait for, no longer than the specified\n"
" duration. Supported conditions are:\n"
" - <none> : by default, just sleep for the specified duration.\n"
" - srv-unused <px>/<sv> : wait for this server to become unused.\n"
"";
if (strcmp(args[2], "-h") == 0)
return cli_msg(appctx, LOG_INFO, err);
@ -2069,6 +2095,7 @@ static int cli_io_handler_wait(struct appctx *appctx)
struct cli_wait_ctx *ctx = appctx->svcctx;
struct stconn *sc = appctx_sc(appctx);
uint total, elapsed, left, wait;
int ret;
/* note: upon first invocation, the timeout is not set */
if (tick_isset(appctx->t->expire) &&
@ -2077,6 +2104,24 @@ static int cli_io_handler_wait(struct appctx *appctx)
/* here we should evaluate our waiting conditions, if any */
if (ctx->cond == CLI_WAIT_COND_SRV_UNUSED) {
/* check if the server in args[0]/args[1] can be released now */
thread_isolate();
ret = srv_check_for_deletion(ctx->args[0], ctx->args[1], NULL, NULL, NULL);
thread_release();
if (ret < 0) {
/* unrecoverable failure */
ctx->error = CLI_WAIT_ERR_FAIL;
return 1;
} else if (ret > 0) {
/* immediate success */
ctx->error = CLI_WAIT_ERR_DONE;
return 1;
}
/* let's check the timer */
}
/* and here we recalculate the new wait time or abort */
left = tick_remain(now_ms, ctx->deadline);
if (!left) {
@ -3530,7 +3575,7 @@ static struct cli_kw_list cli_kws = {{ },{
{ { "show", "version", NULL }, "show version : show version of the current process", cli_parse_show_version, NULL, NULL, NULL, ACCESS_MASTER },
{ { "operator", NULL }, "operator : lower the level of the current CLI session to operator", cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
{ { "user", NULL }, "user : lower the level of the current CLI session to user", cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
{ { "wait", NULL }, "wait {-h|<delay_ms>} : wait the specified delay (-h to see usage)", cli_parse_wait, cli_io_handler_wait, cli_release_wait, NULL },
{ { "wait", NULL }, "wait {-h|<delay_ms>} cond [args...] : wait the specified delay or condition (-h to see list)", cli_parse_wait, cli_io_handler_wait, cli_release_wait, NULL },
{{},}
}};