From abefe2c6a2c890c1ee1549c26060a1487b546f60 Mon Sep 17 00:00:00 2001 From: Andrey Saranchin <Andrey22102001@gmail.com> Date: Fri, 7 Jul 2023 15:48:15 +0300 Subject: [PATCH] session: move all session triggers to the trigger registry The commit populates submodule session with an event for each public trigger - they will be used for user-defined triggers. Old triggers are not removed because they are used for internal purposes - audit from EE uses them, for instance. Also, the commit introduces a test for all triggers that use old API. It checks only session triggers now - all the other triggers will be included there after they will be moved to the trigger registry. In order to have an opportunity to move user-defined triggers to trigger registry, the commit introduces helper luaT_event_reset_trigger that supports two different ways to set triggers - key-value API and positional API. The first one is a new API that allows to set or delete trigger only by name. The second API is implemented for the sake of backward compatibility - it mimics behavior of function lbox_trigger_reset, which was used to modify triggers, but sets the triggers to event from trigger registry instead of separate trigger list. Also, positional API allows to pass a name as the third argument - in this case the function will set or delete trigger by passed name - the first argument is a new trigger (or nil, if the function is called to delete a trigger), the second argument will be ignored in this case. The new helper supports not only functions - any callable object can be used as a trigger. Part of #6484 Part of #8657 NO_CHANGELOG=later NO_DOC=later --- src/box/authentication.c | 6 +- src/box/error.cc | 44 ++++- src/box/error.h | 22 +++ src/box/iproto.cc | 10 +- src/box/lua/session.c | 44 +---- src/box/lua/trigger.c | 147 +++++++++++++++ src/box/lua/trigger.h | 43 +++++ src/box/schema.h | 17 -- src/box/session.c | 73 ++++++- src/box/session.h | 4 + test/box-luatest/triggers_old_api_test.lua | 209 +++++++++++++++++++++ test/unit/luaT_tuple_new.c | 2 + test/unit/tuple_format.c | 3 + 13 files changed, 551 insertions(+), 73 deletions(-) create mode 100644 test/box-luatest/triggers_old_api_test.lua diff --git a/src/box/authentication.c b/src/box/authentication.c index d9b2698552..dc4c6d45df 100644 --- a/src/box/authentication.c +++ b/src/box/authentication.c @@ -17,6 +17,7 @@ #include "diag.h" #include "errcode.h" #include "error.h" +#include "event.h" #include "fiber.h" #include "iostream.h" #include "msgpuck.h" @@ -121,9 +122,8 @@ authenticate(const char *user_name, uint32_t user_name_len, if (access_check_session(user) != 0) return -1; ok: - /* check and run auth triggers on success */ - if (! rlist_empty(&session_on_auth) && - session_run_on_auth_triggers(&auth_res) != 0) + /* run auth triggers on success */ + if (session_run_on_auth_triggers(&auth_res) != 0) return -1; credentials_reset(¤t_session()->credentials, user); return 0; diff --git a/src/box/error.cc b/src/box/error.cc index e0b916454b..0dc3d26244 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -31,7 +31,9 @@ #include "error.h" #include <stdio.h> +#include "event.h" #include "fiber.h" +#include "func_adapter.h" #include "rmean.h" #include "trigger.h" #include "vclock/vclock.h" @@ -266,6 +268,40 @@ BuildXlogGapError(const char *file, unsigned line, } struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); +struct event *on_access_denied_event; + +/** + * Runs on access denied triggers. Does not run triggers from the event if it + * is not initialized. + */ +static int +run_on_access_denied_triggers(const char *access_type, const char *object_type, + const char *object_name) +{ + struct on_access_denied_ctx trigger_ctx = + {access_type, object_type, object_name}; + if (trigger_run(&on_access_denied, &trigger_ctx) != 0) + return -1; + + if (on_access_denied_event == NULL) + return 0; + const char *name = NULL; + struct func_adapter *trigger = NULL; + struct func_adapter_ctx ctx; + struct event_trigger_iterator it; + int rc = 0; + event_trigger_iterator_create(&it, on_access_denied_event); + while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) { + func_adapter_begin(trigger, &ctx); + func_adapter_push_str0(trigger, &ctx, access_type); + func_adapter_push_str0(trigger, &ctx, object_type); + func_adapter_push_str0(trigger, &ctx, object_name); + rc = func_adapter_call(trigger, &ctx); + func_adapter_end(trigger, &ctx); + } + event_trigger_iterator_destroy(&it); + return rc; +} const struct type_info type_AccessDeniedError = make_type("AccessDeniedError", &type_ClientError); @@ -275,19 +311,19 @@ AccessDeniedError::AccessDeniedError(const char *file, unsigned int line, const char *object_type, const char *object_name, const char *user_name, - bool run_trigers) + bool run_triggers) :ClientError(&type_AccessDeniedError, file, line, ER_ACCESS_DENIED) { error_format_msg(this, tnt_errcode_desc(code), access_type, object_type, object_name, user_name); - struct on_access_denied_ctx ctx = {access_type, object_type, object_name}; /* * Don't run the triggers when create after marshaling * through network. */ - if (run_trigers) - trigger_run(&on_access_denied, (void *) &ctx); + if (run_triggers) + run_on_access_denied_triggers(access_type, object_type, + object_name); error_set_str(this, "object_type", object_type); error_set_str(this, "object_name", object_name); error_set_str(this, "access_type", access_type); diff --git a/src/box/error.h b/src/box/error.h index 3cc08edf89..3033e3846f 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -184,6 +184,28 @@ extern const struct type_info type_XlogGapError; extern const struct type_info type_AccessDeniedError; extern const struct type_info type_CustomError; +/** + * Internal triggers fired after access denied error is created. + */ +extern struct rlist on_access_denied; + +/** + * User-definded triggers fired after access denied error is created. + */ +extern struct event *on_access_denied_event; + +/** + * Context passed to on_access_denied trigger. + */ +struct on_access_denied_ctx { + /** Type of declined access */ + const char *access_type; + /** Type of object the required access was denied to */ + const char *object_type; + /** Name of object the required access was denied to */ + const char *object_name; +}; + #if defined(__cplusplus) } /* extern "C" */ #include "exception.h" diff --git a/src/box/iproto.cc b/src/box/iproto.cc index c724c161e5..fddd5b6d0a 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -40,6 +40,7 @@ #include <base64.h> #include "version.h" +#include "event.h" #include "fiber.h" #include "fiber_cond.h" #include "cbus.h" @@ -1777,10 +1778,8 @@ tx_process_disconnect(struct cmsg *m) * closed, its push() method is replaced with a stub. */ con->tx.is_push_pending = false; - if (! rlist_empty(&session_on_disconnect)) { - tx_fiber_init(con->session, 0); - session_run_on_disconnect_triggers(con->session); - } + tx_fiber_init(con->session, 0); + session_run_on_disconnect_triggers(con->session); } } @@ -2726,8 +2725,7 @@ tx_process_connect(struct cmsg *m) greeting_encode(greeting, tarantool_version_id(), &uuid, con->salt, IPROTO_SALT_SIZE); xobuf_dup(out, greeting, IPROTO_GREETING_SIZE); - if (!rlist_empty(&session_on_connect) && - session_run_on_connect_triggers(con->session) != 0) + if (session_run_on_connect_triggers(con->session) != 0) goto error; iproto_wpos_create(&msg->wpos, out); return; diff --git a/src/box/lua/session.c b/src/box/lua/session.c index 811cc958c6..16f9e938f6 100644 --- a/src/box/lua/session.c +++ b/src/box/lua/session.c @@ -36,8 +36,10 @@ #include <lauxlib.h> #include <lualib.h> #include <sio.h> +#include "box/lua/trigger.h" #include "box/box.h" +#include "box/error.h" #include "box/session.h" #include "box/user.h" #include "box/schema.h" @@ -274,31 +276,10 @@ lbox_session_peer(struct lua_State *L) return 1; } -/** - * run on_connect|on_disconnect trigger - */ -static int -lbox_push_on_connect_event(struct lua_State *L, void *event) -{ - (void) L; - (void) event; - return 0; -} - -static int -lbox_push_on_auth_event(struct lua_State *L, void *event) -{ - struct on_auth_trigger_ctx *ctx = (struct on_auth_trigger_ctx *)event; - lua_pushlstring(L, ctx->user_name, ctx->user_name_len); - lua_pushboolean(L, ctx->is_authenticated); - return 2; -} - static int lbox_session_on_connect(struct lua_State *L) { - return lbox_trigger_reset(L, 2, &session_on_connect, - lbox_push_on_connect_event, NULL); + return luaT_event_reset_trigger(L, 1, session_on_connect_event); } static int @@ -313,8 +294,7 @@ lbox_session_run_on_connect(struct lua_State *L) static int lbox_session_on_disconnect(struct lua_State *L) { - return lbox_trigger_reset(L, 2, &session_on_disconnect, - lbox_push_on_connect_event, NULL); + return luaT_event_reset_trigger(L, 1, session_on_disconnect_event); } static int @@ -329,8 +309,7 @@ lbox_session_run_on_disconnect(struct lua_State *L) static int lbox_session_on_auth(struct lua_State *L) { - return lbox_trigger_reset(L, 2, &session_on_auth, - lbox_push_on_auth_event, NULL); + return luaT_event_reset_trigger(L, 1, session_on_auth_event); } static int @@ -351,16 +330,6 @@ lbox_session_run_on_auth(struct lua_State *L) return 0; } -static int -lbox_push_on_access_denied_event(struct lua_State *L, void *event) -{ - struct on_access_denied_ctx *ctx = (struct on_access_denied_ctx *) event; - lua_pushstring(L, ctx->access_type); - lua_pushstring(L, ctx->object_type); - lua_pushstring(L, ctx->object_name); - return 3; -} - /** * Push a message using a protocol, depending on a session type. * @param L Lua state. First argument on the stack is data to @@ -391,8 +360,7 @@ lbox_session_push(struct lua_State *L) static int lbox_session_on_access_denied(struct lua_State *L) { - return lbox_trigger_reset(L, 2, &on_access_denied, - lbox_push_on_access_denied_event, NULL); + return luaT_event_reset_trigger(L, 1, on_access_denied_event); } static int diff --git a/src/box/lua/trigger.c b/src/box/lua/trigger.c index 7a21c3b46e..d62758ba75 100644 --- a/src/box/lua/trigger.c +++ b/src/box/lua/trigger.c @@ -9,6 +9,7 @@ #include <diag.h> #include <fiber.h> #include "lua/utils.h" +#include "tt_static.h" /** * Sets a trigger with passed name to the passed event. @@ -253,3 +254,149 @@ box_lua_trigger_init(struct lua_State *L) luaL_register_type(L, event_trigger_iterator_typename, trigger_iterator_methods); } + +/** Old API compatibility. */ + +/** + * Checks positional arguments for luaT_event_reset_trigger. + * Throws an error if the format is not suitable. + */ +static void +luaT_event_reset_trigger_check_positional_input(struct lua_State *L, int bottom) +{ + /* Push optional arguments. */ + lua_settop(L, bottom + 2); + + /* + * (nil, callable) is OK, deletes the trigger + * (callable, nil), is OK, adds the trigger + * (callable, callable) is OK, replaces the trigger + * no arguments is OK, lists all trigger + * anything else is error. + */ + bool ok = true; + /* Name must be a string if it is passed. */ + ok = ok && (lua_isnil(L, bottom + 2) || luaL_isnull(L, bottom + 2) || + lua_type(L, bottom + 2) == LUA_TSTRING); + ok = ok && (lua_isnil(L, bottom + 1) || luaL_isnull(L, bottom + 1) || + luaL_iscallable(L, bottom + 1)); + ok = ok && (lua_isnil(L, bottom) || luaL_isnull(L, bottom) || + luaL_iscallable(L, bottom)); + if (!ok) + luaL_error(L, "trigger reset: incorrect arguments"); +} + +/** + * Sets or deletes trigger by name depending on passed arguments. Value at + * name_idx must be a string, value at func_idx must be a callable object, + * nil or box.NULL. Otherwise, an error will be thrown. + */ +static int +luaT_event_reset_trigger_by_name(struct lua_State *L, struct event *event, + int name_idx, int func_idx) +{ + if (lua_type(L, name_idx) != LUA_TSTRING) + luaL_error(L, "name must be a string"); + const char *trigger_name = lua_tostring(L, name_idx); + if (luaL_iscallable(L, func_idx)) { + struct func_adapter *func = + func_adapter_lua_create(L, func_idx); + event_reset_trigger(event, trigger_name, func); + lua_pushvalue(L, func_idx); + return 1; + } else if (lua_isnil(L, func_idx) || luaL_isnull(L, func_idx)) { + event_reset_trigger(event, trigger_name, NULL); + return 0; + } + return luaL_error(L, "func must be a callable object or nil"); +} + +int +luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event) +{ + assert(L != NULL); + assert(bottom >= 1); + assert(event != NULL); + /* Use key-value API if the first argument is a non-callable table. */ + if (lua_gettop(L) == bottom && lua_istable(L, -1) && + !luaL_iscallable(L, -1)) { + lua_getfield(L, bottom, "name"); + lua_getfield(L, bottom, "func"); + return luaT_event_reset_trigger_by_name(L, event, -2, -1); + } + /* Old way with name support. */ + luaT_event_reset_trigger_check_positional_input(L, bottom); + const int top = bottom + 2; + if (!lua_isnil(L, top) && !luaL_isnull(L, top)) + return luaT_event_reset_trigger_by_name(L, event, top, bottom); + /* + * Name is not passed - old API support. + * 1. If triggers are not passed, return table of triggers. + * 2. If new_trigger is passed and old_trigger is not - set + * new_trigger using its address as name. + * 3. If old_trigger is passed and new_trigger is not - delete + * trigger by address of old_trigger as a name. + * 4. If both triggers are provided - replace old trigger with + * new one if they have the same address, delete old trigger and + * insert new one at the beginning of the trigger list otherwise. + */ + if (!luaL_iscallable(L, bottom) && !luaL_iscallable(L, bottom + 1)) { + lua_createtable(L, 0, 0); + const char *name = NULL; + struct func_adapter *trigger = NULL; + struct event_trigger_iterator it; + event_trigger_iterator_create(&it, event); + int idx = 0; + while (event_trigger_iterator_next(&it, &trigger, &name)) { + idx++; + func_adapter_lua_get_func(trigger, L); + lua_rawseti(L, -2, idx); + } + event_trigger_iterator_destroy(&it); + return 1; + } + int ret_count = 0; + const void *old_handler = NULL; + const void *new_handler = NULL; + const char *old_name = NULL; + const char *new_name = NULL; + struct func_adapter *new_trg = NULL; + if (luaL_iscallable(L, bottom + 1)) { + old_handler = lua_topointer(L, bottom + 1); + old_name = tt_sprintf("%p", old_handler); + if (event_find_trigger(event, old_name) == NULL) + luaL_error(L, "trigger reset: Trigger is not found"); + } + if (luaL_iscallable(L, bottom)) { + new_handler = lua_topointer(L, bottom); + new_name = tt_sprintf("%p", new_handler); + new_trg = func_adapter_lua_create(L, bottom); + lua_pushvalue(L, bottom); + ret_count = 1; + } + if (new_handler != NULL && old_handler != NULL) { + if (old_handler == new_handler) { + event_reset_trigger(event, new_name, new_trg); + } else { + /* + * Need to reference the event because it can be + * deleted after deleting all its triggers. + */ + event_ref(event); + event_reset_trigger(event, old_name, NULL); + /* + * Delete a trigger with new name to surely place the + * new trigger at the beginning of the trigger list. + */ + event_reset_trigger(event, new_name, NULL); + event_reset_trigger(event, new_name, new_trg); + event_unref(event); + } + } else if (old_handler != NULL) { + event_reset_trigger(event, old_name, NULL); + } else { + assert(new_handler != NULL); + event_reset_trigger(event, new_name, new_trg); + } + return ret_count; +} diff --git a/src/box/lua/trigger.h b/src/box/lua/trigger.h index 6b1f1b443a..e7eaf5bc41 100644 --- a/src/box/lua/trigger.h +++ b/src/box/lua/trigger.h @@ -9,12 +9,55 @@ extern "C" { #endif /* defined(__cplusplus) */ +struct event; + /** * Initializes module trigger. */ void box_lua_trigger_init(struct lua_State *L); +/** + * Creates a Lua trigger, replaces an existing one, or deletes a trigger. + * + * The function accepts a Lua stack. Values starting from index bottom are + * considered as the function arguments. + * + * The function supports two API versions. + * + * The first version - key-value arguments. The function is called with one Lua + * argument which is not callable table. In this case, the table must contain + * key "name" with value of type string - the name of a trigger. The second + * key, "func", is optional. If it is not present, a trigger with passed name + * is deleted, or no-op, if there is no such trigger. If key "func" is present, + * it must contain a callable object as a value - it will be used as a handler + * for a new trigger. The new trigger will be appended to the beginning of the + * trigger list or replace an existing one with the same name. The function + * returns new trigger (or nothing, if it was deleted). + * + * The second version - positional arguments. The function is called with up to + * three Lua arguments. The first one is a new trigger handler - it must be a + * callable object or nil. The second one is an old trigger handler - it must + * be a callable object or nil as well. The third argument is a trigger name of + * type string (it can be nil too). + * If the name is passed, the logic is equivalent to key-value API - + * the third argument is a trigger name, the first one is a trigger handler (or + * nil if the function is called to delete a trigger by name), the second + * argument is ignored, but the type check is still performed. If the name is + * not passed, the function mimics the behavior of function lbox_trigger_reset: + * 1. If triggers (first and second arguments) are not passed, returns table of + * triggers. + * 2. If new trigger is passed and old one is not - sets new trigger using + * its address as name. The new trigger is returned. + * 3. If old trigger is passed and new trigger is not - deletes a trigger by + * address of an old trigger as a name. Returns nothing. + * 4. If both triggers are provided - replace old trigger with new one if they + * have the same address, delete old trigger and insert new one at the beginning + * of the trigger list otherwise. The new trigger is returned. + */ +int +luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/schema.h b/src/box/schema.h index 65f82cdaa0..a9930a56a1 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -146,28 +146,11 @@ sequence_cache_delete(uint32_t id); */ extern struct rlist on_alter_sequence; -/** - * Triggers fired after access denied error is created. - */ -extern struct rlist on_access_denied; - /** * Triggers fired after committing a change in _func space. */ extern struct rlist on_alter_func; -/** - * Context passed to on_access_denied trigger. - */ -struct on_access_denied_ctx { - /** Type of declined access */ - const char *access_type; - /** Type of object the required access was denied to */ - const char *object_type; - /** Name of object the required access was denied to */ - const char *object_name; -}; - /** Global grants to classes of objects. */ struct entity_access { struct access space[BOX_USER_MAX]; diff --git a/src/box/session.c b/src/box/session.c index fb1be86805..5d3ac8f3a7 100644 --- a/src/box/session.c +++ b/src/box/session.c @@ -30,6 +30,7 @@ */ #include "session.h" #include "fiber.h" +#include "func_adapter.h" #include "fiber_cond.h" #include "sio.h" #include "memory.h" @@ -43,6 +44,8 @@ #include "on_shutdown.h" #include "sql.h" #include "tweaks.h" +#include "event.h" +#include "schema.h" const char *session_type_strs[] = { "background", @@ -77,6 +80,9 @@ static struct fiber_cond shutdown_list_empty_cond; RLIST_HEAD(session_on_connect); RLIST_HEAD(session_on_disconnect); RLIST_HEAD(session_on_auth); +struct event *session_on_connect_event; +struct event *session_on_disconnect_event; +struct event *session_on_auth_event; static inline uint64_t sid_max(void) @@ -321,8 +327,13 @@ session_remove_stmt_id(struct session *session, uint32_t stmt_id) */ struct credentials admin_credentials; +/** + * Runs on_connect or on_disconnect triggers. Argument triggers is for internal + * triggers and argument event is for user-defined ones. + */ static int -session_run_triggers(struct session *session, struct rlist *triggers) +session_run_triggers(struct session *session, struct rlist *triggers, + struct event *event) { struct fiber *fiber = fiber(); assert(session == current_session()); @@ -331,7 +342,21 @@ session_run_triggers(struct session *session, struct rlist *triggers) fiber_set_user(fiber, &admin_credentials); int rc = trigger_run(triggers, NULL); - + if (rc != 0) + goto out; + + const char *name = NULL; + struct func_adapter *trigger = NULL; + struct func_adapter_ctx ctx; + struct event_trigger_iterator it; + event_trigger_iterator_create(&it, event); + while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) { + func_adapter_begin(trigger, &ctx); + rc = func_adapter_call(trigger, &ctx); + func_adapter_end(trigger, &ctx); + } + event_trigger_iterator_destroy(&it); +out: /* Restore original credentials */ fiber_set_user(fiber, &session->credentials); @@ -341,20 +366,40 @@ session_run_triggers(struct session *session, struct rlist *triggers) void session_run_on_disconnect_triggers(struct session *session) { - if (session_run_triggers(session, &session_on_disconnect) != 0) + if (session_run_triggers(session, &session_on_disconnect, + session_on_disconnect_event) != 0) diag_log(); } int session_run_on_connect_triggers(struct session *session) { - return session_run_triggers(session, &session_on_connect); + return session_run_triggers(session, &session_on_connect, + session_on_connect_event); } int session_run_on_auth_triggers(const struct on_auth_trigger_ctx *result) { - return trigger_run(&session_on_auth, (void *)result); + if (trigger_run(&session_on_auth, (void *)result) != 0) + return -1; + + const char *name = NULL; + struct func_adapter *trigger = NULL; + struct func_adapter_ctx ctx; + struct event_trigger_iterator it; + int rc = 0; + event_trigger_iterator_create(&it, session_on_auth_event); + while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) { + func_adapter_begin(trigger, &ctx); + func_adapter_push_str(trigger, &ctx, result->user_name, + result->user_name_len); + func_adapter_push_bool(trigger, &ctx, result->is_authenticated); + rc = func_adapter_call(trigger, &ctx); + func_adapter_end(trigger, &ctx); + } + event_trigger_iterator_destroy(&it); + return rc; } void @@ -421,6 +466,16 @@ session_init(void) { for (int type = 0; type < session_type_MAX; type++) session_vtab_registry[type] = generic_session_vtab; + session_on_connect_event = event_get("box.session.on_connect", true); + event_ref(session_on_connect_event); + session_on_disconnect_event = + event_get("box.session.on_disconnect", true); + event_ref(session_on_disconnect_event); + session_on_auth_event = event_get("box.session.on_auth", true); + event_ref(session_on_auth_event); + on_access_denied_event = + event_get("box.session.on_access_denied", true); + event_ref(on_access_denied_event); session_registry = mh_i64ptr_new(); mempool_create(&session_pool, &cord()->slabc, sizeof(struct session)); credentials_create(&admin_credentials, admin_user); @@ -433,6 +488,14 @@ session_init(void) void session_free(void) { + event_unref(session_on_connect_event); + session_on_connect_event = NULL; + event_unref(session_on_disconnect_event); + session_on_disconnect_event = NULL; + event_unref(session_on_auth_event); + session_on_auth_event = NULL; + event_unref(on_access_denied_event); + on_access_denied_event = NULL; if (session_registry) mh_i64ptr_delete(session_registry); credentials_destroy(&admin_credentials); diff --git a/src/box/session.h b/src/box/session.h index a0575b850a..2fb7832e7f 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -188,8 +188,11 @@ session_find(uint64_t sid); /** Global on-connect triggers. */ extern struct rlist session_on_connect; +extern struct event *session_on_connect_event; +/** Global on-auth triggers. */ extern struct rlist session_on_auth; +extern struct event *session_on_auth_event; /** * Get the current session from @a fiber @@ -292,6 +295,7 @@ effective_user(void) /** Global on-disconnect triggers. */ extern struct rlist session_on_disconnect; +extern struct event *session_on_disconnect_event; void session_storage_cleanup(int sid); diff --git a/test/box-luatest/triggers_old_api_test.lua b/test/box-luatest/triggers_old_api_test.lua new file mode 100644 index 0000000000..ed276f9869 --- /dev/null +++ b/test/box-luatest/triggers_old_api_test.lua @@ -0,0 +1,209 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function() + g.server = server:new({alias = 'master'}) + g.server:start() + g.server:exec(function() + -- Every element of this table is an array of 2 elements. The first + -- one is a function that sets triggers, and the second one is the name + -- of the associated event in the trigger registry. The second element + -- is optional. + rawset(_G, 'old_api_triggers', { + {box.session.on_connect, 'box.session.on_connect'}, + {box.session.on_disconnect, 'box.session.on_disconnect'}, + {box.session.on_auth, 'box.session.on_auth'}, + }) + + rawset(_G, 'ffi_monotonic_id', 0) + + -- Helper that generates callable table, userdata and cdata + local function generate_callable_objects(handler) + local ffi = require('ffi') + local a = {} + local mt = {__call = handler} + setmetatable(a, mt) + + local b = newproxy(true) + mt = getmetatable(b) + mt.__call = handler + + local ffi_id = rawget(_G, 'ffi_monotonic_id') + rawset(_G, 'ffi_monotonic_id', ffi_id + 1) + local struct_name = string.format('foo%d', ffi_id) + ffi.cdef(string.format('struct %s { int x; }', struct_name)) + mt = {__call = handler} + ffi.metatype(string.format('struct %s', struct_name), mt) + local c = ffi.new(string.format('struct %s', struct_name), {x = 42}) + return a, b, c + end + rawset(_G, 'generate_callable_objects', generate_callable_objects) + end) +end) + +g.after_all(function() + g.server:stop() +end) + +g.test_old_api = function() + g.server:exec(function() + local old_api_triggers = rawget(_G, 'old_api_triggers') + local generate_callable_objects = + rawget(_G, 'generate_callable_objects') + local function check_trigger(old_api_trigger) + local function h1() end + local h2, h3, h4 = generate_callable_objects(h1) + + t.assert_equals(old_api_trigger(), {}) + + -- Invalid arguments + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, nil, + nil, {1, 2}) + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, nil, + nil, h1) + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, 'abc') + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, nil, + 'abc') + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, 'abc', + 'abc') + t.assert_error_msg_content_equals( + 'trigger reset: incorrect arguments', old_api_trigger, 'abc', + nil, 'name') + t.assert_error_msg_content_equals( + 'trigger reset: Trigger is not found', old_api_trigger, nil, h1) + + -- Set and delete + t.assert_equals(old_api_trigger(h1), h1) + t.assert_equals(old_api_trigger(h2), h2) + t.assert_equals(old_api_trigger(h3), h3) + t.assert_equals(old_api_trigger(h4), h4) + t.assert_equals(old_api_trigger(), {h4, h3, h2, h1}) + t.assert_equals(old_api_trigger(nil, h4), nil) + t.assert_equals(old_api_trigger(), {h3, h2, h1}) + t.assert_equals(old_api_trigger(h2), h2) + t.assert_equals(old_api_trigger(), {h3, h2, h1}) + t.assert_equals(old_api_trigger(h3, box.NULL, box.NULL), h3) + t.assert_equals(old_api_trigger(), {h3, h2, h1}) + + -- Replace + -- Expected behavior: when both triggers has the same name, the + -- order is preserved. Otherwise, old trigger is deleted and the new + -- one is inserted at the beginning of the list. Also, when the + -- names are different, trigger with the new name should be deleted + -- to prevent name duplication. + t.assert_equals(old_api_trigger(h4, h2), h4) + t.assert_equals(old_api_trigger(), {h4, h3, h1}) + t.assert_equals(old_api_trigger(h1, h3), h1) + t.assert_equals(old_api_trigger(), {h1, h4}) + t.assert_equals(old_api_trigger(h3), h3) + t.assert_equals(old_api_trigger(), {h3, h1, h4}) + t.assert_equals(old_api_trigger(h1, h1), h1) + t.assert_equals(old_api_trigger(), {h3, h1, h4}) + t.assert_equals(old_api_trigger(nil, h3), nil) + t.assert_equals(old_api_trigger(), {h1, h4}) + t.assert_equals(old_api_trigger(box.NULL, h4), nil) + t.assert_equals(old_api_trigger(), {h1}) + t.assert_equals(old_api_trigger(h2, h1), h2) + t.assert_equals(old_api_trigger(), {h2}) + t.assert_equals(old_api_trigger(nil, h2, box.NULL), nil) + t.assert_equals(old_api_trigger(), {}) + + -- Name argument + t.assert_equals(old_api_trigger(h1, nil, 't1'), h1) + t.assert_equals(old_api_trigger(), {h1}) + -- Check if the second argument will be ignored + t.assert_equals(old_api_trigger(h2, h1, 't2'), h2) + t.assert_equals(old_api_trigger(), {h2, h1}) + t.assert_equals(old_api_trigger(h3, nil, 't1'), h3) + t.assert_equals(old_api_trigger(), {h2, h3}) + t.assert_equals(old_api_trigger(nil, h1, 't1'), nil) + t.assert_equals(old_api_trigger(), {h2}) + t.assert_equals(old_api_trigger(nil, nil, 't2'), nil) + t.assert_equals(old_api_trigger(), {}) + end + for _, trg_descr in pairs(old_api_triggers) do + local trg = trg_descr[1] + check_trigger(trg) + end + end) +end + +g.test_key_value_args = function() + g.server:exec(function() + local old_api_triggers = rawget(_G, 'old_api_triggers') + local generate_callable_objects = + rawget(_G, 'generate_callable_objects') + local function check_trigger(old_api_trigger) + local function h1() end + local h2, h3, h4 = generate_callable_objects(h1) + + t.assert_equals(old_api_trigger(), {}) + + -- Invalid arguments + t.assert_error_msg_content_equals( + "trigger reset: incorrect arguments", old_api_trigger, + {func = h1, name = 'trg'}, h1) + t.assert_error_msg_content_equals( + "trigger reset: incorrect arguments", old_api_trigger, + {func = h1, name = 'trg'}, 'abc') + t.assert_error_msg_content_equals( + "func must be a callable object or nil", old_api_trigger, + {func = 'str', name = 'trg'}) + t.assert_error_msg_content_equals( + "name must be a string", old_api_trigger, {func = h1}) + t.assert_error_msg_content_equals( + "name must be a string", old_api_trigger, + {func = h1, name = box.NULL}) + + -- Set and delete + t.assert_equals(old_api_trigger{func = h1, name = 'h1'}, h1) + t.assert_equals(old_api_trigger{func = h2, name = 'h2'}, h2) + t.assert_equals(old_api_trigger{func = h3, name = 'h3'}, h3) + t.assert_equals(old_api_trigger{func = h4, name = 'h4'}, h4) + t.assert_equals(old_api_trigger(), {h4, h3, h2, h1}) + t.assert_equals(old_api_trigger{name = 'h4'}, nil) + t.assert_equals(old_api_trigger(), {h3, h2, h1}) + t.assert_equals(old_api_trigger{name = 'h2', func = box.NULL}, nil) + t.assert_equals(old_api_trigger(), {h3, h1}) + t.assert_equals(old_api_trigger{name = 'h3', func = box.NULL}, nil) + t.assert_equals(old_api_trigger(), {h1}) + t.assert_equals(old_api_trigger{name = 'h1'}, nil) + t.assert_equals(old_api_trigger(), {}) + end + for _, trg_descr in pairs(old_api_triggers) do + local trg = trg_descr[1] + check_trigger(trg) + end + end) +end + +g.test_associated_event = function() + g.server:exec(function() + local trigger = require('trigger') + local old_api_triggers = rawget(_G, 'old_api_triggers') + local function check_trigger(old_api_trigger, event_name) + local trg = {"I am the trigger of event " .. event_name} + local mt = {__call = function() end} + setmetatable(trg, mt) + old_api_trigger(trg) + local event_triggers = trigger.info(event_name)[event_name] + t.assert_equals(#event_triggers, 1) + t.assert_equals(event_triggers[1][2], trg) + old_api_trigger(nil, trg) + end + for _, trg_descr in pairs(old_api_triggers) do + local trg = trg_descr[1] + local event_name = trg_descr[2] + if event_name ~= nil then + check_trigger(trg, event_name) + end + end + end) +end diff --git a/test/unit/luaT_tuple_new.c b/test/unit/luaT_tuple_new.c index 42643464c5..4e8d2a5569 100644 --- a/test/unit/luaT_tuple_new.c +++ b/test/unit/luaT_tuple_new.c @@ -4,6 +4,7 @@ #include <lualib.h> /* luaL_openlibs() */ #include "memory.h" /* memory_init() */ #include "fiber.h" /* fiber_init() */ +#include "event.h" /* event_init() */ #include "small/ibuf.h" /* struct ibuf */ #include "box/box.h" /* box_init() */ #include "box/tuple.h" /* box_tuple_format_default() */ @@ -187,6 +188,7 @@ main() struct lua_State *L = luaT_newteststate(); + event_init(); box_init(); tarantool_lua_error_init(L); luaopen_msgpack(L); diff --git a/test/unit/tuple_format.c b/test/unit/tuple_format.c index b3d2cd04b6..98f9899fd5 100644 --- a/test/unit/tuple_format.c +++ b/test/unit/tuple_format.c @@ -7,6 +7,7 @@ #include "coll/coll.h" +#include "core/event.h" #include "core/fiber.h" #include "core/memory.h" @@ -230,12 +231,14 @@ main(void) fiber_init(fiber_c_invoke); coll_init(); tuple_init(test_field_name_hash); + event_init(); box_init(); sql_init(); int rc = test_tuple_format(); box_free(); + event_free(); tuple_free(); coll_free(); fiber_free(); -- GitLab