Repository: tarantool/tarantool
Pull request #10859: replication: add early and graceful make_bootstrap_leader()
Author: login: Totktonada
Labels: name: full-ci
description: Enables all tests for a pull request
Created: 2024-11-27T01:04:32Z | Merged: 2025-04-10T15:29:41Z

This patchset contains a few tightly coupled features.

## Early `box.ctl.make_bootstrap_leader()`

Now, it is allowed to call `box.ctl.make_bootstrap_leader()` before a first `box.cfg()` call to mark the instance as a bootstrap leader.

It works if `box.cfg({bootstrap_strategy = 'supervised'})` is set, ignored otherwise.

So, the following code is now possible:

```lua
box.ctl.make_bootstrap_leader()
box.cfg({
    bootstrap_strategy = 'supervised',
})
```

In earlier Tarantool version a user has to call `box.cfg()` in a separate fiber first and then call `box.ctl.make_bootstrap_leader()`.

Also, earlier Tarantool versions do not allow to bootstrap a database using the `'supervised'` bootstrap strategy if there are no upstreams configured (empty `box.cfg.replication`). It means that there were no way to bootstrap the database without iproto listening port configured (`box.cfg.listen`) except using another bootstrap strategy.

Now, the `'supervised'` bootstrap strategy let a user a full control over the replicaset bootstrapping process.

Fixes #10858

## `box.ctl.make_bootstrap_leader({graceful = true})`

The new option is added to the `box.ctl.make_bootstrap_leader()` call:

```lua
box.ctl.make_bootstrap_leader({graceful = true})
```

It checks a presence of a bootstrap leader across other peers first and, if there are no one, bootstraps the initial database on the current instance.

Strictly speaking:

* If there are no local database:
  * Start connecting to all the peers from `box.cfg.replication` (without waiting).
  * Wait for one of the three situations:
    * Found a peer with the bootstrap leader flag.
    * Connected to all the peers from `box.cfg.replication`, but there is no one with the bootstrap leader flag.
    * `box.cfg.replication_connect_timeout` is over.
  * If the bootstrap leader is found, go as a replica. Otherwise, assign itself as a bootstrap leader.
* If there is a local database, the call is no-op.

The motivating scenario is when we don't really know, whether the replicaset is already bootstrapped and want to bootstrap it from the given instance if not.

The new option doesn't give any protection in the situation, when the replicaset is not bootstrapped and two instances are simultaneously trying to gracefully bootstrap it.

Fixes #11272

## Allow to bootstrap in RO in the `supervised` bootstrap strategy

It allows to run `box.cfg({read_only = true})` on all the instances and wait for an external coordinator to choose one of the instances as the bootstrap leader:

```lua
box.ctl.make_bootstrap_leader({graceful = true})
box.cfg({read_only = false})
```

Needed for #10857

## Wait for bootstrap leader if there are no configured upstreams in the `supervised` bootstrap strategy

It allows to follow the same startup flow for multi-instance and single-instance replicasets:

```lua
box.cfg({
    bootstrap_strategy = 'supervised',
    replication = <...>, -- exists or not
})
```

Then via iproto or the admin console:

```lua
box.ctl.make_bootstrap_leader({graceful = true})
box.cfg({read_only = false})
```

Needed for #10857

Linked issue #10858: Allow to call box.ctl.make_bootstrap_leader() before box.cfg()
The `supervised` failover strategy doesn't allow to choose a bootstrap leader until `box.cfg()` is called. It is not very convenient: a user has to call `box.cfg()` in a fiber and has to place the `box.ctl.make_bootstrap_leader()` call afterwards. This way we need an additional code to couple the replicaset bootstrap leader determination logic with the `box.cfg()` call.

```yaml
# tarantool> box.ctl.make_bootstrap_leader()
---
- error: box.ctl.make_bootstrap_leader() does not support promoting this instance
    before box.cfg() is called
...
```

Next, we can't bootstrap a singleton replicaset (one that has just one instance) in this strategy unless it has iproto enabled (`box.cfg.listen`) and has itself in the upstreams (`box.cfg.replication`).

```yaml
# tarantool> box.cfg({bootstrap_strategy = 'supervised'})
<...>
<...> replication.cc:1513 E> ER_CFG: Incorrect value for option 'bootstrap_strategy': failed to connect to the bootstrap leader
<...> box.cc:5892 F> can't initialize storage: Incorrect value for option 'bootstrap_strategy': failed to connect to the bootstrap leader
```

It seems possible to overcome these contraints by using `auto` or `config` strategy to bootstrap a replicaset and switch to `supervised` afterwards, but it seems more convenient to handle these situations within the `supervised` strategy itself.

The proposal is to allow to call `box.ctl.make_boostrap_leader()` before `box.cfg()`, save the user's choice and bootstrap the replicaset on `box.cfg()` on the given instance. Later, when this or another instance calls `box.ctl.make_bootstrap_leader()`, discard the choice saved on the replicaset bootstrap.

Needed for #10857

Diff:
diff --git a/changelogs/unreleased/allow-bootstrap-in-ro-in-supervised-bs.md b/changelogs/unreleased/allow-bootstrap-in-ro-in-supervised-bs.md
new file mode 100644
index 000000000000..8bca15e64813
--- /dev/null
+++ b/changelogs/unreleased/allow-bootstrap-in-ro-in-supervised-bs.md
@@ -0,0 +1,4 @@
+## feature/replication
+
+* Allow to bootstrap the initial database in read-only mode in the 'supervised'
+  bootstrap strategy.
diff --git a/changelogs/unreleased/early-make-bootstrap-leader.md b/changelogs/unreleased/early-make-bootstrap-leader.md
new file mode 100644
index 000000000000..5f23f5fc9284
--- /dev/null
+++ b/changelogs/unreleased/early-make-bootstrap-leader.md
@@ -0,0 +1,4 @@
+## feature/replication
+
+* Allowed to call `box.ctl.make_bootstrap_leader()` before the first
+  `box.cfg()` call in the `'supervised'` bootstrap strategy (gh-10858).
diff --git a/changelogs/unreleased/graceful-make-bootstrap-leader.md b/changelogs/unreleased/graceful-make-bootstrap-leader.md
new file mode 100644
index 000000000000..b048b2069a1a
--- /dev/null
+++ b/changelogs/unreleased/graceful-make-bootstrap-leader.md
@@ -0,0 +1,3 @@
+## feature/replication
+
+* Added the `graceful` option to `box.ctl.make_bootstrap_leader()` (gh-11272).
diff --git a/changelogs/unreleased/single-instance-in-supervised-bs.md b/changelogs/unreleased/single-instance-in-supervised-bs.md
new file mode 100644
index 000000000000..158a5fad21cb
--- /dev/null
+++ b/changelogs/unreleased/single-instance-in-supervised-bs.md
@@ -0,0 +1,5 @@
+## feature/replication
+
+* Now `box.cfg({bootstrap_strategy = 'supervised'})` without upstreams waits
+  for the `box.ctl.make_bootstrap_leader(<...>)` command till the replication
+  connect timeout instead of an immediate fail.
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9edfb8ef7e8f..919ac80405a9 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -4336,6 +4336,27 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	(void)event;
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
+
+	/*
+	 * Sync the supervised bootstrap flags.
+	 *
+	 * is_supervised_bootstrap_leader has to be consistent
+	 * with bootstrap_leader_uuid.
+	 *
+	 * The graceful bootstrap request is no-op, when the
+	 * database is already bootstrapped. If we met the flag
+	 * here, it is likely that
+	 * box.ctl.make_bootstrap_leader({graceful = true}) is
+	 * issued during the bootstrap or recovery process. It
+	 * means that request is not actual anymore: we either
+	 * already the bootstrap leader or another peer bootstraps
+	 * us.
+	 */
+	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	is_graceful_supervised_bootstrap_requested = false;
+
+	say_info("instance %s is assigned as a bootstrap leader",
+		 tt_uuid_str(uuid));
 	box_broadcast_ballot();
 	return 0;
 }
diff --git a/src/box/box.cc b/src/box/box.cc
index 72d289fa1467..80579e6dec01 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2225,6 +2225,16 @@ box_set_bootstrap_strategy(void)
 	enum bootstrap_strategy strategy = box_check_bootstrap_strategy();
 	if (strategy == BOOTSTRAP_STRATEGY_INVALID)
 		return -1;
+
+	/*
+	 * Drop flags related to the supervised strategy if
+	 * another strategy is configured.
+	 */
+	if (strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		is_supervised_bootstrap_leader = false;
+		is_graceful_supervised_bootstrap_requested = false;
+	}
+
 	bootstrap_strategy = strategy;
 	return 0;
 }
@@ -2248,24 +2258,44 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
 {
-	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 "box.ctl.make_bootstrap_leader()",
-			 "promoting this instance before box.cfg() is called");
-		return -1;
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
 	}
-	/* Bootstrap strategy is read by the time instance uuid is known. */
-	assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
-	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 tt_sprintf("bootstrap_strategy = '%s'",
-				    cfg_gets("bootstrap_strategy")),
-			 "promoting the bootstrap leader via "
-			 "box.ctl.make_bootstrap_leader()");
-		return -1;
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
+
+	/* Wake up replicaset_connect() if the flag is turn on. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID)) {
+		box_broadcast_ballot();
+		replicaset_connect_wakeup();
+	}
+
+	return 0;
+}
+
+static int
+box_make_bootstrap_leader_nongraceful(void)
+{
+	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
+		/*
+		 * Can't store the UUID of the given instance as
+		 * the bootstrap leader UUID at this moment,
+		 * because there is no instance UUID yet. Postpone
+		 * this action till the bootstrap.
+		 */
+		is_supervised_bootstrap_leader = true;
+		say_info("this instance is assigned as a bootstrap leader");
+		return 0;
 	}
 	if (is_box_configured) {
 		if (box_check_writable() != 0)
@@ -2274,11 +2304,34 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
 		box_broadcast_ballot();
+		replicaset_connect_wakeup();
 		return 0;
 	}
 }
 
+int
+box_make_bootstrap_leader(bool graceful)
+{
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+
+	if (graceful)
+		return box_make_bootstrap_leader_graceful();
+	else
+		return box_make_bootstrap_leader_nongraceful();
+}
+
 void
 box_set_replication_sync_lag(void)
 {
@@ -5338,16 +5391,32 @@ check_global_ids_integrity(void)
 static void
 bootstrap_master(void)
 {
-	/* Do not allow to bootstrap a readonly instance as master. */
-	if (cfg_geti("read_only") == 1) {
+	/*
+	 * Do not allow to bootstrap a readonly instance as master.
+	 *
+	 * Allow an exception for the supervised bootstrap
+	 * strategy, because the command to bootstrap may be
+	 * received during the in progress box.cfg() call and
+	 * there is no way to change box.cfg.read_only at this
+	 * time.
+	 */
+	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
+	    cfg_geti("read_only") == 1) {
 		tnt_raise(ClientError, ER_BOOTSTRAP_READONLY);
 	}
 	/*
 	 * With "auto" bootstrap strategy refuse to boot unless everyone agrees
 	 * this node is the bootstrap leader.
 	 */
-	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO)
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO) {
 		check_bootstrap_unanimity();
+	} else if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+		   is_graceful_supervised_bootstrap_requested) {
+		say_info("Graceful bootstrap request succeeded: no bootstrap "
+			 "leader is found in connected peers, so the current "
+			 "instance proceeds as a bootstrap leader");
+		is_graceful_supervised_bootstrap_requested = false;
+	}
 	engine_bootstrap_xc();
 	if (box_set_replication_synchro_queue_max_size() != 0)
 		diag_raise();
