rocketjob 1.3.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +201 -0
  3. data/README.md +15 -10
  4. data/bin/rocketjob +3 -1
  5. data/bin/rocketjob_perf +92 -0
  6. data/lib/rocket_job/cli.rb +71 -31
  7. data/lib/rocket_job/config.rb +21 -23
  8. data/lib/rocket_job/dirmon_entry.rb +63 -45
  9. data/lib/rocket_job/extensions/aasm.rb +56 -0
  10. data/lib/rocket_job/extensions/mongo.rb +23 -0
  11. data/lib/rocket_job/job.rb +9 -433
  12. data/lib/rocket_job/jobs/dirmon_job.rb +20 -20
  13. data/lib/rocket_job/jobs/simple_job.rb +12 -0
  14. data/lib/rocket_job/plugins/document.rb +69 -0
  15. data/lib/rocket_job/plugins/job/callbacks.rb +92 -0
  16. data/lib/rocket_job/plugins/job/defaults.rb +40 -0
  17. data/lib/rocket_job/plugins/job/logger.rb +36 -0
  18. data/lib/rocket_job/plugins/job/model.rb +288 -0
  19. data/lib/rocket_job/plugins/job/persistence.rb +167 -0
  20. data/lib/rocket_job/plugins/job/state_machine.rb +166 -0
  21. data/lib/rocket_job/plugins/job/worker.rb +167 -0
  22. data/lib/rocket_job/plugins/restart.rb +54 -0
  23. data/lib/rocket_job/plugins/singleton.rb +26 -0
  24. data/lib/rocket_job/plugins/state_machine.rb +105 -0
  25. data/lib/rocket_job/version.rb +1 -1
  26. data/lib/rocket_job/worker.rb +150 -119
  27. data/lib/rocketjob.rb +43 -21
  28. data/test/config_test.rb +12 -0
  29. data/test/dirmon_entry_test.rb +81 -85
  30. data/test/dirmon_job_test.rb +40 -28
  31. data/test/job_test.rb +14 -257
  32. data/test/plugins/job/callbacks_test.rb +163 -0
  33. data/test/plugins/job/defaults_test.rb +52 -0
  34. data/test/plugins/job/logger_test.rb +58 -0
  35. data/test/plugins/job/model_test.rb +97 -0
  36. data/test/plugins/job/persistence_test.rb +81 -0
  37. data/test/plugins/job/state_machine_test.rb +118 -0
  38. data/test/plugins/job/worker_test.rb +183 -0
  39. data/test/plugins/restart_test.rb +185 -0
  40. data/test/plugins/singleton_test.rb +94 -0
  41. data/test/plugins/state_machine_event_callbacks_test.rb +101 -0
  42. data/test/plugins/state_machine_test.rb +64 -0
  43. data/test/test_helper.rb +3 -36
  44. metadata +64 -19
  45. data/lib/rocket_job/concerns/singleton.rb +0 -33
  46. data/lib/rocket_job/concerns/worker.rb +0 -214
  47. data/test/files/_archive/archived.txt +0 -3
  48. data/test/job_worker_test.rb +0 -86
  49. data/test/jobs/test_job.rb +0 -46
  50. data/test/worker_test.rb +0 -97
@@ -1,11 +1,10 @@
1
- require 'thread_safe'
1
+ require 'concurrent'
2
2
  require 'pathname'
3
3
  require 'fileutils'
4
- require 'aasm'
5
4
  module RocketJob
6
5
  class DirmonEntry
7
- include MongoMapper::Document
8
- include AASM
6
+ include Plugins::Document
7
+ include Plugins::StateMachine
9
8
 
10
9
  # @formatter:off
11
10
  # User defined name used to identify this DirmonEntry in Mission Control
@@ -67,9 +66,6 @@ module RocketJob
67
66
  # be created in the archive directory.
68
67
  key :archive_directory, String
69
68
 
70
- # Method to perform on the job, usually :perform
71
- key :perform_method, Symbol, default: :perform
72
-
73
69
  # If this DirmonEntry is in the failed state, exception contains the cause
74
70
  one :exception, class_name: 'RocketJob::JobException'
75
71
 
@@ -85,7 +81,7 @@ module RocketJob
85
81
  # Read-only attributes
86
82
  #
87
83
 
88
- # Current state, as set by AASM
84
+ # Current state, as set by the state machine. Do not modify directly.
89
85
  key :state, Symbol, default: :pending
90
86
 
91
87
  # State Machine events and transitions
@@ -120,19 +116,13 @@ module RocketJob
120
116
  transitions from: :failed, to: :disabled
121
117
  end
122
118
 
123
- event :fail do
119
+ event :fail, before: :set_exception do
124
120
  transitions from: :enabled, to: :failed
