From 5a454e84dec99b94c717847092b0b9c8aff6d9e3 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Sun, 27 Apr 2014 12:20:59 +0400
Subject: [PATCH] async-master-master review fixes

* Remove a bogus message at the start of FindLibUUID.cmake.
* Move cluster_add_node to cluster.
* Extract UUID API into a (potentially) platform-independnent header
  tt_uuid.
* Add error codes for error messages used in UUID generation,
  triggers and consistency checks.
* Cleanup.
---
 cmake/FindLibUUID.cmake  |   2 -
 src/CMakeLists.txt       |   1 +
 src/admin.cc             |   2 +-
 src/box/CMakeLists.txt   |   1 +
 src/box/alter.cc         |  79 +++++++++++++++++++----------
 src/box/box.cc           |  23 ++++-----
 src/box/cluster.cc       |  75 +++++++++++++++++++++++++++
 src/box/cluster.h        | 107 +++++++++++++++++++++++++++++++++++++++
 src/box/txn.cc           |   3 +-
 src/errcode.h            |   5 +-
 src/iobuf.cc             |   4 +-
 src/iobuf.h              |   2 +-
 src/iproto.cc            |   2 +-
 src/log_io.cc            |  26 +++++-----
 src/log_io.h             |  12 +++--
 src/lua/info.cc          |   4 +-
 src/recovery.cc          |  72 +++++++-------------------
 src/recovery.h           |  12 ++---
 src/replica.cc           |  15 +++---
 src/replication.cc       |  41 +++++++--------
 src/trivia/util.h        |   8 ---
 src/tt_uuid.c            |  40 +++++++++++++++
 src/tt_uuid.h            |  90 ++++++++++++++++++++++++++++++++
 src/util.cc              |   8 ---
 test/box/misc.result     |  89 ++++++++++++++++----------------
 test/unit/CMakeLists.txt |   2 +-
 test/unit/log_dir.cc     |   6 +--
 27 files changed, 508 insertions(+), 223 deletions(-)
 create mode 100644 src/box/cluster.cc
 create mode 100644 src/box/cluster.h
 create mode 100644 src/tt_uuid.c
 create mode 100644 src/tt_uuid.h

diff --git a/cmake/FindLibUUID.cmake b/cmake/FindLibUUID.cmake
index aece9f4cb8..d6c4163e96 100644
--- a/cmake/FindLibUUID.cmake
+++ b/cmake/FindLibUUID.cmake
@@ -1,6 +1,4 @@
 if(NOT LIBUUID_FOUND)
-    message(STATUS "NOT FOUND")
-
     find_path(LIBUUID_INCLUDE_DIR
         NAMES uuid.h
         PATH_SUFFIXES uuid
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2ec72df1a1..cc437eb7cd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -70,6 +70,7 @@ set (common_sources
      cfg.cc
      cpu_feature.c
      fiob.c
+     tt_uuid.c
      ffisyms.cc
      lua/init.cc
      lua/fiber.cc
diff --git a/src/admin.cc b/src/admin.cc
index 130e053821..7def35ec83 100644
--- a/src/admin.cc
+++ b/src/admin.cc
@@ -97,7 +97,7 @@ admin_handler(va_list ap)
 	for (;;) {
 		if (admin_dispatch(&coio, iobuf, L) < 0)
 			return;
-		iobuf_gc(iobuf);
+		iobuf_reset(iobuf);
 		fiber_gc();
 	}
 }
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 600f848a2b..70d1f31bd6 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -40,6 +40,7 @@ add_library(box
     box.cc
     access.cc
     authentication.cc
+    cluster.cc
     ${lua_sources}
     lua/call.cc
     lua/tuple.cc
diff --git a/src/box/alter.cc b/src/box/alter.cc
index e866787032..d1d86885c8 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -38,7 +38,7 @@
 #include <new> /* for placement new */
 #include <stdio.h> /* snprintf() */
 #include <ctype.h>
-#include "recovery.h" /* for on_replace_dd_schema trigger */
+#include "cluster.h" /* for cluster_set_uuid() */
 
 /** _space columns */
 #define ID               0
@@ -1498,25 +1498,38 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event)
 
 /* {{{ cluster configuration */
 
+/**
+ * Parse a tuple field which is expected to contain a string
+ * representation of UUID, and return a 16-byte representation.
+ */
+tt_uuid
+tuple_field_uuid(struct tuple *tuple, int fieldno)
+{
+	const char *value = tuple_field_cstr(tuple, fieldno);
+	tt_uuid uuid;
+	if (tt_uuid_from_string(value, &uuid) != 0)
+		tnt_raise(ClientError, ER_INVALID_UUID, value);
+	return uuid;
+}
+
+/**
+ * This trigger is normally invoked only upon initial recovery.
+ *
+ * Before a cluster is assigned a cluster id it's read only.
+ */
 static void
 on_replace_dd_schema(struct trigger * /* trigger */, void *event)
 {
 	struct txn *txn = (struct txn *) event;
 	struct tuple *old_tuple = txn->old_tuple;
 	struct tuple *new_tuple = txn->new_tuple;
-	const char *key = tuple_field_cstr(new_tuple ? new_tuple : old_tuple,0);
+	const char *key = tuple_field_cstr(new_tuple ?
+					   new_tuple : old_tuple, 0);
 	if (strcmp(key, "cluster") == 0) {
 		if (old_tuple != NULL || new_tuple == NULL)
-			tnt_raise(IllegalParams, "'cluster' value is read-only");
-
-		const char *value = tuple_field_cstr(new_tuple, 1);
-		uuid_t cluster_uuid;
-		if (uuid_parse(value, cluster_uuid) != 0)
-			tnt_raise(IllegalParams, "invalid 'cluster' value");
-
-		/* Set Cluster-UUID (can only be done from snapshot) */
-		assert(uuid_is_null(recovery_state->cluster_uuid));
-		uuid_copy(recovery_state->cluster_uuid, cluster_uuid);
+			tnt_raise(ClientError, ER_CLUSTER_ID_IS_RO);
+		tt_uuid uu = tuple_field_uuid(new_tuple, 1);
+		cluster_set_id(&uu);
 	}
 }
 
@@ -1527,35 +1540,49 @@ on_commit_dd_cluster(struct trigger *trigger, void *event)
 	(void) trigger;
 	struct txn *txn = (struct txn *) event;
 	uint32_t node_id = tuple_field_u32(txn->new_tuple, 0);
-	uuid_t node_uuid;
-	uuid_parse(tuple_field_cstr(txn->new_tuple, 1), node_uuid);
+	tt_uuid node_uuid = tuple_field_uuid(txn->new_tuple, 1);
 
-	recovery_confirm_node(recovery_state, node_uuid, node_id);
+	cluster_add_node(&node_uuid, node_id);
 }
 
 static struct trigger commit_cluster_trigger =
 	{ rlist_nil, on_commit_dd_cluster, NULL, NULL };
 
 /**
- * A trigger invoked on replace in the space containing cluster configration.
+ * A trigger invoked on replace in the space _cluster,
+ * which contains cluster configuration.
+ *
+ * The space is modified by JOIN command in IPROTO
+ * protocol.
+ *
+ * The trigger updates the cluster configuration cache
+ * with uuid of the newly joined node.
+ *
+ * During recovery, it acts the same way, loading identifiers
+ * of all nodes into the node cache. Node globally unique
+ * identifiers are used to keep track of cluster configuration,
+ * so that a node that previously joined the cluster can
+ * follow updates, and a node that belongs to a different
+ * cluster can not by mistake join/follow another cluster
+ * without first being reset (emptied).
  */
 static void
 on_replace_dd_cluster(struct trigger *trigger, void *event)
 {
 	(void) trigger;
 	struct txn *txn = (struct txn *) event;
-	if (txn->old_tuple != NULL || txn->new_tuple == NULL)
-		tnt_raise(IllegalParams, "can't change Node-UUID!");
+	struct tuple *old_tuple = txn->old_tuple;
+	struct tuple *new_tuple = txn->new_tuple;
+	if (old_tuple != NULL || new_tuple == NULL)
+		tnt_raise(ClientError, ER_NODE_ID_IS_RO);
 
 	/* Check fields */
-	uint32_t node_id = tuple_field_u32(txn->new_tuple, 0);
-	if (node_id == 0)
-		tnt_raise(IllegalParams, "invalid Node-ID!");
-
-	uuid_t node_uuid;
-	if (uuid_parse(tuple_field_cstr(txn->new_tuple, 1), node_uuid) != 0 ||
-	    uuid_is_null(node_uuid))
-		tnt_raise(IllegalParams, "invalid Node-UUID!");
+	uint32_t node_id = tuple_field_u32(new_tuple, 0);
+	if (cnode_id_is_reserved(node_id))
+		tnt_raise(IllegalParams, "Invalid Node-Id");
+	tt_uuid node_uuid = tuple_field_uuid(new_tuple, 1);
+	if (tt_uuid_is_nil(&node_uuid))
+		tnt_raise(ClientError, ER_INVALID_UUID, tt_uuid_str(&node_uuid));
 
 	trigger_set(&txn->on_commit, &commit_cluster_trigger);
 }
