Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion clandestined.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'clandestined'
s.version = '1.0.0'
s.version = '1.0.1'
s.licenses = ['MIT']
s.date = Time.now.strftime('%Y-%m-%d')
s.summary = 'rendezvous hashing implementation based on murmur3 hash'
Expand All @@ -13,6 +13,8 @@ Gem::Specification.new do |s|
s.require_paths = ['lib', 'ext']
s.extensions = ['ext/murmur3_native/extconf.rb']

%w{digest-siphash}.each { |gem| s.add_dependency gem }

if RUBY_VERSION < "1.9"
s.add_development_dependency 'rake', '0.8.7'
s.add_development_dependency 'rake-compiler', '0.8.3'
Expand Down
18 changes: 16 additions & 2 deletions lib/clandestined/rendezvous_hash.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require 'murmur3'
require 'string'
require 'digest/siphash'

module Clandestined
class RendezvousHash
Expand All @@ -9,11 +11,23 @@ class RendezvousHash
attr_reader :seed
attr_reader :hash_function

def initialize(nodes=nil, seed=0)
def initialize(nodes=nil, seed=0, hash_type=:murmur)
@nodes = nodes || []
@seed = seed

@hash_function = lambda { |key| murmur3_32(key, seed) }
case hash_type
when :murmur
@hash_function = lambda { |key| murmur3_32(key, seed) }
when :siphash
if seed == 0
# siphash requires 128bit char
seed = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
end

@hash_function = lambda { |key|
Digest::SipHash.digest(key, seed).reverse.hexencode.to_i(16)
}
end
end

def add_node(node)
Expand Down
9 changes: 9 additions & 0 deletions lib/string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class String
def hexencode
self.unpack('H*').first
end

def hexdecode
[self].pack('H*')
end
end
81 changes: 80 additions & 1 deletion test/test_rendezvous_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
include Clandestined


class RendezvousHashTestCase < Test::Unit::TestCase
class RendezvousHashMurMurTestCase < Test::Unit::TestCase

def test_init_no_options
rendezvous = RendezvousHash.new()
Expand Down Expand Up @@ -86,6 +86,85 @@ def test_find_node_after_addition

end

class RendezvousHashSipHashTestCase < Test::Unit::TestCase

def test_init_no_options
rendezvous = RendezvousHash.new(nil, 0, :siphash)
assert_equal(0, rendezvous.nodes.length)
assert_equal(12268983219697955183, rendezvous.hash_function.call('6666'))
end

def test_init
nodes = ['0', '1', '2']
rendezvous = RendezvousHash.new(nodes, 0, :siphash)
assert_equal(3, rendezvous.nodes.length)
assert_equal(12268983219697955183, rendezvous.hash_function.call('6666'))
end

def test_seed
rendezvous = RendezvousHash.new(nil, "8ed18c1098ec29a2", :siphash)
assert_equal("8ed18c1098ec29a2", rendezvous.seed)
assert_equal(9634808532907877200, rendezvous.hash_function.call('6666'))
end

def test_add_node
rendezvous = RendezvousHash.new(nil, 0, :siphash)
rendezvous.add_node('1')
assert_equal(1, rendezvous.nodes.length)
rendezvous.add_node('1')
assert_equal(1, rendezvous.nodes.length)
rendezvous.add_node('2')
assert_equal(2, rendezvous.nodes.length)
rendezvous.add_node('1')
assert_equal(2, rendezvous.nodes.length)
end

def test_remove_node
nodes = ['0', '1', '2']
rendezvous = RendezvousHash.new(nodes, 0, :siphash)
rendezvous.remove_node('2')
assert_equal(2, rendezvous.nodes.length)
assert_raises(ArgumentError) { rendezvous.remove_node(2, rendezvous.nodes.length) }
assert_equal(2, rendezvous.nodes.length)
rendezvous.remove_node('1')
assert_equal(1, rendezvous.nodes.length)
rendezvous.remove_node('0')
assert_equal(0, rendezvous.nodes.length)
end

def test_find_node
nodes = ['0', '1', '2']
rendezvous = RendezvousHash.new(nodes, 0, :siphash)
assert_equal('1', rendezvous.find_node('ok'))
assert_equal('0', rendezvous.find_node('mykey'))
assert_equal('0', rendezvous.find_node('wat'))
end

def test_find_node_after_removal
nodes = ['0', '1', '2']
rendezvous = RendezvousHash.new(nodes, 0, :siphash)
rendezvous.remove_node('1')
assert_equal('2', rendezvous.find_node('ok'))
assert_equal('0', rendezvous.find_node('mykey'))
assert_equal('0', rendezvous.find_node('wat'))
end

def test_find_node_after_addition
nodes = ['0', '1', '2']
rendezvous = RendezvousHash.new(nodes, 0, :siphash)
assert_equal('1', rendezvous.find_node('ok'))
assert_equal('0', rendezvous.find_node('mykey'))
assert_equal('0', rendezvous.find_node('wat'))
assert_equal('2', rendezvous.find_node('lol'))
rendezvous.add_node('3')
assert_equal('1', rendezvous.find_node('ok'))
assert_equal('3', rendezvous.find_node('mykey'))
assert_equal('3', rendezvous.find_node('wat'))
assert_equal('2', rendezvous.find_node('lol'))
end

end

class RendezvousHashIntegrationTestCase < Test::Unit::TestCase

def test_grow
Expand Down