@@ -5390,6 +5459,13 @@ bootstrap_from_master(struct replica *master)
 {
 	struct applier *applier = master->applier;
 	assert(applier != NULL);
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+	    is_graceful_supervised_bootstrap_requested) {
+		say_info("Graceful bootstrap request failed: other bootstrap "
+			 "leader is found within connected peers, proceed as a "
+			 "regular replica");
+		is_graceful_supervised_bootstrap_requested = false;
+	}
 	try {
 		applier_resume_to_state(applier, APPLIER_READY,
 					TIMEOUT_INFINITY);
diff --git a/src/box/box.h b/src/box/box.h
index 76942eac2968..9f5123e2895e 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -452,9 +452,14 @@ box_iterator_position_unpack(const char *packed_pos,
 
 /**
  * For bootstrap_strategy = "supervised", set this node as the bootstrap leader.
+ *
+ * If the `graceful` flag is set, an attempt to connect to other nodes is
+ * performed first (during box.cfg.replication_connect_timeout) and if another
+ * node advertises itself as a bootstrap leader, the current node joins to the
+ * existing replicaset.
  */
 int
-box_make_bootstrap_leader(void);
+box_make_bootstrap_leader(bool graceful);
 
 /**
  * Select data, satisfying filters (key and iterator), and dump it to port.
diff --git a/src/box/lua/ctl.c b/src/box/lua/ctl.c
index 31910926651b..b6c72af7a15b 100644
--- a/src/box/lua/ctl.c
+++ b/src/box/lua/ctl.c
@@ -118,7 +118,22 @@ lbox_ctl_demote(struct lua_State *L)
 static int
 lbox_ctl_make_bootstrap_leader(struct lua_State *L)
 {
-	if (box_make_bootstrap_leader() != 0)
+	bool graceful = false;
+
+	if (!lua_isnoneornil(L, 1)) {
+		if (!lua_istable(L, 1)) {
+			diag_set(IllegalParams,
+				 "box.ctl.make_bootstrap_leader() expects a "
+				 "table as the first argument, got %s",
+				 luaL_typename(L, 1));
+			return luaT_error(L);
+		}
+		lua_getfield(L, 1, "graceful");
+		graceful = lua_toboolean(L, -1) != 0;
+		lua_pop(L, 1);
+	}
+
+	if (box_make_bootstrap_leader(graceful) != 0)
 		return luaT_error(L);
 	return 0;
 }
diff --git a/src/box/replication.cc b/src/box/replication.cc
index 8f8de2d9a1b1..0a899822cf99 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -69,6 +69,8 @@ bool cfg_replication_anon = true;
 struct tt_uuid cfg_bootstrap_leader_uuid;
 struct uri cfg_bootstrap_leader_uri;
 char cfg_bootstrap_leader_name[NODE_NAME_SIZE_MAX];
+bool is_supervised_bootstrap_leader = false;
+bool is_graceful_supervised_bootstrap_requested = false;
 char cfg_instance_name[NODE_NAME_SIZE_MAX];
 
 bool replication_synchro_timeout_rollback_enabled = true;
@@ -223,6 +225,15 @@ struct replicaset_connect_state {
 	struct fiber_cond wakeup;
 };
 
+static struct fiber_cond *replicaset_connect_wakeup_cond = NULL;
+
+void
+replicaset_connect_wakeup(void)
+{
+	if (replicaset_connect_wakeup_cond != NULL)
+		fiber_cond_signal(replicaset_connect_wakeup_cond);
+}
+
 static void
 replicaset_set_sync_quorum(const struct replicaset_connect_state *state)
 {
@@ -1143,6 +1154,14 @@ applier_is_bootstrap_leader(const struct applier *applier)
 static bool
 bootstrap_leader_is_connected(struct applier **appliers, int count)
 {
+	/*
+	 * In the supervised strategy it is possible that we know
+	 * locally that the given instance is a bootstrap leader.
+	 */
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+	    is_supervised_bootstrap_leader)
+		return true;
+
 	for (int i = 0; i < count; i++) {
 		struct applier *applier = appliers[i];
 		if (applier->state == APPLIER_CONNECTED &&
@@ -1162,11 +1181,31 @@ replicaset_is_connected(struct replicaset_connect_state *state,
 	if (replicaset_state == REPLICASET_BOOTSTRAP ||
 	    replicaset_state == REPLICASET_JOIN) {
 		/*
-		 * With "supervised" strategy we may continue only once the
-		 * bootstrap leader appears.
+		 * With "supervised" strategy we may continue either:
+		 *
+		 * * when the leader appears, or
+		 * * when the graceful bootstrap is requested and
+		 *   all the upstreams are connected
+		 * * when the graceful bootstrap is requested and
+		 *   the connection timeout is exceeded
 		 */
-		if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED)
-			return bootstrap_leader_is_connected(appliers, count);
+		if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED) {
+			bool has_leader = bootstrap_leader_is_connected(
+				appliers, count);
+			if (has_leader)
+				/* Has leader -- proceed. */
+				return true;
+			else if (!is_graceful_supervised_bootstrap_requested)
+				/* No leader -- wait. */
+				return false;
+			/*
+			 * At this point there is a graceful
+			 * bootstrap request and no leaders. Let's
+			 * fall down to check if there are
+			 * connections to wait for (similarly to
+			 * the "auto" and "config" strategies).
+			 */
+		}
 		/*
 		 * With "config" strategy we may continue either when the leader
 		 * appears or when there are no more connections to wait for.
@@ -1221,7 +1260,19 @@ replicaset_connect(const struct uri_set *uris,
 	memset(&state, 0, sizeof(state));
 	fiber_cond_create(&state.wakeup);
 
-	if (uris->uri_count == 0) {
+	/*
+	 * Return immediately if there are no configured
+	 * upstreams to wait.
+	 *
+	 * The supervised bootstrap strategy has an exception
+	 * here, because it waits not only for replicas, but also
+	 * for local bootstrap leadership flags (it is relevant on
+	 * bootstrap/join and irrelevant on a reconfiguration).
+	 */
+	if (uris->uri_count == 0 &&
+	    (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+	     (replicaset_state != REPLICASET_BOOTSTRAP &&
+	      replicaset_state != REPLICASET_JOIN))) {
 		replicaset_set_sync_quorum(&state);
 		/* Cleanup the replica set. */
 		replicaset_update(NULL, 0, false);
@@ -1246,7 +1297,11 @@ replicaset_connect(const struct uri_set *uris,
 	for (; count < uris->uri_count; count++)
 		appliers[count] = applier_new(&uris->uris[count]);
 
-	say_info("connecting to %d replicas", count);
+	if (count == 0)
+		say_info("no upstreams are configured, waiting for the "
+			 "bootstrap command");
+	else
+		say_info("connecting to %d replicas", count);
 
 	if (!connect_quorum) {
 		/*
@@ -1303,6 +1358,10 @@ replicaset_connect(const struct uri_set *uris,
 			trigger_clear(&trigger->base);
 		}
 	});
+	replicaset_connect_wakeup_cond = &state.wakeup;
+	auto cond_guard = make_scoped_guard([&]{
+		replicaset_connect_wakeup_cond = NULL;
+	});
 	while (!replicaset_is_connected(&state, appliers, count,
 					connect_quorum)) {
 		double wait_start = ev_monotonic_now(loop());
@@ -1312,6 +1371,8 @@ replicaset_connect(const struct uri_set *uris,
 		}
 		timeout -= ev_monotonic_now(loop()) - wait_start;
 	}
+	replicaset_connect_wakeup_cond = NULL;
+	cond_guard.is_active = false;
 	if (state.connected < count) {
 		say_crit("failed to connect to %d out of %d replicas",
 			 count - state.connected, count);
@@ -1664,6 +1725,26 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members (except when the graceful
+	 * bootstrap is requested).
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
+		return NULL;
+
 	struct replica *leader = NULL;
 	replicaset_foreach(replica) {
 		struct applier *applier = replica->applier;
@@ -1672,10 +1753,34 @@ replicaset_find_join_master_supervised(void)
 		if (applier_is_bootstrap_leader(applier))
 			leader = replica;
 	}
-	if (leader == NULL) {
+
+	/*
+	 * At this point the waiting logic from
+	 * replicaset_is_connected() is finished. There are the
+	 * following situations, when it is possible:
+	 *
+	 * 1. A bootstrap leader is found.
+	 * 2. All the instances from box.cfg.replication are
+	 *    connected, but no one is a bootstrap leader.
+	 * 3. box.cfg.replication_connect_timeout is reached.
+	 *
+	 * In case of the situation 1 we're going to bootstrap or
+	 * to recovery, depending on whether the leader is the
+	 * current instance.
+	 *
+	 * In the 2 and 3 situations, it depends on the graceful
+	 * bootstrap request. If there is one, we're going to
+	 * bootstrap. Otherwise, an error is reported.
+	 */
+	if (leader == NULL && !is_graceful_supervised_bootstrap_requested) {
 		tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
 			  "failed to connect to the bootstrap leader");
 	}
+
+	/*
+	 * NULL is possible here and it means to assign the
+	 * current instance as a bootstrap leader.
+	 */
 	return leader;
 }
 
diff --git a/src/box/replication.h b/src/box/replication.h
index 041ca35c56d3..c006ec374a0b 100644
--- a/src/box/replication.h
+++ b/src/box/replication.h
@@ -153,6 +153,27 @@ extern struct uri cfg_bootstrap_leader_uri;
  */
 extern char cfg_bootstrap_leader_name[];
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * This flag have to be consistent with bootstrap_leader_uuid
+ * except the following cases:
+ *
+ * - INSTANCE_UUID is zero UUID
+ * - the bootstrap strategy is not 'supervised'
+ */
+extern bool is_supervised_bootstrap_leader;
+
+/**
+ * A graceful bootstrap leadership request means that we check
+ * other peers first and if there is no bootstrap leader across
+ * them, continue as the bootstrap leader.
+ */
+extern bool is_graceful_supervised_bootstrap_requested;
+
 /**
  * Configured name of this instance. Might be different from the actual name if
  * the configuration is not fully applied yet.
@@ -684,6 +705,18 @@ void
 replicaset_connect(const struct uri_set *uris,
 		   bool connect_quorum, bool keep_connect);
 
+/**
+ * Wake up replicaset_connect() to re-verify if the waiting for
+ * connect quorum/bootstrap leader can be finished. To be called
+ * after changing local bootstrap leadership flags in the
+ * supervised bootstrap strategy.
+ *
+ * No-op if replicaset_connect() is not in the waiting loop or not
+ * in progress at all.
+ */
+void
+replicaset_connect_wakeup(void);
+
 /**
  * Reload replica URIs.
  *
diff --git a/test/replication-luatest/bootstrap_strategy_test.lua b/test/replication-luatest/bootstrap_strategy_test.lua
index 5d7dcaec9b19..38aa39894f06 100644
--- a/test/replication-luatest/bootstrap_strategy_test.lua
+++ b/test/replication-luatest/bootstrap_strategy_test.lua
@@ -1,6 +1,7 @@
 local t = require('luatest')
 local server = require('luatest.server')
 local replica_set = require('luatest.replica_set')
+local treegen = require('luatest.treegen')
 local fio = require('fio')
 local fiber = require('fiber')
 local socket = require('socket')
@@ -33,6 +34,12 @@ local function assert_master_waits_for_replica(server, uuid)
              'Server ' .. server.alias .. ' waits for replica ' .. uuid)
 end
 
+local function grep_log(server, query)
+    local logfile = fio.pathjoin(server.workdir, server.alias .. '.log')
+    local opts = {filename = logfile}
+    return server:grep_log(query, nil, opts)
+end
+
 g_auto.after_each(function(cg)
     cg.replica_set:drop()
 end)
@@ -533,18 +540,30 @@ local g_supervised = t.group('gh-8509-bootstrap-strategy-supervised')
 local server2_admin
 local SOCKET_TIMEOUT = 5
 
-local function make_bootstrap_leader_initial(sockname)
+local function admin(sockname, command)
     local sock, err = socket.tcp_connect('unix/', sockname)
     t.assert_equals(err, nil, 'Connection successful')
     local greeting = sock:read(128, SOCKET_TIMEOUT)
     t.assert_str_contains(greeting, 'Tarantool', 'Connected to console')
     t.assert_str_contains(greeting, 'Lua console', 'Connected to console')
-    sock:write('box.ctl.make_bootstrap_leader()\n', SOCKET_TIMEOUT)
+    sock:write(command .. '\n', SOCKET_TIMEOUT)
     local response = sock:read(8, SOCKET_TIMEOUT)
     t.assert_equals(response, '---\n...\n', 'The call succeeded')
     sock:close()
 end
 
+local function make_bootstrap_leader_initial(sockname, opts)
+    opts = opts or {}
+    local command
+    if opts.graceful then
+        command = 'box.ctl.make_bootstrap_leader({graceful = true})'
+    else
+        command = 'box.ctl.make_bootstrap_leader()'
+    end
+
+    admin(sockname, command)
+end
+
 g_supervised.after_each(function(cg)
     cg.replica_set:drop()
 end)
@@ -752,25 +771,669 @@ g_supervised.test_wait_for_bootstrap_leader = function(cg)
     assert_not_booted(cg.server2)
 end
 
-g_supervised.before_test('test_no_bootstrap_without_replication', function(cg)
+g_supervised.before_test('test_bootstrap_without_replication', function(cg)
     cg.replica_set = replica_set:new{}
     cg.server1 = cg.replica_set:build_and_add_server{
         alias = 'server1',
         box_cfg = {
             bootstrap_strategy = 'supervised',
+            -- Let the test fail on :wait_until_ready() timeout if
+            -- the box.ctl.make_bootstrap_leader() command doesn't
+            -- wake up the logic that waits for a bootstrap
+            -- leader (replicaset_connect()).
+            replication_connect_timeout = 1000,
         },
     }
+
+    cg.server1_admin = fio.pathjoin(cg.server1.workdir, 'server1.admin')
+    local run_before_cfg = string.format([[
+        local console = require('console')
+        console.listen('unix/:%s')
+    ]], cg.server1_admin)
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
 end)
 
-g_supervised.test_no_bootstrap_without_replication = function(cg)
+-- If the given instance is not chosen as the bootstrap leader
+-- before the first box.cfg() call and there is no replication
+-- upstreams, it waits for the box.ctl.make_bootstrap_leader()
+-- command.
+g_supervised.test_bootstrap_without_replication = function(cg)
     cg.server1:start{wait_until_ready = false}
-    local logfile = fio.pathjoin(cg.server1.workdir, 'server1.log')
-    local query = "can't initialize storage"
+
+    -- Let the instance step into box.cfg(). This way we verify
+    -- that it doesn't exit immediately if there is no
+    -- box.cfg.make_bootstrap_leader(<...>) before box.cfg().
+    fiber.sleep(1)
+
+    -- Run box.cfg.make_bootstrap_leader() and wait till the
+    -- database bootstrap.
+    t.helpers.retrying({}, function()
+        make_bootstrap_leader_initial(cg.server1_admin)
+    end)
+    cg.server1:wait_until_ready()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil)
+        t.assert_equals(tup[2], box.info.uuid)
+    end)
+end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before
+-- the first box.cfg() call, it is expected to successfully
+-- bootstrap the database even if there are no upstreams
+-- configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before
+-- the first box.cfg() call, it is expected that it is
+-- successfully bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    local leader_uuid = cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+        return box.info.uuid
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+
+    -- Verify a log message on a bootstrap leader.
+    local exp_msg = 'instance [0-9a-f-]+ is assigned as a bootstrap leader'
+    local found = grep_log(cg.server2, exp_msg)
+    t.assert(found)
+    t.assert_str_contains(found, leader_uuid)
+
+    -- Verify a log message on a replica.
+    local exp_msg = 'instance [0-9a-f-]+ is assigned as a bootstrap leader'
+    local found = grep_log(cg.server1, exp_msg)
+    t.assert(found)
+    t.assert_str_contains(found, leader_uuid)
+end
+
+g_supervised.before_test('test_early_leader_several_leaders', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+            -- Both instances advertise itself as bootstrap
+            -- leaders.
+            env = {
+                ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                    'box.ctl.make_bootstrap_leader()',
+            },
+        }
+    end
+end)
+
+-- For the sake of completeness verify behavior in the incorrect
+-- situation: two instances within the same replicaset are
+-- assigned as bootstrap leaders.
+g_supervised.test_early_leader_several_leaders = function(cg)
+    cg.replica_set:start({wait_until_ready = false})
+    local query = 'ER_REPLICASET_UUID_MISMATCH'
+    t.helpers.retrying({}, function()
+        local found = grep_log(cg.server1, query) or
+                      grep_log(cg.server2, query)
+        t.assert(found, ('Found %s'):format(query))
+    end)
+end
+
+local g_graceful_supervised = t.group('graceful-bootstrap-strategy-supervised')
+
+g_graceful_supervised.before_test('test_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader({graceful = true})',
+        },
+    }
+end)
+
+-- Bootstraps immediately if there are no upstreams to wait.
+g_graceful_supervised.test_singleton = function(cg)
+    cg.server1:start()
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+end
+
+g_graceful_supervised.before_test('test_singleton_after_box_cfg', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- Let the test fail on :wait_until_ready() timeout if
+            -- the box.ctl.make_bootstrap_leader({graceful = true})
+            -- command doesn't wake up the logic that waits for a
+            -- bootstrap leader (replicaset_connect()).
+            replication_connect_timeout = 1000,
+        },
+    }
+
+    cg.server1_admin = fio.pathjoin(cg.server1.workdir, 'server1.admin')
+    local run_before_cfg = string.format([[
+        local console = require('console')
+        console.listen('unix/:%s')
+    ]], cg.server1_admin)
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
+end)
+
+-- Same as `test_bootstrap_without_replication`, but for the
+-- graceful bootstrap request.
+g_graceful_supervised.test_singleton_after_box_cfg = function(cg)
+    cg.server1:start{wait_until_ready = false}
+
+    -- Let the instance step into box.cfg(). This way we verify
+    -- that it doesn't exit immediately if there is no
+    -- box.cfg.make_bootstrap_leader(<...>) before box.cfg().
+    fiber.sleep(1)
+
+    -- Run box.cfg.make_bootstrap_leader({graceful = true}) and
+    -- wait till the database bootstrap.
+    t.helpers.retrying({}, function()
+        make_bootstrap_leader_initial(cg.server1_admin, {graceful = true})
+    end)
+    cg.server1:wait_until_ready()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil)
+        t.assert_equals(tup[2], box.info.uuid)
+    end)
+end
+
+g_graceful_supervised.before_test('test_no_peer', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            -- Don't start server2.
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- Wait for server2 for a long time.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader({graceful = true})'
+end)
+
+-- Waits for other peers.
+g_graceful_supervised.test_no_peer = function(cg)
+    cg.server1:start({wait_until_ready = false})
+    fiber.sleep(1)
+    t.assert_error(cg.server1.connect_net_box, cg.server1)
+end
+
+g_graceful_supervised.before_test('test_no_leader', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader({graceful = true})'
+end)
+
+-- All connected, no leader => let's bootstrap.
+--
+-- Plus verify several other cases.
+g_graceful_supervised.test_no_leader = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+    local uuid_1 = cg.server1:call('box.info').uuid
+
+    -- Verify that a message about the reason why this instance
+    -- is a bootstrap leader is issued.
+    local exp_msg = 'Graceful bootstrap request succeeded: no bootstrap ' ..
+        'leader is found in connected peers, so the current instance ' ..
+        'proceeds as a bootstrap leader'
+    local found = grep_log(cg.server1, exp_msg)
+    t.assert(found)
+
+    -- Also, verify that when the database is bootstrapped the
+    -- graceful bootstrap request is discarded.
+    --
+    -- Try it on a replica (server2).
+    cg.server2:exec(function()
+        box.ctl.make_bootstrap_leader({graceful = true})
+    end)
+    local exp_msg = 'Graceful bootstrap request is discarded: the ' ..
+        'instance is already bootstrapped'
     t.helpers.retrying({}, function()
-        t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
-                 'Server fails to boot')
+        local found = grep_log(cg.server2, exp_msg)
+        t.assert(found)
     end)
-    query = 'failed to connect to the bootstrap leader'
-    t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
-             'Bootstrap leader not found')
+    cg.server2:exec(function(uuid_1)
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil)
+        t.assert_equals(tup[2], uuid_1)
+    end, {uuid_1})
+
+    -- And verify that if {graceful = false} is passed, it
+    -- actually means the non-graceful bootstrap leader
+    -- assignment.
+    cg.server2:exec(function()
+        box.ctl.make_bootstrap_leader({graceful = false})
+
+        -- Bootstrap leader is updated. This wouldn't occur with
+        -- the graceful bootstrap request.
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil)
+        t.assert_equals(tup[2], box.info.uuid)
+    end)
+
+    -- Verify incorrect argument.
+    cg.server2:exec(function()
+        local exp_err_msg = 'box.ctl.make_bootstrap_leader() expects a ' ..
+            'table as the first argument, got boolean'
+        t.assert_error_msg_equals(exp_err_msg, function()
+            box.ctl.make_bootstrap_leader(true)
+        end)
+    end)
+end
+
+g_graceful_supervised.before_test('test_other_leader', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader({graceful = true})'
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader({graceful = false})'
+end)
+
+-- If some of the peers is already a leader, act as a regular
+-- replica.
+g_graceful_supervised.test_other_leader = function(cg)
+    cg.server2:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1)
+    cg.server1:start()
+    t.assert_equals(cg.server1:get_instance_id(), 2)
+
+    -- Verify that the message about the graceful bootstrap
+    -- request failure is issued.
+    local exp_msg = 'Graceful bootstrap request failed: other bootstrap ' ..
+        'leader is found within connected peers, proceed as a regular replica'
+    t.helpers.retrying({}, function()
+        local found = grep_log(cg.server1, exp_msg)
+        t.assert(found)
+    end)
+end
+
+g_graceful_supervised.before_test('test_after_box_cfg', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        local box_cfg = table.copy(cg.box_cfg)
+        box_cfg.instance_uuid = ({uuid1, uuid2})[i]
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = box_cfg,
+        }
+    end
+
+    cg.server1_admin = fio.pathjoin(cg.server1.workdir, 'server1.admin')
+    local run_before_cfg = string.format([[
+        local console = require('console')
+        console.listen('unix/:%s')
+    ]], cg.server1_admin)
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
+end)
+
+-- Run box.cfg() first, wait until server1 connects to all the
+-- peers and then issue the graceful bootstrap request.
+--
+-- Similar to test_no_leader, but the graceful bootstrap request
+-- is issued after box.cfg() (and after all the peers are
+-- connected).
+--
+-- This way we verify that the graceful bootstrap request wakes up
+-- replicaset_connect(), where we wait for new connected peers, a
+-- new bootstrap leader, a change in the local supervised
+-- bootstrap flags (set by box.cfg.make_bootstrap_leader(<...>)).
+g_graceful_supervised.test_after_box_cfg = function(cg)
+    -- Start both instances, run box.cfg() on them.
+    cg.replica_set:start({wait_until_ready = false})
+
+    -- Wait until server1 connects to itself and server2.
+    t.helpers.retrying({}, function()
+        local found_1 = grep_log(cg.server1, 'remote master 11111111')
+        local found_2 = grep_log(cg.server1, 'remote master 22222222')
+        t.assert(found_1 ~= nil and found_2 ~= nil)
+    end)
+
+    -- Run box.cfg.make_bootstrap_leader({graceful = true}).
+    t.helpers.retrying({}, function()
+        make_bootstrap_leader_initial(cg.server1_admin, {graceful = true})
+    end)
+
+    -- Now the database should be bootstrapped.
+    cg.server1:wait_until_ready()
+    cg.server2:wait_until_ready()
+
+    -- Verify that server1 was the bootstrap leader.
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+
+    -- Verify that a message about the reason why this instance
+    -- is a bootstrap leader is issued.
+    local exp_msg = 'Graceful bootstrap request succeeded: no bootstrap ' ..
+        'leader is found in connected peers, so the current instance ' ..
+        'proceeds as a bootstrap leader'
+    local found = grep_log(cg.server1, exp_msg)
+    t.assert(found)
+end
+
+g_graceful_supervised.before_test('test_singleton_read_only', function(cg)
+    cg.replica_set = replica_set:new{}
+
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- It is allowed to bootstrap the database in
+            -- read_only in the 'supervised' strategy.
+            read_only = true,
+        },
+    }
+
+    local dir = cg.server1.workdir
+    cg.server1.command = fio.pathjoin(dir, 'server1.lua')
+    treegen.write_file(dir, 'server1.lua', string.dump(function()
+        local fun = require('fun')
+        local json = require('json')
+
+        local function default_cfg()
+            return {
+                work_dir = os.getenv('TARANTOOL_WORKDIR'),
+                listen = os.getenv('TARANTOOL_LISTEN'),
+            }
+        end
+
+        local function env_cfg()
+            local cfg = os.getenv('TARANTOOL_BOX_CFG')
+            if cfg == nil then
+                return {}
+            end
+            local res = json.decode(cfg)
+            assert(type(res) == 'table')
+            return res
+        end
+
+        local function box_cfg(cfg)
+            return fun.chain(default_cfg(), env_cfg(), cfg or {}):tomap()
+        end
+
+        -- It is the same as the default instance file
+        -- (luatest/server_instance.lua) till this line.
+        -- The test case related logic is below.
+
+        -- Bootstrap in RO.
+        box.ctl.make_bootstrap_leader({graceful = true})
+        box.cfg(box_cfg())
+
+        -- Go to RW and grant guest all the permissions (to allow
+        -- server:exec() and other methods work).
+        box.cfg({read_only = false})
+        local opts = {if_not_exists = true}
+        box.schema.user.grant('guest', 'super', nil, nil, opts)
+
+        rawset(_G, 'ready', true)
+    end))
+end)
+
+g_graceful_supervised.test_singleton_read_only = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+    cg.server1:exec(function()
+        t.assert_equals(box.info.ro, false)
+    end)
+end
+
+g_graceful_supervised.before_test('test_after_box_cfg_read_only', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- It is allowed to bootstrap the database in
+        -- read_only in the 'supervised' strategy.
+        read_only = true,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        local box_cfg = table.copy(cg.box_cfg)
+        box_cfg.instance_uuid = ({uuid1, uuid2})[i]
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = box_cfg,
+        }
+    end
+
+    local dir = cg.server1.workdir
+
+    -- luatest works with logs differently depending on its
+    -- revision and we have to set the log file manually to make
+    -- things working before [1] with a non-default instance
+    -- file.
+    --
+    -- [1]: https://github.com/tarantool/luatest/pull/415
+    cg.server1.box_cfg.log = fio.pathjoin(dir, 'server1.log')
+
+    cg.server1.command = fio.pathjoin(dir, 'server1.lua')
+    cg.server1_admin = fio.pathjoin(dir, 'server1.admin')
+    cg.server1.env.TARANTOOL_CONSOLE = ('unix/:%s'):format(cg.server1_admin)
+    treegen.write_file(dir, 'server1.lua', string.dump(function()
+        local fun = require('fun')
+        local json = require('json')
+        local console = require('console')
+        local fiber = require('fiber')
+
+        local function default_cfg()
+            return {
+                work_dir = os.getenv('TARANTOOL_WORKDIR'),
+                listen = os.getenv('TARANTOOL_LISTEN'),
+            }
+        end
+
+        local function env_cfg()
+            local cfg = os.getenv('TARANTOOL_BOX_CFG')
+            if cfg == nil then
+                return {}
+            end
+            local res = json.decode(cfg)
+            assert(type(res) == 'table')
+            return res
+        end
+
+        local function box_cfg(cfg)
+            return fun.chain(default_cfg(), env_cfg(), cfg or {}):tomap()
+        end
+
+        -- It is the same as the default instance file
+        -- (luatest/server_instance.lua) till this line.
+        -- The test case related logic is below.
+
+        console.listen(os.getenv('TARANTOOL_CONSOLE'))
+
+        local bootstrap_is_done_latch = fiber.channel(1)
+        rawset(_G, 'bootstrap', function()
+            box.ctl.make_bootstrap_leader({graceful = true})
+            box.cfg({read_only = false})
+            local opts = {if_not_exists = true}
+            box.schema.user.grant('guest', 'super', nil, nil, opts)
+            bootstrap_is_done_latch:put(true)
+        end)
+
+        box.cfg(box_cfg())
+
+        bootstrap_is_done_latch:get()
+        rawset(_G, 'ready', true)
+    end))
+end)
+
+g_graceful_supervised.test_after_box_cfg_read_only = function(cg)
+    cg.replica_set:start({wait_until_ready = false})
+
+    -- Wait until server1 connects to itself and server2.
+    t.helpers.retrying({}, function()
+        local found_1 = grep_log(cg.server1, 'remote master 11111111')
+        local found_2 = grep_log(cg.server1, 'remote master 22222222')
+        t.assert(found_1 ~= nil and found_2 ~= nil)
+    end)
+
+    -- Bootstrap in RO, reconfigure to RW, grant permissions for
+    -- :exec().
+    t.helpers.retrying({}, function()
+        admin(cg.server1_admin, 'bootstrap()')
+    end)
+
+    -- Now the database should be bootstrapped.
+    cg.server1:wait_until_ready()
+    cg.server2:wait_until_ready()
+
+    -- Verify that server1 was the bootstrap leader.
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+
+    -- Verify that a message about the reason why this instance
+    -- is a bootstrap leader is issued.
+    local exp_msg = 'Graceful bootstrap request succeeded: no bootstrap ' ..
+        'leader is found in connected peers, so the current instance ' ..
+        'proceeds as a bootstrap leader'
+    local found = grep_log(cg.server1, exp_msg)
+    t.assert(found)
 end


Reviews:
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T01:21:24Z
user: login: grafin
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-11-27T01:28:52Z
user: login: grafin
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-11-27T01:30:35Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T01:51:03Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T01:53:56Z
user: login: grafin
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-11-27T01:57:05Z
user: login: grafin
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-11-27T02:28:38Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T06:11:42Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T06:17:47Z
user: login: lenkis
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T10:34:36Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T11:33:31Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-11-27T11:46:04Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T11:46:31Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T11:59:59Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-11-27T12:00:16Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T12:01:19Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T12:01:22Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T12:03:55Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T12:06:04Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-11-27T12:06:20Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-11-27T12:10:26Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-11-27T12:24:13Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T13:57:46Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T14:10:08Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T14:21:17Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T14:31:27Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:06:40Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:08:16Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:09:10Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:09:36Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:13:59Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:14:11Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:17:32Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T19:23:12Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T21:37:35Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-27T21:38:08Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T00:48:50Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:06:06Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:06:11Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:06:15Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:06:19Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:06:37Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T08:14:24Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T10:26:07Z
user: login: Serpentian
body: Let's resolve the last comment and we're good to go. Thank you for the patch!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T10:51:23Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T12:06:44Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T12:26:09Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-11-28T12:49:42Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T18:58:50Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:43:06Z
user: login: sergepetrenko
body: Thanks for the patch!

I have no objections regarding this part, although I'd like to see it together with the patch for #11272.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T07:43:53Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:27:47Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:32:25Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:32:28Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:25:00Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T00:41:46Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T11:39:50Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T11:40:14Z
user: login: sergepetrenko
body: Thanks!

The general implementation idea looks good to me although I'd like to simplify some places here and there.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T13:10:54Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T13:39:38Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:13:57Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T01:43:45Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T01:46:21Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T02:06:41Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T02:18:01Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:20:07Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T14:54:15Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:59:26Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T09:00:02Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T09:10:20Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T09:12:49Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T10:55:24Z
user: login: sergepetrenko
body: Thanks! Looks fine overall, just a couple of places need attention.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T11:23:12Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T12:34:23Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T12:48:01Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T13:36:33Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:35:38Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:36:46Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:37:06Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:39:48Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:13:25Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:19:58Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:24:01Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T19:00:43Z
user: login: Serpentian
body: I didn't manage to find any flaws. The patch is extensively tested and the tests're stable. They seem a little bit difficult to understand. but I see no sense in rewriting the tests now, when we have several before release left. Great work
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T16:37:53Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T23:10:03Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T00:08:39Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T00:08:43Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T00:08:46Z
user: login: Serpentian
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T02:20:53Z
user: login: sergepetrenko
body: Thanks for the patch!
I've left some suggestions below, everything else LGTM.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-10T11:44:25Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T11:44:57Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T11:45:23Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T11:45:56Z
user: login: sergepetrenko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T11:53:44Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:11:32Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:11:40Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:14:16Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:14:22Z
user: login: Totktonada
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T15:29:30Z

Review comments:
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: I'm not very confident about this part.

First of all, it seems that `replicaset_find_join_master` and underneath `replicaset_find_join_master_*` functions incapsulate all the related logic and I ruin this idea to some extent. However, it is attractive to make `am_i_bootstrap_leader` local for this compilation unit. This way it is easier to spot that it is needed just to pass some information from `box_make_bootstrap_leader` to `bootstrap`.

Next, it seems meaningful to wait till `replication_connect_timeout` or till all the upstreams are connected and verify, whether the remote peers have a database bootstrapped. OTOH, `replicaset_find_join_master` doesn't do it. At least, if there is another instance that advertizes itself as a bootstrap leader, we can choose our own applier object anyway. And we only look for the bootstrap leader flag, not whether the database is bootstraped.

So, it seems that `replicaset_find_join_master` is not about such safety checks. If we decide to add them, they're likely will be separated from the master finding procedure.

Taking all that, I've implemented in the given way. However, I'm not very in context about this code and if I missed something, please, let me know.
created_at: 2024-11-27T01:21:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: grafin
body: nit: as always, I have doubts about the naming) something like `is_forced_bootstrap_leader` or `is_configured_bootstrap_leader` sounds better to me, but is see that in https://github.com/tarantool/tarantool/blob/265c74763843f4a71613d592a884536c848f3b4d/src/box/lua/config/applier/box_cfg.lua#L747 you already used this name, so I guess I'm fine with it.
created_at: 2024-11-27T01:28:52Z
author_association: MEMBER
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: grafin
body: I'm not fully in context, but you removed those lines:
```c
		master = replicaset_find_join_master();
		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
							 master->uuid;
		box_broadcast_ballot();
```
why didn't you move them under an `else` statement?
created_at: 2024-11-27T01:30:35Z
author_association: MEMBER
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Totktonada
body: I spin around naming for a while, but I didn't find anything better, to be honest.

'configured' sounds like something from the `box.cfg` table, but it is from a `box.ctl.<...>()` call.

'Forced' seems OK, though. OTOH, it doesn't look more forced than when we call `make_bootstrap_leader` during `box.cfg()`.

Maybe it is better to highlight that it is a kind of a message from `make_bootstrap_leader()`? `make_bootstrap_leader_result`, for example?

I would hear opinions here :)
created_at: 2024-11-27T01:51:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: It seems, you were confused by the identation change. The `-b` diff looks so:

```diff
@@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)

        while (true) {
                box_restart_replication();
+               /*
+                * If the bootstrap strategy is 'supervised' and
+                * it is known that the given instance is the
+                * bootstrap leader, just bootstrap the database.
+                *
+                * Otherwise, find who is the bootstrap leader.
+                */
+               if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+                   !am_i_bootstrap_leader) {
                        master = replicaset_find_join_master();
                        bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
                                                                 master->uuid;
                        box_broadcast_ballot();
+               }

                if (master == NULL ||
                    tt_uuid_is_equal(&master->uuid, &INSTANCE_UUID)) {
```
created_at: 2024-11-27T01:53:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: grafin
body: possibly `is_made_bootstrap_leader` or `is_user_made_bootstrap_leader`?
created_at: 2024-11-27T01:57:05Z
author_association: MEMBER
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: grafin
body: Yeap, you're correct)
created_at: 2024-11-27T02:28:38Z
author_association: MEMBER
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Serpentian
body: +1. The name should start with `is_<...>`, as it's done for `is_box_configured` and others. Let's just make `is_bootsrap_leader` not an argument in `bootstrap()` but a variable in `replication.cc` and set it from `box.cc`. This way `replicaset_find_join_master` will return NULL, when `is_bootstrap_leader` is set. No changes in `bootstrap()` will be needed.

Up to you, I don't like that it's now not incapsulated into `replicaset_find_join_master`. 


created_at: 2024-11-27T05:17:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Serpentian
body: If I have two instances, where I called `box.ctl.make_bootstrap_leader()` before box.cfg, they won't be able to join with each other, since each of them supposes itself as bootstrap leader, they will be bootstrapped independently and then fail to join with `ER_REPLICASET_UUID_MISMATCH`. The question here, are we ok with this behavior? @sergepetrenko @Totktonada 

@Totktonada Did you consider introducing a function in config module, which will save, whether we're supposed to be a bootstrap leader and then call `make_bootstrap_leader()` right after launching box.cfg in a separate fiber? 
created_at: 2024-11-27T05:32:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Serpentian
body: Nit: IMHO, it'll be easier to read, if the condition is `if (!(bootstrap_strategy == SUPERVISED && am_i_bootstrap_leader))`. So that it's the same as in the comment, otherwise De Morgan's law must be applied to figure out, what is done. Up to you

But I still suppose, that it'll cleaner to do all of that in `replicaset_find_join_master`
created_at: 2024-11-27T05:43:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -774,3 +779,87 @@ g_supervised.test_no_bootstrap_without_replication = function(cg)
     t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
              'Bootstrap leader not found')
 end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected to successfully bootstrap
+-- the database even if there are no upstreams configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected that it is successfully
+-- bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+end
path: test/replication-luatest/bootstrap_strategy_test.lua
user: login: Serpentian
body: I suppose, we also need a test, where several `box.ctl.make_bootstrap_leader` are called before box.cfg. But let's firstly discuss, what the expected behavior in such case is
created_at: 2024-11-27T06:10:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Serpentian
body: > Next, it seems meaningful to wait till replication_connect_timeout or till all the upstreams are connected

But we already do that in `box_restart_replication` right before searching for `bootstrap_leader`.

> verify, whether the remote peers have a database bootstrapped

Why do we want to do that? If replica advertises itself as bootstrap leader in ballot, we'll bootstrap from it.
created_at: 2024-11-27T06:17:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
+
+* Allowed to call `box.ctl.make_bootstrap_leader()` before a first `box.cfg()`
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: lenkis
body: ```suggestion
* Allowed to call `box.ctl.make_bootstrap_leader()` before the first `box.cfg()`
```
created_at: 2024-11-27T10:33:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: > > Next, it seems meaningful to wait till replication_connect_timeout or till all the upstreams are connected
>
> But we already do that in `box_restart_replication` right before searching for `bootstrap_leader`.

It seems, we don't (in this bootstrap strategy).

https://github.com/tarantool/tarantool/blob/89cd7f43fd37e7afa459bf1a8f0e5159085c73ae/src/box/replication.cc#L1010-L1015

> > verify, whether the remote peers have a database bootstrapped
>
> Why do we want to do that? If replica advertises itself as bootstrap leader in ballot, we'll bootstrap from it.

As a safety measure. For example, look on this logic:

https://github.com/tarantool/tarantool/blob/89cd7f43fd37e7afa459bf1a8f0e5159085c73ae/src/box/lua/config/applier/box_cfg.lua#L717-L784

In the `auto` bootstrap strategy we just make the instance RW for a short period of time. In the `supervised` strategy without the safety checks we initialize a new replicaset.

I think that we can pass it over here, but I would return to these checks later.
created_at: 2024-11-27T11:33:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: sergepetrenko
body: > If I have two instances, where I called box.ctl.make_bootstrap_leader() before box.cfg, they won't be able to join with each other, since each of them supposes itself as bootstrap leader, they will be bootstrapped independently and then fail to join with ER_REPLICASET_UUID_MISMATCH. The question here, are we ok with this behavior? @sergepetrenko @Totktonada

This matches current `make_bootstrap_leader()` behavior. The only difference is that now you may call `box.ctl.make_bootstrap_leader()` before issuing `box.cfg`. But otherwise the user can shoot off his legs even now: nothing prevents him from issuing `make_bootstrap_leader()` on both nodes while they are still trying to connect to each other.
created_at: 2024-11-27T11:46:04Z
author_association: COLLABORATOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Totktonada
body: > they will be bootstrapped independently and then fail to join with `ER_REPLICASET_UUID_MISMATCH`. The question here, are we ok with this behavior?

I'm not really OK, but OK as a temporary state.

I would like to have some safety checks, but since we have no them now (in this bootstrap strategy), I think it is OK to go step-by-step.

> @Totktonada Did you consider introducing a function in config module, which will save, whether we're supposed to be a bootstrap leader and then call `make_bootstrap_leader()` right after launching box.cfg in a separate fiber? 

Sure. I don't like how much logic is coupled with a certain point in the code, where we call `box.cfg()`. Letting box remember the choice allows to write the same logic simpler.

But it is not the only reason to propose this patch. I also can't bootstrap a single instance replicaset without an iproto port in the supervised strategy and need to switch between config/supervised depending on some conditions.

To sum up: I don't like how complicated/fragile config's code would be without this change. I'm afraid that I'll miss more scenarios in attempting to work around current supervised strategy constraints.
created_at: 2024-11-27T11:46:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -774,3 +779,87 @@ g_supervised.test_no_bootstrap_without_replication = function(cg)
     t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
              'Bootstrap leader not found')
 end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected to successfully bootstrap
+-- the database even if there are no upstreams configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected that it is successfully
+-- bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+end
path: test/replication-luatest/bootstrap_strategy_test.lua
user: login: Totktonada
body: Judging from Sergey's answer in the above thread, it seems that we expect `ER_REPLICASET_UUID_MISMATCH` for now.

I think that the best course is to verify this behavior and link an issue with proposal to add the safety checks. I'll create the issue.

@Serpentian @sergepetrenko Are you OK?
created_at: 2024-11-27T11:59:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: sergepetrenko
body: @Totktonada 
It seems you don't need this particular change at all.
I'd rather put the `am_i_bootstrap_leader` logic into `replicaset_find_join_master`. Besides, there's a designated check `replicaset_find_join_master_supervised`. It feels logical to check `am_i_bootstrap_leader` there, rather than in the calling code.

So, I suggest the following incremental diff:
```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index a198d6587f..ec0af2f8e1 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5421,20 +5421,10 @@ bootstrap(bool *is_bootstrap_leader)
 
        while (true) {
                box_restart_replication();
-               /*
-                * If the bootstrap strategy is 'supervised' and
-                * it is known that the given instance is the
-                * bootstrap leader, just bootstrap the database.
-                *
-                * Otherwise, find who is the bootstrap leader.
-                */
-               if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
-                   !am_i_bootstrap_leader) {
-                       master = replicaset_find_join_master();
-                       bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-                                                                master->uuid;
-                       box_broadcast_ballot();
-               }
+               master = replicaset_find_join_master();
+               bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+                                                        master->uuid;
+               box_broadcast_ballot();
 
                if (master == NULL ||
                    tt_uuid_is_equal(&master->uuid, &INSTANCE_UUID)) {
diff --git a/src/box/replication.cc b/src/box/replication.cc
index ac4d8cbe3e..f4cb7518fa 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1509,7 +1509,7 @@ replicaset_find_join_master_supervised(void)
                if (applier_is_bootstrap_leader(applier))
                        leader = replica;
        }
-       if (leader == NULL) {
+       if (leader == NULL && !am_i_bootstrap_leader) {
                tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
                          "failed to connect to the bootstrap leader");
        }
```
created_at: 2024-11-27T12:00:16Z
author_association: COLLABORATOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Serpentian
body: > This matches current make_bootstrap_leader() behavior. The only difference is that now you may call box.ctl.make_bootstrap_leader() before issuing box.cfg. But otherwise the user can shoot off his legs even now: nothing prevents him from issuing make_bootstrap_leader() on both nodes while they are still trying to connect to each other.

Now they must be called almost simultaneously for that to happen. When it's done before box.cfg it so much easier to shoot off the legs. Moreover, user don't have any way to remove that `am_i_bootstrap_leader` flag, he cannot check, whether the instance will become bootstrap_leader before box.cfg.

We may consider adding internal function for now, which will set this flag. But If everyone is ok with how it works now, then I don't mind.

@Totktonada, can we consider logging, when `box.ctl.make_bootstrap_leader` is called? It won't be very fun to debug on prod, when some failover calls `make_bootstrap_leader` on several instances. 
created_at: 2024-11-27T12:01:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: @sergepetrenko Are you agree with Nikita? Should I incorporate the logic into `replicaset_find_join_master`?

Update: See the answer, thanks!
created_at: 2024-11-27T12:01:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Totktonada
body: > @Totktonada, can we consider logging, when `box.ctl.make_bootstrap_leader` is called? It won't be very fun to debug on prod, when some failover calls `make_bootstrap_leader` on several instances.

I like this idea. Will add.
created_at: 2024-11-27T12:03:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -774,3 +779,87 @@ g_supervised.test_no_bootstrap_without_replication = function(cg)
     t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
              'Bootstrap leader not found')
 end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected to successfully bootstrap
+-- the database even if there are no upstreams configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected that it is successfully
+-- bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+end
path: test/replication-luatest/bootstrap_strategy_test.lua
user: login: Serpentian
body: I'm ok
created_at: 2024-11-27T12:06:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: sergepetrenko
body: > Let's just make is_bootsrap_leader not an argument in bootstrap() but a variable in replication.cc and set it from box.cc. This way replicaset_find_join_master will return NULL, when is_bootstrap_leader is set. No changes in bootstrap() will be needed.

Agreed.
Speaking about the name, it should be less general then. I think `is_made_bootstrap_leader` is a good one.

Probably `is_supervised_bootstrap_leader`?

created_at: 2024-11-27T12:06:20Z
author_association: COLLABORATOR
diff_hunk: @@ -2186,10 +2200,19 @@ int
 box_make_bootstrap_leader(void)
 {
 	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 "box.ctl.make_bootstrap_leader()",
-			 "promoting this instance before box.cfg() is called");
-		return -1;
+		/*
+		 * Can't store the UUID of the given instance as
+		 * the bootstrap leader UUID at this moment,
+		 * because there is no instance UUID yet.
+		 *
+		 * Postpone this action till bootstrap. The
+		 * bootstrap() function assigns the bootstrap
+		 * leader UUID to the UUID of this instance on its
+		 * own if the flag is set (and the bootstrap
+		 * strategy is 'supervised')
+		 */
+		am_i_bootstrap_leader = true;
path: src/box/box.cc
user: login: sergepetrenko
body: I think it would be better to make this variable consistent with the actual `bootstrap_leader`.
Let's also update it below (in the else branch) and in `on_commit_schema_set_bootstrap_leader_uuid` trigger from `alter.cc`.

What do you think?
created_at: 2024-11-27T12:10:25Z
author_association: COLLABORATOR
diff_hunk: @@ -774,3 +779,87 @@ g_supervised.test_no_bootstrap_without_replication = function(cg)
     t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
              'Bootstrap leader not found')
 end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected to successfully bootstrap
+-- the database even if there are no upstreams configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected that it is successfully
+-- bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+end
path: test/replication-luatest/bootstrap_strategy_test.lua
user: login: sergepetrenko
body: Note, after bootstrap is finished the actual `bootstrap_leader` is chosen by the last write wins strategy. So there's no problem with issuing `make_bootstrap_leader()` on multiple instances. Only the last one will prevail (given that we forget about too big time differences between nodes which could mess with determining the "last" write).

TBH, I don't see how we could ensure there's a single set `bootstrap_leader` during cluster bootstrap without changing some `supervised` logic: currently a node only waits for a single connection with any node which names itself a bootstrap leader before attempting bootstrap. Checking if any other node is also made a bootstrap leader would require waiting for all connections before proceeding with bootstrap.

Supervised was specifically designed for cases when one cannot determine which node will be the bootstrap leader before the cluster is started (for example, some particular node can fail to start in time), so it doesn't demand full connectivity now.
created_at: 2024-11-27T12:24:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
+
+* Allowed to call `box.ctl.make_bootstrap_leader()` before a first `box.cfg()`
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Fixed everywhere in the patch. Thanks!

```diff
diff --git a/changelogs/unreleased/early-make-bootstrap-leader.md b/changelogs/unreleased/early-make-bootstrap-leader.md
index 6d8a938ef7..5f23f5fc92 100644
--- a/changelogs/unreleased/early-make-bootstrap-leader.md
+++ b/changelogs/unreleased/early-make-bootstrap-leader.md
@@ -1,4 +1,4 @@
 ## feature/replication
 
-* Allowed to call `box.ctl.make_bootstrap_leader()` before a first `box.cfg()`
-  call in the `'supervised'` bootstrap strategy (gh-10858).
+* Allowed to call `box.ctl.make_bootstrap_leader()` before the first
+  `box.cfg()` call in the `'supervised'` bootstrap strategy (gh-10858).
diff --git a/src/box/box.cc b/src/box/box.cc
index a198d6587f..be33879308 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5367,7 +5367,7 @@ bootstrap(bool *is_bootstrap_leader)
 		tt_uuid_create(&INSTANCE_UUID);
 
 	/*
-	 * If make_bootstrap_leader() is called before a first
+	 * If make_bootstrap_leader() is called before the first
 	 * box.cfg() call, this function can't store the UUID of
 	 * the given instance as a bootstrap leader UUID, because
 	 * there is no instance UUID at the moment.
diff --git a/test/replication-luatest/bootstrap_strategy_test.lua b/test/replication-luatest/bootstrap_strategy_test.lua
index 5b89808858..58a669b162 100644
--- a/test/replication-luatest/bootstrap_strategy_test.lua
+++ b/test/replication-luatest/bootstrap_strategy_test.lua
@@ -763,7 +763,7 @@ g_supervised.before_test('test_no_bootstrap_without_replication', function(cg)
 end)
 
 -- If the given instance is not chosen as the bootstrap leader
--- before a first box.cfg() call and there is no replication
+-- before the first box.cfg() call and there is no replication
 -- upstreams, there are no ways to load the database.
 --
 -- A startup failure is expected in this scenario.
@@ -797,9 +797,10 @@ g_supervised.before_test('test_early_leader_singleton', function(cg)
     }
 end)
 
--- If the given instance is chosen as a bootstrap leader before a
--- first box.cfg() call, it is expected to successfully bootstrap
--- the database even if there are no upstreams configured.
+-- If the given instance is chosen as a bootstrap leader before
+-- the first box.cfg() call, it is expected to successfully
+-- bootstrap the database even if there are no upstreams
+-- configured.
 --
 -- This logic is added in gh-10858.
 g_supervised.test_early_leader_singleton = function(cg)
@@ -846,9 +847,9 @@ g_supervised.before_test('test_early_leader_with_replica', function(cg)
         'box.ctl.make_bootstrap_leader()'
 end)
 
--- If the given instance is chosen as a bootstrap leader before a
--- first box.cfg() call, it is expected that it is successfully
--- bootstrapped and bootstraps a replica.
+-- If the given instance is chosen as a bootstrap leader before
+-- the first box.cfg() call, it is expected that it is
+-- successfully bootstrapped and bootstraps a replica.
 --
 -- This logic is added in gh-10858.
 g_supervised.test_early_leader_with_replica = function(cg)
```

Plus in the commit message.

Will push at the next update.
created_at: 2024-11-27T13:57:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Totktonada
body: > Let's just make `is_bootsrap_leader` not an argument in `bootstrap()` but a variable in `replication.cc` and set it from `box.cc`.

If I understood you correctly, you propose to set it from `make_bootstrap_leader()` as well. I would prefer to don't do that, at least because we should take it into account only in the supervised bootstrap strategy. This way, we need separate 'leadership requested' and 'leadership is in effect' variables. In future, if we add some checks against two bootstrap leaders, there may be more situations, when the leadership request is refused.

Taking into account that I anyway need a separate `is_supervised_bootstrap_leader` variable, I wouldn't touch `is_bootsrap_leader`.

I like the `is_supervised_bootstrap_leader` name more than others. At least it explicitly states that it is related to the supervised bootstrap strategy.

I'm going to rename the variable, move it to `replication.{h,cc}`, take into account in `replicaset_find_join_master_supervised`.
created_at: 2024-11-27T14:10:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2186,10 +2200,19 @@ int
 box_make_bootstrap_leader(void)
 {
 	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 "box.ctl.make_bootstrap_leader()",
-			 "promoting this instance before box.cfg() is called");
-		return -1;
+		/*
+		 * Can't store the UUID of the given instance as
+		 * the bootstrap leader UUID at this moment,
+		 * because there is no instance UUID yet.
+		 *
+		 * Postpone this action till bootstrap. The
+		 * bootstrap() function assigns the bootstrap
+		 * leader UUID to the UUID of this instance on its
+		 * own if the flag is set (and the bootstrap
+		 * strategy is 'supervised')
+		 */
+		am_i_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: TBH, I think that keeping it consistent is not a goal and extra code to do so wouldn't make things simpler or better. The goal is to pass some information from `make_bootstrap_leader` to `bootstrap`.

But if we move the check to `replicaset_find_join_master_supervised`, it seems that we should clean it up or keep actual.

OK, I'll look around the relevant code and try to keep things consistent.
created_at: 2024-11-27T14:21:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Totktonada
body: OTOH, if I anyway trying to keep the flag consistent, maybe it has sense to coalesce it with `is_bootsrap_leader`.

Will look around the relevant code, not sure.
created_at: 2024-11-27T14:31:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: I've pushed an intermediate update that reflects everything we discussed except that I'll add logs a bit later today.
created_at: 2024-11-27T19:06:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Totktonada
body: Renamed the flag to `is_supervised_bootstrap_leader`. I'm afraid of merging it with `is_bootstrap_leader` and more consistency questions you would ask me if I do :)
created_at: 2024-11-27T19:08:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Totktonada
body: Added a test for `ER_REPLICASET_UUID_MISMATCH`. Logs are to be added (later today).
created_at: 2024-11-27T19:09:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Totktonada
body: Now I handle it in `replicaset_find_join_master_supervised`.
created_at: 2024-11-27T19:09:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -774,3 +779,87 @@ g_supervised.test_no_bootstrap_without_replication = function(cg)
     t.assert(cg.server1:grep_log(query, nil, {filename = logfile}),
              'Bootstrap leader not found')
 end
+
+g_supervised.before_test('test_early_leader_singleton', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.server1 = cg.replica_set:build_and_add_server{
+        alias = 'server1',
+        box_cfg = {
+            bootstrap_strategy = 'supervised',
+            -- NB: The test case also verifies that a instance
+            -- successfully bootstraps the database if no
+            -- upstreams are configured.
+        },
+        env = {
+            ['TARANTOOL_RUN_BEFORE_BOX_CFG'] =
+                'box.ctl.make_bootstrap_leader()',
+        },
+    }
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected to successfully bootstrap
+-- the database even if there are no upstreams configured.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_singleton = function(cg)
+    cg.server1:start()
+
+    t.assert_equals(cg.server1:get_instance_id(), 1,
+                    'Server 1 is the bootstrap leader')
+
+    cg.server1:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+end
+
+g_supervised.before_test('test_early_leader_with_replica', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+            -- Don't start this third server to verify that once
+            -- a bootstrap leader is found, a replica doesn't wait
+            -- anything else to register in the replicaset.
+            server.build_listen_uri('server3', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+        -- This way we verify that when the replica connects to
+        -- the bootstrap leader, it correctly determines that it
+        -- is the bootstrap leader and doesn't wait anything else.
+        replication_connect_timeout = 1000,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg.box_cfg,
+        }
+    end
+
+    cg.server2.env.TARANTOOL_RUN_BEFORE_BOX_CFG =
+        'box.ctl.make_bootstrap_leader()'
+end)
+
+-- If the given instance is chosen as a bootstrap leader before a
+-- first box.cfg() call, it is expected that it is successfully
+-- bootstrapped and bootstraps a replica.
+--
+-- This logic is added in gh-10858.
+g_supervised.test_early_leader_with_replica = function(cg)
+    cg.replica_set:start()
+    t.assert_equals(cg.server2:get_instance_id(), 1,
+                    'Server 2 is the bootstrap leader')
+    cg.server2:exec(function()
+        local tup = box.space._schema:get{'bootstrap_leader_uuid'}
+        t.assert(tup ~= nil, 'Bootstrap leader uuid is persisted')
+        t.assert_equals(tup[2], box.info.uuid,
+                        'Bootstrap leader uuid is correct')
+    end)
+    t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
+end
path: test/replication-luatest/bootstrap_strategy_test.lua
user: login: Totktonada
body: Added `test_early_leader_several_leaders`, please, take a look.

I also decided to refuse to look into applier state in `replicaset_find_join_master_supervised` if the given instance is assigned as a bootstrap leader, because even in local testing it catches ~3% of situations and adds instability to testing. These 3% (or even less in real network conditions) don't look valuable for a user.

If we need the checks of this kind, we should guarantee some wait interval, I think.

Now the function looks as follows.

```c
/**
 * Out of all the replicas find the one which was promoted via
 * box.ctl.make_bootstrap_leader()
 */
static struct replica *
replicaset_find_join_master_supervised(void)
{
        /*
         * If we're known as a bootstrap leader, just bootstrap
         * the database.
         *
         * Unlike the 'auto' bootstrap strategy, the 'supervised'
         * one has no safety checks with waiting for ballots from
         * other replicaset members.
         *
         * NB: It is possible to run the cycle below first and
         * verify other members status known at this moment.
         * However, since we don't wait for connecting to all the
         * instances, it has a low chance to catch a problem.
         * As result, it would just make the result of calling
         * this function less predictable without visible gains
         * for a user.
         */
        if (is_supervised_bootstrap_leader)
                return NULL;

        struct replica *leader = NULL;
        replicaset_foreach(replica) {
                struct applier *applier = replica->applier;
                if (applier == NULL)
                        continue;
                if (applier_is_bootstrap_leader(applier))
                        leader = replica;
        }
        if (leader == NULL) {
                tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
                          "failed to connect to the bootstrap leader");
        }
        return leader;
}
```
created_at: 2024-11-27T19:13:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
+
+* Allowed to call `box.ctl.make_bootstrap_leader()` before a first `box.cfg()`
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Pushed.
created_at: 2024-11-27T19:14:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2186,10 +2200,19 @@ int
 box_make_bootstrap_leader(void)
 {
 	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 "box.ctl.make_bootstrap_leader()",
-			 "promoting this instance before box.cfg() is called");
-		return -1;
+		/*
+		 * Can't store the UUID of the given instance as
+		 * the bootstrap leader UUID at this moment,
+		 * because there is no instance UUID yet.
+		 *
+		 * Postpone this action till bootstrap. The
+		 * bootstrap() function assigns the bootstrap
+		 * leader UUID to the UUID of this instance on its
+		 * own if the flag is set (and the bootstrap
+		 * strategy is 'supervised')
+		 */
+		am_i_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: I've pushed my attempt to keep `is_supervised_bootstrap_leader` and `bootstrap_leader_uuid` consistent. Not very confident here, to be honest, and would appreciate another glance from you.
created_at: 2024-11-27T19:17:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: Seems like I've reflected main points from this discussion. I'm going close it. Let's discuss details in separate on-topic discussions.
created_at: 2024-11-27T19:23:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
path: src/box/box.cc
user: login: Totktonada
body: Added the logs in the second commit.

I didn't bother about other bootstrap strategies, only adjusted code that seems relevant to the supervised strategy. I hope it is enough in context of this PR.
created_at: 2024-11-27T21:37:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5381,10 +5421,20 @@ bootstrap(bool *is_bootstrap_leader)
 
 	while (true) {
 		box_restart_replication();
-		master = replicaset_find_join_master();
-		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
-							 master->uuid;
-		box_broadcast_ballot();
+		/*
+		 * If the bootstrap strategy is 'supervised' and
+		 * it is known that the given instance is the
+		 * bootstrap leader, just bootstrap the database.
+		 *
+		 * Otherwise, find who is the bootstrap leader.
+		 */
+		if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+		    !am_i_bootstrap_leader) {
+			master = replicaset_find_join_master();
+			bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
+								 master->uuid;
+			box_broadcast_ballot();
+		}
path: src/box/box.cc
user: login: Totktonada
body: > except that I'll add logs a bit later today.

Done.
created_at: 2024-11-27T21:38:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -996,6 +997,12 @@ bootstrap_leader_is_connected(struct applier **appliers, int count)
 			return true;
 		}
 	}
+	/*
path: src/box/replication.cc
user: login: Serpentian
body: Nit: we probably don't want to check appliers at all in `supervised`. The result will anyway depend on `is_supervised_bootstrap_leader`. Should be moved to the beginning of the function
created_at: 2024-11-28T00:39:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Serpentian
body: Nit: let's warn in the Doc request, that several `box.cfg.make_bootstrap_leader()` will lead to JOIN failure
created_at: 2024-11-28T00:41:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2213,6 +2214,8 @@ box_make_bootstrap_leader(void)
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
 		is_supervised_bootstrap_leader = true;
+		say_info("instance %s (this one) is assigned as a bootstrap "
path: src/box/box.cc
user: login: Serpentian
body: No need for this message. It's duplicated, since in `bootstrap_master -> box_populate_schema_space -> box_set_bootstrap_leader_record` it'll be written to `_schema` and the message will be logged from the `on_commit` trigger
created_at: 2024-11-28T00:45:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4299,6 +4299,12 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	if (is_supervised_bootstrap_leader)
path: src/box/alter.cc
user: login: Serpentian
body: I'd rather not make any condition here. `instance %s is assigned as a bootstrap leader` is enough, IMHO. We never say, whether UUID is our own or not in other log messages
created_at: 2024-11-28T00:47:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Serpentian
body: The same comment, as in message logging (https://github.com/tarantool/tarantool/pull/10859#discussion_r1861378285). AFAICS, we don't need that
created_at: 2024-11-28T00:48:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -996,6 +997,12 @@ bootstrap_leader_is_connected(struct applier **appliers, int count)
 			return true;
 		}
 	}
+	/*
path: src/box/replication.cc
user: login: Totktonada
body: Seems valid. Moved the check:

```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index cdbe424a95..627e749651 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -990,6 +990,14 @@ applier_is_bootstrap_leader(const struct applier *applier)
 static bool
 bootstrap_leader_is_connected(struct applier **appliers, int count)
 {
+       /*
+        * In the supervised strategy it is possible that we know
+        * locally that the given instance is a bootstrap leader.
+        */
+       if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+           is_supervised_bootstrap_leader)
+               return true;
+
        for (int i = 0; i < count; i++) {
                struct applier *applier = appliers[i];
                if (applier->state == APPLIER_CONNECTED &&
@@ -997,12 +1005,6 @@ bootstrap_leader_is_connected(struct applier **appliers, int count)
                        return true;
                }
        }
-       /*
-        * In the supervised strategy it is possible that we know
-        * locally that the given instance is a bootstrap leader.
-        */
-       if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED)
-               return is_supervised_bootstrap_leader;
        return false;
 }