125
121
  end
126
122
  end
127
123
 
128
124
  # @formatter:on
129
- validates_presence_of :pattern, :job_class_name, :perform_method
130
-
131
- validates_each :perform_method do |record, attr, value|
132
- if (klass = record.job_class) && !klass.instance_methods.include?(value)
133
- record.errors.add(attr, "Method not implemented by #{record.job_class_name}")
134
- end
135
- end
125
+ validates_presence_of :pattern, :job_class_name
136
126
 
137
127
  validates_each :job_class_name do |record, attr, value|
138
128
  exists =
@@ -145,8 +135,8 @@ module RocketJob
145
135
  end
146
136
 
147
137
  validates_each :arguments do |record, attr, value|
148
- if (klass = record.job_class) && klass.instance_methods.include?(record.perform_method)
149
- count = klass.argument_count(record.perform_method)
138
+ if klass = record.job_class
139
+ count = klass.rocket_job_argument_count
150
140
  record.errors.add(attr, "There must be #{count} argument(s)") if value.size != count
151
141
  end
152
142
  end
@@ -206,8 +196,39 @@ module RocketJob
206
196
  path
207
197
  end
208
198
 
199
+ # Returns [Hash<String:Integer>] of the number of dirmon entries in each state.
200
+ # Note: If there are no workers in that particular state then the hash will not have a value for it.
201
+ #
202
+ # Example dirmon entries in every state:
203
+ # RocketJob::DirmonEntry.counts_by_state
204
+ # # => {
205
+ # :pending => 1,
206
+ # :enabled => 37,
207
+ # :failed => 1,
208
+ # :disabled => 3
209
+ # }
210
+ #
211
+ # Example no dirmon entries:
212
+ # RocketJob::Job.counts_by_state
213
+ # # => {}
214
+ def self.counts_by_state
215
+ counts = {}
216
+ collection.aggregate([
217
+ {
218
+ '$group' => {
219
+ _id: '$state',
220
+ count: {'$sum' => 1}
221
+ }
222
+ }
223
+ ]
224
+ ).each do |result|
225
+ counts[result['_id']] = result['count']
226
+ end
227
+ counts
228
+ end
229
+
209
230
  # The default archive directory that is used when the job being queued does not respond
210
- # to #file_store_upload or #upload, and do not have an `archive_directory` specified in this entry
231
+ # to #upload, and does not have an `archive_directory` specified in this entry
211
232
  cattr_accessor :default_archive_directory
212
233
 
213
234
  @@default_archive_directory = '_archive'.freeze
@@ -217,7 +238,11 @@ module RocketJob
217
238
  def archive_pathname(file_pathname)
218
239
  if archive_directory
219
240
  path = Pathname.new(archive_directory)
220
- path.mkpath unless path.exist?
241
+ begin
242
+ path.mkpath unless path.exist?
243
+ rescue Errno::ENOENT => exc
244
+ raise(Errno::ENOENT, "DirmonJob failed to create archive directory: #{path}, #{exc.message}")
245
+ end
221
246
  path.realpath
222
247
  else
223
248
  file_pathname.dirname.join(self.class.default_archive_directory).realdirpath
@@ -226,7 +251,7 @@ module RocketJob
226
251
 
227
252
  # Passes each filename [Pathname] found that matches the pattern into the supplied block
228
253
  def each(&block)
229
- logger.tagged("DirmonEntry:#{id}") do
254
+ logger.fast_tag("DirmonEntry:#{id}") do
230
255
  # Case insensitive filename matching
231
256
  Pathname.glob(pattern, File::FNM_CASEFOLD).each do |pathname|
232
257
  next if pathname.directory?
@@ -253,7 +278,7 @@ module RocketJob
253
278
  end
254
279
 
255
280
  # Set exception information for this DirmonEntry and fail it
256
- def fail_with_exception!(worker_name, exc_or_message)
281
+ def set_exception(worker_name, exc_or_message)
257
282
  if exc_or_message.is_a?(Exception)
258
283
  self.exception = JobException.from_exception(exc_or_message)
259
284
  exception.worker_name = worker_name
@@ -265,10 +290,9 @@ module RocketJob
265
290
  worker_name: worker_name
266
291
  )
267
292
  end
268
- fail!
269
293
  end
270
294
 
271
- @@whitelist_paths = ThreadSafe::Array.new
295
+ @@whitelist_paths = Concurrent::Array.new
272
296
 
273
297
  # Returns the Job to be queued
274
298
  def job_class
@@ -280,19 +304,17 @@ module RocketJob
280
304
 
281
305
  # Queues the job for the supplied pathname
282
306
  def later(pathname)
