diff --git a/docs/clustering.md b/docs/clustering.md index 04d6675951c157e6548f3de9a50a0257a78533e3..cb313212e4ea3e1c09b60b095211128d14f18491 100644 --- a/docs/clustering.md +++ b/docs/clustering.md @@ -23,7 +23,7 @@ picodata run --instance-id iN --listen iN --peer i1,i2  КраÑным показан родительÑкий процеÑÑ, который запущен на вÑём протÑжении жизненного цикла инÑтанÑа. Ð’ÑÑ Ð»Ð¾Ð³Ð¸ÐºÐ° поиÑка лидера Raft-группы и приÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ðº ней проиÑходит в дочернем процеÑÑе (голубой цвет). При ÑброÑе ÑоÑтоÑÐ½Ð¸Ñ Ð¸Ð½ÑтанÑа и rebootstrap проиÑходит Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð°Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ„Ð¾Ñ€ÐºÐ° (Ñиреневый цвет). - + Ð”Ð°Ð½Ð½Ð°Ñ Ñхема наиболее полно отражает логику кода в файле `main.rs`. Ðиже опиÑаны детали Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ Ñтапа и ÑоответÑтвующей программной функции. ### fn main() @@ -32,8 +32,7 @@ picodata run --instance-id iN --listen iN --peer i1,i2 ### fn start_discover() -Дочерний процеÑÑ Ð½Ð°Ñ‡Ð¸Ð½Ð°ÐµÑ‚ Ñвоё ÑущеÑтвование Ñ Ð·Ð°Ð¿ÑƒÑка Ð¼Ð¾Ð´ÑƒÐ»Ñ `box.cfg()` и вызова функции `start_discover`. Возможно, что при Ñтом из поÑтоÑнно хранимых данных будет ÑÑно, что bootstrap данного инÑтанÑа уже был произведён ранее и что Raft уже знает о вхождении Ñтого инÑтанÑа в клаÑтер - в таком Ñлучае никакого discovery не будет, инÑÑ‚Ð°Ð½Ñ Ñразу перейдёт к Ñтапу `postjoin()`. Однако, еÑли Ñто новый инÑтанÑ, то алгоритм discovery выдаÑÑ‚ ему флаг `i_am_bootstrap_leader == false` и Ñообщит Ð°Ð´Ñ€ÐµÑ Ð»Ð¸Ð´ÐµÑ€Ð° Raft-группы. Сам лидер (единÑтвенный Ñ `i_am_bootstrap_leader == true`) выполнÑет функцию `start_boot` и затем переходит к функции `postjoin()`. ОÑтальные инÑтанÑÑ‹ (уже запущенные и вÑе будущие) ÑбраÑывают Ñвоё ÑоÑтоÑние (Ñтап rebootstrap) и переходÑÑ‚ к функции `start_join`. - +Дочерний процеÑÑ Ð½Ð°Ñ‡Ð¸Ð½Ð°ÐµÑ‚ Ñвоё ÑущеÑтвование Ñ Ð·Ð°Ð¿ÑƒÑка Ð¼Ð¾Ð´ÑƒÐ»Ñ `box.cfg()` и вызова функции `start_discover`. Возможно, что при Ñтом из поÑтоÑнно хранимых данных будет ÑÑно, что bootstrap данного инÑтанÑа уже был произведён ранее и что Raft уже знает о вхождении Ñтого инÑтанÑа в клаÑтер - в таком Ñлучае никакого discovery не будет, инÑÑ‚Ð°Ð½Ñ Ñразу перейдёт к Ñтапу `postjoin()`. Ð’ противном Ñлучае, еÑли меÑто инÑтанÑа в клаÑтере ещё не извеÑтно, алгоритм discovery опредÑет значение флага `i_am_bootstrap_leader` и Ð°Ð´Ñ€ÐµÑ Ð»Ð¸Ð´ÐµÑ€Ð° Raft-группы. Далее вÑе инÑтанÑÑ‹ ÑбраÑывают Ñвоё ÑоÑтоÑние (Ñтап rebootstrap) чтобы повторно провеÑти инициализацию `box.cfg`, теперь уже Ñ Ð¸Ð·Ð²ÐµÑтными параметрами. Сам лидер (единÑтвенный Ñ `i_am_bootstrap_leader == true`) выполнÑет функцию `start_boot`. ОÑтальные инÑтанÑÑ‹ переходÑÑ‚ к функции `start_join`. ### fn start_boot() diff --git a/docs/clustering_curves.svg b/docs/clustering_curves.svg index 107a7add67fb907180a549fc03ca141531fa3842..69d7454948e1da6f52c89873740918ec5fb85a5b 100644 Binary files a/docs/clustering_curves.svg and b/docs/clustering_curves.svg differ diff --git a/src/main.rs b/src/main.rs index 7b5c8bda4149d4d2040133d7f9f23d80bffeb609..bfc8b2d0d57158c7fbc913265d8dd9300c90c1ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -162,9 +162,11 @@ fn main() -> ! { } } +#[allow(clippy::enum_variant_names)] #[derive(Debug, Serialize, Deserialize)] enum Entrypoint { StartDiscover, + StartBoot, StartJoin { leader_address: String }, } @@ -172,6 +174,7 @@ impl Entrypoint { fn exec(self, args: args::Run, to_supervisor: ipc::Sender<IpcMessage>) { match self { Self::StartDiscover => start_discover(&args, to_supervisor), + Self::StartBoot => start_boot(&args), Self::StartJoin { leader_address } => start_join(&args, leader_address), } } @@ -351,6 +354,8 @@ fn start_discover(args: &args::Run, to_supervisor: ipc::Sender<IpcMessage>) { picolib_setup(args); assert!(tarantool::cfg().is_none()); + // Don't try to guess instance and replicaset uuids now, + // finally, the box will be rebootstraped after discovery. let mut cfg = tarantool::Cfg { listen: None, read_only: false, @@ -364,14 +369,6 @@ fn start_discover(args: &args::Run, to_supervisor: ipc::Sender<IpcMessage>) { tarantool::set_cfg(&cfg); traft::Storage::init_schema(); - - // иÑходный discovery::discover() пришлоÑÑŒ разбить на две чаÑти - - // init_global и wait_global. К Ñожалению, они не могут быть атомарны, - // потому что listen порт надо поднимать именно поÑередине. С неподнÑтым портом - // уходить в кишки discovery::discover() Ð½ÐµÐ»ÑŒÐ·Ñ - на запроÑÑ‹ отвечать будет некому. - // РеÑли поднÑÑ‚ÑŒ порт до инициализации диÑкавери, то образуетÑÑ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ìе окно, - // и прилетевший пакет приведёт к панике "discovery error: expected DISCOVERY - // to be set on instance startup" discovery::init_global(&args.peers); init_handlers(); @@ -386,7 +383,13 @@ fn start_discover(args: &args::Run, to_supervisor: ipc::Sender<IpcMessage>) { match role { discovery::Role::Leader { .. } => { - start_boot(args); + let next_entrypoint = Entrypoint::StartBoot {}; + let msg = IpcMessage { + next_entrypoint, + drop_db: true, + }; + to_supervisor.send(&msg); + std::process::exit(0); } discovery::Role::NonLeader { leader } => { let next_entrypoint = Entrypoint::StartJoin { @@ -416,6 +419,26 @@ fn start_boot(args: &args::Run) { let peer = topology.diff().pop().unwrap(); let raft_id = peer.raft_id; + picolib_setup(args); + assert!(tarantool::cfg().is_none()); + + let cfg = tarantool::Cfg { + listen: None, + read_only: false, + instance_uuid: Some(peer.instance_uuid.clone()), + replicaset_uuid: Some(peer.replicaset_uuid.clone()), + wal_dir: args.data_dir.clone(), + memtx_dir: args.data_dir.clone(), + log_level: args.log_level() as u8, + ..Default::default() + }; + + std::fs::create_dir_all(&args.data_dir).unwrap(); + tarantool::set_cfg(&cfg); + + traft::Storage::init_schema(); + init_handlers(); + start_transaction(|| -> Result<(), Error> { let cs = raft::ConfState { voters: vec![raft_id], diff --git a/test/int/test_joining.py b/test/int/test_joining.py index f2bf7d320bbd84ffc9313217a6d93863131138e4..d153b1974f30684882d4521460bcf5068f50068a 100644 --- a/test/int/test_joining.py +++ b/test/int/test_joining.py @@ -78,27 +78,39 @@ def test_request_follower(cluster2: Cluster): assert e.value.args == ("ER_PROC_C", "not a leader") -def test_instance_uuid(cluster2: Cluster): +def test_uuids(cluster2: Cluster): i1, i2 = cluster2.instances i1.assert_raft_status("Leader") - ret = i1.call( + peer_1 = i1.call( + ".raft_join", + i1.instance_id, + None, # replicaset_id + i1.listen, # address + True, # voter + )[0]["peer"] + assert peer_1["instance_id"] == i1.instance_id + assert peer_1["instance_uuid"] == i1.eval("return box.info.uuid") + assert peer_1["replicaset_uuid"] == i1.eval("return box.info.cluster.uuid") + + peer_2 = i1.call( ".raft_join", i2.instance_id, None, # replicaset_id i2.listen, # address True, # voter )[0]["peer"] - assert ret["instance_id"] == i2.instance_id - assert ret["instance_uuid"] == i2.eval("return box.info.uuid") + assert peer_2["instance_id"] == i2.instance_id + assert peer_2["instance_uuid"] == i2.eval("return box.info.uuid") + assert peer_2["replicaset_uuid"] == i2.eval("return box.info.cluster.uuid") # Two consequent requests must obtain same raft_id and instance_id - ret1 = fake_join(i1, "fake", timeout=1)[0]["peer"] - ret2 = fake_join(i1, "fake", timeout=1)[0]["peer"] - assert ret1["instance_id"] == "fake" - assert ret2["instance_id"] == "fake" - assert ret1["raft_id"] == ret2["raft_id"] - assert ret1["instance_uuid"] == ret2["instance_uuid"] + fake_peer_1 = fake_join(i1, "fake", timeout=1)[0]["peer"] + fake_peer_2 = fake_join(i1, "fake", timeout=1)[0]["peer"] + assert fake_peer_1["instance_id"] == "fake" + assert fake_peer_2["instance_id"] == "fake" + assert fake_peer_1["raft_id"] == fake_peer_2["raft_id"] + assert fake_peer_1["instance_uuid"] == fake_peer_2["instance_uuid"] def test_discovery(cluster: Cluster):