tasks_scheduler 0.3.0 → 0.5.1

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 (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?