use std::collections::HashMap;
use std::pin::Pin;
use std::sync::Arc;
use async_trait::async_trait;
use futures::{Stream, StreamExt};
use openraft::RaftTypeConfig;
use tsoracle_consensus::{ConsensusDriver, ConsensusError, LeaderState};
use tsoracle_core::Epoch;
use tsoracle_openraft_toolkit::LeadershipState;
use tsoracle_openraft_toolkit::lifecycle::leader::stream_from_receiver;
use crate::host::OpenraftHighWaterHost;
pub struct OpenraftDriver<H: OpenraftHighWaterHost> {
host: Arc<H>,
peers: Arc<HashMap<<H::Config as RaftTypeConfig>::NodeId, String>>,
}
impl<H: OpenraftHighWaterHost> OpenraftDriver<H> {
pub fn new(host: H) -> Arc<Self> {
Self::with_peers(host, HashMap::new())
}
pub fn with_peers(
host: H,
peers: HashMap<<H::Config as RaftTypeConfig>::NodeId, String>,
) -> Arc<Self> {
Arc::new(Self {
host: Arc::new(host),
peers: Arc::new(peers),
})
}
pub fn from_arc(host: Arc<H>) -> Arc<Self> {
Self::from_arc_with_peers(host, HashMap::new())
}
pub fn from_arc_with_peers(
host: Arc<H>,
peers: HashMap<<H::Config as RaftTypeConfig>::NodeId, String>,
) -> Arc<Self> {
Arc::new(Self {
host,
peers: Arc::new(peers),
})
}
}
#[async_trait]
impl<H: OpenraftHighWaterHost> ConsensusDriver for OpenraftDriver<H> {
fn leadership_events(&self) -> Pin<Box<dyn Stream<Item = LeaderState> + Send>> {
let host = Arc::clone(&self.host);
let peers = Arc::clone(&self.peers);
Box::pin(owned_leadership_stream::<H>(host, peers))
}
async fn load_high_water(&self) -> Result<u64, ConsensusError> {
self.host.current_high_water().await
}
async fn persist_high_water(
&self,
at_least: u64,
_epoch: Epoch,
) -> Result<u64, ConsensusError> {
self.host.submit_advance(at_least).await
}
}
fn owned_leadership_stream<H: OpenraftHighWaterHost>(
host: Arc<H>,
peers: Arc<HashMap<<H::Config as RaftTypeConfig>::NodeId, String>>,
) -> impl Stream<Item = LeaderState> + Send + 'static {
let rx = host.metrics();
let inner: Pin<Box<dyn Stream<Item = LeaderState> + Send>> = Box::pin(
stream_from_receiver::<H::Config>(rx)
.map(move |state| map_leader_state::<H::Config>(state, peers.as_ref())),
);
KeepAlive { _host: host, inner }
}
struct KeepAlive<H: OpenraftHighWaterHost> {
_host: Arc<H>,
inner: Pin<Box<dyn Stream<Item = LeaderState> + Send>>,
}
impl<H: OpenraftHighWaterHost> Stream for KeepAlive<H> {
type Item = LeaderState;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
fn map_leader_state<C: RaftTypeConfig>(
s: LeadershipState<C>,
peers: &HashMap<C::NodeId, String>,
) -> LeaderState {
match s {
LeadershipState::Leader { term } => LeaderState::Leader {
epoch: Epoch(u128::from(term)),
},
LeadershipState::Follower { term, leader } => LeaderState::Follower {
leader_endpoint: leader.and_then(|(id, _node)| peers.get(&id).cloned()),
leader_epoch: Some(Epoch(u128::from(term))),
},
LeadershipState::Candidate { .. }
| LeadershipState::Learner
| LeadershipState::Shutdown => LeaderState::Unknown,
}
}
#[cfg(test)]
mod tests {
use super::map_leader_state;
use crate::type_config::TypeConfig;
use std::collections::HashMap;
use tsoracle_consensus::LeaderState;
use tsoracle_core::Epoch;
use tsoracle_openraft_toolkit::LeadershipState;
#[test]
fn leader_maps_to_leader_with_epoch() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(LeadershipState::Leader { term: 7 }, &peers);
assert_eq!(s, LeaderState::Leader { epoch: Epoch(7) });
}
#[test]
fn follower_with_no_leader_maps_to_follower_with_epoch() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(
LeadershipState::Follower {
term: 3,
leader: None,
},
&peers,
);
assert_eq!(
s,
LeaderState::Follower {
leader_endpoint: None,
leader_epoch: Some(Epoch(3))
}
);
}
#[test]
fn follower_with_known_leader_resolves_endpoint_and_epoch() {
let mut peers = HashMap::new();
peers.insert(2u64, "http://node-2:50051".to_string());
let s = map_leader_state::<TypeConfig>(
LeadershipState::Follower {
term: 4,
leader: Some((
2u64,
crate::type_config::OpenraftPeer {
addr: "raft-addr".into(),
},
)),
},
&peers,
);
assert_eq!(
s,
LeaderState::Follower {
leader_endpoint: Some("http://node-2:50051".into()),
leader_epoch: Some(Epoch(4)),
}
);
}
#[test]
fn follower_with_leader_absent_from_peer_map_has_no_endpoint() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(
LeadershipState::Follower {
term: 4,
leader: Some((
9u64,
crate::type_config::OpenraftPeer {
addr: "raft-addr".into(),
},
)),
},
&peers,
);
assert_eq!(
s,
LeaderState::Follower {
leader_endpoint: None,
leader_epoch: Some(Epoch(4))
}
);
}
#[test]
fn follower_with_leader_absent_from_nonempty_peer_map_has_no_endpoint() {
let mut peers = HashMap::new();
peers.insert(1u64, "http://node-1:50051".to_string());
peers.insert(3u64, "http://node-3:50051".to_string());
let s = map_leader_state::<TypeConfig>(
LeadershipState::Follower {
term: 5,
leader: Some((
2u64,
crate::type_config::OpenraftPeer {
addr: "raft-addr".into(),
},
)),
},
&peers,
);
assert_eq!(
s,
LeaderState::Follower {
leader_endpoint: None,
leader_epoch: Some(Epoch(5))
}
);
}
#[test]
fn candidate_maps_to_unknown() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(LeadershipState::Candidate { term: 5 }, &peers);
assert_eq!(s, LeaderState::Unknown);
}
#[test]
fn learner_maps_to_unknown() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(LeadershipState::Learner, &peers);
assert_eq!(s, LeaderState::Unknown);
}
#[test]
fn shutdown_maps_to_unknown() {
let peers = HashMap::new();
let s = map_leader_state::<TypeConfig>(LeadershipState::Shutdown, &peers);
assert_eq!(s, LeaderState::Unknown);
}
}