From 8fea6a46b78ef09658786854b69a9c0fe2250de8 Mon Sep 17 00:00:00 2001
From: Georgy Moshkin <gmoshkin@picodata.io>
Date: Mon, 2 Sep 2024 13:25:10 +0300
Subject: [PATCH] refactor: cleanup conf change code

---
 src/governor/conf_change.rs | 126 ++++++++++++++++--------------------
 1 file changed, 57 insertions(+), 69 deletions(-)

diff --git a/src/governor/conf_change.rs b/src/governor/conf_change.rs
index 44df997bc7..717c5708ed 100644
--- a/src/governor/conf_change.rs
+++ b/src/governor/conf_change.rs
@@ -1,14 +1,11 @@
-use ::raft::prelude as raft;
-use ::raft::prelude::ConfChangeType::*;
-
-use std::collections::HashMap;
-use std::collections::{BTreeMap, BTreeSet};
-
 use crate::instance::Instance;
-use crate::instance::StateVariant::*;
 use crate::tier::Tier;
 use crate::traft::{Distance, RaftId};
 use crate::{has_states, tlog};
+use ::raft::prelude as raft;
+use ::raft::prelude::ConfChangeType::*;
+use std::collections::HashMap;
+use std::collections::{BTreeMap, BTreeSet};
 
 struct RaftConf<'a> {
     all: BTreeMap<RaftId, &'a Instance>,
@@ -85,7 +82,6 @@ pub(crate) fn raft_conf_change(
     };
     let mut changes: Vec<raft::ConfChangeSingle> = vec![];
 
