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 <aneziro...@haproxy.com>
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[i].type] == 0)
+                               continue;
+
+                       ptr = stktable_data_ptr(t, ts, filter[i].type);
+
+                       switch (stktable_data_types[filter[i].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[i].type].u);
+                               break;
+                       default:
+                               continue;
+                               break;
+                       }
+
+                       op = filter[i].op;
+
+                       if ((val < filter[i].val && (op == STD_OP_EQ || op == 
STD_OP_GT || op == STD_OP_GE)) ||
+                           (val == filter[i].val && (op == STD_OP_NE || op == 
STD_OP_GT || op == STD_OP_LT)) ||
+                           (val > filter[i].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

Reply via email to