tasks_scheduler 0.3.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/app/assets/javascripts/tasks_scheduler.js +2 -109
  4. data/app/assets/javascripts/tasks_scheduler/alert.js +58 -0
  5. data/app/assets/javascripts/tasks_scheduler/status.js +58 -0
  6. data/app/assets/stylesheets/tasks_scheduler.scss +4 -0
  7. data/app/controllers/scheduled_tasks_controller.rb +5 -4
  8. data/app/controllers/tasks_scheduler_daemon_controller.rb +4 -3
  9. data/app/controllers/tasks_scheduler_daemon_controller/_download_log.rb +6 -0
  10. data/app/helpers/scheduled_tasks_helper.rb +2 -0
  11. data/app/models/scheduled_task.rb +16 -14
  12. data/app/models/scheduled_task/checker.rb +17 -5
  13. data/app/models/scheduled_task/log.rb +9 -6
  14. data/app/models/scheduled_task/runner.rb +9 -5
  15. data/app/models/scheduled_task/status.rb +8 -4
  16. data/config/initializers/assets.rb +3 -1
  17. data/config/routes.rb +2 -0
  18. data/db/migrate/20161122123828_create_scheduled_tasks.rb +5 -1
  19. data/db/migrate/20161123130153_add_status_attributes_to_scheduled_tasks.rb +5 -1
  20. data/db/migrate/20161124200712_add_pid_to_scheduled_tasks.rb +5 -1
  21. data/db/migrate/20161128163702_add_args_to_scheduled_tasks.rb +5 -1
  22. data/db/migrate/20180223155025_add_last_fail_status_to_scheduled_tasks.rb +5 -1
  23. data/db/migrate/20180302170138_add_enabled_to_scheduled_tasks.rb +5 -1
  24. data/exe/tasks_scheduler +3 -1
  25. data/exe/tasks_scheduler_run_task +3 -1
  26. data/lib/tasks_scheduler.rb +4 -8
  27. data/lib/tasks_scheduler/app_gem.rb +18 -0
  28. data/lib/tasks_scheduler/checker.rb +5 -2
  29. data/lib/tasks_scheduler/checker/log.rb +2 -0
  30. data/lib/tasks_scheduler/cron_parser_patch.rb +5 -1
  31. data/lib/tasks_scheduler/cron_scheduling_validator.rb +3 -0
  32. data/lib/tasks_scheduler/daemon.rb +20 -13
  33. data/lib/tasks_scheduler/engine.rb +5 -0
  34. data/lib/tasks_scheduler/version.rb +3 -1
  35. data/test/dummy/Rakefile +3 -1
  36. data/test/dummy/app/controllers/application_controller.rb +2 -0
  37. data/test/dummy/app/helpers/application_helper.rb +2 -0
  38. data/test/dummy/bin/bundle +3 -1
  39. data/test/dummy/bin/rails +3 -1
  40. data/test/dummy/bin/rake +2 -0
  41. data/test/dummy/bin/setup +3 -1
  42. data/test/dummy/config.ru +2 -0
  43. data/test/dummy/config/application.rb +3 -1
  44. data/test/dummy/config/boot.rb +4 -2
  45. data/test/dummy/config/environment.rb +3 -1
  46. data/test/dummy/config/environments/development.rb +2 -0
  47. data/test/dummy/config/environments/production.rb +2 -0
  48. data/test/dummy/config/environments/test.rb +2 -0
  49. data/test/dummy/config/initializers/assets.rb +2 -0
  50. data/test/dummy/config/initializers/backtrace_silencers.rb +1 -0
  51. data/test/dummy/config/initializers/cookies_serializer.rb +2 -0
  52. data/test/dummy/config/initializers/filter_parameter_logging.rb +2 -0
  53. data/test/dummy/config/initializers/inflections.rb +1 -0
  54. data/test/dummy/config/initializers/mime_types.rb +1 -0
  55. data/test/dummy/config/initializers/session_store.rb +2 -0
  56. data/test/dummy/config/initializers/wrap_parameters.rb +2 -0
  57. data/test/dummy/config/routes.rb +2 -0
  58. data/test/fixtures/scheduled_tasks.yml +2 -0
  59. data/test/models/scheduled_task_test.rb +6 -4
  60. data/test/test_helper.rb +6 -4
  61. metadata +88 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89877b670ebd68e63175333d60bce49d90de7c4e04737189f5c73a6a277f0595
