Skip to content

Commit f591726

Browse files
committed
Change: trait IntoNodes adds two new method has_nodes() and node_ids()
`trait IntoNodes` converts types `T` such as `Vec` or `BTreeSet` into `BTreeMap<NID, Node>`. This patch changes the functionality of the `IntoNodes` trait to provide two new methods `has_nodes()` and `node_ids()`, in addition to the existing `into_nodes()` method. The `has_nodes()` method returns true if the type `T` contains any `Node` objects, and `node_ids()` returns a `Vec` of the `NodeId` objects associated with the `Node` objects in `T`. Refactor: The patch also refactors the `Membership::next_safe()` method to return an `Err(LearnerNotFound)` if it attempts to build a `Membership` object containing a `voter_id` that does not correspond to any `Node`.
1 parent 290c551 commit f591726

File tree

3 files changed

+112
-33
lines changed

3 files changed

+112
-33
lines changed

openraft/src/membership/membership.rs

+61-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::BTreeSet;
33

44
use maplit::btreemap;
55

6+
use crate::error::LearnerNotFound;
67
use crate::membership::NodeRole;
78
use crate::node::Node;
89
use crate::quorum::AsJoint;
@@ -18,6 +19,8 @@ where
1819
N: Node,
1920
NID: NodeId,
2021
{
22+
fn has_nodes(&self) -> bool;
23+
fn node_ids(&self) -> Vec<NID>;
2124
fn into_nodes(self) -> BTreeMap<NID, N>;
2225
}
2326

