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.
- checksums.yaml +4 -4
- data/Rakefile +3 -1
- data/app/assets/stylesheets/tasks_scheduler.scss +4 -0
- data/app/controllers/scheduled_tasks_controller.rb +5 -4
- data/app/controllers/tasks_scheduler_daemon_controller.rb +8 -23
- data/app/controllers/tasks_scheduler_daemon_controller/_download_log.rb +42 -0
- data/app/helpers/scheduled_tasks_helper.rb +2 -0
- data/app/models/scheduled_task.rb +30 -15
- data/app/models/scheduled_task/checker.rb +20 -6
- data/app/models/scheduled_task/log.rb +8 -2
- data/app/models/scheduled_task/runner.rb +9 -5
- data/app/models/scheduled_task/status.rb +8 -4
- data/app/views/scheduled_tasks/status_content.html.erb +1 -0
- data/app/views/tasks_scheduler_daemon/_daemon.html.erb +11 -0
- data/app/views/tasks_scheduler_daemon/_logs.html.erb +12 -0
- data/app/views/tasks_scheduler_daemon/_results.html.erb +9 -0
- data/app/views/tasks_scheduler_daemon/_running_status.html.erb +0 -1
- data/app/views/tasks_scheduler_daemon/index.html.erb +3 -20
- data/config/initializers/assets.rb +3 -1
- data/config/routes.rb +4 -2
- data/db/migrate/20161122123828_create_scheduled_tasks.rb +5 -1
- data/db/migrate/20161123130153_add_status_attributes_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20161124200712_add_pid_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20161128163702_add_args_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20180223155025_add_last_fail_status_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20180302170138_add_enabled_to_scheduled_tasks.rb +5 -1
- data/exe/tasks_scheduler +3 -1
- data/exe/tasks_scheduler_run_task +3 -1
- data/lib/tasks_scheduler.rb +4 -8
- data/lib/tasks_scheduler/app_gem.rb +18 -0
- data/lib/tasks_scheduler/checker.rb +23 -4
- data/lib/tasks_scheduler/checker/log.rb +34 -0
- data/lib/tasks_scheduler/cron_parser_patch.rb +5 -1
- data/lib/tasks_scheduler/cron_scheduling_validator.rb +3 -0
- data/lib/tasks_scheduler/daemon.rb +20 -13
- data/lib/tasks_scheduler/engine.rb +5 -0
- data/lib/tasks_scheduler/version.rb +3 -1
- data/test/dummy/Rakefile +3 -1
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/bin/bundle +3 -1
- data/test/dummy/bin/rails +3 -1
- data/test/dummy/bin/rake +2 -0
- data/test/dummy/bin/setup +3 -1
- data/test/dummy/config.ru +2 -0
- data/test/dummy/config/application.rb +3 -1
- data/test/dummy/config/boot.rb +4 -2
- data/test/dummy/config/environment.rb +3 -1
- data/test/dummy/config/environments/development.rb +2 -0
- data/test/dummy/config/environments/production.rb +2 -0
- data/test/dummy/config/environments/test.rb +2 -0
- data/test/dummy/config/initializers/assets.rb +2 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +1 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +2 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +2 -0
- data/test/dummy/config/initializers/inflections.rb +1 -0
- data/test/dummy/config/initializers/mime_types.rb +1 -0
- data/test/dummy/config/initializers/session_store.rb +2 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +2 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/fixtures/scheduled_tasks.yml +2 -0
- data/test/models/scheduled_task_test.rb +6 -4
- data/test/test_helper.rb +6 -4
- metadata +91 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 599df8db33d832f04a2ad1a0581c7775fbd17f345da397e6b2e6e14090dfe9d1
|
4
|
+
data.tar.gz: f20e6101384683f6d398ef55603f2a1d47d1e79dbf111237d68b2bfcc426292c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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('
|
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'
|
@@ -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
|
-
[
|
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.
|
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
|
-
|
3
|
-
|
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:
|
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,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'
|
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
|
24
|
-
|
25
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
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
|
-
|
74
|
+
true
|
72
75
|
rescue Errno::EPERM
|
73
76
|
raise "No permission to query #{pid}!"
|
74
77
|
rescue Errno::ESRCH
|
75
|
-
|
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
|
-
|
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
|
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
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
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 =>
|
48
|
-
run_log(
|
49
|
-
exception =
|
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 []
|
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
|
-
|
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
|
-
|
34
|
+
update!(last_run_start: Time.zone.now)
|
31
35
|
end
|
32
36
|
|
33
37
|
def status_on_end(exception, last_fail_status)
|
34
|
-
|
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
|
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
|
@@ -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>
|