cloud-crowd 0.7.3 → 0.7.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cca46d87f48a34b124b8b581e0f65f3d68b5595b
4
- data.tar.gz: d0b1df360b9a5cd87bb3183b3c49480997890e71
3
+ metadata.gz: 82aa3ec5f4de9712060de2ba1f62207cc4c9d57c
4
+ data.tar.gz: 8129d294659a958f2ae1031dba8f4b507d064d03
5
5
  SHA512:
6
- metadata.gz: 1cf7261ba8b1333302a42793a0d2c18f4c00432a5e2b58c6ad3f01526895e985b8c82da1779d96bb400c1c182529f8e3150a82221a2543141d296c9e1e977b82
7
- data.tar.gz: bebaf475fcc3ccce3d96db36658dc7d16e6fb3b9378788ae2c3623953d646792484cef5c3a562e3fdad3b97882244830419d0da93e5d09f87212a9a4738f1aac
6
+ metadata.gz: 45c9bf5f14369c0b9d30d8eb2e6cc3dcbf6bcf046ccff6e6262682ba94b5bb26b3d23fd4fe4c8c1e7f7a980146abad9f12d5fa3baeeace408e6f2e6ca9db0c38
7
+ data.tar.gz: a4c1687bf3c7ada3354c30a19eaa48d8709f1c2a9bd3b874e4d823519ee3201600d9f20607d003dc7430c188395474d0ffe52d0e36e900e8f2c1c530b96b1111
@@ -1,7 +1,11 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'cloud_crowd/version'
4
+
1
5
  Gem::Specification.new do |s|
2
6
  s.name = 'cloud-crowd'
3
- s.version = '0.7.3' # Keep version in sync with cloud-cloud.rb
4
- s.date = '2014-05-06'
7
+ s.version = CloudCrowd::VERSION
8
+ s.date = CloudCrowd::VERSION_RELEASED
5
9
 
6
10
  s.homepage = "http://wiki.github.com/documentcloud/cloud-crowd"
7
11
  s.summary = "Parallel Processing for the Rest of Us"
@@ -25,6 +25,7 @@ autoload :YAML, 'yaml'
25
25
  # Common code which should really be required in every circumstance.
26
26
  require 'socket'
27
27
  require 'net/http'
28
+ require 'cloud_crowd/version'
28
29
  require 'cloud_crowd/exceptions'
29
30
  require 'rest_client'
30
31
  require 'pathname'
@@ -45,9 +46,7 @@ module CloudCrowd
45
46
  autoload :Server, 'cloud_crowd/server'
46
47
  autoload :Worker, 'cloud_crowd/worker'
47
48
  autoload :WorkUnit, 'cloud_crowd/models'
48
-
49
- # Keep this version in sync with the gemspec.
50
- VERSION = '0.7.2'
49
+ autoload :Dispatcher, 'cloud_crowd/dispatcher'
51
50
 
52
51
  # Increment the schema version when there's a backwards incompatible change.
53
52
  SCHEMA_VERSION = 4
@@ -113,7 +112,11 @@ module CloudCrowd
113
112
  configuration = YAML.load(ERB.new(File.read(config_path)).result)
114
113
  ActiveRecord::Base.establish_connection(configuration)
115
114
  if validate_schema
116
- version = ActiveRecord::Base.connection.select_values('select max(version) from schema_migrations').first.to_i
115
+ begin
116
+ version = ActiveRecord::Base.connection.select_values('select max(version) from schema_migrations').first.to_i
117
+ rescue
118
+ version = 0
119
+ end
117
120
  return true if version == SCHEMA_VERSION
118
121
  puts "Your database schema is out of date. Please use `crowd load_schema` to update it. This will wipe all the tables, so make sure that your jobs have a chance to finish first.\nexiting..."
119
122
  exit
@@ -208,6 +211,12 @@ module CloudCrowd
208
211
  @identity == :node
209
212
  end
210
213
 
