mperham-politics 0.1.0 → 0.2.0

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/History.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = Changelog
2
2
 
3
+ == 0.2.0 (2008-10-??)
4
+
5
+ * Remove BucketWorker based on initial feedback. Add StaticQueueWorker as a more reliable replacement.
6
+
3
7
  == 0.1.0 (2008-10-07)
4
8
 
5
9
  * Add BucketWorker and TokenWorker mixins.
data/README.rdoc CHANGED
@@ -15,9 +15,9 @@ for fault tolerance. This introduces the problem of coordination between those
15
15
  Specifically, how do you keep those processes from stepping on each other's electronic
16
16
  toes? There are several answers:
17
17
 
18
- 1. Break the processing into several parts. Have an individual process "checkout" a part,
19
- work on it, and then return the part for processing. This is a very scalable solution
20
- as it allows N workers to work on the same task concurrently. See the +BucketWorker+ mixin.
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
21
  1. Elect a leader for a short period of time. The leader is the process which performs the
22
22
  actual processing. After a length of time, a new leader is elected from the group. This
23
23
  is fault tolerant but not as scalable, as only one process is performing the task at a given
@@ -25,8 +25,13 @@ toes? There are several answers:
25
25
 
26
26
  == Dependencies
27
27
 
28
- The BucketWorker mixin uses the Starling queue server as the mechanism to hand out parts. The
29
- TokenWorker mixin uses the memcached server as the mechanism to elect a leader.
28
+ StaticQueueWorker mixin
29
+ * memcached - the mechanism to elect a leader amongst a set of peers.
30
+ * DRb - the mechanism to communicate between peers.
31
+ * mDNS - the mechanism to discover peers.
32
+
33
+ TokenWorker mixin
34
+ * memcached - the mechanism to elect a leader amongst a set of peers.
30
35
 
31
36
 
32
37
  = Author
@@ -0,0 +1,35 @@
1
+ #gem 'mperham-politics'
2
+ require 'politics'
3
+ require 'politics/token_worker'
4
+
5
+ # Test this example by starting memcached locally and then in two irb sessions, run this:
6
+ #
7
+ #
8
+ # You can then watch as one of them is elected leader. You can kill the leader and verify
9
+ # the backup process is elected after approximately iteration_length seconds.
10
+ #
11
+ =begin
12
+ require 'token_worker_example'
13
+ p = Politics::TokenWorkerExample.new
14
+ p.start
15
+ =end
16
+ module Politics
17
+ class TokenWorkerExample
18
+ include Politics::TokenWorker
19
+
20
+ def initialize
21
+ register_worker 'token-example', :iteration_length => 10, :servers => memcached_servers
22
+ end
23
+
24
+ def start
25
+ process do
26
+ puts "PID #{$$} processing at #{Time.now}..."
27
+ end
28
+ end
29
+
30
+ def memcached_servers
31
+ ['localhost:11211']
32
+ end
33
+
34
+ end
35
+ end
data/lib/init.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'politics'
2
2
  require 'politics/token_worker'
3
- require 'politics/bucket_worker'
3
+ require 'politics/static_queue_worker'
4
4
  require 'politics/discoverable_node'
@@ -42,7 +42,7 @@ module Politics
42
42
  self.group = group
43
43
  start_drb
44
44
  register_with_bonjour(group)
45
- Politics.log "Registered #{self} in group #{group} with RID #{rid}"
45
+ Politics::log.info { "Registered #{self} in group #{group} with RID #{rid}" }
46
46
  sleep 0.5
47
47
  find_replicas(0)
48
48
  end
@@ -73,7 +73,7 @@ module Politics
73
73
  sleep 0.2
74
74
  find_replicas(count + 1)
75
75
  end
76
- puts "Found #{replicas.size} peers: #{replicas.keys.sort.inspect}" if count == 0
76
+ Politics::log.info { "Found #{replicas.size} peers: #{replicas.keys.sort.inspect}" } if count == 0
77
77
  replicas
78
78
  end
79
79
 
