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::DENSE_WRITE_VERSION;
use tsoracle_openraft_toolkit::LeadershipState;
use tsoracle_openraft_toolkit::leadership_events_from_metrics;
use crate::host::OpenraftHighWaterHost;
use crate::type_config::ServiceEndpoint;
pub struct OpenraftDriver<H: OpenraftHighWaterHost> {
host: Arc<H>,
}
impl<H: OpenraftHighWaterHost> OpenraftDriver<H> {
pub fn new(host: H) -> Arc<Self> {
Arc::new(Self {
host: Arc::new(host),
})
}
pub fn from_arc(host: Arc<H>) -> Arc<Self> {
Arc::new(Self { host })
}
}
#[async_trait]
impl<H: OpenraftHighWaterHost> ConsensusDriver for OpenraftDriver<H>
where
<H::Config as RaftTypeConfig>::Node: ServiceEndpoint,
{
fn leadership_events(&self) -> Pin<Box<dyn Stream<Item = LeaderState> + Send>> {
let host = Arc::clone(&self.host);
Box::pin(owned_leadership_stream::<H>(host))
}
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> {
tsoracle_consensus::reject_out_of_range_advance(at_least)?;
self.host.submit_advance(at_least).await
}
async fn advance_dense(
&self,
key: &tsoracle_core::SeqKey,
count: u32,
_expected_epoch: Epoch,
) -> Result<u64, ConsensusError> {
let active = self.host.active_write_version();
if active < DENSE_WRITE_VERSION {
return Err(ConsensusError::DenseNotActivated {
required: DENSE_WRITE_VERSION,
active,
});
}
self.host.submit_advance_dense(key, count).await
}
async fn load_dense_seq(&self, key: &tsoracle_core::SeqKey) -> Result<u64, ConsensusError> {
self.host.current_dense_seq(key).await
}
}
fn owned_leadership_stream<H: OpenraftHighWaterHost>(
host: Arc<H>,
) -> impl Stream<Item = LeaderState> + Send + 'static
where
<H::Config as RaftTypeConfig>::Node: ServiceEndpoint,
{
let rx = host.metrics();
let inner: Pin<Box<dyn Stream<Item = LeaderState> + Send>> = Box::pin(
leadership_events_from_metrics::<H::Config>(rx).map(map_leader_state::<H::Config>),
);
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>) -> LeaderState
where
C::Node: ServiceEndpoint,
{
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)| node.service_endpoint()),
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 tsoracle_consensus::LeaderState;
use tsoracle_core::{Epoch, PeerEndpoint};
use tsoracle_openraft_toolkit::LeadershipState;
#[test]
fn follower_resolves_endpoint_from_node_service_endpoint() {
let state = map_leader_state::<TypeConfig>(LeadershipState::Follower {
term: 4,
leader: Some((
2u64,
crate::type_config::OpenraftPeer {
addr: "node-2:50052".into(),
service_endpoint: "node-2:50051".into(),
admin_endpoint: String::new(),
},
)),
});
assert_eq!(
state,
LeaderState::Follower {
leader_endpoint: Some(PeerEndpoint::try_from("node-2:50051").unwrap()),
leader_epoch: Some(Epoch(4)),
}
);
}
#[test]
fn follower_with_endpointless_node_has_no_endpoint() {
let state = map_leader_state::<TypeConfig>(LeadershipState::Follower {
term: 5,
leader: Some((
2u64,
crate::type_config::OpenraftPeer {
addr: "node-2:50052".into(),
service_endpoint: String::new(),
admin_endpoint: String::new(),
},
)),
});
assert_eq!(
state,
LeaderState::Follower {
leader_endpoint: None,
leader_epoch: Some(Epoch(5)),
}
);
}
#[test]
fn follower_with_no_leader_maps_to_follower_with_epoch() {
let state = map_leader_state::<TypeConfig>(LeadershipState::Follower {
term: 3,
leader: None,
});
assert_eq!(
state,
LeaderState::Follower {
leader_endpoint: None,
leader_epoch: Some(Epoch(3)),
}
);
}
#[test]
fn leader_maps_to_leader_with_epoch() {
let s = map_leader_state::<TypeConfig>(LeadershipState::Leader { term: 7 });
assert_eq!(s, LeaderState::Leader { epoch: Epoch(7) });
}
#[test]
fn candidate_maps_to_unknown() {
let s = map_leader_state::<TypeConfig>(LeadershipState::Candidate { term: 5 });
assert_eq!(s, LeaderState::Unknown);
}
#[test]
fn learner_maps_to_unknown() {
let s = map_leader_state::<TypeConfig>(LeadershipState::Learner);
assert_eq!(s, LeaderState::Unknown);
}
#[test]
fn shutdown_maps_to_unknown() {
let s = map_leader_state::<TypeConfig>(LeadershipState::Shutdown);
assert_eq!(s, LeaderState::Unknown);
}
struct EchoHost {
rx: openraft::type_config::alias::WatchReceiverOf<
TypeConfig,
openraft::RaftMetrics<TypeConfig>,
>,
}
#[async_trait::async_trait]
impl crate::host::OpenraftHighWaterHost for EchoHost {
type Config = TypeConfig;
fn metrics(
&self,
) -> openraft::type_config::alias::WatchReceiverOf<
Self::Config,
openraft::RaftMetrics<Self::Config>,
> {
self.rx.clone()
}
async fn current_high_water(&self) -> Result<u64, tsoracle_consensus::ConsensusError> {
Ok(0)
}
async fn submit_advance(
&self,
at_least: u64,
) -> Result<u64, tsoracle_consensus::ConsensusError> {
Ok(at_least)
}
fn active_write_version(&self) -> u8 {
tsoracle_openraft_toolkit::BASELINE_WRITE_VERSION
}
async fn submit_advance_dense(
&self,
_key: &tsoracle_core::SeqKey,
_count: u32,
) -> Result<u64, tsoracle_consensus::ConsensusError> {
Err(tsoracle_consensus::ConsensusError::DenseUnsupported)
}
async fn current_dense_seq(
&self,
_key: &tsoracle_core::SeqKey,
) -> Result<u64, tsoracle_consensus::ConsensusError> {
Err(tsoracle_consensus::ConsensusError::DenseUnsupported)
}
}
fn echo_driver() -> std::sync::Arc<super::OpenraftDriver<EchoHost>> {
use openraft::type_config::TypeConfigExt;
let metrics = openraft::RaftMetrics::<TypeConfig>::new_initial(1u64);
let (_tx, rx) = <TypeConfig as TypeConfigExt>::watch_channel(metrics);
super::OpenraftDriver::new(EchoHost { rx })
}
#[tokio::test]
async fn persist_high_water_rejects_out_of_range_before_submit() {
use tsoracle_consensus::ConsensusDriver;
use tsoracle_core::PHYSICAL_MS_MAX;
let driver = echo_driver();
let err = driver
.persist_high_water(PHYSICAL_MS_MAX + 1, Epoch::ZERO)
.await
.expect_err("an out-of-range advance must be rejected, not persisted");
assert!(
matches!(err, tsoracle_consensus::ConsensusError::AdvanceOutOfRange(at_least) if at_least == PHYSICAL_MS_MAX + 1),
"out-of-range advance must surface as AdvanceOutOfRange carrying the offending value, got {err:?}"
);
assert_eq!(
driver
.persist_high_water(PHYSICAL_MS_MAX, Epoch::ZERO)
.await
.expect("the maximum in-range value must persist"),
PHYSICAL_MS_MAX
);
}
}