MEDIUM: lua: Add cli handler for Lua

Now, HAProxy allows to register some keys in the "cli". This patch allows
to handle these keys with Lua code.
This commit is contained in:
Thierry FOURNIER / OZON.IO 2016-11-13 13:19:20 +01:00 committed by Willy Tarreau
parent 6a22dcbe27
commit a44fdd95f9
3 changed files with 280 additions and 0 deletions

View File

@ -519,6 +519,47 @@ Core class
It takes no input, and no output is expected. It takes no input, and no output is expected.
.. js:function:: core.register_cli([path], usage, func)
**context**: body
Register and start independent task. The task is started when the HAProxy
main scheduler starts. For example this type of tasks can be executed to
perform complex health checks.
:param array path: is the sequence of word for which the cli execute the Lua
binding.
:param string usage: is the usage message displayed in the help.
:param function func: is the Lua function called to handle the CLI commands.
The prototype of the Lua function used as argument is:
.. code-block:: lua
function(AppletTCP, [arg1, [arg2, [...]]])
..
I/O are managed with the :ref:`applettcp_class` object. Args are given as
paramter. The args embbed the registred path. If the path is declared like
this:
.. code-block:: lua
core.register_cli({"show", "ssl", "stats"}, "Display SSL stats..", function(applet, arg1, arg2, arg3, arg4, arg5)
end)
..
And we execute this in the prompt:
.. code-block:: text
> prompt
> show ssl stats all
..
Then, arg1, arg2 and arg3 will contains respectivey "show", "ssl" and "stats".
arg4 will contain "all". arg5 contains nil.
.. js:function:: core.set_nice(nice) .. js:function:: core.set_nice(nice)
**context**: task, action, sample-fetch, converter **context**: task, action, sample-fetch, converter

View File