@@ -0,0 +1,269 @@
1
+ puts 'hello'
2
+ require 'socket'
3
+ require 'ipaddr'
4
+ require 'uri'
5
+ require 'drb'
6
+
7
+ begin
8
+ require 'net/dns/mdns-sd'
9
+ require 'net/dns/resolv-mdns'
10
+ require 'net/dns/resolv-replace'
11
+ rescue LoadError => e
12
+ puts "Unable to load net-mdns, please run `sudo gem install net-mdns`: #{e.message}"
13
+ exit(1)
14
+ end
15
+
16
+ begin
17
+ require 'memcache'
18
+ rescue LoadError => e
19
+ puts "Unable to load memcache client, please run `sudo gem install memcache-client`: #{e.message}"
20
+ exit(1)
21
+ end
22
+
23
+ module Politics
24
+
25
+ # The StaticQueueWorker mixin allows a processing daemon to "lease" or checkout
26
+ # a portion of a problem space to ensure no other process is processing that same
27
+ # space at the same time. The processing space is cut into N "buckets", each of
28
+ # which is placed in a queue. Processes then fetch entries from the queue
29
+ # and process them. It is up to the application to map the bucket number onto its
30
+ # specific problem space.
31
+ #
32
+ # Note that memcached is used for leader election. The leader owns the queue during
33
+ # the iteration period and other peers fetch buckets from the current leader during the
34
+ # iteration.
35
+ #
36
+ # The leader hands out buckets in order. Once all the buckets have been processed, the
37
+ # leader returns nil to the processors which causes them to sleep until the end of the
38
+ # iteration. Then everyone wakes up, a new leader is elected, and the processing starts
39
+ # all over again.
40
+ #
41
+ # DRb and mDNS are used for peer discovery and communication.
42
+ #
43
+ # Example usage:
44
+ #
45
+ # class Analyzer
46
+ # include Politics::StaticQueueWorker
47
+ # TOTAL_BUCKETS = 16
48
+ #
49
+ # def start
50
+ # register_worker(self.class.name, TOTAL_BUCKETS)
51
+ # process_bucket do |bucket|
52
+ # puts "Analyzing bucket #{bucket} of #{TOTAL_BUCKETS}"
53
+ # sleep 5
54
+ # end
55
+ # end
56
+ # end
57
+ #
58
+ # Note: process_bucket never returns i.e. this should be the main loop of your processing daemon.
59
+ #
60
+ module StaticQueueWorker
61
+
62
+ def self.included(model) #:nodoc:
63
+ model.class_eval do
64
+ attr_accessor :group_name, :iteration_length
65
+ end
66
+ end
67
+
68
+ # Register this process as able to work on buckets.
69
+ def register_worker(name, bucket_count, config={})
70
+ options = { :iteration_length => 60, :servers => ['127.0.0.1:11211'] }
71
+ options.merge!(config)
72
+
73
+ self.group_name = name
74
+ self.iteration_length = options[:iteration_length]
75
+ @memcache_client = client_for(Array(options[:servers]))
76
+
77
+ @buckets = []
78
+ @bucket_count = bucket_count
79
+ initialize_buckets
80
+
81
+ register_with_bonjour
82
+
83
+ log.info { "Registered #{self} in group #{group_name} at port #{@port}" }
84
+ end
85
+
86
+ # Fetch a bucket out of the queue and pass it to the given block to be processed.
87
+ #
88
+ # +bucket+:: The bucket number to process, within the range 0...TOTAL_BUCKETS
89
+ def process_bucket(&block)
90
+ raise ArgumentError, "process_bucket requires a block!" unless block_given?
91
+ raise ArgumentError, "You must call register_worker before processing!" unless @memcache_client
92
+
93
+ begin
94
+ nominate
95
+ if leader?
96
+ # Drb thread handles leader duties
97
+ log.info { "#{@uri} has been elected leader" }
98
+ relax until_next_iteration
99
+ initialize_buckets
100
+ else
101
+ # Get a bucket from the leader and process it
102
+ begin
103
+ bucket_process(*leader.bucket_request, &block)
104
+ rescue DRb::DRbError => dre
105
+ log.error { "Error talking to leader: #{dre.message}" }
106
+ relax until_next_iteration
107
+ end
108
+ end
109
+ end while loop?
110
+ end
111
+
112
+ def bucket_request
113
+ if leader?
114
+ [@buckets.pop, until_next_iteration]
115
+ else
116
+ :not_leader
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def bucket_process(bucket, sleep_time)
123
+ case bucket
124
+ when nil
125
+ # No more buckets to process this iteration
126
+ log.info { "No more buckets in this iteration, sleeping for #{sleep_time} sec" }
127
+ sleep sleep_time
128
+ when :not_leader
129
+ # Uh oh, race condition? Invalid any local cache and check again
130
+ log.warn { "Recv'd NOT_LEADER from peer." }
131
+ relax 1
132
+ @leader_uri = nil
133
+ else
134
+ log.info { "#{@uri} is processing #{bucket}"}
135
+ yield bucket
136
+ end
137
+ end
138
+
139
+ def log
140
+ @logger ||= Logger.new(STDOUT)
141
+ end
142
+
143
+ def initialize_buckets
144
+ @buckets.clear
145
+ @bucket_count.times { |idx| @buckets << idx }
146
+ end
147
+
148
+ def replicas
149
+ @replicas ||= []
150
+ end
151
+
152
+ def leader
153
+ name = leader_uri
154
+ repl = nil
155
+ while replicas.empty? or repl == nil
156
+ repl = replicas.detect { |replica| replica.__drburi == name }
157
+ unless repl
158
+ relax 1
159
+ bonjour_scan do |replica|
160
+ replicas << replica
161
+ end
162
+ end
163
+ end
164
+ repl
165
+ end
166
+
167
+ def until_next_iteration
168
+ left = iteration_length - (Time.now - @nominated_at)
169
+ left > 0 ? left : 0
170
+ end
171
+
172
+ def loop?
173
+ true
174
+ end
175
+
176
+ def token
177
+ "#{group_name}_token"
178
+ end
179
+
180
+ def cleanup
181
+ at_exit do
182
+ @memcache_client.delete(token) if leader?
183
+ end
184
+ end
185
+
186
+ def pause_until_expiry(elapsed)
187
+ pause_time = (iteration_length - elapsed).to_f
188
+ if pause_time > 0
189
+ relax(pause_time)
190
+ else
191
+ raise ArgumentError, "Negative iteration time left. Assuming the worst and exiting... #{iteration_length}/#{elapsed}"
192
+ end
193
+ end
194
+
195
+ def relax(time)
196
+ sleep time
197
+ end
198
+
199
+ # Nominate ourself as leader by contacting the memcached server
200
+ # and attempting to add the token with our name attached.
201
+ def nominate
202
+ @memcache_client.add(token, @uri, iteration_length)
203
+ @nominated_at = Time.now
204
+ @leader_uri = nil
205
+ end
206
+
207
+ def leader_uri
208
+ @leader_uri ||= @memcache_client.get(token)
209
+ end
210
+
211
+ # Check to see if we are leader by looking at the process name
212
+ # associated with the token.
213
+ def leader?
214
+ until_next_iteration > 0 && @uri == leader_uri
215
+ end
216
+
217
+ # Easy to mock or monkey-patch if another MemCache client is preferred.
218
+ def client_for(servers)
219
+ MemCache.new(servers)
220
+ end
221
+
222
+ def time_for(&block)
223
+ a = Time.now
224
+ yield
225
+ Time.now - a
226
+ end
227
+
228
+
229
+ def register_with_bonjour
230
+ server = DRb.start_service(nil, self)
231
+ @uri = DRb.uri
232
+ @port = URI.parse(DRb.uri).port
233
+
234
+ # Register our DRb server with Bonjour.
235
+ handle = Net::DNS::MDNSSD.register("#{self.group_name}-#{local_ip}-#{$$}",
236
+ "_#{group_name}._tcp", 'local', @port)
237
+
238
+ ['INT', 'TERM'].each { |signal|
239
+ trap(signal) do
240
+ handle.stop
241
+ server.stop_service
242
+ end
243
+ }
244
+ end
245
+
246
+ def bonjour_scan
247
+ Net::DNS::MDNSSD.browse("_#{group_name}._tcp") do |b|
248
+ Net::DNS::MDNSSD.resolve(b.name, b.type) do |r|
249
+ drburl = "druby://#{r.target}:#{r.port}"
250
+ replica = DRbObject.new(nil, drburl)
251
+ yield replica
252
+ end
253
+ end
254
+ end
255
+
256
+ # http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
257
+ def local_ip
258
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
259
+
260
+ UDPSocket.open do |s|
261
+ s.connect '64.233.187.99', 1
262
+ IPAddr.new(s.addr.last).to_i
263
+ end
264
+ ensure
265
+ Socket.do_not_reverse_lookup = orig
266
+ end
267
+
268
+ end
269
+ end
@@ -90,7 +90,7 @@ module Politics
90
90
 
