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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/configure +40 -0
  4. data/lib/sp-job.rb +21 -2
  5. data/lib/sp/job/back_burner.rb +350 -68
  6. data/lib/sp/job/broker.rb +18 -16
  7. data/lib/sp/job/broker_http_client.rb +80 -20
  8. data/lib/sp/job/broker_oauth2_client.rb +12 -4
  9. data/lib/sp/job/common.rb +876 -62
  10. data/lib/sp/job/configure/configure.rb +640 -0
  11. data/lib/sp/job/curl_http_client.rb +100 -0
  12. data/lib/sp/job/easy_http_client.rb +94 -0
  13. data/lib/sp/job/http_client.rb +51 -0
  14. data/lib/sp/job/job_db_adapter.rb +38 -36
  15. data/lib/sp/job/jsonapi_error.rb +31 -74
  16. data/lib/sp/job/jwt.rb +55 -5
  17. data/lib/sp/job/mail_queue.rb +9 -2
  18. data/lib/sp/job/manticore_http_client.rb +94 -0
  19. data/lib/sp/job/pg_connection.rb +90 -10
  20. data/lib/sp/job/query_params.rb +45 -0
  21. data/lib/sp/job/rfc822.rb +13 -0
  22. data/lib/sp/job/session.rb +239 -0
  23. data/lib/sp/job/unique_file.rb +37 -1
  24. data/lib/sp/job/uploaded_image_converter.rb +27 -19
  25. data/lib/sp/job/worker.rb +51 -1
  26. data/lib/sp/job/worker_thread.rb +22 -7
  27. data/lib/sp/jsonapi.rb +24 -0
  28. data/lib/sp/jsonapi/adapters/base.rb +177 -0
  29. data/lib/sp/jsonapi/adapters/db.rb +26 -0
  30. data/lib/sp/jsonapi/adapters/raw_db.rb +96 -0
  31. data/lib/sp/jsonapi/exceptions.rb +54 -0
  32. data/lib/sp/jsonapi/model/base.rb +31 -0
  33. data/lib/sp/jsonapi/model/concerns/attributes.rb +91 -0
  34. data/lib/sp/jsonapi/model/concerns/model.rb +39 -0
  35. data/lib/sp/jsonapi/model/concerns/persistence.rb +212 -0
  36. data/lib/sp/jsonapi/model/concerns/serialization.rb +57 -0
  37. data/lib/sp/jsonapi/parameters.rb +54 -0
  38. data/lib/sp/jsonapi/service.rb +96 -0
  39. data/lib/tasks/configure.rake +2 -496
  40. data/sp-job.gemspec +3 -2
  41. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9672916da9a019a5b292a332195e307e2cf35b5b
4
- data.tar.gz: 48c48bd95897811d2a7c922313bd0ebaa8dd27c2
3
+ metadata.gz: dd0c49ededce3afbbd5e09db28e113b0d6e66790
4
+ data.tar.gz: 511e7878308035eb4e69bb7864c9205479cbdba8
5
5
  SHA512:
