rocketjob 1.3.0 → 2.0.0.rc1
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 +4 -4
- data/LICENSE.txt +201 -0
- data/README.md +15 -10
- data/bin/rocketjob +3 -1
- data/bin/rocketjob_perf +92 -0
- data/lib/rocket_job/cli.rb +71 -31
- data/lib/rocket_job/config.rb +21 -23
- data/lib/rocket_job/dirmon_entry.rb +63 -45
- data/lib/rocket_job/extensions/aasm.rb +56 -0
- data/lib/rocket_job/extensions/mongo.rb +23 -0
- data/lib/rocket_job/job.rb +9 -433
- data/lib/rocket_job/jobs/dirmon_job.rb +20 -20
- data/lib/rocket_job/jobs/simple_job.rb +12 -0
- data/lib/rocket_job/plugins/document.rb +69 -0
- data/lib/rocket_job/plugins/job/callbacks.rb +92 -0
- data/lib/rocket_job/plugins/job/defaults.rb +40 -0
- data/lib/rocket_job/plugins/job/logger.rb +36 -0
- data/lib/rocket_job/plugins/job/model.rb +288 -0
- data/lib/rocket_job/plugins/job/persistence.rb +167 -0
- data/lib/rocket_job/plugins/job/state_machine.rb +166 -0
- data/lib/rocket_job/plugins/job/worker.rb +167 -0
- data/lib/rocket_job/plugins/restart.rb +54 -0
- data/lib/rocket_job/plugins/singleton.rb +26 -0
- data/lib/rocket_job/plugins/state_machine.rb +105 -0
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +150 -119
- data/lib/rocketjob.rb +43 -21
- data/test/config_test.rb +12 -0
- data/test/dirmon_entry_test.rb +81 -85
- data/test/dirmon_job_test.rb +40 -28
- data/test/job_test.rb +14 -257
- data/test/plugins/job/callbacks_test.rb +163 -0
- data/test/plugins/job/defaults_test.rb +52 -0
- data/test/plugins/job/logger_test.rb +58 -0
- data/test/plugins/job/model_test.rb +97 -0
- data/test/plugins/job/persistence_test.rb +81 -0
- data/test/plugins/job/state_machine_test.rb +118 -0
- data/test/plugins/job/worker_test.rb +183 -0
- data/test/plugins/restart_test.rb +185 -0
- data/test/plugins/singleton_test.rb +94 -0
- data/test/plugins/state_machine_event_callbacks_test.rb +101 -0
- data/test/plugins/state_machine_test.rb +64 -0
- data/test/test_helper.rb +3 -36
- metadata +64 -19
- data/lib/rocket_job/concerns/singleton.rb +0 -33
- data/lib/rocket_job/concerns/worker.rb +0 -214
- data/test/files/_archive/archived.txt +0 -3
- data/test/job_worker_test.rb +0 -86
- data/test/jobs/test_job.rb +0 -46
- data/test/worker_test.rb +0 -97
@@ -1,11 +1,10 @@
|
|
1
|
-
require '
|
1
|
+
require 'concurrent'
|
2
2
|
require 'pathname'
|
3
3
|
require 'fileutils'
|
4
|
-
require 'aasm'
|
5
4
|
module RocketJob
|
6
5
|
class DirmonEntry
|
7
|
-
include
|
8
|
-
include
|
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
|
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
|
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
|
149
|
-
count = klass.
|
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 #
|
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
|
-
|
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.
|
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
|
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 =
|
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
|
-
|
284
|
-
properties.merge(
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
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?(:
|
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 #
|
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?(:
|
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 '
|
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
|