]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: lua: Add cli handler for Lua
authorThierry FOURNIER / OZON.IO <thierry.fournier@ozon.io>
Sun, 13 Nov 2016 12:19:20 +0000 (13:19 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 18 Nov 2016 13:32:03 +0000 (14:32 +0100)
Now, HAProxy allows to register some keys in the "cli". This patch allows
to handle these keys with Lua code.

doc/lua-api/index.rst
include/types/applet.h
src/hlua.c

index 32ef1436b96cbb78bb098e48b19a5233c8170942..530cd59cc447be57bd00dad9704927b8cd0a88a9 100644 (file)
@@ -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
index e60b08a3db8f90a89b462e4c69d760f764fdb547..cffc8325638b5cf2b44338ccd7efe13c68bde36f 100644 (file)
@@ -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;
index 7e56b7544dd6cb9e6f669899fff2618bc818b574..02e9b94c9ea91a048a24d67a2cee284dd54524ed 100644 (file)
@@ -32,6 +32,7 @@
 #include <proto/applet.h>
 #include <proto/channel.h>
 #include <proto/connection.h>
+#include <proto/dumpstats.h>
 #include <proto/hdr_idx.h>
 #include <proto/hlua.h>
 #include <proto/hlua_fcn.h>
@@ -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("<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,
                              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);