rocketjob 2.1.3 → 3.0.0.alpha

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -0
  3. data/lib/rocket_job/active_server.rb +48 -0
  4. data/lib/rocket_job/cli.rb +29 -17
  5. data/lib/rocket_job/config.rb +19 -31
  6. data/lib/rocket_job/dirmon_entry.rb +15 -45
  7. data/lib/rocket_job/extensions/mongo/logging.rb +26 -0
  8. data/lib/rocket_job/extensions/rocket_job_adapter.rb +3 -5
  9. data/lib/rocket_job/heartbeat.rb +18 -23
  10. data/lib/rocket_job/job.rb +0 -1
  11. data/lib/rocket_job/job_exception.rb +11 -13
  12. data/lib/rocket_job/jobs/dirmon_job.rb +8 -8
  13. data/lib/rocket_job/jobs/housekeeping_job.rb +13 -15
  14. data/lib/rocket_job/performance.rb +5 -5
  15. data/lib/rocket_job/plugins/cron.rb +3 -10
  16. data/lib/rocket_job/plugins/document.rb +58 -33
  17. data/lib/rocket_job/plugins/job/model.rb +43 -71
  18. data/lib/rocket_job/plugins/job/persistence.rb +7 -63
  19. data/lib/rocket_job/plugins/job/worker.rb +24 -26
  20. data/lib/rocket_job/plugins/processing_window.rb +6 -9
  21. data/lib/rocket_job/plugins/retry.rb +3 -8
  22. data/lib/rocket_job/plugins/singleton.rb +1 -1
  23. data/lib/rocket_job/plugins/state_machine.rb +1 -7
  24. data/lib/rocket_job/server.rb +352 -0
  25. data/lib/rocket_job/version.rb +1 -1
  26. data/lib/rocket_job/worker.rb +46 -336
  27. data/lib/rocketjob.rb +5 -4
  28. data/test/config/mongoid.yml +88 -0
  29. data/test/config_test.rb +1 -1
  30. data/test/dirmon_entry_test.rb +15 -79
  31. data/test/dirmon_job_test.rb +6 -6
  32. data/test/job_test.rb +2 -2
  33. data/test/plugins/job/callbacks_test.rb +40 -32
  34. data/test/plugins/job/defaults_test.rb +10 -8
  35. data/test/plugins/job/model_test.rb +1 -3
  36. data/test/plugins/job/persistence_test.rb +11 -13
  37. data/test/plugins/job/worker_test.rb +45 -26
  38. data/test/plugins/processing_window_test.rb +4 -4
  39. data/test/plugins/restart_test.rb +11 -12
  40. data/test/plugins/state_machine_event_callbacks_test.rb +20 -18
  41. data/test/plugins/state_machine_test.rb +5 -5
  42. data/test/test_helper.rb +4 -1
  43. metadata +15 -29
  44. data/lib/rocket_job/extensions/mongo.rb +0 -23
  45. data/lib/rocket_job/extensions/mongo_mapper.rb +0 -30
  46. data/lib/rocket_job/plugins/job/defaults.rb +0 -40
  47. data/test/config/mongo.yml +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69c06ffc4a4e337c787f06d36873d763ccced1be
4
- data.tar.gz: 401c2bda273c7413b0ceda2853aef6c325b15159
3
+ metadata.gz: 328cd371a61e647e687589d126b222c603dfe3a0
4
+ data.tar.gz: c13d9c10d2156acbacbf92b05af835639c15b112
5
5
  SHA512:
