infopark-politics 0.2.15 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@ require 'socket'
2
2
  require 'ipaddr'
3
3
  require 'uri'
4
4
  require 'drb'
5
+ require 'set'
5
6
 
6
7
  begin
7
8
  require 'net/dns/mdns-sd'
@@ -76,6 +77,7 @@ module Politics
76
77
 
77
78
  @buckets = []
78
79
  @bucket_count = bucket_count
80
+ @followers_to_stop = Set.new
79
81
 
80
82
  register_with_bonjour
81
83
  log.progname = uri
@@ -89,7 +91,9 @@ module Politics
89
91
  def process_bucket(&block)
90
92
  log.debug "start bucket processing"
91
93
  raise ArgumentError, "process_bucket requires a block!" unless block_given?
92
- raise ArgumentError, "You must call register_worker before processing!" unless @memcache_client
94
+ unless @memcache_client
95
+ raise ArgumentError, "You must call register_worker before processing!"
96
+ end
93
97
 
94
98
  begin
95
99
  begin
@@ -144,20 +148,36 @@ module Politics
144
148
  log.debug { "relaxes half the time until next iteration" }
145
149
  relax(until_next_iteration / 2)
146
150
  as_dictator do
147
- update_buckets
151
+ update_buckets unless restart_wanted?
148
152
  end
149
153
  end
150
- handle_leadership_over
154
+ as_dictator() {populate_followers_to_stop} if restart_wanted?
155
+ # keeping leader state as long as there are followers to stop
156
+ while !followers_to_stop.empty? do
157
+ relax(until_next_iteration / 2)
158
+ seize_leadership
159
+ end
160
+ exit 0 if restart_wanted?
151
161
  relax until_next_iteration
152
162
  end
153
163
 
154
- def handle_leadership_over
164
+ def populate_followers_to_stop
165
+ @followers_to_stop.replace(find_workers)
166
+ end
167
+
168
+ def followers_to_stop
169
+ @followers_to_stop.select {|u| DRbObject.new(nil, u).alive? rescue DRb::DRbConnError && false}
155
170
  end
156
171
 
157
172
  def bucket_request(requestor_uri)
158
173
  if leader?
159
174
  log.debug "delivering bucket request"
160
- next_bucket requestor_uri
175
+ bucket_spec = next_bucket(requestor_uri)
176
+ if !bucket_spec[0] && @followers_to_stop.include?(requestor_uri)
177
+ bucket_spec = [:stop, 0]
178
+ @followers_to_stop.delete(requestor_uri)
179
+ end
180
+ bucket_spec
161
181
  else
162
182
  log.debug "received request for bucket but am not leader - delivering :not_leader"
163
183
  [:not_leader, 0]
@@ -191,8 +211,22 @@ module Politics
191
211
  DRbObject.new(nil, leader_uri)
192
212
  end
193
213
 
214
+ def find_workers
215
+ workers = []
216
+ browser = Net::DNS::MDNSSD.browse(mdns_type) do |reply|
217
+ workers << reply.name unless reply.name == uri
218
+ end
219
+ sleep 5
220
+ browser.stop
221
+ workers
222
+ end
223
+
194
224
  private
195
225
 
226
+ def restart_wanted?
227
+ @memcache_client.get(restart_flag)
228
+ end
229
+
196
230
  def bucket_process(bucket, sleep_time)
197
231
  case bucket
198
232
  when nil
@@ -204,6 +238,9 @@ module Politics
204
238
  log.warn { "Recv'd NOT_LEADER from peer." }
205
239
  relax 1
206
240
  @leader_uri = nil
241
+ when :stop
242
+ log.info "Received STOP from leader … exiting."
243
+ exit 0
207
244
  else
208
245
  log.info { "processing #{bucket}" }
209
246
  yield bucket
@@ -230,8 +267,15 @@ module Politics
230
267
  "#{group_name}_token"
231
268
  end
232
269
 
270
+ def restart_flag
271
+ "#{group_name}_restart"
272
+ end
273
+
233
274
  def cleanup
234
- @memcache_client.delete(token) if leader?
275
+ if leader?
276
+ @memcache_client.delete(token)
277
+ @memcache_client.delete(restart_flag)
278
+ end
235
279
  end
236
280
 
237
281
  def pause_until_expiry(elapsed)
@@ -239,7 +283,8 @@ module Politics
239
283
  if pause_time > 0
240
284
  relax(pause_time)
241
285
  else