diff --git a/src/box/box.cc b/src/box/box.cc
index e99e10b1ce..5b9c895d36 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -277,12 +277,14 @@ box_leave_local_standby_mode(void *data __attribute__((unused)))
 }
 
 /**
- * @brief Called when recovery/replication wants to add a new node to cluster.
- * recovery_confirm_node() must be called after insert.
+ * @brief Called when recovery/replication wants to add a new node
+ * to cluster.
+ * cluster_add_node() is called as a commit trigger on _cluster
+ * space and actually adds the node to the cluster.
  * @param node_uuid
  */
 static void
-box_on_cluster_join(uuid_t node_uuid)
+box_on_cluster_join(const tt_uuid *node_uuid)
 {
 	struct space *space = space_cache_find(SC_CLUSTER_ID);
 	class Index *index = index_find(space, 0);
@@ -298,7 +300,7 @@ box_on_cluster_join(uuid_t node_uuid)
 	char *data = buf;
 	data = mp_encode_array(data, 2);
 	data = mp_encode_uint(data, node_id);
-	data = mp_encode_str(data, uuid_str(node_uuid), UUID_STR_LEN);
+	data = mp_encode_str(data, tt_uuid_str(node_uuid), UUID_STR_LEN);
 	assert(data <= buf + sizeof(buf));
 	req.tuple = buf;
 	req.tuple_end = data;
@@ -309,8 +311,8 @@ static void
 box_set_cluster_uuid(struct recovery_state *r)
 {
 	/* Save Cluster-UUID to _schema space */
-	uuid_t cluster_uuid;
-	uuid_generate(cluster_uuid);
+	tt_uuid cluster_uuid;
+	tt_uuid_create(&cluster_uuid);
 
 	const char *key = "cluster";
 	struct request req;
@@ -320,7 +322,7 @@ box_set_cluster_uuid(struct recovery_state *r)
 	char *data = buf;
 	data = mp_encode_array(data, 2);
 	data = mp_encode_str(data, key, strlen(key));
-	data = mp_encode_str(data, uuid_str(cluster_uuid), UUID_STR_LEN);
+	data = mp_encode_str(data, tt_uuid_str(&cluster_uuid), UUID_STR_LEN);
 	assert(data <= buf + sizeof(buf));
 	req.tuple = buf;
 	req.tuple_end = data;
@@ -328,7 +330,7 @@ box_set_cluster_uuid(struct recovery_state *r)
 	process_rw(&null_port, &req);
 
 	/* Cluster-UUID was be updated by a _schema trigger */
-	assert(uuid_compare(r->cluster_uuid, cluster_uuid) == 0);
+	assert(tt_uuid_cmp(&r->cluster_uuid, &cluster_uuid) == 0);
 }
 
 void
@@ -402,15 +404,12 @@ box_init()
 		snapshot_save(recovery_state);
 	}
 
-	if (uuid_is_null(recovery_state->cluster_uuid))
+	if (tt_uuid_is_nil(&recovery_state->cluster_uuid))
 		tnt_raise(ClientError, ER_INVALID_CLUSTER);
 
 	space_end_recover_snapshot();
 	space_end_recover();
 