91
91
  time = 0
92
92
  if leader?
93
- LOG.info { "I've been elected leader: #{worker_name}" }
93
+ Politics::log.info { "#{worker_name} elected leader at #{Time.now}" }
94
94
  # If we are the master worker, do the work.
95
95
  time = time_for do
96
96
  result = block.call(*args)
@@ -98,11 +98,16 @@ module Politics
98
98
  end
99
99
 
100
100
  pause_until_expiry(time)
101
+ reset_state
101
102
  end while loop?
102
103
  end
103
104
 
104
105
  private
105
106
 
107
+ def reset_state
108
+ @leader = nil
109
+ end
110
+
106
111
  def verify_registration
107
112
  unless self.class.worker_instance
108
113
  raise ArgumentError, "Cannot call process without first calling register_worker"
@@ -118,7 +123,7 @@ module Politics
118
123
 
119
124
  def cleanup
120
125
  at_exit do
121
- memcache_client.delete(token)
126
+ memcache_client.delete(token) if leader?
122
127
  end
123
128
  end
124
129
 
@@ -144,8 +149,10 @@ module Politics
144
149
  # Check to see if we are leader by looking at the process name
145
150
  # associated with the token.
146
151
  def leader?
147
- master_worker = memcache_client.get(token)
148
- worker_name == master_worker
152
+ @leader ||= begin
153
+ master_worker = memcache_client.get(token)
154
+ worker_name == master_worker
155
+ end
149
156
  end
