pokan 0.1.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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,67 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Query do
|
4
|
+
before :all do
|
5
|
+
Pokan::Connection.redis.flushall
|
6
|
+
@query = Pokan::Query.new(Pokan::Peer)
|
7
|
+
p1 = Pokan::Peer.new
|
8
|
+
p1.address = p1.udp_port = '1'
|
9
|
+
p1.act_as_seed
|
10
|
+
p1.kill
|
11
|
+
p1.save
|
12
|
+
p2 = Pokan::Peer.new
|
13
|
+
p2.address = p2.udp_port = '2'
|
14
|
+
p2.act_as_seed
|
15
|
+
p2.save
|
16
|
+
p3 = Pokan::Peer.new
|
17
|
+
p3.address = p3.udp_port = '3'
|
18
|
+
p3.save
|
19
|
+
p4 = Pokan::Peer.new
|
20
|
+
p4.address = p4.udp_port = '4'
|
21
|
+
p4.kill
|
22
|
+
p4.save
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when getting all peers' do
|
26
|
+
it 'should return all alive peers' do
|
27
|
+
peers = @query.where(role:'peer', status:'alive').collect { |p| p.id }
|
28
|
+
peers.should =~ ['3:3']
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should return all alive seeds' do
|
32
|
+
peers = @query.where(role:'seed', status:'alive').collect { |p| p.id }
|
33
|
+
peers.should == ['2:2']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when search a specific peer' do
|
38
|
+
it'should return a empty array if it does not exist' do
|
39
|
+
peers = @query.where(id:'5:5').collect! { |p| p.id }
|
40
|
+
peers.should == []
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should return the peer, if it exists' do
|
44
|
+
peers = @query.where(id:'4:4').collect! { |p| p.id }
|
45
|
+
peers.should == ['4:4']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when choosing a peer' do
|
50
|
+
it 'should return a random seed' do
|
51
|
+
seeds = @query.where(role:'seed').collect! { |p| p.id }
|
52
|
+
id = @query.where(random:true, role:'seed')[0].id
|
53
|
+
seeds.include?(id).should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should return a random peer' do
|
57
|
+
peers = @query.where(role:'peer').collect! { |p| p.id }
|
58
|
+
id = @query.where(random:true, role:'peer')[0].id
|
59
|
+
peers.include?(id).should be_true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should not return the peer with the specific id' do
|
63
|
+
peers = @query.where(:not => ['1:1', '2:2', '3:3', '4:4'])
|
64
|
+
peers.include?('1:1').should be_false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe Pokan::RequestHandler do
|
5
|
+
before :all do
|
6
|
+
Pokan::RequestHandler.address = 'ip'
|
7
|
+
Pokan::RequestHandler.port = '1111'
|
8
|
+
@req_handler = Pokan::RequestHandler.new nil
|
9
|
+
|
10
|
+
class Pokan::RequestHandler
|
11
|
+
def send_datagram a, b, c
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
# @req_handler.stub!(:send_datagram).and_return(0)
|
16
|
+
end
|
17
|
+
|
18
|
+
before :each do
|
19
|
+
Pokan::Connection.redis.flushall
|
20
|
+
@req_handler.merge({
|
21
|
+
p1: {
|
22
|
+
status: { value: 'alive', timestamp: 100 },
|
23
|
+
role: { value: 'peer', timestamp: 100 },
|
24
|
+
answer: { value: 'FALSE', timestamp: 100 },
|
25
|
+
foo: { value: 'baz', timestamp: 101 }
|
26
|
+
},
|
27
|
+
p2: {
|
28
|
+
status: { value: 'alive', timestamp: 100 },
|
29
|
+
role: { value: 'peer', timestamp: 100 },
|
30
|
+
answer: { value: '+/-', timestamp: 102 },
|
31
|
+
foo: { value: 'baz', timestamp: 103 }
|
32
|
+
}
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when merging data' do
|
37
|
+
it 'should merge with a new set preserving newer values' do
|
38
|
+
@req_handler.merge({
|
39
|
+
p2: { answer: { value: 'GREAT', timestamp: 200 },
|
40
|
+
foo: {value: 'null', timestamp: 50 }
|
41
|
+
}
|
42
|
+
})
|
43
|
+
query = Pokan::Query.new(Pokan::Peer)
|
44
|
+
peer2 = query.where(id:'p2')[0]
|
45
|
+
peer2.value(:answer).should == 'GREAT'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should not merge with a older set preserving newer values' do
|
49
|
+
@req_handler.merge({
|
50
|
+
p2: {
|
51
|
+
answer: {value: 'GREAT', timestamp: 200},
|
52
|
+
foo: {value: 'null', timestamp: 50}
|
53
|
+
}
|
54
|
+
})
|
55
|
+
query = Pokan::Query.new(Pokan::Peer)
|
56
|
+
peer2 = query.where(id:'p2')[0]
|
57
|
+
peer2.value(:foo).should == 'baz'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should not merge a key of a dead peer' do
|
61
|
+
# need a better test for this
|
62
|
+
query = Pokan::Query.new(Pokan::Peer)
|
63
|
+
peer = query.where(id:'p2')[0]
|
64
|
+
peer.kill
|
65
|
+
@req_handler.merge({
|
66
|
+
p2: { answer: { value: 'GREAT', timestamp: 200 },
|
67
|
+
foo: { value: 'null', timestamp: 50 }
|
68
|
+
}
|
69
|
+
})
|
70
|
+
peer.dead?.should be_true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when checking the given keys' do
|
75
|
+
it 'should return all older keys of all peers' do
|
76
|
+
keys = { p1: { answer: 100, foo: 300 } }
|
77
|
+
older = @req_handler.older(keys)
|
78
|
+
older.should == { p1: [:foo] }
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return all newer key/value/timestamps of all peers' do
|
82
|
+
keys = { p1: { answer: 109, foo: 91 } }
|
83
|
+
newer = @req_handler.newer(keys)
|
84
|
+
newer.should == { p1: { foo: { value: 'baz', timestamp: 101 } } }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when returning its newer data' do
|
89
|
+
it 'should retrieve all requested keys/values/timestamps' do
|
90
|
+
query = Pokan::Query.new(Pokan::Peer)
|
91
|
+
query.where(id:'p1')[0].kill.save
|
92
|
+
|
93
|
+
request = { p1: [:status], p2: [:foo] }
|
94
|
+
data = @req_handler.retrieve(request)
|
95
|
+
data.should == {
|
96
|
+
p2: { foo: { value: 'baz', timestamp: 103 } }
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
context 'when generating a pull message' do
|
103
|
+
before :each do
|
104
|
+
query = Pokan::Query.new(Pokan::Peer)
|
105
|
+
@req_handler.merge({
|
106
|
+
p10: {
|
107
|
+
answer: { value: '+/-', timestamp: 102 },
|
108
|
+
foo: { value: 'baz', timestamp: 103 }
|
109
|
+
}
|
110
|
+
})
|
111
|
+
query.where(id:'p10')[0].store(:role, 'seed', 99317585671)
|
112
|
+
query.where(id:'p1')[0].store(:status, 'dead', 99317585671)
|
113
|
+
query.where(id:'p2')[0].store(:role, 'peer', 99317585671)
|
114
|
+
digest = {
|
115
|
+
action: 'digest',
|
116
|
+
origin: 'ip:1111',
|
117
|
+
data: { keys: { p1: { answer: 12.0, foo: 123.0 } } }
|
118
|
+
}.to_json
|
119
|
+
pull_json = @req_handler.receive_data(digest)
|
120
|
+
@pull = JSON.parse(pull_json)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should set action as pull' do
|
124
|
+
@pull['action'].should == 'pull'
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should set the newer keys from all peers with values and timestamps' do
|
128
|
+
@pull['data']['newer'] == {
|
129
|
+
'p1' => { 'answer' => { 'value' => 'FALSE', 'timestamp' => 100.0 } },
|
130
|
+
'p10' => { 'answer' => { 'value' => '+/-', 'timestamp' => 102.0 },
|
131
|
+
'foo' => { 'value' => 'baz', 'timestamp' => 103.0 } }
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should set the older keys from all peers in arrays' do
|
136
|
+
@pull['data']['older'] == {'older' => {'p1' => ['foo']}}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'when generating a push message' do
|
141
|
+
before :each do
|
142
|
+
@req_handler.merge({
|
143
|
+
p10: {
|
144
|
+
status: { value: 'alive', timestamp: 101 },
|
145
|
+
role: { value: 'seed', timestamp: 101 },
|
146
|
+
answer: { value: '+/-', timestamp: 102 },
|
147
|
+
foo: { value: 'baz', timestamp: 103 }
|
148
|
+
},
|
149
|
+
p1: {
|
150
|
+
status: { value: 'alive', timestamp: 101 },
|
151
|
+
role: { value: 'peer', timestamp: 101 }
|
152
|
+
},
|
153
|
+
p2: {
|
154
|
+
status: { value: 'dead', timestamp: 101 },
|
155
|
+
role: { value: 'peer', timestamp: 101 }
|
156
|
+
}
|
157
|
+
})
|
158
|
+
pull_message = {
|
159
|
+
action: 'pull', origin: 'ip:1111',
|
160
|
+
data: {
|
161
|
+
newer: { p1: { status: { value: 'FALSE', timestamp: 1000.0 } },
|
162
|
+
p10: { answer: { value: '+/-', timestamp: 1020.0 } } },
|
163
|
+
older: { p1: [:foo], p10: [:foo]}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
push_message = @req_handler.receive_data(JSON.generate(pull_message))
|
167
|
+
@push = JSON.parse(push_message)
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should set action as push' do
|
171
|
+
@push['action'].should == 'push'
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should set the requested keys from all peers with values and timestamps' do
|
175
|
+
@push['data'].should == {
|
176
|
+
'p1' => {'foo' => {'value' => 'baz', 'timestamp' => 101.0}},
|
177
|
+
'p10' => {'foo' => {'value' => 'baz', 'timestamp' => 103.0}}
|
178
|
+
}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'when interpreting messages' do
|
183
|
+
context 'when receiving a pull message' do
|
184
|
+
before :each do
|
185
|
+
pull_message = {
|
186
|
+
action: 'pull', origin: 'ip:1111',
|
187
|
+
data: {
|
188
|
+
newer: {
|
189
|
+
p1: { status: { value: 'dead', timestamp: 100000.0 },
|
190
|
+
answer: { value: 'ZOMG!!', timestamp: 1000.0 } },
|
191
|
+
p10: { answer: { value: 'Hi!!', timestamp: 1000.0 } }
|
192
|
+
},
|
193
|
+
older: { p1: ['foo'] }
|
194
|
+
}
|
195
|
+
}
|
196
|
+
@req_handler.receive_data(pull_message.to_json)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should kill the peer if the timestamp is greater than the stored' do
|
200
|
+
Pokan::Query.new(Pokan::Peer).where(id: 'p1')[0].value(:status).should == 'dead'
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'should create the peer locally if it does not exist' do
|
204
|
+
Pokan::Query.new(Pokan::Peer).where(id: 'p10')[0].value(:role).should == 'peer'
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should update the keys with the newers in message' do
|
208
|
+
Pokan::Query.new(Pokan::Peer).where(id: 'p1')[0].value(:answer).should == 'ZOMG!!'
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should update the timestamp of keys with the newers in message' do
|
212
|
+
Pokan::Query.new(Pokan::Peer).where(id: 'p1')[0].timestamp(:answer).to_f.should == 1000.0
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'when receiving a push message' do
|
217
|
+
before :each do
|
218
|
+
push_message = {
|
219
|
+
'action' => 'push',
|
220
|
+
'origin' => 'ip:1111',
|
221
|
+
'data' => {
|
222
|
+
'p1' => {'answer' => {'value' => 'ZOMG!!', 'timestamp' => 1000.0}},
|
223
|
+
'p2' => {'status' => {'value' => 'dead', 'timestamp' => 1000.0}},
|
224
|
+
'p10' => {'role' => {'value' => 'peer', 'timestamp' => 1000.0}}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
@req_handler.receive_data(JSON.generate(push_message))
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should kill the peer if the timestamp is greater than the stored' do
|
231
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id:'p2')[0]
|
232
|
+
peer.value(:status).should == 'dead'
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should create the peer locally if it does not exist' do
|
236
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id:'p10')[0]
|
237
|
+
peer.value(:role).should == 'peer'
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should update the keys with the newers in message' do
|
241
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id:'p1')[0]
|
242
|
+
peer.value(:answer).should == 'ZOMG!!'
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'should update the timestamp of keys with the newers in message' do
|
246
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id:'p1')[0]
|
247
|
+
peer.timestamp(:answer).to_f.should == 1000.0
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::ServerMessages do
|
4
|
+
before :all do
|
5
|
+
class Messenger
|
6
|
+
include Pokan::ServerMessages
|
7
|
+
attr_accessor :address, :port
|
8
|
+
end
|
9
|
+
@messenger = Messenger.new
|
10
|
+
@messenger.port = 'port'
|
11
|
+
@messenger.address = 'ip'
|
12
|
+
Pokan::RequestHandler.address = 'ip'
|
13
|
+
Pokan::RequestHandler.port = 'port'
|
14
|
+
@req_handler = Pokan::RequestHandler.new nil
|
15
|
+
end
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
Pokan::Connection.redis.flushall
|
19
|
+
@req_handler.merge({
|
20
|
+
p1: {
|
21
|
+
status: { value: 'alive', timestamp: 100 },
|
22
|
+
role: { value: 'peer', timestamp: 100 },
|
23
|
+
answer: { value: 'FALSE', timestamp: 100 },
|
24
|
+
foo: { value: 'baz', timestamp: 101 }
|
25
|
+
},
|
26
|
+
p2: {
|
27
|
+
status: { value: 'alive', timestamp: 100 },
|
28
|
+
role: { value: 'peer', timestamp: 100 },
|
29
|
+
answer: { value: '+/-', timestamp: 102 },
|
30
|
+
foo: { value: 'baz', timestamp: 103 }
|
31
|
+
}
|
32
|
+
})
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when generating a digest message' do
|
36
|
+
before :each do
|
37
|
+
@digest = JSON.parse(@messenger.digest_message)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should set action as digest' do
|
41
|
+
@digest['action'].should == 'digest'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should set the timestamps of all keys of all known ids' do
|
45
|
+
@digest['data'].should == {
|
46
|
+
'p1' => { 'answer' => 100, 'foo' => 101, 'status' => 100, 'role' => 100 },
|
47
|
+
'p2' => { 'answer' => 102, 'foo' => 103, 'status' => 100, 'role' => 100 }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when receiving a hello message' do
|
53
|
+
it 'should create the peer and set its role' do
|
54
|
+
hello_message = {
|
55
|
+
'action' => 'hello',
|
56
|
+
'role' => 'peer',
|
57
|
+
'origin' => 'ip:port'
|
58
|
+
}
|
59
|
+
@req_handler.receive_data(hello_message.to_json)
|
60
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id:'ip:port')[0]
|
61
|
+
peer.value(:role).should == 'peer'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when receiving a goodbye message' do
|
66
|
+
it 'should kill the origin peer' do
|
67
|
+
peer = Pokan::Peer.new
|
68
|
+
peer.address = 'ip'
|
69
|
+
peer.udp_port = 'port'
|
70
|
+
peer.save
|
71
|
+
goodbye_message = {
|
72
|
+
'action' => 'goodbye',
|
73
|
+
'timestamp' => 97317585671,
|
74
|
+
'origin' => 'ip:port'
|
75
|
+
}
|
76
|
+
@req_handler.receive_data(goodbye_message.to_json)
|
77
|
+
peer = Pokan::Query.new(Pokan::Peer).where(id: 'ip:port')[0]
|
78
|
+
peer.value(:status).should == 'dead'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pokan::Server do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Pokan::Connection.redis.flushall
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when server is being configured manually' do
|
10
|
+
|
11
|
+
before { @server = Pokan::Server.new('10.10.10.8') }
|
12
|
+
|
13
|
+
it 'should gossip every specified interval' do
|
14
|
+
@server.gossip_every(3)
|
15
|
+
@server.gossip_interval.should == 3
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should use epoll when it is told to' do
|
19
|
+
@server.use_epoll
|
20
|
+
@server.using_epoll?.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when behavior is added to the server' do
|
26
|
+
|
27
|
+
before { @server = Pokan::Server.new('10.10.10.8') }
|
28
|
+
|
29
|
+
it 'should properly call registered callbacks on events' do
|
30
|
+
@block_called = false
|
31
|
+
add_behavior_to_event(:new_key)
|
32
|
+
|
33
|
+
@server.store 'k', 'v'
|
34
|
+
|
35
|
+
@block_called.should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_behavior_to_event(event)
|
39
|
+
@server.on event do |key, value|
|
40
|
+
@block_called = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|