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.
- 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
|