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:
parent
d48d6d284e
commit
8878f8eb3d
@ -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
|
||||
======================
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#define CLASS_SERVER "Server"
|
||||
#define CLASS_LISTENER "Listener"
|
||||
#define CLASS_REGEX "Regex"
|
||||
#define CLASS_STKTABLE "StickTable"
|
||||
|
||||
struct stream;
|
||||
|
||||
|
399
src/hlua_fcn.c
399
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[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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user