dht 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.coveralls.yml +0 -0
- data/.gitignore +10 -0
- data/.travis.yml +16 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +90 -0
- data/MIT-LICENSE +21 -0
- data/README.md +16 -0
- data/Rakefile +5 -0
- data/bin/dht_cell.rb +46 -0
- data/dht.gemspec +21 -0
- data/lib/dht/hash.rb +40 -0
- data/lib/dht/key.rb +66 -0
- data/lib/dht/manager.rb +43 -0
- data/lib/dht/node.rb +47 -0
- data/lib/dht/service.rb +37 -0
- data/lib/dht/storage.rb +30 -0
- data/lib/dht/version.rb +5 -0
- data/spec/dht/hash_spec.rb +73 -0
- data/spec/dht/key_spec.rb +30 -0
- data/spec/dht/manager_spec.rb +70 -0
- data/spec/dht/node_spec.rb +68 -0
- data/spec/dht/service_spec.rb +30 -0
- data/spec/dht/storage_spec.rb +28 -0
- data/spec/integration_spec.rb +41 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/celluloid_method.rb +15 -0
- data/spec/support/test_node.rb +33 -0
- metadata +92 -0
data/.coveralls.yml
ADDED
File without changes
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
before_install: sudo apt-get install libzmq3-dev
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- ruby-head
|
7
|
+
- jruby-19mode
|
8
|
+
- jruby-head
|
9
|
+
- rbx-19mode
|
10
|
+
|
11
|
+
matrix:
|
12
|
+
allow_failures:
|
13
|
+
- rvm: ruby-head
|
14
|
+
- rvm: jruby-head
|
15
|
+
- rvm: jruby-19mode
|
16
|
+
- rvm: rbx-19mode
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dht (0.0.1)
|
5
|
+
dcell
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
bbq-spawn (0.0.3)
|
11
|
+
childprocess
|
12
|
+
bogus (0.1.2)
|
13
|
+
dependor (>= 0.0.4)
|
14
|
+
celluloid (0.14.1)
|
15
|
+
timers (>= 1.0.0)
|
16
|
+
celluloid-io (0.14.1)
|
17
|
+
celluloid (>= 0.14.1)
|
18
|
+
nio4r (>= 0.4.5)
|
19
|
+
celluloid-zmq (0.14.0)
|
20
|
+
celluloid (>= 0.13.0)
|
21
|
+
ffi
|
22
|
+
ffi-rzmq
|
23
|
+
certified (0.1.1)
|
24
|
+
childprocess (0.3.9)
|
25
|
+
ffi (~> 1.0, >= 1.0.11)
|
26
|
+
colorize (0.5.8)
|
27
|
+
coveralls (0.6.7)
|
28
|
+
colorize
|
29
|
+
multi_json (~> 1.3)
|
30
|
+
rest-client
|
31
|
+
simplecov (>= 0.7)
|
32
|
+
thor
|
33
|
+
dcell (0.14.0)
|
34
|
+
celluloid (>= 0.13.0)
|
35
|
+
celluloid-zmq (>= 0.13.0)
|
36
|
+
redis
|
37
|
+
redis-namespace
|
38
|
+
reel
|
39
|
+
dependor (0.0.6)
|
40
|
+
diff-lcs (1.2.4)
|
41
|
+
ffi (1.9.0)
|
42
|
+
ffi-rzmq (1.0.1)
|
43
|
+
ffi
|
44
|
+
http (0.4.0)
|
45
|
+
certified
|
46
|
+
http_parser.rb
|
47
|
+
http_parser.rb (0.5.3)
|
48
|
+
mime-types (1.23)
|
49
|
+
multi_json (1.7.7)
|
50
|
+
nio4r (0.4.6)
|
51
|
+
rack (1.5.2)
|
52
|
+
rake (10.1.0)
|
53
|
+
redis (3.0.4)
|
54
|
+
redis-namespace (1.3.0)
|
55
|
+
redis (~> 3.0.0)
|
56
|
+
reel (0.3.0)
|
57
|
+
celluloid-io (>= 0.8.0)
|
58
|
+
http (>= 0.2.0)
|
59
|
+
http_parser.rb (>= 0.5.3)
|
60
|
+
rack (>= 1.4.0)
|
61
|
+
websocket_parser (>= 0.1.0)
|
62
|
+
rest-client (1.6.7)
|
63
|
+
mime-types (>= 1.16)
|
64
|
+
rspec (2.14.1)
|
65
|
+
rspec-core (~> 2.14.0)
|
66
|
+
rspec-expectations (~> 2.14.0)
|
67
|
+
rspec-mocks (~> 2.14.0)
|
68
|
+
rspec-core (2.14.2)
|
69
|
+
rspec-expectations (2.14.0)
|
70
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
71
|
+
rspec-mocks (2.14.1)
|
72
|
+
simplecov (0.7.1)
|
73
|
+
multi_json (~> 1.0)
|
74
|
+
simplecov-html (~> 0.7.1)
|
75
|
+
simplecov-html (0.7.1)
|
76
|
+
thor (0.18.1)
|
77
|
+
timers (1.1.0)
|
78
|
+
websocket_parser (0.1.4)
|
79
|
+
http
|
80
|
+
|
81
|
+
PLATFORMS
|
82
|
+
ruby
|
83
|
+
|
84
|
+
DEPENDENCIES
|
85
|
+
bbq-spawn
|
86
|
+
bogus
|
87
|
+
coveralls
|
88
|
+
dht!
|
89
|
+
rake
|
90
|
+
rspec
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006-2010 Steve Sloan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= DHT
|
2
|
+
|
3
|
+
[](http://travis-ci.org/LTe/dht)
|
4
|
+
[](https://gemnasium.com/LTe/dht)
|
5
|
+
[](https://codeclimate.com/github/LTe/dht)
|
6
|
+
[](https://coveralls.io/r/LTe/dht?branch=master)
|
7
|
+
[](http://badge.fury.io/rb/dht)
|
8
|
+
|
9
|
+
A Ruby implementation of a Kademlia-style Distributed Hash Table.
|
10
|
+
|
11
|
+
== Work-in-progress
|
12
|
+
|
13
|
+
Author:: Steve Sloan (mailto:steve@finagle.org)
|
14
|
+
Website:: http://github.com/CodeMonkeySteve/dht
|
15
|
+
Copyright:: Copyright (c) 2009 Steve Sloan
|
16
|
+
License:: MIT
|
data/Rakefile
ADDED
data/bin/dht_cell.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__)+'/..' << File.dirname(__FILE__)+'/../lib'
|
3
|
+
|
4
|
+
require 'irb'
|
5
|
+
require 'dcell'
|
6
|
+
require 'dht/node'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: dht_dcell.rb [options]"
|
13
|
+
|
14
|
+
opts.on("-n", "--name [NAME]", String, "Select node name") do |name|
|
15
|
+
options[:name] = name
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-p", "--port [PORT]", String, "Select node port") do |port|
|
19
|
+
options[:port] = port.to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-h", "--host [HOST]", String, "Select node host") do |host|
|
23
|
+
options[:host] = host
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-n", "--node [NODE]", String, "One of nodes: name:host:port") do |node|
|
27
|
+
if node
|
28
|
+
node_attributes = node.split(":")
|
29
|
+
options[:node] = { :id => node_attributes[0], :addr => "tcp://#{node_attributes[1]}:#{node_attributes[2]}" }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-e", "--explorer") do |explorer|
|
34
|
+
options[:explorer] = explorer
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.parse!
|
39
|
+
|
40
|
+
node = DHT::Node.new(options)
|
41
|
+
node.start
|
42
|
+
|
43
|
+
ARGV.clear
|
44
|
+
IRB.start
|
45
|
+
|
46
|
+
|
data/dht.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/dht/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Piotr Niełacny"]
|
6
|
+
gem.email = ["piotr.nielacny@gmail.com"]
|
7
|
+
gem.description = %q{Ruby DHT hash}
|
8
|
+
gem.summary = %q{Implementation of the Distributed Hash Table (DHT) in Ruby}
|
9
|
+
gem.homepage = "https://github.com/LTe/dht"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "dht"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = DHT::Hash::VERSION
|
17
|
+
gem.required_ruby_version = '>= 1.9.3'
|
18
|
+
|
19
|
+
gem.add_dependency "dcell"
|
20
|
+
end
|
21
|
+
|
data/lib/dht/hash.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'dht/node'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
class Hash
|
5
|
+
attr_reader :node
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@node = DHT::Node.new(options)
|
9
|
+
@node.start
|
10
|
+
end
|
11
|
+
|
12
|
+
def me
|
13
|
+
DCell.me
|
14
|
+
end
|
15
|
+
|
16
|
+
def storage
|
17
|
+
me[:storage]
|
18
|
+
end
|
19
|
+
|
20
|
+
def manager
|
21
|
+
me[:manager]
|
22
|
+
end
|
23
|
+
|
24
|
+
def store(key, value)
|
25
|
+
manager.storage_for(key).store(key, value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(key)
|
29
|
+
manager.storage_for(key).get(key)
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(key, value)
|
33
|
+
store(key, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
get(key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/dht/key.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
class Key
|
5
|
+
Digest = ::Digest::SHA1
|
6
|
+
Size = Digest.new.digest_length
|
7
|
+
|
8
|
+
def self.for_content(content)
|
9
|
+
Key.new Digest.digest(content.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(key)
|
13
|
+
@key =
|
14
|
+
if Key === key
|
15
|
+
key.to_binary
|
16
|
+
elsif Integer === key
|
17
|
+
@key_i = key
|
18
|
+
k = @key_i.to_s(16)
|
19
|
+
[('0' * ((Size * 2) - k.size)) + k].pack('H*')
|
20
|
+
else
|
21
|
+
key = key.to_s
|
22
|
+
case key.size
|
23
|
+
when Size then key
|
24
|
+
when Size * 2 then [key].pack('H*')
|
25
|
+
else raise "Invalid key: #{key}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(that)
|
31
|
+
self.eql? that
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_binary
|
35
|
+
@key
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
to_binary.unpack('H*').first
|
40
|
+
end
|
41
|
+
|
42
|
+
def hash
|
43
|
+
@key.hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def eql?(that)
|
47
|
+
if Key === that
|
48
|
+
@key == that.to_binary
|
49
|
+
else
|
50
|
+
@key.to_s == that.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_i
|
59
|
+
@key_i ||= eval( '0x' + self.to_s )
|
60
|
+
end
|
61
|
+
|
62
|
+
def distance_to(that)
|
63
|
+
self.to_i ^ that.to_i
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/dht/manager.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
class Manager
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
def find_nodes
|
8
|
+
dcell_nodes.inject({}) { |hash, node| hash[node[:storage].key] = node; hash }
|
9
|
+
end
|
10
|
+
|
11
|
+
def storage_for(key)
|
12
|
+
closest_node = node_for(key)
|
13
|
+
|
14
|
+
if closest_node == me
|
15
|
+
me[:storage]
|
16
|
+
else
|
17
|
+
closest_node[:manager].storage_for(key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def node_for(key)
|
22
|
+
key = Key.for_content(key.to_s)
|
23
|
+
node_key = nodes.keys.min { |a,b| a.distance_to(key) <=> b.distance_to(key) }
|
24
|
+
nodes[node_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def key
|
28
|
+
Key.for_content(me.addr)
|
29
|
+
end
|
30
|
+
|
31
|
+
def nodes
|
32
|
+
@nodes = find_nodes
|
33
|
+
end
|
34
|
+
|
35
|
+
def dcell_nodes
|
36
|
+
DCell::Node.all
|
37
|
+
end
|
38
|
+
|
39
|
+
def me
|
40
|
+
DCell.me
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/dht/node.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'celluloid'
|
3
|
+
require 'dcell/explorer'
|
4
|
+
require 'dht/key'
|
5
|
+
require 'dht/storage'
|
6
|
+
require 'dht/manager'
|
7
|
+
require 'dht/service'
|
8
|
+
|
9
|
+
module DHT
|
10
|
+
class Node
|
11
|
+
def initialize(options = {})
|
12
|
+
options = default_options.merge(options)
|
13
|
+
|
14
|
+
@host = options.delete(:host)
|
15
|
+
@port = options.delete(:port)
|
16
|
+
@name = options.delete(:name)
|
17
|
+
@node = options.delete(:node)
|
18
|
+
@explorer = options.delete(:explorer)
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
DCell.start(configuration)
|
23
|
+
DHT::Service.new(:key => key, :explorer => @explorer).run
|
24
|
+
end
|
25
|
+
|
26
|
+
def configuration
|
27
|
+
configuration = {:id => @name, :addr => "tcp://#{@host}:#{@port}"}
|
28
|
+
configuration.merge!(:directory => @node) if @node
|
29
|
+
|
30
|
+
configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
def key
|
34
|
+
@key ||= Key.for_content("#{@name}:#{@host}:#{@port}")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def default_options
|
40
|
+
{
|
41
|
+
:name => "default",
|
42
|
+
:port => 3000,
|
43
|
+
:host => "127.0.0.1"
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/dht/service.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module DHT
|
2
|
+
class Service
|
3
|
+
def initialize(options = {})
|
4
|
+
@explorer = options.delete(:explorer)
|
5
|
+
@key = options.delete(:key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
run_services
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_services
|
13
|
+
create_explorer if enable_explorer?
|
14
|
+
create_storage
|
15
|
+
create_manager
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_explorer
|
19
|
+
DCell::Explorer.new("127.0.0.1", 8000)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_storage
|
23
|
+
DHT::Storage.supervise_as :storage, @key
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_manager
|
27
|
+
DHT::Manager.supervise_as :manager
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def enable_explorer?
|
33
|
+
@explorer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/lib/dht/storage.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
class Storage
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
attr_reader :key, :database
|
8
|
+
|
9
|
+
def initialize(key)
|
10
|
+
@key = key
|
11
|
+
@database = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def store(key, value)
|
15
|
+
@database[key] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(key)
|
19
|
+
@database[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
get(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
store(key, value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/dht/version.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
describe Hash do
|
5
|
+
fake(:manager)
|
6
|
+
fake(:storage)
|
7
|
+
fake(:storage2) { Storage }
|
8
|
+
fake(:dcell_node) { DCell::Node }
|
9
|
+
|
10
|
+
before do
|
11
|
+
fake_class(DHT::Node, :new => dcell_node)
|
12
|
+
stub(dcell_node).[](:storage) { storage }
|
13
|
+
stub(dcell_node).[](:manager) { manager }
|
14
|
+
stub(manager).storage_for(:key) { storage }
|
15
|
+
stub(subject).me { dcell_node }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#me" do
|
19
|
+
it "returns current node" do
|
20
|
+
subject.me.should == dcell_node
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#storage" do
|
25
|
+
it "returns storage of node" do
|
26
|
+
subject.storage.should == storage
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#manager" do
|
31
|
+
it "it returns manager of node" do
|
32
|
+
subject.manager.should == manager
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#[]=" do
|
37
|
+
it "store value in storage" do
|
38
|
+
mock(storage).store(:key, :value) { :value }
|
39
|
+
subject.store(:key, :value).should == :value
|
40
|
+
end
|
41
|
+
|
42
|
+
it "store key in another node" do
|
43
|
+
mock(manager).storage_for(:key) { storage2 }
|
44
|
+
mock(storage2).store(:key, :value) { :value }
|
45
|
+
|
46
|
+
subject.store(:key, :value).should == :value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#[]" do
|
51
|
+
it "gets key from current node" do
|
52
|
+
mock(storage).get(:key) { :value }
|
53
|
+
mock(manager).storage_for(:key) { storage }
|
54
|
+
|
55
|
+
subject[:key].should == :value
|
56
|
+
end
|
57
|
+
|
58
|
+
it "cannot find key on closets node" do
|
59
|
+
mock(storage).get(:key) { nil }
|
60
|
+
mock(manager).storage_for(:key) { storage }
|
61
|
+
|
62
|
+
subject[:key].should == nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "get key from other node" do
|
66
|
+
mock(manager).storage_for(:key) { storage2 }
|
67
|
+
mock(storage2).get(:key) { :value }
|
68
|
+
|
69
|
+
subject[:key].should == :value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'dht/key'
|
5
|
+
|
6
|
+
module DHT
|
7
|
+
describe Key do
|
8
|
+
before do
|
9
|
+
@key = Key.new "\v\xEE\xC7\xB5\xEA?\x0F\xDB\xC9]\r\xD4\x7F<[\xC2u\xDA\x000".force_encoding('binary')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'initializes from a hex String' do
|
13
|
+
Key.new('0beec7b5ea3f0fdbc95d0dd47f3c5bc275da0030').should == @key
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'converts to an Integer' do
|
17
|
+
@key.to_i.should == 68123873083688143418383284816464454849230667824
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'computes distance to another key' do
|
21
|
+
k = @key.to_i
|
22
|
+
|
23
|
+
@key.distance_to( k ).should == 0
|
24
|
+
for bit in 0...(Key::Size*8)
|
25
|
+
mask = 1 << bit
|
26
|
+
@key.distance_to( k ^ mask ).should == mask
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
describe Manager do
|
5
|
+
let(:key) { Key.for_content("key") }
|
6
|
+
let(:node) { fake(:addr => "127.0.0.1") }
|
7
|
+
let(:node2) { fake }
|
8
|
+
|
9
|
+
fake(:storage, :key => Key.for_content("node"))
|
10
|
+
fake(:storage2, :key => Key.for_content("node2")) { Storage }
|
11
|
+
fake(:manager)
|
12
|
+
fake(:manager2) { Manager }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub(storage).key { key }
|
16
|
+
stub(node).[](:storage) { storage }
|
17
|
+
stub(node).[](:manager) { manager }
|
18
|
+
stub(node2).[](:storage) { storage2 }
|
19
|
+
stub(node2).[](:manager) { manager2 }
|
20
|
+
stub(subject).me { node }
|
21
|
+
stub(subject).dcell_nodes { [node] }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#nodes" do
|
25
|
+
it "returns DCell nodes" do
|
26
|
+
subject.nodes.should == { key => node }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#find_nodes" do
|
31
|
+
it "find nodes will fill @nodes array" do
|
32
|
+
subject.nodes.should == subject.find_nodes
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#key" do
|
37
|
+
it "returns key for addr of node" do
|
38
|
+
subject.key.should be_kind_of(Key)
|
39
|
+
subject.key.to_binary.should == Digest::SHA1.digest(node.addr)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#storage_for" do
|
44
|
+
it "returns current node" do
|
45
|
+
mock(subject).node_for(:key) { node }
|
46
|
+
|
47
|
+
subject.storage_for(:key).should == storage
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns another node another node" do
|
51
|
+
mock(subject).node_for(:key) { node2 }
|
52
|
+
mock(manager2).storage_for(:key) { storage2 }
|
53
|
+
|
54
|
+
subject.storage_for(:key).should == storage2
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#node_for" do
|
59
|
+
before { mock(subject).dcell_nodes { [node, node2] } }
|
60
|
+
|
61
|
+
it "find closets node for :key" do
|
62
|
+
subject.node_for(:key).should == node
|
63
|
+
end
|
64
|
+
|
65
|
+
it "find closets node for :key_2" do
|
66
|
+
subject.node_for(:key22).should == node2
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
describe Node do
|
5
|
+
let(:name) { "default" }
|
6
|
+
let(:address) { "127.0.0.1" }
|
7
|
+
let(:port) { 3000 }
|
8
|
+
let(:explorer) { false }
|
9
|
+
let(:options) { {:name => name, :host => address, :port => port, :explorer => explorer} }
|
10
|
+
|
11
|
+
subject { Node.new(options) }
|
12
|
+
|
13
|
+
describe "#configuration" do
|
14
|
+
it "returns configuration for DCell" do
|
15
|
+
node = Node.new
|
16
|
+
node.configuration[:id].should == name
|
17
|
+
node.configuration[:addr].should == "tcp://#{address}:#{port}"
|
18
|
+
node.configuration[:directory].should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
context "custom name" do
|
22
|
+
let(:name) { "custom_name" }
|
23
|
+
|
24
|
+
it "returns custom node name for DCell" do
|
25
|
+
subject.configuration[:id].should == name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "custom address" do
|
30
|
+
let(:address) { "google.com" }
|
31
|
+
|
32
|
+
it "returns custom address for DCell" do
|
33
|
+
subject.configuration[:addr].should == "tcp://#{address}:#{port}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "custom port" do
|
38
|
+
let(:port) { 9000 }
|
39
|
+
|
40
|
+
it "returns custom port for DCell" do
|
41
|
+
subject.configuration[:addr].should == "tcp://#{address}:#{port}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#key" do
|
47
|
+
its(:key) { should == Key.new(Digest::SHA1.digest("#{name}:#{address}:#{port}")) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#start" do
|
51
|
+
fake_class(DCell)
|
52
|
+
fake(:service)
|
53
|
+
|
54
|
+
before do
|
55
|
+
stub(service).run { true }
|
56
|
+
stub(DHT::Service).new(:key => subject.key, :explorer => explorer) { service }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "start DCell node" do
|
60
|
+
subject.start
|
61
|
+
end
|
62
|
+
|
63
|
+
it "start DHT::Service" do
|
64
|
+
service.run.should == true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
describe Service do
|
5
|
+
let(:key) { Key.for_content("example") }
|
6
|
+
let(:explorer) { true }
|
7
|
+
|
8
|
+
subject { Service.new(:explorer => explorer, :key => key) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
stub(subject).create_storage
|
12
|
+
stub(subject).create_manager
|
13
|
+
stub(subject).create_explorer
|
14
|
+
end
|
15
|
+
|
16
|
+
it "enables services" do
|
17
|
+
subject.run
|
18
|
+
subject.should have_received.create_explorer
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "disabled explorer" do
|
22
|
+
let(:explorer) { false }
|
23
|
+
|
24
|
+
it "does not enable explorer" do
|
25
|
+
subject.run
|
26
|
+
subject.should_not have_received.create_explorer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DHT
|
4
|
+
describe Storage do
|
5
|
+
fake(:key)
|
6
|
+
subject { Storage.new(key) }
|
7
|
+
|
8
|
+
it "saves key and value to database" do
|
9
|
+
subject.store(:key, "value")
|
10
|
+
|
11
|
+
subject.database.keys.should include(:key)
|
12
|
+
subject.database.values.should include("value")
|
13
|
+
subject.database[:key].should == "value"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "saves to database with #[]= method" do
|
17
|
+
subject[:key] = "value"
|
18
|
+
|
19
|
+
subject.database[:key].should == "value"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "reads from database with #[] method" do
|
23
|
+
subject[:key] = "value"
|
24
|
+
|
25
|
+
subject[:key].should == "value"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "integration specs" do
|
4
|
+
before(:all) do
|
5
|
+
Redis.new.flushall
|
6
|
+
@test_node = TestNode.new
|
7
|
+
@test_node.spawn
|
8
|
+
@hash = DHT::Hash.new
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
@test_node.stop
|
13
|
+
end
|
14
|
+
|
15
|
+
it "recognize another node" do
|
16
|
+
@hash.manager.nodes.count.should == 2
|
17
|
+
end
|
18
|
+
|
19
|
+
it "saves key and value in current node" do
|
20
|
+
@hash[:key_100] = :value_100
|
21
|
+
|
22
|
+
@hash.storage.database.should include(:key_100 => :value_100)
|
23
|
+
@hash[:key_100].should == :value_100
|
24
|
+
end
|
25
|
+
|
26
|
+
it "saves key and value in another node" do
|
27
|
+
@hash[:key_101] = :value_101
|
28
|
+
|
29
|
+
@hash.storage.database.should_not include(:key_101 => :value_101)
|
30
|
+
@hash[:key_101].should == :value_101
|
31
|
+
end
|
32
|
+
|
33
|
+
it "replaces value in network" do
|
34
|
+
@hash[:key_101] = :value_101
|
35
|
+
@hash[:key_101] = :changed
|
36
|
+
|
37
|
+
@hash[:key_101].should == :changed
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__) + '/..'
|
2
|
+
|
3
|
+
require 'coveralls'
|
4
|
+
Coveralls.wear!
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'bogus/rspec'
|
8
|
+
require 'bbq/spawn'
|
9
|
+
require 'dht/hash'
|
10
|
+
|
11
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
12
|
+
|
13
|
+
Bogus.configure do |c|
|
14
|
+
c.search_modules << DHT
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Celluloid
|
2
|
+
class Method
|
3
|
+
def name
|
4
|
+
@proxy.method_missing(:method, @name).name
|
5
|
+
end
|
6
|
+
|
7
|
+
def parameters
|
8
|
+
@proxy.method_missing(:method, @name).parameters
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_proc
|
12
|
+
@proxy.method_missing(:method, @name).to_proc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bbq/spawn'
|
2
|
+
|
3
|
+
class TestNode
|
4
|
+
include Bbq::Spawn
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@port = options.delete(:port) || "3002"
|
8
|
+
end
|
9
|
+
|
10
|
+
def spawn
|
11
|
+
orchestrator.start
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
orchestrator.stop
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def executable
|
21
|
+
File.expand_path("../../../bin/dht_cell.rb", __FILE__)
|
22
|
+
end
|
23
|
+
|
24
|
+
def executor
|
25
|
+
Executor.new("bundle", "exec", "ruby", executable, "--port", @port, "--name", "node")
|
26
|
+
end
|
27
|
+
|
28
|
+
def orchestrator
|
29
|
+
@orchestrator ||= Orchestrator.new.tap do |orchestrator|
|
30
|
+
orchestrator.coordinate(executor, :host => "127.0.0.1", :port => @port)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dht
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Piotr Niełacny
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dcell
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Ruby DHT hash
|
31
|
+
email:
|
32
|
+
- piotr.nielacny@gmail.com
|
33
|
+
executables:
|
34
|
+
- dht_cell.rb
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .coveralls.yml
|
39
|
+
- .gitignore
|
40
|
+
- .travis.yml
|
41
|
+
- Gemfile
|
42
|
+
- Gemfile.lock
|
43
|
+
- MIT-LICENSE
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- bin/dht_cell.rb
|
47
|
+
- dht.gemspec
|
48
|
+
- lib/dht/hash.rb
|
49
|
+
- lib/dht/key.rb
|
50
|
+
- lib/dht/manager.rb
|
51
|
+
- lib/dht/node.rb
|
52
|
+
- lib/dht/service.rb
|
53
|
+
- lib/dht/storage.rb
|
54
|
+
- lib/dht/version.rb
|
55
|
+
- spec/dht/hash_spec.rb
|
56
|
+
- spec/dht/key_spec.rb
|
57
|
+
- spec/dht/manager_spec.rb
|
58
|
+
- spec/dht/node_spec.rb
|
59
|
+
- spec/dht/service_spec.rb
|
60
|
+
- spec/dht/storage_spec.rb
|
61
|
+
- spec/integration_spec.rb
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
- spec/support/celluloid_method.rb
|
64
|
+
- spec/support/test_node.rb
|
65
|
+
homepage: https://github.com/LTe/dht
|
66
|
+
licenses: []
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.9.3
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
hash: 4193095747413724244
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.25
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Implementation of the Distributed Hash Table (DHT) in Ruby
|
92
|
+
test_files: []
|