150
157
 
151
158
  # Easy to mock or monkey-patch if another MemCache client is preferred.
@@ -159,4 +166,4 @@ module Politics
159
166
  Time.now - a
160
167
  end
161
168
  end
162
- end
169
+ end
@@ -1,5 +1,5 @@
1
1
  module Politics
2
2
  module Version
3
- STRING = "0.1.0"
3
+ STRING = "0.2.0"
4
4
  end
5
5
  end
data/lib/politics.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  require 'logger'
2
2
 
3
3
  module Politics
4
- LOG = Logger.new(STDOUT)
4
+
5
+ def self.log=(value)
6
+ @log = log
7
+ end
5
8
 
6
- def self.log(msg)
7
- LOG.info(msg)
9
+ def self.log
10
+ @log ||= Logger.new(STDOUT)
8
11
  end
9
12
  end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Thread.abort_on_exception = true
4
+
5
+ class Worker
6
+ include Politics::StaticQueueWorker
7
+ def initialize
8
+ register_worker 'worker', 10, :iteration_length => 10
9
+ end
10
+
11
+ def start
12
+ process_bucket do |bucket|
13
+ sleep 1
14
+ end
15
+ end
16
+ end
17
+
18
+ class StaticQueueWorkerTest < Test::Unit::TestCase
19
+
20
+ context "nodes" do
21
+ setup do
22
+ @nodes = []
23
+ 5.times do
24
+ @nodes << nil
25
+ end
26
+ end
27
+
28
+ should "start up" do
29
+ processes = @nodes.map do
30
+ fork do
31
+ ['INT', 'TERM'].each { |signal|
32
+ trap(signal) { exit(0) }
33
+ }
34
+ Worker.new.start
35
+ end
36
+ end
37
+ sleep 10
38
+ puts "Terminating"
39
+ Process.kill('INT', *processes)
40
+ end
41
+ end
42
+ end
data/test/test_helper.rb CHANGED
@@ -14,5 +14,6 @@ rescue LoadError => e
14
14
  puts "Please install mocha: `sudo gem install mocha`"
15
15
  end
16
16
 
17
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
17
18
  require File.dirname(__FILE__) + '/../lib/init'
18
- Politics::LOG.level = Logger::WARN
19
+ #Politics::log.level = Logger::WARN
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mperham-politics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-13 00:00:00 -07:00
12
+ date: 2008-10-27 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -19,16 +19,25 @@ dependencies:
19
19
  requirements:
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: "0"
22
+ version: 1.5.0.3
23
23
  version:
24
24
  - !ruby/object:Gem::Dependency
25
- name: fiveruns-starling
25
+ name: starling-starling
26
26
  version_requirement:
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: "0"
31
+ version: 0.9.8
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-mdns
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: "0.4"
32
41
  version:
33
42
  description: Algorithms and Tools for Distributed Computing in Ruby.