-    let not_expelled = |instance: &&Instance| has_states!(instance, * -> not Expelled);
     // Only an instance from tier with `can_vote = true` can be considered as voter.
     let tier_can_vote = |instance: &&Instance| {
         tiers
@@ -96,7 +92,7 @@ pub(crate) fn raft_conf_change(
 
     let number_of_eligible_for_vote = instances
         .iter()
-        .filter(not_expelled)
+        .filter(|instance| has_states!(instance, * -> not Expelled))
         .filter(tier_can_vote)
         .count();
 
@@ -123,49 +119,40 @@ pub(crate) fn raft_conf_change(
 
     // Remove / replace voters
     for voter_id in raft_conf.voters.clone().iter() {
-        let instance = match raft_conf.all.get(voter_id) {
-            Some(instance) if tier_can_vote(instance) => instance,
-            // case when bootstrap leader from tier with `can_vote = false`
-            Some(Instance {
-                tier, instance_id, ..
-            }) => {
-                tlog!(
-                    Warning,
-                    "instance with instance_id '{instance_id}' from \
-                     tier '{tier}' with 'can_vote' = false is in raft configuration as voter, making it follower"
-                );
-                let ccs = raft_conf.change_single(RemoveNode, *voter_id);
-                changes.push(ccs);
-                continue;
-            }
+        let Some(instance) = raft_conf.all.get(voter_id) else {
             // unknown instance which is not in configuration of cluster, nearly impossible
-            None => {
-                let ccs = raft_conf.change_single(RemoveNode, *voter_id);
-                changes.push(ccs);
-                continue;
-            }
+            #[rustfmt::skip]
+            tlog!(Warning, "no info found for instance which is a raft voter: raft_id = {voter_id}");
+            let ccs = raft_conf.change_single(RemoveNode, *voter_id);
+            changes.push(ccs);
+            continue;
         };
-        match instance.target_state.variant {
-            Online => {
-                // Do nothing
-            }
-            Offline => {
-                // A voter goes offline. Replace it with
-                // another online instance if possible.
-                let Some((next_voter_id, _)) = next_farthest(&raft_conf, &promotable) else {
-                    continue;
-                };
-
-                let ccs1 = raft_conf.change_single(AddLearnerNode, instance.raft_id);
-                let ccs2 = raft_conf.change_single(AddNode, next_voter_id);
-                changes.extend_from_slice(&[ccs1, ccs2]);
-                promotable.remove(&next_voter_id);
-            }
-            Expelled => {
-                // Expelled instance is removed unconditionally.
-                let ccs = raft_conf.change_single(RemoveNode, instance.raft_id);
-                changes.push(ccs);
-            }
+
+        if !tier_can_vote(instance) {
+            // case when bootstrap leader from tier with `can_vote = false`
+            #[rustfmt::skip]
+            tlog!(Warning, "instance with instance_id '{}' from tier '{}' with 'can_vote' = false is in raft configuration as voter, making it follower",
+                  instance.instance_id, instance.tier);
+            let ccs = raft_conf.change_single(RemoveNode, *voter_id);
+            changes.push(ccs);
+            continue;
+        }
+
+        if has_states!(instance, * -> Expelled) {
+            // Expelled instance is removed unconditionally.
+            let ccs = raft_conf.change_single(RemoveNode, instance.raft_id);
+            changes.push(ccs);
+        } else if has_states!(instance, * -> Offline) {
+            // A voter goes offline. Replace it with
+            // another online instance if possible.
+            let Some((next_voter_id, _)) = next_farthest(&raft_conf, &promotable) else {
+                continue;
+            };
+
+            let ccs1 = raft_conf.change_single(AddLearnerNode, instance.raft_id);
+            let ccs2 = raft_conf.change_single(AddNode, next_voter_id);
+            changes.extend_from_slice(&[ccs1, ccs2]);
+            promotable.remove(&next_voter_id);
         }
     }
 
@@ -179,20 +166,18 @@ pub(crate) fn raft_conf_change(
     // Remove unknown / expelled learners
     for learner_id in raft_conf.learners.clone().iter() {
         let Some(instance) = raft_conf.all.get(learner_id) else {
-            // Nearly impossible, but rust forces me to check it.
+            // unknown instance which is not in configuration of cluster, nearly impossible
+            #[rustfmt::skip]
+            tlog!(Warning, "no info found for instance which is a raft voter: raft_id = {learner_id}");
             let ccs = raft_conf.change_single(RemoveNode, *learner_id);
             changes.push(ccs);
             continue;
         };
-        match instance.target_state.variant {
-            Online | Offline => {
-                // Do nothing
-            }
-            Expelled => {
-                // Expelled instance is removed unconditionally.
-                let ccs = raft_conf.change_single(RemoveNode, instance.raft_id);
-                changes.push(ccs);
-            }
+
+        if has_states!(instance, * -> Expelled) {
+            // Instance was already expelled => remove it unconditionally.
+            let ccs = raft_conf.change_single(RemoveNode, instance.raft_id);
+            changes.push(ccs);
         }
     }
 
@@ -227,13 +212,17 @@ pub(crate) fn raft_conf_change(
     }
 
     // Promote remaining instances as learners
-    for instance in instances.iter().filter(not_expelled) {
-        if !raft_conf.voters.contains(&instance.raft_id)
-            && !raft_conf.learners.contains(&instance.raft_id)
+    for instance in instances {
+        if has_states!(instance, * -> Expelled)
+            || raft_conf.voters.contains(&instance.raft_id)
+            || raft_conf.learners.contains(&instance.raft_id)
         {
-            let ccs = raft_conf.change_single(AddLearnerNode, instance.raft_id);
-            changes.push(ccs);
+            continue;
         }
+        // Non-expelled instance which is neither a learner nor a voter.
+        // It should become a learner.
+        let ccs = raft_conf.change_single(AddLearnerNode, instance.raft_id);
+        changes.push(ccs);
     }
 
     if changes.is_empty() {
@@ -252,13 +241,12 @@ pub(crate) fn raft_conf_change(
 #[cfg(test)]
 mod tests {
     use super::*;
-
-    use ::raft::prelude as raft;
-    use std::collections::{BTreeMap, BTreeSet};
-
     use crate::failure_domain::FailureDomain;
     use crate::instance::State;
+    use crate::instance::StateVariant::*;
     use crate::traft::RaftId;
+    use ::raft::prelude as raft;
+    use std::collections::{BTreeMap, BTreeSet};
 
     macro_rules! fd {
         ($(,)?) => { FailureDomain::default() };
-- 
GitLab