@ -121,6 +121,10 @@ struct appctx {
struct list wake_on_read; struct list wake_on_read;
struct list wake_on_write; struct list wake_on_write;
} hlua; } hlua;
struct {
struct hlua hlua;
struct task *task;
} hlua_cli;
struct { struct {
struct hlua hlua; struct hlua hlua;
int flags; int flags;

View File

@ -32,6 +32,7 @@
#include <proto/applet.h> #include <proto/applet.h>
#include <proto/channel.h> #include <proto/channel.h>
#include <proto/connection.h> #include <proto/connection.h>
#include <proto/dumpstats.h>
#include <proto/hdr_idx.h> #include <proto/hdr_idx.h>
#include <proto/hlua.h> #include <proto/hlua.h>
#include <proto/hlua_fcn.h> #include <proto/hlua_fcn.h>
@ -6383,6 +6384,239 @@ __LJMP static int hlua_register_service(lua_State *L)
return 0; return 0;
} }
/* This function initialises Lua cli handler. It copies the
* arguments in the Lua stack and create channel IO objects.
*/
static int hlua_cli_parse_fct(char **args, struct appctx *appctx, void *private)
{
struct hlua *hlua;
struct hlua_function *fcn;
int i;
const char *error;
hlua = &appctx->ctx.hlua_cli.hlua;
fcn = private;
appctx->private = private;
/* Create task used by signal to wakeup applets.
* We use the same wakeup fonction than the Lua applet_tcp and
* applet_http. It is absolutely compatible.
*/
appctx->ctx.hlua_cli.task = task_new();
if (!appctx->ctx.hlua_cli.task) {
SEND_ERR(NULL, "Lua applet tcp '%s': out of memory.\n", fcn->name);
return 0;
}
appctx->ctx.hlua_cli.task->nice = 0;
appctx->ctx.hlua_cli.task->context = appctx;
appctx->ctx.hlua_cli.task->process = hlua_applet_wakeup;
/* Initialises the Lua context */
if (!hlua_ctx_init(hlua, appctx->ctx.hlua_cli.task)) {
SEND_ERR(NULL, "Lua cli '%s': can't initialize Lua context.\n", fcn->name);
return 1;
}
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(hlua->T)) {
if (lua_type(hlua->T, -1) == LUA_TSTRING)
error = lua_tostring(hlua->T, -1);
else
error = "critical error";
SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error);
goto error;
}
/* Check stack available size. */
if (!lua_checkstack(hlua->T, 2)) {
SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
goto error;
}
/* Restore the function in the stack. */
lua_rawgeti(hlua->T, LUA_REGISTRYINDEX, fcn->function_ref);
/* Once the arguments parsed, the CLI is like an AppletTCP,
* so push AppletTCP in the stack.
* TODO: get_priv() and set_priv() are useless. Maybe we will
* create a new object without these two functions.
*/
if (!hlua_applet_tcp_new(hlua->T, appctx)) {
SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
goto error;
}
hlua->nargs = 1;
/* push keywords in the stack. */
for (i = 0; *args[i]; i++) {
/* Check stack available size. */
if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
goto error;
}
lua_pushstring(hlua->T, args[i]);
hlua->nargs++;
}
/* We must initialize the execution timeouts. */
hlua->max_time = hlua_timeout_session;
/* At this point the execution is safe. */
RESET_SAFE_LJMP(hlua->T);
/* It's ok */
return 0;
/* It's not ok. */
error:
RESET_SAFE_LJMP(hlua->T);
hlua_ctx_destroy(hlua);
return 1;
}
static int hlua_cli_io_handler_fct(struct appctx *appctx)
{
struct hlua *hlua;
struct stream_interface *si;
struct hlua_function *fcn;
hlua = &appctx->ctx.hlua_cli.hlua;
si = appctx->owner;
fcn = appctx->private;
/* If the stream is disconnect or closed, ldo nothing. */
if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
return 1;
/* Execute the function. */
switch (hlua_ctx_resume(hlua, 1)) {
/* finished. */
case HLUA_E_OK:
return 1;
/* yield. */
case HLUA_E_AGAIN:
/* We want write. */
if (HLUA_IS_WAKERESWR(hlua))
si_applet_cant_put(si);
/* Set the timeout. */
if (hlua->wake_time != TICK_ETERNITY)
task_schedule(hlua->task, hlua->wake_time);
return 0;
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
SEND_ERR(NULL, "Lua cli '%s': %s.\n",
fcn->name, lua_tostring(hlua->T, -1));
lua_pop(hlua->T, 1);
return 1;
case HLUA_E_ERR:
/* Display log. */
SEND_ERR(NULL, "Lua cli '%s' return an unknown error.\n",
fcn->name);
return 1;
default:
return 1;
}
return 1;
}
static void hlua_cli_io_release_fct(struct appctx *appctx)
{
struct hlua *hlua;
hlua = &appctx->ctx.hlua_cli.hlua;
hlua_ctx_destroy(hlua);
}
/* This function is an LUA binding used for registering
* new keywords in the cli. It expects a list of keywords
* which are the "path". It is limited to 5 keywords. A
* description of the command, a function to be executed
* for the parsing and a function for io handlers.
*/
__LJMP static int hlua_register_cli(lua_State *L)
{
struct cli_kw_list *cli_kws;
const char *message;
int ref_io;
int len;
struct hlua_function *fcn;
int index;
int i;
MAY_LJMP(check_args(L, 3, "register_cli"));
/* First argument : an array of maximum 5 keywords. */
if (!lua_istable(L, 1))
WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table"));
/* Second argument : string with contextual message. */
message = MAY_LJMP(luaL_checkstring(L, 2));
/* Third and fourth argument : lua function. */
ref_io = MAY_LJMP(hlua_checkfunction(L, 3));
/* Allocate and fill the sample fetch keyword struct. */
cli_kws = calloc(1, sizeof(*cli_kws) + sizeof(struct cli_kw) * 2);
if (!cli_kws)
WILL_LJMP(luaL_error(L, "lua out of memory error."));
fcn = calloc(1, sizeof(*fcn));
if (!fcn)
WILL_LJMP(luaL_error(L, "lua out of memory error."));
/* Fill path. */
index = 0;
lua_pushnil(L);
while(lua_next(L, 1) != 0) {
if (index >= 5)
WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table with a maximum of 5 entries"));
if (lua_type(L, -1) != LUA_TSTRING)
WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table filled with strings"));
cli_kws->kw[0].str_kw[index] = strdup(lua_tostring(L, -1));
if (!cli_kws->kw[0].str_kw[index])
WILL_LJMP(luaL_error(L, "lua out of memory error."));
index++;
lua_pop(L, 1);
}
/* Copy help message. */
cli_kws->kw[0].usage = strdup(message);
if (!cli_kws->kw[0].usage)
WILL_LJMP(luaL_error(L, "lua out of memory error."));
/* Fill fcn io handler. */
len = strlen("<lua.cli>") + 1;
for (i = 0; i < index; i++)
len += strlen(cli_kws->kw[0].str_kw[i]) + 1;
fcn->name = calloc(1, len);
if (!fcn->name)
WILL_LJMP(luaL_error(L, "lua out of memory error."));
strncat((char *)fcn->name, "<lua.cli", len);
for (i = 0; i < index; i++) {
strncat((char *)fcn->name, ".", len);
strncat((char *)fcn->name, cli_kws->kw[0].str_kw[i], len);
}
strncat((char *)fcn->name, ">", len);
fcn->function_ref = ref_io;
/* Fill last entries. */
cli_kws->kw[0].private = fcn;
cli_kws->kw[0].parse = hlua_cli_parse_fct;
cli_kws->kw[0].io_handler = hlua_cli_io_handler_fct;
cli_kws->kw[0].io_release = hlua_cli_io_release_fct;
/* Register this new converter */
cli_register_kw(cli_kws);
return 0;
}
static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx, static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line, struct proxy *defpx, const char *file, int line,
char **err, unsigned int *timeout) char **err, unsigned int *timeout)
@ -6686,6 +6920,7 @@ void hlua_init(void)
hlua_class_function(gL.T, "register_converters", hlua_register_converters); hlua_class_function(gL.T, "register_converters", hlua_register_converters);
hlua_class_function(gL.T, "register_action", hlua_register_action); hlua_class_function(gL.T, "register_action", hlua_register_action);
hlua_class_function(gL.T, "register_service", hlua_register_service); hlua_class_function(gL.T, "register_service", hlua_register_service);
hlua_class_function(gL.T, "register_cli", hlua_register_cli);
hlua_class_function(gL.T, "yield", hlua_yield); hlua_class_function(gL.T, "yield", hlua_yield);
hlua_class_function(gL.T, "set_nice", hlua_set_nice); hlua_class_function(gL.T, "set_nice", hlua_set_nice);
hlua_class_function(gL.T, "sleep", hlua_sleep); hlua_class_function(gL.T, "sleep", hlua_sleep);