214
+ # Output a message with the current Timestamp prepended.
215
+ # Sinatra will re-direct stdout to a log file located at "log_path"
216
+ def log(message)
217
+ printf("%-20s %s\n", Time.now.strftime("%F-%T:"), message)
218
+ end
219
+
211
220
  end
212
221
 
213
222
  end
@@ -83,7 +83,7 @@ Options:
83
83
  pid_path = CloudCrowd.pid_path('server.pid')
84
84
  rackup_path = File.expand_path("#{@options[:config_path]}/config.ru")
85
85
  FileUtils.mkdir_p(CloudCrowd.log_path) if @options[:daemonize] && !File.exists?(CloudCrowd.log_path)
86
- puts "Starting CloudCrowd Central Server on port #{port}..."
86
+ puts "Starting CloudCrowd Central Server (#{VERSION}) on port #{port}..."
87
87
  exec "thin -e #{@options[:environment]} -p #{port} #{daemonize} --tag cloud-crowd-server --log #{log_path} --pid #{pid_path} -R #{rackup_path} start"
88
88
  end
89
89
 
@@ -114,7 +114,7 @@ Options:
114
114
  # will be long-lived, although its workers will come and go.
115
115
  def start_node
116
116
  @options[:port] ||= Node::DEFAULT_PORT
117
- puts "Starting CloudCrowd Node on port #{@options[:port]}..."
117
+ puts "Starting CloudCrowd Node (#{VERSION}) on port #{@options[:port]}..."
118
118
  Node.new(@options)
119
119
  end
120
120
 
@@ -242,4 +242,4 @@ Options:
242
242
  end
243
243
 
244
244
  end
245
- end
245
+ end
@@ -0,0 +1,52 @@
1
+ module CloudCrowd
2
+
3
+ # The dispatcher is responsible for distributing work_units
4
+ # to the worker nodes.
5
+ #
6
+ # It automatically performs the distribution on a set schedule,
7
+ # but can also be signaled to perform distribution immediately
8
+ class Dispatcher
9
+
10
+ # Starts distributing jobs every "distribution_interval" seconds
11
+ def initialize(distribution_interval)
12
+ @mutex = Mutex.new
13
+ @awaken = ConditionVariable.new
14
+ distribute_periodically(distribution_interval)
15
+ end
16
+
17
+ # Sends a signal to the distribution thread.
18
+ # If it's asleep, it will wake up and perform a distribution.
19
+ def distribute!
20
+ @mutex.synchronize do
21
+ @awaken.signal
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Perform distribution of work units in a background thread
28
+ def distribute_periodically(interval)
29
+ Thread.new{
30
+ loop do
31
+ perform_distribution
32
+ # Sleep for "interval" seconds.
33
+ # If awaken isn't signaled, timeout and attempt distribution
34
+ @mutex.synchronize do
35
+ @awaken.wait(@mutex, interval)
36
+ end
37
+ end
38
+ }
39
+ end
40
+
41
+ def perform_distribution
42
+ #CloudCrowd.log "Distributing jobs to nodes"
43
+ begin
44
+ WorkUnit.distribute_to_nodes
45
+ rescue StandardError => e
46
+ CloudCrowd.log "Exception: #{e}"
47
+ CloudCrowd.log e.backtrace
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -53,7 +53,7 @@ module CloudCrowd
53
53
  return queue_for_workers([outs]) if merging?
54
54
  if complete?
55
55
  update_attributes(:outputs => outs, :time => time_taken)
56
- puts "Job ##{id} (#{action}) #{display_status}." unless ENV['RACK_ENV'] == 'test'
56
+ CloudCrowd.log "Job ##{id} (#{action}) #{display_status}." unless ENV['RACK_ENV'] == 'test'
57
57
  CloudCrowd.defer { fire_callback } if callback_url
58
58
  end
59
59
  self
@@ -82,7 +82,7 @@ module CloudCrowd
82
82
  response = RestClient.post(callback_url, {:job => self.to_json})
