pokan 0.1.0rc1
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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +37 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +60 -0
- data/lib/pokan.rb +19 -0
- data/lib/pokan/collective_peer_operations.rb +88 -0
- data/lib/pokan/connection.rb +27 -0
- data/lib/pokan/entity.rb +165 -0
- data/lib/pokan/event_handler.rb +113 -0
- data/lib/pokan/network.rb +43 -0
- data/lib/pokan/peer.rb +136 -0
- data/lib/pokan/query.rb +39 -0
- data/lib/pokan/request_handler.rb +90 -0
- data/lib/pokan/server.rb +230 -0
- data/lib/pokan/server_messages.rb +27 -0
- data/lib/pokan/version.rb +3 -0
- data/pokan.gemspec +53 -0
- data/spec/pokan/connection_spec.rb +10 -0
- data/spec/pokan/entity_spec.rb +147 -0
- data/spec/pokan/event_handler_spec.rb +84 -0
- data/spec/pokan/network_spec.rb +32 -0
- data/spec/pokan/peer_spec.rb +94 -0
- data/spec/pokan/query_spec.rb +67 -0
- data/spec/pokan/request_handler_spec.rb +253 -0
- data/spec/pokan/server_messages_spec.rb +81 -0
- data/spec/pokan/server_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -0
- metadata +196 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# lib/pokan/pokan_messages.rb
|
2
|
+
|
3
|
+
module Pokan
|
4
|
+
module ServerMessages
|
5
|
+
# Gets a message for the introduction of the new peer to the seed
|
6
|
+
def hello_message
|
7
|
+
{ action: 'hello', origin:"#{address}:#{port}" }.to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Gets the message that the peer needs to send when is going to stop participating of the group
|
12
|
+
def goodbye_message
|
13
|
+
{ action: 'goodbye', origin:"#{address}:#{port}" }.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Gets a digest containing all peers' key/timestamp of all peers
|
18
|
+
# It returns a json with all the pairs for each peer in a Digest Structure
|
19
|
+
def digest_message
|
20
|
+
data= Hash.new
|
21
|
+
peers = Query.new(Peer).where(role:['peer','seed'], status:'alive')
|
22
|
+
peers.each { |peer| data[peer.id] = peer.digest }
|
23
|
+
|
24
|
+
{ action: 'digest', data: data, origin:"#{address}:#{port}" }.to_json
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/pokan.gemspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "pokan"
|
8
|
+
s.version = "0.1.0rc1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Renato Mascarenhas", "Rafael Regis do Prado", "Fabio Lima Pereira"]
|
12
|
+
s.date = "2011-11-10"
|
13
|
+
s.description = "pokan is an implementation of the Gossip protocol \n (best described in http://www.cs.cornell.edu/home/rvr/papers/flowgossip.pdf),\n which aims to have a simple, event based API capable of being used in large,\n not previously known networks."
|
14
|
+
s.email = "haze-gem@googlegroups.com"
|
15
|
+
s.files = [
|
16
|
+
"pokan.gemspec"
|
17
|
+
]
|
18
|
+
s.homepage = "http://github.com/haze/pokan"
|
19
|
+
s.licenses = ["MIT"]
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.rubygems_version = "1.8.10"
|
22
|
+
s.summary = "Gossip-protocol implementation with an event based API"
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
29
|
+
s.add_runtime_dependency(%q<redis>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
31
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
32
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
34
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
35
|
+
else
|
36
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
37
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
38
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
39
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
40
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
41
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
42
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
43
|
+
end
|
44
|
+
else
|
45
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
46
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
47
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
48
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
49
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
50
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
51
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Connection do
|
4
|
+
it "should instance only one Redis object" do
|
5
|
+
@client1 = Pokan::Connection.redis
|
6
|
+
@client2 = Pokan::Connection.redis
|
7
|
+
@client1.should == @client2
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Entity do
|
4
|
+
context 'when comparing key-values' do
|
5
|
+
before :all do
|
6
|
+
Pokan::Connection.redis.flushall
|
7
|
+
@entity = Pokan::Entity.new
|
8
|
+
@entity.id = '1'
|
9
|
+
@entity.store(:k1, "v1")
|
10
|
+
@entity.store(:k2, 5.0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should return true if the internal key have the same value' do
|
14
|
+
@entity.match?(k1: 'v1').should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return false if the internal key doesnt have the same value' do
|
18
|
+
@entity.match?(k1: 'v2').should_not be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return true if the internal key is one of the array' do
|
22
|
+
@entity.match?(k1: ['v1', 'v2']).should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return false if the internal key isnt one of the array' do
|
26
|
+
@entity.match?(k1: ['v3', 'v2']).should_not be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return true if the value for a given key is in the range' do
|
30
|
+
@entity.match?(k2: { min: 3.0, max: 10.0 }).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return true if the value for a given key is in the range' do
|
34
|
+
@entity.match?(k2: { min:6.0, max: 10.0 }).should_not be_true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when storing a value' do
|
39
|
+
before :all do
|
40
|
+
Pokan::Connection.redis.flushall
|
41
|
+
@entity = Pokan::Entity.new
|
42
|
+
@entity.id = '1'
|
43
|
+
@entity.store(:test, 'OK')
|
44
|
+
@entity.save
|
45
|
+
@entity.reload
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should store a new value for a key' do
|
49
|
+
@entity.value(:test).should == 'OK'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should store a timestamp for a key' do
|
53
|
+
@entity.timestamp(:test).should < Time.now
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should update the timestamp of an updated key" do
|
57
|
+
new = @entity.timestamp(:status)
|
58
|
+
@entity.store(:local, 'somewhere')
|
59
|
+
@entity.save
|
60
|
+
@entity.reload
|
61
|
+
newer = @entity.timestamp(:local)
|
62
|
+
newer.to_f.should > new.to_f
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when retrieving a value' do
|
67
|
+
before :all do
|
68
|
+
Pokan::Connection.redis.flushall
|
69
|
+
@entity = Pokan::Peer.new
|
70
|
+
@entity.id = '1'
|
71
|
+
@entity.store(:test, 'OK')
|
72
|
+
@entity.save
|
73
|
+
@entity.reload
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should get the value for a given key' do
|
77
|
+
@entity.value(:test).should == 'OK'
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return nil if a value doesn\'t exist' do
|
81
|
+
@entity.value(:other_test).should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should return zero to timestamp if a value doesn\'t exist' do
|
85
|
+
@entity.timestamp(:other_test).should == Time.at(0)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when merging new data to a entity' do
|
90
|
+
before :each do
|
91
|
+
Pokan::Connection.redis.flushall
|
92
|
+
@entity = Pokan::Entity.new
|
93
|
+
@entity.id = '1'
|
94
|
+
@entity.store(:local, 'GREAT')
|
95
|
+
@entity.save
|
96
|
+
@entity.reload
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should merge with a new set preserving newer values' do
|
100
|
+
@entity.merge({
|
101
|
+
local: { value: 'GREATER', timestamp: Time.now.to_f + 2000 }
|
102
|
+
})
|
103
|
+
@entity.value(:local).should == 'GREATER'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should merge with a new set discarding older values' do
|
107
|
+
@entity.merge({
|
108
|
+
local: {value: 'NOT GREAT', timestamp: 50}
|
109
|
+
})
|
110
|
+
@entity.value(:local).should == 'GREAT'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'when getting data for gossip' do
|
115
|
+
before :all do
|
116
|
+
Pokan::Connection.redis.flushall
|
117
|
+
@entity = Pokan::Entity.new
|
118
|
+
@entity.id = '1'
|
119
|
+
@entity.store(:foo, 'bar')
|
120
|
+
@entity.store(:local, 'OK')
|
121
|
+
@entity.save
|
122
|
+
@entity.reload
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should return all key/timestamps of a peer' do
|
126
|
+
@entity.digest.keys.should =~ [:local, :foo]
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should return all older keys of a peer' do
|
130
|
+
digest = {local: 0, foo: (Time.now.to_f + 1000).to_s}
|
131
|
+
@entity.older(digest).should == [:foo]
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should return all newer key/value/timestamps of an peer' do
|
135
|
+
digest = {local: 0, foo: (Time.now.to_f + 1000).to_s}
|
136
|
+
@entity.newer(digest)[:local][:value].should_not == nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should return all the peer\'s keys' do
|
140
|
+
@entity.keys.should =~ [:foo, :local]
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should return the keys, values and timestamps of all required keys' do
|
144
|
+
@entity.values([:local])[:local][:value].should == 'OK'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::EventHandler do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@event_handler = Pokan::EventHandler.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should register a new event' do
|
10
|
+
register_callback(:dummy)
|
11
|
+
|
12
|
+
@event_handler.events.should include :dummy
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should raise ArgumentError if event name is not a symbol' do
|
16
|
+
expect { @event_handler.register 'dummy' }.to raise_exception ArgumentError
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should not allow a call to `register` without a block' do
|
20
|
+
expect { @event_handler.register :dummy }.to raise_exception Pokan::NoBlockGivenError
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should list no subscribers for a not registered event' do
|
24
|
+
@event_handler.subscribers(:dummy).should be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should list all subscribers for an event' do
|
28
|
+
3.times { register_callback(:dummy) }
|
29
|
+
|
30
|
+
@event_handler.subscribers(:dummy).size.should == 3
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should do nothing when `emit` is called for an unexisting event' do
|
34
|
+
@event_handler.emit(:dummy).should == @event_handler
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should call registered callbacks after `emit`' do
|
38
|
+
@block_called = false
|
39
|
+
register_callback(:dummy)
|
40
|
+
|
41
|
+
@event_handler.emit(:dummy)
|
42
|
+
|
43
|
+
@block_called.should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should call registered callbacks twice' do
|
47
|
+
@block_called = false
|
48
|
+
register_callback(:dummy)
|
49
|
+
|
50
|
+
@event_handler.emit(:dummy)
|
51
|
+
@block_called.should be_true
|
52
|
+
|
53
|
+
@block_called = false
|
54
|
+
@event_handler.emit(:dummy)
|
55
|
+
|
56
|
+
@block_called.should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should pass correct parameters when `emit` is called using `with` option' do
|
60
|
+
@parameter = false
|
61
|
+
|
62
|
+
@event_handler.register :dummy do |message|
|
63
|
+
@parameter = message
|
64
|
+
end
|
65
|
+
|
66
|
+
@event_handler.emit :dummy, :with => [true]
|
67
|
+
@parameter.should be_true
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should reset registered callbacks for an event' do
|
71
|
+
3.times { register_callback(:dummy) }
|
72
|
+
|
73
|
+
@event_handler.reset(:dummy)
|
74
|
+
|
75
|
+
@event_handler.subscribers(:dummy).should be_empty
|
76
|
+
end
|
77
|
+
|
78
|
+
def register_callback(event)
|
79
|
+
@event_handler.register event do
|
80
|
+
@block_called = true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Network do
|
4
|
+
|
5
|
+
context 'when sending via UDP' do
|
6
|
+
|
7
|
+
it 'should send the message' do
|
8
|
+
Pokan::Network.udp('dummy', '127.0.0.1', 7777).should be_true
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when sending a message via TCP without a server to accept it' do
|
14
|
+
|
15
|
+
it 'should not send the message' do
|
16
|
+
Pokan::Network.tcp('dummy', '127.0.0.1', 7777).should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when sending a message via TCP with a server accepting' do
|
22
|
+
|
23
|
+
before { @server = TCPServer.open(7777) }
|
24
|
+
after { @server.close }
|
25
|
+
|
26
|
+
it 'should send the message' do
|
27
|
+
Pokan::Network.tcp('dummy', '127.0.0.1', 7777).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Peer do
|
4
|
+
context 'when checking the status of the peer' do
|
5
|
+
before :all do
|
6
|
+
Pokan::Connection.redis.flushall
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should check if a peer is dead' do
|
10
|
+
peer = Pokan::Peer.new
|
11
|
+
peer.address = peer.udp_port = '3'
|
12
|
+
peer.kill
|
13
|
+
peer.save
|
14
|
+
peer.reload
|
15
|
+
peer.dead?.should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should check if a peer is not dead' do
|
19
|
+
peer = Pokan::Peer.new
|
20
|
+
peer.address = peer.udp_port = '3'
|
21
|
+
peer.save
|
22
|
+
peer.reload
|
23
|
+
peer.dead?.should be_false
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should check if a peer is not alive' do
|
27
|
+
peer = Pokan::Peer.new
|
28
|
+
peer.address = peer.udp_port = '3'
|
29
|
+
peer.kill
|
30
|
+
peer.save
|
31
|
+
peer.alive?.should be_false
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should check if peer is alive' do
|
35
|
+
peer = Pokan::Peer.new
|
36
|
+
peer.address = peer.udp_port = '3'
|
37
|
+
peer.save
|
38
|
+
peer.alive?.should be_true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when changing the status of a peer' do
|
43
|
+
before :all do
|
44
|
+
Pokan::Connection.redis.flushall
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should not set a new status if the timestamp is not greater' do
|
48
|
+
peer = Pokan::Peer.new
|
49
|
+
peer.address = peer.udp_port = '3'
|
50
|
+
peer.store(:status, 'dead', Time.at(0))
|
51
|
+
peer.save
|
52
|
+
peer.reload
|
53
|
+
peer.dead?.should be_false
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should set a new status if the timestamp is greater' do
|
57
|
+
peer = Pokan::Peer.new
|
58
|
+
peer.address = peer.udp_port = '3'
|
59
|
+
peer.store('status', 'dead', Time.now + 1000)
|
60
|
+
peer.save
|
61
|
+
peer.reload
|
62
|
+
peer.dead?.should be_true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when setting up a peer' do
|
67
|
+
before :all do
|
68
|
+
Pokan::Connection.redis.flushall
|
69
|
+
@peer = Pokan::Peer.new
|
70
|
+
@peer.address = @peer.udp_port = '3'
|
71
|
+
@peer.save
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should start as a normal peer' do
|
75
|
+
@peer.seed?.should be_false
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should start act as a seed' do
|
79
|
+
@peer.act_as_seed
|
80
|
+
@peer.save
|
81
|
+
@peer.seed?.should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should kill a peer' do
|
85
|
+
@peer.kill
|
86
|
+
@peer.dead?.should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should revive a peer' do
|
90
|
+
@peer.revive
|
91
|
+
@peer.dead?.should be_false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|