tasks_scheduler 0.2.3 → 0.5.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/app/assets/stylesheets/tasks_scheduler.scss +4 -0
  4. data/app/controllers/scheduled_tasks_controller.rb +5 -4
  5. data/app/controllers/tasks_scheduler_daemon_controller.rb +8 -23
  6. data/app/controllers/tasks_scheduler_daemon_controller/_download_log.rb +42 -0
  7. data/app/helpers/scheduled_tasks_helper.rb +2 -0
  8. data/app/models/scheduled_task.rb +30 -15
  9. data/app/models/scheduled_task/checker.rb +20 -6
  10. data/app/models/scheduled_task/log.rb +8 -2
  11. data/app/models/scheduled_task/runner.rb +9 -5
  12. data/app/models/scheduled_task/status.rb +8 -4
  13. data/app/views/scheduled_tasks/status_content.html.erb +1 -0
  14. data/app/views/tasks_scheduler_daemon/_daemon.html.erb +11 -0
  15. data/app/views/tasks_scheduler_daemon/_logs.html.erb +12 -0
  16. data/app/views/tasks_scheduler_daemon/_results.html.erb +9 -0
  17. data/app/views/tasks_scheduler_daemon/_running_status.html.erb +0 -1
  18. data/app/views/tasks_scheduler_daemon/index.html.erb +3 -20
  19. data/config/initializers/assets.rb +3 -1
  20. data/config/routes.rb +4 -2
  21. data/db/migrate/20161122123828_create_scheduled_tasks.rb +5 -1
  22. data/db/migrate/20161123130153_add_status_attributes_to_scheduled_tasks.rb +5 -1
  23. data/db/migrate/20161124200712_add_pid_to_scheduled_tasks.rb +5 -1
  24. data/db/migrate/20161128163702_add_args_to_scheduled_tasks.rb +5 -1
  25. data/db/migrate/20180223155025_add_last_fail_status_to_scheduled_tasks.rb +5 -1
  26. data/db/migrate/20180302170138_add_enabled_to_scheduled_tasks.rb +5 -1
  27. data/exe/tasks_scheduler +3 -1
  28. data/exe/tasks_scheduler_run_task +3 -1
  29. data/lib/tasks_scheduler.rb +4 -8
  30. data/lib/tasks_scheduler/app_gem.rb +18 -0
  31. data/lib/tasks_scheduler/checker.rb +23 -4
  32. data/lib/tasks_scheduler/checker/log.rb +34 -0
  33. data/lib/tasks_scheduler/cron_parser_patch.rb +5 -1
  34. data/lib/tasks_scheduler/cron_scheduling_validator.rb +3 -0
  35. data/lib/tasks_scheduler/daemon.rb +20 -13
  36. data/lib/tasks_scheduler/engine.rb +5 -0
  37. data/lib/tasks_scheduler/version.rb +3 -1
  38. data/test/dummy/Rakefile +3 -1
  39. data/test/dummy/app/controllers/application_controller.rb +2 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/bin/bundle +3 -1
  42. data/test/dummy/bin/rails +3 -1
  43. data/test/dummy/bin/rake +2 -0
  44. data/test/dummy/bin/setup +3 -1
  45. data/test/dummy/config.ru +2 -0
  46. data/test/dummy/config/application.rb +3 -1
  47. data/test/dummy/config/boot.rb +4 -2
  48. data/test/dummy/config/environment.rb +3 -1
  49. data/test/dummy/config/environments/development.rb +2 -0
  50. data/test/dummy/config/environments/production.rb +2 -0
  51. data/test/dummy/config/environments/test.rb +2 -0
  52. data/test/dummy/config/initializers/assets.rb +2 -0
  53. data/test/dummy/config/initializers/backtrace_silencers.rb +1 -0
  54. data/test/dummy/config/initializers/cookies_serializer.rb +2 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +2 -0
  56. data/test/dummy/config/initializers/inflections.rb +1 -0
  57. data/test/dummy/config/initializers/mime_types.rb +1 -0
  58. data/test/dummy/config/initializers/session_store.rb +2 -0
  59. data/test/dummy/config/initializers/wrap_parameters.rb +2 -0
  60. data/test/dummy/config/routes.rb +2 -0
  61. data/test/fixtures/scheduled_tasks.yml +2 -0
  62. data/test/models/scheduled_task_test.rb +6 -4
  63. data/test/test_helper.rb +6 -4
  64. metadata +91 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ffe5fd69eab67510ab378506158799d08b21d4577ed368ddbadf12cfb0a6957
