From: Thierry FOURNIER / OZON.IO Date: Sun, 13 Nov 2016 12:19:20 +0000 (+0100) Subject: MEDIUM: lua: Add cli handler for Lua X-Git-Tag: v1.7.0~83 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a44fdd95f9fa11a7ed4c073657bf22f8450d4875;p=thirdparty%2Fhaproxy.git 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. --- diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst index 32ef1436b9..530cd59cc4 100644 --- a/doc/lua-api/index.rst +++ b/doc/lua-api/index.rst @@ -519,6 +519,47 @@ Core class 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) **context**: task, action, sample-fetch, converter diff --git a/include/types/applet.h b/include/types/applet.h index e60b08a3db..cffc832563 100644 --- a/include/types/applet.h +++ b/include/types/applet.h @@ -121,6 +121,10 @@ struct appctx { struct list wake_on_read; struct list wake_on_write; } hlua; + struct { + struct hlua hlua; + struct task *task; + } hlua_cli; struct { struct hlua hlua; int flags; diff --git a/src/hlua.c b/src/hlua.c index 7e56b7544d..02e9b94c9e 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -6383,6 +6384,239 @@ __LJMP static int hlua_register_service(lua_State *L) 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("") + 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, "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, struct proxy *defpx, const char *file, int line, 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_action", hlua_register_action); 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, "set_nice", hlua_set_nice); hlua_class_function(gL.T, "sleep", hlua_sleep);