use std::error::Error;
use std::fmt;
use std::sync::Arc;
use openraft_macros::since;
use validit::Validate;
use crate::ChangeMembers;
use crate::LogIdOptionExt;
use crate::Membership;
use crate::errors::ChangeMembershipError;
use crate::errors::InProgress;
use crate::errors::NodeMetadataChanged;
use crate::errors::UnsupportedMembershipTransition;
#[cfg(test)]
mod change_membership_test;
#[cfg(test)]
mod membership_state_test;
use crate::log_id::LogId;
use crate::membership::StoredMembership;
use crate::node::Node;
use crate::node::NodeId;
use crate::vote::RaftCommittedLeaderId;
#[since(
version = "0.10.0",
change = "from `MembershipState<C>` to `MembershipState<CLID, NID, N>`"
)]
#[derive(Debug, Clone)]
#[derive(PartialEq, Eq)]
pub struct MembershipState<CLID, NID, N>
where
CLID: RaftCommittedLeaderId,
NID: NodeId,
N: Node,
{
committed: Arc<StoredMembership<CLID, NID, N>>,
effective: Arc<StoredMembership<CLID, NID, N>>,
}
impl<CLID, NID, N> Default for MembershipState<CLID, NID, N>
where
CLID: RaftCommittedLeaderId,
NID: NodeId,
N: Node,
{
fn default() -> Self {
Self {
committed: Arc::new(StoredMembership::default()),
effective: Arc::new(StoredMembership::default()),
}
}
}
impl<CLID, NID, N> fmt::Display for MembershipState<CLID, NID, N>
where
CLID: RaftCommittedLeaderId,
NID: NodeId,
N: Node,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MembershipState{{committed: {}, effective: {}}}",
self.committed, self.effective
)
}
}
impl<CLID, NID, N> MembershipState<CLID, NID, N>
where
CLID: RaftCommittedLeaderId,
NID: NodeId,
N: Node,
{
pub(crate) fn new(
committed: Arc<StoredMembership<CLID, NID, N>>,
effective: Arc<StoredMembership<CLID, NID, N>>,
) -> Self {
Self { committed, effective }
}
pub(crate) fn contains(&self, id: &NID) -> bool {
self.effective.membership().contains(id)
}
pub(crate) fn is_voter(&self, id: &NID) -> bool {
self.effective.membership().is_voter(id)
}
pub(crate) fn is_voter_in_effective_or_committed(&self, id: &NID) -> bool {
self.effective.is_voter(id) || self.committed.is_voter(id)
}
pub(crate) fn next_membership(
&self,
change: ChangeMembers<NID, N>,
retain: bool,
) -> Result<Membership<NID, N>, ChangeMembershipError<CLID, NID>> {
self.ensure_committed()?;
let effective = self.effective();
let membership = effective.membership().clone();
let new_membership = membership.change(change, retain)?;
Ok(new_membership)
}
pub(crate) fn validate_append_membership(
&self,
proposed: &Membership<NID, N>,
) -> Result<(), ChangeMembershipError<CLID, NID>> {
self.ensure_committed()?;
proposed.ensure_valid()?;
proposed.ensure_quorum_defined()?;
let effective = self.effective().membership();
let changed_node_id = effective.find_changed_node_metadata(proposed);
if let Some(node_id) = changed_node_id {
return Err(NodeMetadataChanged { node_id }.into());
}
let compatible = effective.is_direct_append_compatible_with(proposed);
if !compatible {
let err = UnsupportedMembershipTransition {
previous: effective.get_joint_config().clone(),
proposed: proposed.get_joint_config().clone(),
};
return Err(err.into());
}
Ok(())
}
fn ensure_committed(&self) -> Result<(), InProgress<CLID>> {
let effective = self.effective();
let committed = self.committed();
if effective.log_id() == committed.log_id() {
return Ok(());
}
Err(InProgress {
committed: committed.log_id().clone(),
membership_log_id: effective.log_id().clone(),
})
}
pub(crate) fn commit(&mut self, committed_log_id: &Option<LogId<CLID>>) {
let current = self.committed.log_id().clone();
let last = self.effective().log_id().clone();
if committed_log_id >= &last && current < last {
debug_assert!(committed_log_id.index() >= last.index());
self.committed = self.effective.clone();
}
}
pub(crate) fn install_membership_snapshot(
&mut self,
membership_snapshot: Arc<StoredMembership<CLID, NID, N>>,
snapshot_last_log_index: u64,
) {
let effective_is_purged = self.effective.log_id().index() <= Some(snapshot_last_log_index);
if effective_is_purged {
self.committed = membership_snapshot.clone();
self.effective = membership_snapshot;
return;
}
if membership_snapshot.log_id().index() >= self.effective.log_id().index() {
self.effective = membership_snapshot.clone()
}
#[allow(clippy::collapsible_if)]
if cfg!(debug_assertions) {
if membership_snapshot.log_id() == self.committed.log_id() {
debug_assert_eq!(
membership_snapshot.membership(),
self.committed.membership(),
"the same log id implies the same membership"
);
}
}
if membership_snapshot.log_id() > self.committed.log_id() {
self.committed = membership_snapshot
}
}
pub(crate) fn append(&mut self, m: Arc<StoredMembership<CLID, NID, N>>) {
debug_assert!(
m.log_id() > self.effective.log_id(),
"new membership has to have a greater log_id"
);
debug_assert!(
m.log_id().index() > self.effective.log_id().index(),
"new membership has to have a greater index"
);
self.committed = self.effective.clone();
self.effective = m;
}
pub(crate) fn truncate(&mut self, since: u64) -> Option<Arc<StoredMembership<CLID, NID, N>>> {
debug_assert!(
since >= self.committed().log_id().next_index(),
"committed log should never be truncated: committed membership cannot conflict with the leader"
);
if Some(since) <= self.effective().log_id().index() {
tracing::debug!(
"effective membership is in conflicting logs, revert it to last committed: effective: {}, committed: {}",
self.effective(),
self.committed()
);
self.effective = self.committed.clone();
return Some(self.effective.clone());
}
None
}
#[cfg(test)]
pub(crate) fn set_effective(&mut self, e: Arc<StoredMembership<CLID, NID, N>>) {
self.effective = e
}
pub fn committed(&self) -> &Arc<StoredMembership<CLID, NID, N>> {
&self.committed
}
pub fn effective(&self) -> &Arc<StoredMembership<CLID, NID, N>> {
&self.effective
}
}
impl<CLID, NID, N> Validate for MembershipState<CLID, NID, N>
where
CLID: RaftCommittedLeaderId,
NID: NodeId,
N: Node,
{
fn validate(&self) -> Result<(), Box<dyn Error>> {
validit::less_equal!(self.committed.log_id(), self.effective.log_id());
validit::less_equal!(self.committed.log_id().index(), self.effective.log_id().index());
Ok(())
}
}