```
created_at: 2024-11-28T08:06:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2213,6 +2214,8 @@ box_make_bootstrap_leader(void)
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
 		is_supervised_bootstrap_leader = true;
+		say_info("instance %s (this one) is assigned as a bootstrap "
path: src/box/box.cc
user: login: Totktonada
body: Agree, removed.
created_at: 2024-11-28T08:06:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4299,6 +4299,12 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	if (is_supervised_bootstrap_leader)
path: src/box/alter.cc
user: login: Totktonada
body: OK, changed:

```diff
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9eb59852a9..6db60115e2 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -4299,12 +4299,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
-	if (is_supervised_bootstrap_leader)
-		say_info("instance %s (this one) is assigned as a bootstrap "
-			 "leader", tt_uuid_str(uuid));
-	else
-		say_info("instance %s is assigned as a bootstrap leader",
-			 tt_uuid_str(uuid));
+	say_info("instance %s is assigned as a bootstrap leader",
+		 tt_uuid_str(uuid));
 	box_broadcast_ballot();
 	return 0;
 }
diff --git a/test/replication-luatest/bootstrap_strategy_test.lua b/test/replication-luatest/bootstrap_strategy_test.lua
index 7ac56f12d3..1a510a42e0 100644
--- a/test/replication-luatest/bootstrap_strategy_test.lua
+++ b/test/replication-luatest/bootstrap_strategy_test.lua
@@ -871,21 +871,8 @@ g_supervised.test_early_leader_with_replica = function(cg)
     end)
     t.helpers.retrying({}, cg.server1.assert_follows_upstream, cg.server1, 1)
 