83
83
  CloudCrowd.defer { self.destroy } if response && response.code == 201
84
84
  rescue RestClient::Exception => e
85
- puts "Job ##{id} (#{action}) failed to fire callback: #{callback_url}"
85
+ CloudCrowd.log "Job ##{id} (#{action}) failed to fire callback: #{callback_url}"
86
86
  end
87
87
  end
88
88
 
@@ -32,7 +32,13 @@ module CloudCrowd
32
32
  :max_workers => params[:max_workers],
33
33
  :enabled_actions => params[:enabled_actions]
34
34
  }
35
- self.find_or_create_by(:host => params[:host]).update_attributes!(attrs)
35
+ host_attr = {:host => params[:host]}
36
+ if (record = where(host_attr).first)
37
+ record.update_attributes!(attrs)
38
+ record
39
+ else
40
+ create!(attrs.merge(host_attr))
41
+ end
36
42
  end
37
43
 
38
44
  # Dispatch a WorkUnit to this node. Places the node at back at the end of
@@ -46,14 +52,14 @@ module CloudCrowd
46
52
  touch && true
47
53
  rescue RestClient::RequestTimeout
48
54
  # The node's gone away. Destroy it and it will check in when it comes back
49
- puts "Node #{host} received RequestTimeout, removing it"
55
+ CloudCrowd.log "Node #{host} received RequestTimeout, removing it"
50
56
  destroy && false
51
57
  rescue RestClient::RequestFailed => e
52
58
  raise e unless e.http_code == 503 && e.http_body == Node::OVERLOADED_MESSAGE
53
59
  update_attribute(:busy, true) && false
54
60
  rescue RestClient::Exception, Errno::ECONNREFUSED, Timeout::Error, Errno::ECONNRESET=>e
55
61
  # Couldn't post to node, assume it's gone away.
56
- puts "Node #{host} received #{e.class} #{e}, removing it"
62
+ CloudCrowd.log "Node #{host} received #{e.class} #{e}, removing it"
57
63
  destroy && false
58
64
  end
59
65
 
@@ -121,7 +127,6 @@ module CloudCrowd
121
127
  # Redistribute in a separate thread to avoid delaying shutdown.
122
128
  def redistribute_work_units
123
129
  release_work_units
124
- CloudCrowd.defer { WorkUnit.distribute_to_nodes }
125
130
  end
126
131
 
127
132
  end
@@ -79,7 +79,7 @@ module CloudCrowd
79
79
  reservation = SecureRandom.random_number(MAX_RESERVATION)
80
80
  conditions = "reservation is null and node_record_id is null and status in (#{INCOMPLETE.join(',')}) and #{options[:conditions]}"
81
81
  query = WorkUnit.where(conditions)
82
- query.limit(options[:limit]) if options[:limit]
82
+ query = query.limit(options[:limit]) if options[:limit]
83
83
  any = query.update_all("reservation = #{reservation}") > 0
84
84
  any && reservation
85
85
  end
@@ -59,7 +59,8 @@ module CloudCrowd
59
59
  throw :halt, [503, OVERLOADED_MESSAGE] if @overloaded
60
60
  unit = JSON.parse(params[:work_unit])
61
61
  pid = fork { Worker.new(self, unit).run }
62
- Process.detach(pid)
62
+ thread = Process.detach(pid)
63
+ track_work(unit["id"], thread)
63
64
  json :pid => pid
64
65
  end
65
66
 
@@ -78,6 +79,7 @@ module CloudCrowd
78
79
  @overloaded = false
79
80
  @max_load = CloudCrowd.config[:max_load]
80
81
  @min_memory = CloudCrowd.config[:min_free_memory]
82
+ @work = {}
81
83
  start unless ENV['RACK_ENV'] == 'test'
82
84
  end
83
85
 
@@ -92,7 +94,7 @@ module CloudCrowd
92
94
  @server.daemonize if @daemon
93
95
  trap_signals
94
96
  asset_store