4
- data.tar.gz: 18d4d5224dcabd9dd2238d0cc8a5dea03689d58323de808b5bb4622664a2cc01
3
+ metadata.gz: f297d8aab57fc0f8e97be84ea5ca71a1dda0623a9231f124866f066a65a96703
4
+ data.tar.gz: be6470159ccbc01e0953b40f4560e1543b3a2687f75c17147fda86d492f0523f
5
5
  SHA512:
6
- metadata.gz: 6bb72a00e15bd394f6baa9cbae395e8737295ff72f1177315fd15937b2b60189901b7db0cfe3c2b7b9daf08599023c694558231539d1f6e2b92ea4f6fd0bd2a4
7
- data.tar.gz: 766a81812effee371317d38ee615e0e5b57400abac7d6a376a6a5971bc98fc301bdca3e03dbfa3c8f1d3f1c7903ddbdf479fc7b89451292880c386c813311b69
6
+ metadata.gz: cf3db7dde7ceb96ece05b878c725eda9bdd3642e0c7ec229f248de845aea79028454533eb49692cbe772d892daaef40cb6f08153606dd489f4b50786e0275d11
7
+ data.tar.gz: cc9a7644b4447216bdc692eea253f39c9183ddc4f24b44e5994b61a84cad0bf093b96607f9d80cc7bb9c33b08ed38292394affc28b49a0a03a85890210f98b62
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'
@@ -4,112 +4,5 @@
4
4
  function TasksScheduler() {
5
5
  }
6
6
 