6
- metadata.gz: 87d8f78b0e766a6d1ab06efb00ff83a3b7b27ade12d11109630f01bed3a753ba8f1fe6241d198209870657ee2e6256d5f6c6ba49293c21ad6dc305c85cc4835e
7
- data.tar.gz: d87017700e5fb67815b6ce934b73a683d4c08d96ed04e5f802e58ced4a4aa28b2c67be371258c6f643edb6444f90d66e57f0dc1d698b7398dd199bb80eb1d4c6
6
+ metadata.gz: bb17eef819ef602e0d58fd89546495ac6f080e803c8421511be078e6821f9500860e40443120f417710cd1313e8e0fd329328c4cdcc0ace6ce4056aa7ab9703f
7
+ data.tar.gz: 74691dda4d998f54208a5ee3edbb004f49b078dd9f6d9d003b4795b654999aa292986fc36041aca282bca22f5b3b70d45e8a15e55323ebad602195b0d47a4e32
data/README.md CHANGED
@@ -17,6 +17,42 @@ Checkout http://rocketjob.io/
17
17
  * Questions? Join the chat room on Gitter for [rocketjob support](https://gitter.im/rocketjob/support)
18
18
  * [Report bugs](https://github.com/rocketjob/rocketjob/issues)
19
19
 
20
+ ## Upgrading to V3
21
+
22
+ * V3 replaces MongoMapper with Mongoid which supports the latest MongoDB Ruby client driver.
23
+ * Replase usages of `rocket_job do` to set default values:
24
+
25
+ ~~~ruby
26
+ rocket_job do |job|
27
+ job.priority = 25
28
+ end
29
+ ~~~
30
+
31
+ With:
32
+
33
+ ~~~ruby
34
+ self.priority = 25
35
+ ~~~
36
+
37
+ * Replace `key` with `field` when adding attributes to a job:
38
+
39
+ ~~~ruby
40
+ key :inquiry_defaults, Hash
41
+ ~~~
42
+
43
+ With:
44
+
45
+ ~~~ruby
46
+ field :inquiry_defaults, type: Hash, default: {}
47
+ ~~~
48
+
49
+ * Replace usage of `public_rocket_job_properties` with the `user_editable` option:
50
+
51
+ ~~~ruby
52
+ field :priority, type: Integer, default: 50, user_editable: true
53
+ ~~~
54
+
55
+
20
56
  ## Ruby Support
21
57
 
22
58
  Rocket Job is tested and supported on the following Ruby platforms:
@@ -0,0 +1,48 @@
1
+ module RocketJob
2
+ # Information about a server currently working on a job
3
+ class ActiveServer
4
+ # When this server started working on this job / slice
5
+ attr_accessor :started_at
6
+
7
+ attr_accessor :name, :job
8
+
9
+ # Returns [Hash<String:ActiveWorker>] hash of all servers sorted by name
10
+ # and what they are currently working on.
11
+ # Returns {} if no servers are currently busy doing any work
12
+ def self.all
13
+ servers = {}
14
+ # Need paused, failed or aborted since servers may still be working on active slices
15
+ RocketJob::Job.where(state: [:running, :paused, :failed, :aborted]).each do |job|
16
+ job.rocket_job_active_servers.each_pair do |name, active_server|
17
+ (servers[name] ||= []) << active_server
18
+ end
19
+ end
20
+ servers
21
+ end
22
+
23
+ def initialize(name, started_at, job)
24
+ @name = name
25
+ @started_at = started_at
26
+ @job = job
27
+ end
28
+
29
+ # Duration in human readable form
30
+ def duration
31
+ RocketJob.seconds_as_duration(duration_s)
32
+ end
33
+
34
+ # Number of seconds this server has been working on this job / slice
35
+ def duration_s
36
+ Time.now - started_at
37
+ end
38
+
39
+ def server
40
+ @server ||= RocketJob::Server.find_by(name: name)
41
+ end
42
+
43
+ def name=(name)
44
+ @server = nil
45
+ @name = name
46
+ end
47
+ end
48
+ end
@@ -4,21 +4,23 @@ module RocketJob
4
4
  # Command Line Interface parser for RocketJob
5
5
  class CLI
6
6
  include SemanticLogger::Loggable
7
- attr_accessor :name, :threads, :environment, :pidfile, :directory, :quiet, :log_level, :log_file
7
+ attr_accessor :name, :workers, :environment, :pidfile, :directory, :quiet, :log_level, :log_file, :mongo_config, :symmetric_encryption_config
8
8
 
9
9
  def initialize(argv)
10
- @name = nil
11
- @threads = nil
12
- @quiet = false
13
- @environment = nil
14
- @pidfile = nil
15
- @directory = '.'
16
- @log_level = nil
17
- @log_file = nil
10
+ @name = nil
11
+ @workers = nil
12
+ @quiet = false
13
+ @environment = nil
14
+ @pidfile = nil
15
+ @directory = '.'
16
+ @log_level = nil
17
+ @log_file = nil
18
+ @mongo_config = nil
19
+ @symmetric_encryption_config = nil
18
20
  parse(argv)
19
21
  end
20
22
 
21
- # Run a RocketJob::Worker from the command line
23
+ # Run a RocketJob::Server from the command line
22
24
  def run
23
25
  Thread.current.name = 'rocketjob main'
24
26
  setup_environment
@@ -28,8 +30,8 @@ module RocketJob
28
30
 
29
31
  opts = {}
30
32
  opts[:name] = name if name
31
- opts[:max_threads] = threads if threads
32
- Worker.run(opts)
33
+ opts[:max_workers] = workers if workers
34
+ Server.run(opts)
33
35
  end
34
36
 
35
37
  def rails?
@@ -57,7 +59,7 @@ module RocketJob
57
59
  SemanticLogger.default_level = log_level.to_sym if log_level
58
60
 
59
61
  if Rails.configuration.eager_load
60
- RocketJob::Worker.logger.measure_info('Eager loaded Rails and all Engines') do
62
+ logger.measure_info('Eager loaded Rails and all Engines') do
61
63
  Rails.application.eager_load!
62
64
  Rails::Engine.subclasses.each(&:eager_load!)
63
65
  end
@@ -85,7 +87,7 @@ module RocketJob
85
87
  SemanticLogger.add_appender(file_name: path.to_s, formatter: :color)
86
88
 
87
89
  logger.info "Rails not detected. Running standalone: #{environment}"
88
- RocketJob::Config.load!(environment)
90
+ RocketJob::Config.load!(environment, mongo_config, symmetric_encryption_config)
89
91
  self.class.eager_load_jobs
90
92
  end
91
93
 
@@ -130,11 +132,15 @@ module RocketJob
130
132
  # Parse command line options placing results in the corresponding instance variables
131
133
  def parse(argv)
132
134
  parser = OptionParser.new do |o|
133
- o.on('-n', '--name NAME', 'Unique Name of this worker instance (Default: host_name:PID)') do |arg|
135
+ o.on('-n', '--name NAME', 'Unique Name of this server (Default: host_name:PID)') do |arg|
134
136
  @name = arg
135
137
  end
136
- o.on('-t', '--threads COUNT', 'Number of worker threads to start') do |arg|
137
- @threads = arg.to_i
138
+ o.on('-w', '--workers COUNT', 'Number of workers (threads) to start') do |arg|
139
+ @workers = arg.to_i
140
+ end
141
+ o.on('-t', '--threads COUNT', 'Deprecated') do |arg|
142
+ warn '-t and --threads are deprecated, use -w or --workers'
143
+ @workers = arg.to_i
138
144
  end
139
145
  o.on('-q', '--quiet', 'Do not write to stdout, only to logfile. Necessary when running as a daemon') do
140
146
  @quiet = true
@@ -154,6 +160,12 @@ module RocketJob
154
160
  o.on('--pidfile PATH', 'Use PATH as a pidfile') do |arg|
155
161
  @pidfile = arg
156
162
  end
163
+ o.on('-m', '--mongo MONGO_CONFIG_FILE_NAME', 'Path and filename of config file. Default: config/mongoid.yml') do |arg|
164
+ @mongo_config = arg
165
+ end
166
+ o.on('-s', '--symmetric-encryption SYMMETRIC_ENCRYPTION_CONFIG_FILE_NAME', 'Path and filename of Symmetric Encryption config file. Default: config/symmetric-encryption.yml') do |arg|
167
+ @symmetric_encryption_config = arg
168
+ end
157
169
  o.on('-v', '--version', 'Print the version information') do
158
170
  puts "Rocket Job v#{RocketJob::VERSION}"
159
171
  exit 1
@@ -21,15 +21,22 @@ module RocketJob
21
21
  # Also, exceptions will be raised instead of failing the job.
22
22
  cattr_accessor(:inline_mode) { false }
23
23
 
24
- # @formatter:off
25
- # The maximum number of worker threads to create on any one worker
26
- key :max_worker_threads, Integer, default: 10
24
+ #
25
+ # Servers
26
+ #
27
+
28
+ # The maximum number of workers to create on any one server
29
+ field :max_worker_threads, type: Integer, default: 10
27
30
 
28
- # Number of seconds between heartbeats from Rocket Job Worker processes
29
- key :heartbeat_seconds, Integer, default: 15
31
+ # Number of seconds between heartbeats from a Rocket Job Server process
32
+ field :heartbeat_seconds, type: Integer, default: 15
33
+
34
+ #
35
+ # Workers
36
+ #
30
37
 
31
38
  # Maximum number of seconds a Worker will wait before checking for new jobs
32
- key :max_poll_seconds, Integer, default: 5
39
+ field :max_poll_seconds, type: Integer, default: 5
33
40
 
34
41
  # Number of seconds between checking for:
35
42
  # - Jobs with a higher priority
@@ -40,33 +47,14 @@ module RocketJob
40
47
  #
41
48
  # Note:
42
49
  # Not all job types support pausing in the middle
43
- key :re_check_seconds, Integer, default: 60
44
-
45
- # @formatter:on
46
-
47
- # Replace the MongoMapper default mongo connection for holding jobs
48
- def self.mongo_connection=(connection)
49
- connection(connection)
50
- Worker.connection(connection)
51
- Job.connection(connection)
52
- Config.connection(connection)
53
- DirmonEntry.connection(connection)
54
-
55
- db_name = connection.db.name
56
- set_database_name(db_name)
57
- Worker.set_database_name(db_name)
58
- Job.set_database_name(db_name)
59
- Config.set_database_name(db_name)
60
- DirmonEntry.set_database_name(db_name)
61
- end
50
+ field :re_check_seconds, type: Integer, default: 60
62
51
 
63
- # Configure MongoMapper
64
- def self.load!(environment='development', file_name=nil, encryption_file_name=nil)
65
- config_file = file_name ? Pathname.new(file_name) : Pathname.pwd.join('config/mongo.yml')
52
+ # Configure Mongoid
53
+ def self.load!(environment = 'development', file_name = nil, encryption_file_name = nil)
54
+ config_file = file_name ? Pathname.new(file_name) : Pathname.pwd.join('config/mongoid.yml')
66
55
  if config_file.file?
67
- logger.debug "Reading MongoDB configuration from: #{config_file}"
68
- config = YAML.load(ERB.new(config_file.read).result)
69
- MongoMapper.setup(config, environment)
56
+ logger.debug "Reading Mongo configuration from: #{config_file}"
57
+ Mongoid.load!(config_file, environment)
70
58
  else
71
59
  raise(ArgumentError, "Mongo Configuration file: #{config_file.to_s} not found")
72
60
  end
@@ -6,9 +6,8 @@ module RocketJob
6
6
  include Plugins::Document
7
7
  include Plugins::StateMachine
8
8
 
9
- # @formatter:off
10
9
  # User defined name used to identify this DirmonEntry in Mission Control
11
- key :name, String
10
+ field :name, type: String
12
11
 
13
12
  # Pattern for finding files
14
13
  #
@@ -27,36 +26,19 @@ module RocketJob
27
26
  # - If there is no '*' in the pattern then an exact filename match is expected
28
27
  # - The pattern is not validated to ensure the path exists, it will be validated against the
29
28
  # `whitelist_paths` when processed by DirmonJob
30
- key :pattern, String
29
+ field :pattern, type: String
31
30
 
32
31
  # Job to enqueue for processing for every file that matches the pattern
33
32
  #
34
33
  # Example:
35
34
  # "ProcessItJob"
36
- key :job_class_name, String
37
-
38
- # Any user supplied arguments for the method invocation
39
- # All keys must be UTF-8 strings. The values can be any valid BSON type:
40
- # Integer
41
- # Float
42
- # Time (UTC)
43
- # String (UTF-8)
44
- # Array
45
- # Hash
46
- # True
47
- # False
48
- # Symbol
49
- # nil
50
- # Regular Expression
51
- #
52
- # Note: Date is not supported, convert it to a UTC time
53
- key :arguments, Array
35
+ field :job_class_name, type: String
54
36
 
55
37
  # Any job properties to set
56
38
  #
57
39
  # Example, override the default job priority:
58
40
  # { priority: 45 }
59
- key :properties, Hash
41
+ field :properties, type: Hash, default: {}
60
42
 
61
43
  # Archive directory to move files to when processed to prevent processing the
62
44
  # file again.
@@ -64,10 +46,10 @@ module RocketJob
64
46
  # If supplied, the file will be moved to this directory before the job is started
65
47
  # If the file was in a sub-directory, the corresponding sub-directory will
66
48
  # be created in the archive directory.
67
- key :archive_directory, String
49
+ field :archive_directory, type: String
68
50
 
69
51
  # If this DirmonEntry is in the failed state, exception contains the cause
70
- one :exception, class_name: 'RocketJob::JobException'
52
+ embeds_one :exception, class_name: 'RocketJob::JobException'
71
53
 
72
54
  # The maximum number of files that should ever match during a single poll of the pattern.
73
55
  #
@@ -75,14 +57,17 @@ module RocketJob
75
57
  # Exceeding this number will result in an exception being logged in a failed Dirmon instance.
76
58
  # Dirmon processing will continue with new instances.
77
59
  # TODO: Implement max_hits
78
- #key :max_hits, Integer, default: 100
60
+ #field :max_hits, type: Integer, default: 100
79
61
 
80
62
  #
81
63
  # Read-only attributes
82
64
  #
83
65
 
84
66
  # Current state, as set by the state machine. Do not modify directly.
85
- key :state, Symbol, default: :pending
67
+ field :state, type: Symbol, default: :pending
68
+
69
+ # Unique index on pattern to help prevent two entries from scanning the same files
70
+ index({pattern: 1}, background: true, unique: true, drop_dups: true)
86
71
 
87
72
  # State Machine events and transitions
88
73
  #
@@ -107,13 +92,13 @@ module RocketJob
107
92
  state :disabled
108
93
 
109
94
  event :enable do
110
- transitions from: :pending, to: :enabled
95
+ transitions from: :pending, to: :enabled
111
96
  transitions from: :disabled, to: :enabled
112
97
  end
113
98
 
114
99
  event :disable do
115
100
  transitions from: :enabled, to: :disabled
116
- transitions from: :failed, to: :disabled
101
+ transitions from: :failed, to: :disabled
117
102
  end
118
103
 
119
104
  event :fail, before: :set_exception do
@@ -134,13 +119,6 @@ module RocketJob
134
119
  record.errors.add(attr, 'job_class_name must be defined and must be derived from RocketJob::Job') unless exists
135
120
  end
136
121
 
137
- validates_each :arguments do |record, attr, value|
138
- if klass = record.job_class
139
- count = klass.rocket_job_argument_count
140
- record.errors.add(attr, "There must be #{count} argument(s)") if value.size != count
141
- end
142
- end
143
-
144
122
  validates_each :properties do |record, attr, value|
145
123
  if record.job_class && (methods = record.job_class.instance_methods)
146
124
  value.each_pair do |k, v|
@@ -149,12 +127,6 @@ module RocketJob
149
127
  end
150
128
  end
151
129
 
152
- # Create indexes
153
- def self.create_indexes
154
- # Unique index on pattern to help prevent two entries from scanning the same files
155
- ensure_index({pattern: 1}, background: true, unique: true)
156
- end
157
-
158
130
  # Security Settings
159
131
  #
160
132
  # A whitelist of paths from which to process files.
@@ -306,7 +278,7 @@ module RocketJob
306
278
  def later(pathname)
307
279
  if klass = job_class
308
280
  logger.measure_info "Enqueued: #{name}, Job class: #{job_class_name}" do
309
- job = klass.new(properties.merge(arguments: arguments))
281
+ job = klass.new(properties)
310
282
  upload_file(job, pathname)
311
283
  job.save!
312
284
  job
@@ -341,10 +313,8 @@ module RocketJob
341
313
  job.upload_file_name = full_file_name
342
314
  elsif job.respond_to?(:full_file_name=)
343
315
  job.full_file_name = full_file_name
344
- elsif job.arguments.first.is_a?(Hash)
345
- job.arguments.first[:full_file_name] = full_file_name
346
316
  else
347
- raise(ArgumentError, "#{job_class_name} must either have attribute 'upload_file_name' or the first argument must be a Hash")
317
+ raise(ArgumentError, "#{job_class_name} must either have attribute 'upload_file_name' or 'full_file_name'")
348
318
  end
349
319
  end
350
320
 
@@ -0,0 +1,26 @@
1
+ require 'mongo/monitoring/command_log_subscriber'
2
+
3
+ module Mongo
4
+ class Monitoring
5
+ class CommandLogSubscriber
6
+ include SemanticLogger::Loggable
7
+ self.logger.name = 'Mongo'
8
+
9
+ def started(event)
10
+ @event_command = event.command
11
+ end
12
+
13
+ def succeeded(event)
14
+ logger.debug(message: prefix(event), duration: (event.duration * 1000), payload: @event_command)
15
+ end
16
+
17
+ def failed(event)
18
+ logger.debug(message: "#{prefix(event)} Failed: #{event.message}", duration: (event.duration * 1000), payload: @event_command)
19
+ end
20
+
21
+ def prefix(event)
22
+ "#{event.address.to_s} | #{event.database_name}.#{event.command_name}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,3 @@
1
- #require 'rocketjob'
2
-
3
1
  module ActiveJob
4
2
  module QueueAdapters
5
3
  # == Rocket Job adapter for Active Job
@@ -38,9 +36,9 @@ module ActiveJob
38
36
  end
39
37
 
40
38
  class JobWrapper < RocketJob::Job #:nodoc:
41
- key :active_job_id, String
42
- key :active_job_class, String
43
- key :active_job_queue, String
39
+ field :active_job_id, type: String
40
+ field :active_job_class, type: String
41
+ field :active_job_queue, type: String
44
42
 
45
43
  def perform(job_data)
46
44
  Base.execute job_data