95
- @server_thread = CloudCrowd.defer { @server.start }
97
+ @server_thread = Thread.new { @server.start }
96
98
  check_in(true)
97
99
  check_in_periodically
98
100
  monitor_system if @max_load || @min_memory
@@ -110,7 +112,7 @@ module CloudCrowd
110
112
  :enabled_actions => @enabled_actions.join(',')
111
113
  )
112
114
  rescue RestClient::Exception, Errno::ECONNREFUSED
113
- puts "Failed to connect to the central server (#{@central.to_s})."
115
+ CloudCrowd.log "Failed to connect to the central server (#{@central.to_s})."
114
116
  raise SystemExit if critical
115
117
  end
116
118
 
@@ -151,15 +153,44 @@ module CloudCrowd
151
153
  raise NotImplementedError, "'min_free_memory' is not yet implemented on your platform"
152
154
  end
153
155
  end
156
+
157
+ def track_work(id, thread)
158
+ @work[id] = { thread: thread, start: Time.now }
159
+ end
154
160
 
155
-
161
+ def check_on_workers
162
+ # ToDo, this isn't really thread safe.
163
+ # there are events in which a job completes and exits successfully
164
+ # while iteration here is taking place. However the interleaving
165
+ # is such that the work unit should be complete / cleaned up already
166
+ # even in the event that a thread is flagged as dead here.
167
+ @work.each do |unit_id, work|
168
+ unless work[:thread].alive?
169
+ CloudCrowd.log "Notifying central server that worker #{work[:thread].pid} for unit #{unit_id} mysteriously died."
170
+ data = {
171
+ id: unit_id,
172
+ pid: work[:thread].pid,
173
+ status: 'failed',
174
+ output: { output: "Worker thread #{work[:thread].pid} died on #{host} prior to #{Time.now}" }.to_json,
175
+ time: Time.now - work[:start] # this is time until failure was noticed
176
+ }
177
+ @central["/work/#{unit_id}"].put(data)
178
+ resolve_work(unit_id)
179
+ end
180
+ end
181
+ end
182
+
183
+ def resolve_work(unit_id)
184
+ @work.delete(unit_id)
185
+ end
186
+
156
187
  private
157
188
 
158
189
  # Launch a monitoring thread that periodically checks the node's load
159
190
  # average and the amount of free memory remaining. If we transition out of
160
191
  # the overloaded state, let central know.
161
192
  def monitor_system
162
- @monitor_thread = CloudCrowd.defer do
193
+ @monitor_thread = Thread.new do
163
194
  loop do
164
195
  was_overloaded = @overloaded
165
196
  @overloaded = overloaded?
@@ -173,8 +204,9 @@ module CloudCrowd
173
204
  # will assume that the node has gone down. Checking in will let central know
174
205
  # it's still online.
175
206
  def check_in_periodically
176
- @check_in_thread = CloudCrowd.defer do
207
+ @check_in_thread = Thread.new do
177
208
  loop do
209
+ check_on_workers
178
210
  reply = ""
179
211
  1.upto(5).each do | attempt_number |
180
212
  # sleep for an ever increasing amount of time to prevent overloading the server
@@ -183,13 +215,13 @@ module CloudCrowd
183
215
  # if we did not receive a reply, the server has went away; it
184
216
  # will reply with an empty string if the check-in succeeds
185
217
  if reply.nil?
186
- puts "Failed on attempt # #{attempt_number} to check in with server"
218
+ CloudCrowd.log "Failed on attempt ##{attempt_number} to check in with server"
187
219
  else
188
220
  break
189
221
  end
190
222
  end
191
223
  if reply.nil?
192
- puts "Giving up after repeated attempts to contact server"
224
+ CloudCrowd.log "Giving up after repeated attempts to contact server"
193
225
  raise SystemExit
194
226
  end
195
227
  end
@@ -200,7 +232,6 @@ module CloudCrowd
200
232
  def trap_signals