-    -- There are three related log messages:
-    --
-    -- I> this instance is assigned as a bootstrap leader
-    -- I> instance <uuid> (this one) is assigned as a bootstrap leader
-    -- I> instance <uuid> is assigned as a bootstrap leader
-    --
-    -- The first one is issued before the logger is configured, so
-    -- goes to stderr. As far, as I know, luatest has no API to
-    -- catch it.
-    --
-    -- Two others are verified below.
-
     -- Verify a log message on a bootstrap leader.
-    local exp_msg = 'instance [0-9a-f-]+ %(this one%) is assigned as a ' ..
-        'bootstrap leader'
+    local exp_msg = 'instance [0-9a-f-]+ is assigned as a bootstrap leader'
     local found = grep_log(cg.server2, exp_msg)
     t.assert(found)
     t.assert_str_contains(found, leader_uuid)
```
created_at: 2024-11-28T08:06:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: OK, removed and things seems working, but I'm a bit afraid that there is a point, when `bootstrap_leader_uuid` is not consistent with `is_supervised_bootstrap_leader`. That's how I described this contraint:

```c
/**
 * Whether the given instance is configured to make bootstrap of
 * the replicaset.
 *
 * Has an effect only in the supervised bootstrap strategy.
 *
 * This flag have to be consistent with bootstrap_leader_uuid
 * except the following cases:
 *
 * - INSTANCE_UUID is zero UUID
 * - the bootstrap strategy is not 'supervised'
 */
