From 4e5d60d819c68caea1923311cda6fd9cda2ec3b5 Mon Sep 17 00:00:00 2001
From: Serge Petrenko <sergepetrenko@tarantool.org>
Date: Thu, 23 Apr 2020 14:17:15 +0300
Subject: [PATCH] replication: add box.info.replication_anon

Closes #4900

@TarantoolBot document
Title: add new field to box.info: replication_anon

It is now possible to list all the anonymous replicas following the
instance with a call to `box.info.replication_anon()`
The output is similar to the one produced by `box.info.replication` with
an exception that anonymous replicas are indexed by their uuid strings
rather then server ids, since server ids have no meaning for anonymous
replicas.

Note, that when you issue a plain `box.info.replication_anon`, the only
info returned is the number of anonymous replicas following the current
instance. In order to see the full stats, you have to call
`box.info.replication_anon()`. This is done to not overload the `box.info`
output with excess info, since there may be lots of anonymous replicas.
Example:

```
tarantool> box.info.replication_anon
---
- count: 2
...

tarantool> box.info.replication_anon()
---
- 3a6a2cfb-7e47-42f6-8309-7a25c37feea1:
    id: 0
    uuid: 3a6a2cfb-7e47-42f6-8309-7a25c37feea1
    lsn: 0
    downstream:
      status: follow
      idle: 0.76203499999974
      vclock: {1: 1}
  f58e4cb0-e0a8-42a1-b439-591dd36c8e5e:
    id: 0
    uuid: f58e4cb0-e0a8-42a1-b439-591dd36c8e5e
    lsn: 0
    downstream:
      status: follow
      idle: 0.0041349999992235
      vclock: {1: 1}
...

```

Note, that anonymous replicas hide their lsn from the others, so
anonymous replica lsn will always be reported as zero, even if anonymous
replicas perform some local space operations.
To know the anonymous replica's lsn, you have to issue `box.info.lsn` on
it.
---
 src/box/lua/info.c             | 50 ++++++++++++++++++++++++++++++++++
 src/box/replication.cc         |  3 ++
 src/box/replication.h          |  2 ++
 test/box/info.result           |  1 +
 test/replication/anon.result   | 24 ++++++++++++++--
 test/replication/anon.test.lua | 11 ++++++--
 6 files changed, 86 insertions(+), 5 deletions(-)

diff --git a/src/box/lua/info.c b/src/box/lua/info.c
index a789f78228..1e77737fd5 100644
--- a/src/box/lua/info.c
+++ b/src/box/lua/info.c
@@ -214,6 +214,55 @@ lbox_info_replication(struct lua_State *L)
 	return 1;
 }
 