201
233
  Signal.trap('QUIT') { shut_down }
202
234
  Signal.trap('INT') { shut_down }
203
- Signal.trap('KILL') { shut_down }
204
235
  Signal.trap('TERM') { shut_down }
205
236
  end
206
237
 
@@ -20,6 +20,10 @@ module CloudCrowd
20
20
  class Server < Sinatra::Base
21
21
  use ActiveRecord::ConnectionAdapters::ConnectionManagement
22
22
 
23
+ # The interval (in seconds) at which the server will distribute
24
+ # new work units to the nodes
25
+ DISTRIBUTE_INTERVAL=20
26
+
23
27
  set :root, ROOT
24
28
  set :authorization_realm, "CloudCrowd"
25
29
 
@@ -54,11 +58,6 @@ module CloudCrowd
54
58
  `tail -n 100 #{CloudCrowd.log_path('server.log')}`
55
59
  end
56
60
 
57
- # Get the JSON for what a worker is up to.
58
- get '/worker/:name' do
59
- json WorkUnit.find_by_worker_name(params[:name]) || {}
60
- end
61
-
62
61
  # To monitor the central server with Monit, God, Nagios, or another
63
62
  # monitoring tool, you can hit /heartbeat to make sure.
64
63
  get '/heartbeat' do
@@ -71,8 +70,8 @@ module CloudCrowd
71
70
  # Distributes all work units to available nodes.
72
71
  post '/jobs' do
73
72
  job = Job.create_from_request(JSON.parse(params[:job]))
74
- CloudCrowd.defer { WorkUnit.distribute_to_nodes }
75
- puts "Job ##{job.id} (#{job.action}) started." unless ENV['RACK_ENV'] == 'test'
73
+ CloudCrowd.log("Job ##{job.id} (#{job.action}) started.") unless ENV['RACK_ENV'] == 'test'
74
+ @dispatcher.distribute!
76
75
  json job
77
76
  end
78
77
 
@@ -95,11 +94,8 @@ module CloudCrowd
95
94
  # configuration with the central server. Triggers distribution of WorkUnits.
96
95
  put '/node/:host' do
97
96
  NodeRecord.check_in(params, request)
98
- puts "Node #{params[:host]} checked in."
99
- CloudCrowd.defer do
100
- sleep 15 # Give the new node awhile to start listening
101
- WorkUnit.distribute_to_nodes
102
- end
97
+ CloudCrowd.log "Node #{params[:host]} checked in."
98
+ @dispatcher.distribute!
103
99
  json nil
104
100
  end
105
101
 
@@ -107,7 +103,8 @@ module CloudCrowd
107
103
  # WorkUnits it may have had checked out.
108
104
  delete '/node/:host' do
109
105
  NodeRecord.destroy_all(:host => params[:host])
110
- puts "Node #{params[:host]} checked out."
106
+ CloudCrowd.log "Node #{params[:host]} checked out."
107
+ @dispatcher.distribute!
111
108
  json nil
112
109
  end
113
110
 
@@ -115,20 +112,22 @@ module CloudCrowd
115
112
  # they mark it back on the central server and exit. Triggers distribution
116
113
  # of pending work units.
117
114
  put '/work/:work_unit_id' do
115
+ CloudCrowd.log "Job #{current_work_unit.job_id} WorkUnit #{current_work_unit.id} #{current_work_unit.action} #{params[:status]} in #{params[:time]}"
118
116
  case params[:status]
119
117
  when 'succeeded' then current_work_unit.finish(params[:output], params[:time])
120
118
  when 'failed' then current_work_unit.fail(params[:output], params[:time])
121
119
  else error(500, "Completing a work unit must specify status.")
122
120
  end
123
- CloudCrowd.defer { WorkUnit.distribute_to_nodes }
124
-
121
+ @dispatcher.distribute!
125
122
  json nil
126
123
  end
127
124
 
128
125
  # At initialization record the identity of this Ruby instance as a server.
129
126
  def initialize(*args)
