infopark-politics 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjRmYzE1MmU4OGM0YmE4YTZhZTA2N2NjYjc3NjI4MjJiM2VlYTJjMQ==
5
+ data.tar.gz: !binary |-
6
+ ZTliMGY5NzcyNDk3ZjliZjBmZjY1NGM5MGE3MzkzZmM3NDg5ZDljZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzYzMjdmYzgxZTgyNjgxODhiYzg0NWVmZmI4YmY5N2JkZmMxZTZjN2Q4OGRh
10
+ MDIxYzg1NTc0MTk5MDA2ZTI3N2VmMjkzODQ4YzMxYzM3MTZiNWViZDRhM2I1
11
+ Yzc0ZDM3MWU2N2FlNzlhMDFmODMwYjZkMWMzMWIxYzRhNGY4ZjQ=
12
+ data.tar.gz: !binary |-
13
+ MjkxNGRkMzFkNTlhYTY3Yzk4MDBhMzE4OGRkMzA3YzM2ZTZlZjc2MDczNzIz
14
+ N2JjMWFjYmRmNzM2ODE1NGUxOWI2MDE5NmQ2MTA0NmRkYmY1ODM5ODY4ODI2
15
+ Yzc1Yzk3MzZiOTdkN2NlYzIzZGRkYzdmODNiNDU2Yzk0MGY0M2I=
data/.rbenv-version CHANGED
@@ -1 +1 @@
1
- 1.8.7-p374
1
+ 1.9.3-p448
data/README.md ADDED
@@ -0,0 +1,9 @@
1
+ Infopark-Politics
2
+ =================
3
+
4
+ Infopark-Politics was derived from mperham-politics. All parts except of the StaticQueueWorker had
5
+ eventually been dropped. The StaticQueueWorker remained for breaking up work into a large amount of
6
+ pieces (called buckets) which then are distributed on n-1 worker nodes where n ist the total number
7
+ of nodes.
8
+
9
+ Actually there is no further documentation available…
@@ -1,23 +1,23 @@
1
1
  # encoding: utf-8
2
- requirement = Gem::Requirement.new(">= 1.6.2")
2
+ requirement = Gem::Requirement.new(">= 1.8")
3
3
  unless requirement.satisfied_by?(Gem::Version.new(Gem::VERSION))
4
4
  raise "You need at RubyGems in Version #{requirement} to build this gem."
5
5
  end
6
6
 
7
7
  Gem::Specification.new do |gem|
8
8
  gem.name = "infopark-politics"
9
- gem.version = "0.3.3"
9
+ gem.version = "0.4.0"
10
10
  gem.summary = "Algorithms and Tools for Distributed Computing in Ruby."
11
11
  gem.description = ""
12
12
  gem.authors = ["Mike Perham", "Tilo Prütz"]
13
13
  gem.email = "tilo@infopark.de"
14
14
  gem.homepage = "http://github.com/infopark/politics"
15
15
 
16
+ gem.required_ruby_version = Gem::Requirement.new('>= 1.9')
16
17
  gem.required_rubygems_version = Gem::Requirement.new(">= 1.8")
17
18
  gem.extra_rdoc_files = [
18
- "History.rdoc",
19
19
  "LICENSE",
20
- "README.rdoc"
20
+ "README.md"
21
21
  ]
22
22
  gem.files = `git ls-files`.split("\n")
23
23
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -30,7 +30,6 @@ Gem::Specification.new do |gem|
30
30
  gem.add_development_dependency "rubygems-tasks", ">=0.2"
31
31
 
32
32
  gem.add_runtime_dependency "memcache-client", ">= 1.5.0"
33
- gem.add_runtime_dependency "starling-starling", ">= 0.9.8"
34
- gem.add_runtime_dependency "net-mdns", ">= 0.4"
33
+ gem.add_runtime_dependency "starling", ">= 0.9.8"
35
34
  end
36
35
 
@@ -4,15 +4,7 @@ require 'ipaddr'
4
4
  require 'uri'
5
5
  require 'drb'
6
6
  require 'set'
7
-
8
- begin
9
- require 'net/dns/mdns-sd'
10
- require 'net/dns/resolv-mdns'
11
- require 'net/dns/resolv-replace'
12
- rescue LoadError => e
13
- puts "Unable to load net-mdns, please run `sudo gem install net-mdns`: #{e.message}"
14
- exit(1)
15
- end
7
+ require 'logger'
16
8
 
17
9
  begin
18
10
  require 'memcache'
@@ -22,42 +14,6 @@ rescue LoadError => e
22
14
  end
23
15
 
24
16
  module Politics
