Welcome! Log In Create A New Profile

Advanced

[PATCH] MEDIUM: lua: Add stick table support for Lua

Posted by Adis Nezirovic 
Adis Nezirovic
[PATCH] MEDIUM: lua: Add stick table support for Lua
August 20, 2018 02:20PM
Hi guys,

I've attached a patch to add stick table support to Lua. Operations are
mostly similar to "show table" functionality from admin socket, namely:

- Show basic table info
- Key lookup
- Table dump, optionally using data/column filter

One side note, the code provides support for multiple filters
(4 by default) while CLI doesn't support that, nor it complains about
multiple "<data.type> <op> <val>" clauses.

Also, if this patch is accepted, maybe we can use provided helper
functions in other places in the code.

Best regards,
Adis
From 27d0c08a85e8a997fb96e65937c70e983e31c21e Mon Sep 17 00:00:00 2001
From: Adis Nezirovic <[email protected]>
Date: Fri, 13 Jul 2018 12:18:33 +0200
Subject: [PATCH] MEDIUM: lua: Add stick table support for Lua (read-only ops).

---
doc/lua-api/index.rst | 46 +++++
include/types/hlua.h | 1 +
src/hlua_fcn.c | 401 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 448 insertions(+)

diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 0c79766e..63848661 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -852,6 +852,10 @@ Proxy class
Contain a table with the attached servers. The table is indexed by server
name, and each server entry is an object of type :ref:`server_class`.

+.. js:attribute:: Proxy.stktable
+
+ Contains a stick table object attached to the proxy.
+
.. js:attribute:: Proxy.listeners

Contain a table with the attached listeners. The table is indexed by listener
@@ -2489,6 +2493,48 @@ AppletTCP class
:see: :js:func:`AppletTCP.unset_var`
:see: :js:func:`AppletTCP.set_var`

+StickTable class
+================
+
+.. js:class:: StickTable
+
+ This class is used with applets that requires the 'tcp' mode. The tcp applet
+ can be registered with the *core.register_service()* function. They are used
+ for processing a tcp stream like a server in back of HAProxy.
+
+.. js:function:: StickTable.info()
+
+ Returns relevant stick table attributes: type, length, size, used, nopurge,
+ expire and exact interval values for frequency data columns.
+
+ :returns: Lua table
+
+.. js:function:: StickTable.lookup(key)
+
+ Returns stick table entry for given <key>
+
+ :param string key: Stick table key (IP addresses and strings are supported)
+ :returns: Lua table
+
+.. js:function:: StickTable.dump([filter])
+
+ Returns all entries in stick table. An optional filter can be used
+ to extract entries with specific data values. Filter is a table with valid
+ comparison operators as keys followed by data type name and value pairs.
+ e.g.
+
+ :param table filter: Stick table filter
+ :returns: Stick table entries (table)
+
+.. code-block:: lua
+
+ local filter = {
+ lt={{"gpc0", 1}, {"gpc1", 2}},
+ gt={{"conn_rate", 3}},
+ eq={{"conn_cur", 4}}
+ }
+
+
External Lua libraries
======================

diff --git a/include/types/hlua.h b/include/types/hlua.h
index 5a8173f3..2e453351 100644
--- a/include/types/hlua.h
+++ b/include/types/hlua.h
@@ -25,6 +25,7 @@
#define CLASS_SERVER "Server"
#define CLASS_LISTENER "Listener"
#define CLASS_REGEX "Regex"
+#define CLASS_STKTABLE "StickTable"

struct stream;

diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index cebce224..49f7ef03 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -30,6 +30,7 @@
#include <proto/proxy.h>
#include <proto/server.h>
#include <proto/stats.h>
+#include <proto/stick_table.h>

/* Contains the class reference of the concat object. */
static int class_concat_ref;
@@ -37,7 +38,9 @@ static int class_proxy_ref;
static int class_server_ref;
static int class_listener_ref;
static int class_regex_ref;
+static int class_stktable_ref;

+#define MAX_STK_FILTER_LEN 4
#define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))

static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -49,6 +52,38 @@ int hlua_checkboolean(lua_State *L, int index)
return lua_toboolean(L, index);
}

+/* Helper to push unsigned integers to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned(lua_State *L, unsigned int val)
+{
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ lua_pushinteger(L, val);
+#else
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
+/* Helper to push unsigned long long to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned_ll(lua_State *L, unsigned long long val) {
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ /* 64 bits case, U64 is supported until LLONG_MAX */
+ if (val > LLONG_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, val);
+#else
+ /* 32 bits case, U64 is supported until INT_MAX */
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
/* This function gets a struct field and convert it in Lua
* variable. The variable is pushed at the top of the stak.
*/
@@ -446,6 +481,356 @@ static int hlua_concat_init(lua_State *L)
return 1;
}