34
43
  email: mperham@gmail.com
@@ -41,17 +50,18 @@ extra_rdoc_files:
41
50
  - History.rdoc
42
51
  - LICENSE
43
52
  files:
44
- - README.rdoc
45
- - LICENSE
46
- - History.rdoc
47
- - Rakefile
48
53
  - lib/init.rb
49
54
  - lib/politics
50
- - lib/politics/bucket_worker.rb
51
55
  - lib/politics/discoverable_node.rb
56
+ - lib/politics/static_queue_worker.rb
52
57
  - lib/politics/token_worker.rb
53
58
  - lib/politics/version.rb
54
59
  - lib/politics.rb
60
+ - examples/queue_worker_example.rb
61
+ - examples/token_worker_example.rb
62
+ - README.rdoc
63
+ - History.rdoc
64
+ - LICENSE
55
65
  has_rdoc: true
56
66
  homepage: http://github.com/mperham/politics/
57
67
  post_install_message:
@@ -87,7 +97,6 @@ signing_key:
87
97
  specification_version: 2
88
98
  summary: Algorithms and Tools for Distributed Computing in Ruby.
89
99
  test_files:
90
- - test/bucket_worker_test.rb
91
- - test/political_test.rb
100
+ - test/static_queue_worker_test.rb
92
101
  - test/test_helper.rb
93
102
  - test/token_worker_test.rb