25
-
26
- # The StaticQueueWorker mixin allows a processing daemon to "lease" or checkout
27
- # a portion of a problem space to ensure no other process is processing that same
28
- # space at the same time. The processing space is cut into N "buckets", each of
29
- # which is placed in a queue. Processes then fetch entries from the queue
30
- # and process them. It is up to the application to map the bucket number onto its
31
- # specific problem space.
32
- #
33
- # Note that memcached is used for leader election. The leader owns the queue during
34
- # the iteration period and other peers fetch buckets from the current leader during the
35
- # iteration.
36
- #
37
- # The leader hands out buckets in order. Once all the buckets have been processed, the
38
- # leader returns nil to the processors which causes them to sleep until the end of the
39
- # iteration. Then everyone wakes up, a new leader is elected, and the processing starts
40
- # all over again.
41
- #
42
- # DRb and mDNS are used for peer discovery and communication.
43
- #
44
- # Example usage:
45
- #
46
- # class Analyzer
47
- # include Politics::StaticQueueWorker
48
- # TOTAL_BUCKETS = 16
49
- #
50
- # def start
51
- # register_worker(self.class.name, TOTAL_BUCKETS)
52
- # process_bucket do |bucket|
53
- # puts "Analyzing bucket #{bucket} of #{TOTAL_BUCKETS}"
54
- # sleep 5
55
- # end
56
- # end
57
- # end
58
- #
59
- # Note: process_bucket never returns i.e. this should be the main loop of your processing daemon.
60
- #
61
17
  module StaticQueueWorker
62
18
  attr_reader :group_name, :iteration_length, :dictatorship_length, :uri, :buckets
63
19
 
@@ -72,17 +28,15 @@ module Politics
72
28
  @iteration_length = options[:iteration_length]
73
29
  @memcache_client = client_for(Array(options[:servers]))
74
30
  @nominated_at = Time.now
75
- # FIXME: Tests
76
- @domain = options[:domain]
77
31
  @dictatorship_length = options[:dictatorship_length]
78
32
 
79
33
  @buckets = []
80
34
  @bucket_count = bucket_count
81
35
  @followers_to_stop = Set.new
82
36
 
83
- register_with_bonjour
37
+ start_druby_service
84
38
  log.progname = uri
85
- log.info { "Registered in group #{group_name} at port #{@port}" }
39
+ log.info { "Registered in group #{group_name} at #{uri}" }
86
40
  at_exit do
87
41
  cleanup
88
42
  end
@@ -213,18 +167,7 @@ module Politics
213
167
  end
214
168
 
215
169
  def find_workers