-	say_debug("node: %s", uuid_str(recovery_state->node_uuid));
-	say_debug("cluster: %s", uuid_str(recovery_state->cluster_uuid));
-
 	stat_cleanup(stat_base, IPROTO_DML_REQUEST_MAX);
 	title("orphan", NULL);
 	recovery_follow_local(recovery_state,
diff --git a/src/box/cluster.cc b/src/box/cluster.cc
new file mode 100644
index 0000000000..b4be84de8e
--- /dev/null
+++ b/src/box/cluster.cc
@@ -0,0 +1,75 @@
+/*
+ * 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 "cluster.h"
+#include "recovery.h"
+#include "exception.h"
+
+void
+cluster_set_id(const tt_uuid *uu)
+{
+	/* Set cluster UUID. */
+	assert(tt_uuid_is_nil(&recovery_state->cluster_uuid));
+	recovery_state->cluster_uuid = *uu;
+}
+
+void
+cluster_add_node(const tt_uuid *node_uuid, cnode_id_t node_id)
+{
+	struct recovery_state *r = recovery_state;
+
+	assert(!tt_uuid_is_nil(node_uuid));
+	assert(!cnode_id_is_reserved(node_id));
+
+	/* Add node */
+	struct node *node = (struct node *) calloc(1, sizeof(*node));
+	if (node == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(*node),
+			  "recovery", "r->cluster");
+	}
+	node->id = node_id;
+	node->uuid = *node_uuid;
+	uint32_t k = mh_cluster_put(recovery_state->cluster,
+		(const struct node **) &node, NULL, NULL);
+	if (k == mh_end(recovery_state->cluster)) {
+		free(node);
+		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(*node),
+			  "recovery", "r->cluster");
+	}
+
+	say_debug("confirm node: {uuid = %s, id = %u}",
+		  tt_uuid_str(node_uuid), node_id);
+
+	/* Confirm Local node */
+	if (tt_uuid_cmp(&r->node_uuid, node_uuid) == 0) {
+		/* Confirm Local Node */
+		say_info("synchronized with cluster");
+		assert(r->local_node == NULL || r->local_node->id == 0);
+		r->local_node = node;
+	}
+}
diff --git a/src/box/cluster.h b/src/box/cluster.h
new file mode 100644
index 0000000000..40d6314104
--- /dev/null
+++ b/src/box/cluster.h
@@ -0,0 +1,107 @@
+#ifndef INCLUDES_BOX_CLUSTER_H
+#define INCLUDES_BOX_CLUSTER_H
+/*
+ * 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 "tt_uuid.h"
+#include <stdint.h>
+/**
+ * @module cluster - global state of multi-master
+ * replicated database.
+ *
+ * Right now the cluster can only consist of instances
+ * connected with asynchronous master-master replication.
+ *
+ * Each cluster has a globally unique identifier. Each
+ * node in the cluster is identified as well. A node
+ * which is part of one cluster can not join another
+ * cluster.
+ *
+ * Cluster and node identifiers are stored in a system
+ * space _cluster on all nodes. The node identifier
+ * is also stored in each snapshot header, this is how
+ * the node knows which node id in the cluster belongs
+ * to it.
+ *
+ * Cluster and node identifiers are globally unique
+ * (UUID, universally unique identifiers). In addition
+ * to a long UUID, which is stored in _cluster system
+ * space for each node, a short integer id is used for
+ * pervasive node identification in a replication stream,
+ * a snapshot, or internal data structures.
+ * The mapping between 16-byte node globally unique id and
+ * 4 byte cluster local id is stored in _cluster table. When
+ * a node joins the cluster, it sends its globally unique
+ * identifier to one of the masters, and gets its cluster
+ * local identifier as part of the reply to the JOIN request
+ * (in fact, it gets it as a REPLACE request in _cluster
+ * system space along with the rest of the replication
+ * stream).
+ *
+ * Cluster state on each node is represented by a table
+ * like below:
+ *
+ *   ----------------------------------
+ *  | node id          | confirmed lsn |
+ *   ----------------------------------
+ *  | 1                |  1258         | <-- changes of the local node
+ *   ----------------------------------
+ *  | 2                |  1292         | <-- changes received from
+ *   ----------------------------------       a remote node
+ */
+
+/** Cluster-local node identifier. */
+typedef uint32_t cnode_id_t;
+
+static inline bool
+cnode_id_is_reserved(cnode_id_t id)
+{
+	return id == 0;
+}
+
+/**
+ * Bootstrap a new cluster consisting of this node by
+ * assigning it a new globally unique cluster id. Used
+ * during bootstrapping in an empty data directory when no
+ * existing cluster for joining has been provided in the
+ * database configuration.
+ */
+void
+cluster_set_id(const tt_uuid *uu);
+
+/**
+ * Register the universally unique identifier of a remote node and
+ * a matching cluster-local identifier in the  cluster registry.
+ * Called when a remote master joins the cluster.
+ *
+ * The node is added to the cluster lsn table with LSN 0.
+ */
+void
+cluster_add_node(const tt_uuid *node_uu, cnode_id_t id);
+
+#endif
diff --git a/src/box/txn.cc b/src/box/txn.cc
index a99dea7a1a..91f43ab93b 100644
--- a/src/box/txn.cc
+++ b/src/box/txn.cc
@@ -30,7 +30,8 @@
 #include "tuple.h"
 #include "space.h"
 #include <tarantool.h>
-#include <recovery.h>
+#include "cluster.h"
+#include "recovery.h"
 #include <fiber.h>
 #include "request.h" /* for request_name */
 
diff --git a/src/errcode.h b/src/errcode.h
index 74ff5c1e62..1130318193 100644
--- a/src/errcode.h
+++ b/src/errcode.h
@@ -113,8 +113,9 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/* 61 */_(ER_LOCAL_NODE_IS_NOT_ACTIVE,	2, "Local node is not active") \
 	/* 62 */_(ER_UNKNOWN_NODE,		2, "Unknown node %u") \
 	/* 63 */_(ER_INVALID_CLUSTER,		2, "Invalid cluster id") \
-
-
+	/* 64 */_(ER_INVALID_UUID,		2, "Invalid UUID: %s") \
+	/* 65 */_(ER_CLUSTER_ID_IS_RO,		2, "Can't reset cluster id: it is already assigned") \
+	/* 66 */_(ER_NODE_ID_IS_RO,		2, "Can't reset node id") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/iobuf.cc b/src/iobuf.cc
index f47e33dc5e..d4837d673a 100644
--- a/src/iobuf.cc
+++ b/src/iobuf.cc
@@ -320,7 +320,7 @@ iobuf_flush(struct iobuf *iobuf, struct ev_io *coio)
 	ssize_t total = coio_writev(coio, iobuf->out.iov,
 				    obuf_iovcnt(&iobuf->out),
 				    obuf_size(&iobuf->out));
-	iobuf_gc(iobuf);
+	iobuf_reset(iobuf);
 	/*
 	 * If there is some residue in the input buffer, move it
 	 * but only in case if we don't have iobuf_readahead
@@ -335,7 +335,7 @@ iobuf_flush(struct iobuf *iobuf, struct ev_io *coio)
 }
 
 void
-iobuf_gc(struct iobuf *iobuf)
+iobuf_reset(struct iobuf *iobuf)
 {
 	/*
 	 * If we happen to have fully processed the input,
diff --git a/src/iobuf.h b/src/iobuf.h
index 0bd43b3cd2..25d97babc8 100644
--- a/src/iobuf.h
+++ b/src/iobuf.h
@@ -226,7 +226,7 @@ iobuf_flush(struct iobuf *iobuf, struct ev_io *coio);
  * Is automatically called by iobuf_flush().
  */
 void
-iobuf_gc(struct iobuf *iobuf);
+iobuf_reset(struct iobuf *iobuf);
 
 /** Return true if there is no input and no output. */
 static inline bool
