use hashring::HashRing;
use std::fmt;
use std::hash::Hash;
#[derive(Debug, Clone, Hash, PartialEq)]
pub struct VNode {
id: usize,
name: String,
}
impl VNode {
fn new(id: usize, name: String) -> Self {
VNode { id, name }
}
}
impl fmt::Display for VNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}|{}", self.name, self.id)
}
}
impl VNode {
pub fn name(&self) -> &str {
&self.name
}
}
pub struct VNodeHashRing {
replica_count: usize,
ring: HashRing<VNode>,
}
impl VNodeHashRing {
pub fn new(replica_count: usize) -> Self {
VNodeHashRing {
replica_count,
ring: HashRing::new(),
}
}
pub fn add(&mut self, name: String) {
for id in 0..self.replica_count {
let vnode = VNode::new(id, name.clone());
self.ring.add(vnode);
}
}
pub fn get<U: Hash>(&self, key: &U) -> Option<&VNode> {
self.ring.get(key)
}
pub fn get_with_replicas<U: Hash>(&self, key: &U, replicas: usize) -> Option<Vec<VNode>> {
self.ring.get_with_replicas(key, replicas)
}
pub fn len(&self) -> usize {
self.ring.len()
}
pub fn is_empty(&self) -> bool {
self.ring.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vnode_new() {
let vnode = VNode::new(1, "default-pod-1".to_string());
assert_eq!(vnode.id, 1);
assert_eq!(vnode.name, "default-pod-1");
}
#[test]
fn test_vnode_to_string() {
let vnode = VNode::new(1, "default-pod-1".to_string());
assert_eq!(vnode.to_string(), "default-pod-1|1");
}
#[test]
fn test_hashring_new() {
let ring = VNodeHashRing::new(3);
assert_eq!(ring.replica_count, 3);
assert!(ring.is_empty());
assert_eq!(ring.len(), 0);
}
#[test]
fn test_add_and_len() {
let mut ring = VNodeHashRing::new(2);
ring.add("default-pod-1".to_string());
assert_eq!(ring.len(), 2); ring.add("default-pod-2".to_string());
assert_eq!(ring.len(), 4); assert!(!ring.is_empty());
}
#[test]
fn test_get_empty_ring() {
let ring = VNodeHashRing::new(2);
let key = "test_key";
assert!(ring.get(&key).is_none());
}
#[test]
fn test_get_with_nodes() {
let mut ring = VNodeHashRing::new(2);
ring.add("default-pod-1".to_string());
ring.add("default-pod-2".to_string());
let key = "test_key";
let node = ring.get(&key);
assert!(node.is_some());
let node = node.unwrap();
assert!(node.name() == "default-pod-1" || node.name() == "default-pod-2");
assert!(node.id == 0 || node.id == 1);
}
#[test]
fn test_get_with_replicas_empty() {
let ring = VNodeHashRing::new(2);
let key = "test_key";
assert!(ring.get_with_replicas(&key, 2).is_none());
}
#[test]
fn test_get_with_replicas() {
let mut ring = VNodeHashRing::new(2);
ring.add("default-pod-1".to_string());
ring.add("default-pod-2".to_string());
let key = "test_key";
let replicas = ring.get_with_replicas(&key, 3).unwrap();
assert_eq!(replicas.len(), 4);
assert!(replicas.iter().all(|vnode| {
(vnode.name() == "default-pod-1" || vnode.name() == "default-pod-2")
&& (vnode.id == 0 || vnode.id == 1)
}));
}
#[test]
fn test_get_with_replicas_exact_size() {
let mut ring = VNodeHashRing::new(2);
ring.add("default-pod-1".to_string());
ring.add("default-pod-2".to_string());
let key = "test_key";
let replicas = ring.get_with_replicas(&key, 4).unwrap();
assert_eq!(replicas.len(), 5);
}
#[test]
fn test_get_with_replicas_smaller_size() {
let mut ring = VNodeHashRing::new(2);
ring.add("default-pod-1".to_string());
ring.add("default-pod-2".to_string());
let key = "test_key";
let replicas = ring.get_with_replicas(&key, 2).unwrap();
assert_eq!(replicas.len(), 3);
assert!(replicas.iter().all(|vnode| {
(vnode.name() == "default-pod-1" || vnode.name() == "default-pod-2")
&& (vnode.id == 0 || vnode.id == 1)
}));
}
#[test]
fn test_add_order_does_not_affect_get_result_many_keys() {
use uuid::Uuid;
let nodes_a = vec![
"default-pod-1".to_string(),
"default-pod-2".to_string(),
"default-pod-3".to_string(),
];
let nodes_b = vec![
"default-pod-3".to_string(),
"default-pod-1".to_string(),
"default-pod-2".to_string(),
];
let mut ring_a = VNodeHashRing::new(150);
for n in nodes_a {
ring_a.add(n);
}
let mut ring_b = VNodeHashRing::new(150);
for n in nodes_b {
ring_b.add(n);
}
for _ in 0..200 {
let key = Uuid::new_v4().to_string();
let va = ring_a.get(&key).unwrap();
let vb = ring_b.get(&key).unwrap();
assert_eq!(va.to_string(), vb.to_string(), "key={}", key);
}
}
}