[tarantool-patches] [PATCH 2/2] On ctl event trigger

Georgy Kirichenko georgy at tarantool.org
Thu Aug 30 16:04:56 MSK 2018


Sorry, i forgot to attach some files in the last commit, there is updated diff 
version. Corresponding branch is updated.

commit f77206197559df7acc0cf53822c2aecadbf5a04e
Author: Georgy Kirichenko <georgy at tarantool.org>
Date:   Tue Aug 28 18:09:24 2018 +0300

    On ctl event trigger
    
    Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
    changes, a space create/alter/drop action, an applier state change and
    shutdown. Trigger could be set with box.ctl_event even before the first
    box.cfg invocation to control recovery and bootstrap behavior.
    
    Event constants accessible via box.ctl_event.const()
    There are events:
      - RECOVERY
      - SPACE
      - SHUTDOWN
      - APPLIER
    
    A recovery event might have a status:
       * RECOVERY_SNAPSHOT_START
       * RECOVERY_SNAPSHOT_DONE
       * RECOVERY_HOT_STANDBY_START
       * RECOVERY_HOT_STANDBY_DONE
       * RECOVERY_XLOGS_DONE
       * RECOVERY_BOOTSTRAP_START
       * RECOVERY_BOOTSTRAP_DONE
       * RECOVERY_INITIAL_JOIN_START
       * RECOVERY_INITIAL_JOIN_DONE
       * RECOVERY_FINAL_JOIN_DONE
    
    A space event consists of space identifier and action:
       * SPACE_CREATE
       * SPACE_ALTER
       * SPACE_DELETE
    
    An applier event contains peer uuid and state:
       * APPLIER_OFF
       * APPLIER_CONNECT
       * APPLIER_CONNECTED
       * APPLIER_AUTH
       * APPLIER_READY
       * APPLIER_INITIAL_JOIN
       * APPLIER_FINAL_JOIN
       * APPLIER_JOINED
       * APPLIER_SYNC
       * APPLIER_FOLLOW
       * APPLIER_STOPPED
       * APPLIER_DISCONNECTED
       * APPLIER_LOADING
    
    Fixes: #3159

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index cab6a2276..ee495a037 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -113,6 +113,7 @@ add_library(box STATIC
     journal.c
     wal.c
     call.c
+    ctl_event.c
     ${lua_sources}
     lua/init.c
     lua/call.c
@@ -131,6 +132,7 @@ add_library(box STATIC
     lua/session.c
     lua/net_box.c
     lua/xlog.c
+    lua/ctl_event.c
     ${bin_sources})
 
 target_link_libraries(box box_error tuple stat xrow xlog vclock crc32 
scramble
diff --git a/src/box/alter.cc b/src/box/alter.cc
index b2758a4d9..8e44ada9b 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -52,6 +52,7 @@
 #include "identifier.h"
 #include "version.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 /**
  * chap-sha1 of empty string, i.e.
@@ -1620,6 +1621,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_CREATE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_space->def->name, old_space->def->id,
 				 old_space->def->uid, SC_SPACE, PRIV_D, true);
@@ -1663,6 +1669,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 			txn_alter_trigger_new(on_drop_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, old_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_DELETE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else { /* UPDATE, REPLACE */
 		assert(old_space != NULL && new_tuple != NULL);
 		struct space_def *def =
@@ -1720,6 +1731,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 		alter_space_do(txn, alter);
 		alter_guard.is_active = false;
 		trigger_run_xc(&on_alter_space, alter->new_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_ALTER;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	}
 }
 
diff --git a/src/box/applier.cc b/src/box/applier.cc
index 28df8f7ca..4a42c6b43 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -48,6 +48,7 @@
 #include "error.h"
 #include "session.h"
 #include "cfg.h"
+#include "ctl_event.h"
 
 STRS(applier_state, applier_STATE);
 
@@ -58,6 +59,11 @@ applier_set_state(struct applier *applier, enum 
applier_state state)
 	say_debug("=> %s", applier_state_strs[state] +
 		  strlen("APPLIER_"));
 	trigger_run_xc(&applier->on_state, applier);
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_APPLIER;
+	ctl_event.applier.replica_uuid = applier->uuid;
+	ctl_event.applier.status = state;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
diff --git a/src/box/box.cc b/src/box/box.cc
index 0004140e9..01e512cc2 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -73,6 +73,7 @@
 #include "call.h"
 #include "func.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 static char status[64] = "unknown";
 
@@ -1602,6 +1603,9 @@ box_free(void)
 	 * initialized
 	 */
 	if (is_box_configured) {
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_SHUTDOWN;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 #if 0
 		session_free();
 		user_cache_free();
@@ -1670,6 +1674,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (boxk(IPROTO_DELETE, BOX_CLUSTER_ID, "[%u]", 1) != 0)
 		diag_raise();
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	/* Register the first replica in the replica set */
 	box_register_replica(replica_id, &INSTANCE_UUID);
 	assert(replica_by_uuid(&INSTANCE_UUID)->id == 1);
@@ -1690,6 +1699,9 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (engine_begin_checkpoint() ||
 	    engine_commit_checkpoint(&replicaset.vclock))
 		panic("failed to create a checkpoint");
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
@@ -1703,6 +1715,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 static void
 bootstrap_from_master(struct replica *master)
 {
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	struct applier *applier = master->applier;
 	assert(applier != NULL);
 	applier_resume_to_state(applier, APPLIER_READY, TIMEOUT_INFINITY);
@@ -1725,6 +1742,9 @@ bootstrap_from_master(struct replica *master)
 	 */
 	engine_begin_initial_recovery_xc(NULL);
 	applier_resume_to_state(applier, APPLIER_FINAL_JOIN, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/*
 	 * Process final data (WALs).
@@ -1735,6 +1755,9 @@ bootstrap_from_master(struct replica *master)
 	journal_set(&journal.base);
 
 	applier_resume_to_state(applier, APPLIER_JOINED, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_FINAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Clear the pointer to journal before it goes out of scope */
 	journal_set(NULL);
@@ -1883,13 +1906,22 @@ local_recovery(const struct tt_uuid *instance_uuid,
 	 */
 	memtx_engine_recover_snapshot_xc(memtx, checkpoint_vclock);
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	engine_begin_final_recovery_xc();
 	recover_remaining_wals(recovery, &wal_stream.base, NULL, false);
 	/*
 	 * Leave hot standby mode, if any, only after
 	 * acquiring the lock.
 	 */
+
 	if (wal_dir_lock < 0) {
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		title("hot_standby");
 		say_info("Entering hot standby mode");
 		recovery_follow_local(recovery, &wal_stream.base, "hot_standby",
@@ -1908,11 +1940,17 @@ local_recovery(const struct tt_uuid *instance_uuid,
 		 * applied in hot standby mode.
 		 */
 		vclock_copy(&replicaset.vclock, &recovery->vclock);
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_DONE;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		box_listen();
 		box_sync_replication(false);
 	}
 	recovery_finalize(recovery);
 	engine_end_recovery_xc();
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_XLOGS_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Check replica set UUID. */
 	if (!tt_uuid_is_nil(replicaset_uuid) &&
@@ -2021,6 +2059,10 @@ box_cfg_xc(void)
 	bool is_bootstrap_leader = false;
 	if (last_checkpoint_lsn >= 0) {
 		/* Recover the instance from the local directory */
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		local_recovery(&instance_uuid, &replicaset_uuid,
 			       &last_checkpoint_vclock);
 	} else {
diff --git a/src/box/ctl_event.c b/src/box/ctl_event.c
new file mode 100644
index 000000000..387bdd367
--- /dev/null
+++ b/src/box/ctl_event.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "ctl_event.h"
+
+struct rlist on_ctl_trigger = RLIST_HEAD_INITIALIZER(on_ctl_trigger);
diff --git a/src/box/ctl_event.h b/src/box/ctl_event.h
new file mode 100644
index 000000000..ac57e51b5
--- /dev/null
+++ b/src/box/ctl_event.h
@@ -0,0 +1,100 @@
+#ifndef BOX_CTL_EVENT_H
+#define BOX_CTL_EVENT_H
+
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+#include "small/rlist.h"
+#include "tt_uuid.h"
+
+/* Ctl event types. */
+#define CTL_RECOVERY     1
+#define CTL_SPACE        2
+#define CTL_SHUTDOWN     3
+#define CTL_APPLIER      4
+
+/* CTL_RECOVERY event status. */
+#define CTL_RECOVERY_SNAPSHOT_START       1
+#define CTL_RECOVERY_SNAPSHOT_DONE        2
+#define CTL_RECOVERY_HOT_STANDBY_START    3
+#define CTL_RECOVERY_HOT_STANDBY_DONE     4
+#define CTL_RECOVERY_XLOGS_DONE           5
+#define CTL_RECOVERY_BOOTSTRAP_START      6
+#define CTL_RECOVERY_BOOTSTRAP_DONE       7
+#define CTL_RECOVERY_INITIAL_JOIN_START   8
+#define CTL_RECOVERY_INITIAL_JOIN_DONE    9
+#define CTL_RECOVERY_FINAL_JOIN_DONE      10
+
+#define CTL_SPACE_CREATE    1
+#define CTL_SPACE_ALTER     2
+#define CTL_SPACE_DELETE    3
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+extern struct rlist on_ctl_trigger;
+
+
+/* CTL_RECOVERY event specific data. */
+struct on_ctl_recovery_event {
+	uint32_t status;
+};
+
+/* CTL_SPACE event specific data. */
+struct on_ctl_space_event {
+	uint32_t action;
+	uint32_t space_id;
+};
+
+struct on_ctl_applier_event {
+	struct tt_uuid replica_uuid;
+	uint32_t status;
+};
+
+struct on_ctl_event {
+	uint32_t type;
+	union {
+		struct on_ctl_recovery_event recovery;
+		struct on_ctl_space_event space;
+		struct on_ctl_applier_event applier;
+	};
+};
+
+
+#if defined(__cplusplus)
+}
+#endif /* defined(__cplusplus) */
+
+
+#endif
diff --git a/src/box/lua/ctl_event.c b/src/box/lua/ctl_event.c
new file mode 100644
index 000000000..aead1cf23
--- /dev/null
+++ b/src/box/lua/ctl_event.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "trigger.h"
+#include "box/ctl_event.h"
+#include "box/applier.h"
+
+#include "lua/trigger.h"
+#include "lua/utils.h"
+
+static int
+lbox_ctl_event_const(struct lua_State *L)
+{
+	lua_newtable(L);
+	lua_pushstring(L, "RECOVERY");
+	lua_pushinteger(L, CTL_RECOVERY);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE");
+	lua_pushinteger(L, CTL_SPACE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SHUTDOWN");
+	lua_pushinteger(L, CTL_SHUTDOWN);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "RECOVERY_SNAPSHOT_START");
+	lua_pushinteger(L, CTL_RECOVERY_SNAPSHOT_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_SNAPSHOT_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_SNAPSHOT_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_HOT_STANDBY_START");
+	lua_pushinteger(L, CTL_RECOVERY_HOT_STANDBY_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_HOT_STANDBY_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_HOT_STANDBY_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_XLOGS_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_XLOGS_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_BOOTSTRAP_START");
+	lua_pushinteger(L, CTL_RECOVERY_BOOTSTRAP_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_BOOTSTRAP_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_BOOTSTRAP_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_INITIAL_JOIN_START");
+	lua_pushinteger(L, CTL_RECOVERY_INITIAL_JOIN_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_INITIAL_JOIN_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_INITIAL_JOIN_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_FINAL_JOIN_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_FINAL_JOIN_DONE);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "SPACE_CREATE");
+	lua_pushinteger(L, CTL_SPACE_CREATE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE_ALTER");
+	lua_pushinteger(L, CTL_SPACE_ALTER);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE_DELETE");
+	lua_pushinteger(L, CTL_SPACE_DELETE);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "APPLIER_OFF");
+	lua_pushinteger(L, APPLIER_OFF);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_CONNECT");
+	lua_pushinteger(L, APPLIER_CONNECT);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_CONNECTED");
+	lua_pushinteger(L, APPLIER_CONNECTED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_AUTH");
+	lua_pushinteger(L, APPLIER_AUTH);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_READY");
+	lua_pushinteger(L, APPLIER_READY);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_INITIAL_JOIN");
+	lua_pushinteger(L, APPLIER_INITIAL_JOIN);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_FINAL_JOIN");
+	lua_pushinteger(L, APPLIER_FINAL_JOIN);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_JOINED");
+	lua_pushinteger(L, APPLIER_JOINED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_SYNC");
+	lua_pushinteger(L, APPLIER_SYNC);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_FOLLOW");
+	lua_pushinteger(L, APPLIER_FOLLOW);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_STOPPED");
+	lua_pushinteger(L, APPLIER_STOPPED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_DISCONNECTED");
+	lua_pushinteger(L, APPLIER_DISCONNECTED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_LOADING");
+	lua_pushinteger(L, APPLIER_LOADING);
+	lua_settable(L, -3);
+
+	return 1;
+}
+
+static int
+lbox_push_on_ctl_event(struct lua_State *L, void *data)
+{
+	struct on_ctl_event *event = (struct on_ctl_event *)data;
+	switch (event->type) {
+	case CTL_RECOVERY:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_RECOVERY);
+		lua_settable(L, -3);
+		lua_pushstring(L, "status");
+		lua_pushinteger(L, event->recovery.status);
+		lua_settable(L, -3);
+		break;
+	case CTL_SPACE:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_SPACE);
+		lua_settable(L, -3);
+		lua_pushstring(L, "action");
+		lua_pushinteger(L, event->space.action);
+		lua_settable(L, -3);
+		lua_pushstring(L, "space_id");
+		lua_pushinteger(L, event->space.space_id);
+		lua_settable(L, -3);
+		break;
+	case CTL_SHUTDOWN:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_SHUTDOWN);
+		lua_settable(L, -3);
+		break;
+	case CTL_APPLIER:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_APPLIER);
+		lua_settable(L, -3);
+		lua_pushstring(L, "replica");
+		lua_pushstring(L, tt_uuid_str(&event->applier.replica_uuid));
+		lua_settable(L, -3);
+		lua_pushstring(L, "status");
+		lua_pushinteger(L, event->applier.status);
+		lua_settable(L, -3);
+		break;
+	default:
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+static int
+lbox_on_ctl_event(struct lua_State *L)
+{
+	return lbox_trigger_reset(L, 2, &on_ctl_trigger,
+				  lbox_push_on_ctl_event, NULL);
+}
+
+static const struct luaL_Reg lbox_ctl_lib[] = {
+	{"const", lbox_ctl_event_const},
+	{"on_ctl_event", lbox_on_ctl_event},
+	{NULL, NULL}
+};
+
+void
+box_lua_ctl_event_init(struct lua_State *L)
+{
+	luaL_register_module(L, "box.ctl_event", lbox_ctl_lib);
+	lua_pop(L, 1);
+}
diff --git a/src/box/lua/ctl_event.h b/src/box/lua/ctl_event.h
new file mode 100644
index 000000000..a878707a3
--- /dev/null
+++ b/src/box/lua/ctl_event.h
@@ -0,0 +1,48 @@
+#ifndef INCLUDES_TARANTOOL_LUA_CTL_EVENT_H
+#define INCLUDES_TARANTOOL_LUA_CTL_EVENT_H
+
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+void
+box_lua_ctl_event_init(struct lua_State *L);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* INCLUDES_TARANTOOL_LUA_CTL_EVENT_H */
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 694b5bfd3..140803384 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -52,6 +52,7 @@
 #include "box/lua/stat.h"
 #include "box/lua/info.h"
 #include "box/lua/ctl.h"
+#include "box/lua/ctl_event.h"
 #include "box/lua/session.h"
 #include "box/lua/net_box.h"
 #include "box/lua/cfg.h"
@@ -306,6 +307,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_info_init(L);
 	box_lua_stat_init(L);
 	box_lua_ctl_init(L);
+	box_lua_ctl_event_init(L);
 	box_lua_session_init(L);
 	box_lua_xlog_init(L);
 	luaopen_net_box(L);
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index c68a3583f..1935143a6 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -397,6 +397,7 @@ local box_cfg_guard_whitelist = {
     tuple = true;
     runtime = true;
     NULL = true;
+    ctl_event = true;
 };
 
 local box = require('box')
diff --git a/test/box/ctl_event.result b/test/box/ctl_event.result
new file mode 100644
index 000000000..5da8cf40d
--- /dev/null
+++ b/test/box/ctl_event.result
@@ -0,0 +1,364 @@
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/
trig_master.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_master")
+---
+- true
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+---
+...
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+---
+...
+t1 = box.schema.space.create('trig1')
+---
+...
+_ = t1:create_index('pk')
+---
+...
+t1:replace({1, 2})
+---
+- [1, 2, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+...
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+---
+...
+t2 = box.schema.space.create('trig2')
+---
+...
+_ = t2:create_index('pk')
+---
+...
+t2:replace({1, 2})
+---
+- [1, 2]
+...
+t2:select()
+---
+- - [1, 2]
+...
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+---
+- true
+...
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+---
+- true
+...
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = 
true})
+---
+...
+test_run:cmd("create server trig_replica with rpl_master=trig_master, 
script='box/lua/trig_replica.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+t1:replace({2, 3})
+---
+- [2, 3, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+  - [2, 3, 'M']
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 1
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 5
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 1
+    status: 9
+  - type: 4
+    server: <master-uuid>
+    status: 6
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 10
+  - type: 4
+    server: <master-uuid>
+    status: 7
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check before replace trigger
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+t1:replace({3, 4})
+---
+- [3, 4, 'M']
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 1
+    status: 1
+  - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 2
+  - type: 1
+    status: 5
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check tuples changed only one time
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+  - [3, 4, 'M', 'R']
+...
+test_run:cmd("switch default")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+test_run:cmd("stop server trig_master")
+---
+- true
+...
diff --git a/test/box/ctl_event.test.lua b/test/box/ctl_event.test.lua
new file mode 100644
index 000000000..8cb97d80f
--- /dev/null
+++ b/test/box/ctl_event.test.lua
@@ -0,0 +1,76 @@
+env = require('test_run')
+test_run = env.new()
+
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/
trig_master.lua'")
+test_run:cmd("start server trig_master")
+test_run:cmd("switch trig_master")
+env = require('test_run')
+test_run = env.new()
+
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+test_run:cmd("setopt delimiter ';'")
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+test_run:cmd("setopt delimiter ''");
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+t1 = box.schema.space.create('trig1')
+_ = t1:create_index('pk')
+t1:replace({1, 2})
+t1:select()
+
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+t2 = box.schema.space.create('trig2')
+_ = t2:create_index('pk')
+t2:replace({1, 2})
+t2:select()
+
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = 
true})
+test_run:cmd("create server trig_replica with rpl_master=trig_master, 
script='box/lua/trig_replica.lua'")
+test_run:cmd("start server trig_replica")
+t1:replace({2, 3})
+t1:select()
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check before replace trigger
+box.space.trig1:select()
+
+test_run:cmd("switch trig_master")
+test_run:cmd("stop server trig_replica")
+
+t1:replace({3, 4})
+
+test_run:cmd("start server trig_replica")
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check tuples changed only one time
+box.space.trig1:select()
+test_run:cmd("switch default")
+test_run:cmd("stop server trig_replica")
+test_run:cmd("stop server trig_master")
diff --git a/test/box/lua/trig_master.lua b/test/box/lua/trig_master.lua
new file mode 100644
index 000000000..fa253ba4f
--- /dev/null
+++ b/test/box/lua/trig_master.lua
@@ -0,0 +1,8 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/lua/trig_replica.lua b/test/box/lua/trig_replica.lua
new file mode 100644
index 000000000..581bdfdb7
--- /dev/null
+++ b/test/box/lua/trig_replica.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+events = {}
+
+local ctl_const = box.ctl_event.const()
+local recovery_status = nil
+
+local function before_replace(old, new)
+    if recovery_status == ctl_const.RECOVERY_SNAPSHOT_START or
+       recovery_status == ctl_const.RECOVERY_SNAPSHOT_DONE then
+       -- local files
+       return new
+    end
+    if new == nil then
+        return new
+    end
+    local k = {new:unpack()}
+    table.insert(k, 'R')
+    return box.tuple.new(k)
+end
+
+
+local function ctl_event_trigger(event)
+    -- register the event
+    table.insert(events, event)
+    if event.type == ctl_const.RECOVERY then
+        recovery_status = event.status
+    end
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        if event.space_id > 511 then
+            local space = box.space[event.space_id]
+            space:before_replace(before_replace)
+        end
+    end
+end
+
+box.ctl_event.on_ctl_event(ctl_event_trigger)
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    replication         = os.getenv("MASTER"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/misc.result b/test/box/misc.result
index e213d7964..969081e59 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -62,6 +62,7 @@ t
   - cfg
   - commit
   - ctl
+  - ctl_event
   - error
   - feedback
   - index
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: This is a digitally signed message part.
URL: <https://lists.tarantool.org/pipermail/tarantool-patches/attachments/20180830/6558f7cd/attachment.sig>


More information about the Tarantool-patches mailing list