+int hlua_fcn_new_stktable(lua_State *L, struct stktable *tbl)
+{
+ lua_newtable(L);
+
+ /* Pop a class stktbl metatable and affect it to the userdata. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_stktable_ref);
+ lua_setmetatable(L, -2);
+
+ lua_pushlightuserdata(L, tbl);
+ lua_rawseti(L, -2, 0);
+ return 1;
+}
+
+static struct stktable *hlua_check_stktable(lua_State *L, int ud)
+{
+ return hlua_checkudata(L, ud, class_stktable_ref);
+}
+
+/* Extract stick table attributes into Lua table */
+int hlua_stktable_info(lua_State *L)
+{
+ struct stktable *tbl;
+ int dt;
+
+ tbl = hlua_check_stktable(L, 1);
+
+ if (!tbl->id) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ lua_pushstring(L, "type");
+ lua_pushstring(L, stktable_types[tbl->type].kw);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "length");
+ lua_pushinteger(L, tbl->key_size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "size");
+ hlua_fcn_pushunsigned(L, tbl->size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "used");
+ hlua_fcn_pushunsigned(L, tbl->current);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "nopurge");
+ lua_pushboolean(L, tbl->nopurge > 0);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tbl->expire);
+ lua_settable(L, -3);
+
+ /* Save data types periods (if applicable) in 'data' table */
+ lua_pushstring(L, "data");
+ lua_newtable(L);
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+ if (tbl->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
+ lua_pushinteger(L, tbl->data_arg[dt].u);
+ else
+ lua_pushinteger(L, -1);
+
+ lua_settable(L, -3);
+ }
+
+ lua_settable(L, -3);
+
+ return 1;
+}
+
+/* Helper to get extract stick table entry into Lua table */
+static void hlua_stktable_entry(lua_State *L, struct stktable *t, struct stksess *ts)
+{
+ int dt;
+ void *ptr;
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+
+ if (t->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ ptr = stktable_data_ptr(t, ts, dt);
+ switch (stktable_data_types[dt].std_type) {
+ case STD_T_SINT:
+ lua_pushinteger(L, stktable_data_cast(ptr, std_t_sint));
+ break;
+ case STD_T_UINT:
+ hlua_fcn_pushunsigned(L, stktable_data_cast(ptr, std_t_uint));
+ break;
+ case STD_T_ULL:
+ hlua_fcn_pushunsigned_ll(L, stktable_data_cast(ptr, std_t_ull));
+ break;
+ case STD_T_FRQP:
+ lua_pushinteger(L, read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[dt].u));
+ break;
+ }
+
+ lua_settable(L, -3);
+ }
+}
+
+/* Looks in table <t> for a sticky session matching key <key>
+ * Returns table with session data or nil
+ *
+ * The returned table always contains 'use' and 'expire' (integer) fields.
+ * For frequency/rate counters, each data entry is returned as table with
+ * 'value' and 'period' fields.
+ */
+int hlua_stktable_lookup(lua_State *L)
+{
+ struct stktable *t;
+ const char *key;
+ struct sample smp;
+ struct stktable_key *skey;
+ struct stksess *ts;
+
+ t = hlua_check_stktable(L, 1);
+ key = luaL_checkstring(L, 2);
+
+ smp.data.type = SMP_T_STR;
+ smp.flags |= SMP_F_CONST;
+ smp.data.u.str.area = (char *)key;
+
+ skey = smp_to_stkey(&smp, t);
+ if (!skey) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ ts = stktable_lookup_key(t, skey);
+ if (!ts) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+ lua_pushstring(L, "use");
+ lua_pushinteger(L, ts->ref_cnt - 1);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tick_remain(now_ms, ts->expire));
+ lua_settable(L, -3);
+
+ hlua_stktable_entry(L, t, ts);
+ ts->ref_cnt--;
+
+ return 1;
+}
+
+struct stk_filter {
+ long long val;
+ int type;
+ int op;
+};
+
+
+static int hlua_error(lua_State *L, const char *msg) {
+ lua_pushnil(L);
+ lua_pushstring(L, msg);
+ return 2;
+}
+
+/* Dump the contents of stick table <t>*/
+int hlua_stktable_dump(lua_State *L)
+{
+ struct stktable *t;
+ struct ebmb_node *eb;
+ struct ebmb_node *n;
+ struct stksess *ts;
+ int type;
+ int op;
+ int dt;
+ long long val;
+ struct stk_filter filter[MAX_STK_FILTER_LEN];
+ int filter_count = 0;
+ int i;
+ int skip_entry;
+ void *ptr;
+
+ t = hlua_check_stktable(L, 1);
+ type = lua_type(L, 2);
+
+ switch (type) {
+ case LUA_TNONE:
+ case LUA_TNIL:
+ break;
+ case LUA_TTABLE:
+ lua_pushnil(L);
+ while (lua_next(L, 2) != 0) {
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ return hlua_error(L, "String key (operator name) expected");
+ }
+
+ op = get_std_op(lua_tostring(L, -2));
+ if (op < 0) {
+ return hlua_error(L, "Unknown stick table/acl operator");
+ }
+
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Datatype/value table expected");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ int entry_idx = 0;
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ filter[filter_count].op = op;
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Filter table must be multidimensional");
+ }
+
+ if (lua_rawlen(L, -1) != 2) {
+ return hlua_error(L, "Filter table entry length must be 2");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ if (entry_idx == 0) {
+ if (lua_type(L, -1) != LUA_TSTRING) {
+ return hlua_error(L, "Stick table data type must be string");
+ }
+ dt = stktable_get_data_type((char *)lua_tostring(L, -1));
+
+ if (t->data_ofs[dt] == 0) {
+ return hlua_error(L, "Stick table filter column not present in table");
+
+ }
+
+ filter[filter_count].type = dt;
+ } else {
+ val = lua_tointeger(L, -1);
+ filter[filter_count].val = val;
+ filter_count++;
+ }
+ entry_idx++;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+
+ break;
+ default:
+ return hlua_error(L, "filter table expected");
+ }
+
+ lua_newtable(L);
+
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ eb = ebmb_first(&t->keys);
+ for (n = eb; n; n = ebmb_next(n))
+ {
+ ts = ebmb_entry(n, struct stksess, key);
+ if (!ts) {
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+ return 1;
+ }
+ ts->ref_cnt++;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ /* multi condition/value filter */
+ skip_entry = 0;
+ for (i = 0; i < filter_count; i++) {
+ if (t->data_ofs[filter.type] == 0)
+ continue;
+
+ ptr = stktable_data_ptr(t, ts, filter.type);
+
+ switch (stktable_data_types[filter.type].std_type) {
+ case STD_T_SINT:
+ val = stktable_data_cast(ptr, std_t_sint);
+ break;
+ case STD_T_UINT:
+ val = stktable_data_cast(ptr, std_t_uint);
+ break;
+ case STD_T_ULL:
+ val = stktable_data_cast(ptr, std_t_ull);
+ break;
+ case STD_T_FRQP:
+ val = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[filter.type].u);
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ op = filter.op;
+
+ if ((val < filter.val && (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+ (val == filter.val && (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+ (val > filter.val && (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+ skip_entry = 1;
+ break;
+ }
+ }
+
+ if (skip_entry) {
+ ts->ref_cnt--;
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ continue;
+ }
+
+ if (t->type == SMP_T_IPV4) {
+ char addr[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_IPV6) {
+ char addr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_SINT) {
+ hlua_fcn_pushunsigned(L, *(unsigned int *)ts->key.key);
+ } else if (t->type == SMP_T_STR) {
+ lua_pushstring(L, (const char *)ts->key.key);
+ } else {
+ return hlua_error(L, "Unsupported stick table key type");
+ }
+
+ ts->ref_cnt--;
+ lua_newtable(L);
+ hlua_stktable_entry(L, t, ts);
+ lua_settable(L, -3);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ }
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
int hlua_fcn_new_listener(lua_State *L, struct listener *lst)
{
lua_newtable(L);
@@ -887,6 +1272,12 @@ int hlua_fcn_new_proxy(lua_State *L, struct proxy *px)
}
lua_settable(L, -3);

+ if (px->table.id) {
+ lua_pushstring(L, "stktable");
+ hlua_fcn_new_stktable(L, &px->table);
+ lua_settable(L, -3);
+ }
+
return 1;
}

@@ -1289,6 +1680,16 @@ int hlua_fcn_reg_core_fcn(lua_State *L)
lua_setmetatable(L, -2);
lua_setglobal(L, CLASS_REGEX); /* Create global object called Regex */

+ /* Create stktable object. */
+ lua_newtable(L);
+ lua_pushstring(L, "__index");
+ lua_newtable(L);
+ hlua_class_function(L, "info", hlua_stktable_info);
+ hlua_class_function(L, "lookup", hlua_stktable_lookup);
+ hlua_class_function(L, "dump", hlua_stktable_dump);
+ lua_settable(L, -3); /* -> META["__index"] = TABLE */
+ class_stktable_ref = hlua_register_metatable(L, CLASS_STKTABLE);
+
/* Create listener object. */
lua_newtable(L);
lua_pushstring(L, "__index");
--
2.18.0
Adis Nezirovic
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 20, 2018 03:20PM
On Mon, Aug 20, 2018 at 02:11:13PM +0200, Adis Nezirovic wrote:
> Hi guys,
>
> I've attached a patch to add stick table support to Lua. Operations are
> mostly similar to "show table" functionality from admin socket, namely:
>
> - Show basic table info
> - Key lookup
> - Table dump, optionally using data/column filter
>
> One side note, the code provides support for multiple filters
> (4 by default) while CLI doesn't support that, nor it complains about
> multiple "<data.type> <op> <val>" clauses.
>
> Also, if this patch is accepted, maybe we can use provided helper
> functions in other places in the code.
>

It's always funny to reply to self, right sending email to public I've
spotted a bug. New patch attached.

SMT_T_SINT should be treated as ordinary signed integer, and shoud use
lua_pushinteger() on it?

On many places in the code it is noted as "64" bit integer, but in
stick_table.c it is defined as 32bit integer:

struct stktable_type stktable_types[SMP_TYPES] = {
[SMP_T_SINT] = { "integer", 0, 4 },


Best regards,
Adis
From 4c699b0d57ba5d6d5463595a4bdaa2f2ae8c2c56 Mon Sep 17 00:00:00 2001
From: Adis Nezirovic <[email protected]>
Date: Fri, 13 Jul 2018 12:18:33 +0200
Subject: [PATCH] MEDIUM: lua: Add stick table support for Lua (read-only ops).

---
doc/lua-api/index.rst | 46 +++++
include/types/hlua.h | 1 +
src/hlua_fcn.c | 401 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 448 insertions(+)

diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 0c79766e..63848661 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -852,6 +852,10 @@ Proxy class
Contain a table with the attached servers. The table is indexed by server
name, and each server entry is an object of type :ref:`server_class`.

+.. js:attribute:: Proxy.stktable
+
+ Contains a stick table object attached to the proxy.
+
.. js:attribute:: Proxy.listeners

Contain a table with the attached listeners. The table is indexed by listener
@@ -2489,6 +2493,48 @@ AppletTCP class
:see: :js:func:`AppletTCP.unset_var`
:see: :js:func:`AppletTCP.set_var`

+StickTable class
+================
+
+.. js:class:: StickTable
+
+ This class is used with applets that requires the 'tcp' mode. The tcp applet
+ can be registered with the *core.register_service()* function. They are used
+ for processing a tcp stream like a server in back of HAProxy.
+
+.. js:function:: StickTable.info()
+
+ Returns relevant stick table attributes: type, length, size, used, nopurge,
+ expire and exact interval values for frequency data columns.
+
+ :returns: Lua table
+
+.. js:function:: StickTable.lookup(key)
+
+ Returns stick table entry for given <key>
+
+ :param string key: Stick table key (IP addresses and strings are supported)
+ :returns: Lua table
+
+.. js:function:: StickTable.dump([filter])
+
+ Returns all entries in stick table. An optional filter can be used
+ to extract entries with specific data values. Filter is a table with valid
+ comparison operators as keys followed by data type name and value pairs.
+ e.g.
+
+ :param table filter: Stick table filter
+ :returns: Stick table entries (table)
+
+.. code-block:: lua
+
+ local filter = {
+ lt={{"gpc0", 1}, {"gpc1", 2}},
+ gt={{"conn_rate", 3}},
+ eq={{"conn_cur", 4}}
+ }
+
+
External Lua libraries
======================

diff --git a/include/types/hlua.h b/include/types/hlua.h
index 5a8173f3..2e453351 100644
--- a/include/types/hlua.h
+++ b/include/types/hlua.h
@@ -25,6 +25,7 @@
#define CLASS_SERVER "Server"
#define CLASS_LISTENER "Listener"
#define CLASS_REGEX "Regex"
+#define CLASS_STKTABLE "StickTable"

struct stream;

diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index cebce224..f38b9f37 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -30,6 +30,7 @@
#include <proto/proxy.h>
#include <proto/server.h>
#include <proto/stats.h>
+#include <proto/stick_table.h>

/* Contains the class reference of the concat object. */
static int class_concat_ref;
@@ -37,7 +38,9 @@ static int class_proxy_ref;
static int class_server_ref;
static int class_listener_ref;
static int class_regex_ref;
+static int class_stktable_ref;

+#define MAX_STK_FILTER_LEN 4
#define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))

static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -49,6 +52,38 @@ int hlua_checkboolean(lua_State *L, int index)
return lua_toboolean(L, index);
}

+/* Helper to push unsigned integers to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned(lua_State *L, unsigned int val)
+{
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ lua_pushinteger(L, val);
+#else
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
+/* Helper to push unsigned long long to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned_ll(lua_State *L, unsigned long long val) {
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ /* 64 bits case, U64 is supported until LLONG_MAX */
+ if (val > LLONG_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, val);
+#else
+ /* 32 bits case, U64 is supported until INT_MAX */
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
/* This function gets a struct field and convert it in Lua
* variable. The variable is pushed at the top of the stak.
*/
@@ -446,6 +481,356 @@ static int hlua_concat_init(lua_State *L)
return 1;
}

+int hlua_fcn_new_stktable(lua_State *L, struct stktable *tbl)
+{
+ lua_newtable(L);
+
+ /* Pop a class stktbl metatable and affect it to the userdata. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_stktable_ref);
+ lua_setmetatable(L, -2);
+
+ lua_pushlightuserdata(L, tbl);
+ lua_rawseti(L, -2, 0);
+ return 1;
+}
+
+static struct stktable *hlua_check_stktable(lua_State *L, int ud)
+{
+ return hlua_checkudata(L, ud, class_stktable_ref);
+}
+
+/* Extract stick table attributes into Lua table */
+int hlua_stktable_info(lua_State *L)
+{
+ struct stktable *tbl;
+ int dt;
+
+ tbl = hlua_check_stktable(L, 1);
+
+ if (!tbl->id) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ lua_pushstring(L, "type");
+ lua_pushstring(L, stktable_types[tbl->type].kw);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "length");
+ lua_pushinteger(L, tbl->key_size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "size");
+ hlua_fcn_pushunsigned(L, tbl->size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "used");
+ hlua_fcn_pushunsigned(L, tbl->current);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "nopurge");
+ lua_pushboolean(L, tbl->nopurge > 0);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tbl->expire);
+ lua_settable(L, -3);
+
+ /* Save data types periods (if applicable) in 'data' table */
+ lua_pushstring(L, "data");
+ lua_newtable(L);
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+ if (tbl->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
+ lua_pushinteger(L, tbl->data_arg[dt].u);
+ else
+ lua_pushinteger(L, -1);
+
+ lua_settable(L, -3);
+ }
+
+ lua_settable(L, -3);
+
+ return 1;
+}
+
+/* Helper to get extract stick table entry into Lua table */
+static void hlua_stktable_entry(lua_State *L, struct stktable *t, struct stksess *ts)
+{
+ int dt;
+ void *ptr;
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+
+ if (t->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ ptr = stktable_data_ptr(t, ts, dt);
+ switch (stktable_data_types[dt].std_type) {
+ case STD_T_SINT:
+ lua_pushinteger(L, stktable_data_cast(ptr, std_t_sint));
+ break;
+ case STD_T_UINT:
+ hlua_fcn_pushunsigned(L, stktable_data_cast(ptr, std_t_uint));
+ break;
+ case STD_T_ULL:
+ hlua_fcn_pushunsigned_ll(L, stktable_data_cast(ptr, std_t_ull));
+ break;
+ case STD_T_FRQP:
+ lua_pushinteger(L, read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[dt].u));
+ break;
+ }
+
+ lua_settable(L, -3);
+ }
+}
+
+/* Looks in table <t> for a sticky session matching key <key>
+ * Returns table with session data or nil
+ *
+ * The returned table always contains 'use' and 'expire' (integer) fields.
+ * For frequency/rate counters, each data entry is returned as table with
+ * 'value' and 'period' fields.
+ */
+int hlua_stktable_lookup(lua_State *L)
+{
+ struct stktable *t;
+ const char *key;
+ struct sample smp;
+ struct stktable_key *skey;
+ struct stksess *ts;
+
+ t = hlua_check_stktable(L, 1);
+ key = luaL_checkstring(L, 2);
+
+ smp.data.type = SMP_T_STR;
+ smp.flags |= SMP_F_CONST;
+ smp.data.u.str.area = (char *)key;
+
+ skey = smp_to_stkey(&smp, t);
+ if (!skey) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ ts = stktable_lookup_key(t, skey);
+ if (!ts) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+ lua_pushstring(L, "use");
+ lua_pushinteger(L, ts->ref_cnt - 1);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tick_remain(now_ms, ts->expire));
+ lua_settable(L, -3);
+
+ hlua_stktable_entry(L, t, ts);
+ ts->ref_cnt--;
+
+ return 1;
+}
+
+struct stk_filter {
+ long long val;
+ int type;
+ int op;
+};
+
+
+static int hlua_error(lua_State *L, const char *msg) {
+ lua_pushnil(L);
+ lua_pushstring(L, msg);
+ return 2;
+}
+
+/* Dump the contents of stick table <t>*/
+int hlua_stktable_dump(lua_State *L)
+{
+ struct stktable *t;
+ struct ebmb_node *eb;
+ struct ebmb_node *n;
+ struct stksess *ts;
+ int type;
+ int op;
+ int dt;
+ long long val;
+ struct stk_filter filter[MAX_STK_FILTER_LEN];
+ int filter_count = 0;
+ int i;
+ int skip_entry;
+ void *ptr;
+
+ t = hlua_check_stktable(L, 1);
+ type = lua_type(L, 2);
+
+ switch (type) {
+ case LUA_TNONE:
+ case LUA_TNIL:
+ break;
+ case LUA_TTABLE:
+ lua_pushnil(L);
+ while (lua_next(L, 2) != 0) {
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ return hlua_error(L, "String key (operator name) expected");
+ }
+
+ op = get_std_op(lua_tostring(L, -2));
+ if (op < 0) {
+ return hlua_error(L, "Unknown stick table/acl operator");
+ }
+
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Datatype/value table expected");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ int entry_idx = 0;
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ filter[filter_count].op = op;
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Filter table must be multidimensional");
+ }
+
+ if (lua_rawlen(L, -1) != 2) {
+ return hlua_error(L, "Filter table entry length must be 2");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ if (entry_idx == 0) {
+ if (lua_type(L, -1) != LUA_TSTRING) {
+ return hlua_error(L, "Stick table data type must be string");
+ }
+ dt = stktable_get_data_type((char *)lua_tostring(L, -1));
+
+ if (t->data_ofs[dt] == 0) {
+ return hlua_error(L, "Stick table filter column not present in table");
+
+ }
+
+ filter[filter_count].type = dt;
+ } else {
+ val = lua_tointeger(L, -1);
+ filter[filter_count].val = val;
+ filter_count++;
+ }
+ entry_idx++;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+
+ break;
+ default:
+ return hlua_error(L, "filter table expected");
+ }
+
+ lua_newtable(L);
+
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ eb = ebmb_first(&t->keys);
+ for (n = eb; n; n = ebmb_next(n))
+ {
+ ts = ebmb_entry(n, struct stksess, key);
+ if (!ts) {
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+ return 1;
+ }
+ ts->ref_cnt++;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ /* multi condition/value filter */
+ skip_entry = 0;
+ for (i = 0; i < filter_count; i++) {
+ if (t->data_ofs[filter.type] == 0)
+ continue;
+
+ ptr = stktable_data_ptr(t, ts, filter.type);
+
+ switch (stktable_data_types[filter.type].std_type) {
+ case STD_T_SINT:
+ val = stktable_data_cast(ptr, std_t_sint);
+ break;
+ case STD_T_UINT:
+ val = stktable_data_cast(ptr, std_t_uint);
+ break;
+ case STD_T_ULL:
+ val = stktable_data_cast(ptr, std_t_ull);
+ break;
+ case STD_T_FRQP:
+ val = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[filter.type].u);
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ op = filter.op;
+
+ if ((val < filter.val && (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+ (val == filter.val && (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+ (val > filter.val && (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+ skip_entry = 1;
+ break;
+ }
+ }
+
+ if (skip_entry) {
+ ts->ref_cnt--;
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ continue;
+ }
+
+ if (t->type == SMP_T_IPV4) {
+ char addr[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_IPV6) {
+ char addr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_SINT) {
+ lua_pushinteger(L, *ts->key.key);
+ } else if (t->type == SMP_T_STR) {
+ lua_pushstring(L, (const char *)ts->key.key);
+ } else {
+ return hlua_error(L, "Unsupported stick table key type");
+ }
+
+ ts->ref_cnt--;
+ lua_newtable(L);
+ hlua_stktable_entry(L, t, ts);
+ lua_settable(L, -3);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ }
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
int hlua_fcn_new_listener(lua_State *L, struct listener *lst)
{
lua_newtable(L);
@@ -887,6 +1272,12 @@ int hlua_fcn_new_proxy(lua_State *L, struct proxy *px)
}
lua_settable(L, -3);

+ if (px->table.id) {
+ lua_pushstring(L, "stktable");
+ hlua_fcn_new_stktable(L, &px->table);
+ lua_settable(L, -3);
+ }
+
return 1;
}

@@ -1289,6 +1680,16 @@ int hlua_fcn_reg_core_fcn(lua_State *L)
lua_setmetatable(L, -2);
lua_setglobal(L, CLASS_REGEX); /* Create global object called Regex */

+ /* Create stktable object. */
+ lua_newtable(L);
+ lua_pushstring(L, "__index");
+ lua_newtable(L);
+ hlua_class_function(L, "info", hlua_stktable_info);
+ hlua_class_function(L, "lookup", hlua_stktable_lookup);
+ hlua_class_function(L, "dump", hlua_stktable_dump);
+ lua_settable(L, -3); /* -> META["__index"] = TABLE */
+ class_stktable_ref = hlua_register_metatable(L, CLASS_STKTABLE);
+
/* Create listener object. */
lua_newtable(L);
lua_pushstring(L, "__index");
--
2.18.0
Thierry Fournier
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 21, 2018 09:50AM
> On 20 Aug 2018, at 15:15, Adis Nezirovic <[email protected]> wrote:
>
> On Mon, Aug 20, 2018 at 02:11:13PM +0200, Adis Nezirovic wrote:
>> Hi guys,
>>
>> I've attached a patch to add stick table support to Lua. Operations are
>> mostly similar to "show table" functionality from admin socket, namely:
>>
>> - Show basic table info
>> - Key lookup
>> - Table dump, optionally using data/column filter
>>
>> One side note, the code provides support for multiple filters
>> (4 by default) while CLI doesn't support that, nor it complains about
>> multiple "<data.type> <op> <val>" clauses.


Hi Adis,

This is a great feature. I look this ASAP.


>> Also, if this patch is accepted, maybe we can use provided helper
>> functions in other places in the code.
>>
>
> It's always funny to reply to self, right sending email to public I've
> spotted a bug. New patch attached.
>
> SMT_T_SINT should be treated as ordinary signed integer, and shoud use
> lua_pushinteger() on it?


There are a function which convert haproxy type in Lua types: hlua_arg2lua(),
and SINT is converted with lua_pushinteger(). I guess that stick tables SINT
are the same.


> On many places in the code it is noted as "64" bit integer, but in
> stick_table.c it is defined as 32bit integer:
>
> struct stktable_type stktable_types[SMP_TYPES] = {
> [SMP_T_SINT] = { "integer", 0, 4 },


The lua_pushinteger function takes a lua_Integer which is defined as known C
type (int, long or long long). So, when the function lua_pushinteger() is
called, the compilator perform a conversion between the type passed as argument
and the expected type. So I’m confident with the good behavior.

br,
Thierry


> Best regards,
> Adis
> <0001-MEDIUM-lua-Add-stick-table-support-for-Lua-read-only.patch>
Thierry Fournier
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 21, 2018 11:40AM
Hi Adis,

Thanks for this patch, it is a very useful class.

Some remark about the documentation and the formats:
----------------------------------------------------

js:function:: StickTable.info()
js:function:: StickTable.lookup(key)

Maybe the specification and an example of the returnes values
will be welcome, because I guess that the table keys are hardcoded.
Something like :

{
type = <string> -- list of given string,
length = <int>,
...
}

or

{
expire = <integer> -- Specifiy the unit (ms or ?)
}

js:function:: StickTable.dump([filter])

The exact list of allowed operators will helps the user to use
your class. It seems that string or regexes operators are not
allowed, only the integer operator are taken in account. This
list is "eq", "ne", "le", "lt", "ge", "gt".

Same remarqk for allowed data type. Maybe a link to the HAProxy
documentation will be sufficient for the data type.

I see in the code that the filters can exceed 4 entries. This
limitation must be written in the doc.

I miss also the relation between oprators and between the content
of operators. I mean AND or OR. How I understand your example:

+ local filter = {
+ lt={{"gpc0", 1}, {"gpc1", 2}},
+ gt={{"conn_rate", 3}},
+ eq={{"conn_cur", 4}}
+ }

( gpc0 >= 1 AND gpc1 >= 2 ) OR (conn_rate > 3) OR (conn_cur = 4)
or
( gpc0 >= 1 OR gpc1 >= 2 ) OR (conn_rate > 3) OR (conn_cur = 4)
or
( gpc0 >= 1 AND gpc1 >= 2 ) AND (conn_rate > 3) AND (conn_cur = 4)
or
( gpc0 >= 1 OR gpc1 >= 2 ) AND (conn_rate > 3) AND (conn_cur = 4)

Are you sure that the syntax <operator>=<list of left, right operands>
is a good format ? Maybe something like the following, with the operator
as argument between the two operands. lines are implicitly OR, and columns
are AND:

+ local filter = {
+ {{"gpc0", "lt", 1}, {"gpc1", "lt", 2}},
+ {{"conn_rate", "gt", 3}},
+ {{"conn_cur", "eq", 4}}
+ }

It is read:

( gpc0 >= 1 OR gpc1 >= 2 ) AND (conn_rate > 3) AND (conn_cur = 4)

You can also implement a parser for natural language, in order to understand
directly the previous string ? This algoritm is very simple to implement:

https://en.wikipedia.org/wiki/Shunting-yard_algorithm

Idea of extension for the future: Maybe it will be safe to compile
sticktable filter during the initialisation of the Lua code, to avoid
runtime errors ?


Other point with the doc of this function, you must specify that the
execution of this function can be very long for millions of entry, even
if a filter is specified because the stick table is entirely scanned.


some remarks about the code and the logs:
-----------------------------------------

The line 182 of the patch contains space in place of tab.

Line 274 of your patch, I don't see any HA_SPIN_LOCK(STK_TABLE_LOCK
I don't known very well the thread, so maybe there are useles, maybe no.

Line 311: I see the decrement of the refcount without ATOMIC function
and whitout lock. Once again, I don't known very well the thread but
I send a warning. Maybe locks are useles, maybe no.

Line 286 of your patch. It seems that smp.flags is used uninitialized.
Maybe you should apply this change:

- smp.flags |= SMP_F_CONST;
+ smp.flags = SMP_F_CONST;

l.365, 369: The user doesn't have context about the error. there are the
first entry of the table, the second ? Which operator doesn't exists ?

L.380, 384: Which line is wrong ?

L.431: Your release the lock, so the next element relative to the current
"n", can disappear and the ebmb_next() can return wrong memory.

That's all.
Thierry





> On 20 Aug 2018, at 15:15, Adis Nezirovic <[email protected]> wrote:
>
> On Mon, Aug 20, 2018 at 02:11:13PM +0200, Adis Nezirovic wrote:
>> Hi guys,
>>
>> I've attached a patch to add stick table support to Lua. Operations are
>> mostly similar to "show table" functionality from admin socket, namely:
>>
>> - Show basic table info
>> - Key lookup
>> - Table dump, optionally using data/column filter
>>
>> One side note, the code provides support for multiple filters
>> (4 by default) while CLI doesn't support that, nor it complains about
>> multiple "<data.type> <op> <val>" clauses.
>>
>> Also, if this patch is accepted, maybe we can use provided helper
>> functions in other places in the code.
>>
>
> It's always funny to reply to self, right sending email to public I've
> spotted a bug. New patch attached.
>
> SMT_T_SINT should be treated as ordinary signed integer, and shoud use
> lua_pushinteger() on it?
>
> On many places in the code it is noted as "64" bit integer, but in
> stick_table.c it is defined as 32bit integer:
>
> struct stktable_type stktable_types[SMP_TYPES] = {
> [SMP_T_SINT] = { "integer", 0, 4 },
>
>
> Best regards,
> Adis
> <0001-MEDIUM-lua-Add-stick-table-support-for-Lua-read-only.patch>
Adis Nezirovic
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 21, 2018 11:40PM
On Tue, Aug 21, 2018 at 11:32:34AM +0200, Thierry Fournier wrote:
> Some remark about the documentation and the formats:
> ----------------------------------------------------
>
> js:function:: StickTable.info()
> js:function:: StickTable.lookup(key)
>
> Maybe the specification and an example of the returnes values
> will be welcome, because I guess that the table keys are hardcoded.
> Something like :

That makes sense, I've updated the docs.

> js:function:: StickTable.dump([filter])
>
> The exact list of allowed operators will helps the user to use
> your class. It seems that string or regexes operators are not
> allowed, only the integer operator are taken in account. This
> list is "eq", "ne", "le", "lt", "ge", "gt".
Since only integers are available as data columns, we can only use
numeric operators. Noted in the docs now, with pointer to "show table".

> Same remarqk for allowed data type. Maybe a link to the HAProxy
> documentation will be sufficient for the data type.

> I see in the code that the filters can exceed 4 entries. This
> limitation must be written in the doc.
Noted.

> I miss also the relation between oprators and between the content
> of operators. I mean AND or OR. How I understand your example:
>
> + local filter = {
> + lt={{"gpc0", 1}, {"gpc1", 2}},
> + gt={{"conn_rate", 3}},
> + eq={{"conn_cur", 4}}
> + }
>
> Are you sure that the syntax <operator>=<list of left, right operands>
> is a good format ? Maybe something like the following, with the operator
> as argument between the two operands. lines are implicitly OR, and columns
> are AND:
>
> + local filter = {
> + {{"gpc0", "lt", 1}, {"gpc1", "lt", 2}},
> + {{"conn_rate", "gt", 3}},
> + {{"conn_cur", "eq", 4}}
> + }
Actually, I was playing with some other ideas, and it was useful to be
able to "preselect" filter operators.
However, the CLI doesn't even support more than one, maybe we don't need
to complicate too much. Maybe we can simplify to this:

local filter = {
{"gpc0", "lt", 1},
{"gpc1", "lt", 2},
{"conn_rate", "gt", 3},
{"conn_cur", "eq", 4}
}

The default operator would be AND, and we would not support other
operators (to keep the things simple). e.g. example use case for the
filter would be to filter out on gpc0 > X AND gpc1 > Y

If this sounds good, I can update/simplify the code.

> Idea of extension for the future: Maybe it will be safe to compile
> sticktable filter during the initialisation of the Lua code, to avoid
> runtime errors ?
I'm returning runtime errors since it can be easy to mix up data from
the client side (most probably data would come as json table, then
transformed to Lua table)


> Other point with the doc of this function, you must specify that the
> execution of this function can be very long for millions of entry, even
> if a filter is specified because the stick table is entirely scanned.
Noted.


> some remarks about the code and the logs:
> -----------------------------------------
>
> The line 182 of the patch contains space in place of tab.
Sorry, I generally try to be careful with code style, to many different
vim profiles :-D. Fixef.

> Line 274 of your patch, I don't see any HA_SPIN_LOCK(STK_TABLE_LOCK
> I don't known very well the thread, so maybe there are useles, maybe no.
hlua_stktable_lookup() uses stktable_lookup_key() which does have locks,
so I guess that it should be fine then?

> Line 311: I see the decrement of the refcount without ATOMIC function
> and whitout lock. Once again, I don't known very well the thread but
> I send a warning. Maybe locks are useles, maybe no.
You are totally right, locks are needed when decrementing references.
Fixed.

> Line 286 of your patch. It seems that smp.flags is used uninitialized.
> Maybe you should apply this change:
>
> - smp.flags |= SMP_F_CONST;
> + smp.flags = SMP_F_CONST;
Fixed, and cleaned up a bit (unecessary 'char *key' variable)

> l.365, 369: The user doesn't have context about the error. there are the
> first entry of the table, the second ? Which operator doesn't exists ?
>
> L.380, 384: Which line is wrong ?
Yes, it is somwehat cryptic. I've tried to avoid returning user supplied
data in the error messages. We can revisit this if/when we change the
filter table format.

> L.431: Your release the lock, so the next element relative to the current
> "n", can disappear and the ebmb_next() can return wrong memory.
I was under impression that we only have to acquire lock and increment
ref_cnt (so we can be sure our current node n is not deleted)
ebmb_next() is called only when we're holding lock, first and every
other iteration, i.e.

HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
eb = ebmb_first(&t->keys);
for (n = eb; n; n = ebmb_next(n)) {
...
ts->ref_cnt++;
HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
...

HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
}

Or I didn't get your point?
From 1f7912cbdf9a664a43ab64505d8dd0415984ca4e Mon Sep 17 00:00:00 2001
From: Adis Nezirovic <[email protected]>
Date: Fri, 13 Jul 2018 12:18:33 +0200
Subject: [PATCH] MEDIUM: lua: Add stick table support for Lua (read-only ops).

---
doc/lua-api/index.rst | 74 ++++++++
include/types/hlua.h | 1 +
src/hlua_fcn.c | 399 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 474 insertions(+)

diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 0c79766e..f54a37fd 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -852,6 +852,10 @@ Proxy class
Contain a table with the attached servers. The table is indexed by server
name, and each server entry is an object of type :ref:`server_class`.

+.. js:attribute:: Proxy.stktable
+
+ Contains a stick table object attached to the proxy.
+
.. js:attribute:: Proxy.listeners

Contain a table with the attached listeners. The table is indexed by listener
@@ -2489,6 +2493,76 @@ AppletTCP class
:see: :js:func:`AppletTCP.unset_var`
:see: :js:func:`AppletTCP.set_var`

+StickTable class
+================
+
+.. js:class:: StickTable
+
+ **context**: task, action, sample-fetch, converter
+
+ This class can be used to access the HAProxy stick tables from Lua.
+
+.. js:function:: StickTable.info()
+
+ Returns stick table attributes as a Lua table. See HAProxy documentation for
+ "stick-table" for canonical info, or check out example bellow.
+
+ :returns: Lua table
+
+ Assume our table has IPv4 key and gpc0 and conn_rate "columns":
+
+.. code-block:: lua
+
+ {
+ expire=<int>, # Value in ms
+ size=<int>, # Maximum table size
+ used=<int>, # Actual number of entries in table
+ data={ # Data columns, with types as key, and periods as values
+ (-1 if type is not rate counter)
+ conn_rate=<int>,
+ gpc0=-1
+ },
+ length=<int>, # max string length for string table keys, key length
+ # otherwise
+ nopurge=<boolean>,
+ type="ip" # can be "ip", "ipv6", "integer", "string", "binary"
+ }
+
+.. js:function:: StickTable.lookup(key)
+
+ Returns stick table entry for given <key>
+
+ :param string key: Stick table key (IP addresses and strings are supported)
+ :returns: Lua table
+
+.. js:function:: StickTable.dump([filter])
+
+ Returns all entries in stick table. An optional filter can be used
+ to extract entries with specific data values. Filter is a table with valid
+ comparison operators as keys followed by data type name and value pairs.
+ Check out the HAProxy docs for "show table" for more details. For the
+ reference, the supported operators are:
+ "eq", "ne", "le", "lt", "ge", "gt"
+
+ For large tables, execution of this function can take a long time (for
+ HAProxy standards). That's also true when filter is used, so take care and
+ measure the impact.
+
+ :param table filter: Stick table filter
+ :returns: Stick table entries (table)
+
+ See below for example filter, which contains 4 entries (or comparisons).
+ (Maximum number of filter entries is 4, hardcoded in the source code)
+
+.. code-block:: lua
+
+ local filter = {
+ lt={{"gpc0", 1}, {"gpc1", 2}},
+ gt={{"conn_rate", 3}},
+ eq={{"conn_cur", 4}}
+ }
+
+
External Lua libraries
======================

diff --git a/include/types/hlua.h b/include/types/hlua.h
index 5a8173f3..2e453351 100644
--- a/include/types/hlua.h
+++ b/include/types/hlua.h
@@ -25,6 +25,7 @@
#define CLASS_SERVER "Server"
#define CLASS_LISTENER "Listener"
#define CLASS_REGEX "Regex"
+#define CLASS_STKTABLE "StickTable"

struct stream;

diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index cebce224..6c5c8995 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -30,6 +30,7 @@
#include <proto/proxy.h>
#include <proto/server.h>
#include <proto/stats.h>
+#include <proto/stick_table.h>

/* Contains the class reference of the concat object. */
static int class_concat_ref;
@@ -37,7 +38,9 @@ static int class_proxy_ref;
static int class_server_ref;
static int class_listener_ref;
static int class_regex_ref;
+static int class_stktable_ref;

+#define MAX_STK_FILTER_LEN 4
#define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))

static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -49,6 +52,38 @@ int hlua_checkboolean(lua_State *L, int index)
return lua_toboolean(L, index);
}

+/* Helper to push unsigned integers to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned(lua_State *L, unsigned int val)
+{
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ lua_pushinteger(L, val);
+#else
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
+/* Helper to push unsigned long long to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned_ll(lua_State *L, unsigned long long val) {
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ /* 64 bits case, U64 is supported until LLONG_MAX */
+ if (val > LLONG_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, val);
+#else
+ /* 32 bits case, U64 is supported until INT_MAX */
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
/* This function gets a struct field and convert it in Lua
* variable. The variable is pushed at the top of the stak.
*/
@@ -446,6 +481,354 @@ static int hlua_concat_init(lua_State *L)
return 1;
}

+int hlua_fcn_new_stktable(lua_State *L, struct stktable *tbl)
+{
+ lua_newtable(L);
+
+ /* Pop a class stktbl metatable and affect it to the userdata. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_stktable_ref);
+ lua_setmetatable(L, -2);
+
+ lua_pushlightuserdata(L, tbl);
+ lua_rawseti(L, -2, 0);
+ return 1;
+}
+
+static struct stktable *hlua_check_stktable(lua_State *L, int ud)
+{
+ return hlua_checkudata(L, ud, class_stktable_ref);
+}
+
+/* Extract stick table attributes into Lua table */
+int hlua_stktable_info(lua_State *L)
+{
+ struct stktable *tbl;
+ int dt;
+
+ tbl = hlua_check_stktable(L, 1);
+
+ if (!tbl->id) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ lua_pushstring(L, "type");
+ lua_pushstring(L, stktable_types[tbl->type].kw);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "length");
+ lua_pushinteger(L, tbl->key_size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "size");
+ hlua_fcn_pushunsigned(L, tbl->size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "used");
+ hlua_fcn_pushunsigned(L, tbl->current);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "nopurge");
+ lua_pushboolean(L, tbl->nopurge > 0);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tbl->expire);
+ lua_settable(L, -3);
+
+ /* Save data types periods (if applicable) in 'data' table */
+ lua_pushstring(L, "data");
+ lua_newtable(L);
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+ if (tbl->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
+ lua_pushinteger(L, tbl->data_arg[dt].u);
+ else
+ lua_pushinteger(L, -1);
+
+ lua_settable(L, -3);
+ }
+
+ lua_settable(L, -3);
+
+ return 1;
+}
+
+/* Helper to get extract stick table entry into Lua table */
+static void hlua_stktable_entry(lua_State *L, struct stktable *t, struct stksess *ts)
+{
+ int dt;
+ void *ptr;
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+
+ if (t->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ ptr = stktable_data_ptr(t, ts, dt);
+ switch (stktable_data_types[dt].std_type) {
+ case STD_T_SINT:
+ lua_pushinteger(L, stktable_data_cast(ptr, std_t_sint));
+ break;
+ case STD_T_UINT:
+ hlua_fcn_pushunsigned(L, stktable_data_cast(ptr, std_t_uint));
+ break;
+ case STD_T_ULL:
+ hlua_fcn_pushunsigned_ll(L, stktable_data_cast(ptr, std_t_ull));
+ break;
+ case STD_T_FRQP:
+ lua_pushinteger(L, read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[dt].u));
+ break;
+ }
+
+ lua_settable(L, -3);
+ }
+}
+
+/* Looks in table <t> for a sticky session matching key <key>
+ * Returns table with session data or nil
+ *
+ * The returned table always contains 'use' and 'expire' (integer) fields.
+ * For frequency/rate counters, each data entry is returned as table with
+ * 'value' and 'period' fields.
+ */
+int hlua_stktable_lookup(lua_State *L)
+{
+ struct stktable *t;
+ struct sample smp;
+ struct stktable_key *skey;
+ struct stksess *ts;
+
+ t = hlua_check_stktable(L, 1);
+ smp.data.type = SMP_T_STR;
+ smp.flags = SMP_F_CONST;
+ smp.data.u.str.area = (char *)luaL_checkstring(L, 2);
+
+ skey = smp_to_stkey(&smp, t);
+ if (!skey) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ ts = stktable_lookup_key(t, skey);
+ if (!ts) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+ lua_pushstring(L, "use");
+ lua_pushinteger(L, ts->ref_cnt - 1);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tick_remain(now_ms, ts->expire));
+ lua_settable(L, -3);
+
+ hlua_stktable_entry(L, t, ts);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
+struct stk_filter {
+ long long val;
+ int type;
+ int op;
+};
+
+
+static int hlua_error(lua_State *L, const char *msg) {
+ lua_pushnil(L);
+ lua_pushstring(L, msg);
+ return 2;
+}
+
+/* Dump the contents of stick table <t>*/
+int hlua_stktable_dump(lua_State *L)
+{
+ struct stktable *t;
+ struct ebmb_node *eb;
+ struct ebmb_node *n;
+ struct stksess *ts;
+ int type;
+ int op;
+ int dt;
+ long long val;
+ struct stk_filter filter[MAX_STK_FILTER_LEN];
+ int filter_count = 0;
+ int i;
+ int skip_entry;
+ void *ptr;
+
+ t = hlua_check_stktable(L, 1);
+ type = lua_type(L, 2);
+
+ switch (type) {
+ case LUA_TNONE:
+ case LUA_TNIL:
+ break;
+ case LUA_TTABLE:
+ lua_pushnil(L);
+ while (lua_next(L, 2) != 0) {
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ return hlua_error(L, "String key (operator name) expected");
+ }
+
+ op = get_std_op(lua_tostring(L, -2));
+ if (op < 0) {
+ return hlua_error(L, "Unknown stick table/acl operator");
+ }
+
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Datatype/value table expected");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ int entry_idx = 0;
+ if (filter_count >= sizeof(filter)/sizeof(struct stk_filter)) {
+ break;
+ }
+ filter[filter_count].op = op;
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ return hlua_error(L, "Filter table must be multidimensional");
+ }
+
+ if (lua_rawlen(L, -1) != 2) {
+ return hlua_error(L, "Filter table entry length must be 2");
+ }
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ if (entry_idx == 0) {
+ if (lua_type(L, -1) != LUA_TSTRING) {
+ return hlua_error(L, "Stick table data type must be string");
+ }
+ dt = stktable_get_data_type((char *)lua_tostring(L, -1));
+
+ if (t->data_ofs[dt] == 0) {
+ return hlua_error(L, "Stick table filter column not present in table");
+
+ }
+
+ filter[filter_count].type = dt;
+ } else {
+ val = lua_tointeger(L, -1);
+ filter[filter_count].val = val;
+ filter_count++;
+ }
+ entry_idx++;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+
+ break;
+ default:
+ return hlua_error(L, "filter table expected");
+ }
+
+ lua_newtable(L);
+
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ eb = ebmb_first(&t->keys);
+ for (n = eb; n; n = ebmb_next(n)) {
+ ts = ebmb_entry(n, struct stksess, key);
+ if (!ts) {
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+ return 1;
+ }
+ ts->ref_cnt++;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ /* multi condition/value filter */
+ skip_entry = 0;
+ for (i = 0; i < filter_count; i++) {
+ if (t->data_ofs[filter.type] == 0)
+ continue;
+
+ ptr = stktable_data_ptr(t, ts, filter.type);
+
+ switch (stktable_data_types[filter.type].std_type) {
+ case STD_T_SINT:
+ val = stktable_data_cast(ptr, std_t_sint);
+ break;
+ case STD_T_UINT:
+ val = stktable_data_cast(ptr, std_t_uint);
+ break;
+ case STD_T_ULL:
+ val = stktable_data_cast(ptr, std_t_ull);
+ break;
+ case STD_T_FRQP:
+ val = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[filter.type].u);
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ op = filter.op;
+
+ if ((val < filter.val && (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+ (val == filter.val && (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+ (val > filter.val && (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+ skip_entry = 1;
+ break;
+ }
+ }
+
+ if (skip_entry) {
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ continue;
+ }
+
+ if (t->type == SMP_T_IPV4) {
+ char addr[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_IPV6) {
+ char addr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_SINT) {
+ lua_pushinteger(L, *ts->key.key);
+ } else if (t->type == SMP_T_STR) {
+ lua_pushstring(L, (const char *)ts->key.key);
+ } else {
+ return hlua_error(L, "Unsupported stick table key type");
+ }
+
+ lua_newtable(L);
+ hlua_stktable_entry(L, t, ts);
+ lua_settable(L, -3);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ }
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
int hlua_fcn_new_listener(lua_State *L, struct listener *lst)
{
lua_newtable(L);
@@ -887,6 +1270,12 @@ int hlua_fcn_new_proxy(lua_State *L, struct proxy *px)
}
lua_settable(L, -3);

+ if (px->table.id) {
+ lua_pushstring(L, "stktable");
+ hlua_fcn_new_stktable(L, &px->table);
+ lua_settable(L, -3);
+ }
+
return 1;
}

@@ -1289,6 +1678,16 @@ int hlua_fcn_reg_core_fcn(lua_State *L)
lua_setmetatable(L, -2);
lua_setglobal(L, CLASS_REGEX); /* Create global object called Regex */

+ /* Create stktable object. */
+ lua_newtable(L);
+ lua_pushstring(L, "__index");
+ lua_newtable(L);
+ hlua_class_function(L, "info", hlua_stktable_info);
+ hlua_class_function(L, "lookup", hlua_stktable_lookup);
+ hlua_class_function(L, "dump", hlua_stktable_dump);
+ lua_settable(L, -3); /* -> META["__index"] = TABLE */
+ class_stktable_ref = hlua_register_metatable(L, CLASS_STKTABLE);
+
/* Create listener object. */
lua_newtable(L);
lua_pushstring(L, "__index");
--
2.18.0
Thierry Fournier
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 23, 2018 11:00AM
Hi

[...]

>> I miss also the relation between oprators and between the content
>> of operators. I mean AND or OR. How I understand your example:
>>
>> + local filter = {
>> + lt={{"gpc0", 1}, {"gpc1", 2}},
>> + gt={{"conn_rate", 3}},
>> + eq={{"conn_cur", 4}}
>> + }
>>
>> Are you sure that the syntax <operator>=<list of left, right operands>
>> is a good format ? Maybe something like the following, with the operator
>> as argument between the two operands. lines are implicitly OR, and columns
>> are AND:
>>
>> + local filter = {
>> + {{"gpc0", "lt", 1}, {"gpc1", "lt", 2}},
>> + {{"conn_rate", "gt", 3}},
>> + {{"conn_cur", "eq", 4}}
>> + }
> Actually, I was playing with some other ideas, and it was useful to be
> able to "preselect" filter operators.
> However, the CLI doesn't even support more than one, maybe we don't need
> to complicate too much. Maybe we can simplify to this:
>
> local filter = {
> {"gpc0", "lt", 1},
> {"gpc1", "lt", 2},
> {"conn_rate", "gt", 3},
> {"conn_cur", "eq", 4}
> }
>
> The default operator would be AND, and we would not support other
> operators (to keep the things simple). e.g. example use case for the
> filter would be to filter out on gpc0 > X AND gpc1 > Y
>
> If this sounds good, I can update/simplify the code.


Ok, it sounds good. I think this kind of syntax is easily understandable
and it allow a good way for filtering values.


>> Idea of extension for the future: Maybe it will be safe to compile
>> sticktable filter during the initialisation of the Lua code, to avoid
>> runtime errors ?
> I'm returning runtime errors since it can be easy to mix up data from
> the client side (most probably data would come as json table, then
> transformed to Lua table)


ok

[...]


>> Line 274 of your patch, I don't see any HA_SPIN_LOCK(STK_TABLE_LOCK
>> I don't known very well the thread, so maybe there are useles, maybe no.
> hlua_stktable_lookup() uses stktable_lookup_key() which does have locks,
> so I guess that it should be fine then?


sure !


[...]


>> l.365, 369: The user doesn't have context about the error. there are the
>> first entry of the table, the second ? Which operator doesn't exists ?
>>
>> L.380, 384: Which line is wrong ?
> Yes, it is somwehat cryptic. I've tried to avoid returning user supplied
> data in the error messages. We can revisit this if/when we change the
> filter table format.


ok


>> L.431: Your release the lock, so the next element relative to the current
>> "n", can disappear and the ebmb_next() can return wrong memory.
> I was under impression that we only have to acquire lock and increment
> ref_cnt (so we can be sure our current node n is not deleted)
> ebmb_next() is called only when we're holding lock, first and every
> other iteration, i.e.
>
> HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
> eb = ebmb_first(&t->keys);
> for (n = eb; n; n = ebmb_next(n)) {
> ...
> ts->ref_cnt++;
> HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
> ...
>
> HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
> }
>
> Or I didn't get your point?


ok, you probably right.


Thierry
Willy Tarreau
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 23, 2018 03:50PM
Hi Thierry,

On Thu, Aug 23, 2018 at 10:53:15AM +0200, Thierry Fournier wrote:
(...)
> Ok, it sounds good. I think this kind of syntax is easily understandable
> and it allow a good way for filtering values.

Does this mean I should merge Adis' patch or do you want to verify
other things ? Just let me know.

Thanks,
Willy
Adis Nezirovic
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 23, 2018 04:40PM
On Thu, Aug 23, 2018 at 03:43:59PM +0200, Willy Tarreau wrote:
> Does this mean I should merge Adis' patch or do you want to verify
> other things ? Just let me know.

Willy,

I'll submit new patch later today with simplified filter definitions and
then we can ask Thierry for final ack for the patch.

Best regards,
Adis
Adis Nezirovic
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
August 24, 2018 11:50PM
Thierry,

Something for Monday :-)

Latest version of the patch in attachment:

- Filter table format is flattened/simplified
- I've tried to address filter table format error messages
(what is the error, and which filter entry is wrong)
- Fixed one bug: stktable_get_data_type() return value can be < 0
(i.e. filter table contains unknown data column)

I've run a few unit tests for my Lua code using stick tables, hitting
all methods and handling regular return values and filter table errors.
So far so good, it looks good on my side.

Best regards,
Adis
From 6b702ff6f12f919ba4d2f42a7962fa2345272382 Mon Sep 17 00:00:00 2001
From: Adis Nezirovic <[email protected]>
Date: Fri, 13 Jul 2018 12:18:33 +0200
Subject: [PATCH] MEDIUM: lua: Add stick table support for Lua.

This ads support for accessing stick tables from Lua. The supported
operations are reading general table info, lookup by string/IP key, and
dumping the table.

Similar to "show table", a data filter is available during dump, and as
an improvement over "show table" it's possible to use up to 4 filter
expressions instead of just one (with implicit AND clause binding the
expressions). Dumping with/without filters can take a long time for
large tables, and should be used sparingly.
---
doc/lua-api/index.rst | 72 ++++++++
include/types/hlua.h | 1 +
src/hlua_fcn.c | 399 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 472 insertions(+)

diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 0c79766e..9a2e039a 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -852,6 +852,10 @@ Proxy class
Contain a table with the attached servers. The table is indexed by server
name, and each server entry is an object of type :ref:`server_class`.

+.. js:attribute:: Proxy.stktable
+
+ Contains a stick table object attached to the proxy.
+
.. js:attribute:: Proxy.listeners

Contain a table with the attached listeners. The table is indexed by listener
@@ -2489,6 +2493,74 @@ AppletTCP class
:see: :js:func:`AppletTCP.unset_var`
:see: :js:func:`AppletTCP.set_var`

+StickTable class
+================
+
+.. js:class:: StickTable
+
+ **context**: task, action, sample-fetch
+
+ This class can be used to access the HAProxy stick tables from Lua.
+
+.. js:function:: StickTable.info()
+
+ Returns stick table attributes as a Lua table. See HAProxy documentation for
+ "stick-table" for canonical info, or check out example bellow.
+
+ :returns: Lua table
+
+ Assume our table has IPv4 key and gpc0 and conn_rate "columns":
+
+.. code-block:: lua
+
+ {
+ expire=<int>, # Value in ms
+ size=<int>, # Maximum table size
+ used=<int>, # Actual number of entries in table
+ data={ # Data columns, with types as key, and periods as values
+ (-1 if type is not rate counter)
+ conn_rate=<int>,
+ gpc0=-1
+ },
+ length=<int>, # max string length for string table keys, key length
+ # otherwise
+ nopurge=<boolean>, # purge oldest entries when table is full
+ type="ip" # can be "ip", "ipv6", "integer", "string", "binary"
+ }
+
+.. js:function:: StickTable.lookup(key)
+
+ Returns stick table entry for given <key>
+
+ :param string key: Stick table key (IP addresses and strings are supported)
+ :returns: Lua table
+
+.. js:function:: StickTable.dump([filter])
+
+ Returns all entries in stick table. An optional filter can be used
+ to extract entries with specific data values. Filter is a table with valid
+ comparison operators as keys followed by data type name and value pairs.
+ Check out the HAProxy docs for "show table" for more details. For the
+ reference, the supported operators are:
+ "eq", "ne", "le", "lt", "ge", "gt"
+
+ For large tables, execution of this function can take a long time (for
+ HAProxy standards). That's also true when filter is used, so take care and
+ measure the impact.
+
+ :param table filter: Stick table filter
+ :returns: Stick table entries (table)
+
+ See below for example filter, which contains 4 entries (or comparisons).
+ (Maximum number of filter entries is 4, defined in the source code)
+
+.. code-block:: lua
+
+ local filter = {
+ {"gpc0", "gt", 30}, {"gpc1", "gt", 20}}, {"conn_rate", "le", 10}
+ }
+
+
External Lua libraries
======================

diff --git a/include/types/hlua.h b/include/types/hlua.h
index 5a8173f3..2e453351 100644
--- a/include/types/hlua.h
+++ b/include/types/hlua.h
@@ -25,6 +25,7 @@
#define CLASS_SERVER "Server"
#define CLASS_LISTENER "Listener"
#define CLASS_REGEX "Regex"
+#define CLASS_STKTABLE "StickTable"

struct stream;

diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index cebce224..6f6d8380 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -30,6 +30,7 @@
#include <proto/proxy.h>
#include <proto/server.h>
#include <proto/stats.h>
+#include <proto/stick_table.h>

/* Contains the class reference of the concat object. */
static int class_concat_ref;
@@ -37,7 +38,9 @@ static int class_proxy_ref;
static int class_server_ref;
static int class_listener_ref;
static int class_regex_ref;
+static int class_stktable_ref;

+#define MAX_STK_FILTER_LEN 4
#define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))

static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -49,6 +52,38 @@ int hlua_checkboolean(lua_State *L, int index)
return lua_toboolean(L, index);
}

+/* Helper to push unsigned integers to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned(lua_State *L, unsigned int val)
+{
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ lua_pushinteger(L, val);
+#else
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
+/* Helper to push unsigned long long to Lua stack, respecting Lua limitations */
+static int hlua_fcn_pushunsigned_ll(lua_State *L, unsigned long long val) {
+#if (LUA_MAXINTEGER == LLONG_MAX || ((LUA_MAXINTEGER == LONG_MAX) && (__WORDSIZE == 64)))
+ /* 64 bits case, U64 is supported until LLONG_MAX */
+ if (val > LLONG_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, val);
+#else
+ /* 32 bits case, U64 is supported until INT_MAX */
+ if (val > INT_MAX)
+ lua_pushnumber(L, (lua_Number)val);
+ else
+ lua_pushinteger(L, (int)val);
+#endif
+ return 1;
+}
+
/* This function gets a struct field and convert it in Lua
* variable. The variable is pushed at the top of the stak.
*/
@@ -446,6 +481,354 @@ static int hlua_concat_init(lua_State *L)
return 1;
}

+int hlua_fcn_new_stktable(lua_State *L, struct stktable *tbl)
+{
+ lua_newtable(L);
+
+ /* Pop a class stktbl metatable and affect it to the userdata. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_stktable_ref);
+ lua_setmetatable(L, -2);
+
+ lua_pushlightuserdata(L, tbl);
+ lua_rawseti(L, -2, 0);
+ return 1;
+}
+
+static struct stktable *hlua_check_stktable(lua_State *L, int ud)
+{
+ return hlua_checkudata(L, ud, class_stktable_ref);
+}
+
+/* Extract stick table attributes into Lua table */
+int hlua_stktable_info(lua_State *L)
+{
+ struct stktable *tbl;
+ int dt;
+
+ tbl = hlua_check_stktable(L, 1);
+
+ if (!tbl->id) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ lua_pushstring(L, "type");
+ lua_pushstring(L, stktable_types[tbl->type].kw);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "length");
+ lua_pushinteger(L, tbl->key_size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "size");
+ hlua_fcn_pushunsigned(L, tbl->size);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "used");
+ hlua_fcn_pushunsigned(L, tbl->current);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "nopurge");
+ lua_pushboolean(L, tbl->nopurge > 0);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tbl->expire);
+ lua_settable(L, -3);
+
+ /* Save data types periods (if applicable) in 'data' table */
+ lua_pushstring(L, "data");
+ lua_newtable(L);
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+ if (tbl->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
+ lua_pushinteger(L, tbl->data_arg[dt].u);
+ else
+ lua_pushinteger(L, -1);
+
+ lua_settable(L, -3);
+ }
+
+ lua_settable(L, -3);
+
+ return 1;
+}
+
+/* Helper to get extract stick table entry into Lua table */
+static void hlua_stktable_entry(lua_State *L, struct stktable *t, struct stksess *ts)
+{
+ int dt;
+ void *ptr;
+
+ for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+
+ if (t->data_ofs[dt] == 0)
+ continue;
+
+ lua_pushstring(L, stktable_data_types[dt].name);
+
+ ptr = stktable_data_ptr(t, ts, dt);
+ switch (stktable_data_types[dt].std_type) {
+ case STD_T_SINT:
+ lua_pushinteger(L, stktable_data_cast(ptr, std_t_sint));
+ break;
+ case STD_T_UINT:
+ hlua_fcn_pushunsigned(L, stktable_data_cast(ptr, std_t_uint));
+ break;
+ case STD_T_ULL:
+ hlua_fcn_pushunsigned_ll(L, stktable_data_cast(ptr, std_t_ull));
+ break;
+ case STD_T_FRQP:
+ lua_pushinteger(L, read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[dt].u));
+ break;
+ }
+
+ lua_settable(L, -3);
+ }
+}
+
+/* Looks in table <t> for a sticky session matching key <key>
+ * Returns table with session data or nil
+ *
+ * The returned table always contains 'use' and 'expire' (integer) fields.
+ * For frequency/rate counters, each data entry is returned as table with
+ * 'value' and 'period' fields.
+ */
+int hlua_stktable_lookup(lua_State *L)
+{
+ struct stktable *t;
+ struct sample smp;
+ struct stktable_key *skey;
+ struct stksess *ts;
+
+ t = hlua_check_stktable(L, 1);
+ smp.data.type = SMP_T_STR;
+ smp.flags = SMP_F_CONST;
+ smp.data.u.str.area = (char *)luaL_checkstring(L, 2);
+
+ skey = smp_to_stkey(&smp, t);
+ if (!skey) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ ts = stktable_lookup_key(t, skey);
+ if (!ts) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+ lua_pushstring(L, "use");
+ lua_pushinteger(L, ts->ref_cnt - 1);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "expire");
+ lua_pushinteger(L, tick_remain(now_ms, ts->expire));
+ lua_settable(L, -3);
+
+ hlua_stktable_entry(L, t, ts);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
+struct stk_filter {
+ long long val;
+ int type;
+ int op;
+};
+
+
+/* Helper for returning errors to callers using Lua convention (nil, err) */
+static int hlua_error(lua_State *L, const char *fmt, ...) {
+ char buf[256];
+ int len;
+ va_list args;
+ va_start(args, fmt);
+ len = vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ if (len < 0) {
+ ha_alert("hlua_error(): Could not write error message.\n");
+ lua_pushnil(L);
+ return 1;
+ } else if (len >= sizeof(buf))
+ ha_alert("hlua_error(): Error message was truncated.\n");
+
+ lua_pushnil(L);
+ lua_pushstring(L, buf);
+
+ return 2;
+}
+
+/* Dump the contents of stick table <t>*/
+int hlua_stktable_dump(lua_State *L)
+{
+ struct stktable *t;
+ struct ebmb_node *eb;
+ struct ebmb_node *n;
+ struct stksess *ts;
+ int type;
+ int op;
+ int dt;
+ long long val;
+ struct stk_filter filter[MAX_STK_FILTER_LEN];
+ int filter_count = 0;
+ int i;
+ int skip_entry;
+ void *ptr;
+
+ t = hlua_check_stktable(L, 1);
+ type = lua_type(L, 2);
+
+ switch (type) {
+ case LUA_TNONE:
+ case LUA_TNIL:
+ break;
+ case LUA_TTABLE:
+ lua_pushnil(L);
+ while (lua_next(L, 2) != 0) {
+ int entry_idx = 0;
+
+ if (filter_count >= MAX_STK_FILTER_LEN)
+ return hlua_error(L, "Filter table too large (len > %d)", MAX_STK_FILTER_LEN);
+
+ if (lua_type(L, -1) != LUA_TTABLE || lua_rawlen(L, -1) != 3)
+ return hlua_error(L, "Filter table entry must be a triplet: {\"data_col\", \"op\", val} (entry #%d)", filter_count + 1);
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ switch (entry_idx) {
+ case 0:
+ if (lua_type(L, -1) != LUA_TSTRING)
+ return hlua_error(L, "Filter table data column must be string (entry #%d)", filter_count + 1);
+
+ dt = stktable_get_data_type((char *)lua_tostring(L, -1));
+ if (dt < 0 || t->data_ofs[dt] == 0)
+ return hlua_error(L, "Filter table data column not present in stick table (entry #%d)", filter_count + 1);
+ filter[filter_count].type = dt;
+ break;
+ case 1:
+ if (lua_type(L, -1) != LUA_TSTRING)
+ return hlua_error(L, "Filter table operator must be string (entry #%d)", filter_count + 1);
+
+ op = get_std_op(lua_tostring(L, -1));
+ if (op < 0)
+ return hlua_error(L, "Unknown operator in filter table (entry #%d)", filter_count + 1);
+ filter[filter_count].op = op;
+ break;
+ case 2:
+ val = lua_tointeger(L, -1);
+ filter[filter_count].val = val;
+ filter_count++;
+ break;
+ default:
+ break;
+ }
+ entry_idx++;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+ }
+ break;
+ default:
+ return hlua_error(L, "filter table expected");
+ }
+
+ lua_newtable(L);
+
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ eb = ebmb_first(&t->keys);
+ for (n = eb; n; n = ebmb_next(n)) {
+ ts = ebmb_entry(n, struct stksess, key);
+ if (!ts) {
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+ return 1;
+ }
+ ts->ref_cnt++;
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ /* multi condition/value filter */
+ skip_entry = 0;
+ for (i = 0; i < filter_count; i++) {
+ if (t->data_ofs[filter.type] == 0)
+ continue;
+
+ ptr = stktable_data_ptr(t, ts, filter.type);
+
+ switch (stktable_data_types[filter.type].std_type) {
+ case STD_T_SINT:
+ val = stktable_data_cast(ptr, std_t_sint);
+ break;
+ case STD_T_UINT:
+ val = stktable_data_cast(ptr, std_t_uint);
+ break;
+ case STD_T_ULL:
+ val = stktable_data_cast(ptr, std_t_ull);
+ break;
+ case STD_T_FRQP:
+ val = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+ t->data_arg[filter.type].u);
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ op = filter.op;
+
+ if ((val < filter.val && (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+ (val == filter.val && (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+ (val > filter.val && (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+ skip_entry = 1;
+ break;
+ }
+ }
+
+ if (skip_entry) {
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ continue;
+ }
+
+ if (t->type == SMP_T_IPV4) {
+ char addr[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_IPV6) {
+ char addr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, (const void *)&ts->key.key, addr, sizeof(addr));
+ lua_pushstring(L, addr);
+ } else if (t->type == SMP_T_SINT) {
+ lua_pushinteger(L, *ts->key.key);
+ } else if (t->type == SMP_T_STR) {
+ lua_pushstring(L, (const char *)ts->key.key);
+ } else {
+ return hlua_error(L, "Unsupported stick table key type");
+ }
+
+ lua_newtable(L);
+ hlua_stktable_entry(L, t, ts);
+ lua_settable(L, -3);
+ HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
+ ts->ref_cnt--;
+ }
+ HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
+
+ return 1;
+}
+
int hlua_fcn_new_listener(lua_State *L, struct listener *lst)
{
lua_newtable(L);
@@ -887,6 +1270,12 @@ int hlua_fcn_new_proxy(lua_State *L, struct proxy *px)
}
lua_settable(L, -3);

+ if (px->table.id) {
+ lua_pushstring(L, "stktable");
+ hlua_fcn_new_stktable(L, &px->table);
+ lua_settable(L, -3);
+ }
+
return 1;
}

@@ -1289,6 +1678,16 @@ int hlua_fcn_reg_core_fcn(lua_State *L)
lua_setmetatable(L, -2);
lua_setglobal(L, CLASS_REGEX); /* Create global object called Regex */

+ /* Create stktable object. */
+ lua_newtable(L);
+ lua_pushstring(L, "__index");
+ lua_newtable(L);
+ hlua_class_function(L, "info", hlua_stktable_info);
+ hlua_class_function(L, "lookup", hlua_stktable_lookup);
+ hlua_class_function(L, "dump", hlua_stktable_dump);
+ lua_settable(L, -3); /* -> META["__index"] = TABLE */
+ class_stktable_ref = hlua_register_metatable(L, CLASS_STKTABLE);
+
/* Create listener object. */
lua_newtable(L);
lua_pushstring(L, "__index");
--
2.18.0
Adis Nezirovic
Re: [PATCH] MEDIUM: lua: Add stick table support for Lua
September 03, 2018 12:20PM
On Fri, Aug 24, 2018 at 11:40:51PM +0200, Adis Nezirovic wrote:
> Thierry,
>
> Something for Monday :-)
>
> Latest version of the patch in attachment:
>
> - Filter table format is flattened/simplified
> - I've tried to address filter table format error messages
> (what is the error, and which filter entry is wrong)
> - Fixed one bug: stktable_get_data_type() return value can be < 0
> (i.e. filter table contains unknown data column)

Hi Thierry,

Have you had the time to review my patches?

Best regards,
Adis
Sorry, only registered users may post in this forum.

Click here to login