Skip to content

Commit 0b145e2

Browse files
authored
Merge pull request #684 from drmingdrmer/60-refact-write
Refactor: Adds a new method `ensure_last_membership_committed()` to the `MembershipState`
2 parents 06a9599 + ac579d5 commit 0b145e2

File tree

3 files changed

+103
-16
lines changed

3 files changed

+103
-16
lines changed

openraft/src/core/raft_core.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ impl<C: RaftTypeConfig, N: RaftNetworkFactory<C>, S: RaftStorage<C>> RaftCore<C,
391391
turn_to_learner: bool,
392392
tx: RaftRespTx<ClientWriteResponse<C>, ClientWriteError<C::NodeId, C::Node>>,
393393
) -> Result<(), Fatal<C::NodeId>> {
394-
let res = self.engine.state.membership_state.next_membership(changes, turn_to_learner);
394+
let res = self.engine.state.membership_state.create_updated_membership(changes, turn_to_learner);
395395
let new_membership = match res {
396396
Ok(x) => x,
397397
Err(e) => {

openraft/src/membership/membership_state.rs

+33-15
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,25 @@ where
8080
self.effective.membership.is_voter(id)
8181
}
8282

83-
/// Build a new membership config by applying changes to the current config.
83+
/// Builds a new membership configuration by applying changes to the current configuration.
8484
///
85-
/// The removed voter is left in membership config as learner if `removed_to_learner` is true.
86-
pub(crate) fn next_membership(
85+
/// * `changes`: The changes to apply to the current membership configuration.
86+
/// * `convert_removed_to_learner`: Indicates whether the removed voter should be left in the
87+
/// membership configuration as a learner.
88+
///
89+
/// A Result containing the new membership configuration if the operation succeeds, or a
90+
/// `ChangeMembershipError` if an error occurs.
91+
///
92+
/// This function ensures that the cluster will have at least one voter in the new membership
93+
/// configuration.
94+
pub(crate) fn create_updated_membership(
8795
&self,
8896
changes: ChangeMembers<NID>,
89-
removed_to_learner: bool,
97+
convert_removed_to_learner: bool,
9098
) -> Result<Membership<NID, N>, ChangeMembershipError<NID>> {
9199
let effective = self.effective();
92-
let committed = self.committed();
93100

94-
if committed.log_id == effective.log_id {
95-
// Ok: last membership(effective) is committed
96-
} else {
97-
return Err(InProgress {
98-
committed: committed.log_id,
99-
membership_log_id: effective.log_id,
100-
}
101-
.into());
102-
}
101+
self.ensure_last_membership_committed()?;
103102

104103
let last = effective.membership.get_joint_config().last().unwrap();
105104
let new_voter_ids = changes.apply_to(last);
@@ -109,12 +108,31 @@ where
109108
return Err(EmptyMembership {}.into());
110109
}
111110

112-
let new_membership = effective.membership.next_safe(new_voter_ids, removed_to_learner)?;
111+
let new_membership = effective.membership.next_safe(new_voter_ids, convert_removed_to_learner)?;
113112

114113
tracing::debug!(?new_membership, "new membership config");
115114
Ok(new_membership)
116115
}
117116

117+
/// Ensures that the latest membership configuration has been committed.
118+
///
119+
/// Returns Ok if the last membership configuration is committed, or an InProgress error
120+
/// otherwise.
121+
pub(crate) fn ensure_last_membership_committed(&self) -> Result<(), InProgress<NID>> {
122+
let effective = self.effective();
123+
let committed = self.committed();
124+
125+
if self.committed().log_id == self.effective().log_id {
126+
// Ok: last membership(effective) is committed
127+
Ok(())
128+
} else {
129+
Err(InProgress {
130+
committed: committed.log_id,
131+
membership_log_id: effective.log_id,
132+
})
133+
}
134+
}
135+
118136
/// Update membership state if the specified committed_log_id is greater than `self.effective`
119137
pub(crate) fn commit(&mut self, committed_log_id: &Option<LogId<NID>>) {
120138
if committed_log_id >= &self.effective().log_id {

openraft/src/membership/membership_state_test.rs

+69
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
use std::sync::Arc;
22

3+
use maplit::btreemap;
34
use maplit::btreeset;
45

6+
use crate::error::ChangeMembershipError;
7+
use crate::error::EmptyMembership;
8+
use crate::error::InProgress;
9+
use crate::error::LearnerNotFound;
510
use crate::testing::log_id;
11+
use crate::ChangeMembers;
612
use crate::EffectiveMembership;
713
use crate::Membership;
814
use crate::MembershipState;
@@ -174,3 +180,66 @@ fn test_membership_state_truncate() -> anyhow::Result<()> {
174180

175181
Ok(())
176182
}
183+
184+
#[test]
185+
fn test_membership_state_next_membership_not_committed() -> anyhow::Result<()> {
186+
let new = || MembershipState::new(effmem(2, 2, m1()), effmem(3, 4, m123_345()));
187+
let res = new().create_updated_membership(ChangeMembers::Add(btreeset! {1}), false);
188+
189+
assert_eq!(
190+
Err(ChangeMembershipError::InProgress(InProgress {
191+
committed: Some(log_id(2, 2)),
192+
membership_log_id: Some(log_id(3, 4))
193+
})),
194+
res
195+
);
196+
197+
Ok(())
198+
}
199+
200+
#[test]
201+
fn test_membership_state_create_updated_membership_empty_voters() -> anyhow::Result<()> {
202+
let new = || MembershipState::new(effmem(3, 4, m1()), effmem(3, 4, m1()));
203+
let res = new().create_updated_membership(ChangeMembers::Remove(btreeset! {1}), false);
204+
205+
assert_eq!(Err(ChangeMembershipError::EmptyMembership(EmptyMembership {})), res);
206+
207+
Ok(())
208+
}
209+
210+
#[test]
211+
fn test_membership_state_create_updated_membership_learner_not_found() -> anyhow::Result<()> {
212+
let new = || MembershipState::new(effmem(3, 4, m1()), effmem(3, 4, m1()));
213+
let res = new().create_updated_membership(ChangeMembers::Add(btreeset! {2}), false);
214+
215+
assert_eq!(
216+
Err(ChangeMembershipError::LearnerNotFound(LearnerNotFound { node_id: 2 })),
217+
res
218+
);
219+
220+
Ok(())
221+
}
222+
223+
#[test]
224+
fn test_membership_state_create_updated_membership_removed_to_learner() -> anyhow::Result<()> {
225+
let new = || MembershipState::new(effmem(3, 4, m12()), effmem(3, 4, m123_345()));
226+
227+
// Do not leave removed voters as learner
228+
let res = new().create_updated_membership(ChangeMembers::Remove(btreeset! {1,2}), false);
229+
assert_eq!(
230+
Ok(Membership::new(vec![btreeset! {3,4,5}], btreemap! {3=>(),4=>(),5=>()})),
231+
res
232+
);
233+
234+
// Leave removed voters as learner
235+
let res = new().create_updated_membership(ChangeMembers::Remove(btreeset! {1,2}), true);
236+
assert_eq!(
237+
Ok(Membership::new(
238+
vec![btreeset! {3,4,5}],
239+
btreemap! {1=>(),2=>(),3=>(),4=>(),5=>()}
240+
)),
241+
res
242+
);
243+
244+
Ok(())
245+
}

0 commit comments

Comments
 (0)