From b2b1b29384238ab279972822befbf9ad3987cc2a Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov <yaroslav.dynnikov@gmail.com> Date: Sun, 22 May 2022 23:35:52 +0300 Subject: [PATCH] bug: uuid mismatch on bootstrap leader When bootstrapping an instance, there're two possible execution paths - `start_boot` and `start_join`. While `start_join` takes all uuids from JoinResponse, `start_boot` already deals with a bootstrapped `box.cfg` (it's done in `start_discover`, refer to [1]). In order to make uuids consistent across `box.cfg` and topology module, `start_boot` stage is preceded with rebootstrap. This case is also covered with a pytest. - [1] doc/clustering.md --- docs/clustering.md | 5 ++--- docs/clustering_curves.svg | Bin 113797 -> 124610 bytes src/main.rs | 41 +++++++++++++++++++++++++++++-------- test/int/test_joining.py | 32 ++++++++++++++++++++--------- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/docs/clustering.md b/docs/clustering.md index 04d6675951..cb313212e4 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 GIT binary patch delta 1113 zcmZ`&O=ule6y}aE#@G1UHl(8B&DesKxwrTKC-!M^Dea<&p&}ICdowj-&CD}zYU&>a zK}1Dryo)DvVQGrG62mOsqDaM6su&B3xT>`vg_c5>l_Gd&5<@F-G4RcI?)koR&OP_t zzwZ3*$I-6?z$kp2cMi#RLSiN($Oxl|Sj5QAa1(^EpT}v>e^YVZ&Dyg`E9{mMd=c!> zMhL@+s!u_fW{3OTb}yOg9&Lr1P)w{*+=0f{rsY^!#x!$#8WUslX<W>-LTV%{_cZPT z`zsgkGn3E1bR><Fek&~IjwJt;tz)Ru$-VqW@qX6sp>C2D$(;M<j)`OSf8KdH=@w`< zjeB{T75^h$A1#uj1?sf>$t!8*o7~7^O|921X8u)rsxE%_`jF}>EACj;Q!nqQMVe>6 zTg5SFmhng`tO=1utThN>K9~Bk+iM3SvK~m|R#*#CVN?irRc5<)t$S6s5x<ru^EDqQ zhx)6NtkUB0;K=7q_gQnQ$@V2#j5t#6Li73Z=Ez~tKnf8@#>UIWeIIQG#w|=vWX#uP z6=f09m{KmRguYZ^1o4!`#4sf_G!heHD#~E)H$@xBDCUY78xicg5{5#-Fj1Dsh+$2M zwF)YN1*6nN5_8QBlkShn)1W_GdIRhQ?)4q_mcKtXEdVUl0iYkUSZZapj;z7TXBiP` zFG;0l<<rMcSoh9@TLwQJ2b;#J##RfZd1daE87Fe519xw8*H4X>H<n)rUA`Ow<X*k{ z>_%D07cVSjw`92+%PRrWZejRn`RBQlAV9Llr++vZ1c=wTyYi;TtNh>RKk-=A{8v9$ zn0Y)|wtqbsc;k<U+sjM8JvR~{TGP`PUaXFkOBc_2>B?rZT)5QqaZ=+mm)}}V3*CV$ z=e^<J>}K%r;QLW<Pr3i<mnZfiwfZir{qh$*>mRJb+-Sxk>ZePzG7S1@aY|`~h~GNR zxV&wrss(16T84%XybnGF!{&PcJmRi>c>iGK6lhjme*3{QTNtre8*ON0v5>+TCI-<W ecrq}`U4LSH@b)6uJ2nk1Hr7<Z;V+9|Y1=;t3`+F? delta 298 zcmW-Yy-UMD96<S%Qe%Z8wO~PnBUrSMT<`mG#o%9|2*n|!6|3|ED(G6lu}d=q=_=}` za7e+y!BJFjbMmhc#6hqK?``irz0RHg<nA)S8QLHbA@RQC+<1r)#(XkERNXv6XwYl6 z`!nm!Ufhj3t<ZfRS^?LYiZYBEruBb@P`i6Hh@*b19-oG8ASjkBkij@xa=77GX(f^< zTV6P6H;)fOcXkrHm31J4YW5=cFmHRsQ8D>lTQ)-BKye~HpE4{2Q4~?au+&5w&o`I~ zWu!(_NGzCPp3ksh0hc^Et6U~a-hGO$AWc@P1v^~d5s<eZ4bTq3oGL}M{Tfe9`nU;B kx>tje{d{S-)59X%Ow#Zf7N_600ITWl2ny5fH|UiA06%+G9{>OV diff --git a/src/main.rs b/src/main.rs index 7b5c8bda41..bfc8b2d0d5 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 f2bf7d320b..d153b1974f 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): -- GitLab