data/Rakefile DELETED
@@ -1,34 +0,0 @@
1
- require 'echoe'
2
-
3
- require File.dirname(__FILE__) << "/lib/politics/version"
4
-
5
- Echoe.new 'politics' do |p|
6
- p.version = Politics::Version::STRING
7
- p.author = "Mike Perham"
8
- p.email = 'mperham@gmail.com'
9
- p.project = 'politics'
10
- p.summary = "Algorithms and Tools for Distributed Computing in Ruby."
11
- p.url = "http://github.com/mperham/politics"
12
- p.dependencies = %w(memcache-client)
13
- p.development_dependencies = []
14
- p.include_rakefile = true
15
- p.rubygems_version = nil
16
- end
17
-
18
-
19
- require 'rake/testtask'
20
-
21
- desc "Run tests"
22
- Rake::TestTask.new do |t|
23
- t.libs << ['test', 'lib']
24
- t.test_files = FileList['test/*_test.rb']
25
- end
26
-
27
- desc "Create rdoc"
28
- Rake::RDocTask.new do |rd|
29
- rd.main = "README.rdoc"
30
- rd.rdoc_files.include("README.rdoc", "History.rdoc", "lib/**/*.rb")
31
- end
32
-
33
-
34
- task :default => :test
@@ -1,107 +0,0 @@
1
- begin
2
- require 'starling'
3
- rescue LoadError => e
4
- puts "Unable to load starling, please run `sudo gem install starling`: #{e.message}"
5
- exit(1)
6
- end
7
-
8
- module Politics
9
-
10
- # The BucketWorker mixin allows a processing daemon to "lease" or checkout
11
- # a portion of a problem space to ensure no other process is processing that same
12
- # space at the same time. The processing space is cut into N "buckets", each of
13
- # which is placed in a queue. Processes then fetch entries from the queue
14
- # and process them. It is up to the application to map the bucket number onto its
15
- # specific problem space.
16
- #
17
- # Note that the Starling queue server is the single point of failure with this
18
- # mechanism. Only you can decide if this is an acceptable tradeoff for your needs.
19
- #
20
- # Example usage:
21
- #
22
- # class Analyzer
23
- # include Politics::BucketWorker
24
- # TOTAL_BUCKETS = 16
25
- #
26
- # def start
27
- # register_worker(self.class.name)
28
- # create_buckets(TOTAL_BUCKETS) if master?
29
- # process_bucket do |bucket|
30
- # puts "Analyzing bucket #{bucket} of #{TOTAL_BUCKETS}"
31
- # sleep 5
32
- # end
33
- # end
34
- #
35
- # def master?
36
- # # TODO Add your own logic here to denote a 'master' process
37
- # ARGV.include? '-m'
38
- # end
39
- # end
40
- #
41
- # Note: process_bucket never returns i.e. this should be the main loop of your processing daemon.
42
- #
43
- module BucketWorker
44
-
45
- def self.included(model) #:nodoc:
46
- model.class_eval do
47
- attr_accessor :starling_client, :bucket_count, :queue_name
48
- end
49
- end
50
-
51
- # Register this process as able to work on buckets.
52
- #
53
- # +name+:: The name of the queue to access
54
- # +servers+:: The starling server(s) to use, defaults to +['localhost:22122']+
55
- def register_worker(name, servers=['localhost:22122'])
56
- self.queue_name = name
57
- self.starling_client = client_for(Array(servers))
58
- end
59
-
60
- # Create the given number of buckets. Should ONLY be called by a single worker.
61
- # TODO Obviously a major weakness of this algorithm. Is there a cleaner way?
62
- #
63
- # +bucket_count+:: The number of buckets to create
64
- def create_buckets(bucket_count)
65
- starling_client.flush(queue_name)
66
- bucket_count.times do |count|
67
- starling_client.set(queue_name, count)
68
- end
69
- end
70
-
71
- # Fetch a bucket out of the queue and pass it to the given block to be processed.
72
- # Once processing has completed, it will put the bucket back onto the queue for processing
73
- # by a BucketWorker again, possibly immediately, depending on the number of buckets vs
74
- # number of workers.
75
- #
76
- # +bucket+:: The bucket number to process, within the range 0...TOTAL_BUCKETS
77
- def process_bucket
78
- raise ArgumentError, "process_bucket requires a block!" unless block_given?
79
- raise ArgumentError, "You must call register_worker before processing!" unless starling_client
80
-
81
- begin
82
- bucket = get_bucket
83
- yield bucket
84
- ensure
85
- push_bucket(bucket)
86
- end while loop?
87
- end
88
-
89
- private
90
-
91
- def get_bucket
92
- starling_client.get(queue_name)
93
- end
94
-
95
- def push_bucket(bucket)
96
- starling_client.set(queue_name, bucket)
97
- end
98
-
99
- def client_for(servers)
100
- Starling.new(servers)
101
- end
102
-
103
- def loop?
104
- true
105
- end
106
- end
107
- end
@@ -1,51 +0,0 @@
1
- require 'test_helper'
2
-
3
- class BucketWorkerTest < Test::Unit::TestCase
4
-
5
- context "bucket workers" do
6
- setup do
7
- @harness = Class.new
8
- @harness.send(:include, Politics::BucketWorker)
9
- @harness.any_instance.stubs(:loop?).returns(false)
10
- @worker = @harness.new
11
- end
12
-
13
- should "have instance property accessors" do
14
- assert @worker.bucket_count = 20
15
- assert_equal 20, @worker.bucket_count
16
- end
17
-
18
- should 'register correctly' do
19
- @worker.register_worker('testing')
20
- @worker.register_worker('testing', ['localhost:5555,localhost:12121'])
21
- end
22
-
23
- should 'process a bucket' do
24
- @worker.register_worker('testing')
25
- @worker.starling_client.expects(:get).returns(4)
26
- @worker.starling_client.expects(:set).with('testing', 4).returns(nil)
27
- processed = false
28
- @worker.process_bucket do |bucket|
29
- assert_equal 4, bucket
30
- processed = true
31
- end
32
- assert processed
33
- end
34
-
35
- should 'not allow processing without block' do
36
- assert_raises ArgumentError do
37
- @worker.register_worker('hello')
38
- @worker.process_bucket
39
- end
40
- end
41
-
42
- should 'not allow processing without registration' do
43
- assert_raises ArgumentError do
44
- @worker.process_bucket do
45
- fail 'Should not process!'
46
- end
47
- end
48
- end
49
-
50
- end
51
- end
@@ -1,32 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'shoulda'
4
- require File.dirname(__FILE__) + '/../lib/init'
5
-
6
- Thread.abort_on_exception = true
7
-
8
- class PoliticalTest < Test::Unit::TestCase
9
-
10
- context "nodes" do
11
- setup do
12
- @nodes = []
13
- 5.times do
14
- Object.send(:include, Politics::DiscoverableNode)
15
- node = Object.new
16
- @nodes << node
17
- end
18
- end
19
-
20
- should "start up" do
21
- processes = @nodes.map do |node|
22
- fork do
23
- ['INT', 'TERM'].each { |signal|
24
- trap(signal) { exit(0) }
25
- }
26
- node.register
27
- end
28
- end
29
- Process.wait
30
- end
31
- end
32
- end