extern bool is_supervised_bootstrap_leader;
```
created_at: 2024-11-28T08:06:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Ouch, missed in this push. Will do now.
created_at: 2024-11-28T08:06:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Added a remark at the end.

> If several instances call `box.cfg.make_bootstrap_leader()` to bootstrap the database, each bootstraps its own database and it leads to a JOIN failure. With great power comes great responsibility!
created_at: 2024-11-28T08:14:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Serpentian
body: > when bootstrap_leader_uuid is not consistent with is_supervised_bootstrap_leader

I was wrong. Not only the flag is not consistent with `bootstrap_leader_uuid`, but it's also looks like that it's not guaranteed, that we become a bootsrap leader, since `replicaset_find_join_master` may return another instance, not NULL or ourselves (especially, when applier to the current instance doesn't exist), `is_supervised_bootstrap_leader` will be `false` at that point.

The tests are stable, since this situation is not covered: we have only the test before the `box.cfg` but not during it (as it's done in `test_bootstrap`, but there only one `make_bootstrap_leader` is called). I won't insist on adding this test, since it seems it will be difficult to stabilize.

However, let's revert that change, flag should be set here. Sorry
created_at: 2024-11-28T10:26:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: Thank you for another glance!

I've tried to create a test case and found that it actually depends on an exact point of time, when we call `box.ctl.make_bootstrap_leader()` via console.

It mostly fails before the update, but passes 3/4 times afterwards.

I'll not add it into the pull request (don't know how to stabilize), but here it is:

<details>
<summary><code>test_several_leaders</code></summary>

```lua
g_supervised.before_test('test_several_leaders', function(cg)
    cg.replica_set = replica_set:new{}
    cg.box_cfg_1 = {
        bootstrap_strategy = 'supervised',
        -- Has only itself in upstreams and can bootstrap on its
        -- own.
        replication = {
            server.build_listen_uri('server1', cg.replica_set.id),
        },
        replication_timeout = 0.1,
    }
    cg.box_cfg_2 = {
        bootstrap_strategy = 'supervised',
        -- Has no itself in upstreams, but has another instance
        -- that advertises itself as a bootstrap leader.
        --
        -- The expectation is that server2 anyway bootstraps its
        -- own database, because box.ctl.make_bootstrap_leader()
        -- is called before box.cfg().
        replication = {
            server.build_listen_uri('server1', cg.replica_set.id),
        },
        replication_timeout = 0.1,
    }
    for i = 1, 2 do
        local alias = 'server' .. i
        cg[alias] = cg.replica_set:build_and_add_server{
            alias = alias,
            box_cfg = cg['box_cfg_' .. i],
        }
        cg['admin' .. i] = fio.pathjoin(cg[alias].workdir, 'server.admin')
        local run_before_cfg = string.format([[
            local console = require('console')
            console.listen('unix/:%s')
        ]], cg['admin' .. i])
        cg[alias].env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
    end
end)

-- XXX
g_supervised.test_several_leaders = function(cg)
    cg.server1:start({wait_until_ready = false})
    t.helpers.retrying({}, make_bootstrap_leader_initial, cg.admin1)

    cg.server2:start({wait_until_ready = false})
    t.helpers.retrying({}, make_bootstrap_leader_initial, cg.admin2)

    local query = 'ER_REPLICASET_UUID_MISMATCH'
    t.helpers.retrying({}, function()
        local found = grep_log(cg.server2, query)
        t.assert(found, ('Found %s'):format(query))
    end)
end
```
</details>
created_at: 2024-11-28T12:06:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Serpentian
body: Here's the stable version. Note, that in your version `box_cfg_1.replication` is incorrect.

<details><summary>Details</summary>
<p>

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index f4761f283f..f15df94f42 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5386,6 +5386,7 @@ bootstrap(bool *is_bootstrap_leader)
 	bool say_once = false;
 
 	while (true) {
+		ERROR_INJECT_YIELD(ERRINJ_BOOTSTRAP_DELAY);
 		box_restart_replication();
 		master = replicaset_find_join_master();
 		bootstrap_leader_uuid = master == NULL ? INSTANCE_UUID :
diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h
index b2b8ffa9e2..39ab38c10a 100644
--- a/src/lib/core/errinj.h
+++ b/src/lib/core/errinj.h
@@ -79,6 +79,7 @@ struct errinj {
 	_(ERRINJ_APPLIER_SLOW_ACK, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_APPLIER_STOP_DELAY, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_APPLIER_SUBSCRIBE_DELAY, ERRINJ_BOOL, {.bparam = false}) \
+	_(ERRINJ_BOOTSTRAP_DELAY, ERRINJ_BOOL, {.bparam = true}) \
 	_(ERRINJ_BUILD_INDEX, ERRINJ_INT, {.iparam = -1}) \
 	_(ERRINJ_BUILD_INDEX_DELAY, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_BUILD_INDEX_DISABLE_YIELD, ERRINJ_BOOL, {.bparam = false}) \
diff --git a/test/replication-luatest/bootstrap_strategy_test.lua b/test/replication-luatest/bootstrap_strategy_test.lua
index 1a510a42e0..5de3121a53 100644
--- a/test/replication-luatest/bootstrap_strategy_test.lua
+++ b/test/replication-luatest/bootstrap_strategy_test.lua
@@ -539,18 +539,22 @@ local g_supervised = t.group('gh-8509-bootstrap-strategy-supervised')
 local server2_admin
 local SOCKET_TIMEOUT = 5
 
-local function make_bootstrap_leader_initial(sockname)
+local function make_socket_exec(sockname, command, resp)
     local sock, err = socket.tcp_connect('unix/', sockname)
     t.assert_equals(err, nil, 'Connection successful')
     local greeting = sock:read(128, SOCKET_TIMEOUT)
     t.assert_str_contains(greeting, 'Tarantool', 'Connected to console')
     t.assert_str_contains(greeting, 'Lua console', 'Connected to console')
-    sock:write('box.ctl.make_bootstrap_leader()\n', SOCKET_TIMEOUT)
+    sock:write(command, SOCKET_TIMEOUT)
     local response = sock:read(8, SOCKET_TIMEOUT)
-    t.assert_equals(response, '---\n...\n', 'The call succeeded')
+    t.assert_equals(response, resp, 'The call succeeded')
     sock:close()
 end
 
+local function make_bootstrap_leader_initial(sockname)
+    make_socket_exec(sockname, 'box.ctl.make_bootstrap_leader()\n', '---\n...\n')
+end
+
 g_supervised.after_each(function(cg)
     cg.replica_set:drop()
 end)
@@ -921,3 +925,63 @@ g_supervised.test_early_leader_several_leaders = function(cg)
         t.assert(found, ('Found %s'):format(query))
     end)
 end
+
+g_supervised.before_test('test_several_leaders', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg_1 = {
+        bootstrap_strategy = 'supervised',
+        -- Has only itself in upstreams and can bootstrap on its
+        -- own.
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    cg.box_cfg_2 = {
+        bootstrap_strategy = 'supervised',
+        -- Has no itself in upstreams, but has another instance
+        -- that advertises itself as a bootstrap leader.
+        --
+        -- The expectation is that server2 anyway bootstraps its
+        -- own database, because box.ctl.make_bootstrap_leader()
+        -- is called before box.cfg().
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = cg['box_cfg_' .. i],
+        }
+        cg['admin' .. i] = fio.pathjoin(cg[alias].workdir, 'server.admin')
+        local run_before_cfg = string.format([[
+            box.error.injection.set('ERRINJ_BOOTSTRAP_DELAY', true)
+            local console = require('console')
+            console.listen('unix/:%s')
+        ]], cg['admin' .. i])
+        cg[alias].env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
+    end
+end)
+
+-- XXX
+g_supervised.test_several_leaders = function(cg)
+    cg.server1:start({wait_until_ready = false})
+    t.helpers.retrying({}, make_bootstrap_leader_initial, cg.admin1)
+
+    cg.server2:start({wait_until_ready = false})
+    t.helpers.retrying({}, make_bootstrap_leader_initial, cg.admin2)
+
+    local cmd = "box.error.injection.set('ERRINJ_BOOTSTRAP_DELAY', false)\n"
+    local response = '---\n- ok'
+    t.helpers.retrying({}, make_socket_exec, cg.admin1, cmd, response)
+    t.helpers.retrying({}, make_socket_exec, cg.admin2, cmd, response)
+
+    local query = 'ER_REPLICASET_UUID_MISMATCH'
+    t.helpers.retrying({}, function()
+        local found = grep_log(cg.server2, query)
+        t.assert(found, ('Found %s'):format(query))
+    end)
+end


```

</p>
</details> 
created_at: 2024-11-28T12:26:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: I'm a bit worrying about the errinj on replica and master both and would try to make the test case more step-by-step. Later today, I hope.
created_at: 2024-11-28T12:49:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +205,20 @@ struct event *box_on_recovery_state_event;
 /** Cached event 'tarantool.trigger.on_change'. */
 static struct event *tarantool_trigger_on_change_event;
 
+/**
+ * Whether the given instance is configured to make bootstrap of
+ * the replicaset before instance UUID is assigned to the node.
+ *
+ * Has an effect only in the supervised bootstrap strategy.
+ *
+ * Has effect only on bootstrap, ignored afterwards. This way we
+ * don't have to clear the flag.
+ *
+ * Use bootstrap_leader_uuid to judge about an actual bootstrap
+ * leader.
+ */
+static bool am_i_bootstrap_leader;
path: src/box/box.cc
user: login: Totktonada
body: The variable is renamed now, so I think I can close this discussion.
created_at: 2025-03-18T18:58:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Totktonada
body: Well, I spins here for a while, and it seems that it is too cumbersome for me to create such a test. I hope it is OK to pass it over.
created_at: 2025-03-19T09:43:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: sergepetrenko
body: Typo in the docbot request:
```
So, the following code is now possible:

```lua
box.cfg.make_bootstrap_leader()
```
^ `box.ctl.make_bootstrap_leader()`
created_at: 2025-03-20T07:00:52Z
author_association: COLLABORATOR
diff_hunk: @@ -1664,6 +1673,25 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members.
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
path: src/box/replication.cc
user: login: sergepetrenko
body: I suppose you'll have to move this check below once you introduce #11272. Otherwise you'd always choose yourself even when there is some other bootstrap leader.

TBH, I'd rather look at this whole patchset together with #11272 to better understand how it'll all turn out together.

AFAIU you're planning to implement #11272 right after this patch anyway? 
created_at: 2025-03-20T07:27:46Z
author_association: COLLABORATOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: sergepetrenko
body: Let's add some details here:

You could print `This instance (uuid) is assigned as a bootstrap leader` judging by `is_supervised_bootstrap_leader`. Would be  bit more informative IMO and more similar to the other message from `make_bootstrap_leader` itself.
created_at: 2025-03-20T07:30:47Z
author_association: COLLABORATOR
diff_hunk: @@ -1664,6 +1673,25 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members.
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
path: src/box/replication.cc
user: login: sergepetrenko
body: Probably a comment is needed here that by returning `NULL` we mean `self`, and not `none`.
created_at: 2025-03-20T07:34:39Z
author_association: COLLABORATOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: Totktonada
body: @Serpentian suggested the opposite :) See https://github.com/tarantool/tarantool/pull/10859#discussion_r1861378859.

I don't mind any option, TBH.


created_at: 2025-03-20T18:27:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Thanks! Will fix with the next push.
created_at: 2025-03-20T18:32:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1664,6 +1673,25 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members.
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
path: src/box/replication.cc
user: login: Totktonada
body: OK, it is easier to look these patches together, I agree.

In fact, I'm going to add a separate flag for the graceful bootstrap request -- things look more orthogonal this way for me. So, I'll check `is_supervised_bootstrap_leader` first and then the new graceful bootstrap leader flag.

However, other changes are needed in this function. Let's discuss, when I'll prepare the patch (I've a working draft for now, will polish it a bit).

Also, I've added `return NULL` comment in the graceful bootstrap patch, because I also found that this is not very obvious what the `NULL` retval means.

Will ping.
created_at: 2025-03-20T18:32:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,4 @@
+## feature/replication
path: changelogs/unreleased/early-make-bootstrap-leader.md
user: login: Totktonada
body: Done.
created_at: 2025-03-21T00:25:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1664,6 +1673,25 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members.
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
path: src/box/replication.cc
user: login: Totktonada
body: Pushed the graceful bootstrap implementation as the third commit here. I do it as early as possible to let you know, what I'm going to do, and some things may be inaccurate. Maybe I'll change some minor details tomorrov.

~(And I'll update the PR description tomorrow.) --- Done.~

TBH, I don't like how it is going to evolve:

* Separate waiting and decision-making functions, both customized for a strategy.
* Controlling these two functions using global flags (and, again, differently for each strategy).
* A need for synchronizing the flag values with each other and with the database initialization/recovery flow.

I think that the bootstrap/recovery decision process should be like a state machine, while the bootstrap request/graceful bootstrap request should be a kind of limited lifespan object or, maybe, a state of this state machine.

However, I think that it is better to leave more-or-less architectural solutions to the core team and provide the needed behavior in some direct way (plus some tests). Also, if I would go actually implement it, it is likely takes an order of magnitude more time for me.
created_at: 2025-03-21T00:41:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4299,6 +4299,12 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	if (is_supervised_bootstrap_leader)
path: src/box/alter.cc
user: login: sergepetrenko
body: > I'd rather not make any condition here. instance %s is assigned as a bootstrap leader is enough, IMHO. We never say, whether UUID is our own or not in other log messages

Personally I find it inconvenient determining if a particular identifier belongs to the instance we are looking at or not.

For example, messages like `got a request {term: 2, vote: 3, state = 'follower} from 2`. Who is `3`? It would be much more convenient if places like this mentioned `vote: 3 (us)` or something similar.

Same here: `Instance <uuid> is assigned as a bootstrap leader`. Which instance is this? Should I really go over all the logs and match the string `Instance uuid ...` to see if this is us or not?

What do you think @Serpentian ?
created_at: 2025-03-21T11:39:50Z
author_association: COLLABORATOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: sergepetrenko
body: Oh, ok. Let's see what @Serpentian thinks.
created_at: 2025-03-21T11:40:14Z
author_association: COLLABORATOR
diff_hunk: @@ -1690,21 +1729,101 @@ replicaset_find_join_master_supervised(void)
 	 * for a user.
 	 */
 	if (is_supervised_bootstrap_leader)
-		return NULL;
+		goto do_bootstrap;
 
-	struct replica *leader = NULL;
 	replicaset_foreach(replica) {
 		struct applier *applier = replica->applier;
 		if (applier == NULL)
 			continue;
 		if (applier_is_bootstrap_leader(applier))
 			leader = replica;
 	}
-	if (leader == NULL) {
-		tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
-			  "failed to connect to the bootstrap leader");
+
path: src/box/replication.cc
user: login: sergepetrenko
body: The diff in `replicaset_find_join_master_supervised` looks too complicated, TBH.

I'd suggest something along these lines (an incremental diff on top of your patch). I didn't try it out, it's just to illustrate my idea.

<details><summary>diff</summary>

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index fbec28fad3..878226a2b3 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5360,6 +5360,10 @@ bootstrap_master(void)
 	 */
 	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO)
 		check_bootstrap_unanimity();
