commit 98c91930fd736b4ae9a48fd88e4ceee4db258d20 from: Ilya Verbin via: Serge Petrenko <35663196+sergepetrenko@users.noreply.github.com> date: Tue Feb 13 09:40:35 2024 UTC iproto: override request handlers using event triggers (part 1) This patch allows to override IPROTO request handlers by setting triggers on the corresponding events before the initial `box.cfg{}' call. For triggers that are set after the first `box.cfg{}', see next commit. There are 2 types of iproto-overriding events: 1. set by request type id, e.g.: - box.iproto.override[1] - box.iproto.override[-1] 2. set by request type name (the name must be in the lowercase), e.g.: - box.iproto.override.select - box.iproto.override.unknown Override-by-id allows to set a handler for a particular request type, that is not known by the given version of Tarantool. This is not possible with override-by-name, where a type name must be known by Tarantool. Also there are a special type name "unknown" and a type id box.iproto.type.UNKNOWN (== -1) that allow to set a single handler for all unknown request types. Multiple triggers can be associated with a single event. The triggers are called in reverse order of their installation, however triggers set by id are called before triggers set by name. If a trigger returns `false`, the next trigger in the list is called, or a system handler if there are no more triggers. If a trigger returns `true`, no more triggers or system handlers are called. Part of #8138 NO_DOC=see next commit NO_CHANGELOG=see next commit Co-authored-by: Andrey Saranchin commit - 2515ab166e77192a440be01082aa3a28ce75dc42 commit + 98c91930fd736b4ae9a48fd88e4ceee4db258d20 blob - e4fc51c2d8235be128ccb8fc3715e7c747ee2986 blob + 5de28463f41054a99af2791b61ae57ddb876ca08 --- src/box/box.cc +++ src/box/box.cc @@ -5927,6 +5927,7 @@ box_storage_free(void) void box_init(void) { + iproto_constants_init(); box_on_recovery_state_event = event_get("box.ctl.on_recovery_state", true); event_ref(box_on_recovery_state_event); @@ -6002,6 +6003,7 @@ box_free(void) box_on_recovery_state_event = NULL; txn_event_trigger_free(); tuple_free(); + iproto_constants_free(); /* schema_module_free(); */ /* session_free(); */ /* user_cache_free(); */ blob - 0a70681c8c361ca673992987f1762d89faae72eb blob + 8514e85a013e5a29e608bdad6dc2ac7de3d40c8a --- src/box/iproto.cc +++ src/box/iproto.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,7 @@ #include "version.h" #include "event.h" +#include "func_adapter.h" #include "fiber.h" #include "fiber_cond.h" #include "cbus.h" @@ -296,10 +298,22 @@ static int iproto_msg_max = IPROTO_MSG_MAX_MIN; /** * Request handlers meta information. The IPROTO request of each type can be * overridden by the following types of handlers (listed in priority order): - * 1. C handler, set by `iproto_override()'. + * 1. Lua handlers, set in the event registry by request type id; + * 2. Lua handlers, set in the event registry by request type name; + * 3. C handler, set by `iproto_override()'. */ struct iproto_req_handlers { /** + * Triggers from the event registry, set by request type id. + * NULL if no such triggers. + */ + struct event *event_by_id; + /** + * Triggers from the event registry, set by request type name. + * NULL if no such triggers. + */ + struct event *event_by_name; + /** * C request handler. */ struct { @@ -2829,6 +2843,10 @@ iproto_req_handlers_new(void) static void iproto_req_handlers_delete(struct iproto_req_handlers *handlers) { + if (handlers->event_by_id != NULL) + event_unref(handlers->event_by_id); + if (handlers->event_by_name != NULL) + event_unref(handlers->event_by_name); if (handlers->c.destroy != NULL) handlers->c.destroy(handlers->c.ctx); TRASH(handlers); @@ -2879,6 +2897,34 @@ mh_req_handlers_del(uint32_t req_type) } /** + * Replaces an event in `handlers' by the new `event'. If `is_by_id', the + * handler is set by request type id, otherwise it is set by request type name. + */ +static void +iproto_req_handlers_set_event(struct iproto_req_handlers *handlers, + struct event *event, bool is_by_id) +{ + assert(handlers != NULL); + assert(event != NULL); + + if (is_by_id) { + if (handlers->event_by_id == NULL) { + event_ref(event); + handlers->event_by_id = event; + } else { + assert(handlers->event_by_id == event); + } + } else { + if (handlers->event_by_name == NULL) { + event_ref(event); + handlers->event_by_name = event; + } else { + assert(handlers->event_by_name == event); + } + } +} + +/** * Returns `true' if there is at least one handler in `handlers'. */ static bool @@ -2887,7 +2933,79 @@ iproto_req_handler_is_set(struct iproto_req_handlers * if (handlers == NULL) return false; - return handlers->c.cb != NULL; + return handlers->event_by_id != NULL || + handlers->event_by_name != NULL || + handlers->c.cb != NULL; +} + +/** + * Returns `enum iproto_type' if `name' is a valid IPROTO type name or equals + * "unknown". Otherwise, iproto_type_MAX is returned. The name is expected to + * be in lowercase. + */ +static enum iproto_type +get_iproto_type_by_name(const char *name) +{ + for (uint32_t i = 0; i < iproto_type_MAX; i++) { + const char *type_name = iproto_type_name_lower(i); + if (type_name != NULL && strcmp(type_name, name) == 0) + return (enum iproto_type)i; + } + if (strcmp(name, "unknown") == 0) + return IPROTO_UNKNOWN; + return iproto_type_MAX; +} + +/** + * Runs triggers registered for the `event'. + * The given header and body the IPROTO packet are passed as trigger args. + * Returns IPROTO_HANDLER_OK if some trigger successfully handled the request, + * IPROTO_HANDLER_FALLBACK if no triggers handled the request, or + * IPROTO_HANDLER_ERROR on failure. + */ +static enum iproto_handler_status +tx_run_override_triggers(struct event *event, const char *header, + const char *header_end, const char *body, + const char *body_end) +{ + enum iproto_handler_status rc = IPROTO_HANDLER_FALLBACK; + 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 (event_trigger_iterator_next(&it, &trigger, &name)) { + struct mp_ctx mp_ctx_header, mp_ctx_body; + mp_ctx_create_default(&mp_ctx_header, iproto_key_translation); + mp_ctx_create_default(&mp_ctx_body, iproto_key_translation); + + func_adapter_begin(trigger, &ctx); + func_adapter_push_msgpack_with_ctx(trigger, &ctx, header, + header_end, &mp_ctx_header); + func_adapter_push_msgpack_with_ctx(trigger, &ctx, body, + body_end, &mp_ctx_body); + if (func_adapter_call(trigger, &ctx) == 0) { + if (func_adapter_is_bool(trigger, &ctx)) { + bool ok = false; + func_adapter_pop_bool(trigger, &ctx, &ok); + if (ok) + rc = IPROTO_HANDLER_OK; + } else { + diag_set(ClientError, ER_PROC_LUA, + "Invalid Lua IPROTO handler return " + "type: expected boolean"); + rc = IPROTO_HANDLER_ERROR; + } + } else { + rc = IPROTO_HANDLER_ERROR; + } + func_adapter_end(trigger, &ctx); + if (rc != IPROTO_HANDLER_FALLBACK) + break; + } + event_trigger_iterator_destroy(&it); + return rc; } /** @@ -2921,10 +3039,29 @@ tx_process_override(struct cmsg *m) if (handlers == NULL) handlers = mh_req_handlers_get(IPROTO_UNKNOWN); assert(handlers != NULL); - enum iproto_handler_status rc; + enum iproto_handler_status rc = IPROTO_HANDLER_FALLBACK; - rc = handlers->c.cb(header, header_end, body, body_end, - handlers->c.ctx); + /* + * Run handlers from the event registry, set by request type id. + */ + if (handlers->event_by_id != NULL) { + rc = tx_run_override_triggers(handlers->event_by_id, header, + header_end, body, body_end); + } + /* + * Run handlers from the event registry, set by request type name. + */ + if (rc == IPROTO_HANDLER_FALLBACK && handlers->event_by_name != NULL) { + rc = tx_run_override_triggers(handlers->event_by_name, header, + header_end, body, body_end); + } + /* + * Run C handlers. + */ + if (rc == IPROTO_HANDLER_FALLBACK && handlers->c.cb != NULL) { + rc = handlers->c.cb(header, header_end, body, body_end, + handlers->c.ctx); + } struct cmsg_hop *route = NULL; switch (rc) { @@ -3480,8 +3617,136 @@ iproto_thread_init(struct iproto_thread *iproto_thread iproto_thread->tx.requests_in_progress = 0; iproto_thread->requests_in_stream_queue = 0; rlist_create(&iproto_thread->connections); +} + +/** + * True for IPROTO request types that can be overridden. + */ +static bool +is_iproto_override_supported(uint32_t req_type) +{ + switch (req_type) { + case IPROTO_JOIN: + case IPROTO_SUBSCRIBE: + case IPROTO_FETCH_SNAPSHOT: + case IPROTO_REGISTER: + return false; + default: + return true; + } } +/** + * If the `name' contains a valid name of an IPROTO overriding event, sets + * `req_type' and returns True. If the name contains correct prefix, but + * the request type is invalid, the error is logged with CRIT log level. + * `is_by_id' set to True if the request is overridden by id, False if by name. + */ +static bool +get_iproto_type_from_event_name(const char *name, uint32_t *req_type, + bool *is_by_id) +{ + const char *prefix = "box.iproto.override"; + const size_t prefix_len = strlen(prefix); + if (strncmp(name, prefix, prefix_len) != 0) + return false; + + const char *req_name = name + prefix_len; + const char *req_name_err = req_name; + if (*req_name == '.') { + *is_by_id = false; + /* Skip the dot. */ + req_name++; + req_name_err = req_name; + *req_type = get_iproto_type_by_name(req_name); + if (*req_type == iproto_type_MAX) + goto err_bad_type; + } else if (*req_name == '[') { + *is_by_id = true; + /* Skip open bracket. */ + req_name++; + if (!isdigit(*req_name) && *req_name != '-') + goto err_bad_type; + char *endptr; + *req_type = strtol(req_name, &endptr, 10); + if (endptr == req_name) + goto err_bad_type; + /* + * At least one digit is parsed. + * Check that the rest of the string equals "]". + */ + if (*endptr != ']' || endptr[1] != 0) + goto err_bad_type; + } else { + /* Not in IPROTO override namespace. */ + return false; + } + + if (!is_iproto_override_supported(*req_type)) { + say_crit("IPROTO request handler overriding does not support " + "`%s' request type", iproto_type_name(*req_type)); + return false; + } + return true; + +err_bad_type: + say_crit("The event `%s' is in IPROTO override namespace, but `%s' is " + "not a valid request type", name, req_name_err); + return false; +} + +/** + * Gets an arbitrary `event', checks its name, and adds it to `req_handlers' if + * it is a valid IPROTO overriding event. + * If the event name contains correct IPROTO overriding prefix, but the request + * type is invalid, the error is logged with CRIT log level. + */ +static bool +iproto_override_event_init(struct event *event, void *arg) +{ + (void)arg; + uint32_t type; + bool is_by_id; + if (!get_iproto_type_from_event_name(event->name, &type, &is_by_id)) + return true; + + struct iproto_req_handlers *handlers = mh_req_handlers_get(type); + if (handlers == NULL) { + handlers = iproto_req_handlers_new(); + mh_req_handlers_put(type, handlers); + } + iproto_req_handlers_set_event(handlers, event, is_by_id); + + for (int i = 0; i < iproto_threads_count; i++) { + struct iproto_thread *iproto_thread = &iproto_threads[i]; + mh_i32_put(iproto_thread->req_handlers, &type, NULL, NULL); + } + return true; +} + +/** + * Notifies IPROTO threads that a new request handler has been set. + */ +static void +iproto_cfg_override(uint32_t req_type, bool is_set); + +/** + * Calls iproto_cfg_override() and destroys the handlers when necessary. + */ +static void +iproto_override_finish(struct iproto_req_handlers *handlers, uint32_t req_type, + bool old_is_set) +{ + bool new_is_set = iproto_req_handler_is_set(handlers); + if (new_is_set != old_is_set) + iproto_cfg_override(req_type, new_is_set); + + if (!new_is_set && handlers != NULL) { + mh_req_handlers_del(req_type); + iproto_req_handlers_delete(handlers); + } +} + /** Initialize the iproto subsystem and start network io thread */ void iproto_init(int threads_count) @@ -3507,6 +3772,17 @@ iproto_init(int threads_count) struct iproto_thread *iproto_thread = &iproto_threads[i]; iproto_thread->id = i; iproto_thread_init(iproto_thread); + } + + /* + * Go through all events with triggers, and initialize overridden + * request handlers that were registered before IPROTO initialization. + */ + tx_req_handlers = mh_i32ptr_new(); + event_foreach(iproto_override_event_init, NULL); + + for (int i = 0; i < threads_count; i++) { + struct iproto_thread *iproto_thread = &iproto_threads[i]; if (cord_costart(&iproto_thread->net_cord, "iproto", net_cord_f, iproto_thread)) panic("failed to start iproto thread"); @@ -3519,7 +3795,6 @@ iproto_init(int threads_count) iproto_msg_max / 2); } - tx_req_handlers = mh_i32ptr_new(); session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab; if (box_on_shutdown(NULL, iproto_on_shutdown_f, NULL) != 0) @@ -3861,9 +4136,6 @@ iproto_session_new(struct iostream *io, struct user *u return 0; } -/** - * Notifies IPROTO threads that a new request handler has been set. - */ static void iproto_cfg_override(uint32_t req_type, bool is_set) { @@ -4031,8 +4303,7 @@ int iproto_override(uint32_t req_type, iproto_handler_t cb, iproto_handler_destroy_t destroy, void *ctx) { - if (req_type == IPROTO_JOIN || req_type == IPROTO_FETCH_SNAPSHOT || - req_type == IPROTO_REGISTER || req_type == IPROTO_SUBSCRIBE) { + if (!is_iproto_override_supported(req_type)) { const char *feature = tt_sprintf("%s request type", iproto_type_name(req_type)); diag_set(ClientError, ER_UNSUPPORTED, @@ -4042,7 +4313,7 @@ iproto_override(uint32_t req_type, iproto_handler_t cb struct iproto_req_handlers *handlers; handlers = mh_req_handlers_get(req_type); - bool old_is_set = iproto_req_handler_is_set(handlers); + bool is_set = iproto_req_handler_is_set(handlers); if (handlers != NULL && handlers->c.destroy != NULL) handlers->c.destroy(handlers->c.ctx); @@ -4061,13 +4332,6 @@ iproto_override(uint32_t req_type, iproto_handler_t cb handlers->c.ctx = NULL; } - bool new_is_set = iproto_req_handler_is_set(handlers); - if (new_is_set != old_is_set) - iproto_cfg_override(req_type, new_is_set); - - if (!new_is_set && handlers != NULL) { - mh_req_handlers_del(req_type); - iproto_req_handlers_delete(handlers); - } + iproto_override_finish(handlers, req_type, is_set); return 0; } blob - 9040f7bf550a099ad064362f806c8f934777200f blob + 64344213647cdb61d14e77c5c0e22bc02916b3fc --- src/box/iproto_constants.c +++ src/box/iproto_constants.c @@ -94,6 +94,8 @@ const char *iproto_type_strs[iproto_type_MAX] = { IPROTO_TYPES(IPROTO_TYPE_STRS_MEMBER) }; +char *iproto_type_lower_strs[iproto_type_MAX]; + #define IPROTO_RAFT_KEY_STRS_MEMBER(s, ...) \ [IPROTO_RAFT_ ## s] = #s, blob - fa00b5b60b4d1729652f2e5604e3d9279e1eb392 blob + df6795650d5c48e2df9135a56c5b23a17409d1f9 --- src/box/iproto_constants.h +++ src/box/iproto_constants.h @@ -426,6 +426,9 @@ enum iproto_type { /** IPROTO type name by code */ extern const char *iproto_type_strs[]; + +/** IPROTO type name by code, in lower case. */ +extern char *iproto_type_lower_strs[]; #define IPROTO_RAFT_KEYS(_) \ _(TERM, 0) \ @@ -465,6 +468,15 @@ iproto_type_name(uint16_t type) default: return NULL; } +} + +/** Returns lowercase IPROTO type name by IPROTO `type'. */ +static inline const char * +iproto_type_name_lower(uint16_t type) +{ + if (type < iproto_type_MAX) + return iproto_type_lower_strs[type]; + return NULL; } /** Predefined replication group identifiers. */ @@ -670,6 +682,27 @@ vy_row_index_key_name(enum vy_row_index_key key) return vy_row_index_key_strs[key]; } +/** Initialize the "IPROTO constants" subsystem. */ +static inline void +iproto_constants_init(void) +{ + for (size_t i = 0; i < iproto_type_MAX; i++) { + const char *type_name = iproto_type_strs[i]; + iproto_type_lower_strs[i] = type_name == NULL ? NULL : + strtolowerdup(type_name); + } +} + +/** Destroy the "IPROTO constants" subsystem. */ +static inline void +iproto_constants_free(void) +{ + for (size_t i = 0; i < iproto_type_MAX; i++) { + free(iproto_type_lower_strs[i]); + iproto_type_lower_strs[i] = NULL; + } +} + #if defined(__cplusplus) } /* extern "C" */ #endif blob - 41f064857dd885e4daa5898871bbd703f7c9a20b blob + 053ef6d83a1310aa508b7546dcc9ee221ef16e93 --- test/box-luatest/iproto_request_handlers_overriding_test.lua +++ test/box-luatest/iproto_request_handlers_overriding_test.lua @@ -1,6 +1,24 @@ local server = require('luatest.server') local t = require('luatest') +-- Keep in sync with `is_iproto_override_supported' in `iproto.cc'. +local unsupported_rq_types = { + JOIN = box.iproto.type.JOIN, + SUBSCRIBE = box.iproto.type.SUBSCRIBE, + FETCH_SNAPSHOT = box.iproto.type.FETCH_SNAPSHOT, + REGISTER = box.iproto.type.REGISTER, +} + +-- Grep server logs for error messages about unsupported request types. +local function check_unsupported_rq_types(cg) + local msg + for req in pairs(unsupported_rq_types) do + msg = "C> IPROTO request handler overriding does not support `" .. + req .. "' request type" + t.assert(cg.server:grep_log(msg)) + end +end + local g = t.group() g.before_all(function(cg) @@ -133,7 +151,7 @@ end -- Checks that `box.iproto.override` errors are handled correctly. g.test_box_iproto_override_errors = function(cg) - cg.server:exec(function() + cg.server:exec(function(unsupported_rq_types) local err_msg = "Usage: box.iproto.override(request_type, callback)" t.assert_error_msg_content_equals(err_msg, function() box.iproto.override() @@ -153,12 +171,6 @@ g.test_box_iproto_override_errors = function(cg) t.assert_error_msg_content_equals(err_msg, function() box.iproto.override(0, 'str') end) - local unsupported_rq_types = { - JOIN = box.iproto.type.JOIN, - FETCH_SNAPSHOT = box.iproto.type.FETCH_SNAPSHOT, - REGISTER = box.iproto.type.REGISTER, - SUBSCRIBE = box.iproto.type.SUBSCRIBE, - } for rq_name, rq_type in pairs(unsupported_rq_types) do err_msg = ("IPROTO request handler overriding does not " .. "support %s request type"):format(rq_name) @@ -166,7 +178,7 @@ g.test_box_iproto_override_errors = function(cg) box.iproto.override(rq_type, function() end) end) end - end) + end, {unsupported_rq_types}) end -- Checks that `box.iproto.override` reset of non-existing request handler is @@ -430,5 +442,187 @@ g.test_box_iproto_override_fallback_double_accounting box.iproto.override(box.iproto.type.PING, nil) local after = box.stat.net().REQUESTS_IN_PROGRESS.current t.assert_equals(after - before, 0) + end) +end + +-- Start server and set global functions. +local function init_server(cg, server_env) + cg.server = server:new({env = server_env}) + cg.server:start() + cg.server:exec(function(net_box_uri) + rawset(_G, 'send_request_and_read_response', function(request_type) + local uri = require('uri') + local socket = require('socket') + -- Connect to the server. + local u = uri.parse(net_box_uri) + local s = socket.tcp_connect(u.host, u.service) + local greeting = s:read(box.iproto.GREETING_SIZE) + greeting = box.iproto.decode_greeting(greeting) + t.assert_covers(greeting, {protocol = 'Binary'}) + -- Send the request. + local request = box.iproto.encode_packet( + {request_type = request_type}, {} + ) + t.assert_equals(s:write(request), #request) + -- Read the response. + local response = '' + local header, body + repeat + header, body = box.iproto.decode_packet(response) + if header == nil then + local size = body + local data = s:read(size) + t.assert_is_not(data) + response = response .. data + end + until header ~= nil + s:close() + return body[1] + end) + end, {cg.server.net_box_uri}) +end + +-- Delete spaces and triggers. +local function delete_spaces_and_triggers(cg) + cg.server:exec(function() + local trigger = require('trigger') + if box.space.test then box.space.test:drop() end + -- Delete all registered triggers. + for event, trigger_list in pairs(trigger.info()) do + for _, trigger_descr in pairs(trigger_list) do + local trigger_name = trigger_descr[1] + trigger.del(event, trigger_name) + end + end + end) +end + +-- Grep server logs for error messages about wrong request types. +-- Note that event names are case-sensitive. +local function check_wrong_rq_types(cg) + local msg + msg = "C> The event `box.iproto.override.' is in IPROTO override " .. + "namespace, but `' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override.pinG' is in IPROTO override " .. + "namespace, but `pinG' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[' is in IPROTO override " .. + "namespace, but `%[' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[64' is in IPROTO override " .. + "namespace, but `%[64' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[ 64%]' is in IPROTO override " .. + "namespace, but `%[ 64%]' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[64 %]' is in IPROTO override " .. + "namespace, but `%[64 %]' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[64%] ' is in IPROTO override " .. + "namespace, but `%[64%] ' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[%-%]' is in IPROTO override " .. + "namespace, but `%[%-%]' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[ping%]' is in IPROTO override " .. + "namespace, but `%[ping%]' is not a valid request type" + t.assert(cg.server:grep_log(msg)) + msg = "C> The event `box.iproto.override%[64%].ping' is in IPROTO " .. + "override namespace, but `%[64%].ping' is not a valid request type" + t.assert(cg.server:grep_log(msg)) +end + +-- Test IPROTO request handlers override using event triggers, that are set +-- before `box.cfg{}'. Requests are sent via a raw socket to test the response +-- on the unknown request types. +local g2 = t.group('gh-8138-triggers-before-box-cfg') + +g2.before_all(function(cg) + -- List of unsupported request types, represented as a string. + local req_types_str = '' + for req in pairs(unsupported_rq_types) do + req_types_str = req_types_str .. ("'%s', "):format(req:lower()) + end + -- Set event triggers before `box.cfg{}'. + t.assert_equals(box.iproto.type.EXECUTE, 11) + local run_before_cfg = ([[ + local trigger = require('trigger') + local function handler_execute() + local resp = 'IPROTO_EXECUTE handler, set by name, before box.cfg{}' + box.iproto.send(box.session.id(), {}, {resp}) + return true + end + local function handler_n11() + local resp = 'IPROTO_EXECUTE handler, set by id, before box.cfg{}' + box.iproto.send(box.session.id(), {}, {resp}) + return true + end + local function handler_ping() + local resp = 'IPROTO_PING handler, set by name, before box.cfg{}' + box.iproto.send(box.session.id(), {}, {resp}) + return true + end + local function handler_n555() + local resp = 'IPROTO #555 handler, set by id, before box.cfg{}' + box.iproto.send(box.session.id(), {}, {resp}) + return true + end + local function handler_unknown() + local resp = 'IPROTO_UNKNOWN handler, set by name, before box.cfg{}' + box.iproto.send(box.session.id(), {}, {resp}) + return true + end + trigger.set('box.iproto.override.execute', 'exec', handler_execute) + trigger.set('box.iproto.override[11]', '#11', handler_n11) + trigger.set('box.iproto.override.ping', 'ping', handler_ping) + trigger.set('box.iproto.override[555]', '#555', handler_n555) + trigger.set('box.iproto.override.unknown', 'unk', handler_unknown) + -- Set triggers on the unsupported request types. + for _, req in pairs({%s}) do + trigger.set('box.iproto.override.' .. req, 'unsup', function() end) + end + -- Set triggers on the wrong request types. + trigger.set('box.iproto.override.', 'wrong', function() end) + trigger.set('box.iproto.override.pinG', 'wrong', function() end) + trigger.set('box.iproto.override[', 'wrong', function() end) + trigger.set('box.iproto.override[64', 'wrong', function() end) + trigger.set('box.iproto.override[ 64]', 'wrong', function() end) + trigger.set('box.iproto.override[64 ]', 'wrong', function() end) + trigger.set('box.iproto.override[64] ', 'wrong', function() end) + trigger.set('box.iproto.override[-]', 'wrong', function() end) + trigger.set('box.iproto.override[ping]', 'wrong', function() end) + trigger.set('box.iproto.override[64].ping', 'wrong', function() end) + ]]):format(req_types_str) + init_server(cg, {['TARANTOOL_RUN_BEFORE_BOX_CFG'] = run_before_cfg}) +end) + +g2.after_all(function(cg) cg.server:drop() end) +g2.after_each(delete_spaces_and_triggers) + +-- Check that it is possible to override the handlers using event triggers. +g2.test_event_triggers = function(cg) + -- Grep logs for errors about unsupported and wrong request types. + check_unsupported_rq_types(cg) + check_wrong_rq_types(cg) + + -- Send correct requests with overridden handlers. + cg.server:exec(function() + -- Note that IPROTO_EXECUTE is overridden both by id and by name, and + -- "by id" handler is always called first. + t.assert_equals( + _G.send_request_and_read_response(box.iproto.type.EXECUTE), + 'IPROTO_EXECUTE handler, set by id, before box.cfg{}') + t.assert_equals( + _G.send_request_and_read_response(box.iproto.type.PING), + 'IPROTO_PING handler, set by name, before box.cfg{}') + -- Send an unknown request with a dedicated handler. + t.assert_equals( + _G.send_request_and_read_response(555), + 'IPROTO #555 handler, set by id, before box.cfg{}') + -- Send an unknown request without a dedicated handler. + t.assert_equals( + _G.send_request_and_read_response(666), + 'IPROTO_UNKNOWN handler, set by name, before box.cfg{}') end) end