dht 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/LTe/dht.png)](http://travis-ci.org/LTe/dht)
|
4
|
+
[![Dependency Status](https://gemnasium.com/LTe/dht.png)](https://gemnasium.com/LTe/dht)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/LTe/dht.png)](https://codeclimate.com/github/LTe/dht)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/LTe/dht/badge.png?branch=master)](https://coveralls.io/r/LTe/dht?branch=master)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/dht.png)](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: []
|