diff --git a/src/iproto.cc b/src/iproto.cc
index 158ca4e32a..7f1be78104 100644
--- a/src/iproto.cc
+++ b/src/iproto.cc
@@ -615,7 +615,7 @@ iproto_flush(struct iobuf *iobuf, int fd, struct obuf_svp *svp)
 
 	if (nwr > 0) {
 		if (svp->size + nwr == obuf_size(&iobuf->out)) {
-			iobuf_gc(iobuf);
+			iobuf_reset(iobuf);
 			*svp = obuf_create_svp(&iobuf->out);
 			return 0;
 		}
diff --git a/src/log_io.cc b/src/log_io.cc
index e48408d8d7..eb291c7b49 100644
--- a/src/log_io.cc
+++ b/src/log_io.cc
@@ -143,8 +143,9 @@ log_dir_add_to_index(struct log_dir *dir, int64_t lsnsum)
 	/*
 	 * Open xlog to find SETLSN
 	 */
-	uuid_t uuid;
-	struct log_io *wal = log_io_open_for_read(dir, lsnsum, uuid, INPROGRESS);
+	tt_uuid uuid;
+	struct log_io *wal = log_io_open_for_read(dir, lsnsum, &uuid,
+						  INPROGRESS);
 	if (wal == NULL)
 		return -1;
 	auto log_guard = make_scoped_guard([&]{
@@ -870,13 +871,11 @@ log_io_sync(struct log_io *l)
 #define NODE_UUID_KEY "Node"
 
 static int
-log_io_write_meta(struct log_io *l, const uuid_t node_uuid)
+log_io_write_meta(struct log_io *l, const tt_uuid *node_uuid)
 {
-	char uuid_str[UUID_STR_LEN + 1];
-	uuid_unparse(node_uuid, uuid_str);
-
 	if (fprintf(l->f, "%s%s", l->dir->filetype, v12) < 0 ||
-	    fprintf(l->f, NODE_UUID_KEY ": %s\n\n", uuid_str) < 0) {
+	    fprintf(l->f, NODE_UUID_KEY ": %s\n\n",
+		    tt_uuid_str(node_uuid)) < 0) {
 		return -1;
 	}
 
@@ -892,7 +891,8 @@ log_io_write_meta(struct log_io *l, const uuid_t node_uuid)
  * @return 0 if success, -1 on error.
  */
 static int
-log_io_verify_meta(struct log_io *l, uuid_t node_uuid, const char **errmsg)
+log_io_verify_meta(struct log_io *l, tt_uuid *node_uuid,
+		   const char **errmsg)
 {
 	char filetype[32], version[32], buf[256];
 	struct log_dir *dir = l->dir;
@@ -934,7 +934,7 @@ log_io_verify_meta(struct log_io *l, uuid_t node_uuid, const char **errmsg)
 
 		if (strcmp(key, NODE_UUID_KEY) == 0) {
 			if ((end - val) != UUID_STR_LEN ||
-			    uuid_parse(val, node_uuid) != 0) {
+			    tt_uuid_from_string(val, node_uuid) != 0) {
 				*errmsg = "can't parse node uuid";
 				goto error;
 			}
@@ -949,7 +949,7 @@ log_io_verify_meta(struct log_io *l, uuid_t node_uuid, const char **errmsg)
 
 struct log_io *
 log_io_open(struct log_dir *dir, enum log_mode mode, const char *filename,
-	    uuid_t node_uuid, enum log_suffix suffix, FILE *file)
+	    tt_uuid *node_uuid, enum log_suffix suffix, FILE *file)
 {
 	struct log_io *l = NULL;
 	int save_errno;
@@ -995,8 +995,8 @@ log_io_open(struct log_dir *dir, enum log_mode mode, const char *filename,
 }
 
 struct log_io *
-log_io_open_for_read(struct log_dir *dir, int64_t lsnsum, uuid_t node_uuid,
-		     enum log_suffix suffix)
+log_io_open_for_read(struct log_dir *dir, int64_t lsnsum,
+		     tt_uuid *node_uuid, enum log_suffix suffix)
 {
 	const char *filename = format_filename(dir, lsnsum, suffix);
 	FILE *f = fopen(filename, "r");
@@ -1012,7 +1012,7 @@ log_io_open_for_read(struct log_dir *dir, int64_t lsnsum, uuid_t node_uuid,
  * and sets errno.
  */
 struct log_io *
-log_io_open_for_write(struct log_dir *dir, int64_t lsn, uuid_t node_uuid,
+log_io_open_for_write(struct log_dir *dir, int64_t lsn, tt_uuid *node_uuid,
 		      enum log_suffix suffix)
 {
 	char *filename;
diff --git a/src/log_io.h b/src/log_io.h
index b5196b1230..adc783c725 100644
--- a/src/log_io.h
+++ b/src/log_io.h
@@ -35,7 +35,9 @@
 #include "trivia/util.h"
 #include "third_party/tarantool_ev.h"
 #include "iproto_constants.h"
-#include "uuid/uuid.h"
+#include "tt_uuid.h"
+
+extern const uint32_t xlog_format;
 
 enum log_mode {
 	LOG_READ,
@@ -151,14 +153,14 @@ struct log_io {
 };
 
 struct log_io *
-log_io_open_for_read(struct log_dir *dir, int64_t lsn, uuid_t node_uuid,
+log_io_open_for_read(struct log_dir *dir, int64_t lsn, tt_uuid *node_uuid,
 		     enum log_suffix suffix);
 struct log_io *
-log_io_open_for_write(struct log_dir *dir, int64_t lsn, uuid_t node_uuid,
-		      enum log_suffix suffix);
+log_io_open_for_write(struct log_dir *dir, int64_t lsn,
+		      tt_uuid *node_uuid, enum log_suffix suffix);
 struct log_io *
 log_io_open(struct log_dir *dir, enum log_mode mode, const char *filename,
-	    uuid_t node_uuid, enum log_suffix suffix, FILE *file);
+	    tt_uuid *node_uuid, enum log_suffix suffix, FILE *file);
 int
 log_io_sync(struct log_io *l);
 int
diff --git a/src/lua/info.cc b/src/lua/info.cc
index 0b8e4d0464..4c523db4ad 100644
--- a/src/lua/info.cc
+++ b/src/lua/info.cc
@@ -65,7 +65,7 @@ lbox_info_recovery_last_update_tstamp(struct lua_State *L)
 static int
 lbox_info_node(struct lua_State *L)
 {
-	lua_pushlstring(L, uuid_str(recovery_state->node_uuid), UUID_STR_LEN);
+	lua_pushlstring(L, tt_uuid_str(&recovery_state->node_uuid), UUID_STR_LEN);
 	return 1;
 }
 
@@ -77,7 +77,7 @@ lbox_info_cluster(struct lua_State *L)
 	uint32_t k;
 	mh_foreach(recovery_state->cluster, k) {
 		struct node *node = *mh_cluster_node(recovery_state->cluster,k);
-		lua_pushlstring(L, uuid_str(node->uuid), UUID_STR_LEN);
+		lua_pushlstring(L, tt_uuid_str(&node->uuid), UUID_STR_LEN);
 		luaL_pushnumber64(L, node->confirmed_lsn);
 		lua_settable(L, -3);
 	}
diff --git a/src/recovery.cc b/src/recovery.cc
index 6752ab12d1..4a06d01cc7 100644
--- a/src/recovery.cc
+++ b/src/recovery.cc
@@ -45,6 +45,7 @@
 #include "iproto_constants.h"
 #include "crc32.h"
 #include "scoped_guard.h"
+#include "box/cluster.h"
 
 /*
  * Recovery subsystem
@@ -195,11 +196,10 @@ confirm_lsn(struct node *node, int64_t lsn, bool is_commit)
 	if (node->confirmed_lsn < lsn) {
 		if (is_commit) {
 			if (node->confirmed_lsn + 1 != lsn) {
-				char uuid_str[UUID_STR_LEN + 1];
-				uuid_unparse(node->uuid, uuid_str);
-				say_warn("non consecutive LSN for %s "
+				say_warn("non consecutive LSN for node %u (%s) "
 					 "confirmed: %jd, new: %jd, diff: %jd",
-					 uuid_str,
+					 (unsigned) node->id,
+					 tt_uuid_str(&node->uuid),
 					 (intmax_t) node->confirmed_lsn,
 					 (intmax_t) lsn,
 					 (intmax_t) (lsn - node->confirmed_lsn));
@@ -207,15 +207,13 @@ confirm_lsn(struct node *node, int64_t lsn, bool is_commit)
 			node->confirmed_lsn = lsn;
 		 }
 	} else {
-		 /*
+		/*
 		 * There can be holes in
 		 * confirmed_lsn, in case of disk write failure, but
 		 * wal_writer never confirms LSNs out order.
 		 */
-		char uuid_str[UUID_STR_LEN + 1];
-		uuid_unparse(node->uuid, uuid_str);
 		panic("LSN for %s is used twice or COMMIT order is broken: "
-		      "confirmed: %jd, new: %jd", uuid_str,
+		      "confirmed: %jd, new: %jd", tt_uuid_str(&node->uuid),
 		      (intmax_t) node->confirmed_lsn, (intmax_t) lsn);
 	}
 }
@@ -284,7 +282,7 @@ recovery_init(const char *snap_dirname, const char *wal_dirname,
 	if (node == NULL)
 		panic("cannot allocate struct node");
 	node->id = 0;
-	assert(uuid_is_null(node->uuid));
+	assert(tt_uuid_is_nil(&node->uuid));
 	uint32_t k = mh_cluster_put(r->cluster,
 		(const struct node **) &node, NULL, NULL);
 	if (k == mh_end(r->cluster))
@@ -426,55 +424,19 @@ recovery_process(struct recovery_state *r, struct iproto_packet *packet)
 	return r->row_handler(r->row_handler_param, packet);
 }
 
-void
-recovery_confirm_node(struct recovery_state *r, uuid_t node_uuid,
-		      uint32_t node_id)
-{
-	/* Node-Id must be unique */
-	assert(mh_cluster_find(r->cluster, node_id, NULL) == mh_end(r->cluster));
-	assert(!uuid_is_null(node_uuid));
-	assert(node_id != 0);
-
-	/* Add node */
-	struct node *node = (struct node *) calloc(1, sizeof(*node));
-	if (node == NULL) {
-		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(*node),
-			  "recovery", "r->cluster");
-	}
-	node->id = node_id;
-	memcpy(node->uuid, node_uuid, sizeof(uuid_t));
-	uint32_t k = mh_cluster_put(recovery_state->cluster,
-		(const struct node **) &node, NULL, NULL);
-	if (k == mh_end(recovery_state->cluster)) {
-		free(node);
-		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(*node),
-			  "recovery", "r->cluster");
-	}
-
-	say_debug("confirm node: {uuid = %s, id = %u}",
-		  uuid_str(node_uuid), node_id);
-
-	if (uuid_compare(r->node_uuid, node_uuid) == 0) {
-		/* Confirm Local Node */
-		say_info("synchronized with cluster");
-		assert(r->local_node == NULL || r->local_node->id == 0);
-		r->local_node = node;
-	}
-}
-
 void
 cluster_bootstrap(struct recovery_state *r)
 {
 	/* Generate Node-UUID */
-	uuid_generate(r->node_uuid);
+	tt_uuid_create(&r->node_uuid);
 
 	/* Recover from bootstrap.snap */
 	say_info("initializing cluster");
 	FILE *f = fmemopen((void *) &bootstrap_bin,
 			   sizeof(bootstrap_bin), "r");
-	uuid_t bootstrap_uuid; /* ignored */
+	tt_uuid bootstrap_uuid; /* ignored */
 	struct log_io *snap = log_io_open(&r->snap_dir, LOG_READ,
-		"bootstrap.snap", bootstrap_uuid, NONE, f);
+		"bootstrap.snap", &bootstrap_uuid, NONE, f);
 	assert(snap != NULL);
 	auto snap_guard = make_scoped_guard([&]{
 		log_io_close(&snap);
@@ -486,10 +448,10 @@ cluster_bootstrap(struct recovery_state *r)
 		panic("failed to bootstrap data directory");
 
 	/* Initialize local node */
-	r->join_handler(r->node_uuid);
+	r->join_handler(&r->node_uuid);
 	assert(r->local_node != NULL);
 	assert(r->local_node->id == 1);
-	assert(uuid_compare(r->local_node->uuid, r->node_uuid) == 0);
+	assert(tt_uuid_cmp(&r->local_node->uuid, &r->node_uuid) == 0);
 
 	say_info("done");
 }
@@ -517,13 +479,13 @@ recover_snap(struct recovery_state *r)
 		say_error("can't find snapshot");
 		goto error;
 	}
-	snap = log_io_open_for_read(&r->snap_dir, lsn, r->node_uuid, NONE);
+	snap = log_io_open_for_read(&r->snap_dir, lsn, &r->node_uuid, NONE);
 	if (snap == NULL) {
 		say_error("can't find/open snapshot");
 		goto error;
 	}
 
-	if (uuid_is_null(r->node_uuid)) {
+	if (tt_uuid_is_nil(&r->node_uuid)) {
 		say_error("can't find node uuid in snapshot");
 		goto error;
 	}
@@ -646,7 +608,7 @@ recover_remaining_wals(struct recovery_state *r)
 				goto find_next_wal;
 		}
 		next_wal = log_io_open(&r->wal_dir, LOG_READ, filename,
-				       r->node_uuid, suffix, f);
+				       &r->node_uuid, suffix, f);
 		/*
 		 * When doing final recovery, and dealing with the
 		 * last file, try opening .<ext>.inprogress.
@@ -1227,7 +1189,7 @@ wal_opt_rotate(struct log_io **wal, struct fio_batch *batch,
 	if (l == NULL) {
 		/* Open WAL with '.inprogress' suffix. */
 		int64_t lsnsum = mh_cluster_current_sum(cluster);
-		l = log_io_open_for_write(&r->wal_dir, lsnsum, r->node_uuid,
+		l = log_io_open_for_write(&r->wal_dir, lsnsum, &r->node_uuid,
 					  INPROGRESS);
 		if (l != NULL) {
 			if (wal_write_setlsn(l, batch, cluster) != 0)
@@ -1516,7 +1478,7 @@ snapshot_save(struct recovery_state *r)
 	assert(r->snapshot_handler != NULL);
 	struct log_io *snap;
 	int64_t lsnsum = mh_cluster_current_sum(r->cluster);
-	snap = log_io_open_for_write(&r->snap_dir, lsnsum, r->node_uuid,
+	snap = log_io_open_for_write(&r->snap_dir, lsnsum, &r->node_uuid,
 				     INPROGRESS);
 	if (snap == NULL)
 		panic_status(errno, "Failed to save snapshot: failed to open file in write mode.");
diff --git a/src/recovery.h b/src/recovery.h
index 996a84331d..2b8a7e9884 100644
--- a/src/recovery.h
+++ b/src/recovery.h
@@ -33,7 +33,7 @@
 #include "trivia/util.h"
 #include "third_party/tarantool_ev.h"
 #include "log_io.h"
-#include <uuid/uuid.h>
+#include "tt_uuid.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -44,7 +44,7 @@ struct tbuf;
 
 typedef void (row_handler)(void *, struct iproto_packet *packet);
 typedef void (snapshot_handler)(struct log_io *);
-typedef void (join_handler)(uuid_t node_uuid);
+typedef void (join_handler)(const tt_uuid *node_uuid);
 
 /** A "condition variable" that allows fibers to wait when a given
  * LSN makes it to disk.
@@ -64,7 +64,7 @@ extern const char *wal_mode_STRS[];
  */
 struct node {
 	uint32_t id;
-	uuid_t uuid;
+	tt_uuid uuid;
 	int64_t current_lsn;
 	int64_t confirmed_lsn;
 };
@@ -112,8 +112,8 @@ struct recovery_state {
 	int rows_per_wal;
 	double wal_fsync_delay;
 	enum wal_mode wal_mode;
-	uuid_t node_uuid;
-	uuid_t cluster_uuid;
+	tt_uuid node_uuid;
+	tt_uuid cluster_uuid;
 
 	bool finalize;
 };
@@ -146,8 +146,6 @@ int wal_write(struct recovery_state *r, struct iproto_packet *packet);
 
 void recovery_setup_panic(struct recovery_state *r, bool on_snap_error, bool on_wal_error);
 void recovery_process(struct recovery_state *r, struct iproto_packet *packet);
-void recovery_confirm_node(struct recovery_state *r, uuid_t node_uuid,
-			   uint32_t node_id);
 void recovery_fix_lsn(struct recovery_state *r, bool master_bootstrap);
 
 struct fio_batch;
diff --git a/src/replica.cc b/src/replica.cc
index 94f25f7978..7ae0355eec 100644
--- a/src/replica.cc
+++ b/src/replica.cc
@@ -28,7 +28,6 @@
  */
 #include "recovery.h"
 #include "tarantool.h"
-
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -120,7 +119,7 @@ replica_bootstrap(struct recovery_state *r, const char *replication_source)
 	say_info("bootstrapping replica");
 
 	/* Generate Node-UUID */
-	uuid_generate(r->node_uuid);
+	tt_uuid_create(&r->node_uuid);
 
 	char ip_addr[32];
 	char greeting[IPROTO_GREETING_SIZE];
@@ -155,8 +154,8 @@ replica_bootstrap(struct recovery_state *r, const char *replication_source)
 	char *data = buf;
 	data = mp_encode_map(data, 1);
 	data = mp_encode_uint(data, IPROTO_NODE_UUID);
-	data = mp_encode_str(data, (const char *) recovery_state->node_uuid,
-			     sizeof(uuid_t));
+	data = mp_encode_str(data, (const char *) &recovery_state->node_uuid,
+			     sizeof(tt_uuid));
 
 	assert(data <= buf + sizeof(buf));
 	packet.body[0].iov_base = buf;
@@ -213,9 +212,11 @@ remote_connect(struct recovery_state *r, struct ev_io *coio,const char **err)
 	char *data = buf;
 	data = mp_encode_map(data, 3);
 	data = mp_encode_uint(data, IPROTO_CLUSTER_UUID);
-	data = mp_encode_str(data, (const char *) r->cluster_uuid, sizeof(uuid_t));
+	data = mp_encode_str(data, (const char *) &r->cluster_uuid,
+			     sizeof(tt_uuid));
 	data = mp_encode_uint(data, IPROTO_NODE_UUID);
-	data = mp_encode_str(data, (const char *) r->node_uuid, sizeof(uuid_t));
+	data = mp_encode_str(data, (const char *) &r->node_uuid,
+			     sizeof(tt_uuid));
 	data = mp_encode_uint(data, IPROTO_LSNMAP);
 	data = mp_encode_map(data, cluster_size);
 	uint32_t k;
@@ -274,7 +275,7 @@ pull_from_remote(va_list ap)
 
 			recovery_process(r, &packet);
 
-			iobuf_gc(iobuf);
+			iobuf_reset(iobuf);
 			fiber_gc();
 		} catch (FiberCancelException *e) {
 			title("replica", "%s/%s", r->remote->source, "failed");
diff --git a/src/replication.cc b/src/replication.cc
index 3149a1269a..5f72b453a4 100644
--- a/src/replication.cc
+++ b/src/replication.cc
@@ -227,8 +227,7 @@ replication_join(int fd, struct iproto_packet *packet)
 	if (mp_check(&d, end) != 0 || mp_typeof(*data) != MP_MAP)
 		tnt_raise(ClientError, ER_INVALID_MSGPACK, "JOIN body");
 
-	uuid_t node_uuid;
-	memset(node_uuid, 0, sizeof(node_uuid));
+	tt_uuid node_uuid = uuid_nil;
 	d = data;
 	uint32_t map_size = mp_decode_map(&d);
 	for (uint32_t i = 0; i < map_size; i++) {
@@ -240,26 +239,24 @@ replication_join(int fd, struct iproto_packet *packet)
 		uint8_t key = mp_decode_uint(&d);
 		if (key == IPROTO_NODE_UUID) {
 			if (mp_typeof(*d) != MP_STR ||
-			    mp_decode_strl(&d) != sizeof(uuid_t)) {
+			    mp_decode_strl(&d) != sizeof(tt_uuid)) {
 				tnt_raise(ClientError, ER_INVALID_MSGPACK,
 					  "invalid Node-UUID");
 			}
-			memcpy(node_uuid, d, sizeof(uuid_t));
-			d += sizeof(uuid_t);
+			tt_uuid_set(&node_uuid, d);
+			d += sizeof(node_uuid);
 		} else {
 			mp_next(&d); /* value */
 		}
 	}
 
-	if (uuid_is_null(node_uuid)) {
+	if (tt_uuid_is_nil(&node_uuid)) {
 		tnt_raise(ClientError, ER_INVALID_MSGPACK,
 			  "Can't find Node-UUID in JOIN request");
 	}
 
 	/* Notify box about new cluster node */
-	char uuid_str[UUID_STR_LEN + 1];
-	uuid_unparse(node_uuid, uuid_str);
-	recovery_state->join_handler(node_uuid);
+	recovery_state->join_handler(&node_uuid);
 
 	struct replication_request *request = (struct replication_request *)
 			malloc(sizeof(*request));
@@ -283,17 +280,14 @@ replication_subscribe(int fd, struct iproto_packet *packet)
 {
 	assert(packet->code == IPROTO_SUBSCRIBE);
 	if (packet->bodycnt == 0)
-		tnt_raise(ClientError, ER_INVALID_MSGPACK, "SUBSCRIBE body");
+		tnt_raise(ClientError, ER_INVALID_MSGPACK, "subscribe body");
 	assert(packet->bodycnt == 1);
 	const char *data = (const char *) packet->body[0].iov_base;
 	const char *end = data + packet->body[0].iov_len;
 	const char *d = data;
 	if (mp_check(&d, end) != 0 || mp_typeof(*data) != MP_MAP)
-		tnt_raise(ClientError, ER_INVALID_MSGPACK, "SUBSCRIBE body");
-	uuid_t cluster_uuid;
-	memset(cluster_uuid, 0, sizeof(cluster_uuid));
-	uuid_t node_uuid;
-	memset(node_uuid, 0, sizeof(node_uuid));
+		tnt_raise(ClientError, ER_INVALID_MSGPACK, "subscribe body");
+	tt_uuid cluster_uuid = uuid_nil, node_uuid = uuid_nil;
 
 	const char *lsnmap = NULL;
 	d = data;
@@ -308,20 +302,20 @@ replication_subscribe(int fd, struct iproto_packet *packet)
 		switch (key) {
 		case IPROTO_CLUSTER_UUID:
 			if (mp_typeof(*d) != MP_STR ||
-			    mp_decode_strl(&d) != sizeof(uuid_t)) {
+			    mp_decode_strl(&d) != sizeof(cluster_uuid)) {
 				tnt_raise(ClientError, ER_INVALID_MSGPACK,
 					  "invalid Cluster-UUID");
 			}
-			memcpy(cluster_uuid, d, sizeof(cluster_uuid));
+			tt_uuid_set(&cluster_uuid, d);
 			d += sizeof(cluster_uuid);
 			break;
 		case IPROTO_NODE_UUID:
 			if (mp_typeof(*d) != MP_STR ||
-			    mp_decode_strl(&d) != sizeof(uuid_t)) {
+			    mp_decode_strl(&d) != sizeof(node_uuid)) {
 				tnt_raise(ClientError, ER_INVALID_MSGPACK,
 					  "invalid Node-UUID");
 			}
-			memcpy(node_uuid, d, sizeof(node_uuid));
+			tt_uuid_set(&node_uuid, d);
 			d += sizeof(node_uuid);
 			break;
 		case IPROTO_LSNMAP:
@@ -338,15 +332,16 @@ replication_subscribe(int fd, struct iproto_packet *packet)
 	}
 
 	/* Check Cluster-UUID */
-	if (uuid_compare(cluster_uuid, recovery_state->cluster_uuid) != 0)
-		tnt_raise(ClientError, ER_INVALID_CLUSTER);
-
+	if (tt_uuid_cmp(&cluster_uuid, &recovery_state->cluster_uuid) != 0) {
+		tnt_raise(ClientError, ER_INVALID_MSGPACK,
+			  "Unknown Cluster-UUID");
+	}
 	/* Check Node-UUID */
 	struct node *node = NULL;
 	uint32_t k;
 	mh_foreach(recovery_state->cluster, k) {
 		struct node *n = *mh_cluster_node(recovery_state->cluster, k);
-		if (uuid_compare(n->uuid, node_uuid) == 0) {
+		if (tt_uuid_cmp(&n->uuid, &node_uuid) == 0) {
 			node = n;
 			break;
 		}
diff --git a/src/trivia/util.h b/src/trivia/util.h
index 6584b8b295..cb071e612a 100644
--- a/src/trivia/util.h
+++ b/src/trivia/util.h
@@ -34,7 +34,6 @@
 #include <unistd.h>
 #include <inttypes.h>
 #include <assert.h>
-#include "uuid/uuid.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -201,13 +200,6 @@ FILE *
 fmemopen(void *buf, size_t size, const char *mode);
 #endif /* HAVE_FMEMOPEN */
 
-enum {
-	UUID_STR_LEN = 36 /** Length of encoded UUID */
-};
-
-char *
-uuid_str(uuid_t uuid);
-
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/tt_uuid.c b/src/tt_uuid.c
new file mode 100644
index 0000000000..502809c672
--- /dev/null
+++ b/src/tt_uuid.c
@@ -0,0 +1,40 @@
+/*
+ * 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 "tt_uuid.h"
+/* Zeroed by the linker. */
+tt_uuid uuid_nil;
+
+char *
+tt_uuid_str(const struct tt_uuid *uu)
+{
+	static __thread char buf[UUID_STR_LEN + 1];
+	tt_uuid_to_string(uu, buf);
+	return buf;
+}
+
diff --git a/src/tt_uuid.h b/src/tt_uuid.h
new file mode 100644
index 0000000000..2e934ebf25
--- /dev/null
+++ b/src/tt_uuid.h
@@ -0,0 +1,90 @@
+#ifndef TARANTOOL_UUID_H_INCLUDED
+#define TARANTOOL_UUID_H_INCLUDED
+/*
+ * 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 <uuid/uuid.h>
+#include <string.h>
+#include <stdbool.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum { UUID_STR_LEN = 36 };
+
+typedef struct tt_uuid {
+	uuid_t id;
+} tt_uuid;
+
+extern tt_uuid uuid_nil;
+
+static inline void
+tt_uuid_create(tt_uuid *uu)
+{
+	uuid_generate(uu->id);
+}
+
+static inline int
+tt_uuid_from_string(const char *in, tt_uuid *uu)
+{
+	return uuid_parse((char *) in, uu->id);
+}
+
+static inline void
+tt_uuid_to_string(const tt_uuid *uu, char *out)
+{
+	uuid_unparse(uu->id, out);
+}
+
+static inline void
+tt_uuid_set(tt_uuid *uu, const void *data)
+{
+	memcpy(uu->id, data, sizeof(uu->id));
+}
+
+char *
+tt_uuid_str(const struct tt_uuid *uu);
+
+static inline bool
+tt_uuid_cmp(const tt_uuid *lhs, const tt_uuid *rhs)
+{
+	return uuid_compare(lhs->id, rhs->id);
+}
+
+static inline bool
+tt_uuid_is_nil(const tt_uuid *uu)
+{
+	return uuid_is_null(uu->id);
+}
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif
+
+#endif /* TARANTOOL_UUID_H_INCLUDED */
diff --git a/src/util.cc b/src/util.cc
index 2651be13a9..1cf563abf2 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -525,11 +525,3 @@ addr2symbol(void *addr)
 }
 
 #endif /* HAVE_BFD */
-
-char *
-uuid_str(uuid_t uuid)
-{
-	static char buf[UUID_STR_LEN + 1];
-	uuid_unparse(uuid, buf);
-	return buf;
-}
diff --git a/test/box/misc.result b/test/box/misc.result
index a75da65f3d..eb279fe24f 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -193,70 +193,73 @@ end;
 ...
 t;
 ---
-- - 'box.error.ER_FUNCTION_ACCESS_DENIED : 53'
-  - 'box.error.ER_CREATE_FUNCTION : 50'
-  - 'box.error.ER_NO_SUCH_INDEX : 35'
-  - 'box.error.ER_TUPLE_FOUND : 3'
-  - 'box.error.ER_UNKNOWN_NODE : 62'
-  - 'box.error.ER_CREATE_SPACE : 9'
-  - 'box.error.ER_LOCAL_NODE_IS_NOT_ACTIVE : 61'
+- - 'box.error.ER_CREATE_FUNCTION : 50'
   - 'box.error.ER_PROC_RET : 21'
   - 'box.error.ER_TUPLE_FORMAT_LIMIT : 16'
-  - 'box.error.ER_FIELD_TYPE : 23'
-  - 'box.error.ER_CFG : 59'
-  - 'box.error.ER_UNKNOWN_SCHEMA_OBJECT : 49'
-  - 'box.error.ER_OK : 0'
-  - 'box.error.ER_NO_SUCH_ENGINE : 57'
+  - 'box.error.ER_FUNCTION_MAX : 54'
   - 'box.error.ER_TUPLE_NOT_FOUND : 4'
-  - 'box.error.ER_INDEX_ARITY : 39'
-  - 'box.error.ER_WAL_IO : 40'
-  - 'box.error.ER_USER_MAX : 56'
-  - 'box.error.ER_NO_SUCH_FUNCTION : 51'
-  - 'box.error.ER_INJECTION : 8'
-  - 'box.error.ER_DROP_PRIMARY_KEY : 17'
-  - 'box.error.ER_INDEX_TYPE : 13'
+  - 'box.error.ER_PASSWORD_MISMATCH : 47'
+  - 'box.error.ER_LAST_DROP : 15'
   - 'box.error.ER_ARG_TYPE : 26'
-  - 'box.error.ER_FUNCTION_MAX : 54'
   - 'box.error.ER_INVALID_CLUSTER : 63'
-  - 'box.error.ER_SPACE_ARITY : 38'
   - 'box.error.ER_INVALID_MSGPACK : 20'
-  - 'box.error.ER_SPACE_ACCESS_DENIED : 55'
-  - 'box.error.ER_KEY_PART_COUNT : 31'
   - 'box.error.ER_RELOAD_CFG : 58'
   - 'box.error.ER_USER_EXISTS : 46'
   - 'box.error.ER_MEMORY_ISSUE : 2'
   - 'box.error.ER_ILLEGAL_PARAMS : 1'
   - 'box.error.ER_KEY_FIELD_TYPE : 18'
   - 'box.error.ER_NONMASTER : 6'
-  - 'box.error.ER_UNKNOWN_REQUEST_TYPE : 48'
-  - 'box.error.ER_FIELD_TYPE_MISMATCH : 24'
-  - 'box.error.ER_MODIFY_INDEX : 14'
-  - 'box.error.ER_PASSWORD_MISMATCH : 47'
-  - 'box.error.ER_EXACT_MATCH : 19'
   - 'box.error.ER_NO_SUCH_USER : 45'
-  - 'box.error.ER_SECONDARY : 7'
-  - 'box.error.ER_FUNCTION_EXISTS : 52'
+  - 'box.error.ER_EXACT_MATCH : 19'
   - 'box.error.ER_CREATE_USER : 43'
-  - 'box.error.ER_ACCESS_DENIED : 42'
-  - 'box.error.ER_LAST_DROP : 15'
-  - 'box.error.ER_UPDATE_FIELD : 29'
+  - 'box.error.ER_FUNCTION_EXISTS : 52'
+  - 'box.error.ER_NO_SUCH_FUNCTION : 51'
   - 'box.error.ER_FIBER_STACK : 30'
-  - 'box.error.ER_UNKNOWN_UPDATE_OP : 28'
-  - 'box.error.ER_DROP_USER : 44'
-  - 'box.error.ER_UNSUPPORTED : 5'
-  - 'box.error.ER_NO_SUCH_FIELD : 37'
+  - 'box.error.ER_FUNCTION_ACCESS_DENIED : 53'
+  - 'box.error.ER_CFG : 59'
   - 'box.error.ER_TUPLE_NOT_ARRAY : 22'
-  - 'box.error.ER_NO_SUCH_SPACE : 36'
+  - 'box.error.ER_CLUSTER_ID_IS_RO : 65'
   - 'box.error.ER_MORE_THAN_ONE_TUPLE : 41'
-  - 'box.error.ER_ALTER_SPACE : 12'
-  - 'box.error.ER_NO_SUCH_PROC : 33'
+  - 'box.error.ER_NO_SUCH_SPACE : 36'
+  - 'box.error.ER_NO_SUCH_INDEX : 35'
+  - 'box.error.ER_TUPLE_FOUND : 3'
+  - 'box.error.ER_CREATE_SPACE : 9'
+  - 'box.error.ER_FIELD_TYPE : 23'
+  - 'box.error.ER_OK : 0'
+  - 'box.error.ER_INDEX_ARITY : 39'
+  - 'box.error.ER_WAL_IO : 40'
+  - 'box.error.ER_INJECTION : 8'
+  - 'box.error.ER_NO_SUCH_ENGINE : 57'
+  - 'box.error.ER_INDEX_TYPE : 13'
+  - 'box.error.ER_UNKNOWN_SCHEMA_OBJECT : 49'
+  - 'box.error.ER_SPACE_ACCESS_DENIED : 55'
+  - 'box.error.ER_KEY_PART_COUNT : 31'
   - 'box.error.ER_SPACE_EXISTS : 10'
+  - 'box.error.ER_UNKNOWN_NODE : 62'
+  - 'box.error.ER_MODIFY_INDEX : 14'
+  - 'box.error.ER_SECONDARY : 7'
+  - 'box.error.ER_NODE_ID_IS_RO : 66'
+  - 'box.error.ER_INVALID_UUID : 64'
+  - 'box.error.ER_FIELD_TYPE_MISMATCH : 24'
+  - 'box.error.ER_SPLICE : 25'
+  - 'box.error.ER_TUPLE_IS_TOO_LONG : 27'
+  - 'box.error.ER_DROP_SPACE : 11'
+  - 'box.error.ER_SPACE_ARITY : 38'
+  - 'box.error.ER_LOCAL_NODE_IS_NOT_ACTIVE : 61'
+  - 'box.error.ER_UNSUPPORTED : 5'
+  - 'box.error.ER_ACCESS_DENIED : 42'
   - 'box.error.ER_PROC_LUA : 32'
+  - 'box.error.ER_UPDATE_FIELD : 29'
+  - 'box.error.ER_NO_SUCH_FIELD : 37'
+  - 'box.error.ER_ALTER_SPACE : 12'
+  - 'box.error.ER_DROP_USER : 44'
+  - 'box.error.ER_UNKNOWN_UPDATE_OP : 28'
+  - 'box.error.ER_NO_SUCH_PROC : 33'
   - 'box.error.ER_SOPHIA : 60'
   - 'box.error.ER_NO_SUCH_TRIGGER : 34'
-  - 'box.error.ER_TUPLE_IS_TOO_LONG : 27'
-  - 'box.error.ER_SPLICE : 25'
-  - 'box.error.ER_DROP_SPACE : 11'
+  - 'box.error.ER_UNKNOWN_REQUEST_TYPE : 48'
+  - 'box.error.ER_DROP_PRIMARY_KEY : 17'
+  - 'box.error.ER_USER_MAX : 56'
 ...
 --# setopt delimiter ''
 -- A test case for Bug#901674
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 072fe852d2..41ac98c10e 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -38,7 +38,7 @@ target_link_libraries(arena_mt.test small pthread)
 add_executable(pt_alloc.test pt_alloc.cc)
 target_link_libraries(pt_alloc.test small)
 add_executable(log_dir.test log_dir.cc test.c)
-target_link_libraries(log_dir.test  core small salad misc bitset msgpuck uuid
+target_link_libraries(log_dir.test  uuid core small salad misc bitset msgpuck
     ${LIBEV_LIBRARIES}
     ${LIBEIO_LIBRARIES}
     ${LIBCORO_LIBRARIES})
diff --git a/test/unit/log_dir.cc b/test/unit/log_dir.cc
index ef1f6d0fa7..0dcf422585 100644
--- a/test/unit/log_dir.cc
+++ b/test/unit/log_dir.cc
@@ -14,7 +14,7 @@ extern "C" {
 #define header() note("*** %s ***", __func__)
 #define footer() note("*** %s: done ***", __func__)
 
-uuid_t node_uuid;
+tt_uuid node_uuid;
 
 static void
 testset_create(struct log_dir *dir, int64_t *files, int files_n, int node_n)
@@ -53,7 +53,7 @@ testset_create(struct log_dir *dir, int64_t *files, int files_n, int node_n)
 		}
 
 		/* Write XLOG */
-		struct log_io *l = log_io_open_for_write(dir, lsnsum, node_uuid,
+		struct log_io *l = log_io_open_for_write(dir, lsnsum, &node_uuid,
 							 INPROGRESS);
 		int rc = wal_write_setlsn(l, batch, cluster);
 		assert(rc == 0);
@@ -234,7 +234,7 @@ main(int argc, char *argv[])
 	memory_init();
 	fiber_init();
 	crc32_init();
-	uuid_generate(node_uuid);
+	tt_uuid_create(&node_uuid);
 
 	plan(1);
 	test1();
-- 
GitLab