+	else if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+		 is_graceful_supervised_bootstrap_requested) {
+		say_info("Graceful bootstrap succeeded");
+	}
 	engine_bootstrap_xc();
 	if (box_set_replication_synchro_queue_max_size() != 0)
 		diag_raise();
@@ -5402,6 +5406,10 @@ bootstrap_from_master(struct replica *master)
 {
 	struct applier *applier = master->applier;
 	assert(applier != NULL);
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+	    is_graceful_supervised_bootstrap_requested) {
+		say_info("Graceful bootsrtap didn't work.");
+	}
 	try {
 		applier_resume_to_state(applier, APPLIER_READY,
 					TIMEOUT_INFINITY);
diff --git a/src/box/replication.cc b/src/box/replication.cc
index 103cb6b7ae..ffadf147bc 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1694,31 +1694,13 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
-	/*
-	 * Drop the graceful bootstrap request, if any.
-	 *
-	 * Whether we're going to act as a bootstrap leader or
-	 * not, whether the request is taken into account or not,
-	 * the die is cast and the graceful bootstrap request
-	 * is not actual after execution of this function.
-	 *
-	 * Don't give a chance for code executed afterwards to see
-	 * this flag.
-	 */
-	bool graceful = is_graceful_supervised_bootstrap_requested;
-	if (graceful)
-		is_graceful_supervised_bootstrap_requested = false;
-
-	struct replica *leader = NULL;
-
 	/*
 	 * If we're known as a bootstrap leader, just bootstrap
 	 * the database.
 	 *
 	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
 	 * one has no safety checks with waiting for ballots from
-	 * other replicaset members (except when the graceful
-	 * bootstrap is requested).
+	 * other replicaset members.
 	 *
 	 * NB: It is possible to run the cycle below first and
 	 * verify other members status known at this moment.
@@ -1729,8 +1711,9 @@ replicaset_find_join_master_supervised(void)
 	 * for a user.
 	 */
 	if (is_supervised_bootstrap_leader)
-		goto do_bootstrap;
+		return NULL;
 
+	struct replica *leader = NULL;
 	replicaset_foreach(replica) {
 		struct applier *applier = replica->applier;
 		if (applier == NULL)
@@ -1738,92 +1721,11 @@ replicaset_find_join_master_supervised(void)
 		if (applier_is_bootstrap_leader(applier))
 			leader = replica;
 	}
-
-	/* If the leader is found, just return it. */
-	if (leader != NULL) {
-		if (tt_uuid_is_equal(&leader->uuid, &INSTANCE_UUID))
-			goto do_bootstrap;
-		goto do_recovery;
-	}
-
-	/*
-	 * If we see this flag at this point, it means that the
-	 * waiting logic from replicaset_is_connected() is
-	 * finished. There are three situations, when it is
-	 * possible:
-	 *
-	 * 1. A bootstrap leader is found.
-	 * 2. All the instances from box.cfg.replication are
-	 *    connected, but no one is a bootstrap leader.
-	 * 3. box.cfg.replication_connect_timeout is reached.
-	 *
-	 * The logic above covers bullet 1. We either in 2 or 3.
-	 * In any of these cases, let's continue as a bootstrap
-	 * leader.
-	 */
-	if (graceful)
-		goto do_bootstrap;
-
-	goto do_error;
-
-do_bootstrap:
-	/*
-	 * Keep is_supervised_bootstrap_leader and
-	 * bootstrap_leader_uuid is sync. The latter
-	 * is set in bootstrap() (from box.cc) after
-	 * returning from this function.
-	 */
-	is_supervised_bootstrap_leader = true;
-	/*
-	 * If there is a graceful bootstrap request, report how it
-	 * is finished.
-	 */
-	if (graceful) {
-		say_info("graceful bootstrap request succeeds: no bootstrap "
-			 "leader is found in connected peers, so the current "
-			 "instance proceeds as a bootstrap leader");
-	}
-	/*
-	 * Assign the current instance as a bootstrap
-	 * leader.
-	 */
-	return NULL;
-do_recovery:
-	assert(leader != NULL);
-	/* Sync the flag. */
-	is_supervised_bootstrap_leader = false;
-	/*
-	 * If there is a graceful bootstrap request, report how it
-	 * is finished.
-	 */
-	if (graceful) {
-		say_info("graceful bootstrap request failed: other bootstrap "
-			 "leader is found within connected peers, proceed as a "
-			 "regular replica");
+	if (leader == NULL && !is_graceful_supervised_bootstrap_requested) {
+		tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
+			  "failed to connect to the bootstrap leader");
 	}
-	/* Return the bootstrap leader. */
 	return leader;
-do_error:
-	/* Sync the flag. */
-	is_supervised_bootstrap_leader = false;
-	/*
-	 * If the graceful bootstrap was requested, we go to
-	 * do_bootstrap or do_recovery.
-	 */
-	assert(!graceful);
-	/*
-	 * This error is reported if replicaset_connect() reaches
-	 * the end of box.cfg.replication_connect_timeout timeout
-	 * and no bootstrap leader is found across the peers
-	 * (including the current instance).
-	 *
-	 * Also, it is reported immediately if box.cfg() is called
-	 * without configured upstreams and
-	 * box.ctl.make_bootstrap_leader(<...>) is not called
-	 * beforehand.
-	 */
-	tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
-		  "failed to connect to the bootstrap leader");
 }
 
 struct replica *
```

</details>
created_at: 2025-03-21T13:09:44Z
author_association: COLLABORATOR
diff_hunk: @@ -1664,6 +1673,25 @@ replicaset_find_join_master_cfg(void)
 static struct replica *
 replicaset_find_join_master_supervised(void)
 {
+	/*
+	 * If we're known as a bootstrap leader, just bootstrap
+	 * the database.
+	 *
+	 * Unlike the 'auto' bootstrap strategy, the 'supervised'
+	 * one has no safety checks with waiting for ballots from
+	 * other replicaset members.
+	 *
+	 * NB: It is possible to run the cycle below first and
+	 * verify other members status known at this moment.
+	 * However, since we don't wait for connecting to all the
+	 * instances, it has a low chance to catch a problem.
+	 * As result, it would just make the result of calling
+	 * this function less predictable without visible gains
+	 * for a user.
+	 */
+	if (is_supervised_bootstrap_leader)
path: src/box/replication.cc
user: login: sergepetrenko
body: Thanks!
In the review below I've suggested a slightly different approach compared to your one.

created_at: 2025-03-21T13:39:38Z
author_association: COLLABORATOR
diff_hunk: @@ -1690,21 +1729,101 @@ replicaset_find_join_master_supervised(void)
 	 * for a user.
 	 */
 	if (is_supervised_bootstrap_leader)
-		return NULL;
+		goto do_bootstrap;
 
-	struct replica *leader = NULL;
 	replicaset_foreach(replica) {
 		struct applier *applier = replica->applier;
 		if (applier == NULL)
 			continue;
 		if (applier_is_bootstrap_leader(applier))
 			leader = replica;
 	}
-	if (leader == NULL) {
-		tnt_raise(ClientError, ER_CFG, "bootstrap_strategy",
-			  "failed to connect to the bootstrap leader");
+
path: src/box/replication.cc
user: login: Totktonada
body: Good idea, thanks! I've updated the top patch following it.
created_at: 2025-03-21T16:13:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: We shouldn't set `is_graceful_supervised_bootstrap_requested = true` in the case, I think. Will fix.
created_at: 2025-03-24T01:43:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	} else if (graceful) {
+		say_info("graceful bootstrap is requested: the instance "
+			 "is going to check whether a bootstrap leader "
+			 "already exists and if the check fails the instance "
+			 "takes this role");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: We need to call `box_broadcast_ballot()` here. Otherwise `replicaset_connect()` freezes infinitely on the fiber cond and doesn't repeat the `replicaset_is_connected()` call.
created_at: 2025-03-24T01:46:21Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: Totktonada
body: Also I need an exception for 037bd58cdb4b2aaebeacffddd49e731d311d6638 for the supervised bootstrap strategy.

Otherwise I can't run `box.cfg(<...>)` with all my configuration and `read_only = true` and make something like this from the coordinator:

```lua
box.ctl.make_bootstrap_leader({graceful = true})
box.cfg({read_only = false})
```

Proposal:

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index 4aa584c3df..df3f8ee1fd 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5350,8 +5352,17 @@ check_global_ids_integrity(void)
 static void
 bootstrap_master(void)
 {
-       /* Do not allow to bootstrap a readonly instance as master. */
-       if (cfg_geti("read_only") == 1) {
+       /*
+        * Do not allow to bootstrap a readonly instance as master.
+        *
+        * Allow an exception for the supervised bootstrap
+        * strategy, because the commmand to bootstrap may be
+        * received during the in progress box.cfg() call and
+        * there is no way to change box.cfg.read_only at this
+        * time.
+        */
+       if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
+           cfg_geti("read_only") == 1) {
                tnt_raise(ClientError, ER_BOOTSTRAP_READONLY);
        }
        /*
```
created_at: 2025-03-24T02:06:41Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: Totktonada
body: In the scenario, when I run `box.cfg()` with all my configuration as part of the YAML config startup flow and wait for coordinator's command, I've a problem with a replicaset that contains just one instance.

First, we lean on `box_broadcast_ballot()`, but if `box.cfg.replication` doesn't contain the current instance (that is completely OK, because we can lack `box.cfg.listen` too), the `replicaset_connect()` code will not wake up.

We possibly need something like the following:

<details>
<summary>Diff</summary>

```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index a8156c33dd..6fd8718664 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -225,6 +225,15 @@ struct replicaset_connect_state {
        struct fiber_cond wakeup;
 };

+static struct fiber_cond *replicaset_connect_wakeup_cond = NULL;
+
+void
+replicaset_connect_wakeup(void)
+{
+       if (replicaset_connect_wakeup_cond != NULL)
+               fiber_cond_signal(replicaset_connect_wakeup_cond);
+}
+
 static void
 replicaset_set_sync_quorum(const struct replicaset_connect_state *state)
 {
@@ -1333,6 +1351,7 @@ replicaset_connect(const struct uri_set *uris,
                        trigger_clear(&trigger->base);
                }
        });
+       replicaset_connect_wakeup_cond = &state.wakeup;
        while (!replicaset_is_connected(&state, appliers, count,
                                        connect_quorum)) {
                double wait_start = ev_monotonic_now(loop());
@@ -1342,6 +1361,7 @@ replicaset_connect(const struct uri_set *uris,
                }
                timeout -= ev_monotonic_now(loop()) - wait_start;
        }
+       replicaset_connect_wakeup_cond = NULL;
        if (state.connected < count) {
                say_crit("failed to connect to %d out of %d replicas",
                         count - state.connected, count);
diff --git a/src/box/replication.h b/src/box/replication.h
index bb8ade1ea6..9da99b820c 100644
--- a/src/box/replication.h
+++ b/src/box/replication.h
@@ -705,6 +705,12 @@ void
 replicaset_connect(const struct uri_set *uris,
                   bool connect_quorum, bool keep_connect);

+/**
+ * XXX
+ */
+void
+replicaset_connect_wakeup(void);
+
 /**
  * Reload replica URIs.
  *
diff --git a/src/box/box.cc b/src/box/box.cc
index 4aa584c3df..df3f8ee1fd 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2248,7 +2248,6 @@ box_set_bootstrap_leader_record(void)
 int
 box_make_bootstrap_leader(bool graceful)
 {
-       is_graceful_supervised_bootstrap_requested = graceful;
        if (graceful && is_box_configured) {
                say_info("graceful bootstrap request is discarded: the "
                         "instance is already bootstrapped");
@@ -2258,6 +2257,9 @@ box_make_bootstrap_leader(bool graceful)
                         "is going to check whether a bootstrap leader "
                         "already exists and if the check fails the instance "
                         "takes this role");
+               is_graceful_supervised_bootstrap_requested = graceful;
+               //box_broadcast_ballot();
+               replicaset_connect_wakeup();
                return 0;
        }
```
</details>

Second, we don't even step into this code, because `replicaset_connect()` just exits if there are no upstreams. I propose an exception for the supervised strategy:

<details>
<summary>Diff</summary>

```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index a8156c33dd..6fd8718664 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1251,7 +1265,11 @@ replicaset_connect(const struct uri_set *uris,
        memset(&state, 0, sizeof(state));
        fiber_cond_create(&state.wakeup);

-       if (uris->uri_count == 0) {
+       /*
+        * An exception for supervised XXX.
+        */
+       if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
+           uris->uri_count == 0) {
                replicaset_set_sync_quorum(&state);
                /* Cleanup the replica set. */
                replicaset_update(NULL, 0, false);
```
</details>
created_at: 2025-03-24T02:18:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4299,6 +4299,12 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	if (is_supervised_bootstrap_leader)
path: src/box/alter.cc
user: login: Serpentian
body: > Personally I find it inconvenient determining if a particular identifier belongs to the instance we are looking at or not.

I agree, but we should not solve that in the scope of this PR. IMHO, it should be done on the lower level, for example in `tt_uuid_str_say`, so that it prints `<uuid> (current)` instead of just `<uuid>`? This condition so high in the code looks crutchy and it'll require patching a lot of log messages, if we go this way and decide to change all log msgs.