216
- workers = []
217
- browser = Net::DNS::MDNSSD.browse(mdns_type) do |reply|
218
- worker_uri = reply.name.gsub(/#/, '.')
219
- workers << worker_uri unless worker_uri == uri
220
- end
221
- sleep 5
222
- browser.stop
223
- workers
224
- end
225
-
226
- def hostname
227
- nil
170
+ raise "Please provide a method ”find_workers” returning a list of all other worker URIs"
228
171
  end
229
172
 
230
173
  private
@@ -328,26 +271,16 @@ module Politics
328
271
  Time.now - a
329
272
  end
330
273
 
331
- def mdns_type
332
- "_#{group_name}._tcp"
274
+ def hostname
333
275
  end
334
276
 
335
- def register_with_bonjour
277
+ def register_service
278
+ end
279
+
280
+ def start_druby_service
336
281
  server = DRb.start_service("druby://#{hostname || ""}:0", self)
337
282
  @uri = DRb.uri
338
- @port = URI.parse(DRb.uri).port
339
-
340
- # Register our DRb server with Bonjour.
341
- name = @uri.gsub(/\./, '#')
342
- domain = "local"
343
- log.debug "register service #{name} of type #{mdns_type} within domain #{domain} at port #{@port}"
344
- handle = Net::DNS::MDNSSD.register(name, mdns_type, domain, @port) do |reply|
345
- log.debug "registered as #{reply.fullname}"
346
- if reply.name != name
347
- log.debug "Registered name #{reply.name} differs from requested name #{name} … exiting."
348
- handle.stop
349
- end
350
- end
283
+ register_service
351
284
  end
352
285
  end
353
286
  end
data/lib/politics.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # encoding: utf-8
2
+ require_relative 'politics/static_queue_worker'
3
+
2
4
  module Politics
3
5
 
4
6
  def self.log=(value)
data/spec/spec_helper.rb CHANGED
@@ -15,3 +15,6 @@ RSpec.configure do |config|
15
15
  # --seed 1234
16
16
  config.order = 'random'
17
17
  end
18
+
19
+ require_relative 'politics'
20
+ Politics::log.level = Logger::FATAL
@@ -1,9 +1,6 @@
1
1
  # encoding: utf-8
2
- # FIXME oberen Teil in spec_helper.rb auslagern
3
- require 'rubygems'
4
- $:.unshift(File.dirname(__FILE__) + '/../lib')
5
- require File.dirname(__FILE__) + '/../lib/init'
6
- Politics::log.level = Logger::FATAL
2
+
3
+ require 'politics'
7
4
 
8
5
  class UninitializedWorker
9
6
  include Politics::StaticQueueWorker
@@ -56,7 +53,7 @@ describe UninitializedWorker do
56
53
  end
57
54
 
58
55
  it "should not have a hostname" do
59
- worker.hostname.should be_nil
56
+ worker.send(:hostname).should be_nil
60
57
  end
61
58
 
62
59
  context "when it has a hostname" do
@@ -477,33 +474,6 @@ describe Worker do
477
474
  end
478
475
  end
479
476
 
480
- describe "when finding workers" do
481
- before do
482
- Net::DNS::MDNSSD.stub(:browse).
483
- and_yield(double('response1', :name => 'w1')).
484
- and_yield(double('response2', :name => 'w2')).
485
- and_yield(double('response3', :name => 'w3')).
486
- and_yield(double('response4', :name => 'w4')).
487
- and_return(@browser = double('browser', :stop => nil))
488
- worker.stub(:sleep)
489
- end
490
-
491
- it "should browse mdns group and return workers found" do
492
- worker.find_workers.should == %w(w1 w2 w3 w4)
493
- end
494
-
495
- it "should not add itself to the result list" do
496
- worker.stub(:uri).and_return('w3')
497
- worker.find_workers.should_not include('w3')
498
- end
499
-
500
- it "should stop browser thread after five seconds" do
501
- worker.should_receive(:sleep).with(5).ordered
502
- @browser.should_receive(:stop)
503
- worker.find_workers
504
- end
505
- end
506
-
507
477
  describe "when populating followers_to_stop" do
508
478
  before do
509
479
  worker.stub(:find_workers).and_return(%w(a b c))
metadata CHANGED
@@ -1,203 +1,145 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: infopark-politics
3
- version: !ruby/object:Gem::Version
4
- hash: 21
5
- prerelease:
6
- segments:
7
- - 0
8
- - 3
9
- - 3
10
- version: 0.3.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Mike Perham
14
- - "Tilo Pr\xC3\xBCtz"
8
+ - Tilo Prütz
15
9
  autorequire:
16
10
  bindir: bin
17
11
  cert_chain: []
18
-
19
- date: 2013-11-09 00:00:00 +01:00
20
- default_executable:
21
- dependencies:
22
- - !ruby/object:Gem::Dependency
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 31
29
- segments:
30
- - 2
31
- - 14
32
- version: "2.14"
12
+ date: 2013-11-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
33
15
  name: rspec
34
- version_requirements: *id001
35
- prerelease: false
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '2.14'
36
21
  type: :development
37
- - !ruby/object:Gem::Dependency
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 23
44
- segments:
45
- - 10
46
- version: "10"
47
- name: rake
48
- version_requirements: *id002
49
22
  prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '2.14'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '10'
50
35
  type: :development
51
- - !ruby/object:Gem::Dependency
52
- requirement: &id003 !ruby/object:Gem::Requirement
53
- none: false
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- hash: 21
58
- segments:
59
- - 3
60
- - 9
61
- version: "3.9"
62
- name: rdoc
63
- version_requirements: *id003
64
36
  prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '10'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rdoc
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '3.9'
65
49
  type: :development
66
- - !ruby/object:Gem::Dependency
67
- requirement: &id004 !ruby/object:Gem::Requirement
68
- none: false
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- hash: 15
73
- segments:
74
- - 0
75
- - 2
76
- version: "0.2"
77
- name: rubygems-tasks
78
- version_requirements: *id004
79
50
  prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '3.9'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubygems-tasks
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0.2'
80
63
  type: :development
81
- - !ruby/object:Gem::Dependency
82
- requirement: &id005 !ruby/object:Gem::Requirement
83
- none: false
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- hash: 3
88
- segments:
89
- - 1
90
- - 5
91
- - 0
92
- version: 1.5.0
93
- name: memcache-client
94
- version_requirements: *id005
95
64
  prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0.2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: memcache-client
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 1.5.0
96
77
  type: :runtime
97
- - !ruby/object:Gem::Dependency
98
- requirement: &id006 !ruby/object:Gem::Requirement
99
- none: false
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- hash: 43
104
- segments:
105
- - 0
106
- - 9
107
- - 8
108
- version: 0.9.8
109
- name: starling-starling
110
- version_requirements: *id006
111
78
  prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: 1.5.0
84
+ - !ruby/object:Gem::Dependency
85
+ name: starling
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: 0.9.8
112
91
  type: :runtime
113
- - !ruby/object:Gem::Dependency
114
- requirement: &id007 !ruby/object:Gem::Requirement
115
- none: false
116
- requirements:
117
- - - ">="
118
- - !ruby/object:Gem::Version
119
- hash: 3
120
- segments:
121
- - 0
122
- - 4
123
- version: "0.4"
124
- name: net-mdns
125
- version_requirements: *id007
126
92
  prerelease: false
127
- type: :runtime
128
- description: ""
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: 0.9.8
98
+ description: ''
129
99
  email: tilo@infopark.de
130
100
  executables: []
131
-
132
101
  extensions: []
133
-
134
- extra_rdoc_files:
135
- - History.rdoc
102
+ extra_rdoc_files:
136
103
  - LICENSE
137
- - README.rdoc
138
- files:
104
+ - README.md
105
+ files:
139
106
  - .gitignore
140
107
  - .rbenv-version
141
108
  - .rspec
142
109
  - Gemfile
143
- - History.rdoc
144
110
  - LICENSE
145
111
  - Manifest
146
- - README.rdoc
112
+ - README.md
147
113
  - Rakefile
148
- - VERSION
149
114
  - dcc_config.rb
150
- - examples/queue_worker_example.rb
151
- - examples/token_worker_example.rb
152
115
  - infopark-politics.gemspec
153
- - lib/init.rb
154
116
  - lib/politics.rb
155
- - lib/politics/discoverable_node.rb
156
117
  - lib/politics/static_queue_worker.rb
157
- - lib/politics/token_worker.rb
158
118
  - spec/spec_helper.rb
159
119
  - spec/static_queue_worker_spec.rb
160
- - test/static_queue_worker_test.rb
161
- - test/test_helper.rb
162
- - test/token_worker_test.rb
163
- has_rdoc: true
164
120
  homepage: http://github.com/infopark/politics
165
121
  licenses: []
166
-
122
+ metadata: {}
167
123
  post_install_message:
168
124
  rdoc_options: []
169
-
170
- require_paths:
125
+ require_paths:
171
126
  - lib
172
- required_ruby_version: !ruby/object:Gem::Requirement
173
- none: false
174
- requirements:
175
- - - ">="
176
- - !ruby/object:Gem::Version
177
- hash: 3
178
- segments:
179
- - 0
180
- version: "0"
181
- required_rubygems_version: !ruby/object:Gem::Requirement
182
- none: false
183
- requirements:
184
- - - ">="
185
- - !ruby/object:Gem::Version
186
- hash: 31
187
- segments:
188
- - 1
189
- - 8
190
- version: "1.8"
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '1.9'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '1.8'
191
137
  requirements: []
192
-
193
138
  rubyforge_project:
194
- rubygems_version: 1.6.2
139
+ rubygems_version: 2.1.10
195
140
  signing_key:
196
- specification_version: 3
141
+ specification_version: 4
197
142
  summary: Algorithms and Tools for Distributed Computing in Ruby.
198
- test_files:
143
+ test_files:
199
144
  - spec/spec_helper.rb
200
145
  - spec/static_queue_worker_spec.rb
201
- - test/static_queue_worker_test.rb
202
- - test/test_helper.rb
203
- - test/token_worker_test.rb
data/History.rdoc DELETED
@@ -1,31 +0,0 @@
1
- = Changelog
2
-
3
- == 0.2.5 (2009-02-04)
4
-
5
- * Gracefully handle MemCache::MemCacheErrors. Just sleep until memcached comes back.
6
-
7
- == 0.2.4 (2009-01-28)
8
-
9
- * Reduce leader token expiration time to discourage a get/set race condition. (Brian Dainton)
10
-
11
- == 0.2.3 (2009-01-12)
12
-
13
- * Fix invalid result check in previous change. (Brian Dainton)
14
-
15
- == 0.2.2 (2009-01-07)
16
-
17
- * Fix invalid leader? logic in TokenWorker which could allow
18
- two workers to become leader at the same time. (Brian Dainton)
19
-
20
- == 0.2.1 (2008-11-04)
21
-
22
- * Cleanup and prepare for public release for RubyConf 2008.
23
- * Election Day. Politics. Get it? Hee hee.
24
-
25
- == 0.2.0 (2008-10-24)
26
-
27
- * Remove BucketWorker based on initial feedback. Add StaticQueueWorker as a more reliable replacement.
28
-
29
- == 0.1.0 (2008-10-07)
30
-
31
- * Add BucketWorker and TokenWorker mixins.
data/README.rdoc DELETED
@@ -1,49 +0,0 @@
1
- = Politics
2
-
3
- Politics is a Ruby library providing utilities and algorithms for solving common distributed
4
- computing problems. Distributed Computing and Politics have a number of things in common:
5
- 1) they can be beautiful in theory but get really ugly in reality; 2) after working with
6
- either for a few weeks/months/years (depending on your moral flexibility) you'll find yourself
7
- intellectually devoid, a hollow shell of a man/woman/cybernetic killing machine.
8
-
9
- So the name is to be taken tongue in cheek. Onto the real details.
10
-
11
- == Common Problems in Distributed Computing
12
-
13
- Ruby services are often deployed as a cloud of many processes across several machines,
14
- for fault tolerance. This introduces the problem of coordination between those processes.
15
- Specifically, how do you keep those processes from stepping on each other's electronic
16
- toes? There are several answers:
17
-
18
- 1. Break the processing into N 'buckets'. Have an individual process fetch a bucket,
19
- work on it, and ask for another. This is a very scalable solution as it allows N workers
20
- to work on different parts of the same task concurrently. See the +StaticQueueWorker+ mixin.
21
- 1. Elect a leader for a short period of time. The leader is the process which performs the
22
- actual processing. After a length of time, a new leader is elected from the group. This
23
- is fault tolerant but not as scalable, as only one process is performing the task at a given
24
- point in time. See the +TokenWorker+ mixin.
25
-
26
- == Installation
27
-
28
- sudo gem install mperham-politics -s http://gems.github.com
29
-
30
- == Dependencies
31
-
32
- StaticQueueWorker mixin
33
- * memcached - the mechanism to elect a leader amongst a set of peers.
34
- * DRb - the mechanism to communicate between peers.
35
- * mDNS - the mechanism to discover peers.
36
-
37
- TokenWorker mixin
38
- * memcached - the mechanism to elect a leader amongst a set of peers.
39
-
40
-
41
- = Author
42
-
43
- Name:: Mike Perham
44
- Email:: mailto:mperham@gmail.com
45
- Twitter:: http://twitter.com/mperham
46
- Homepage:: http://mikeperham.com/
47
-
48
- This software is free for you to use as you'd like. If you find it useful, please consider giving
49
- me a recommendation at {Working with Rails}[http://workingwithrails.com/person/10797-mike-perham].
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.3.2
@@ -1,38 +0,0 @@
1
- # encoding: utf-8
2
- #gem 'mperham-politics'
3
- require 'politics'
4
- require 'politics/static_queue_worker'
5
-
6
- # Test this example by starting memcached locally and then in two irb sessions, run this:
7
- #
8
- =begin
9
- require 'queue_worker_example'
10
- p = Politics::QueueWorkerExample.new
11
- p.start
12
- =end
13
- #
14
- # You can then watch as one of them is elected leader. You can kill the leader and verify
15
- # the backup process is elected after approximately iteration_length seconds.
16
- #
17
- module Politics
18
- class QueueWorkerExample
19
- include Politics::StaticQueueWorker
20
- TOTAL_BUCKETS = 20
21
-
22
- def initialize
23
- register_worker 'queue-example', TOTAL_BUCKETS, :iteration_length => 60, :servers => memcached_servers
24
- end
25
-
26
- def start
27
- process_bucket do |bucket|
28
- puts "PID #{$$} processing bucket #{bucket}/#{TOTAL_BUCKETS} at #{Time.now}..."
29
- sleep 1.5
30
- end
31
- end
32
-
33
- def memcached_servers
34
- ['127.0.0.1:11211']
35
- end
36
-
37
- end
38
- end
@@ -1,36 +0,0 @@
1
- # encoding: utf-8
2
- #gem 'mperham-politics'
3
- require 'politics'
4
- require 'politics/token_worker'
5
-
6
- # Test this example by starting memcached locally and then in two irb sessions, run this:
7
- #
8
- =begin
9
- require 'token_worker_example'
10
- p = Politics::TokenWorkerExample.new
11
- p.start
12
- =end
13
- #
14
- # You can then watch as one of them is elected leader. You can kill the leader and verify
15
- # the backup process is elected after approximately iteration_length seconds.
16
- #
17
- module Politics
18
- class TokenWorkerExample
19
- include Politics::TokenWorker
20
-
21
- def initialize
22
- register_worker 'token-example', :iteration_length => 10, :servers => memcached_servers
23
- end
24
-
25
- def start
26
- process do
27
- puts "PID #{$$} processing at #{Time.now}..."
28
- end
29
- end
30
-
31
- def memcached_servers
32
- ['localhost:11211']
33
- end
34
-
35
- end
36
- end
data/lib/init.rb DELETED
@@ -1,5 +0,0 @@
1
- # encoding: utf-8
2
- require 'politics'
3
- require 'politics/token_worker'
4
- require 'politics/static_queue_worker'
5
- require 'politics/discoverable_node'
@@ -1,138 +0,0 @@
1
- # encoding: utf-8
2
- require 'socket'
3
- require 'ipaddr'
4
- require 'uri'
5
- require 'drb'
6
-
7
- require 'net/dns/mdns-sd'
8
- require 'net/dns/resolv-mdns'
9
- require 'net/dns/resolv-replace'
10
-
11
- =begin
12
- IRB setup:
13
- require 'lib/politics'
14
- require 'lib/politics/discoverable_node'
15
- require 'lib/politics/convention'
16
- Object.send(:include, Election::Candidate)
17
- p = Object.new
18
- p.register
19
- =end
20
-
21
- module Politics
22
-
23
- # A module to solve the Group Membership problem in distributed computing.
24
- # The "group" is the cloud of processes which are replicas and need to coordinate.
25
- # Handling group membership is the first step in solving distributed computing
26
- # problems. There are two issues:
27
- # 1) replica discovery
28
- # 2) controlling and maintaining a consistent group of replicas in each replica
29
- #
30
- # Peer discovery is implemented using Bonjour for local network auto-discovery.
31
- # Each process registers itself on the network as a process of a given type.
32
- # Each process then queries the network for other replicas of the same type.
33
- #
34
- # The replicas then run the Multi-Paxos algorithm to provide consensus on a given
35
- # replica set. The algorithm is robust in the face of crash failures, but not
36
- # Byzantine failures.
37
- module DiscoverableNode
38
-
39
- attr_accessor :group
40
- attr_accessor :coordinator
41
-
42
- def register(group='foo')
43
- self.group = group
44
- start_drb
45
- register_with_bonjour(group)
46
- Politics::log.info { "Registered #{self} in group #{group} with RID #{rid}" }
47
- sleep 0.5
48
- find_replicas(0)
49
- end
50
-
51
- def replicas
52
- @replicas ||= {}
53
- end
54
-
55
- def find_replicas(count)
56
- replicas.clear if count % 5 == 0
57
- return if count > 10 # Guaranteed to terminate, but not successfully :-(
58
-
59
- #puts "Finding replicas"
60
- peer_set = []
61
- bonjour_scan do |replica|
62
- (his_rid, his_peers) = replica.hello(rid)
63
- unless replicas.has_key?(his_rid)
64
- replicas[his_rid] = replica
65
- end
66
- his_peers.each do |peer|
67
- peer_set << peer unless peer_set.include? peer
68
- end
69
- end
70
- #p [peer_set.sort, replicas.keys.sort]
71
- if peer_set.sort != replicas.keys.sort
72
- # Recursively call ourselves until the network has settled down and all
73
- # peers have reached agreement on the peer group membership.
74
- sleep 0.2
75
- find_replicas(count + 1)
76
- end
77
- Politics::log.info { "Found #{replicas.size} peers: #{replicas.keys.sort.inspect}" } if count == 0
78
- replicas
79
- end
80
-
81
- # Called for one peer to introduce itself to another peer. The caller
82
- # sends his RID, the responder sends his RID and his list of current peer
83
- # RIDs.
84
- def hello(remote_rid)
85
- [rid, replicas.keys]
86
- end
87
-
88
- # A process's Replica ID is its PID + a random 16-bit value. We don't want
89
- # weigh solely based on PID or IP as that may unduly load one machine.
90
- def rid
91
- @rid ||= begin
92
- rand(65536) + $$
93
- end
94
- end
95
-
96
- private
97
-
98
- def register_with_bonjour(group)
99
- # Register our DRb server with Bonjour.
100
- handle = Net::DNS::MDNSSD.register("#{self.group}-#{local_ip}-#{$$}",
101
- "_#{self.group}._tcp", 'local', @port)
102
-
103
- ['INT', 'TERM'].each { |signal|
104
- trap(signal) { handle.stop }
105
- }
106
- end
107
-
108
- def start_drb
109
- server = DRb.start_service(nil, self)
110
- @port = URI.parse(DRb.uri).port
111
- ['INT', 'TERM'].each { |signal|
112
- trap(signal) { server.stop_service }
113
- }
114
- end
115
-
116
- def bonjour_scan
117
- Net::DNS::MDNSSD.browse("_#{@group}._tcp") do |b|
118
- Net::DNS::MDNSSD.resolve(b.name, b.type) do |r|
119
- drburl = "druby://#{r.target}:#{r.port}"
120
- replica = DRbObject.new(nil, drburl)
121
- yield replica
122
- end
123
- end
124
- end
125
-
126
- # http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
127
- def local_ip
128
- orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
129
-
130
- UDPSocket.open do |s|
131
- s.connect '64.233.187.99', 1
132
- IPAddr.new(s.addr.last).to_i
133
- end
134
- ensure
135
- Socket.do_not_reverse_lookup = orig
136
- end
137
- end
138
- end
@@ -1,175 +0,0 @@
1
- # encoding: utf-8
2
- begin
3
- require 'memcache'
4
- rescue LoadError => e
5
- puts "Unable to load memcache client, please run `sudo gem install memcache-client`: #{e.message}"
6
- exit(1)
7
- end
8
-
9
- module Politics
10
-
11
- # An algorithm to provide leader election between a set of identical processing daemons.
12
- #
13
- # Each TokenWorker is an instance which needs to perform some processing.
14
- # The worker instance must obtain the leader token before performing some task.
15
- # We use a memcached server as a central token authority to provide a shared,
16
- # network-wide view for all processors. This reliance on a single resource means
17
- # if your memcached server goes down, so do the processors. Oftentimes,
18
- # this is an acceptable trade-off since many high-traffic web sites would
19
- # not be useable without memcached running anyhow.
20
- #
21
- # Essentially each TokenWorker attempts to elect itself every +:iteration_length+
22
- # seconds by simply setting a key in memcached to its own name. Memcached tracks
23
- # which name got there first. The key expires after +:iteration_length+ seconds.
24
- #
25
- # Example usage:
26
- # class Analyzer
27
- # include Politics::TokenWorker
28
- #
29
- # def initialize
30
- # register_worker 'analyzer', :iteration_length => 120, :servers => ['localhost:11211']
31
- # end
32
- #
33
- # def start
34
- # process do
35
- # # do analysis here, will only be done when this process
36
- # # is actually elected leader, otherwise it will sleep for
37
- # # iteration_length seconds.
38
- # end
39
- # end
40
- # end
41
- #
42
- # Notes:
43
- # * This will not work with multiple instances in the same Ruby process.
44
- # The library is only designed to elect a leader from a set of processes, not instances within
45
- # a single process.
46
- # * The algorithm makes no attempt to keep the same leader during the next iteration.
47
- # This can often times be quite beneficial (e.g. leveraging a warm cache from the last iteration)
48
- # for performance but is left to the reader to implement.
49
- module TokenWorker
50
-
51
- def self.included(model) #:nodoc:
52
- model.class_eval do
53
- attr_accessor :memcache_client, :token, :iteration_length, :worker_name
54
- class << self
55
- attr_accessor :worker_instance #:nodoc:
56
- end
57
- end
58
- end
59
-
60
- # Register this instance as a worker.
61
- #
62
- # Options:
63
- # +:iteration_length+:: The length of a processing iteration, in seconds. The
64
- # leader's 'reign' lasts for this length of time.
65
- # +:servers+:: An array of memcached server strings
66
- def register_worker(name, config={})
67
- # track the latest instance of this class, there's really only supposed to be
68
- # a single TokenWorker instance per process.
69
- self.class.worker_instance = self
70
-
71
- options = { :iteration_length => 60, :servers => ['localhost:11211'] }
72
- options.merge!(config)
73
-
74
- self.token = "#{name}_token"
75
- self.memcache_client = client_for(Array(options[:servers]))
76
- self.iteration_length = options[:iteration_length]
77
- self.worker_name = "#{Socket.gethostname}:#{$$}"
78
-
79
- cleanup
80
- end
81
-
82
- def process(*args, &block)
83
- verify_registration
84
-
85
- begin
86
- # Try to add our name as the worker with the master token.
87
- # If another process got there first, this is a noop.
88
- # We add an expiry so that the master token will constantly
89
- # need to be refreshed (in case the current leader dies).
90
- time = 0
91
- begin
92
- nominate
93
-
94
- if leader?
95
- Politics::log.info { "#{worker_name} elected leader at #{Time.now}" }
96
- # If we are the master worker, do the work.
97
- time = time_for do
98
- result = block.call(*args)
99
- end
100
- end
101
- rescue MemCache::MemCacheError => me
102
- Politics::log.error("Error from memcached, pausing until the next iteration...")
103
- Politics::log.error(me.message)
104
- Politics::log.error(me.backtrace.join("\n"))
105
- self.memcache_client.reset
106
- end
107
-
108
- pause_until_expiry(time)
109
- reset_state
110
- end while loop?
111
- end
112
-
113
- private
114
-
115
- def reset_state
116
- @leader = nil
117
- end
118
-
119
- def verify_registration
120
- unless self.class.worker_instance
121
- raise ArgumentError, "Cannot call process without first calling register_worker"
122
- end
123
- unless self.class.worker_instance == self
124
- raise SecurityError, "Only one instance of #{self.class} per process. Another instance was created after this one."
125
- end
126
- end
127
-
128
- def loop?
129
- true
130
- end
131
-
132
- def cleanup
133
- at_exit do
134
- memcache_client.delete(token) if leader?
135
- end
136
- end
137
-
138
- def pause_until_expiry(elapsed)
139
- pause_time = (iteration_length - elapsed).to_f
140
- if pause_time > 0
141
- relax(pause_time)
142
- else
143
- raise ArgumentError, "Negative iteration time left. Assuming the worst and exiting... #{iteration_length}/#{elapsed}"
144
- end
145
- end
146
-
147
- def relax(time)
148
- sleep time
149
- end
150
-
151
- # Nominate ourself as leader by contacting the memcached server
152
- # and attempting to add the token with our name attached.
153
- # The result will tell us if memcached stored our value and therefore
154
- # if we are now leader.
155
- def nominate
156
- result = memcache_client.add(token, worker_name, (iteration_length * 0.9).to_i)
157
- @leader = (result =~ /\ASTORED/)
158
- end
159
-
160
- def leader?
161
- @leader
162
- end
163
-
164
- # Easy to mock or monkey-patch if another MemCache client is preferred.
165
- def client_for(servers)
166
- MemCache.new(servers)
167
- end
168
-
169
- def time_for(&block)
170
- a = Time.now
171
- yield
172
- Time.now - a
173
- end
174
- end
175
- end
@@ -1,43 +0,0 @@
1
- # encoding: utf-8
2
- require File.dirname(__FILE__) + '/test_helper'
3
-
4
- Thread.abort_on_exception = true
5
-
6
- class Worker
7
- include Politics::StaticQueueWorker
8
- def initialize
9
- register_worker 'worker', 10, :iteration_length => 10
10
- end
11
-
12
- def start
13
- process_bucket do |bucket|
14
- sleep 1
15
- end
16
- end
17
- end
18
-
19
- class StaticQueueWorkerTest < Test::Unit::TestCase
20
-
21
- context "nodes" do
22
- setup do
23
- @nodes = []
24
- 5.times do
25
- @nodes << nil
26
- end
27
- end
28
-
29
- should "start up" do
30
- processes = @nodes.map do
31
- fork do
32
- ['INT', 'TERM'].each { |signal|
33
- trap(signal) { exit(0) }
34
- }
35
- Worker.new.start
36
- end
37
- end
38
- sleep 10
39
- puts "Terminating"
40
- Process.kill('INT', *processes)
41
- end
42
- end
43
- end
data/test/test_helper.rb DELETED
@@ -1,20 +0,0 @@
1
- # encoding: utf-8
2
- require 'rubygems'
3
- require 'test/unit'
4
-
5
- begin
6
- gem 'thoughtbot-shoulda', '>=2.0.2'
7
- require 'shoulda'
8
- rescue LoadError => e
9
- puts "Please install shoulda: `sudo gem install thoughtbot-shoulda -s http://gems.github.com`"
10
- end
11
-
12
- begin
13
- require 'mocha'
14
- rescue LoadError => e
15
- puts "Please install mocha: `sudo gem install mocha`"
16
- end
17
-
18
- $:.unshift(File.dirname(__FILE__) + '/../lib')
19
- require File.dirname(__FILE__) + '/../lib/init'
20
- Politics::log.level = Logger::WARN
@@ -1,79 +0,0 @@
1
- # encoding: utf-8
2
- require 'test_helper'
3
-
4
- class TokenWorkerTest < Test::Unit::TestCase
5
-
6
- context "token workers" do
7
- setup do
8
- @harness = Class.new
9
- @harness.send(:include, Politics::TokenWorker)
10
- @harness.any_instance.stubs(:cleanup)
11
- @harness.any_instance.stubs(:loop?).returns(false)
12
- @harness.any_instance.stubs(:pause_until_expiry)
13
- @harness.any_instance.stubs(:relax)
14
-
15
- @worker = @harness.new
16
- end
17
-
18
- should "test_instance_property_accessors" do
19
- assert @worker.iteration_length = 20
20
- assert_equal 20, @worker.iteration_length
21
- end
22
-
23
- should 'test_tracks_a_registered_singleton' do
24
- assert_nil @worker.class.worker_instance
25
- @worker.register_worker('testing')
26
- assert_equal @worker.class.worker_instance, @worker
27
- end
28
-
29
- should 'not process if they are not leader' do
30
- @worker.expects(:nominate)
31
- @worker.expects(:leader?).returns(false)
32
- @worker.register_worker('testing')
33
- @worker.process do
34
- assert false
35
- end
36
- end
37
-
38
- should 'handle unexpected MemCache errors' do
39
- @worker.expects(:nominate)
40
- @worker.expects(:leader?).raises(MemCache::MemCacheError)
41
- Politics::log.expects(:error).times(3)
42
-
43
- @worker.register_worker('testing')
44
- @worker.process do
45
- assert false
46
- end
47
- end
48
-
49
- should 'process if they are leader' do
50
- @worker.expects(:nominate)
51
- @worker.expects(:leader?).returns(true)
52
- @worker.register_worker('testing')
53
-
54
- worked = 0
55
- @worker.process do
56
- worked += 1
57
- end
58
-
59
- assert_equal 1, worked
60
- end
61
-
62
- should 'not allow processing without registration' do
63
- assert_raises ArgumentError do
64
- @worker.process
65
- end
66
- end
67
-
68
- should 'not allow processing by old instances' do
69
- @worker.register_worker('testing')
70
-
71
- foo = @worker.class.new
72
- foo.register_worker('testing')
73
-
74
- assert_raises SecurityError do
75
- @worker.process
76
- end
77
- end
78
- end
79
- end