├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - master 6 | 7 | name: CI 8 | 9 | # Adapted from recipe: https://github.com/actions-rs/meta/blob/master/recipes/matrix.md#workflow. 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | rust: 16 | - stable 17 | - beta 18 | - nightly 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: ${{ matrix.rust }} 27 | override: true 28 | components: rustfmt, clippy 29 | 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: build 33 | 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: test 37 | 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | command: fmt 41 | args: --all -- --check 42 | 43 | - uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | args: -- -D warnings 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | release.txt -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.3.6](https://github.com/jeromefroe/hashring-rs/tree/0.3.6) - 2024-07-31 4 | 5 | - Remove `Clone` trait bound from `IntoIterator` implementation. 6 | 7 | ## [v0.3.5](https://github.com/jeromefroe/hashring-rs/tree/0.3.5) - 2024-05-24 8 | 9 | - Derive `PartialEq` and `Debug` traits on `HashRing`. 10 | 11 | ## [v0.3.4](https://github.com/jeromefroe/hashring-rs/tree/0.3.4) - 2024-05-18 12 | 13 | - Derive `Clone` trait on `HashRing`. 14 | 15 | ## [v0.3.3](https://github.com/jeromefroe/hashring-rs/tree/0.3.3) - 2023-11-12 16 | 17 | - Add `batch_add` method. 18 | 19 | ## [v0.3.2](https://github.com/jeromefroe/hashring-rs/tree/0.3.2) - 2023-07-19 20 | 21 | - Add `get_with_replicas` method. 22 | 23 | ## [v0.3.1](https://github.com/jeromefroe/hashring-rs/tree/0.3.1) - 2023-07-14 24 | 25 | - Add support for iterators. 26 | 27 | ## [v0.3.0](https://github.com/jeromefroe/hashring-rs/tree/0.3.0) - 2022-03-05 28 | 29 | - Get rid of unnecessary transformation of hash value. 30 | 31 | ## [v0.2.1](https://github.com/jeromefroe/hashring-rs/tree/0.2.1) - 2022-02-28 32 | 33 | - Use reference instead of mutable reference in `get` method. 34 | 35 | ## [v0.2.0](https://github.com/jeromefroe/hashring-rs/tree/0.2.0) - 2020-02-13 36 | 37 | - Make hash function configurable and replace MD5 as default with SipHash. 38 | 39 | ## [v0.1.0](https://github.com/jeromefroe/hashring-rs/tree/0.1.0) - 2016-11-27 40 | 41 | - Initial release. 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hashring" 3 | version = "0.3.6" 4 | authors = ["Jerome Froelich "] 5 | description = "A minimal implementation of consistent hashing" 6 | homepage = "https://github.com/jeromefroe/hashring-rs" 7 | repository = "https://github.com/jeromefroe/hashring-rs.git" 8 | documentation = "https://docs.rs/hashring/" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = ["consistent", "hashing", "hash", "ring"] 12 | 13 | [features] 14 | nightly = [] 15 | 16 | [dependencies] 17 | siphasher = "0.3.1" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jerome Froelich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HashRing 2 | 3 | [![Build Status](https://travis-ci.org/jeromefroe/hashring-rs.svg?branch=master)](https://travis-ci.org/jeromefroe/hashring-rs) 4 | [![codecov](https://codecov.io/gh/jeromefroe/hashring-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/jeromefroe/hashring-rs) 5 | [![crates.io](https://img.shields.io/crates/v/hashring.svg)](https://crates.io/crates/hashring/) 6 | [![docs.rs](https://docs.rs/hashring/badge.svg)](https://docs.rs/hashring/) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/jeromefroe/hashring-rs/master/LICENSE) 8 | 9 | [Documentation](https://docs.rs/hashring/) 10 | 11 | A minimal implementation of consistent hashing as described in [Consistent 12 | Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot 13 | Spots on the World Wide Web](https://www.akamai.com/es/es/multimedia/documents/technical-publication/consistent-hashing-and-random-trees-distributed-caching-protocols-for-relieving-hot-spots-on-the-world-wide-web-technical-publication.pdf). 14 | Clients can use the `HashRing` struct to add consistent hashing to their 15 | applications. `HashRing`'s API consists of three methods: `add`, `remove`, 16 | and `get` for adding a node to the ring, removing a node from the ring, and 17 | getting the node responsible for the provided key. 18 | 19 | ## Example 20 | 21 | Below is a simple example of how an application might use `HashRing` to make 22 | use of consistent hashing. Since `HashRing` exposes only a minimal API clients 23 | can build other abstractions, such as virtual nodes, on top of it. The example 24 | below shows one potential implementation of virtual nodes on top of `HashRing` 25 | 26 | ```rust 27 | use std::net::{IpAddr, SocketAddr}; 28 | use std::str::FromStr; 29 | 30 | use hashring::HashRing; 31 | 32 | #[derive(Debug, Copy, Clone, Hash, PartialEq)] 33 | struct VNode { 34 | id: usize, 35 | addr: SocketAddr, 36 | } 37 | 38 | impl VNode { 39 | fn new(ip: &str, port: u16, id: usize) -> Self { 40 | let addr = SocketAddr::new(IpAddr::from_str(&ip).unwrap(), port); 41 | VNode { 42 | id: id, 43 | addr: addr, 44 | } 45 | } 46 | } 47 | 48 | impl ToString for VNode { 49 | fn to_string(&self) -> String { 50 | format!("{}|{}", self.addr, self.id) 51 | } 52 | } 53 | 54 | fn main() { 55 | let mut ring: HashRing = HashRing::new(); 56 | 57 | let mut nodes = vec![]; 58 | nodes.push(VNode::new("127.0.0.1", 1024, 1)); 59 | nodes.push(VNode::new("127.0.0.1", 1024, 2)); 60 | nodes.push(VNode::new("127.0.0.2", 1024, 1)); 61 | nodes.push(VNode::new("127.0.0.2", 1024, 2)); 62 | nodes.push(VNode::new("127.0.0.2", 1024, 3)); 63 | nodes.push(VNode::new("127.0.0.3", 1024, 1)); 64 | 65 | for node in nodes { 66 | ring.add(node); 67 | } 68 | 69 | println!("{:?}", ring.get(&"foo")); 70 | println!("{:?}", ring.get(&"bar")); 71 | println!("{:?}", ring.get(&"baz")); 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2016 Jerome Froelich 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | //! A minimal implementation of consistent hashing as described in [Consistent 24 | //! Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot 25 | //! Spots on the World Wide Web](https://www.akamai.com/es/es/multimedia/documents/technical-publication/consistent-hashing-and-random-trees-distributed-caching-protocols-for-relieving-hot-spots-on-the-world-wide-web-technical-publication.pdf). 26 | //! Clients can use the `HashRing` struct to add consistent hashing to their 27 | //! applications. `HashRing`'s API consists of three methods: `add`, `remove`, 28 | //! and `get` for adding a node to the ring, removing a node from the ring, and 29 | //! getting the node responsible for the provided key. 30 | //! 31 | //! ## Example 32 | //! 33 | //! Below is a simple example of how an application might use `HashRing` to make 34 | //! use of consistent hashing. Since `HashRing` exposes only a minimal API clients 35 | //! can build other abstractions, such as virtual nodes, on top of it. The example 36 | //! below shows one potential implementation of virtual nodes on top of `HashRing` 37 | //! 38 | //! ``` rust,no_run 39 | //! extern crate hashring; 40 | //! 41 | //! use std::net::{IpAddr, SocketAddr}; 42 | //! use std::str::FromStr; 43 | //! 44 | //! use hashring::HashRing; 45 | //! 46 | //! #[derive(Debug, Copy, Clone, Hash, PartialEq)] 47 | //! struct VNode { 48 | //! id: usize, 49 | //! addr: SocketAddr, 50 | //! } 51 | //! 52 | //! impl VNode { 53 | //! fn new(ip: &str, port: u16, id: usize) -> Self { 54 | //! let addr = SocketAddr::new(IpAddr::from_str(&ip).unwrap(), port); 55 | //! VNode { 56 | //! id: id, 57 | //! addr: addr, 58 | //! } 59 | //! } 60 | //! } 61 | //! 62 | //! fn main() { 63 | //! let mut ring: HashRing = HashRing::new(); 64 | //! 65 | //! let mut nodes = vec![]; 66 | //! nodes.push(VNode::new("127.0.0.1", 1024, 1)); 67 | //! nodes.push(VNode::new("127.0.0.1", 1024, 2)); 68 | //! nodes.push(VNode::new("127.0.0.2", 1024, 1)); 69 | //! nodes.push(VNode::new("127.0.0.2", 1024, 2)); 70 | //! nodes.push(VNode::new("127.0.0.2", 1024, 3)); 71 | //! nodes.push(VNode::new("127.0.0.3", 1024, 1)); 72 | //! 73 | //! for node in nodes { 74 | //! ring.add(node); 75 | //! } 76 | //! 77 | //! println!("{:?}", ring.get(&"foo")); 78 | //! println!("{:?}", ring.get(&"bar")); 79 | //! println!("{:?}", ring.get(&"baz")); 80 | //! } 81 | //! ``` 82 | 83 | extern crate siphasher; 84 | 85 | use siphasher::sip::SipHasher; 86 | use std::cmp::Ordering; 87 | use std::fmt::Debug; 88 | use std::hash::BuildHasher; 89 | use std::hash::Hash; 90 | 91 | #[derive(Clone, PartialEq, Debug)] 92 | pub struct DefaultHashBuilder; 93 | 94 | impl BuildHasher for DefaultHashBuilder { 95 | type Hasher = SipHasher; 96 | 97 | fn build_hasher(&self) -> Self::Hasher { 98 | SipHasher::new() 99 | } 100 | } 101 | 102 | // Node is an internal struct used to encapsulate the nodes that will be added and 103 | // removed from `HashRing` 104 | #[derive(Clone, Debug)] 105 | struct Node { 106 | key: u64, 107 | node: T, 108 | } 109 | 110 | impl Node { 111 | fn new(key: u64, node: T) -> Node { 112 | Node { key, node } 113 | } 114 | } 115 | 116 | // Implement `PartialEq`, `Eq`, `PartialOrd` and `Ord` so we can sort `Node`s 117 | impl PartialEq for Node { 118 | fn eq(&self, other: &Node) -> bool { 119 | self.key == other.key 120 | } 121 | } 122 | 123 | impl Eq for Node {} 124 | 125 | impl PartialOrd for Node { 126 | fn partial_cmp(&self, other: &Node) -> Option { 127 | Some(self.cmp(other)) 128 | } 129 | } 130 | 131 | impl Ord for Node { 132 | fn cmp(&self, other: &Node) -> Ordering { 133 | self.key.cmp(&other.key) 134 | } 135 | } 136 | 137 | #[derive(Clone, PartialEq, Debug)] 138 | pub struct HashRing { 139 | hash_builder: S, 140 | ring: Vec>, 141 | } 142 | 143 | impl Default for HashRing { 144 | fn default() -> Self { 145 | HashRing { 146 | hash_builder: DefaultHashBuilder, 147 | ring: Vec::new(), 148 | } 149 | } 150 | } 151 | 152 | /// Hash Ring 153 | /// 154 | /// A hash ring that provides consistent hashing for nodes that are added to it. 155 | impl HashRing { 156 | /// Create a new `HashRing`. 157 | pub fn new() -> HashRing { 158 | Default::default() 159 | } 160 | } 161 | 162 | impl HashRing { 163 | /// Creates an empty `HashRing` which will use the given hash builder. 164 | pub fn with_hasher(hash_builder: S) -> HashRing { 165 | HashRing { 166 | hash_builder, 167 | ring: Vec::new(), 168 | } 169 | } 170 | 171 | /// Get the number of nodes in the hash ring. 172 | pub fn len(&self) -> usize { 173 | self.ring.len() 174 | } 175 | 176 | /// Returns true if the ring has no elements. 177 | pub fn is_empty(&self) -> bool { 178 | self.ring.len() == 0 179 | } 180 | } 181 | 182 | impl HashRing { 183 | /// Add `node` to the hash ring. 184 | pub fn add(&mut self, node: T) { 185 | let key = get_key(&self.hash_builder, &node); 186 | self.ring.push(Node::new(key, node)); 187 | self.ring.sort(); 188 | } 189 | 190 | pub fn batch_add(&mut self, nodes: Vec) { 191 | for node in nodes { 192 | let key = get_key(&self.hash_builder, &node); 193 | self.ring.push(Node::new(key, node)); 194 | } 195 | self.ring.sort() 196 | } 197 | 198 | /// Remove `node` from the hash ring. Returns an `Option` that will contain the `node` 199 | /// if it was in the hash ring or `None` if it was not present. 200 | pub fn remove(&mut self, node: &T) -> Option { 201 | let key = get_key(&self.hash_builder, node); 202 | match self.ring.binary_search_by(|node| node.key.cmp(&key)) { 203 | Err(_) => None, 204 | Ok(n) => Some(self.ring.remove(n).node), 205 | } 206 | } 207 | 208 | /// Get the node responsible for `key`. Returns an `Option` that will contain the `node` 209 | /// if the hash ring is not empty or `None` if it was empty. 210 | pub fn get(&self, key: &U) -> Option<&T> { 211 | if self.ring.is_empty() { 212 | return None; 213 | } 214 | 215 | let k = get_key(&self.hash_builder, key); 216 | let n = match self.ring.binary_search_by(|node| node.key.cmp(&k)) { 217 | Err(n) => n, 218 | Ok(n) => n, 219 | }; 220 | 221 | if n == self.ring.len() { 222 | return Some(&self.ring[0].node); 223 | } 224 | 225 | Some(&self.ring[n].node) 226 | } 227 | 228 | /// Get the node responsible for `key` along with the next `replica` nodes after. 229 | /// Returns None when the ring is empty. If `replicas` is larger than the length 230 | /// of the ring, this function will shrink to just contain the entire ring. 231 | pub fn get_with_replicas(&self, key: &U, replicas: usize) -> Option> 232 | where 233 | T: Clone + Debug, 234 | { 235 | if self.ring.is_empty() { 236 | return None; 237 | } 238 | 239 | let replicas = if replicas > self.ring.len() { 240 | self.ring.len() 241 | } else { 242 | replicas + 1 243 | }; 244 | 245 | let k = get_key(&self.hash_builder, key); 246 | let n = match self.ring.binary_search_by(|node| node.key.cmp(&k)) { 247 | Err(n) => n, 248 | Ok(n) => n, 249 | }; 250 | 251 | let mut nodes = self.ring.clone(); 252 | nodes.rotate_left(n); 253 | 254 | let replica_nodes = nodes 255 | .iter() 256 | .cycle() 257 | .take(replicas) 258 | .map(|node| node.node.clone()) 259 | .collect(); 260 | 261 | Some(replica_nodes) 262 | } 263 | } 264 | 265 | pub struct HashRingIterator { 266 | ring: std::vec::IntoIter>, 267 | } 268 | 269 | impl Iterator for HashRingIterator { 270 | type Item = T; 271 | 272 | fn next(&mut self) -> Option { 273 | self.ring.next().map(|node| node.node) 274 | } 275 | } 276 | 277 | impl IntoIterator for HashRing { 278 | type Item = T; 279 | 280 | type IntoIter = HashRingIterator; 281 | 282 | fn into_iter(self) -> Self::IntoIter { 283 | HashRingIterator { 284 | ring: self.ring.into_iter(), 285 | } 286 | } 287 | } 288 | 289 | // An internal function for converting a reference to a hashable type into a `u64` which 290 | // can be used as a key in the hash ring. 291 | fn get_key(hash_builder: &S, input: T) -> u64 292 | where 293 | S: BuildHasher, 294 | T: Hash, 295 | { 296 | hash_builder.hash_one(input) 297 | } 298 | 299 | #[cfg(test)] 300 | mod tests { 301 | use std::hash::Hash; 302 | use std::hash::Hasher; 303 | use std::net::{Ipv4Addr, SocketAddrV4}; 304 | use std::str::FromStr; 305 | 306 | use super::HashRing; 307 | 308 | #[derive(Debug, Copy, Clone, PartialEq)] 309 | struct VNode { 310 | id: usize, 311 | addr: SocketAddrV4, 312 | } 313 | 314 | impl VNode { 315 | fn new(ip: &str, port: u16, id: usize) -> Self { 316 | let addr = SocketAddrV4::new(Ipv4Addr::from_str(ip).unwrap(), port); 317 | VNode { id, addr } 318 | } 319 | } 320 | 321 | impl Hash for VNode { 322 | fn hash(&self, s: &mut H) { 323 | (self.id, self.addr.port(), self.addr.ip()).hash(s) 324 | } 325 | } 326 | 327 | #[test] 328 | fn add_and_remove_nodes() { 329 | let mut ring: HashRing = HashRing::new(); 330 | 331 | assert_eq!(ring.len(), 0); 332 | assert!(ring.is_empty()); 333 | 334 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 335 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 336 | let vnode3 = VNode::new("127.0.0.2", 1024, 1); 337 | 338 | ring.add(vnode1); 339 | ring.add(vnode2); 340 | ring.add(vnode3); 341 | assert_eq!(ring.len(), 3); 342 | assert!(!ring.is_empty()); 343 | 344 | assert_eq!(ring.remove(&vnode2).unwrap(), vnode2); 345 | assert_eq!(ring.len(), 2); 346 | 347 | let vnode4 = VNode::new("127.0.0.2", 1024, 2); 348 | let vnode5 = VNode::new("127.0.0.2", 1024, 3); 349 | let vnode6 = VNode::new("127.0.0.3", 1024, 1); 350 | 351 | ring.batch_add(vec![vnode4, vnode5, vnode6]); 352 | 353 | assert_eq!(ring.remove(&vnode1).unwrap(), vnode1); 354 | assert_eq!(ring.remove(&vnode3).unwrap(), vnode3); 355 | assert_eq!(ring.remove(&vnode6).unwrap(), vnode6); 356 | assert_eq!(ring.len(), 2); 357 | } 358 | 359 | #[test] 360 | fn get_nodes() { 361 | let mut ring: HashRing = HashRing::new(); 362 | 363 | assert_eq!(ring.get(&"foo"), None); 364 | 365 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 366 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 367 | let vnode3 = VNode::new("127.0.0.2", 1024, 1); 368 | let vnode4 = VNode::new("127.0.0.2", 1024, 2); 369 | let vnode5 = VNode::new("127.0.0.2", 1024, 3); 370 | let vnode6 = VNode::new("127.0.0.3", 1024, 1); 371 | 372 | ring.add(vnode1); 373 | ring.add(vnode2); 374 | ring.add(vnode3); 375 | ring.add(vnode4); 376 | ring.add(vnode5); 377 | ring.add(vnode6); 378 | 379 | assert_eq!(ring.get(&"foo"), Some(&vnode6)); 380 | assert_eq!(ring.get(&"bar"), Some(&vnode5)); 381 | assert_eq!(ring.get(&"baz"), Some(&vnode4)); 382 | 383 | assert_eq!(ring.get(&"abc"), Some(&vnode1)); 384 | assert_eq!(ring.get(&"def"), Some(&vnode1)); 385 | assert_eq!(ring.get(&"ghi"), Some(&vnode6)); 386 | 387 | assert_eq!(ring.get(&"cat"), Some(&vnode5)); 388 | assert_eq!(ring.get(&"dog"), Some(&vnode4)); 389 | assert_eq!(ring.get(&"bird"), Some(&vnode4)); 390 | 391 | // at least each node as a key 392 | let mut nodes = vec![0; 6]; 393 | for x in 0..50_000 { 394 | let node = ring.get(&x).unwrap(); 395 | if vnode1 == *node { 396 | nodes[0] += 1; 397 | } 398 | if vnode2 == *node { 399 | nodes[1] += 1; 400 | } 401 | if vnode3 == *node { 402 | nodes[2] += 1; 403 | } 404 | if vnode4 == *node { 405 | nodes[3] += 1; 406 | } 407 | if vnode5 == *node { 408 | nodes[4] += 1; 409 | } 410 | if vnode6 == *node { 411 | nodes[5] += 1; 412 | } 413 | } 414 | println!("{:?}", nodes); 415 | assert!(nodes.iter().all(|x| *x != 0)); 416 | } 417 | 418 | #[test] 419 | fn get_nodes_with_replicas() { 420 | let mut ring: HashRing = HashRing::new(); 421 | 422 | assert_eq!(ring.get(&"foo"), None); 423 | assert_eq!(ring.get_with_replicas(&"foo", 1), None); 424 | 425 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 426 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 427 | let vnode3 = VNode::new("127.0.0.2", 1024, 3); 428 | let vnode4 = VNode::new("127.0.0.2", 1024, 4); 429 | let vnode5 = VNode::new("127.0.0.2", 1024, 5); 430 | let vnode6 = VNode::new("127.0.0.3", 1024, 6); 431 | 432 | ring.add(vnode1); 433 | ring.add(vnode2); 434 | ring.add(vnode3); 435 | ring.add(vnode4); 436 | ring.add(vnode5); 437 | ring.add(vnode6); 438 | 439 | assert_eq!( 440 | ring.get_with_replicas(&"bar", 2).unwrap(), 441 | vec![vnode3, vnode1, vnode2] 442 | ); 443 | 444 | assert_eq!( 445 | ring.get_with_replicas(&"foo", 4).unwrap(), 446 | vec![vnode5, vnode4, vnode3, vnode1, vnode2] 447 | ); 448 | } 449 | 450 | #[test] 451 | fn get_with_replicas_returns_too_many_replicas() { 452 | let mut ring: HashRing = HashRing::new(); 453 | 454 | assert_eq!(ring.get(&"foo"), None); 455 | assert_eq!(ring.get_with_replicas(&"foo", 1), None); 456 | 457 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 458 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 459 | let vnode3 = VNode::new("127.0.0.2", 1024, 3); 460 | let vnode4 = VNode::new("127.0.0.2", 1024, 4); 461 | let vnode5 = VNode::new("127.0.0.2", 1024, 5); 462 | let vnode6 = VNode::new("127.0.0.3", 1024, 6); 463 | 464 | ring.add(vnode1); 465 | ring.add(vnode2); 466 | ring.add(vnode3); 467 | ring.add(vnode4); 468 | ring.add(vnode5); 469 | ring.add(vnode6); 470 | 471 | assert_eq!( 472 | ring.get_with_replicas(&"bar", 20).unwrap(), 473 | vec![vnode3, vnode1, vnode2, vnode6, vnode5, vnode4], 474 | "too high of replicas causes the count to shrink to ring length" 475 | ); 476 | } 477 | 478 | #[test] 479 | fn into_iter() { 480 | let mut ring: HashRing = HashRing::new(); 481 | 482 | assert_eq!(ring.get(&"foo"), None); 483 | 484 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 485 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 486 | let vnode3 = VNode::new("127.0.0.2", 1024, 1); 487 | 488 | ring.add(vnode1); 489 | ring.add(vnode2); 490 | ring.add(vnode3); 491 | 492 | let mut iter = ring.into_iter(); 493 | 494 | assert_eq!(Some(vnode3), iter.next()); 495 | assert_eq!(Some(vnode1), iter.next()); 496 | assert_eq!(Some(vnode2), iter.next()); 497 | assert_eq!(None, iter.next()); 498 | } 499 | 500 | #[test] 501 | fn hash_ring_eq() { 502 | let mut ring: HashRing = HashRing::new(); 503 | let mut other = ring.clone(); 504 | assert_eq!(ring, other); 505 | assert_eq!(ring.len(), 0); 506 | 507 | let vnode1 = VNode::new("127.0.0.1", 1024, 1); 508 | let vnode2 = VNode::new("127.0.0.1", 1024, 2); 509 | let vnode3 = VNode::new("127.0.0.2", 1024, 1); 510 | other.add(vnode1); 511 | other.add(vnode2); 512 | other.add(vnode3); 513 | assert_ne!(ring, other); 514 | assert_eq!(other.len(), 3); 515 | 516 | other.remove(&vnode1).unwrap(); 517 | other.remove(&vnode2).unwrap(); 518 | other.remove(&vnode3).unwrap(); 519 | assert_eq!(ring, other); 520 | assert_eq!(other.len(), 0); 521 | 522 | ring.add(vnode1); 523 | ring.add(vnode2); 524 | other.add(vnode2); 525 | other.add(vnode3); 526 | other.remove(&vnode3); 527 | ring.remove(&vnode1); 528 | assert_eq!(ring, other); 529 | assert_eq!(ring.len(), 1); 530 | assert_eq!(other.len(), 1); 531 | } 532 | } 533 | --------------------------------------------------------------------------------