283
- job = job_class.new(
284
- properties.merge(
285
- arguments: arguments,
286
- properties: properties,
287
- perform_method: perform_method
288
- )
289
- )
290
- upload_file(job, pathname)
291
- job.save!
292
- job
307
+ if klass = job_class
308
+ job = klass.new(properties.merge(arguments: arguments))
309
+ upload_file(job, pathname)
310
+ job.save!
311
+ job
312
+ else
313
+ raise(ArgumentError, "Cannot instantiate a class for: #{job_class_name.inspect}")
314
+ end
293
315
  end
294
316
 
295
- protected
317
+ private
296
318
 
297
319
  # Instance method to return whitelist paths
298
320
  def whitelist_paths
@@ -301,13 +323,7 @@ module RocketJob
301
323
 
302
324
  # Upload the file to the job
303
325
  def upload_file(job, pathname)
304
- if job.respond_to?(:file_store_upload)
305
- # Allow the job to determine what to do with the file
306
- # Pass the pathname as a string, not a Pathname (IO) instance
307
- # so that it can read the file directly
308
- job.file_store_upload(pathname.to_s)
309
- archive_directory ? archive_file(job, pathname) : pathname.unlink
310
- elsif job.respond_to?(:upload)
326
+ if job.respond_to?(:upload)
311
327
  # With RocketJob Pro the file can be uploaded directly into the Job itself
312
328
  job.upload(pathname.to_s)
313
329
  archive_directory ? archive_file(job, pathname) : pathname.unlink
@@ -316,15 +332,17 @@ module RocketJob
316
332
  end
317
333
  end
318
334
 
319
- # Archives the file for a job where there was no #file_store_upload or #upload method
335
+ # Archives the file for a job where there was no #upload method
320
336
  def upload_default(job, pathname)
321
337
  full_file_name = archive_file(job, pathname)
322
- if job.respond_to?(:full_file_name=)
338
+ if job.respond_to?(:upload_file_name=)
339
+ job.upload_file_name = full_file_name
340
+ elsif job.respond_to?(:full_file_name=)
323
341
  job.full_file_name = full_file_name
324
342
  elsif job.arguments.first.is_a?(Hash)
325
343
  job.arguments.first[:full_file_name] = full_file_name
326
344
  else
327
- raise(ArgumentError, "#{job_class_name} must either have attribute 'full_file_name' or the first argument must be a Hash")
345
+ raise(ArgumentError, "#{job_class_name} must either have attribute 'upload_file_name' or the first argument must be a Hash")
328
346
  end
329
347
  end
330
348
 
@@ -0,0 +1,56 @@
1
+ require 'aasm'
2
+
3
+ # The following patches can be removed once the following PR has been merged into AASM:
4
+ # https://github.com/aasm/aasm/pull/269
5
+
6
+ AASM::Core::Event
7
+ module AASM::Core
8
+ class Event
9
+ def initialize_copy(orig)
10
+ super
11
+ @transitions = @transitions.collect { |transition| transition.clone }
12
+ @guards = @guards.dup
13
+ @unless = @unless.dup
14
+ @options = {}
15
+ orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
16
+ end
17
+ end
18
+ end
19
+
20
+ AASM::Core::State
21
+ module AASM::Core
22
+ class State
23
+ # called internally by Ruby 1.9 after clone()
24
+ def initialize_copy(orig)
25
+ super
26
+ @options = {}
27
+ orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
28
+ end
29
+ end
30
+ end
31
+
32
+ AASM::Core::Transition
33
+ module AASM::Core
34
+ class Transition
35
+ def initialize_copy(orig)
36
+ super
37
+ @guards = @guards.dup
38
+ @unless = @unless.dup
39
+ @opts = {}
40
+ orig.opts.each_pair { |name, setting| @opts[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
41
+ end
42
+ end
43
+ end
44
+
45
+ AASM::StateMachine
46
+ module AASM
47
+ class StateMachine
48
+ def initialize_copy(orig)
49
+ super
50
+ @states = orig.states.collect { |state| state.clone }
51
+ @events = {}
52
+ orig.events.each_pair { |name, event| @events[name] = event.clone }
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,23 @@
1
+ require 'mongo'
2
+
3
+ Mongo::Logging
4
+ module Mongo
5
+ module Logging
6
+
7
+ # Remove annoying message on startup
8
+ def write_logging_startup_message
9
+ end
10
+
11
+ # Cleanup output
12
+ def log(level, msg)
13
+ MongoClient.logger.send(level, msg)
14
+ end
15
+
16
+ protected
17
+
18
+ def log_operation(name, payload, duration)
19
+ MongoClient.logger.benchmark_trace(name, duration: (duration * 1000), payload: payload)
20
+ end
21
+
22
+ end
23
+ end