Commit Diff


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 <stdarg.h>
 #include <stdio.h>
 #include <fcntl.h>
+#include <ctype.h>
 
 #include <msgpuck.h>
 #include <small/ibuf.h>
@@ -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