4
- data.tar.gz: e471eaeb074d10557c8c3bd55dff0d8bbd0a9e1b83723d28f6255b2b2907fe29
3
+ metadata.gz: 599df8db33d832f04a2ad1a0581c7775fbd17f345da397e6b2e6e14090dfe9d1
4
+ data.tar.gz: f20e6101384683f6d398ef55603f2a1d47d1e79dbf111237d68b2bfcc426292c
5
5
  SHA512:
6
- metadata.gz: 435c0e3aa4e1c7a721933b6a47bd3b72f33ab51d5a5c67323cee7a179c2aef731bc6a39119763fa169c064670883a50728703c1ad37614c9d7996ba7b7993336
7
- data.tar.gz: 9112b3ad93fd00dfc3d10d8487befc0c698461d046a8fdeead98611c86ecdd0d4f44dcb929864fa53e95fcf409fedd9eefcd85abe7ccf0817269f44d50b2b3df
6
+ metadata.gz: ca786519acd5d3925cc521ffed0f3d61471d409abefe94b70b32002951ce27d51cc63435567b2b820d7be7315265f7d7a847723ba721835246531fb391474539
7
+ data.tar.gz: 6e0e915ebd3b41d75499899b3155b33e71f34ad08fe80f03f30dc03dc75ead448acbd34dc71c6eb8ab38c8e9f49fd1c0f95c8922c4c76afb6ea9f3a30847368c
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -14,7 +16,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
16
  rdoc.rdoc_files.include('lib/**/*.rb')
15
17
  end
16
18
 
17
- APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
18
20
  load 'rails/tasks/engine.rake'
19
21
 
20
22
  load 'rails/tasks/statistics.rake'
@@ -39,6 +39,10 @@
39
39
  td.status_disabled {
40
40
  color: lightgrey;
41
41
  }
42
+
43
+ td.status_task_not_found {
44
+ color: violet;
45
+ }
42
46
  }
43
47
 