7
- TasksScheduler.Status = function () {
8
- };
9
-
10
- // Shortcut
11
- var _S = TasksScheduler.Status;
12
-
13
- _S.initialized = false;
14
-
15
- _S.init = function (url, interval_max) {
16
- if (!_S.initialized) {
17
- _S.initialized = true;
18
- _S.url = url;
19
- _S.interval_max = interval_max;
20
- _S.update();
21
- }
22
- };
23
-
24
- _S.content = function () {
25
- return $('#TaskScheduler_Status_Content');
26
- };
27
-
28
- _S.status = function () {
29
- return $('#TaskScheduler_Status_Status');
30
- };
31
-
32
- _S.update_status = function () {
33
- _S.status().html(
34
- "Updating in " + _S.interval + " seconds..."
35
- );
36
- };
37
-
38
- _S.check = function () {
39
- if (_S.interval <= 0) {
40
- _S.update();
41
- } else {
42
- _S.interval--;
43
- _S.update_status();
44
- setTimeout(_S.check, 1000);
45
- }
46
- };
47
-
48
- _S.update = function () {
49
- $.ajax({
50
- url: _S.url,
51
- success: function (result) {
52
- _S.content().html(result);
53
- },
54
- complete: function (result) {
55
- _S.interval = _S.interval_max + 1;
56
- _S.last_update = new Date();
57
- _S.check();
58
- }
59
- });
60
- };
61
-
62
- TasksScheduler.Alert = function () {
63
- };
64
-
65
- _A = TasksScheduler.Alert;
66
-
67
- _A.DEFAULT_REFRESH_INTERVAL = 5000;
68
- _A.DEFAULT_ELEMENT_SELECTOR = '#tasks_scheduler_alert';
69
- _A.CSS_CLASSES_PREFIX = 'alert_';
70
- _A.url = Routes.status_tasks_scheduler_daemon_path();
71
-
72
- _A.init = function (options) {
73
- options = typeof options !== 'undefined' ? options : {};
74
- $(document).ready(function () {
75
- _A.options = options;
76
- if (!_A.options.refresh_interval) {
77
- _A.options.refresh_interval = _A.DEFAULT_REFRESH_INTERVAL;
78
- }
79
- if (!_A.options.element_selector) {
80
- _A.options.element_selector = _A.DEFAULT_ELEMENT_SELECTOR;
81
- }
82
- _A.refresh();
83
- });
84
- };
85
-
86
- _A.setNextRefresh = function () {
87
- setTimeout(_A.refresh, _A.options.refresh_interval);
88
- };
89
-
90
- _A.refresh = function () {
91
- $.ajax({
92
- url: _A.url,
93
- success: function (result) {
94
- var alert = $(_A.options.element_selector);
95
- var pattern = new RegExp('(^|\\s)' + _A.CSS_CLASSES_PREFIX + "\\S+", 'g');
96
- alert.removeClass (function (index, className) {
97
- return (className.match (pattern) || []).join(' ');
98
- });
99
- alert.addClass(_A.resultToCssClass(result));
100
- },
101
- complete: function (result) {
102
- _A.setNextRefresh();
103
- }
104
- });
105
- };
106
-
107
- _A.resultToCssClass = function(result) {
108
- var suffix = "ok"
109
- if (!result.daemon_running) {
110
- suffix = "daemon_stopped";
111
- } else if (!result.tasks_all_ok) {
112
- suffix = "task_failed";
113
- }
114
- return _A.CSS_CLASSES_PREFIX + suffix;
115
- };
7
+ //= require tasks_scheduler/alert
8
+ //= require tasks_scheduler/status
@@ -0,0 +1,58 @@
1
+ TasksScheduler.Alert = function () {
2
+ };
3
+
4
+ _A = TasksScheduler.Alert;
5
+
6
+ _A.DEFAULT_REFRESH_INTERVAL = 5000;
7
+ _A.DEFAULT_ELEMENT_SELECTOR = '#tasks_scheduler_alert';
8
+ _A.CSS_CLASSES_PREFIX = 'alert_';
9
+ _A.url = Routes.status_tasks_scheduler_daemon_path();
10
+
11
+ _A.init = function (options) {
12
+ options = typeof options !== 'undefined' ? options : {};
13
+ $(document).ready(function () {
14
+ _A.options = options;
15
+ if (!_A.options.refresh_interval) {
16
+ _A.options.refresh_interval = _A.DEFAULT_REFRESH_INTERVAL;
17
+ }
18
+ if (!_A.options.element_selector) {
19
+ _A.options.element_selector = _A.DEFAULT_ELEMENT_SELECTOR;
20
+ }
21
+ _A.refresh();
22
+ });
23
+ };
24
+
25
+ _A.setNextRefresh = function () {
26
+ setTimeout(_A.refresh, _A.options.refresh_interval);
27
+ };
28
+
29
+ _A.refresh = function () {
30
+ $.ajax(_A.refreshAjaxData());
31
+ };
32
+
33
+ _A.refreshAjaxData = function () {
34
+ return {
35
+ url: _A.url,
36
+ success: function (result) {
37
+ var alert = $(_A.options.element_selector);
38
+ var pattern = new RegExp('(^|\\s)' + _A.CSS_CLASSES_PREFIX + "\\S+", 'g');
39
+ alert.removeClass (function (index, className) {
40
+ return (className.match (pattern) || []).join(' ');
41
+ });
42
+ alert.addClass(_A.resultToCssClass(result));
43
+ },
44
+ complete: function (result) {
45
+ _A.setNextRefresh();
46
+ }
47
+ };
48
+ };
49
+
50
+ _A.resultToCssClass = function(result) {
51
+ var suffix = "ok"
52
+ if (!result.daemon_running) {
53
+ suffix = "daemon_stopped";
54
+ } else if (!result.tasks_all_ok) {
55
+ suffix = "task_failed";
56
+ }
57
+ return _A.CSS_CLASSES_PREFIX + suffix;
58
+ };
@@ -0,0 +1,58 @@
1
+ TasksScheduler.Status = function () {
2
+ };
3
+
4
+ // Shortcut
5
+ var _S = TasksScheduler.Status;
6
+
7
+ _S.initialized = false;
8
+
9
+ _S.init = function (url, interval_max) {
10
+ if (!_S.initialized) {
11
+ _S.initialized = true;
12
+ _S.url = url;
13
+ _S.interval_max = interval_max;
14
+ _S.update();
15
+ }
16
+ };
17
+
18
+ _S.content = function () {
19
+ return $('#TaskScheduler_Status_Content');
20
+ };
21
+
22
+ _S.status = function () {
23
+ return $('#TaskScheduler_Status_Status');
24
+ };
25
+
26
+ _S.update_status = function () {
27
+ _S.status().html(
28
+ "Updating in " + _S.interval + " seconds..."
29
+ );
30
+ };
31
+
32
+ _S.check = function () {
33
+ if (_S.interval <= 0) {
34
+ _S.update();
35
+ } else {
36
+ _S.interval--;
37
+ _S.update_status();
38
+ setTimeout(_S.check, 1000);
39
+ }
40
+ };
41
+
42
+ _S.update = function () {
43
+ $.ajax(_S.updateAjaxData());
44
+ };
45
+
46
+ _S.updateAjaxData = function() {
47
+ return {
48
+ url: _S.url,
49
+ success: function (result) {
50
+ _S.content().html(result);
51
+ },
52
+ complete: function (result) {
53
+ _S.interval = _S.interval_max + 1;
54
+ _S.last_update = new Date();
55
+ _S.check();
56
+ }
57
+ };
58
+ };
@@ -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,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tasks_scheduler/checker'
2
4
 