+static int
+lbox_info_replication_anon_call(struct lua_State *L)
+{
+	lua_newtable(L);
+
+	/* Metatable. */
+	lua_newtable(L);
+	lua_pushliteral(L, "mapping");
+	lua_setfield(L, -2, "__serialize");
+	lua_setmetatable(L, -2);
+
+	replicaset_foreach(replica) {
+		if (!replica->anon)
+			continue;
+
+		lua_pushstring(L, tt_uuid_str(&replica->uuid));
+		lbox_pushreplica(L, replica);
+
+		lua_settable(L, -3);
+	}
+
+	return 1;
+}
+
+static int
+lbox_info_replication_anon(struct lua_State *L)
+{
+	/*
+	 * Make the .replication_anon field callable in order to
+	 * not flood the output with possibly lots of anonymous
+	 * replicas on box.info call.
+	 */
+	lua_newtable(L);
+
+	lua_pushliteral(L, "count");
+	lua_pushinteger(L, replicaset.anon_count);
+	lua_settable(L, -3);
+
+	/* Metatable. */
+	lua_newtable(L);
+
+	lua_pushstring(L, "__call");
+	lua_pushcfunction(L, lbox_info_replication_anon_call);
+	lua_settable(L, -3);
+
+	lua_setmetatable(L, -2);
+	return 1;
+}
+
 static int
 lbox_info_id(struct lua_State *L)
 {
@@ -534,6 +583,7 @@ static const struct luaL_Reg lbox_info_dynamic_meta[] = {
 	{"vclock", lbox_info_vclock},
 	{"ro", lbox_info_ro},
 	{"replication", lbox_info_replication},
+	{"replication_anon", lbox_info_replication_anon},
 	{"status", lbox_info_status},
 	{"uptime", lbox_info_uptime},
 	{"pid", lbox_info_pid},
diff --git a/src/box/replication.cc b/src/box/replication.cc
index 7c10fb6f20..1b61c92c0d 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -219,6 +219,7 @@ replicaset_add_anon(const struct tt_uuid *replica_uuid)
 	replica->uuid = *replica_uuid;
 	replica_hash_insert(&replicaset.hash, replica);
 	replica->anon = true;
+	replicaset.anon_count++;
 	return replica;
 }
 
@@ -909,6 +910,8 @@ replica_on_relay_stop(struct replica *replica)
 			 * replicas.
 			 */
 			assert(replica->applier == NULL);
+			assert(replicaset.anon_count > 0);
+			replicaset.anon_count--;
 		}
 	}
 	if (replica_is_orphan(replica)) {
diff --git a/src/box/replication.h b/src/box/replication.h
index 9df91e6118..93a25c8a78 100644
--- a/src/box/replication.h
+++ b/src/box/replication.h
@@ -203,6 +203,8 @@ struct replicaset {
 	 * from a remote master.
 	 */
 	bool is_joining;
+	/* A number of anonymous replicas following this instance. */
+	int anon_count;
 	/** Applier state. */
 	struct {
 		/**
diff --git a/test/box/info.result b/test/box/info.result
index e6d0ba6aa9..40eeae0695 100644
--- a/test/box/info.result
+++ b/test/box/info.result
@@ -83,6 +83,7 @@ t
   - package
   - pid
   - replication
+  - replication_anon
   - ro
   - signature
   - sql
diff --git a/test/replication/anon.result b/test/replication/anon.result
index cbbeeef09c..a7e244d3f4 100644
--- a/test/replication/anon.result
+++ b/test/replication/anon.result
@@ -151,10 +151,28 @@ test_run:cmd('switch default')
  | - true
  | ...
 
--- Replica isn't visible on master.
-#box.info.replication
+-- Test box.info.replication_anon.
+box.info.replication_anon
+ | ---
+ | - count: 1
+ | ...
+#box.info.replication_anon()
+ | ---
+ | - 0
+ | ...
+uuid, tbl = next(box.info.replication_anon())
+ | ---
+ | ...
+-- Anonymous replicas are indexed by uuid strings.
+require("uuid").fromstr(uuid) ~= nil
+ | ---
+ | - true
+ | ...
+-- Anonymous replicas share box.info representation with
+-- normal replicas.
+tbl.downstream.status
  | ---
- | - 1
+ | - follow
  | ...
 
 -- Test that replication (even anonymous) from an anonymous
diff --git a/test/replication/anon.test.lua b/test/replication/anon.test.lua
index 627dc5c8ec..fef72d07d3 100644
--- a/test/replication/anon.test.lua
+++ b/test/replication/anon.test.lua
@@ -50,8 +50,15 @@ box.space.loc:truncate()
 
 test_run:cmd('switch default')
 
--- Replica isn't visible on master.
-#box.info.replication
+-- Test box.info.replication_anon.
+box.info.replication_anon
+#box.info.replication_anon()
+uuid, tbl = next(box.info.replication_anon())
+-- Anonymous replicas are indexed by uuid strings.
+require("uuid").fromstr(uuid) ~= nil
+-- Anonymous replicas share box.info representation with
+-- normal replicas.
+tbl.downstream.status
 
 -- Test that replication (even anonymous) from an anonymous
 -- instance is forbidden. An anonymous replica will fetch
-- 
GitLab