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,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
|