use crate::hashkit::DynToken;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
#[repr(u8)]
pub enum PeerState {
#[default]
Unknown = 0,
Joining = 1,
Normal = 2,
Standby = 3,
Down = 4,
Reset = 5,
Leaving = 6,
}
impl PeerState {
#[must_use]
pub fn name(self) -> &'static str {
match self {
PeerState::Unknown => "UNKNOWN",
PeerState::Joining => "JOINING",
PeerState::Normal => "NORMAL",
PeerState::Standby => "STANDBY",
PeerState::Down => "DOWN",
PeerState::Reset => "RESET",
PeerState::Leaving => "LEAVING",
}
}
#[must_use]
pub fn is_routable(self) -> bool {
matches!(self, PeerState::Normal | PeerState::Joining)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct PeerEndpoint {
host: String,
port: u16,
}
impl PeerEndpoint {
#[must_use]
pub fn tcp(host: String, port: u16) -> Self {
Self { host, port }
}
#[must_use]
pub fn host(&self) -> &str {
&self.host
}
#[must_use]
pub fn port(&self) -> u16 {
self.port
}
#[must_use]
pub fn pname(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
#[derive(Clone, Debug)]
pub struct Peer {
idx: u32,
endpoint: PeerEndpoint,
rack: String,
dc: String,
tokens: Vec<DynToken>,
is_local: bool,
is_same_dc: bool,
is_secure: bool,
state: PeerState,
failure_count: u32,
last_state_ts_secs: u64,
fd: crate::cluster::failure_detector::PhiAccrual,
}
impl Peer {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
idx: u32,
endpoint: PeerEndpoint,
rack: String,
dc: String,
tokens: Vec<DynToken>,
is_local: bool,
is_same_dc: bool,
is_secure: bool,
) -> Self {
let state = if is_local {
PeerState::Joining
} else {
PeerState::Down
};
Self {
idx,
endpoint,
rack,
dc,
tokens,
is_local,
is_same_dc,
is_secure,
state,
failure_count: 0,
last_state_ts_secs: 0,
fd: crate::cluster::failure_detector::PhiAccrual::default(),
}
}
#[must_use]
pub fn idx(&self) -> u32 {
self.idx
}
#[must_use]
pub fn endpoint(&self) -> &PeerEndpoint {
&self.endpoint
}
#[must_use]
pub fn rack(&self) -> &str {
&self.rack
}
#[must_use]
pub fn dc(&self) -> &str {
&self.dc
}
#[must_use]
pub fn tokens(&self) -> &[DynToken] {
&self.tokens
}
#[must_use]
pub fn is_local(&self) -> bool {
self.is_local
}
#[must_use]
pub fn is_same_dc(&self) -> bool {
self.is_same_dc
}
#[must_use]
pub fn is_secure(&self) -> bool {
self.is_secure
}
#[must_use]
pub fn state(&self) -> PeerState {
self.state
}
pub fn set_state(&mut self, state: PeerState, ts_secs: u64) {
self.state = state;
self.last_state_ts_secs = ts_secs;
}
#[must_use]
pub fn last_state_ts_secs(&self) -> u64 {
self.last_state_ts_secs
}
pub fn record_failure(&mut self) {
self.failure_count = self.failure_count.saturating_add(1);
}
pub fn record_success(&mut self) {
self.failure_count = 0;
}
#[must_use]
pub fn failure_count(&self) -> u32 {
self.failure_count
}
#[must_use]
pub fn failure_detector(&self) -> &crate::cluster::failure_detector::PhiAccrual {
&self.fd
}
pub fn failure_detector_mut(&mut self) -> &mut crate::cluster::failure_detector::PhiAccrual {
&mut self.fd
}
#[must_use]
pub fn primary_token(&self) -> Option<&DynToken> {
self.tokens.first()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mk(rack: &str, dc: &str, is_local: bool, is_same_dc: bool) -> Peer {
Peer::new(
0,
PeerEndpoint::tcp("127.0.0.1".into(), 8101),
rack.into(),
dc.into(),
vec![DynToken::from_u32(1)],
is_local,
is_same_dc,
false,
)
}
#[test]
fn local_peer_starts_joining() {
let p = mk("r", "d", true, true);
assert_eq!(p.state(), PeerState::Joining);
}
#[test]
fn remote_peer_starts_down() {
let p = mk("r", "d", false, true);
assert_eq!(p.state(), PeerState::Down);
}
#[test]
fn state_names_round_trip() {
for s in [
PeerState::Unknown,
PeerState::Joining,
PeerState::Normal,
PeerState::Standby,
PeerState::Down,
PeerState::Reset,
PeerState::Leaving,
] {
assert!(!s.name().is_empty());
}
}
#[test]
fn failure_counter_works() {
let mut p = mk("r", "d", false, true);
p.record_failure();
p.record_failure();
assert_eq!(p.failure_count(), 2);
p.record_success();
assert_eq!(p.failure_count(), 0);
}
#[test]
fn routable_states() {
assert!(PeerState::Normal.is_routable());
assert!(PeerState::Joining.is_routable());
assert!(!PeerState::Down.is_routable());
assert!(!PeerState::Leaving.is_routable());
}
}