rocketjob 5.4.0.beta2 → 6.0.0.rc3
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/README.md +149 -5
- data/bin/rocketjob_batch_perf +1 -1
- data/bin/rocketjob_perf +1 -1
- data/lib/rocket_job/batch.rb +3 -1
- data/lib/rocket_job/batch/categories.rb +341 -0
- data/lib/rocket_job/batch/io.rb +128 -60
- data/lib/rocket_job/batch/model.rb +20 -68
- data/lib/rocket_job/batch/performance.rb +19 -7
- data/lib/rocket_job/batch/statistics.rb +34 -12
- data/lib/rocket_job/batch/tabular.rb +2 -0
- data/lib/rocket_job/batch/tabular/input.rb +8 -6
- data/lib/rocket_job/batch/tabular/output.rb +4 -2
- data/lib/rocket_job/batch/throttle_running_workers.rb +8 -17
- data/lib/rocket_job/batch/worker.rb +27 -24
- data/lib/rocket_job/category/base.rb +78 -0
- data/lib/rocket_job/category/input.rb +110 -0
- data/lib/rocket_job/category/output.rb +25 -0
- data/lib/rocket_job/cli.rb +25 -17
- data/lib/rocket_job/dirmon_entry.rb +22 -12
- data/lib/rocket_job/event.rb +1 -1
- data/lib/rocket_job/extensions/iostreams/path.rb +32 -0
- data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +2 -2
- data/lib/rocket_job/extensions/mongoid/factory.rb +4 -12
- data/lib/rocket_job/extensions/mongoid/stringified_symbol.rb +50 -0
- data/lib/rocket_job/extensions/psych/yaml_tree.rb +8 -0
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -2
- data/lib/rocket_job/jobs/conversion_job.rb +39 -0
- data/lib/rocket_job/jobs/dirmon_job.rb +2 -2
- data/lib/rocket_job/jobs/housekeeping_job.rb +7 -7
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +17 -6
- data/lib/rocket_job/jobs/on_demand_job.rb +1 -2
- data/lib/rocket_job/jobs/performance_job.rb +3 -1
- data/lib/rocket_job/jobs/re_encrypt/relational_job.rb +103 -96
- data/lib/rocket_job/jobs/upload_file_job.rb +44 -8
- data/lib/rocket_job/lookup_collection.rb +69 -0
- data/lib/rocket_job/plugins/job/model.rb +25 -50
- data/lib/rocket_job/plugins/job/throttle.rb +2 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +12 -4
- data/lib/rocket_job/plugins/job/worker.rb +2 -7
- data/lib/rocket_job/plugins/restart.rb +12 -5
- data/lib/rocket_job/plugins/state_machine.rb +2 -1
- data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +38 -0
- data/lib/rocket_job/ractor_worker.rb +42 -0
- data/lib/rocket_job/server/model.rb +1 -1
- data/lib/rocket_job/sliced.rb +15 -70
- data/lib/rocket_job/sliced/bzip2_output_slice.rb +1 -1
- data/lib/rocket_job/sliced/input.rb +1 -1
- data/lib/rocket_job/sliced/slice.rb +5 -13
- data/lib/rocket_job/sliced/slices.rb +14 -2
- data/lib/rocket_job/sliced/writer/output.rb +33 -45
- data/lib/rocket_job/subscribers/server.rb +1 -1
- data/lib/rocket_job/thread_worker.rb +46 -0
- data/lib/rocket_job/throttle_definitions.rb +7 -1
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +21 -55
- data/lib/rocket_job/worker_pool.rb +5 -7
- data/lib/rocketjob.rb +53 -43
- metadata +36 -26
- data/lib/rocket_job/extensions/mongoid/remove_warnings.rb +0 -12
- data/lib/rocket_job/jobs/on_demand_batch_tabular_job.rb +0 -28
|
@@ -38,12 +38,11 @@
|
|
|
38
38
|
#
|
|
39
39
|
# Example: Retain output:
|
|
40
40
|
# code = <<~CODE
|
|
41
|
-
#
|
|
41
|
+
# data['result'] = data['a'] * data['b']
|
|
42
42
|
# CODE
|
|
43
43
|
#
|
|
44
44
|
# RocketJob::Jobs::OnDemandJob.create!(
|
|
45
45
|
# code: code,
|
|
46
|
-
# collect_output: true,
|
|
47
46
|
# data: {'a' => 10, 'b' => 2}
|
|
48
47
|
# )
|
|
49
48
|
#
|
|
@@ -6,9 +6,11 @@ module RocketJob
|
|
|
6
6
|
# Define the job's default attributes
|
|
7
7
|
self.description = "Performance Test"
|
|
8
8
|
self.priority = 5
|
|
9
|
-
self.slice_size = 100
|
|
10
9
|
self.destroy_on_complete = false
|
|
11
10
|
|
|
11
|
+
input_category slice_size: 100
|
|
12
|
+
output_category
|
|
13
|
+
|
|
12
14
|
# No operation, just return the supplied line (record)
|
|
13
15
|
def perform(line)
|
|
14
16
|
line
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
require "active_record"
|
|
2
|
-
require "sync_attr"
|
|
3
|
-
|
|
4
1
|
# Batch Worker to Re-encrypt all encrypted fields in MySQL that start with `encrytped_`.
|
|
5
2
|
#
|
|
6
3
|
# Run in Rails console:
|
|
@@ -11,116 +8,126 @@ require "sync_attr"
|
|
|
11
8
|
# * This job will find any column in the database that starts with`encrypted_`.
|
|
12
9
|
# * This means that temporary or other tables not part of the application tables will also be processed.
|
|
13
10
|
# * Since it automatically finds and re-encrypts any column, new columns are handled without any manual intervention.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
11
|
+
if defined?(ActiveRecord) && defined?(SyncAttr)
|
|
12
|
+
require "active_record"
|
|
13
|
+
require "sync_attr"
|
|
14
|
+
|
|
15
|
+
module RocketJob
|
|
16
|
+
module Jobs
|
|
17
|
+
module ReEncrypt
|
|
18
|
+
class RelationalJob < RocketJob::Job
|
|
19
|
+
include RocketJob::Batch
|
|
20
|
+
|
|
21
|
+
self.priority = 30
|
|
22
|
+
self.destroy_on_complete = false
|
|
23
|
+
self.throttle_running_jobs = 1
|
|
24
|
+
self.throttle_running_workers = 10
|
|
25
|
+
|
|
26
|
+
input_category slice_size: 1_000
|
|
27
|
+
|
|
28
|
+
# Name of the table being re-encrypted
|
|
29
|
+
field :table_name, type: String
|
|
30
|
+
|
|
31
|
+
# Limit the number of records to re-encrypt in test environments
|
|
32
|
+
field :limit, type: Integer
|
|
33
|
+
|
|
34
|
+
validates_presence_of :table_name
|
|
35
|
+
before_batch :upload_records
|
|
36
|
+
|
|
37
|
+
# Returns [Hash] of table names with each entry being an array
|
|
38
|
+
# of columns that start with encrypted_
|
|
39
|
+
sync_cattr_reader :encrypted_columns do
|
|
40
|
+
h = {}
|
|
41
|
+
connection.tables.each do |table|
|
|
42
|
+
columns = connection.columns(table)
|
|
43
|
+
columns.each do |column|
|
|
44
|
+
if column.name.start_with?("encrypted_")
|
|
45
|
+
add_column = column.name
|
|
46
|
+
(h[table] ||= []) << add_column if add_column
|
|
47
|
+
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
50
|
+
h
|
|
48
51
|
end
|
|
49
|
-
h
|
|
50
|
-
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
# Re-encrypt all `encrypted_` columns in the relational database.
|
|
54
|
+
# Queues a Job for each table that needs re-encryption.
|
|
55
|
+
def self.start(**args)
|
|
56
|
+
encrypted_columns.keys.collect do |table|
|
|
57
|
+
create!(table_name: table, description: table, **args)
|
|
58
|
+
end
|
|
57
59
|
end
|
|
58
|
-
end
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# Re-encrypt all encrypted columns for the named table.
|
|
62
|
+
# Does not use AR models since we do not have models for all tables.
|
|
63
|
+
def perform(range)
|
|
64
|
+
start_id, end_id = range
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
columns = self.class.encrypted_columns[table_name]
|
|
67
|
+
unless columns&.size&.positive?
|
|
68
|
+
logger.error "No columns for table: #{table_name} from #{start_id} to #{end_id}"
|
|
69
|
+
return
|
|
70
|
+
end
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
logger.info "Processing: #{table_name} from #{start_id} to #{end_id}"
|
|
73
|
+
sql = "select id, #{columns.join(',')} from #{quoted_table_name} where id >= #{start_id} and id <= #{end_id}"
|
|
74
|
+
|
|
75
|
+
# Use AR to fetch all the records
|
|
76
|
+
self.class.connection.select_rows(sql).each do |row|
|
|
77
|
+
row.unshift(nil)
|
|
78
|
+
index = 1
|
|
79
|
+
sql = "update #{quoted_table_name} set "
|
|
80
|
+
updates = []
|
|
81
|
+
columns.collect do |column|
|
|
82
|
+
index += 1
|
|
83
|
+
value = row[index]
|
|
84
|
+
# Prevent re-encryption
|
|
85
|
+
unless value.blank?
|
|
86
|
+
new_value = re_encrypt(value)
|
|
87
|
+
updates << "#{column} = \"#{new_value}\"" if new_value != value
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
if updates.size.positive?
|
|
91
|
+
sql << updates.join(", ")
|
|
92
|
+
sql << " where id=#{row[1]}"
|
|
93
|
+
logger.trace sql
|
|
94
|
+
self.class.connection.execute sql
|
|
95
|
+
else
|
|
96
|
+
logger.trace { "Skipping empty values #{table_name}:#{row[1]}" }
|
|
87
97
|
end
|
|
88
|
-
end
|
|
89
|
-
if updates.size.positive?
|
|
90
|
-
sql << updates.join(", ")
|
|
91
|
-
sql << " where id=#{row[1]}"
|
|
92
|
-
logger.trace sql
|
|
93
|
-
self.class.connection.execute sql
|
|
94
|
-
else
|
|
95
|
-
logger.trace { "Skipping empty values #{table_name}:#{row[1]}" }
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
|
-
end
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
# Returns a database connection.
|
|
102
|
+
#
|
|
103
|
+
# Override this method to support other ways of obtaining a thread specific database connection.
|
|
104
|
+
def self.connection
|
|
105
|
+
ActiveRecord::Base.connection
|
|
106
|
+
end
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
private
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
def quoted_table_name
|
|
111
|
+
@quoted_table_name ||= self.class.connection.quote_table_name(table_name)
|
|
112
|
+
end
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
def re_encrypt(encrypted_value)
|
|
115
|
+
return encrypted_value if (encrypted_value == "") || encrypted_value.nil?
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
SymmetricEncryption.encrypt(SymmetricEncryption.decrypt(encrypted_value))
|
|
118
|
+
end
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
# Upload range to re-encrypt all rows in the specified table.
|
|
121
|
+
def upload_records
|
|
122
|
+
start_id = self.class.connection.select_value("select min(id) from #{quoted_table_name}").to_i
|
|
123
|
+
last_id = self.class.connection.select_value("select max(id) from #{quoted_table_name}").to_i
|
|
124
|
+
self.record_count =
|
|
125
|
+
if last_id.positive?
|
|
126
|
+
input.upload_integer_range_in_reverse_order(start_id, last_id) * input_category.slice_size
|
|
127
|
+
else
|
|
128
|
+
0
|
|
129
|
+
end
|
|
130
|
+
end
|
|
124
131
|
end
|
|
125
132
|
end
|
|
126
133
|
end
|
|
@@ -19,7 +19,7 @@ module RocketJob
|
|
|
19
19
|
field :properties, type: Hash, default: {}, user_editable: true
|
|
20
20
|
|
|
21
21
|
# File to upload
|
|
22
|
-
field :upload_file_name, type:
|
|
22
|
+
field :upload_file_name, type: IOStreams::Path, user_editable: true
|
|
23
23
|
|
|
24
24
|
# The original Input file name.
|
|
25
25
|
# Used by #upload to extract the IOStreams when present.
|
|
@@ -33,10 +33,11 @@ module RocketJob
|
|
|
33
33
|
validate :job_is_a_rocket_job
|
|
34
34
|
validate :job_implements_upload
|
|
35
35
|
validate :file_exists
|
|
36
|
+
validate :job_has_properties
|
|
36
37
|
|
|
37
38
|
# Create the job and upload the file into it.
|
|
38
39
|
def perform
|
|
39
|
-
job = job_class.
|
|
40
|
+
job = job_class.from_properties(properties)
|
|
40
41
|
job.id = job_id if job_id
|
|
41
42
|
upload_file(job)
|
|
42
43
|
job.save!
|
|
@@ -66,7 +67,10 @@ module RocketJob
|
|
|
66
67
|
elsif job.respond_to?(:full_file_name=)
|
|
67
68
|
job.full_file_name = upload_file_name
|
|
68
69
|
else
|
|
69
|
-
raise(
|
|
70
|
+
raise(
|
|
71
|
+
ArgumentError,
|
|
72
|
+
"Model #{job_class_name} must implement '#upload', or have attribute 'upload_file_name' or 'full_file_name'"
|
|
73
|
+
)
|
|
70
74
|
end
|
|
71
75
|
end
|
|
72
76
|
|
|
@@ -85,17 +89,49 @@ module RocketJob
|
|
|
85
89
|
klass = job_class
|
|
86
90
|
return if klass.nil? || klass.instance_methods.any? { |m| VALID_INSTANCE_METHODS.include?(m) }
|
|
87
91
|
|
|
88
|
-
errors.add(:job_class_name,
|
|
92
|
+
errors.add(:job_class_name,
|
|
93
|
+
"#{job_class} must implement any one of: :#{VALID_INSTANCE_METHODS.join(' :')} instance methods")
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
def file_exists
|
|
92
|
-
|
|
97
|
+
# Only check for file existence when it is a local file
|
|
98
|
+
return unless upload_file_name.is_a?(IOStreams::Paths::File)
|
|
99
|
+
return errors.add(:upload_file_name, "Upload file name can't be blank.") if upload_file_name.to_s == ""
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
return unless uri.scheme.nil? || uri.scheme == "file"
|
|
96
|
-
return if File.exist?(upload_file_name)
|
|
101
|
+
return if upload_file_name.exist?
|
|
97
102
|
|
|
98
103
|
errors.add(:upload_file_name, "Upload file: #{upload_file_name} does not exist.")
|
|
104
|
+
rescue NotImplementedError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def job_has_properties
|
|
109
|
+
klass = job_class
|
|
110
|
+
return unless klass
|
|
111
|
+
|
|
112
|
+
properties.each_pair do |k, _v|
|
|
113
|
+
next if klass.public_method_defined?("#{k}=".to_sym)
|
|
114
|
+
|
|
115
|
+
if %i[output_categories input_categories].include?(k)
|
|
116
|
+
category_class = k == :input_categories ? RocketJob::Category::Input : RocketJob::Category::Output
|
|
117
|
+
properties[k].each do |category|
|
|
118
|
+
category.each_pair do |key, _value|
|
|
119
|
+
next if category_class.public_method_defined?("#{key}=".to_sym)
|
|
120
|
+
|
|
121
|
+
errors.add(
|
|
122
|
+
:properties,
|
|
123
|
+
"Unknown Property in #{k}: Attempted to set a value for #{key}.#{k} which is not allowed on the job #{job_class_name}"
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
errors.add(
|
|
131
|
+
:properties,
|
|
132
|
+
"Unknown Property: Attempted to set a value for #{k.inspect} which is not allowed on the job #{job_class_name}"
|
|
133
|
+
)
|
|
134
|
+
end
|
|
99
135
|
end
|
|
100
136
|
end
|
|
101
137
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module RocketJob
|
|
2
|
+
class LookupCollection < Mongo::Collection
|
|
3
|
+
# Rapidly upload individual records in batches.
|
|
4
|
+
#
|
|
5
|
+
# Operates directly on a Mongo Collection to avoid the overhead of creating Mongoid objects
|
|
6
|
+
# for each and every row.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# lookup_collection(:my_lookup).upload do |io|
|
|
10
|
+
# io << {id: 123, data: "first record"}
|
|
11
|
+
# io << {id: 124, data: "second record"}
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# input_category(:my_lookup).find(id: 123).first
|
|
15
|
+
def upload(batch_size: 10_000, &block)
|
|
16
|
+
BatchUploader.upload(batch_size: batch_size, &block)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Looks up the value at the specified id.
|
|
20
|
+
# Returns [nil] if no record was found with the supplied id.
|
|
21
|
+
def lookup(id)
|
|
22
|
+
find(id: id).first
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Internal class for uploading records in batches
|
|
26
|
+
class BatchUploader
|
|
27
|
+
attr_reader :record_count
|
|
28
|
+
|
|
29
|
+
def self.upload(collection, **args)
|
|
30
|
+
writer = new(collection, **args)
|
|
31
|
+
yield(writer)
|
|
32
|
+
writer.record_count
|
|
33
|
+
ensure
|
|
34
|
+
writer&.close
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(collection, batch_size:)
|
|
38
|
+
@batch_size = batch_size
|
|
39
|
+
@record_count = 0
|
|
40
|
+
@batch_count = 0
|
|
41
|
+
@documents = []
|
|
42
|
+
@collection = collection
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def <<(record)
|
|
46
|
+
raise(ArgumentError, "Record must be a Hash") unless record.is_a?(Hash)
|
|
47
|
+
|
|
48
|
+
unless record.key?(:id) || record.key?("id") || record.key?("_id")
|
|
49
|
+
raise(ArgumentError, "Record must include an :id key")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@documents << record
|
|
53
|
+
@record_count += 1
|
|
54
|
+
@batch_count += 1
|
|
55
|
+
if @batch_count >= @batch_size
|
|
56
|
+
@collection.insert_many(@documents)
|
|
57
|
+
@documents.clear
|
|
58
|
+
@batch_count = 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def close
|
|
65
|
+
@collection.insert_many(@documents) unless @documents.empty?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -37,12 +37,10 @@ module RocketJob
|
|
|
37
37
|
# arrives, then the current job will complete the current slices and process
|
|
38
38
|
# the new higher priority job
|
|
39
39
|
field :priority, type: Integer, default: 50, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
40
|
+
validates_inclusion_of :priority, in: 1..100
|
|
40
41
|
|
|
41
42
|
# When the job completes destroy it from both the database and the UI
|
|
42
|
-
field :destroy_on_complete, type: Boolean, default: true, class_attribute: true, copy_on_restart: true
|
|
43
|
-
|
|
44
|
-
# Whether to store the results from this job
|
|
45
|
-
field :collect_output, type: Boolean, default: false, class_attribute: true
|
|
43
|
+
field :destroy_on_complete, type: Mongoid::Boolean, default: true, class_attribute: true, copy_on_restart: true
|
|
46
44
|
|
|
47
45
|
# Run this job no earlier than this time
|
|
48
46
|
field :run_at, type: Time, user_editable: true
|
|
@@ -54,14 +52,15 @@ module RocketJob
|
|
|
54
52
|
# Can be used to reduce log noise, especially during high volume calls
|
|
55
53
|
# For debugging a single job can be logged at a low level such as :trace
|
|
56
54
|
# Levels supported: :trace, :debug, :info, :warn, :error, :fatal
|
|
57
|
-
field :log_level, type:
|
|
55
|
+
field :log_level, type: Mongoid::StringifiedSymbol, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
56
|
+
validates_inclusion_of :log_level, in: SemanticLogger::LEVELS + [nil]
|
|
58
57
|
|
|
59
58
|
#
|
|
60
59
|
# Read-only attributes
|
|
61
60
|
#
|
|
62
61
|
|
|
63
62
|
# Current state, as set by the state machine. Do not modify this value directly.
|
|
64
|
-
field :state, type:
|
|
63
|
+
field :state, type: Mongoid::StringifiedSymbol, default: :queued
|
|
65
64
|
|
|
66
65
|
# When the job was created
|
|
67
66
|
field :created_at, type: Time, default: -> { Time.now }
|
|
@@ -89,17 +88,12 @@ module RocketJob
|
|
|
89
88
|
# Store the last exception for this job
|
|
90
89
|
embeds_one :exception, class_name: "RocketJob::JobException"
|
|
91
90
|
|
|
92
|
-
#
|
|
93
|
-
# and the job returned actually returned a Hash, otherwise nil
|
|
94
|
-
# Not applicable to SlicedJob jobs, since its output is stored in a
|
|
95
|
-
# separate collection
|
|
96
|
-
field :result, type: Hash
|
|
97
|
-
|
|
91
|
+
# Used when workers fetch jobs to work on.
|
|
98
92
|
index({state: 1, priority: 1, _id: 1}, background: true)
|
|
93
|
+
# Used by Mission Control to display completed jobs sorted by completion.
|
|
94
|
+
index({completed_at: 1}, background: true)
|
|
99
95
|
|
|
100
96
|
validates_presence_of :state, :failure_count, :created_at
|
|
101
|
-
validates :priority, inclusion: 1..100
|
|
102
|
-
validates :log_level, inclusion: SemanticLogger::LEVELS + [nil]
|
|
103
97
|
end
|
|
104
98
|
|
|
105
99
|
module ClassMethods
|
|
@@ -155,14 +149,8 @@ module RocketJob
|
|
|
155
149
|
|
|
156
150
|
# Scope for queued jobs that can run now
|
|
157
151
|
# I.e. Queued jobs excluding scheduled jobs
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
queued.and(RocketJob::Job.where(run_at: nil).or(:run_at.lte => Time.now))
|
|
161
|
-
end
|
|
162
|
-
else
|
|
163
|
-
def queued_now
|
|
164
|
-
queued.or({run_at: nil}, :run_at.lte => Time.now)
|
|
165
|
-
end
|
|
152
|
+
def queued_now
|
|
153
|
+
queued.and(RocketJob::Job.where(run_at: nil).or(:run_at.lte => Time.now))
|
|
166
154
|
end
|
|
167
155
|
|
|
168
156
|
# Defines all the fields that are accessible on the Document
|
|
@@ -183,43 +171,30 @@ module RocketJob
|
|
|
183
171
|
#
|
|
184
172
|
# @return [ Field ] The generated field
|
|
185
173
|
def field(name, options)
|
|
186
|
-
if options.delete(:user_editable) == true
|
|
187
|
-
self.user_editable_fields += [name.to_sym]
|
|
174
|
+
if (options.delete(:user_editable) == true) && !user_editable_fields.include?(name.to_sym)
|
|
175
|
+
self.user_editable_fields += [name.to_sym]
|
|
188
176
|
end
|
|
177
|
+
|
|
189
178
|
if options.delete(:class_attribute) == true
|
|
190
179
|
class_attribute(name, instance_accessor: false)
|
|
191
180
|
public_send("#{name}=", options[:default]) if options.key?(:default)
|
|
192
181
|
options[:default] = -> { self.class.public_send(name) }
|
|
193
182
|
end
|
|
194
|
-
|
|
195
|
-
|
|
183
|
+
|
|
184
|
+
if (options.delete(:copy_on_restart) == true) && !rocket_job_restart_attributes.include?(name.to_sym)
|
|
185
|
+
self.rocket_job_restart_attributes += [name.to_sym]
|
|
196
186
|
end
|
|
197
|
-
super(name, options)
|
|
198
|
-
end
|
|
199
187
|
|
|
200
|
-
|
|
201
|
-
def rocket_job
|
|
202
|
-
warn "Replace calls to .rocket_job with calls to set class instance variables. For example: self.priority = 50"
|
|
203
|
-
yield(self)
|
|
188
|
+
super(name, options)
|
|
204
189
|
end
|
|
205
190
|
|
|
206
|
-
#
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
191
|
+
# Builds this job instance from the supplied properties hash.
|
|
192
|
+
# Overridden by batch to support child objects.
|
|
193
|
+
def from_properties(properties)
|
|
194
|
+
new(properties)
|
|
210
195
|
end
|
|
211
196
|
end
|
|
212
197
|
|
|
213
|
-
# Returns [true|false] whether to collect nil results from running this batch
|
|
214
|
-
def collect_nil_output?
|
|
215
|
-
collect_output? ? (collect_nil_output == true) : false
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Returns [true|false] whether to collect the results from running this batch
|
|
219
|
-
def collect_output?
|
|
220
|
-
collect_output == true
|
|
221
|
-
end
|
|
222
|
-
|
|
223
198
|
# Returns [Float] the number of seconds the job has taken
|
|
224
199
|
# - Elapsed seconds to process the job from when a worker first started working on it
|
|
225
200
|
# until now if still running, or until it was completed
|
|
@@ -282,7 +257,6 @@ module RocketJob
|
|
|
282
257
|
# Returns [Hash] status of this job
|
|
283
258
|
def as_json
|
|
284
259
|
attrs = serializable_hash(methods: %i[seconds duration])
|
|
285
|
-
attrs.delete("result") unless collect_output?
|
|
286
260
|
attrs.delete("failure_count") unless failure_count.positive?
|
|
287
261
|
if queued?
|
|
288
262
|
attrs.delete("started_at")
|
|
@@ -319,16 +293,17 @@ module RocketJob
|
|
|
319
293
|
h = as_json
|
|
320
294
|
h.delete("seconds")
|
|
321
295
|
h.dup.each_pair do |k, v|
|
|
322
|
-
|
|
296
|
+
case v
|
|
297
|
+
when Time
|
|
323
298
|
h[k] = v.in_time_zone(time_zone).to_s
|
|
324
|
-
|
|
299
|
+
when BSON::ObjectId
|
|
325
300
|
h[k] = v.to_s
|
|
326
301
|
end
|
|
327
302
|
end
|
|
328
303
|
h
|
|
329
304
|
end
|
|
330
305
|
|
|
331
|
-
# Returns [
|
|
306
|
+
# Returns [true|false] whether the worker runs on a particular server.
|
|
332
307
|
def worker_on_server?(server_name)
|
|
333
308
|
return false unless worker_name.present? && server_name.present?
|
|
334
309
|
|