created_at: 2025-04-01T16:20:06Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: sergepetrenko
body: Makes sense, I agree.
created_at: 2025-04-07T14:54:15Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: Fixed this way:

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index 2b65ff26ec..834f9f3717 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2248,12 +2248,13 @@ box_set_bootstrap_leader_record(void)
 int
 box_make_bootstrap_leader(bool graceful)
 {
-	is_graceful_supervised_bootstrap_requested = graceful;
 	if (graceful && is_box_configured) {
+		is_graceful_supervised_bootstrap_requested = false;
 		say_info("graceful bootstrap request is discarded: the "
 			 "instance is already bootstrapped");
 		return 0;
 	} else if (graceful) {
+		is_graceful_supervised_bootstrap_requested = true;
 		say_info("graceful bootstrap is requested: the instance "
 			 "is going to check whether a bootstrap leader "
 			 "already exists and if the check fails the instance "
@@ -2261,6 +2262,8 @@ box_make_bootstrap_leader(bool graceful)
 		return 0;
 	}
 
+	is_graceful_supervised_bootstrap_requested = false;
+
 	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
 		/*
 		 * Can't store the UUID of the given instance as
```

TBH, it is for consistency and I don't know any scenario, where `is_graceful_supervised_bootstrap_requested == true` would affect anything in case of already configured database:

* We don't go to `bootstrap_master()` or `bootstrap_from_master()`, do we? Well, maybe in some rebootstrap scenario.
* If some other bootstrap leader appears, we just make the flag `false` in alter.cc.

So, I have no test case for this change.
created_at: 2025-04-08T08:59:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	} else if (graceful) {
+		say_info("graceful bootstrap is requested: the instance "
+			 "is going to check whether a bootstrap leader "
+			 "already exists and if the check fails the instance "
+			 "takes this role");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: Added the `box_broadcast_ballot()` call (if there is an instance uuid), wrote a test case. The incremental diff is below.

<details>
<summary>Diff</summary>

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index 834f9f3717..add6c255a8 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2248,6 +2248,18 @@ box_set_bootstrap_leader_record(void)
 int
 box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+
 	if (graceful && is_box_configured) {
 		is_graceful_supervised_bootstrap_requested = false;
 		say_info("graceful bootstrap request is discarded: the "
@@ -2259,6 +2271,8 @@ box_make_bootstrap_leader(bool graceful)
 			 "is going to check whether a bootstrap leader "
 			 "already exists and if the check fails the instance "
 			 "takes this role");
+		if (!tt_uuid_is_nil(&INSTANCE_UUID))
+			box_broadcast_ballot();
 		return 0;
 	}
 
@@ -2275,16 +2289,6 @@ box_make_bootstrap_leader(bool graceful)
 		say_info("this instance is assigned as a bootstrap leader");
 		return 0;
 	}
-	/* Bootstrap strategy is read by the time instance uuid is known. */
-	assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
-	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 tt_sprintf("bootstrap_strategy = '%s'",
-				    cfg_gets("bootstrap_strategy")),
-			 "promoting the bootstrap leader via "
-			 "box.ctl.make_bootstrap_leader()");
-		return -1;
-	}
 	if (is_box_configured) {
 		if (box_check_writable() != 0)
 			return -1;
diff --git a/test/replication-luatest/bootstrap_strategy_test.lua b/test/replication-luatest/bootstrap_strategy_test.lua
index 009f10dfa2..0201c5b2f5 100644
--- a/test/replication-luatest/bootstrap_strategy_test.lua
+++ b/test/replication-luatest/bootstrap_strategy_test.lua
@@ -539,13 +539,21 @@ local g_supervised = t.group('gh-8509-bootstrap-strategy-supervised')
 local server2_admin
 local SOCKET_TIMEOUT = 5
 
-local function make_bootstrap_leader_initial(sockname)
+local function make_bootstrap_leader_initial(sockname, opts)
+    opts = opts or {}
+    local command
+    if opts.graceful then
+        command = 'box.ctl.make_bootstrap_leader({graceful = true})'
+    else
+        command = 'box.ctl.make_bootstrap_leader()'
+    end
+
     local sock, err = socket.tcp_connect('unix/', sockname)
     t.assert_equals(err, nil, 'Connection successful')
     local greeting = sock:read(128, SOCKET_TIMEOUT)
     t.assert_str_contains(greeting, 'Tarantool', 'Connected to console')
     t.assert_str_contains(greeting, 'Lua console', 'Connected to console')
-    sock:write('box.ctl.make_bootstrap_leader()\n', SOCKET_TIMEOUT)
+    sock:write(command .. '\n', SOCKET_TIMEOUT)
     local response = sock:read(8, SOCKET_TIMEOUT)
     t.assert_equals(response, '---\n...\n', 'The call succeeded')
     sock:close()
@@ -1099,3 +1107,75 @@ g_graceful_supervised.test_other_leader = function(cg)
         t.assert(found)
     end)
 end
+
+g_graceful_supervised.before_test('test_after_box_cfg', function(cg)
+    cg.replica_set = replica_set:new{}
+    cg.box_cfg = {
+        bootstrap_strategy = 'supervised',
+        replication = {
+            server.build_listen_uri('server1', cg.replica_set.id),
+            server.build_listen_uri('server2', cg.replica_set.id),
+        },
+        replication_timeout = 0.1,
+    }
+    for i = 1, 2 do
+        local alias = 'server' .. i
+        local box_cfg = table.copy(cg.box_cfg)
+        box_cfg.instance_uuid = ({uuid1, uuid2})[i]
+        cg[alias] = cg.replica_set:build_and_add_server{
+            alias = alias,
+            box_cfg = box_cfg,
+        }
+    end
+
+    cg.server1_admin = fio.pathjoin(cg.server1.workdir, 'server1.admin')
+    local run_before_cfg = string.format([[
+        local console = require('console')
+        console.listen('unix/:%s')
+    ]], cg.server1_admin)
+
+    cg.server1.env.TARANTOOL_RUN_BEFORE_BOX_CFG = run_before_cfg
+end)
+
+-- Run box.cfg() first, wait until server1 connects to all the
+-- peers and then issue the graceful bootstrap request.
+--
+-- Similar to test_no_leader, but the graceful bootstrap request
+-- is issued after box.cfg() (and after all the peers are
+-- connected).
+--
+-- This way we verify that the graceful bootstrap request wakes up
+-- replicaset_connect(), where we wait for new connected peers, a
+-- new bootstrap leader, a change in the local supervised
+-- bootstrap flags (set by box.cfg.make_bootstrap_leader(<...>)).
+g_graceful_supervised.test_after_box_cfg = function(cg)
+    -- Start both instances, run box.cfg() on them.
+    cg.replica_set:start({wait_until_ready = false})
+
+    -- Wait until server1 connects to itself and server2.
+    t.helpers.retrying({}, function()
+        local found_1 = grep_log(cg.server1, 'remote master 11111111')
+        local found_2 = grep_log(cg.server1, 'remote master 22222222')
+        t.assert(found_1 ~= nil and found_2 ~= nil)
+    end)
+
+    -- Run box.cfg.make_bootstrap_leader({graceful = true}).
+    t.helpers.retrying({}, function()
+        make_bootstrap_leader_initial(cg.server1_admin, {graceful = true})
+    end)
+
+    -- Now the database should be bootstrapped.
+    cg.server1:wait_until_ready()
+    cg.server2:wait_until_ready()
+
+    -- Verify that server1 was the bootstrap leader.
+    t.assert_equals(cg.server1:get_instance_id(), 1)
+
+    -- Verify that a message about the reason why this instance
+    -- is a bootstrap leader is issued.
+    local exp_msg = 'graceful bootstrap request succeeded: no bootstrap ' ..
+        'leader is found in connected peers, so the current instance ' ..
+        'proceeds as a bootstrap leader'
+    local found = grep_log(cg.server1, exp_msg)
+    t.assert(found)
+end
```
</details>
created_at: 2025-04-08T09:00:01Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: Totktonada
body: I've added a commit that adds this exception (as shown above). The test cases are scary and have a lot of boilerplace code. But at least we have some regression testing.
created_at: 2025-04-08T09:10:20Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: Totktonada
body: It is still on me, but I would like to review other parts for now.
created_at: 2025-04-08T09:12:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
path: src/box/box.cc
user: login: sergepetrenko
body: I think you should also reset `is_graceful_supervised_bootstrap_requested` and `is_supervised_bootstrap_leader` flags in `box_set_bootstrap_strategy()`, if strategy happens to be not 'supervised'.
created_at: 2025-04-08T10:55:23Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+
+	if (graceful && is_box_configured) {
+		is_graceful_supervised_bootstrap_requested = false;
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	} else if (graceful) {
+		is_graceful_supervised_bootstrap_requested = true;
+		say_info("graceful bootstrap is requested: the instance "
+			 "is going to check whether a bootstrap leader "
+			 "already exists and if the check fails the instance "
+			 "takes this role");
+		if (!tt_uuid_is_nil(&INSTANCE_UUID))
+			box_broadcast_ballot();
path: src/box/box.cc
user: login: sergepetrenko
body: Why do you need a `box_broadcast_ballot()` here?
created_at: 2025-04-08T11:15:50Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
path: src/box/box.cc
user: login: sergepetrenko
body: I suggest the following incremental diff here. Would look simpler IMO:
```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index 0db30a1ddf..8d38a72bd8 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2245,39 +2245,27 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(bool graceful)
+static int
+box_make_bootstrap_leader_graceful(void)
 {
-	/* Bootstrap strategy is read by the time instance uuid is known. */
-	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
-	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
-		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 tt_sprintf("bootstrap_strategy = '%s'",
-				    cfg_gets("bootstrap_strategy")),
-			 "promoting the bootstrap leader via "
-			 "box.ctl.make_bootstrap_leader()");
-		return -1;
-	}
-
-	if (graceful && is_box_configured) {
-		is_graceful_supervised_bootstrap_requested = false;
-		say_info("graceful bootstrap request is discarded: the "
-			 "instance is already bootstrapped");
-		return 0;
-	} else if (graceful) {
-		is_graceful_supervised_bootstrap_requested = true;
-		say_info("graceful bootstrap is requested: the instance "
-			 "is going to check whether a bootstrap leader "
-			 "already exists and if the check fails the instance "
-			 "takes this role");
-		if (!tt_uuid_is_nil(&INSTANCE_UUID))
-			box_broadcast_ballot();
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped.");
 		return 0;
 	}
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role.");
+	if (!tt_uuid_is_nil(&INSTANCE_UUID))
+		box_broadcast_ballot();
+	return 0;
+}
 
-	is_graceful_supervised_bootstrap_requested = false;
-
+static int
+box_make_bootstrap_leader_nongraceful(void)
+{
 	if (tt_uuid_is_nil(&INSTANCE_UUID)) {
 		/*
 		 * Can't store the UUID of the given instance as
@@ -2286,22 +2274,40 @@ box_make_bootstrap_leader(bool graceful)
 		 * this action till the bootstrap.
 		 */
 		is_supervised_bootstrap_leader = true;
-		say_info("this instance is assigned as a bootstrap leader");
+		say_info("This instance is assigned as a bootstrap leader.");
 		return 0;
-	}
-	if (is_box_configured) {
-		if (box_check_writable() != 0)
-			return -1;
-		/* Ballot broadcast will happen in an on_commit trigger. */
-		return box_set_bootstrap_leader_record();
-	} else {
+	} else if (!is_box_configured) {
 		bootstrap_leader_uuid = INSTANCE_UUID;
 		is_supervised_bootstrap_leader = true;
 		box_broadcast_ballot();
 		return 0;
+	} else {
+		if (box_check_writable() != 0)
+			return -1;
+		/* Ballot broadcast will happen in an on_commit trigger. */
+		return box_set_bootstrap_leader_record();
 	}
 }
 