127
+ CloudCrowd.log "Starting server"
130
128
  super(*args)
131
129
  CloudCrowd.identity = :server
130
+ @dispatcher = Dispatcher.new(DISTRIBUTE_INTERVAL)
132
131
  end
133
132
 
134
133
  end
@@ -0,0 +1,4 @@
1
+ module CloudCrowd
2
+ VERSION = '0.7.5'
3
+ VERSION_RELEASED = '2015-06-17'
4
+ end
@@ -89,6 +89,7 @@ module CloudCrowd
89
89
  action.cleanup_work_directory if action
90
90
  fail_work_unit(e)
91
91
  end
92
+ @node.resolve_work(@unit['id'])
92
93
  end
93
94
 
94
95
  # Run this worker inside of a fork. Attempts to exit cleanly.
@@ -141,7 +142,6 @@ module CloudCrowd
141
142
  def trap_signals
142
143
  Signal.trap('QUIT') { Process.exit! }
143
144
  Signal.trap('INT') { Process.exit! }
144
- Signal.trap('KILL') { Process.exit! }
145
145
  Signal.trap('TERM') { Process.exit! }
146
146
  end
147
147
 
@@ -4,9 +4,10 @@ require 'test_helper'
4
4
  class FailingWorkUnitsTest < Minitest::Test
5
5
 
6
6
  should "retry work units when they fail" do
7
- WorkUnit.expects(:distribute_to_nodes).returns(true)
7
+ WorkUnit.stubs(:distribute_to_nodes).returns([])
8
+ Dispatcher.any_instance.stubs(:distribute_periodically)
9
+ Dispatcher.any_instance.expects(:distribute!)
8
10
  browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::Server))
9
-
10
11
  browser.post '/jobs', :job => {
11
12
  'action' => 'failure_testing',
12
13
  'inputs' => ['one', 'two', 'three'],
@@ -11,10 +11,13 @@ class ServerTest < Minitest::Test
11
11
  context "The CloudCrowd::Server (Sinatra)" do
12
12
 
13
13
  setup do
14
+ WorkUnit.stubs(:distribute_to_nodes).returns([])
15
+ Dispatcher.any_instance.stubs(:distribute_periodically)
14
16
  Job.destroy_all
15
17
  2.times { Job.make! }
16
18
  end
17
19
 
20
+
18
21
  should "set the identity of the Ruby instance" do
19
22
  app.new
20
23
  assert CloudCrowd.server?
@@ -37,7 +40,7 @@ class ServerTest < Minitest::Test
37
40
  end
38
41
 
39
42
  should "be able to create a job" do
40
- WorkUnit.expects(:distribute_to_nodes).returns(true)
43
+ Dispatcher.any_instance.expects(:distribute!)
41
44
  post('/jobs', :job => '{"action":"graphics_magick","inputs":["http://www.google.com/"]}')
42
45
  assert last_response.ok?
43
46
  job_info = JSON.parse(last_response.body)
@@ -5,7 +5,8 @@ class WordCountTest < Minitest::Test
5
5
  context "the word_count action" do
6
6
 
7
7
  setup do
8
- WorkUnit.expects(:distribute_to_nodes).returns(true)
8
+ Dispatcher.any_instance.expects(:distribute!)
9
+ Dispatcher.any_instance.stubs(:distribute_periodically)
9
10
  @asset_store = AssetStore.new
10
11
  @browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::Server))
11
12
  @browser.put('/worker', :name => 'test_worker', :thread_status => 'sleeping')
@@ -16,14 +17,13 @@ class WordCountTest < Minitest::Test
16
17
  should "be able to create a word_count job" do
17
18
  assert @browser.last_response.ok?
18
19
  info = JSON.parse(@browser.last_response.body)
19
- assert info['status'] == 'processing'
20
- assert info['work_units'] == 1
20
+ assert_equal 'processing', info['status']
21
+ assert_equal 1, info['work_units']
21
22
  end