@@ -26,6 +29,14 @@ where
2629
N: Node,
2730
NID: NodeId,
2831
{
32+
fn has_nodes(&self) -> bool {
33+
false
34+
}
35+
36+
fn node_ids(&self) -> Vec<NID> {
37+
vec![]
38+
}
39+
2940
fn into_nodes(self) -> BTreeMap<NID, N> {
3041
btreemap! {}
3142
}
@@ -36,6 +47,14 @@ where
3647
N: Node,
3748
NID: NodeId,
3849
{
50+
fn has_nodes(&self) -> bool {
51+
false
52+
}
53+
54+
fn node_ids(&self) -> Vec<NID> {
55+
self.iter().copied().collect()
56+
}
57+
3958
fn into_nodes(self) -> BTreeMap<NID, N> {
4059
self.into_iter().map(|node_id| (node_id, N::default())).collect()
4160
}
@@ -46,6 +65,19 @@ where
4665
N: Node,
4766
NID: NodeId,
4867
{
68+
fn has_nodes(&self) -> bool {
69+
true
70+
}
71+
72+
fn node_ids(&self) -> Vec<NID> {
73+
match self {
74+
None => {
75+
vec![]
76+
}
77+
Some(bs) => bs.iter().copied().collect(),
78+
}
79+
}
80+
4981
fn into_nodes(self) -> BTreeMap<NID, N> {
5082
match self {
5183
None => BTreeMap::new(),
@@ -59,6 +91,14 @@ where
5991
N: Node,
6092
NID: NodeId,
6193
{
94+
fn has_nodes(&self) -> bool {
95+
true
96+
}
97+
98+
fn node_ids(&self) -> Vec<NID> {
99+
self.keys().copied().collect()
100+
}
101+
62102
fn into_nodes(self) -> BTreeMap<NID, N> {
63103
self
64104
}
@@ -304,11 +344,28 @@ where
304344
/// curr = next;
305345
/// }
306346
/// ```
307-
pub(crate) fn next_safe<T>(&self, goal: T, removed_to_learner: bool) -> Self
347+
pub(crate) fn next_safe<T>(&self, goal: T, removed_to_learner: bool) -> Result<Self, LearnerNotFound<NID>>
308348
where T: IntoNodes<NID, N> {
309-
let goal = goal.into_nodes();
349+
let goal = if goal.has_nodes() {
350+
goal.into_nodes()
351+
} else {
352+
// If `goal` does not contains Node, inherit them from current config.
353+
354+
let mut voters_map = BTreeMap::new();
355+
356+
// There has to be corresponding `Node` for every voter_id
357+
for node_id in goal.node_ids().iter() {
358+
let n = self.get_node(node_id);
359+
if let Some(n) = n {
360+
voters_map.insert(*node_id, n.clone());
361+
} else {
362+
return Err(LearnerNotFound { node_id: *node_id });
363+
}
364+
}
365+
voters_map
366+
};
310367

311-
let goal_ids = goal.keys().cloned().collect::<BTreeSet<_>>();
368+
let goal_ids = goal.keys().copied().collect::<BTreeSet<_>>();
312369

313370
let config = Joint::from(self.configs.clone()).find_coherent(goal_ids).children().clone();
314371

@@ -323,7 +380,7 @@ where
323380
}
324381
};
325382

326-
Membership::with_nodes(config, nodes)
383+
Ok(Membership::with_nodes(config, nodes))
327384
}
328385

329386
/// Build a QuorumSet from current joint config

openraft/src/membership/membership_state.rs

+9-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::sync::Arc;
44
use crate::error::ChangeMembershipError;
55
use crate::error::EmptyMembership;
66
use crate::error::InProgress;
7-
use crate::error::LearnerNotFound;
87
use crate::less_equal;
98
use crate::node::Node;
109
use crate::validate::Validate;
@@ -92,21 +91,6 @@ where
9291
let effective = self.effective();
9392
let committed = self.committed();
9493

95-
let last = effective.membership.get_joint_config().last().unwrap();
96-
let new_voter_ids = changes.apply_to(last);
97-
98-
// Ensure cluster will have at least one voter.
99-
if new_voter_ids.is_empty() {
100-
return Err(EmptyMembership {}.into());
101-
}
102-
103-
// There has to be corresponding `Node` for every voter_id
104-
for node_id in new_voter_ids.iter() {
105-
if !effective.contains(node_id) {
106-
return Err(LearnerNotFound { node_id: *node_id }.into());
107-
}
108-
}
109-
11094
if committed.log_id == effective.log_id {
11195
// Ok: last membership(effective) is committed
11296
} else {
@@ -117,7 +101,15 @@ where
117101
.into());
118102
}
119103

120-
let new_membership = effective.membership.next_safe(new_voter_ids, removed_to_learner);
104+
let last = effective.membership.get_joint_config().last().unwrap();
105+
let new_voter_ids = changes.apply_to(last);
106+
107+
// Ensure cluster will have at least one voter.
108+
if new_voter_ids.is_empty() {
109+
return Err(EmptyMembership {}.into());
110+
}
111+
112+
let new_membership = effective.membership.next_safe(new_voter_ids, removed_to_learner)?;
121113

122114
tracing::debug!(?new_membership, "new membership config");
123115
Ok(new_membership)

openraft/src/membership/membership_test.rs

+42-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::fmt::Formatter;
55
use maplit::btreemap;
66
use maplit::btreeset;
77

8+
use crate::error::LearnerNotFound;
9+
use crate::membership::IntoNodes;
810
use crate::Membership;
911
use crate::MessageSummary;
1012

@@ -206,28 +208,56 @@ fn test_membership_with_nodes_panic() {
206208

207209
#[test]
208210
fn test_membership_next_safe() -> anyhow::Result<()> {
211+
let nodes = || btreeset! {1,2,3,4,5,6,7,8,9}.into_nodes();
209212
let c1 = || btreeset! {1,2,3};
210213
let c2 = || btreeset! {3,4,5};
211214
let c3 = || btreeset! {7,8,9};
212215

213-
let m1 = Membership::<u64, ()>::new(vec![c1()], None);
214-
let m2 = Membership::<u64, ()>::new(vec![c2()], None);
215-
let m12 = Membership::<u64, ()>::new(vec![c1(), c2()], None);
216-
let m23 = Membership::<u64, ()>::new(vec![c2(), c3()], None);
216+
#[allow(clippy::redundant_closure)]
217+
let new_mem = |voter_ids, ns| Membership::<u64, ()>::new(voter_ids, ns);
217218

218-
assert_eq!(m1, m1.next_safe(c1(), false));
219-
assert_eq!(m12, m1.next_safe(c2(), false));
220-
assert_eq!(m1, m12.next_safe(c1(), false));
221-
assert_eq!(m2, m12.next_safe(c2(), false));
222-
assert_eq!(m23, m12.next_safe(c3(), false));
219+
let m1 = new_mem(vec![c1()], nodes());
220+
let m12 = new_mem(vec![c1(), c2()], nodes());
221+
222+
assert_eq!(m1, m1.next_safe(c1(), false)?);
223+
assert_eq!(m12, m1.next_safe(c2(), false)?);
224+
assert_eq!(
225+
new_mem(vec![c1()], btreeset! {1,2,3,6,7,8,9}.into_nodes()),
226+
m12.next_safe(c1(), false)?
227+
);
228+
assert_eq!(
229+
new_mem(vec![c2()], btreeset! {3,4,5,6,7,8,9}.into_nodes()),
230+
m12.next_safe(c2(), false)?
231+
);
232+
assert_eq!(
233+
new_mem(vec![c2(), c3()], btreeset! {3,4,5,6,7,8,9}.into_nodes()),
234+
m12.next_safe(c3(), false)?
235+
);
223236

224237
// Turn removed members to learners
225238

226239
let old_learners = || btreeset! {1, 2};
227240
let learners = || btreeset! {1, 2, 3, 4, 5};
228241
let m23_with_learners_old = Membership::<u64, ()>::new(vec![c2(), c3()], Some(old_learners()));
229242
let m23_with_learners_new = Membership::<u64, ()>::new(vec![c3()], Some(learners()));
230-
assert_eq!(m23_with_learners_new, m23_with_learners_old.next_safe(c3(), true));
243+
assert_eq!(m23_with_learners_new, m23_with_learners_old.next_safe(c3(), true)?);
244+
245+
Ok(())
246+
}
247+
248+
#[test]
249+
fn test_membership_next_safe_learner_not_found() -> anyhow::Result<()> {
250+
let c1 = || btreeset! {1,2,3};
251+
let c2 = || btreeset! {3,4,5};
252+
let c3 = || btreeset! {7};
253+
254+
#[allow(clippy::redundant_closure)]
255+
let new_mem = |voter_ids, ns| Membership::<u64, ()>::new(voter_ids, ns);
256+
257+
let m12 = new_mem(vec![c1(), c2()], None);
258+
259+
let res = m12.next_safe(c3(), false);
260+
assert_eq!(Err(LearnerNotFound { node_id: 7 }), res);
231261

232262
Ok(())
233263
}
@@ -246,15 +276,15 @@ fn test_membership_next_safe_with_nodes() -> anyhow::Result<()> {
246276

247277
// joint [{2}, {1,2}]
248278

249-
let res = initial.next_safe(btreeset! {1,2}, false);
279+
let res = initial.next_safe(btreeset! {1,2}, false)?;
250280
assert_eq!(
251281
btreemap! {1=>node("1"), 2=>node("2")},
252282
res.nodes().map(|(nid, n)| (*nid, n.clone())).collect::<BTreeMap<_, _>>()
253283
);
254284

255285
// Removed to learner
256286

257-
let res = initial.next_safe(btreeset! {1}, true);
287+
let res = initial.next_safe(btreeset! {1}, true)?;
258288
assert_eq!(
259289
btreemap! {1=>node("1"), 2=>node("2")},
260290
res.nodes().map(|(nid, n)| (*nid, n.clone())).collect::<BTreeMap<_, _>>()

0 commit comments

Comments
 (0)