mperham-politics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = Changelog
2
+
3
+ == 0.1.0 (2008-10-07)
4
+
5
+ * Add BucketWorker and TokenWorker mixins.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Mike Perham
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,39 @@
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 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.
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+ and +PaxosMember+ mixins.
25
+
26
+ == Dependencies
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.
30
+
31
+
32
+ = Author
33
+
34
+ Name:: Mike Perham
35
+ Email:: mailto:mperham@gmail.com
36
+ Twitter:: http://twitter.com/mperham
37
+
38
+ This software is free for you to use as you'd like. If you find it useful, please consider giving
39
+ me a recommendation at {Working with Rails}[http://workingwithrails.com/person/10797-mike-perham].
data/Rakefile ADDED
@@ -0,0 +1,34 @@
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
data/lib/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'politics'
2
+ require 'politics/token_worker'
3
+ require 'politics/bucket_worker'
4
+ require 'politics/discoverable_node'
@@ -0,0 +1,107 @@
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
@@ -0,0 +1,137 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+ require 'uri'
4
+ require 'drb'
5
+
6
+ require 'net/dns/mdns-sd'
7
+ require 'net/dns/resolv-mdns'
8
+ require 'net/dns/resolv-replace'
9
+
10
+ =begin
11
+ IRB setup:
12
+ require 'lib/politics'
13
+ require 'lib/politics/discoverable_node'
14
+ require 'lib/politics/convention'
15
+ Object.send(:include, Election::Candidate)
16
+ p = Object.new
17
+ p.register
18
+ =end
19
+
20
+ module Politics
21
+
22
+ # A module to solve the Group Membership problem in distributed computing.
23
+ # The "group" is the cloud of processes which are replicas and need to coordinate.
24
+ # Handling group membership is the first step in solving distributed computing
25
+ # problems. There are two issues:
26
+ # 1) replica discovery
27
+ # 2) controlling and maintaining a consistent group of replicas in each replica
28
+ #
29
+ # Peer discovery is implemented using Bonjour for local network auto-discovery.
30
+ # Each process registers itself on the network as a process of a given type.
31
+ # Each process then queries the network for other replicas of the same type.
32
+ #
33
+ # The replicas then run the Multi-Paxos algorithm to provide consensus on a given
34
+ # replica set. The algorithm is robust in the face of crash failures, but not
35
+ # Byzantine failures.
36
+ module DiscoverableNode
37
+
38
+ attr_accessor :group
39
+ attr_accessor :coordinator
40
+
41
+ def register(group='foo')
42
+ self.group = group
43
+ start_drb
44
+ register_with_bonjour(group)
45
+ Politics.log "Registered #{self} in group #{group} with RID #{rid}"
46
+ sleep 0.5
47
+ find_replicas(0)
48
+ end
49
+
50
+ def replicas
51
+ @replicas ||= {}
52
+ end
53
+
54
+ def find_replicas(count)
55
+ replicas.clear if count % 5 == 0
56
+ return if count > 10 # Guaranteed to terminate, but not successfully :-(
57
+
58
+ #puts "Finding replicas"
59
+ peer_set = []
60
+ bonjour_scan do |replica|
61
+ (his_rid, his_peers) = replica.hello(rid)
62
+ unless replicas.has_key?(his_rid)
63
+ replicas[his_rid] = replica
64
+ end
65
+ his_peers.each do |peer|
66
+ peer_set << peer unless peer_set.include? peer
67
+ end
68
+ end
69
+ #p [peer_set.sort, replicas.keys.sort]
70
+ if peer_set.sort != replicas.keys.sort
71
+ # Recursively call ourselves until the network has settled down and all
72
+ # peers have reached agreement on the peer group membership.
73
+ sleep 0.2
74
+ find_replicas(count + 1)
75
+ end
76
+ puts "Found #{replicas.size} peers: #{replicas.keys.sort.inspect}" if count == 0
77
+ replicas
78
+ end
79
+
80
+ # Called for one peer to introduce itself to another peer. The caller
81
+ # sends his RID, the responder sends his RID and his list of current peer
82
+ # RIDs.
83
+ def hello(remote_rid)
84
+ [rid, replicas.keys]
85
+ end
86
+
87
+ # A process's Replica ID is its PID + a random 16-bit value. We don't want
88
+ # weigh solely based on PID or IP as that may unduly load one machine.
89
+ def rid
90
+ @rid ||= begin
91
+ rand(65536) + $$
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def register_with_bonjour(group)
98
+ # Register our DRb server with Bonjour.
99
+ handle = Net::DNS::MDNSSD.register("#{self.group}-#{local_ip}-#{$$}",
100
+ "_#{self.group}._tcp", 'local', @port)
101
+
102
+ ['INT', 'TERM'].each { |signal|
103
+ trap(signal) { handle.stop }
104
+ }
105
+ end
106
+
107
+ def start_drb
108
+ server = DRb.start_service(nil, self)
109
+ @port = URI.parse(DRb.uri).port
110
+ ['INT', 'TERM'].each { |signal|
111
+ trap(signal) { server.stop_service }
112
+ }
113
+ end
114
+
115
+ def bonjour_scan
116
+ Net::DNS::MDNSSD.browse("_#{@group}._tcp") do |b|
117
+ Net::DNS::MDNSSD.resolve(b.name, b.type) do |r|
118
+ drburl = "druby://#{r.target}:#{r.port}"
119
+ replica = DRbObject.new(nil, drburl)
120
+ yield replica
121
+ end
122
+ end
123
+ end
124
+
125
+ # http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
126
+ def local_ip
127
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
128
+
129
+ UDPSocket.open do |s|
130
+ s.connect '64.233.187.99', 1
131
+ IPAddr.new(s.addr.last).to_i
132
+ end
133
+ ensure
134
+ Socket.do_not_reverse_lookup = orig
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,162 @@
1
+ begin
2
+ require 'memcache'
3
+ rescue LoadError => e
4
+ puts "Unable to load memcache client, please run `sudo gem install memcache-client`: #{e.message}"
5
+ exit(1)
6
+ end
7
+
8
+ module Politics
9
+
10
+ # An algorithm to provide leader election between a set of identical processing daemons.
11
+ #
12
+ # Each TokenWorker is an instance which needs to perform some processing.
13
+ # The worker instance must obtain the leader token before performing some task.
14
+ # We use a memcached server as a central token authority to provide a shared,
15
+ # network-wide view for all processors. This reliance on a single resource means
16
+ # if your memcached server goes down, so do the processors. Oftentimes,
17
+ # this is an acceptable trade-off since many high-traffic web sites would
18
+ # not be useable without memcached running anyhow.
19
+ #
20
+ # Essentially each TokenWorker attempts to elect itself every +:iteration_length+
21
+ # seconds by simply setting a key in memcached to its own name. Memcached tracks
22
+ # which name got there first. The key expires after +:iteration_length+ seconds.
23
+ #
24
+ # Example usage:
25
+ # class Analyzer
26
+ # include Politics::TokenWorker
27
+ #
28
+ # def initialize
29
+ # register_worker 'analyzer', :iteration_length => 120, :servers => ['localhost:11211']
30
+ # end
31
+ #
32
+ # def start
33
+ # process do
34
+ # # do analysis here, will only be done when this process
35
+ # # is actually elected leader, otherwise it will sleep for
36
+ # # iteration_length seconds.
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # Notes:
42
+ # * This will not work with multiple instances in the same Ruby process.
43
+ # The library is only designed to elect a leader from a set of processes, not instances within
44
+ # a single process.
45
+ # * The algorithm makes no attempt to keep the same leader during the next iteration.
46
+ # This can often times be quite beneficial (e.g. leveraging a warm cache from the last iteration)
47
+ # for performance but is left to the reader to implement.
48
+ module TokenWorker
49
+
50
+ def self.included(model) #:nodoc:
51
+ model.class_eval do
52
+ attr_accessor :memcache_client, :token, :iteration_length, :worker_name
53
+ class << self
54
+ attr_accessor :worker_instance #:nodoc:
55
+ end
56
+ end
57
+ end
58
+
59
+ # Register this instance as a worker.
60
+ #
61
+ # Options:
62
+ # +:iteration_length+:: The length of a processing iteration, in seconds. The
63
+ # leader's 'reign' lasts for this length of time.
64
+ # +:servers+:: An array of memcached server strings
65
+ def register_worker(name, config={})
66
+ # track the latest instance of this class, there's really only supposed to be
67
+ # a single TokenWorker instance per process.
68
+ self.class.worker_instance = self
69
+
70
+ options = { :iteration_length => 60, :servers => ['localhost:11211'] }
71
+ options.merge!(config)
72
+
73
+ self.token = "#{name}_token"
74
+ self.memcache_client = client_for(Array(options[:servers]))
75
+ self.iteration_length = options[:iteration_length]
76
+ self.worker_name = "#{Socket.gethostname}:#{$$}"
77
+
78
+ cleanup
79
+ end
80
+
81
+ def process(*args, &block)
82
+ verify_registration
83
+
84
+ begin
85
+ # Try to add our name as the worker with the master token.
86
+ # If another process got there first, this is a noop.
87
+ # We add an expiry so that the master token will constantly
88
+ # need to be refreshed (in case the current leader dies).
89
+ nominate
90
+
91
+ time = 0
92
+ if leader?
93
+ LOG.info { "I've been elected leader: #{worker_name}" }
94
+ # If we are the master worker, do the work.
95
+ time = time_for do
96
+ result = block.call(*args)
97
+ end
98
+ end
99
+
100
+ pause_until_expiry(time)
101
+ end while loop?
102
+ end
103
+
104
+ private
105
+
106
+ def verify_registration
107
+ unless self.class.worker_instance
108
+ raise ArgumentError, "Cannot call process without first calling register_worker"
109
+ end
110
+ unless self.class.worker_instance == self
111
+ raise SecurityError, "Only one instance of #{self.class} per process. Another instance was created after this one."
112
+ end
113
+ end
114
+
115
+ def loop?
116
+ true
117
+ end
118
+
119
+ def cleanup
120
+ at_exit do
121
+ memcache_client.delete(token)
122
+ end
123
+ end
124
+
125
+ def pause_until_expiry(elapsed)
126
+ pause_time = (iteration_length - elapsed).to_f
127
+ if pause_time > 0
128
+ relax(pause_time)
129
+ else
130
+ raise ArgumentError, "Negative iteration time left. Assuming the worst and exiting... #{iteration_length}/#{elapsed}"
131
+ end
132
+ end
133
+
134
+ def relax(time)
135
+ sleep time
136
+ end
137
+
138
+ # Nominate ourself as leader by contacting the memcached server
139
+ # and attempting to add the token with our name attached.
140
+ def nominate
141
+ memcache_client.add(token, worker_name, iteration_length)
142
+ end
143
+
144
+ # Check to see if we are leader by looking at the process name
145
+ # associated with the token.
146
+ def leader?
147
+ master_worker = memcache_client.get(token)
148
+ worker_name == master_worker
149
+ end
150
+
151
+ # Easy to mock or monkey-patch if another MemCache client is preferred.
152
+ def client_for(servers)
153
+ MemCache.new(servers)
154
+ end
155
+
156
+ def time_for(&block)
157
+ a = Time.now
158
+ yield
159
+ Time.now - a
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,5 @@
1
+ module Politics
2
+ module Version
3
+ STRING = "0.1.0"
4
+ end
5
+ end
data/lib/politics.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'logger'
2
+
3
+ module Politics
4
+ LOG = Logger.new(STDOUT)
5
+
6
+ def self.log(msg)
7
+ LOG.info(msg)
8
+ end
9
+ end
@@ -0,0 +1,51 @@
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
@@ -0,0 +1,32 @@
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
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ begin
5
+ gem 'thoughtbot-shoulda', '>=2.0.2'
6
+ require 'shoulda'
7
+ rescue GemError, LoadError => e
8
+ puts "Please install shoulda: `sudo gem install thoughtbot-shoulda -s http://gems.github.com`"
9
+ end
10
+
11
+ begin
12
+ require 'mocha'
13
+ rescue LoadError => e
14
+ puts "Please install mocha: `sudo gem install mocha`"
15
+ end
16
+
17
+ require File.dirname(__FILE__) + '/../lib/init'
18
+ Politics::LOG.level = Logger::WARN
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class TokenWorkerTest < Test::Unit::TestCase
4
+
5
+ context "token workers" do
6
+ setup do
7
+ @harness = Class.new
8
+ @harness.send(:include, Politics::TokenWorker)
9
+ @harness.any_instance.stubs(:cleanup)
10
+ @harness.any_instance.stubs(:loop?).returns(false)
11
+ @harness.any_instance.stubs(:pause_until_expiry)
12
+ @harness.any_instance.stubs(:relax)
13
+
14
+ @worker = @harness.new
15
+ end
16
+
17
+ should "test_instance_property_accessors" do
18
+ assert @worker.iteration_length = 20
19
+ assert_equal 20, @worker.iteration_length
20
+ end
21
+
22
+ should 'test_tracks_a_registered_singleton' do
23
+ assert_nil @worker.class.worker_instance
24
+ @worker.register_worker('testing')
25
+ assert_equal @worker.class.worker_instance, @worker
26
+ end
27
+
28
+ should 'not process if they are not leader' do
29
+ @worker.expects(:nominate)
30
+ @worker.expects(:leader?).returns(false)
31
+ @worker.register_worker('testing')
32
+ @worker.process do
33
+ assert false
34
+ end
35
+ end
36
+
37
+ should 'process if they are leader' do
38
+ @worker.expects(:nominate)
39
+ @worker.expects(:leader?).returns(true)
40
+ @worker.register_worker('testing')
41
+
42
+ worked = 0
43
+ @worker.process do
44
+ worked += 1
45
+ end
46
+
47
+ assert_equal 1, worked
48
+ end
49
+
50
+ should 'not allow processing without registration' do
51
+ assert_raises ArgumentError do
52
+ @worker.process
53
+ end
54
+ end
55
+
56
+ should 'not allow processing by old instances' do
57
+ @worker.register_worker('testing')
58
+
59
+ foo = @worker.class.new
60
+ foo.register_worker('testing')
61
+
62
+ assert_raises SecurityError do
63
+ @worker.process
64
+ end
65
+ end
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mperham-politics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Perham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fiveruns-memcache-client
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: fiveruns-starling
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: "0"
32
+ version:
33
+ description: Algorithms and Tools for Distributed Computing in Ruby.
34
+ email: mperham@gmail.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ - History.rdoc
42
+ - LICENSE
43
+ files:
44
+ - README.rdoc
45
+ - LICENSE
46
+ - History.rdoc
47
+ - Rakefile
48
+ - lib/init.rb
49
+ - lib/politics
50
+ - lib/politics/bucket_worker.rb
51
+ - lib/politics/discoverable_node.rb
52
+ - lib/politics/token_worker.rb
53
+ - lib/politics/version.rb
54
+ - lib/politics.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/mperham/politics/
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --quiet
60
+ - --title
61
+ - Politics documentation
62
+ - --opname
63
+ - index.html
64
+ - --line-numbers
65
+ - --main
66
+ - README.rdoc
67
+ - --inline-source
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.2.0
86
+ signing_key:
87
+ specification_version: 2
88
+ summary: Algorithms and Tools for Distributed Computing in Ruby.
89
+ test_files:
90
+ - test/bucket_worker_test.rb
91
+ - test/political_test.rb
92
+ - test/test_helper.rb
93
+ - test/token_worker_test.rb