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.
This commit is contained in:
Adis Nezirovic 2018-07-13 12:18:33 +02:00 committed by Willy Tarreau
parent d48d6d284e
commit 8878f8eb3d
3 changed files with 472 additions and 0 deletions

View File

@ -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
======================

View File

@ -26,6 +26,7 @@
#define CLASS_SERVER "Server"
#define CLASS_LISTENER "Listener"
#define CLASS_REGEX "Regex"
#define CLASS_STKTABLE "StickTable"
struct stream;

View File

@ -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[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");