22
23
 
23
24
  should "be able to perform the processing stage of a word_count" do
24
25
  action = CloudCrowd.actions['word_count'].new(1, "file://#{File.expand_path(__FILE__)}", {}, @asset_store)
25
- count = action.process
26
- assert count == 101
26
+ assert_equal 96, action.process
27
27
  end
28
28
 
29
29
  end
@@ -19,10 +19,25 @@ CloudCrowd.configure_database(here + '/config/database.yml')
19
19
 
20
20
  require "#{CloudCrowd::ROOT}/test/blueprints.rb"
21
21
 
22
+
23
+ module TestHelpers
24
+ def setup
25
+ CloudCrowd::WorkUnit.stubs(:distribute_to_nodes).returns([])
26
+ CloudCrowd.stubs(:log)
27
+ super
28
+ end
29
+ def teardown
30
+ Mocha::Mockery.instance.teardown
31
+ Mocha::Mockery.reset_instance
32
+ super
33
+ end
34
+ end
35
+
22
36
  class Minitest::Test
23
- include CloudCrowd
37
+ include TestHelpers
24
38
  include Shoulda::Matchers::ActiveRecord
25
39
  extend Shoulda::Matchers::ActiveRecord
26
40
  include Shoulda::Matchers::ActiveModel
27
41
  extend Shoulda::Matchers::ActiveModel
42
+ include CloudCrowd
28
43
  end
@@ -77,6 +77,7 @@ class JobTest < Minitest::Test
77
77
  Job.any_instance.stubs(:fire_callback).returns(true)
78
78
  Job.any_instance.expects(:fire_callback)
79
79
  @job.work_units.first.finish('{"output":"output"}', 10)
80
+ sleep 0.5 # block to allow Crowd.defer thread to execute
80
81
  assert @job.all_work_units_complete?
81
82
  end
82
83
 
@@ -39,6 +39,24 @@ class NodeRecordTest < Minitest::Test
39
39
  assert !!URI.parse(@node.url)
40
40
  end
41
41
 
42
+ should "be able to check-in and be updated" do
43
+ request = Rack::Request.new({'REMOTE_ADDR'=>'127.0.0.1'})
44
+ node_data = {
45
+ :ip_address => '127.0.0.1',
46
+ :host => "hostname-42:6032",
47
+ :busy => false,
48
+ :max_workers => 3,
49
+ :enabled_actions => 'graphics_magick,word_count'
50
+ }
51
+ node_data[:host] << ':6093'
52
+ record = NodeRecord.check_in( node_data, request )
53
+ assert_equal '127.0.0.1', record.ip_address
54
+ assert_equal 3, record.max_workers
55
+ node_data[:max_workers] = 2
56
+ updated_record = NodeRecord.check_in( node_data, request )
57
+ assert_equal updated_record, record
58
+ assert_equal 2, updated_record.max_workers
59
+ end
42
60
  end
43
61
 
44
62
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud-crowd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Ashkenas
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-05-06 00:00:00.000000000 Z
13
+ date: 2015-06-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -214,6 +214,7 @@ files:
214
214
  - lib/cloud_crowd/asset_store/filesystem_store.rb
215
215
  - lib/cloud_crowd/asset_store/s3_store.rb
216
216
  - lib/cloud_crowd/command_line.rb
217
+ - lib/cloud_crowd/dispatcher.rb
217
218
  - lib/cloud_crowd/exceptions.rb
218
219
  - lib/cloud_crowd/helpers.rb
219
220
  - lib/cloud_crowd/helpers/authorization.rb
@@ -226,6 +227,7 @@ files:
226
227
  - lib/cloud_crowd/node.rb
227
228
  - lib/cloud_crowd/schema.rb
228
229
  - lib/cloud_crowd/server.rb
230
+ - lib/cloud_crowd/version.rb
229
231
  - lib/cloud_crowd/worker.rb
230
232
  - public/css/admin_console.css
231
233
  - public/css/reset.css