+int
+box_make_bootstrap_leader(bool graceful)
+{
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    botstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+	if (graceful)
+		return box_make_bootstrap_leader_graceful();
+	else
+		return box_make_bootstrap_leader_nongraceful();
+}
+
 void
 box_set_replication_sync_lag(void)
 {
```
created_at: 2025-04-08T11:22:07Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+
+	if (graceful && is_box_configured) {
+		is_graceful_supervised_bootstrap_requested = false;
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	} else if (graceful) {
+		is_graceful_supervised_bootstrap_requested = true;
+		say_info("graceful bootstrap is requested: the instance "
+			 "is going to check whether a bootstrap leader "
+			 "already exists and if the check fails the instance "
+			 "takes this role");
+		if (!tt_uuid_is_nil(&INSTANCE_UUID))
+			box_broadcast_ballot();
path: src/box/box.cc
user: login: Totktonada
body: To wake up `replicaset_connect()`, see the details in https://github.com/tarantool/tarantool/pull/10859#discussion_r2009347949. I'll add a remark in the code.
created_at: 2025-04-08T12:34:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
path: src/box/box.cc
user: login: Totktonada
body: I think it worth to do. It doesn't affect usual scenarios, where the strategy is not changed. However, it brings more expected behavior in some unusual ones, where the strategy is changed. Thanks for the idea!
created_at: 2025-04-08T12:48:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: OTOH, we don't have to turn off the flag within this logic and it becomes simpler. The flag is dropped after the request is finished. I'll update this code a bit on the next push.
created_at: 2025-04-08T13:36:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
path: src/box/box.cc
user: login: Totktonada
body: I mostly took it, but in a bit more conservative way (without switching if branches and so on). In particular:

* splitted to the two functions
* titlecased the log messages

I agree, now it looks simpler.
created_at: 2025-04-08T14:35:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
+	    bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED) {
+		assert(bootstrap_strategy != BOOTSTRAP_STRATEGY_INVALID);
+		diag_set(ClientError, ER_UNSUPPORTED,
+			 tt_sprintf("bootstrap_strategy = '%s'",
+				    cfg_gets("bootstrap_strategy")),
+			 "promoting the bootstrap leader via "
+			 "box.ctl.make_bootstrap_leader()");
+		return -1;
+	}
+
+	if (graceful && is_box_configured) {
+		is_graceful_supervised_bootstrap_requested = false;
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	} else if (graceful) {
+		is_graceful_supervised_bootstrap_requested = true;
+		say_info("graceful bootstrap is requested: the instance "
+			 "is going to check whether a bootstrap leader "
+			 "already exists and if the check fails the instance "
+			 "takes this role");
+		if (!tt_uuid_is_nil(&INSTANCE_UUID))
+			box_broadcast_ballot();
path: src/box/box.cc
user: login: Totktonada
body: Added a short remark:

```c
/* Wake up replicaset_connect() if the flag is turn on. */
if (!tt_uuid_is_nil(&INSTANCE_UUID))
        box_broadcast_ballot();
```
created_at: 2025-04-08T14:36:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
path: src/box/box.cc
user: login: Totktonada
body: Added in the corresponding commits.
created_at: 2025-04-08T14:37:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2246,8 +2246,21 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	is_graceful_supervised_bootstrap_requested = graceful;
+	if (graceful && is_box_configured) {
+		say_info("graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
path: src/box/box.cc
user: login: Totktonada
body: So, irrelevant now.
created_at: 2025-04-08T14:39:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2208,6 +2212,7 @@ box_make_bootstrap_leader(void)
 		return box_set_bootstrap_leader_record();
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
+		is_supervised_bootstrap_leader = true;
path: src/box/box.cc
user: login: Serpentian
body:  I don't mind passing it over
created_at: 2025-04-08T16:13:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: Serpentian
body: https://github.com/tarantool/tarantool/pull/10859#discussion_r2023182732
created_at: 2025-04-08T16:19:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: Serpentian
body: Let's solve the problem systematically and not in just one place. It's difficult to read all logs, including the logs from the third party modules,  log.info should also print the postfix, when possible, as well as all says in tarantool
created_at: 2025-04-08T16:24:01Z
author_association: CONTRIBUTOR
path: changelogs/unreleased/graceful-make-bootstrap-leader.md
user: login: Totktonada
body: Added as a separate commit on top of the patchset.
created_at: 2025-04-08T19:00:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2253,8 +2255,31 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
+{
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	}
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
path: src/box/box.cc
user: login: Serpentian
body: Nit: it should be in docs, not in the logs, IMHO. "Graceful bootstrap is requested" seems enough
And it seems we don't start log msgs with capital letters
created_at: 2025-04-09T11:53:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5385,8 +5385,17 @@ check_global_ids_integrity(void)
 static void
 bootstrap_master(void)
 {
-	/* Do not allow to bootstrap a readonly instance as master. */
-	if (cfg_geti("read_only") == 1) {
+	/*
+	 * Do not allow to bootstrap a readonly instance as master.
+	 *
+	 * Allow an exception for the supervised bootstrap
path: src/box/box.cc
user: login: Serpentian
body: Nit: it'd be also great to add motivation to the commit msg
created_at: 2025-04-09T16:21:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1251,7 +1260,13 @@ replicaset_connect(const struct uri_set *uris,
 	memset(&state, 0, sizeof(state));
 	fiber_cond_create(&state.wakeup);
 
-	if (uris->uri_count == 0) {
+	/*
+	 * The supervised bootstrap strategy has an exception
+	 * here, because it waits not only for replicas, but also
+	 * for local bootstrap leadership flags.
+	 */
+	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
path: src/box/replication.cc
user: login: Serpentian
body: A meaningful log msg would be great here. This is not very self explanatory:

```
2025-04-09 19:29:06.848 [378076] main/104/interactive box.cc:5573 I> instance uuid 28e82fd7-34f9-41f7-8595-710acc2ed917
2025-04-09 19:29:06.848 [378076] main/104/interactive replication.cc:1294 I> connecting to 0 replicas
```
created_at: 2025-04-09T16:34:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,5 @@
+## feature/replication
+
+* Now `box.cfg({bootstrap_strategy = 'supervised'})` without upstreams waits
+  for the `box.ctl.make_bootstrap_leader(<...>)` command till the replication
+  connect timeout instead of an immediate fail.
path: changelogs/unreleased/single-instance-in-supervised-bs.md
user: login: Serpentian
body: Motivatiom in the commit msg. Ditto
created_at: 2025-04-09T16:34:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2253,8 +2255,31 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
+{
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	}
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
path: src/box/box.cc
user: login: Totktonada
body: > Nit: it should be in docs, not in the logs, IMHO. "Graceful bootstrap is requested" seems enough

I'm OK to re-write it in any way you and Serge want (so I would like to hear him first), but personally I found it nice to let a user know what is going on. I see nothing bad in self-explanatory log messages, alerts, errors.

There are different variants to achieve this goal, in fact. We can add IDs or URIs to log messages and have a website section that explains them. That would be even better, because it allows to add some details after a release.

Just 'find it somewhere in docs' feels unfriendly for me.

But, again, that's just my feelings, while the component is not mine and the decisions are on you and Serge.

> And it seems we don't start log msgs with capital letters

Hm. I did it after Serge's [comment](https://github.com/tarantool/tarantool/pull/10859#discussion_r2032976216). Don't know, whether we have some consistent approach here. The sensence-alive formatting feels a bit better for my taste. I'll change it (if needed) once you and Serge got a decision :)
created_at: 2025-04-09T23:10:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5385,8 +5385,17 @@ check_global_ids_integrity(void)
 static void
 bootstrap_master(void)
 {
-	/* Do not allow to bootstrap a readonly instance as master. */
-	if (cfg_geti("read_only") == 1) {
+	/*
+	 * Do not allow to bootstrap a readonly instance as master.
+	 *
+	 * Allow an exception for the supervised bootstrap
path: src/box/box.cc
user: login: Totktonada
body: Sorry for that. I had feeling that the documentation request gives enough details. But after your remark I looked on it once again and added the following context information into the commit message:

> The idea of the `supervised` bootstrap strategy spins around some external logic/agent/coordinator that makes decisions about a bootstrap leader.
>
> There are two possible approaches to bootstrap a replicaset:
>
> 1. At first, wait for the agent's decision. Do the first `box.cfg()` on all the instances after this. The bootstrap leader receives the desired configuration plus `read_only = false` (and the command to go to bootstrap). All the other replicaset peers receives the desired configuration plus `read_only = true`.
>
>    This way we can't use iproto to receive the decision and there are different ways to overcome it. For example, cartridge has its own implementation of a subset of the iproto protocol to receive such pre-box-cfg commands.
>
>    Another possible approach is to implement a separate daemon that receives remote commands and feeds them to tarantool admin console.
> 2. Call `box.cfg()` first with a desired configuration plus `read_only = true` on all the peers. Wait for the agent's decision. The chosen bootstrap leader receives the command to go to bootstrap plus `box.cfg({read_only = false})`.
>
> The second approach feels more native to tarantool and just simpler. This commit allows it. It is going to be used in the `native` bootstrap strategy in #10857.
>
> There are alternatives, in fact: see #11286 for some of them. However, I still think that the given one-line solution worth it (at least until we have something better designed and implemented).

I hope it brings enough background to have a vision about cons and pros behind the idea of the commit.
created_at: 2025-04-10T00:08:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1251,7 +1260,13 @@ replicaset_connect(const struct uri_set *uris,
 	memset(&state, 0, sizeof(state));
 	fiber_cond_create(&state.wakeup);
 
-	if (uris->uri_count == 0) {
+	/*
+	 * The supervised bootstrap strategy has an exception
+	 * here, because it waits not only for replicas, but also
+	 * for local bootstrap leadership flags.
+	 */
+	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
path: src/box/replication.cc
user: login: Totktonada
body: Good point, thanks!

Done this way:

```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index 347c0b62de..d74a3a028f 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1291,7 +1291,11 @@ replicaset_connect(const struct uri_set *uris,
 	for (; count < uris->uri_count; count++)
 		appliers[count] = applier_new(&uris->uris[count]);
 
-	say_info("connecting to %d replicas", count);
+	if (count == 0)
+		say_info("no upstreams are configured, waiting for the "
+			 "bootstrap command");
+	else
+		say_info("connecting to %d replicas", count);
 
 	if (!connect_quorum) {
 		/*
```
created_at: 2025-04-10T00:08:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,5 @@
+## feature/replication
+
+* Now `box.cfg({bootstrap_strategy = 'supervised'})` without upstreams waits
+  for the `box.ctl.make_bootstrap_leader(<...>)` command till the replication
+  connect timeout instead of an immediate fail.
path: changelogs/unreleased/single-instance-in-supervised-bs.md
user: login: Totktonada
body: I've added some context information. Sorry that I didn't do that from scratch.

> The `supervised` bootstrap strategy allows to run `box.cfg()` with desired initial configuration first and then issue a command to bootstrap the database on a particular instance. The instances are waiting for the bootstrap command during the replication connect timeout period (30 seconds by default).
>
> However, before this commit this flow doesn't work for a single-instance replicaset without itself in `box.cfg.replication` (which is fine, because it is fine to don't have `box.cfg.listen` at all).
>
> This difference requires to implement two different replicaset bootstrap flows that would significantly complicate a logic in #10857 that implements the `native` bootstrap strategy.
>
> However, we can eliminate the difference on the lower level and follow the same logic for the single-instance replicaset as we do for the multi-instance replicaset. And it magically simplifies the upper level logic too. Win-win.
>
> This commit makes the initial `box.cfg()` wait for the bootstrap leader in the `supervised` bootstrap strategy even if `box.cfg.replication` is `nil` or empty.
created_at: 2025-04-10T00:08:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2253,8 +2255,31 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
+{
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	}
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
path: src/box/box.cc
user: login: Serpentian
body: @sergepetrenko 
created_at: 2025-04-10T02:20:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5357,8 +5393,15 @@ bootstrap_master(void)
 	 * With "auto" bootstrap strategy refuse to boot unless everyone agrees
 	 * this node is the bootstrap leader.
 	 */
-	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO)
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO) {
 		check_bootstrap_unanimity();
+	} else if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+		 is_graceful_supervised_bootstrap_requested) {
path: src/box/box.cc
user: login: sergepetrenko
body: Nit: wrong indentation:
```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index 67dfe2e357..d9ed828f5f 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -5396,7 +5396,7 @@ bootstrap_master(void)
        if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO) {
                check_bootstrap_unanimity();
        } else if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
-                is_graceful_supervised_bootstrap_requested) {
+                  is_graceful_supervised_bootstrap_requested) {
                say_info("Graceful bootstrap request succeeded: no bootstrap "
                         "leader is found in connected peers, so the current "
                         "instance proceeds as a bootstrap leader");

```
created_at: 2025-04-10T11:17:27Z
author_association: COLLABORATOR
diff_hunk: @@ -2273,7 +2273,7 @@ box_make_bootstrap_leader_graceful(void)
 
 	/* Wake up replicaset_connect() if the flag is turn on. */
 	if (!tt_uuid_is_nil(&INSTANCE_UUID))
-		box_broadcast_ballot();
+		replicaset_connect_wakeup();
path: src/box/box.cc
user: login: sergepetrenko
body: Nit: let's drop `box_broadcast_ballot()` here and below in a separate commit with a proper explanation on why it isn't needed or at least mention it in this commit message.
created_at: 2025-04-10T11:23:30Z
author_association: COLLABORATOR
diff_hunk: @@ -1251,7 +1260,13 @@ replicaset_connect(const struct uri_set *uris,
 	memset(&state, 0, sizeof(state));
 	fiber_cond_create(&state.wakeup);
 
-	if (uris->uri_count == 0) {
+	/*
+	 * The supervised bootstrap strategy has an exception
+	 * here, because it waits not only for replicas, but also
+	 * for local bootstrap leadership flags.
+	 */
+	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
path: src/box/replication.cc
user: login: sergepetrenko
body: Let's please also check for `replicaset_state == REPLICASET_BOOTSRTAP || replicaset_state == REPLICASET_JOIN` here. Otherwise you'll log the 'waiting for the bootstrap command' line even if it's just a replication reconfiguration on a working instance.
```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index d74a3a028f..e926e5b254 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1265,8 +1265,10 @@ replicaset_connect(const struct uri_set *uris,
         * here, because it waits not only for replicas, but also
         * for local bootstrap leadership flags.
         */
-       if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
-           uris->uri_count == 0) {
+       if (uris->uri_count == 0 &&
+           (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+            (replicaset_state != REPLICASET_BOOTSTRAP &&
+             replicaset_state != REPLICASET_JOIN))) {
                replicaset_set_sync_quorum(&state);
                /* Cleanup the replica set. */
                replicaset_update(NULL, 0, false);
```
created_at: 2025-04-10T11:38:28Z
author_association: COLLABORATOR
diff_hunk: @@ -1342,6 +1362,7 @@ replicaset_connect(const struct uri_set *uris,
 		}
 		timeout -= ev_monotonic_now(loop()) - wait_start;
 	}
+	replicaset_connect_wakeup_cond = NULL;
path: src/box/replication.cc
user: login: sergepetrenko
body: Leaking a pointer to `state.wakeup` in case `fiber_testcancel()` throws an error above.
```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index d74a3a028f..ff5136b9cf 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1353,6 +1353,9 @@ replicaset_connect(const struct uri_set *uris,
                }
        });
        replicaset_connect_wakeup_cond = &state.wakeup;
+       auto cond_guard = make_scoped_guard([&]{
+               replicaset_connect_wakeup_cond = NULL;
+       });
        while (!replicaset_is_connected(&state, appliers, count,
                                        connect_quorum)) {
                double wait_start = ev_monotonic_now(loop());
@@ -1363,6 +1366,7 @@ replicaset_connect(const struct uri_set *uris,
                timeout -= ev_monotonic_now(loop()) - wait_start;
        }
        replicaset_connect_wakeup_cond = NULL;
+       cond_guard.is_active = false;
        if (state.connected < count) {
                say_crit("failed to connect to %d out of %d replicas",
                         count - state.connected, count);
```
created_at: 2025-04-10T11:42:08Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
path: src/box/box.cc
user: login: sergepetrenko
body: Thanks!
created_at: 2025-04-10T11:44:57Z
author_association: COLLABORATOR
diff_hunk: @@ -4325,6 +4325,8 @@ on_commit_schema_set_bootstrap_leader_uuid(struct trigger *trigger, void *event)
 	struct tt_uuid *uuid = (struct tt_uuid *)trigger->data;
 	bootstrap_leader_uuid = *uuid;
 	is_supervised_bootstrap_leader = tt_uuid_is_equal(uuid, &INSTANCE_UUID);
+	say_info("instance %s is assigned as a bootstrap leader",
path: src/box/alter.cc
user: login: sergepetrenko
body: > [#10859 (comment)](https://github.com/tarantool/tarantool/pull/10859#discussion_r2023182732)

Haven't thought of this, that's a nice idea.
created_at: 2025-04-10T11:45:23Z
author_association: COLLABORATOR
diff_hunk: @@ -2246,8 +2246,38 @@ box_set_bootstrap_leader_record(void)
 }
 
 int
-box_make_bootstrap_leader(void)
+box_make_bootstrap_leader(bool graceful)
 {
+	/* Bootstrap strategy is read by the time instance uuid is known. */
+	if (!tt_uuid_is_nil(&INSTANCE_UUID) &&
path: src/box/box.cc
user: login: sergepetrenko
body: Thanks! Looks much cleaner this way.
created_at: 2025-04-10T11:45:55Z
author_association: COLLABORATOR
diff_hunk: @@ -2253,8 +2255,31 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
+{
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	}
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
path: src/box/box.cc
user: login: sergepetrenko
body: > Hm. I did it after Serge's https://github.com/tarantool/tarantool/pull/10859#discussion_r2032976216. Don't know, whether we have some consistent approach here. The sensence-alive formatting feels a bit better for my taste. I'll change it (if needed) once you and Serge got a decision :)

It seems we don't have a single way of doing this. Also it seems more log messages start with a lowercase letter.
I formatted the message like we format comments for some reason, so I'm fine with any approach. Let's just keep it as it already is now, no need to change it back and forth at the last minute.

> Nit: it should be in docs, not in the logs, IMHO. "Graceful bootstrap is requested" seems enough

I like the longer message here. Let's keep Alexander's variant.
created_at: 2025-04-10T11:53:43Z
author_association: COLLABORATOR
diff_hunk: @@ -5357,8 +5393,15 @@ bootstrap_master(void)
 	 * With "auto" bootstrap strategy refuse to boot unless everyone agrees
 	 * this node is the bootstrap leader.
 	 */
-	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO)
+	if (bootstrap_strategy == BOOTSTRAP_STRATEGY_AUTO) {
 		check_bootstrap_unanimity();
+	} else if (bootstrap_strategy == BOOTSTRAP_STRATEGY_SUPERVISED &&
+		 is_graceful_supervised_bootstrap_requested) {
path: src/box/box.cc
user: login: Totktonada
body: Fixed.
created_at: 2025-04-10T14:11:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2273,7 +2273,7 @@ box_make_bootstrap_leader_graceful(void)
 
 	/* Wake up replicaset_connect() if the flag is turn on. */
 	if (!tt_uuid_is_nil(&INSTANCE_UUID))
-		box_broadcast_ballot();
+		replicaset_connect_wakeup();
path: src/box/box.cc
user: login: Totktonada
body: I had a thought that `box_broadcast_ballot()` here is purely to wake up `replicaset_connect()`. Yesterday you pointed me out that it also may be needed to send the new bootstrap leader UUID in a ballot to other peers.

It seems like we anyway call `box_broadcast_ballot()` later after `replicaset_connect()`. However, I'm not 100% sure and I've no goal to eliminate the call. I need to add `replicaset_connect_wakeup()`, so I'll just do that (without a removal of the ballot sending).

The incremental diff:

```diff
diff --git a/src/box/box.cc b/src/box/box.cc
index b62fd40b07..f94388979d 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -2272,8 +2272,10 @@ box_make_bootstrap_leader_graceful(void)
 		 "takes this role");
 
 	/* Wake up replicaset_connect() if the flag is turn on. */
-	if (!tt_uuid_is_nil(&INSTANCE_UUID))
+	if (!tt_uuid_is_nil(&INSTANCE_UUID)) {
+		box_broadcast_ballot();
 		replicaset_connect_wakeup();
+	}
 
 	return 0;
 }
@@ -2300,6 +2302,7 @@ box_make_bootstrap_leader_nongraceful(void)
 	} else {
 		bootstrap_leader_uuid = INSTANCE_UUID;
 		is_supervised_bootstrap_leader = true;
+		box_broadcast_ballot();
 		replicaset_connect_wakeup();
 		return 0;
 	}
```
created_at: 2025-04-10T14:11:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1251,7 +1260,13 @@ replicaset_connect(const struct uri_set *uris,
 	memset(&state, 0, sizeof(state));
 	fiber_cond_create(&state.wakeup);
 
-	if (uris->uri_count == 0) {
+	/*
+	 * The supervised bootstrap strategy has an exception
+	 * here, because it waits not only for replicas, but also
+	 * for local bootstrap leadership flags.
+	 */
+	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
path: src/box/replication.cc
user: login: Totktonada
body: Seems worthful, thanks. I also updated the remarks above.

The incremental diff:

```diff
diff --git a/src/box/replication.cc b/src/box/replication.cc
index d74a3a028f..3b2a0ac66b 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -1261,12 +1261,18 @@ replicaset_connect(const struct uri_set *uris,
 	fiber_cond_create(&state.wakeup);
 
 	/*
+	 * Return immediately if there are no configured
+	 * upstreams to wait.
+	 *
 	 * The supervised bootstrap strategy has an exception
 	 * here, because it waits not only for replicas, but also
-	 * for local bootstrap leadership flags.
+	 * for local bootstrap leadership flags (it is relevant on
+	 * bootstrap/join and irrelevant on a reconfiguration).
 	 */
-	if (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED &&
-	    uris->uri_count == 0) {
+	if (uris->uri_count == 0 &&
+	    (bootstrap_strategy != BOOTSTRAP_STRATEGY_SUPERVISED ||
+	     (replicaset_state != REPLICASET_BOOTSTRAP &&
+	      replicaset_state != REPLICASET_JOIN))) {
 		replicaset_set_sync_quorum(&state);
 		/* Cleanup the replica set. */
 		replicaset_update(NULL, 0, false);
```
created_at: 2025-04-10T14:14:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1342,6 +1362,7 @@ replicaset_connect(const struct uri_set *uris,
 		}
 		timeout -= ev_monotonic_now(loop()) - wait_start;
 	}
+	replicaset_connect_wakeup_cond = NULL;
path: src/box/replication.cc
user: login: Totktonada
body: Wow, totally overlooked it. Thank you!

Applied your diff here.
created_at: 2025-04-10T14:14:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2253,8 +2255,31 @@ box_set_bootstrap_leader_record(void)
 		    tt_uuid_str(&INSTANCE_UUID), fiber_time64(), instance_id);
 }
 
-int
-box_make_bootstrap_leader(void)
+static int
+box_make_bootstrap_leader_graceful(void)
+{
+	/* No-op if the database is already initialized. */
+	if (is_box_configured) {
+		say_info("Graceful bootstrap request is discarded: the "
+			 "instance is already bootstrapped");
+		return 0;
+	}
+
+	is_graceful_supervised_bootstrap_requested = true;
+	say_info("Graceful bootstrap is requested: the instance "
+		 "is going to check whether a bootstrap leader "
+		 "already exists and if the check fails the instance "
+		 "takes this role");
path: src/box/box.cc
user: login: Totktonada
body: I hope Nikita doesn't mind. I'm going to resolve this last open discussion.
created_at: 2025-04-10T15:29:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: coveralls
created_at: 2024-11-27T01:20:42Z
body: 
[![Coverage Status](https://coveralls.io/builds/73167861/badge)](https://coveralls.io/builds/73167861)

coverage: 87.497% (+0.02%) from 87.481%
when pulling **81b88d7f67a3eb98e02ccc2eb8f61d8632a1779c on Totktonada:gh-10858-choose-bootstrap-leader-before-box-cfg**
into **a249d26b848533880fa62ec22d3acf6e01f5c4d0
 on tarantool:master**.

author_association: NONE
user: login: Totktonada
created_at: 2025-03-19T09:45:09Z
body: @Serpentian @sergepetrenko I was in doubts, whether such a new behavior is too dangerous. Now I think that it is OK if we also implement #11272. So, I return it for your review. Sorry for the long delay.
author_association: CONTRIBUTOR
user: login: Totktonada
created_at: 2025-04-08T09:44:48Z
body: @sergepetrenko @Serpentian I'm still working on bootstrapping a singleton replicaset ([discussion](https://github.com/tarantool/tarantool/pull/10859#discussion_r2009368023)), but all the other parts seems ready for review. I would appreciate a glance on these parts.
author_association: CONTRIBUTOR
user: login: Totktonada
created_at: 2025-04-10T00:54:46Z
body: @Serpentian, thank you for the warm words!

The tests are actually... to be honest, they're messy. I guess I can do them a bit more structured, but it requires some significant work. For my taste, I'm close to the framework limits here and, in fact, I already did someting to solve that.

I wrote the cbuilder/cluster/interactive_tarantool helpers to solve problems like this. The tests that are written for the YAML config part of these changes (PR #11287) are based on these helpers and I like these tests much more.

In fact, I can rewrite some (or even all) the test cases from this PR using these helpers too. When I started to work on this PR it looked like I should use the usual `replication-luatest` tools (mimic the format of other test cases) and shouldn't use my helpers here. However, if I would know about all the cases I finally need to cover, I would likely just use the new helpers and things would be more structured.

Anyway, we have some regression tests. And I'll take this experience into account for future patches :)
author_association: CONTRIBUTOR