sp-job 0.2.3 → 0.3.22
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/VERSION +1 -1
- data/bin/configure +40 -0
- data/lib/sp-job.rb +21 -2
- data/lib/sp/job/back_burner.rb +350 -68
- data/lib/sp/job/broker.rb +18 -16
- data/lib/sp/job/broker_http_client.rb +80 -20
- data/lib/sp/job/broker_oauth2_client.rb +12 -4
- data/lib/sp/job/common.rb +876 -62
- data/lib/sp/job/configure/configure.rb +640 -0
- data/lib/sp/job/curl_http_client.rb +100 -0
- data/lib/sp/job/easy_http_client.rb +94 -0
- data/lib/sp/job/http_client.rb +51 -0
- data/lib/sp/job/job_db_adapter.rb +38 -36
- data/lib/sp/job/jsonapi_error.rb +31 -74
- data/lib/sp/job/jwt.rb +55 -5
- data/lib/sp/job/mail_queue.rb +9 -2
- data/lib/sp/job/manticore_http_client.rb +94 -0
- data/lib/sp/job/pg_connection.rb +90 -10
- data/lib/sp/job/query_params.rb +45 -0
- data/lib/sp/job/rfc822.rb +13 -0
- data/lib/sp/job/session.rb +239 -0
- data/lib/sp/job/unique_file.rb +37 -1
- data/lib/sp/job/uploaded_image_converter.rb +27 -19
- data/lib/sp/job/worker.rb +51 -1
- data/lib/sp/job/worker_thread.rb +22 -7
- data/lib/sp/jsonapi.rb +24 -0
- data/lib/sp/jsonapi/adapters/base.rb +177 -0
- data/lib/sp/jsonapi/adapters/db.rb +26 -0
- data/lib/sp/jsonapi/adapters/raw_db.rb +96 -0
- data/lib/sp/jsonapi/exceptions.rb +54 -0
- data/lib/sp/jsonapi/model/base.rb +31 -0
- data/lib/sp/jsonapi/model/concerns/attributes.rb +91 -0
- data/lib/sp/jsonapi/model/concerns/model.rb +39 -0
- data/lib/sp/jsonapi/model/concerns/persistence.rb +212 -0
- data/lib/sp/jsonapi/model/concerns/serialization.rb +57 -0
- data/lib/sp/jsonapi/parameters.rb +54 -0
- data/lib/sp/jsonapi/service.rb +96 -0
- data/lib/tasks/configure.rake +2 -496
- data/sp-job.gemspec +3 -2
- metadata +24 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd0c49ededce3afbbd5e09db28e113b0d6e66790
|
|
4
|
+
data.tar.gz: 511e7878308035eb4e69bb7864c9205479cbdba8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ac3252e84c0994f71c0c1d748080e7d6313378eecabc0f7caa1427cb8bbf77943a563c2bdf09a8242499394022ec4e68b45072dca3d52032553bf3fcf6b7801
|
|
7
|
+
data.tar.gz: 683adc1dbe186ad0034e1762180332b733cb1fd3233139079a05e89ec6ab0f5f1c9e9d4e342f2a36f528d344f46850b5dcd9ae640f2ce749462c6d67e96f795f
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.22
|
data/bin/configure
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of sp-job.
|
|
5
|
+
#
|
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
19
|
+
# encoding: utf-8
|
|
20
|
+
#
|
|
21
|
+
require 'bundler/setup'
|
|
22
|
+
require 'optparse'
|
|
23
|
+
require 'sp/job/configure/configure'
|
|
24
|
+
|
|
25
|
+
$args = {}
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Parse command line arguments
|
|
29
|
+
#
|
|
30
|
+
$option_parser = OptionParser.new do |opts|
|
|
31
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]"
|
|
32
|
+
opts.on('-m', '--dump' , "dump merged configuration") { $args[:print_config] = true }
|
|
33
|
+
opts.on('-o', '--overwrite' , "overwrite system, project and user files, BE CAREFULL!") { $args[:action] = 'overwrite' }
|
|
34
|
+
opts.on('-h', '--hotfix' , "hotfix modifies only project and user files") { $args[:action] = 'hotfix' }
|
|
35
|
+
opts.on('-d', '--debug' , "developer mode: log to stdout and print job") { $args[:debug] = true }
|
|
36
|
+
end
|
|
37
|
+
$option_parser.parse!
|
|
38
|
+
|
|
39
|
+
run_configure($args)
|
|
40
|
+
|
data/lib/sp-job.rb
CHANGED
|
@@ -35,12 +35,31 @@ require 'ostruct'
|
|
|
35
35
|
require 'json'
|
|
36
36
|
require 'mail'
|
|
37
37
|
require 'uri'
|
|
38
|
+
require 'securerandom'
|
|
38
39
|
|
|
39
40
|
require 'sp/job'
|
|
40
41
|
require 'sp/job/version'
|
|
41
42
|
require 'sp/job/worker'
|
|
42
43
|
require 'sp/job/worker_thread'
|
|
44
|
+
require 'sp/job/rfc822'
|
|
43
45
|
require 'sp/job/common'
|
|
44
46
|
require 'sp/job/unique_file'
|
|
45
|
-
require 'sp/job/
|
|
46
|
-
require 'sp/job/
|
|
47
|
+
require 'sp/job/jwt'
|
|
48
|
+
require 'sp/job/http_client'
|
|
49
|
+
require 'sp/job/query_params'
|
|
50
|
+
require 'sp/job/broker_http_client' unless RUBY_ENGINE == 'jruby'
|
|
51
|
+
require 'sp/job/broker_oauth2_client' unless RUBY_ENGINE == 'jruby'
|
|
52
|
+
|
|
53
|
+
# JSONAPI library classes
|
|
54
|
+
require 'sp/jsonapi'
|
|
55
|
+
require 'sp/jsonapi/exceptions'
|
|
56
|
+
# Parameters class
|
|
57
|
+
require 'sp/jsonapi/parameters'
|
|
58
|
+
# Service classes
|
|
59
|
+
require 'sp/jsonapi/service'
|
|
60
|
+
# Adpater classes
|
|
61
|
+
require 'sp/jsonapi/adapters/base'
|
|
62
|
+
require 'sp/jsonapi/adapters/raw_db'
|
|
63
|
+
require 'sp/jsonapi/adapters/db'
|
|
64
|
+
|
|
65
|
+
require 'sp/jsonapi/model/base'
|
data/lib/sp/job/back_burner.rb
CHANGED
|
@@ -19,10 +19,80 @@
|
|
|
19
19
|
# encoding: utf-8
|
|
20
20
|
#
|
|
21
21
|
require 'sp/job/pg_connection'
|
|
22
|
+
require 'sp/job/session'
|
|
23
|
+
require 'sp/job/broker'
|
|
22
24
|
require 'sp/job/job_db_adapter'
|
|
25
|
+
require 'sp/job/jsonapi_error'
|
|
26
|
+
require 'sp/job/broker_oauth2_client' unless RUBY_ENGINE == 'jruby' # ::SP::Job::BrokerOAuth2Client::InvalidToken
|
|
23
27
|
require 'roadie'
|
|
24
28
|
require 'thread'
|
|
25
29
|
|
|
30
|
+
#
|
|
31
|
+
# Helper class that encapsulates the objects needed to access each cluster
|
|
32
|
+
#
|
|
33
|
+
class ClusterMember
|
|
34
|
+
|
|
35
|
+
attr_reader :redis # connection to cluster redis
|
|
36
|
+
attr_reader :session # access to redis session
|
|
37
|
+
attr_reader :db # database connection
|
|
38
|
+
attr_reader :number # cluster number, 1, 2 ...
|
|
39
|
+
attr_reader :config # cluster configuration read from the conf.json
|
|
40
|
+
attr_reader :broker # Driver for casper broker OAUTH 2.0 authentication
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Create the cluster member wrapper
|
|
44
|
+
#
|
|
45
|
+
# @param configuration the cluster member configuration structure
|
|
46
|
+
# @param serviceId the service prefix used by casper redis keys
|
|
47
|
+
# @param db a fb connection to reuse or nil if a new one should be created
|
|
48
|
+
#
|
|
49
|
+
def initialize (configuration:, serviceId:, db: nil)
|
|
50
|
+
@redis = Redis.new(:host => configuration[:redis][:casper][:host], :port => configuration[:redis][:casper][:port], :db => 0)
|
|
51
|
+
@session = ::SP::Job::Session.new(configuration: configuration, serviceId: serviceId, redis: @redis, programName: $args[:program_name])
|
|
52
|
+
if db.nil?
|
|
53
|
+
@db = ::SP::Job::PGConnection.new(owner: 'back_burner', config: configuration[:db])
|
|
54
|
+
else
|
|
55
|
+
@db = db
|
|
56
|
+
end
|
|
57
|
+
@number = configuration[:number]
|
|
58
|
+
@config = configuration
|
|
59
|
+
unless RUBY_ENGINE == 'jruby'
|
|
60
|
+
@broker = ::SP::Job::Broker::Job.new(config: {
|
|
61
|
+
:service_id => serviceId,
|
|
62
|
+
:oauth2 => configuration[:oauth2],
|
|
63
|
+
:redis => @redis
|
|
64
|
+
})
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# Returns the global logger object, borrowed from common.rb
|
|
70
|
+
#
|
|
71
|
+
def self.logger
|
|
72
|
+
Backburner.configuration.logger
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# Creates the global structure that contains the cluster configuration
|
|
77
|
+
#
|
|
78
|
+
def self.configure_cluster
|
|
79
|
+
$cluster_members = {}
|
|
80
|
+
|
|
81
|
+
$config[:cluster][:members].each do |cfg|
|
|
82
|
+
next if !cfg[:exclude_member].nil? && cfg[:exclude_member] == true
|
|
83
|
+
cfg[:db][:conn_str] = pg_conn_str(cfg[:db])
|
|
84
|
+
if cfg[:number] == $config[:runs_on_cluster]
|
|
85
|
+
$cluster_members[cfg[:number]] = ClusterMember.new(configuration: cfg, serviceId: $config[:service_id], db: $pg)
|
|
86
|
+
else
|
|
87
|
+
$cluster_members[cfg[:number]] = ClusterMember.new(configuration: cfg, serviceId: $config[:service_id])
|
|
88
|
+
end
|
|
89
|
+
logger.info "Cluster member #{cfg[:number]}: #{cfg[:url]} db=#{cfg[:db][:host]}:#{cfg[:db][:port]}(#{cfg[:db][:dbname]}) redis=#{cfg[:redis][:casper][:host]}:#{cfg[:redis][:casper][:port]}#{' <=' if cfg[:number] == $config[:runs_on_cluster]}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
26
96
|
module SP
|
|
27
97
|
module Job
|
|
28
98
|
|
|
@@ -39,7 +109,7 @@ module SP
|
|
|
39
109
|
attr_reader :args
|
|
40
110
|
|
|
41
111
|
def initialize (args:, job: nil)
|
|
42
|
-
super(args[:message] || $
|
|
112
|
+
super(args[:message] || $args[:program_name])
|
|
43
113
|
@job = job
|
|
44
114
|
@args = args
|
|
45
115
|
end
|
|
@@ -57,11 +127,15 @@ module SP
|
|
|
57
127
|
end
|
|
58
128
|
end
|
|
59
129
|
|
|
60
|
-
class ThreadData < Struct.new(:job_status, :report_time_stamp, :exception_reported, :job_id, :publish_key, :job_key, :current_job, :job_notification, :jsonapi)
|
|
130
|
+
class ThreadData < Struct.new(:job_status, :report_time_stamp, :exception_reported, :job_id, :publish_key, :job_key, :current_job, :job_notification, :jsonapi, :job_tube, :notification_lock, :notification_lock_key)
|
|
61
131
|
def initialize
|
|
62
|
-
|
|
63
|
-
if $config[:
|
|
64
|
-
|
|
132
|
+
self.job_status = {}
|
|
133
|
+
if $config[:jsonapi] && $config[:jsonapi][:prefix]
|
|
134
|
+
if RUBY_ENGINE == 'jruby' # TODO suck in the base class from SP-DUH
|
|
135
|
+
self.jsonapi = SP::JSONAPI::Service.new($pg, $config[:jsonapi][:prefix], SP::Job::JobDbAdapter)
|
|
136
|
+
else
|
|
137
|
+
self.jsonapi = SP::Duh::JSONAPI::Service.new($pg, $config[:jsonapi][:prefix], SP::Job::JobDbAdapter)
|
|
138
|
+
end
|
|
65
139
|
end
|
|
66
140
|
end
|
|
67
141
|
end
|
|
@@ -75,6 +149,15 @@ module SP
|
|
|
75
149
|
end
|
|
76
150
|
end
|
|
77
151
|
|
|
152
|
+
#
|
|
153
|
+
# Helper to build BG connection strings
|
|
154
|
+
#
|
|
155
|
+
def pg_conn_str (config, app_name = nil)
|
|
156
|
+
if app_name.nil?
|
|
157
|
+
app_name = "application_name=#{$args[:program_name]}"
|
|
158
|
+
end
|
|
159
|
+
return "host=#{config[:host]} port=#{config[:port]} dbname=#{config[:dbname]} user=#{config[:user]}#{config[:password] && config[:password].size != 0 ? ' password='+ config[:password] : '' } #{app_name}"
|
|
160
|
+
end
|
|
78
161
|
|
|
79
162
|
#
|
|
80
163
|
# Initialize global data needed for configuration
|
|
@@ -82,6 +165,7 @@ end
|
|
|
82
165
|
$prefix = OS.mac? ? '/usr/local' : ''
|
|
83
166
|
$rollbar = false
|
|
84
167
|
$min_progress = 3
|
|
168
|
+
$gracefull_exit = false
|
|
85
169
|
$args = {
|
|
86
170
|
stdout: false,
|
|
87
171
|
log_level: 'info',
|
|
@@ -95,11 +179,11 @@ $args = {
|
|
|
95
179
|
#
|
|
96
180
|
$option_parser = OptionParser.new do |opts|
|
|
97
181
|
opts.banner = "Usage: #{$PROGRAM_NAME} ARGS"
|
|
98
|
-
opts.on('-c', '--config=CONFIG.JSON', "path to json configuration file (default: '#{$args[:
|
|
99
|
-
opts.on('-l', '--log=LOGFILE' , "path to log file (default: '#{$args[:
|
|
100
|
-
opts.on('-d', '--debug' , "developer mode: log to stdout and print job")
|
|
101
|
-
opts.on('-v', '--log_level=LEVEL' , "Log level DEBUG, INFO, WARN, ERROR, FATAL")
|
|
102
|
-
opts.on('-i', '--index=IDX' , "systemd instance index")
|
|
182
|
+
opts.on('-c', '--config=CONFIG.JSON', "path to json configuration file (default: '#{$args[:config_file]}')") { |v| $args[:config_file] = File.expand_path(v) }
|
|
183
|
+
opts.on('-l', '--log=LOGFILE' , "path to log file (default: '#{$args[:default_log_file]}')") { |v| $args[:log_file] = File.expand_path(v) }
|
|
184
|
+
opts.on('-d', '--debug' , "developer mode: log to stdout and print job") { $args[:debug] = true }
|
|
185
|
+
opts.on('-v', '--log_level=LEVEL' , "Log level DEBUG, INFO, WARN, ERROR, FATAL") { |v| $args[:log_level] = v }
|
|
186
|
+
opts.on('-i', '--index=IDX' , "systemd instance index") { |v| $args[:index] = v }
|
|
103
187
|
end
|
|
104
188
|
$option_parser.parse!
|
|
105
189
|
|
|
@@ -131,13 +215,6 @@ File.write("#{$prefix}/var/run/jobs/#{$args[:program_name]}#{$args[:index].nil?
|
|
|
131
215
|
$config = JSON.parse(File.read(File.expand_path($args[:config_file])), symbolize_names: true)
|
|
132
216
|
$min_progress = $config[:options][:min_progress]
|
|
133
217
|
|
|
134
|
-
#
|
|
135
|
-
# Global data for mutex and sync
|
|
136
|
-
#
|
|
137
|
-
$threads = [ Thread.current ]
|
|
138
|
-
$thread_data = {}
|
|
139
|
-
$thread_data[Thread.current] = ::SP::Job::ThreadData.new
|
|
140
|
-
|
|
141
218
|
#
|
|
142
219
|
# Sanity check we only support multithreading on JRUBY
|
|
143
220
|
#
|
|
@@ -156,7 +233,13 @@ end
|
|
|
156
233
|
# Configure rollbar
|
|
157
234
|
#
|
|
158
235
|
unless $config[:rollbar].nil?
|
|
159
|
-
|
|
236
|
+
|
|
237
|
+
if $config[:rollbar][:enabled] == 'false'
|
|
238
|
+
$rollbar = false
|
|
239
|
+
else
|
|
240
|
+
$rollbar = true
|
|
241
|
+
end
|
|
242
|
+
|
|
160
243
|
Rollbar.configure do |config|
|
|
161
244
|
config.access_token = $config[:rollbar][:token] if $config[:rollbar][:token]
|
|
162
245
|
config.environment = $config[:rollbar][:environment] if $config[:rollbar] && $config[:rollbar][:environment]
|
|
@@ -166,36 +249,132 @@ end
|
|
|
166
249
|
#
|
|
167
250
|
# Configure backburner queue
|
|
168
251
|
#
|
|
252
|
+
|
|
253
|
+
class InternalBrokerException
|
|
254
|
+
|
|
255
|
+
def self.handle(task:, exception:, hooks:, callback:)
|
|
256
|
+
|
|
257
|
+
response = InternalBrokerException.translate_to_response(e: exception)
|
|
258
|
+
job_id = task.id
|
|
259
|
+
job_body = JSON.parse(task.body, symbolize_names: true)
|
|
260
|
+
|
|
261
|
+
job_options = hooks[:var].invoke_hook_events(hooks[:klass], :on_raise_response_was_sent, job_id, job_body, response)
|
|
262
|
+
if nil == job_options || false == job_options.is_a?(Array) || 1 != job_options.size || false == job_options[0].is_a?(Hash)
|
|
263
|
+
rv = { response: response }
|
|
264
|
+
else
|
|
265
|
+
job_options = job_options[0]
|
|
266
|
+
# rollbar it
|
|
267
|
+
if true == job_options.include?(:rollbar) && true == job_options[:rollbar]
|
|
268
|
+
InternalBrokerException.rollbar(job_id: job_id, job_body: job_body, exception: exception)
|
|
269
|
+
end
|
|
270
|
+
#
|
|
271
|
+
rv = { bury: job_options[:bury] , raise: job_options[:raise], response: job_options[:response] || response }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# send response
|
|
275
|
+
callback.call(rv.delete(:response))
|
|
276
|
+
|
|
277
|
+
# done
|
|
278
|
+
rv
|
|
279
|
+
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def self.translate_to_response(e:)
|
|
283
|
+
args = {}
|
|
284
|
+
args[:status] = 'error'
|
|
285
|
+
args[:action] = 'response'
|
|
286
|
+
if e.is_a?(::SP::Job::JSONAPI::Error)
|
|
287
|
+
args[:status_code] = e.status_code
|
|
288
|
+
args[:content_type] = e.content_type
|
|
289
|
+
args[:response] = e.body
|
|
290
|
+
elsif e.is_a?(::SP::Job::BrokerOAuth2Client::InvalidToken)
|
|
291
|
+
args[:status_code] = 401
|
|
292
|
+
args[:content_type] = ''
|
|
293
|
+
args[:response] = ''
|
|
294
|
+
else
|
|
295
|
+
e = ::SP::Job::JSONAPI::Error.new(status: 500, code: '999', detail: e.message)
|
|
296
|
+
args[:status_code] = e.status_code
|
|
297
|
+
args[:content_type] = e.content_type
|
|
298
|
+
args[:response] = e.body
|
|
299
|
+
end
|
|
300
|
+
args
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
#
|
|
304
|
+
# Report exception to rollbar
|
|
305
|
+
#
|
|
306
|
+
# @param j Job ID
|
|
307
|
+
# @param a Job Body
|
|
308
|
+
# @param e Exception
|
|
309
|
+
#
|
|
310
|
+
def self.rollbar(job_id:, job_body:, exception:)
|
|
311
|
+
$roolbar_mutex.synchronize {
|
|
312
|
+
if $rollbar
|
|
313
|
+
if exception.instance_of? ::SP::Job::JobException
|
|
314
|
+
exception.job[:password] = '<redacted>'
|
|
315
|
+
Rollbar.error(exception, exception.message, { job: exception.job, args: exception.args})
|
|
316
|
+
elsif exception.is_a?(::SP::Job::JSONAPI::Error)
|
|
317
|
+
[:access_token, :refresh_token, :password].each do | s |
|
|
318
|
+
if job_body.has_key?(s)
|
|
319
|
+
job_body[s] = '<redacted>'
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
Rollbar.error(exception, exception.message, { job: job_id, args: job_body, response: exception.body })
|
|
323
|
+
else
|
|
324
|
+
Rollbar.error(exception)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
}
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
end
|
|
331
|
+
|
|
169
332
|
Backburner.configure do |config|
|
|
170
333
|
|
|
171
334
|
config.beanstalk_url = "beanstalk://#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
|
|
172
335
|
config.on_error = lambda { |e|
|
|
173
336
|
td = thread_data
|
|
337
|
+
|
|
338
|
+
# ensure currently open ( if any ) transaction rollback
|
|
339
|
+
$pg.rollback unless ! $pg
|
|
340
|
+
|
|
174
341
|
if td.exception_reported == false
|
|
175
342
|
td.exception_reported = true
|
|
176
343
|
if e.instance_of? Beaneater::DeadlineSoonError
|
|
177
344
|
logger.warn "got a deadline warning".red
|
|
178
345
|
else
|
|
179
346
|
begin
|
|
180
|
-
|
|
181
|
-
|
|
347
|
+
if $config[:options] && $config[:options][:source] == 'broker'
|
|
348
|
+
send_response(InternalBrokerException.translate_to_response(e:e))
|
|
349
|
+
else
|
|
350
|
+
if e.is_a?(::SP::Job::JobAborted) || e.is_a?(::SP::Job::JobException)
|
|
351
|
+
raise_error(message: e)
|
|
352
|
+
else
|
|
353
|
+
raise_error(message: 'i18n_unexpected_server_error')
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
rescue
|
|
182
357
|
# Do not retrow!!!!
|
|
183
358
|
end
|
|
184
359
|
end
|
|
185
360
|
end
|
|
186
|
-
|
|
187
361
|
# Report exception to rollbar
|
|
188
362
|
$roolbar_mutex.synchronize {
|
|
189
363
|
if $rollbar
|
|
190
364
|
if e.instance_of? ::SP::Job::JobException
|
|
191
365
|
e.job[:password] = '<redacted>'
|
|
192
366
|
Rollbar.error(e, e.message, { job: e.job, args: e.args})
|
|
367
|
+
elsif e.is_a?(::SP::Job::JSONAPI::Error)
|
|
368
|
+
Rollbar.error(e, e.body)
|
|
193
369
|
else
|
|
194
370
|
Rollbar.error(e)
|
|
195
371
|
end
|
|
196
372
|
end
|
|
197
373
|
}
|
|
198
374
|
|
|
375
|
+
# Signal job termination
|
|
376
|
+
td.job_id = nil
|
|
377
|
+
|
|
199
378
|
# Catch fatal exception that must be handled with a restarts (systemctl will restart us)
|
|
200
379
|
case e
|
|
201
380
|
when PG::UnableToSend, PG::AdminShutdown, PG::ConnectionBad
|
|
@@ -307,34 +486,31 @@ module Backburner
|
|
|
307
486
|
|
|
308
487
|
module Logger
|
|
309
488
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
else
|
|
318
|
-
|
|
319
|
-
def log_job_begin(name, args)
|
|
320
|
-
log_info "Work job #{name}"
|
|
321
|
-
Thread.current[:job_started_at] = Time.now
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# Print out when a job completed
|
|
325
|
-
# If message is nil, job is considered complete
|
|
326
|
-
def log_job_end(name, message = nil)
|
|
327
|
-
ellapsed = Time.now - Thread.current[:job_started_at]
|
|
328
|
-
ms = (ellapsed.to_f * 1000).to_i
|
|
329
|
-
action_word = message ? 'Finished' : 'Completed'
|
|
330
|
-
log_info("#{action_word} #{name} in #{ms}ms #{message}")
|
|
489
|
+
def log_job_begin(name, args)
|
|
490
|
+
param_log = ''
|
|
491
|
+
args = args[0]
|
|
492
|
+
[ :user_id, :entity_id, :entity_schema, :sharded_schema, :subentity_id, :subentity_prefix, :subentity_schema, :action].each do |key|
|
|
493
|
+
if args.has_key?(key) && !(args[key].nil? || args[key].empty?)
|
|
494
|
+
param_log += "#{key}: #{args[key]},"
|
|
495
|
+
end
|
|
331
496
|
end
|
|
497
|
+
log_info "Job ##{args[:id]} started #{name}: #{param_log}".white
|
|
498
|
+
Thread.current[:job_started_at] = Time.now
|
|
499
|
+
end
|
|
332
500
|
|
|
501
|
+
# Print out when a job completed
|
|
502
|
+
# If message is nil, job is considered complete
|
|
503
|
+
def log_job_end(name, message = nil)
|
|
504
|
+
ellapsed = Time.now - Thread.current[:job_started_at]
|
|
505
|
+
ms = (ellapsed.to_f * 1000).to_i
|
|
506
|
+
action_word = message ? 'finished' : 'completed'
|
|
507
|
+
log_info "Job ##{$thread_data[Thread.current][:current_job][:id]} #{action_word} (#{name}) in #{ms}ms #{message}".white
|
|
333
508
|
end
|
|
509
|
+
|
|
334
510
|
end
|
|
335
511
|
|
|
336
512
|
class Job
|
|
337
|
-
|
|
513
|
+
include SP::Job::Common # to bring in logger and report_error into this class
|
|
338
514
|
|
|
339
515
|
# Processes a job and handles any failure, deleting the job once complete
|
|
340
516
|
#
|
|
@@ -342,16 +518,24 @@ module Backburner
|
|
|
342
518
|
# @task.process
|
|
343
519
|
#
|
|
344
520
|
def process
|
|
521
|
+
td = thread_data
|
|
522
|
+
|
|
345
523
|
# Invoke the job setup function, bailout if the setup returns false
|
|
346
524
|
unless job_class.respond_to?(:prepare_job) && job_class.prepare_job(*args)
|
|
347
|
-
task.delete
|
|
348
525
|
logger.warn "Delete stale or preempted task".red
|
|
526
|
+
|
|
527
|
+
# Signal job termination and remove from queue
|
|
528
|
+
td.job_id = nil
|
|
529
|
+
task.delete
|
|
349
530
|
return false
|
|
350
531
|
end
|
|
351
532
|
|
|
352
533
|
# Invoke before hook and stop if false
|
|
353
534
|
res = @hooks.invoke_hook_events(job_class, :before_perform, *args)
|
|
535
|
+
@hooks.invoke_hook_events(job_class, :prepend_platform_configuration, *args)
|
|
354
536
|
unless res
|
|
537
|
+
# Signal job termination and remove from queue
|
|
538
|
+
td.job_id = nil
|
|
355
539
|
task.delete
|
|
356
540
|
return false
|
|
357
541
|
end
|
|
@@ -362,76 +546,174 @@ module Backburner
|
|
|
362
546
|
# b) ttr == 1, so that we don't accidentally set it to never time out
|
|
363
547
|
# NB: A ttr of 1 will likely result in race conditions between
|
|
364
548
|
# Backburner and beanstalkd and should probably be avoided
|
|
365
|
-
|
|
549
|
+
if task.stats.nil?
|
|
550
|
+
ttr = 60 # Experimental
|
|
551
|
+
elsif task.ttr > 1
|
|
552
|
+
ttr = task.ttr - 1
|
|
553
|
+
else
|
|
554
|
+
ttr = task.ttr
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
timeout_job_after(ttr) { job_class.perform(*args) }
|
|
366
558
|
end
|
|
367
559
|
task.delete
|
|
368
560
|
# Invoke after perform hook
|
|
369
561
|
@hooks.invoke_hook_events(job_class, :after_perform, *args)
|
|
370
|
-
|
|
562
|
+
# ensure currently open ( if any ) transaction rollback
|
|
563
|
+
$pg.rollback unless ! $pg
|
|
564
|
+
rescue ::SP::Job::JobAborted
|
|
371
565
|
#
|
|
372
566
|
# This exception:
|
|
373
567
|
# 1. is sent to the rollbar
|
|
374
568
|
# 2. does not bury the job, instead the job is deleted
|
|
375
569
|
#
|
|
376
|
-
logger.debug "Received job aborted exception #{Thread.current}".yellow
|
|
377
570
|
unless task.nil?
|
|
378
|
-
logger.debug 'Task deleted'.yellow
|
|
379
571
|
task.delete
|
|
380
572
|
end
|
|
381
573
|
# Invoke after perform hook
|
|
382
574
|
@hooks.invoke_hook_events(job_class, :after_perform, *args)
|
|
383
|
-
|
|
575
|
+
td.job_id = nil
|
|
576
|
+
# ensure currently open ( if any ) transaction rollback
|
|
577
|
+
$pg.rollback unless ! $pg
|
|
384
578
|
rescue ::SP::Job::JobCancelled => jc
|
|
385
579
|
#
|
|
386
580
|
# This exception:
|
|
387
581
|
# 1. is not sent to the rollbar
|
|
388
582
|
# 2. does not bury the job, instead the job is deleted
|
|
389
583
|
#
|
|
390
|
-
logger.
|
|
584
|
+
logger.info "Received job cancellation exception #{Thread.current}".yellow
|
|
391
585
|
unless task.nil?
|
|
392
|
-
logger.
|
|
393
|
-
|
|
586
|
+
logger.info 'Task deleted'.yellow
|
|
587
|
+
begin
|
|
588
|
+
task.delete
|
|
589
|
+
rescue Beaneater::NotFoundError => bnfe
|
|
590
|
+
@hooks.invoke_hook_events(job_class, :on_failure, bnfe, *args)
|
|
591
|
+
end
|
|
394
592
|
end
|
|
395
593
|
@hooks.invoke_hook_events(job_class, :on_failure, jc, *args)
|
|
396
|
-
|
|
594
|
+
error_handler(message: 'i18n_job_cancelled', status: 'cancelled')
|
|
397
595
|
if $redis_mutex.nil?
|
|
398
|
-
$redis.hset(
|
|
596
|
+
$redis.hset(td.job_key, 'cancelled', true)
|
|
399
597
|
else
|
|
400
598
|
$redis_mutex.synchronize {
|
|
401
|
-
$redis.hset(
|
|
599
|
+
$redis.hset(td.job_key, 'cancelled', true)
|
|
402
600
|
}
|
|
403
601
|
end
|
|
404
|
-
|
|
602
|
+
td.job_id = nil
|
|
603
|
+
# ensure currently open ( if any ) transaction rollback
|
|
604
|
+
$pg.rollback unless ! $pg
|
|
405
605
|
rescue => e
|
|
406
|
-
|
|
407
|
-
|
|
606
|
+
# ensure currently open ( if any ) transaction rollback
|
|
607
|
+
|
|
608
|
+
if $pg
|
|
609
|
+
$pg.rollback
|
|
610
|
+
if e.is_a?(Backburner::Job::JobTimeout)
|
|
611
|
+
logger.info 'RESET PG connection because Backburner::Job::JobTimeout could be inside PG'.red
|
|
612
|
+
$pg.connect()
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# if we're in broker mode
|
|
617
|
+
if $config[:options] && $config[:options][:source] == 'broker'
|
|
618
|
+
# prepare next action for this exception
|
|
619
|
+
exception_options = {
|
|
620
|
+
bury: $config[:options].has_key?(:bury) ? $config[:options][:bury] || false : false,
|
|
621
|
+
raise: true
|
|
622
|
+
}
|
|
623
|
+
begin
|
|
624
|
+
tmp = InternalBrokerException.handle(task: task, exception: e, hooks: { klass: job_class, var:@hooks }, callback: method(:send_response))
|
|
625
|
+
exception_options[:bury] = tmp.has_key?(:bury) ? tmp[:bury] : exception_options[:bury]
|
|
626
|
+
exception_options[:raise] = tmp.has_key?(:raise) ? tmp[:raise] : exception_options[:raise]
|
|
627
|
+
rescue => ne
|
|
628
|
+
@hooks.invoke_hook_events(job_class, :on_failure, ne, *args)
|
|
629
|
+
raise ne
|
|
630
|
+
end
|
|
631
|
+
# delete it now?
|
|
632
|
+
if nil != task
|
|
633
|
+
if true == exception_options[:bury]
|
|
634
|
+
task.bury
|
|
635
|
+
else
|
|
636
|
+
task.delete
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
# re-raise?
|
|
640
|
+
if true == exception_options[:raise]
|
|
641
|
+
raise e
|
|
642
|
+
end
|
|
643
|
+
else
|
|
644
|
+
@hooks.invoke_hook_events(job_class, :on_failure, e, *args)
|
|
645
|
+
raise e
|
|
646
|
+
end
|
|
647
|
+
# Signal job termination
|
|
648
|
+
td.job_id = nil
|
|
408
649
|
end
|
|
409
650
|
end
|
|
651
|
+
|
|
410
652
|
end
|
|
411
653
|
|
|
412
654
|
# Mix-in the common mix-in to make code available for the lambdas used in this file
|
|
413
655
|
extend SP::Job::Common
|
|
414
656
|
|
|
415
|
-
logger.
|
|
416
|
-
logger.
|
|
657
|
+
logger.info "Log file ...... #{$args[:log_file]}"
|
|
658
|
+
logger.info "PID ........... #{Process.pid}"
|
|
417
659
|
|
|
418
660
|
#
|
|
419
661
|
# Now create the global data needed by the mix-in methods
|
|
420
662
|
#
|
|
421
663
|
$connected = false
|
|
664
|
+
|
|
665
|
+
#### TODO read config direct from cluster that will allow unification of jobs configs
|
|
666
|
+
|
|
667
|
+
# Cluster age config here
|
|
668
|
+
|
|
669
|
+
#### TODO else classical config
|
|
670
|
+
|
|
422
671
|
$redis = Redis.new(:host => $config[:redis][:host], :port => $config[:redis][:port], :db => 0)
|
|
423
|
-
$transient_job = $config[:options] && $config[:options][:transient] == true
|
|
672
|
+
$transient_job = $config[:options] && ( $config[:options][:transient] == true || $config[:options][:source] == 'broker' )
|
|
673
|
+
# raw_response, in the job conf.json can either be:
|
|
674
|
+
# - a Boolean (true or false)
|
|
675
|
+
# - an Array of tube names; in this case, the response will be raw if the current tube name is one of the Array names
|
|
676
|
+
$raw_response = ($config[:options] ? ($config[:options][:raw_response].nil? ? false : $config[:options][:raw_response]) : false)
|
|
677
|
+
$verbose_log = $config[:options] && $config[:options][:verbose_log] == true
|
|
424
678
|
$beaneater = Beaneater.new "#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
|
|
425
679
|
if $config[:postgres] && $config[:postgres][:conn_str]
|
|
426
|
-
$pg = ::SP::Job::PGConnection.new(owner: $
|
|
427
|
-
if $
|
|
680
|
+
$pg = ::SP::Job::PGConnection.new(owner: $args[:program_name], config: $config[:postgres], multithreaded: $multithreading)
|
|
681
|
+
if $verbose_log
|
|
428
682
|
$pg.exec("SET log_min_duration_statement TO 0;")
|
|
429
683
|
end
|
|
430
|
-
|
|
431
|
-
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
#### TODO end ####
|
|
687
|
+
|
|
688
|
+
# Check if the user DB is on a different database
|
|
689
|
+
if config[:cluster]
|
|
690
|
+
if config[:cluster][:user_db].instance_of? Hash
|
|
691
|
+
config[:cluster][:user_db][:conn_str] = pg_conn_str(config[:cluster][:user_db])
|
|
692
|
+
$user_db = ::SP::Job::PGConnection.new(owner: $PROGRAM_NAME, config: config[:cluster][:user_db], multithreaded: $multithreading)
|
|
693
|
+
logger.info "Central DB .... #{$user_db.config[:host]}:#{$user_db.config[:port]}(#{$user_db.config[:dbname]})"
|
|
694
|
+
else
|
|
695
|
+
$user_db = nil # Will be grabbed from $cluster_members
|
|
696
|
+
logger.info "Central DB .... embedded in cluster #{config[:cluster][:user_db]}"
|
|
432
697
|
end
|
|
433
698
|
end
|
|
434
699
|
|
|
700
|
+
$excluded_members = $config[:cluster].nil? ? [] : ( $config[:cluster][:members].map {|m| m[:number] if m[:exclude_member] }.compact )
|
|
701
|
+
|
|
702
|
+
#
|
|
703
|
+
# Global data for mutex and sync
|
|
704
|
+
#
|
|
705
|
+
$threads = [ Thread.current ]
|
|
706
|
+
$thread_data = {}
|
|
707
|
+
$thread_data[Thread.current] = ::SP::Job::ThreadData.new
|
|
708
|
+
|
|
709
|
+
#
|
|
710
|
+
# Signal handler
|
|
711
|
+
#
|
|
712
|
+
Signal.trap('SIGUSR2') {
|
|
713
|
+
$gracefull_exit = true
|
|
714
|
+
check_gracefull_exit(dolog: false)
|
|
715
|
+
}
|
|
716
|
+
|
|
435
717
|
#
|
|
436
718
|
# Open a second thread that will listen to cancellation and other "signals"
|
|
437
719
|
#
|
|
@@ -443,17 +725,17 @@ $cancel_thread = Thread.new {
|
|
|
443
725
|
begin
|
|
444
726
|
message = JSON.parse(msg, {symbolize_names: true})
|
|
445
727
|
$threads.each do |thread|
|
|
446
|
-
if $thread_data[thread].job_id != nil && message[:id].to_s == $thread_data[thread].job_id && message[:status] == 'cancelled'
|
|
728
|
+
if $thread_data[thread].job_id != nil && message[:id].to_s == $thread_data[thread].job_id.to_s && message[:status] == 'cancelled'
|
|
447
729
|
logger.info "Received cancel signal for job #{$thread_data[thread].job_id}"
|
|
448
730
|
thread.raise(::SP::Job::JobCancelled.new)
|
|
449
731
|
end
|
|
450
732
|
end
|
|
451
|
-
rescue Exception
|
|
733
|
+
rescue Exception
|
|
452
734
|
# ignore invalid payloads
|
|
453
735
|
end
|
|
454
736
|
end
|
|
455
737
|
end
|
|
456
|
-
rescue Redis::CannotConnectError
|
|
738
|
+
rescue Redis::CannotConnectError
|
|
457
739
|
logger.fatal "Can't connect to redis exiting now".red
|
|
458
740
|
exit
|
|
459
741
|
rescue Exception => e
|