242
- raise ArgumentError, "Negative iteration time left. Assuming the worst and exiting... #{iteration_length}/#{elapsed}"
286
+ raise ArgumentError, "Negative iteration time left. " +
287
+ "Assuming the worst and exiting… #{iteration_length}/#{elapsed}"
243
288
  end
244
289
  end
245
290
 
@@ -277,38 +322,26 @@ module Politics
277
322
  Time.now - a
278
323
  end
279
324
 
325
+ def mdns_type
326
+ "_#{group_name}._tcp"
327
+ end
328
+
280
329
  def register_with_bonjour
281
330
  server = DRb.start_service(nil, self)
282
331
  @uri = DRb.uri
283
332
  @port = URI.parse(DRb.uri).port
284
333
 
285
334
  # Register our DRb server with Bonjour.
286
- name = "#{group_name}-#{local_ip}-#{$$}"
287
- type = "_#{group_name}._tcp"
335
+ name = @uri
288
336
  domain = "local"
289
- log.debug "register service #{name} of type #{type} within domain #{domain} at port #{@port}"
290
- handle = Net::DNS::MDNSSD.register(name, type, domain, @port) do |reply|
337
+ log.debug "register service #{name} of type #{mdns_type} within domain #{domain} at port #{@port}"
338
+ handle = Net::DNS::MDNSSD.register(name, mdns_type, domain, @port) do |reply|
291
339
  log.debug "registered as #{reply.fullname}"
340
+ if reply.name != name
341
+ log.debug "Registered name #{reply.name} differs from requested name #{name} … exiting."
342
+ handle.stop
343
+ end
292
344
  end
293
-
294
- # ['INT', 'TERM'].each { |signal|
295
- # trap(signal) do
296
- # handle.stop
297
- # server.stop_service
298
- # end
299
- # }
300
- end
301
-
302
- # http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
303
- def local_ip
304
- orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
305
-
306
- UDPSocket.open do |s|
307
- s.connect '64.233.187.99', 1
308
- IPAddr.new(s.addr.last).to_i
309
- end
310
- ensure
311
- Socket.do_not_reverse_lookup = orig
312
345
  end
313
346
  end
314
347
  end
@@ -124,14 +124,22 @@ describe Worker do
124
124
  before do
125
125
  @worker.stub!(:leader?).and_return false
126
126
  @worker.stub!(:leader_uri).and_return "the leader"
127
+ @worker.stub!(:leader).and_return(@leader = mock('leader'))
128
+ @leader.stub!(:bucket_request).and_return([1, 0])
127
129
  end
128
130
 
129
131
  it "should get the bucket to process from the leader at every iteration" do
130
- @worker.should_receive(:leader).exactly(4).times.and_return(leader = mock('leader'))
131
- leader.should_receive(:bucket_request).with(@worker.uri).exactly(4).times.
132
+ @worker.should_receive(:leader).exactly(4).times.and_return @leader
133
+ @leader.should_receive(:bucket_request).with(@worker.uri).exactly(4).times.
132
134
  and_return([1, 2])
133
135
  @worker.start
134
136
  end
137
+
138
+ it "should exit on :stop bucket" do
139
+ @leader.should_receive(:bucket_request).ordered.once.and_return([:stop, 0])
140
+ @worker.should_receive(:exit).with(0).ordered
141
+ @worker.start
142
+ end
135
143
  end
136
144
  end
137
145
  end
@@ -146,6 +154,27 @@ describe Worker do
146
154
  @worker.should_receive(:next_bucket).with("requestor").and_return "the bucket"
147
155
  @worker.bucket_request("requestor").should == "the bucket"
148
156
  end
157
+
158
+ describe "when no buckets are left" do
159
+ before do
160
+ @worker.stub!(:find_workers).and_return(%w(1 2 3))
161
+ @worker.populate_followers_to_stop
162
+ DRbObject.stub(:new).and_return(mock('o', :alive? => true))
163
+ end
164
+
165
+ it "should deliver the :stop bucket if requestor is in followers_to_stop list" do
166
+ @worker.bucket_request("1").should == [:stop, 0]
167
+ end
168
+
169
+ it "should not deliver the :stop bucket if requestor is not in followers_to_stop list" do
170
+ @worker.bucket_request("requestor")[0].should be_nil
171
+ end
172
+
173
+ it "should remove the requestor from the followers_to_stop list" do
174
+ @worker.bucket_request("2")
175
+ @worker.followers_to_stop.should == %w(1 3)
176
+ end
177
+ end
149
178
  end