3
5
  class TasksSchedulerDaemonController < ApplicationController
4
6
  require_relative 'tasks_scheduler_daemon_controller/_download_log'
5
7
 
6
- def index
7
- end
8
+ def index; end
8
9
 
9
10
  def execute
10
11
  @result = ::TasksScheduler::Daemon.execute(params[:tasks_scheduler_execute_action])
@@ -13,6 +14,6 @@ class TasksSchedulerDaemonController < ApplicationController
13
14
 
14
15
  def status
15
16
  render json: { daemon_running: ::TasksScheduler::Daemon.running?,
16
- tasks_all_ok: !::ScheduledTask.all.any?(&:failed?) }
17
+ tasks_all_ok: ::ScheduledTask.all.none?(&:failed?) }
17
18
  end
18
19
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TasksSchedulerDaemonController < ApplicationController
2
4
  def download_log
3
5
  return unless download_log_validate_log_key
6
+
4
7
  log = ::TasksScheduler::Checker.instance.send("#{download_log_key}_log")
5
8
  return unless download_log_validate_log_exist(log)
9
+
6
10
  send_log_file(log)
7
11
  end
8
12
 
@@ -14,6 +18,7 @@ class TasksSchedulerDaemonController < ApplicationController
14
18
 
15
19
  def download_log_validate_log_key
16
20
  return true if ::TasksScheduler::Checker::LOGS_KEYS.include?(download_log_key)
21
+
17
22
  redirect_to(tasks_scheduler_daemon_path,
18
23
  notice: "Invalid log key: \"#{download_log_key}\"")
19
24
  false
@@ -21,6 +26,7 @@ class TasksSchedulerDaemonController < ApplicationController
21
26
 
22
27
  def download_log_validate_log_exist(log)
23
28
  return true if log.exist?
29
+
24
30
  redirect_to(tasks_scheduler_daemon_path, notice: "Log \"#{log.key}\" does not exist.")
25
31
  false
26
32
  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,21 +23,18 @@ 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
40
  validates :task, presence: true
@@ -69,13 +69,14 @@ class ScheduledTask < ActiveRecord::Base
69
69
 
70
70
  def process_running?
71
71
  return false if pid.nil?
72
+
72
73
  Process.kill(0, pid)
73
- return true
74
+ true
74
75
  rescue Errno::EPERM
75
76
  raise "No permission to query #{pid}!"
76
77
  rescue Errno::ESRCH
77
- return false
78
- rescue
78
+ false
79
+ rescue StandardError
79
80
  raise "Unable to determine status for #{pid}"
80
81
  end
81
82
 
@@ -87,6 +88,7 @@ class ScheduledTask < ActiveRecord::Base
87
88
  return if task.blank?
88
89
  return unless task_changed?
89
90
  return if self.class.rake_tasks.include?(task)
91
+
90
92
  errors.add(:task, "Task \"#{task}\" not found")
91
93
  end
92
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,13 +65,13 @@ 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
73
  if !task_exist?
64
- check_log("Task does not exist: #{task}")
74
+ check_on_task_not_exist
65
75
  elsif next_run < Time.zone.now
66
76
  check_log('Next run reached. Running...')
67
77
  spawn_task
@@ -74,11 +84,13 @@ class ScheduledTask < ActiveRecord::Base
74
84
  params = ['bundle', 'exec', 'tasks_scheduler_run_task', id.to_s]
75
85
  check_log("Spawn command: #{params} (Task: #{task})")
76
86
  spawn_pid = nil
77
- Dir.chdir(Rails.root) do
78
- 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
79
91
  end
80
92
  Process.detach(spawn_pid)
81
- update_attributes!(pid: spawn_pid, last_fail_status: nil)
93
+ update!(pid: spawn_pid, last_fail_status: nil)
82
94
  end
83
95
 
84
96
  def timeout?