6
- metadata.gz: 167ac6ad6e86de69551c4241d27154a56701819cac80cdbcdadf69f66fc1dff1afa63b26bdece1705f06a911bae01a8ec6a5aa0cfa0e557a811b8583e1a87c92
7
- data.tar.gz: d9bee2c1dbdd785f42c674f6247255fc218a3f068352318ee25e1de5fab1c93f9addb21bd68b7bc28b26a5bb1518ad8f77fd92be12d8b525c8f6abac342725e4
6
+ metadata.gz: 2ac3252e84c0994f71c0c1d748080e7d6313378eecabc0f7caa1427cb8bbf77943a563c2bdf09a8242499394022ec4e68b45072dca3d52032553bf3fcf6b7801
7
+ data.tar.gz: 683adc1dbe186ad0034e1762180332b733cb1fd3233139079a05e89ec6ab0f5f1c9e9d4e342f2a36f528d344f46850b5dcd9ae640f2ce749462c6d67e96f795f
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
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/broker_http_client' unless RUBY_ENGINE == 'jruby'
46
- require 'sp/job/broker_oauth2_client' unless RUBY_ENGINE == 'jruby'
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'
@@ -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] || $current_job[:tube] || $args[:program_name])
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
- @job_status = {}
63
- if $config[:options] && $config[:options][:jsonapi] == true
64
- @jsonapi = SP::Duh::JSONAPI::Service.new($pg, nil, SP::Job::JobDbAdapter)
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[:default_log_file]}')") { |v| $args[:config_file] = File.expand_path(v) }
99
- opts.on('-l', '--log=LOGFILE' , "path to log file (default: '#{$args[:log_file]}')") { |v| $args[:log_file] = File.expand_path(v) }
100
- opts.on('-d', '--debug' , "developer mode: log to stdout and print job") { $args[:debug] = true }
101
- opts.on('-v', '--log_level=LEVEL' , "Log level DEBUG, INFO, WARN, ERROR, FATAL") { |v| $args[:log_level] = v }
102
- opts.on('-i', '--index=IDX' , "systemd instance index") { |v| $args[:index] = v }
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
- $rollbar = true
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
- raise_error(message: e)
181
- rescue => e
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
- if RUBY_ENGINE != 'jruby'
311
-
312
- def log_job_begin(name, args)
313
- log_info "Work job #{name}"
314
- @job_started_at = Time.now
315
- end
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
- extend SP::Job::Common # to bring in logger and report_error into this class
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
- timeout_job_after(task.ttr > 1 ? task.ttr - 1 : task.ttr) { job_class.perform(*args) }
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
- rescue ::SP::Job::JobAborted => ja
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
- thread_data.job_id = nil
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.debug "Received job cancellation exception #{Thread.current}".yellow
584
+ logger.info "Received job cancellation exception #{Thread.current}".yellow
391
585
  unless task.nil?
392
- logger.debug 'Task deleted'.yellow
393
- task.delete
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
- report_error(message: 'i18n_job_cancelled', status: 'cancelled')
594
+ error_handler(message: 'i18n_job_cancelled', status: 'cancelled')
397
595
  if $redis_mutex.nil?
398
- $redis.hset(thread_data.job_key, 'cancelled', true)
596
+ $redis.hset(td.job_key, 'cancelled', true)
399
597
  else
400
598
  $redis_mutex.synchronize {
401
- $redis.hset(thread_data.job_key, 'cancelled', true)
599
+ $redis.hset(td.job_key, 'cancelled', true)
402
600
  }
403
601
  end
404
- thread_data.job_id = nil
602
+ td.job_id = nil
603
+ # ensure currently open ( if any ) transaction rollback
604
+ $pg.rollback unless ! $pg
405
605
  rescue => e
406
- @hooks.invoke_hook_events(job_class, :on_failure, e, *args)
407
- raise e
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.debug "Log file ... #{$args[:log_file]}"
416
- logger.debug "PID ........ #{Process.pid}"
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: $PROGRAM_NAME, config: $config[:postgres], multithreaded: $multithreading)
427
- if $PROGRAM_NAME.split('/').last == 'saft-importer' || $PROGRAM_NAME.split('/').last == 'saft-destroyer'
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
- if $config[:options][:jsonapi] == true
431
- $jsonapi = SP::Duh::JSONAPI::Service.new($pg, ($jsonapi.nil? ? nil : $jsonapi.url), SP::Job::JobDbAdapter)
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 => e
733
+ rescue Exception
452
734
  # ignore invalid payloads
453
735
  end
454
736
  end
455
737
  end
456
- rescue Redis::CannotConnectError => ccc
738
+ rescue Redis::CannotConnectError
457
739
  logger.fatal "Can't connect to redis exiting now".red
458
740
  exit
459
741
  rescue Exception => e