sp-job 0.2.3 → 0.3.22

Sign up to get free protection for your applications and to get access to all the features.
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