use tracing::debug;
use crate::conf_change::{ConfChange, ConfChangeType};
use crate::error::{ClusterError, Result};
use super::core::MultiRaft;
impl MultiRaft {
pub fn propose_conf_change(
&mut self,
group_id: u64,
change: &ConfChange,
) -> Result<(u64, u64)> {
let (log_index, committed_immediately) = {
let node = self
.groups
.get_mut(&group_id)
.ok_or(ClusterError::GroupNotFound { group_id })?;
let data = change.to_entry_data();
let log_index = node.propose(data)?;
let committed_immediately = node.commit_index() >= log_index;
(log_index, committed_immediately)
};
if committed_immediately {
self.apply_conf_change(group_id, change)?;
}
Ok((group_id, log_index))
}
pub fn apply_conf_change(&mut self, group_id: u64, change: &ConfChange) -> Result<()> {
let self_node_id = self.node_id;
let node = self
.groups
.get_mut(&group_id)
.ok_or(ClusterError::GroupNotFound { group_id })?;
match change.change_type {
ConfChangeType::AddNode => {
node.add_peer(change.node_id);
if let Some(info) = self.routing.group_info(group_id)
&& !info.members.contains(&change.node_id)
{
let mut new_members = info.members.clone();
new_members.push(change.node_id);
self.routing.set_group_members(group_id, new_members);
}
}
ConfChangeType::RemoveNode => {
node.remove_peer(change.node_id);
if let Some(info) = self.routing.group_info(group_id) {
let new_members: Vec<u64> = info
.members
.iter()
.copied()
.filter(|&id| id != change.node_id)
.collect();
self.routing.set_group_members(group_id, new_members);
}
}
ConfChangeType::AddLearner => {
node.add_learner(change.node_id);
self.routing.add_group_learner(group_id, change.node_id);
}
ConfChangeType::PromoteLearner => {
let promoted = node.promote_learner(change.node_id);
if promoted {
self.routing.promote_group_learner(group_id, change.node_id);
}
if change.node_id == self_node_id {
node.promote_self_to_voter();
}
}
}
debug!(
node = self.node_id,
group = group_id,
change_type = ?change.change_type,
target_node = change.node_id,
voters = ?self.groups.get(&group_id).map(|n| n.voters().to_vec()),
learners = ?self.groups.get(&group_id).map(|n| n.learners().to_vec()),
"applied conf change"
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::routing::RoutingTable;
use nodedb_raft::NodeRole;
use super::super::core::MultiRaft;
fn new_mr(node_id: u64, group_ids: &[u64]) -> MultiRaft {
let dir = tempfile::tempdir().unwrap();
let rt = RoutingTable::uniform(group_ids.len() as u64, &[node_id], 1);
let mut mr = MultiRaft::new(node_id, rt, dir.path().to_path_buf());
std::mem::forget(dir); for &gid in group_ids {
mr.add_group(gid, vec![]).unwrap();
}
mr
}
#[test]
fn apply_add_learner_updates_routing_and_raftnode() {
let mut mr = new_mr(1, &[0]);
let change = ConfChange {
change_type: ConfChangeType::AddLearner,
node_id: 2,
};
mr.apply_conf_change(0, &change).unwrap();
let node = mr.groups.get(&0).unwrap();
assert_eq!(node.learners(), &[2]);
assert!(node.voters().is_empty());
let info = mr.routing.group_info(0).unwrap();
assert_eq!(info.learners, vec![2]);
assert_eq!(info.members, vec![1]); }
#[test]
fn apply_promote_learner_moves_peer_to_voters() {
let mut mr = new_mr(1, &[0]);
mr.apply_conf_change(
0,
&ConfChange {
change_type: ConfChangeType::AddLearner,
node_id: 2,
},
)
.unwrap();
mr.apply_conf_change(
0,
&ConfChange {
change_type: ConfChangeType::PromoteLearner,
node_id: 2,
},
)
.unwrap();
let node = mr.groups.get(&0).unwrap();
assert_eq!(node.voters(), &[2]);
assert!(node.learners().is_empty());
let info = mr.routing.group_info(0).unwrap();
assert_eq!(info.learners, Vec::<u64>::new());
assert!(info.members.contains(&2));
}
#[test]
fn apply_promote_self_flips_role() {
let dir = tempfile::tempdir().unwrap();
let rt = RoutingTable::uniform(1, &[1, 2], 1);
let mut mr = MultiRaft::new(2, rt, dir.path().to_path_buf());
mr.add_group_as_learner(0, vec![1], vec![]).unwrap();
mr.groups.get_mut(&0).unwrap().add_learner(2);
let node = mr.groups.get_mut(&0).unwrap();
assert_eq!(node.role(), NodeRole::Learner);
node.promote_self_to_voter();
assert_eq!(node.role(), NodeRole::Follower);
}
}