150
179
 
151
180
  describe "as follower" do
@@ -159,12 +188,27 @@ describe Worker do
159
188
  end
160
189
  end
161
190
 
191
+ describe "when determining if restart is wanted" do
192
+ it "should return true if the restart flag is set in memcache" do
193
+ @@memcache_client.should_receive(:get).with('worker_restart').and_return true
194
+ @worker.should be_restart_wanted
195
+ end
196
+
197
+ it "should return false if the restart flag is not set in memcache" do
198
+ @@memcache_client.should_receive(:get).with('worker_restart').and_return false
199
+ @worker.should_not be_restart_wanted
200
+ @@memcache_client.should_receive(:get).with('worker_restart').and_return nil
201
+ @worker.should_not be_restart_wanted
202
+ end
203
+ end
204
+
162
205
  describe "when performing leader duties" do
163
206
  before do
164
207
  @worker.stub!(:until_next_iteration).and_return 0
165
208
  @worker.stub!(:leader?).and_return true
166
209
  @worker.stub!(:dictatorship_length).and_return 666
167
210
  @worker.stub!(:iteration_length).and_return 5
211
+ @worker.stub!(:find_workers).and_return []
168
212
  end
169
213
 
170
214
  it "should initialize buckets as dictator" do
@@ -195,6 +239,20 @@ describe Worker do
195
239
  @worker.should_receive(:seize_leadership).at_least(4).times
196
240
  @worker.perform_leader_duties
197
241
  end
242
+
243
+ it "should seize the leadership periodically even if restart is wanted" do
244
+ @worker.stub!(:restart_wanted?).and_return true
245
+ @worker.stub!(:exit)
246
+ @worker.should_receive(:seize_leadership).at_least(4).times
247
+ @worker.perform_leader_duties
248
+ end
249
+
250
+ it "should not update buckets if restart is wanted" do
251
+ @worker.stub!(:restart_wanted?).and_return true
252
+ @worker.stub!(:exit)
253
+ @worker.should_not_receive(:update_buckets)
254
+ @worker.perform_leader_duties
255
+ end
198
256
  end
199
257
 
200
258
  describe "if there are no more buckets" do
@@ -202,17 +260,55 @@ describe Worker do
202
260
  @worker.stub!(:buckets).and_return([])
203
261
  end
204
262
 
205
- it "should relax until next iteration" do
206
- @worker.stub!(:until_next_iteration).and_return(6)
207
- @worker.should_receive(:relax).with(6).once
263
+ it "should populate the followers_to_stop list before evaluating it if restart is wanted" do
264
+ @worker.stub!(:restart_wanted?).and_return true
265
+ @worker.stub!(:exit)
266
+ @worker.should_receive(:populate_followers_to_stop).ordered.once
267
+ @worker.should_receive(:followers_to_stop).ordered.and_return []
208
268
  @worker.perform_leader_duties
209
269
  end
210
270
 
211
- it "should call leadership over hook before relaxing" do
212
- @worker.should_receive(:handle_leadership_over).ordered
213
- @worker.should_receive(:relax).ordered
271
+ it "should not populate the followers_to_stop list if restart is not wanted" do
272
+ @worker.stub!(:restart_wanted?).and_return false
273
+ @worker.should_not_receive(:populate_followers_to_stop)
214
274
  @worker.perform_leader_duties
215
275
  end
276
+
277
+ describe "as long as there are followers to stop" do
278
+ before do
279
+ @worker.stub!(:followers_to_stop).and_return([1], [2], [3], [4], [])
280
+ @worker.stub!(:relax)
281
+ end
282
+
283
+ it "should relax half of the time to the next iteration" do
284
+ @worker.stub!(:until_next_iteration).and_return(6)
285
+ @worker.should_receive(:relax).with(3).exactly(4).times
286
+ @worker.perform_leader_duties
287
+ end
288
+
289
+ it "should seize the leadership periodically" do
290
+ @worker.should_receive(:seize_leadership).at_least(4).times
291
+ @worker.perform_leader_duties
292
+ end
293
+ end
294
+
295
+ describe "if there are no more followers to stop" do
296
+ before do
297
+ @worker.stub!(:followers_to_stop).and_return([])
298
+ end
299
+
300
+ it "should relax until next iteration" do
301
+ @worker.stub!(:until_next_iteration).and_return(6)
302
+ @worker.should_receive(:relax).with(6).once
303
+ @worker.perform_leader_duties
304
+ end
305
+
306
+ it "should exit if restart is wanted" do
307
+ @worker.stub!(:restart_wanted?).and_return true
308
+ @worker.should_receive(:exit).with(0)
309
+ @worker.perform_leader_duties
310
+ end
311
+ end
216
312
  end