44
48
  .last_run {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ScheduledTasksController < ApplicationController
2
4
  class << self
3
5
  private
@@ -8,7 +10,7 @@ class ScheduledTasksController < ApplicationController
8
10
  end
9
11
 
10
12
  active_scaffold :scheduled_task do |conf|
11
- [:create, :update, :list].each do |action|
13
+ %i[create update list].each do |action|
12
14
  conf.send(action).columns.exclude(:next_run, :last_fail_status, :last_run_start,
13
15
  :last_run_successful_start, :last_run_unsuccessful_start,
14
16
  :last_run_successful_end, :last_run_unsuccessful_end,
@@ -26,8 +28,7 @@ class ScheduledTasksController < ApplicationController
26
28
  @log_file = record.log_file(params[:identifier])
27
29
  end
28
30
 
29
- def status
30
- end
31
+ def status; end
31
32
 
32
33
  def status_content
33
34
  @scheduled_tasks = ::ScheduledTask.order(task: :asc, scheduling: :asc)
@@ -36,7 +37,7 @@ class ScheduledTasksController < ApplicationController
36
37
 
37
38
  def run_now
38
39
  process_action_link_action do |record|
39
- record.update_attributes!(next_run: Time.zone.now)
40
+ record.update!(next_run: Time.zone.now)
40
41
  record.reload
41
42
  flash[:info] = "Next run adjusted to #{record.next_run}"
42
43
  end
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tasks_scheduler/checker'
4
+
1
5
  class TasksSchedulerDaemonController < ApplicationController
2
- def index
3
- end
6
+ require_relative 'tasks_scheduler_daemon_controller/_download_log'
7
+
8
+ def index; end
4
9
 
5
10
  def execute
6
11
  @result = ::TasksScheduler::Daemon.execute(params[:tasks_scheduler_execute_action])
@@ -9,26 +14,6 @@ class TasksSchedulerDaemonController < ApplicationController
9
14
 
10
15
  def status
11
16
  render json: { daemon_running: ::TasksScheduler::Daemon.running?,
12
- tasks_all_ok: !::ScheduledTask.all.any?(&:failed?) }
13
- end
14
-
15
- def download_log
16
- if File.exist?(::TasksScheduler::Checker.instance.log_path)
17
- send_log_file
18
- else
19
- redirect_to(tasks_scheduler_daemon_path,
20
- notice: "Arquivo \"#{::TasksScheduler::Checker.instance.log_path}\" não existe")
21
- end
22
- end
23
-
24
- private
25
-
26
- def send_log_file
27
- send_file(
28
- ::TasksScheduler::Checker.instance.log_path,
29
- filename: "#{request.base_url.parameterize}_tasks-scheduler_checker-log_" \
30
- "#{Time.zone.now.to_s.parameterize}.log",
31
- type: 'text/plain'
32
- )
17
+ tasks_all_ok: ::ScheduledTask.all.none?(&:failed?) }
33
18
  end
34
19
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TasksSchedulerDaemonController < ApplicationController
4
+ def download_log
5
+ return unless download_log_validate_log_key
6
+
7
+ log = ::TasksScheduler::Checker.instance.send("#{download_log_key}_log")
8
+ return unless download_log_validate_log_exist(log)
9
+
10
+ send_log_file(log)
11
+ end
12
+
13
+ private
14
+
15
+ def download_log_key
16
+ params[:log_key]
17
+ end
18
+
19
+ def download_log_validate_log_key
20
+ return true if ::TasksScheduler::Checker::LOGS_KEYS.include?(download_log_key)
21
+
22
+ redirect_to(tasks_scheduler_daemon_path,
23
+ notice: "Invalid log key: \"#{download_log_key}\"")
24
+ false
25
+ end
26
+
27
+ def download_log_validate_log_exist(log)
28
+ return true if log.exist?
29
+
30
+ redirect_to(tasks_scheduler_daemon_path, notice: "Log \"#{log.key}\" does not exist.")
31
+ false
32
+ end
33
+
34
+ def send_log_file(log)
35
+ send_file(
36
+ log.path,
37
+ filename: "#{request.base_url.parameterize}_tasks-scheduler_checker-log_" \
38
+ "#{Time.zone.now.to_s.parameterize}.log",
39
+ type: 'text/plain'
40
+ )
41
+ end
42
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ScheduledTasksHelper
2
4
  def scheduled_tasks_status_time(time)
3
5
  if time.present?
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'eac_ruby_utils/core_ext'
1
4
  require 'rake'
2
5
 
3
6
  class ScheduledTask < ActiveRecord::Base
@@ -6,7 +9,7 @@ class ScheduledTask < ActiveRecord::Base
6
9
  include ::ScheduledTask::Runner
7
10
  include ::ScheduledTask::Status
8
11
 
9
- DEFAULT_TIMEOUT_ENVVAR_NAME = 'TASKS_SCHEDULER_TIMEOUT'.freeze
12
+ DEFAULT_TIMEOUT_ENVVAR_NAME = 'TASKS_SCHEDULER_TIMEOUT'
10
13
  DEFAULT_TIMEOUT = 12.hours
11
14
 
12
15
  class << self
@@ -20,26 +23,25 @@ class ScheduledTask < ActiveRecord::Base
20
23
  def timeout
21
24
  @timeout ||= begin
22
25
  r = Integer(ENV[DEFAULT_TIMEOUT_ENVVAR_NAME])
23
- r > 0 ? r.seconds : DEFAULT_TIMEOUT
24
- rescue ArgumentError, TypeError
25
- DEFAULT_TIMEOUT
26
+ r.positive? ? r.seconds : DEFAULT_TIMEOUT
27
+ rescue ArgumentError, TypeError
28
+ DEFAULT_TIMEOUT
26
29
  end
27
30
  end
28
31
  end
29
32
 
30
- STATUS_RUNNING = 'running'
31
- STATUS_FAILED = 'failed'
32
- STATUS_WAITING = 'waiting'
33
- STATUS_ABORTED = 'aborted'
34
- STATUS_TIMEOUT = 'timeout'
35
- STATUS_DISABLED = 'disabled'
33
+ enable_listable
34
+ lists.add_string :status, 'aborted', 'disabled', 'failed', 'running', 'task_not_found', 'timeout',
35
+ 'waiting'
36
36
 
37
- LAST_FAIL_STATUSES = [STATUS_FAILED, STATUS_ABORTED, STATUS_TIMEOUT]
37
+ LAST_FAIL_STATUSES = [STATUS_FAILED, STATUS_ABORTED, STATUS_TASK_NOT_FOUND, STATUS_TIMEOUT].freeze
38
38
 
39
39
  validates :scheduling, presence: true, 'tasks_scheduler/cron_scheduling': true
40
- validates :task, presence: true, inclusion: { in: rake_tasks }
40
+ validates :task, presence: true
41
41
  validates :last_fail_status, allow_blank: true, inclusion: { in: LAST_FAIL_STATUSES }
42
42
 
43
+ validate :validate_task
44
+
43
45
  LOG_RUNNING = 'running'
44
46
  LOG_SUCCESSFUL = 'successful'
45
47
  LOG_UNSUCCESSFUL = 'unsuccessful'
@@ -67,13 +69,26 @@ class ScheduledTask < ActiveRecord::Base
67
69
 
68
70
  def process_running?
69
71
  return false if pid.nil?
72
+
70
73
  Process.kill(0, pid)
71
- return true
74
+ true
72
75
  rescue Errno::EPERM
73
76
  raise "No permission to query #{pid}!"
74
77
  rescue Errno::ESRCH
75
- return false
76
- rescue
78
+ false
79
+ rescue StandardError
77
80
  raise "Unable to determine status for #{pid}"
78
81
  end
82
+
83
+ def task_exist?
84
+ self.class.rake_tasks.include?(task)
85
+ end
86
+
87
+ def validate_task
88
+ return if task.blank?
89
+ return unless task_changed?
90
+ return if self.class.rake_tasks.include?(task)
91
+
92
+ errors.add(:task, "Task \"#{task}\" not found")
93
+ end
79
94
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake'
4
+ require 'eac_ruby_utils/ruby'
2
5
 
3
6
  class ScheduledTask < ActiveRecord::Base
4
7
  module Checker
@@ -34,6 +37,7 @@ class ScheduledTask < ActiveRecord::Base
34
37
 
35
38
  def check_on_pid_not_present
36
39
  return unless enabled?
40
+
37
41
  if next_run.present?
38
42
  check_task_with_next_run
39
43
  else
@@ -41,6 +45,12 @@ class ScheduledTask < ActiveRecord::Base
41
45
  end
42
46
  end
43
47
 
48
+ def check_on_task_not_exist
49
+ message = "Task does not exist: #{task}"
50
+ check_log(message)
51
+ on_end_running(::StandardError.new(message), STATUS_TASK_NOT_FOUND)
52
+ end
53
+
44
54
  def check_log(message, method = :info)
45
55
  Rails.logger.send(method, "TASK_CHECK(#{id}): #{message}")
46
56
  end
@@ -55,12 +65,14 @@ class ScheduledTask < ActiveRecord::Base
55
65
 
56
66
  def check_task_without_next_run
57
67
  check_log('Next run blank')
58
- update_attributes!(next_run: calculate_next_run)
68
+ update!(next_run: calculate_next_run)
59
69
  check_log("Next run scheduled: #{next_run.in_time_zone}")
60
70
  end
61
71
 
62
72
  def check_task_with_next_run
63
- if next_run < Time.zone.now
73
+ if !task_exist?
74
+ check_on_task_not_exist
75
+ elsif next_run < Time.zone.now
64
76
  check_log('Next run reached. Running...')
65
77
  spawn_task
66
78
  else
@@ -70,13 +82,15 @@ class ScheduledTask < ActiveRecord::Base
70
82
 
71
83
  def spawn_task
72
84
  params = ['bundle', 'exec', 'tasks_scheduler_run_task', id.to_s]
73
- check_log("Spawn command: #{params}")
85
+ check_log("Spawn command: #{params} (Task: #{task})")
74
86
  spawn_pid = nil
75
- Dir.chdir(Rails.root) do
76
- spawn_pid = Process.spawn(*params)
87
+ ::EacRubyUtils::Ruby.on_clean_environment do
88
+ Dir.chdir(Rails.root) do
89
+ spawn_pid = ::Process.spawn(*params)
90
+ end
77
91
  end
78
92
  Process.detach(spawn_pid)
79
- update_attributes!(pid: spawn_pid, last_fail_status: nil)
93
+ update!(pid: spawn_pid, last_fail_status: nil)
80
94
  end
81
95
 
82
96
  def timeout?
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ScheduledTask < ActiveRecord::Base
2
4
  module Log
3
5
  def log_file(identifier)
4
6
  unless log_identifiers.include?(identifier)
5
- fail "Log identifier unknown: \"#{identifier}\" (Valid: #{log_identifiers})"
7
+ raise "Log identifier unknown: \"#{identifier}\" (Valid: #{log_identifiers})"
6
8
  end
9
+
7
10
  Rails.root.join('log', 'tasks_scheduler', "#{id}_#{identifier}.log")
8
11
  end
9
12
 
@@ -23,9 +26,12 @@ class ScheduledTask < ActiveRecord::Base
23
26
  end
24
27
 
25
28
  def log_on_end(exception)
29
+ running_log = log_file(LOG_RUNNING)
30
+ return unless ::File.exist?(running_log)
31
+
26
32
  target_log = exception ? log_file(LOG_UNSUCCESSFUL) : log_file(LOG_SUCCESSFUL)
27
33
  File.unlink(target_log) if File.exist?(target_log)
28
- File.rename(log_file(LOG_RUNNING), target_log)
34
+ File.rename(running_log, target_log)
29
35
  end
30
36
  end
31
37
  end
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ScheduledTask < ActiveRecord::Base
2
4
  module Runner
3
5
  def run
4
6
  log_on_start
5
7
  run_banner
6
8
  return if process_running? && pid != Process.pid
9
+
7
10
  status_on_start
8
11
  exception = invoke_task
9
12
  on_end_running(exception, STATUS_FAILED)
@@ -33,7 +36,7 @@ class ScheduledTask < ActiveRecord::Base
33
36
 
34
37
  def run_banner
35
38
  run_log("Task: #{self}")
36
- run_log("PID: #{pid ? pid : '-'} (Current: #{Process.pid})")
39
+ run_log("PID: #{pid || '-'} (Current: #{Process.pid})")
37
40
  run_log("Process running? #{process_running? ? 'Yes' : 'No'}")
38
41
  run_log("Rails.env: #{Rails.env}")
39
42
  end
@@ -44,15 +47,16 @@ class ScheduledTask < ActiveRecord::Base
44
47
  Rake::Task.clear
45
48
  Rails.application.load_tasks
46
49
  Rake::Task[task].invoke(*invoke_args)
47
- rescue StandardError => ex
48
- run_log(ex, :fatal)
49
- exception = ex
50
+ rescue StandardError => e
51
+ run_log(e, :fatal)
52
+ exception = e
50
53
  end
51
54
  exception
52
55
  end
53
56
 
54
57
  def invoke_args
55
- return [] unless args.present?
58
+ return [] if args.blank?
59
+
56
60
  args.split('|')
57
61
  end
58
62
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ScheduledTask < ActiveRecord::Base
2
4
  module Status
3
5
  def status
4
6
  return STATUS_DISABLED unless enabled?
5
7
  return STATUS_RUNNING if running?
6
8
  return STATUS_WAITING if waiting?
7
- last_fail_status.present? ? last_fail_status : STATUS_FAILED
9
+
10
+ last_fail_status.presence || STATUS_FAILED
8
11
  end
9
12
 
10
13
  def failed?
@@ -17,6 +20,7 @@ class ScheduledTask < ActiveRecord::Base
17
20
 
18
21
  def waiting?
19
22
  return true if ended?(last_run_successful_end, last_run_unsuccessful_end)
23
+
20
24
  status_attributes.all? { |a| send(a).blank? }
21
25
  end
22
26
 
@@ -27,11 +31,11 @@ class ScheduledTask < ActiveRecord::Base
27
31
  end
28
32
 
29
33
  def status_on_start
30
- update_attributes!(last_run_start: Time.zone.now)
34
+ update!(last_run_start: Time.zone.now)
31
35
  end
32
36
 
33
37
  def status_on_end(exception, last_fail_status)
34
- update_attributes!(
38
+ update!(
35
39
  next_run: calculate_next_run,
36
40
  (exception ? :last_run_unsuccessful_start : :last_run_successful_start) => last_run_start,
37
41
  (exception ? :last_run_unsuccessful_end : :last_run_successful_end) => Time.zone.now,
@@ -42,7 +46,7 @@ class ScheduledTask < ActiveRecord::Base
42
46
  end
43
47
 
44
48
  def status_attributes
45
- %w(start successful_start successful_end unsuccessful_start unsuccessful_end).map do |a|
49
+ %w[start successful_start successful_end unsuccessful_start unsuccessful_end].map do |a|
46
50
  "last_run_#{a}"
47
51
  end
48
52
  end
@@ -2,6 +2,7 @@
2
2
  <strong>Last update: </strong>
3
3
  <%= I18n.l(Time.zone.now, format: :short) %>
4
4
  <br/>
5
+ <strong>Daemon status: </strong>
5
6
  <%= render partial: '/tasks_scheduler_daemon/running_status' %>
6
7
  </p>
7
8
  <table>
@@ -0,0 +1,11 @@
1
+ <h3>Daemon</h3>
2
+ <p>
3
+ <strong>Status: </strong>
4
+ <%= render partial: '/tasks_scheduler_daemon/running_status' %>
5
+ <br/>
6
+ <strong>Actions: </strong>
7
+ <%= safe_join(::TasksScheduler::Daemon::ACTIONS.map do |action|
8
+ link_to action, execute_tasks_scheduler_daemon_path(action), method: :post
9
+ end, ' | ') %>
10
+ <br/>
11
+ </p>