217
313
  end
218
314
 
@@ -324,16 +420,22 @@ describe Worker do
324
420
 
325
421
  describe "when cleaning up" do
326
422
  before do
327
- @worker.stub!(:token).and_return('dcc-group')
423
+ @worker.stub!(:group_name).and_return('the group')
328
424
  end
329
425
 
330
426
  describe "as leader" do
331
427
  before do
332
428
  @worker.stub!(:leader?).and_return true
429
+ @@memcache_client.stub!(:delete)
430
+ end
431
+
432
+ it "should remove the leadership token from memcache" do
433
+ @@memcache_client.should_receive(:delete).with('the group_token')
434
+ @worker.send(:cleanup)
333
435
  end
334
436
 
335
- it "should remove the leadership token" do
336
- @@memcache_client.should_receive(:delete).with('dcc-group')
437
+ it "should remove the restart wanted flag from memcache" do
438
+ @@memcache_client.should_receive(:delete).with('the group_restart')
337
439
  @worker.send(:cleanup)
338
440
  end
339
441
  end
@@ -343,10 +445,75 @@ describe Worker do
343
445
  @worker.stub!(:leader?).and_return false
344
446
  end
345
447
 
346
- it "should not remove the leadership token" do
448
+ it "should not remove anything from memcache" do
347
449
  @@memcache_client.should_not_receive(:delete)
348
450
  @worker.send(:cleanup)
349
451
  end
350
452
  end
351
453
  end
454
+
455
+ describe "when finding workers" do
456
+ before do
457
+ Net::DNS::MDNSSD.stub(:browse).
458
+ and_yield(mock('response1', :name => 'w1')).
459
+ and_yield(mock('response2', :name => 'w2')).
460
+ and_yield(mock('response3', :name => 'w3')).
461
+ and_yield(mock('response4', :name => 'w4')).
462
+ and_return(@browser = mock('browser', :stop => nil))
463
+ @worker.stub!(:sleep)
464
+ end
465
+
466
+ it "should browse mdns group and return workers found" do
467
+ @worker.find_workers.should == %w(w1 w2 w3 w4)
468
+ end
469
+
470
+ it "should not add itself to the result list" do
471
+ @worker.stub!(:uri).and_return('w3')
472
+ @worker.find_workers.should_not include('w3')
473
+ end
474
+
475
+ it "should stop browser thread after five seconds" do
476
+ @worker.should_receive(:sleep).with(5).ordered
477
+ @browser.should_receive(:stop)
478
+ @worker.find_workers
479
+ end
480
+ end
481
+
482
+ describe "when populating followers_to_stop" do
483
+ before do
484
+ @worker.stub(:find_workers).and_return(%w(a b c))
485
+ DRbObject.stub(:new).and_return(mock('o', :alive? => true))
486
+ end
487
+
488
+ it "should add all visible workers" do
489
+ @worker.populate_followers_to_stop
490
+ @worker.followers_to_stop.should == %w(a b c)
491
+ end
492
+ end
493
+
494
+ describe "when delivering followers_to_stop" do
495
+ before do
496
+ @worker.stub(:find_workers).and_return(%w(a b c))
497
+ @worker.populate_followers_to_stop
498
+ DRbObject.stub(:new).and_return(mock('o', :alive? => true))
499
+ end
500
+
501
+ it "should return the actual followers_to_stop" do
502
+ @worker.followers_to_stop.should == %w(a b c)
503
+ end
504
+
505
+ it "should not deliver entries that are not reachable at the moment" do
506
+ DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => false))
507
+ DRbObject.stub!(:new).with(nil, 'b').and_return(x = mock('o'))
508
+ x.stub(:alive?).and_raise DRb::DRbConnError.new('nix da')
509
+ @worker.followers_to_stop.should == %w(c)
510
+ end
511
+
512
+ it "should not remove unreachable entries from the list - maybe they reappear" do
513
+ DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => false))
514
+ @worker.followers_to_stop.should == %w(b c)
515
+ DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => true))
516
+ @worker.followers_to_stop.should == %w(a b c)
517
+ end
518
+ end
352
519
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: infopark-politics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.15
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham