resque-admin-scheduler 1.0.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/bin/migrate_to_timestamps_set.rb +16 -0
  3. data/exe/resque-scheduler +2 -2
  4. data/lib/resque/scheduler/cli.rb +6 -6
  5. data/lib/resque/scheduler/delaying_extensions.rb +14 -14
  6. data/lib/resque/scheduler/env.rb +7 -7
  7. data/lib/resque/scheduler/lock.rb +1 -1
  8. data/lib/resque/scheduler/locking.rb +7 -7
  9. data/lib/resque/scheduler/logger_builder.rb +4 -4
  10. data/lib/resque/scheduler/scheduling_extensions.rb +4 -4
  11. data/lib/resque/scheduler/server/views/delayed.erb +4 -4
  12. data/lib/resque/scheduler/server/views/search.erb +1 -1
  13. data/lib/resque/scheduler/server.rb +37 -37
  14. data/lib/resque/scheduler/signal_handling.rb +1 -1
  15. data/lib/resque/scheduler/tasks.rb +6 -6
  16. data/lib/resque/scheduler/util.rb +4 -4
  17. data/lib/resque/scheduler_admin/cli.rb +147 -0
  18. data/lib/resque/scheduler_admin/delaying_extensions.rb +324 -0
  19. data/lib/resque/scheduler_admin/env.rb +89 -0
  20. data/lib/resque/scheduler_admin/lock.rb +4 -0
  21. data/lib/resque/scheduler_admin/locking.rb +104 -0
  22. data/lib/resque/scheduler_admin/logger_builder.rb +72 -0
  23. data/lib/resque/scheduler_admin/scheduling_extensions.rb +141 -0
  24. data/lib/resque/scheduler_admin/server/views/delayed.erb +63 -0
  25. data/lib/resque/scheduler_admin/server/views/search.erb +72 -0
  26. data/lib/resque/scheduler_admin/signal_handling.rb +40 -0
  27. data/lib/resque/scheduler_admin/tasks.rb +25 -0
  28. data/lib/resque/scheduler_admin/util.rb +39 -0
  29. data/lib/resque-admin-scheduler.rb +4 -0
  30. data/lib/resque_admin/scheduler/cli.rb +147 -0
  31. data/lib/{resque → resque_admin}/scheduler/configuration.rb +1 -1
  32. data/lib/resque_admin/scheduler/delaying_extensions.rb +324 -0
  33. data/lib/resque_admin/scheduler/env.rb +89 -0
  34. data/lib/{resque → resque_admin}/scheduler/extension.rb +1 -1
  35. data/lib/{resque → resque_admin}/scheduler/failure_handler.rb +2 -2
  36. data/lib/{resque → resque_admin}/scheduler/lock/base.rb +3 -3
  37. data/lib/{resque → resque_admin}/scheduler/lock/basic.rb +4 -4
  38. data/lib/{resque → resque_admin}/scheduler/lock/resilient.rb +4 -4
  39. data/lib/resque_admin/scheduler/lock.rb +4 -0
  40. data/lib/resque_admin/scheduler/locking.rb +104 -0
  41. data/lib/resque_admin/scheduler/logger_builder.rb +72 -0
  42. data/lib/{resque → resque_admin}/scheduler/plugin.rb +1 -1
  43. data/lib/resque_admin/scheduler/scheduling_extensions.rb +141 -0
  44. data/lib/resque_admin/scheduler/server/views/delayed.erb +63 -0
  45. data/lib/{resque → resque_admin}/scheduler/server/views/delayed_schedules.erb +0 -0
  46. data/lib/{resque → resque_admin}/scheduler/server/views/delayed_timestamp.erb +2 -2
  47. data/lib/{resque → resque_admin}/scheduler/server/views/requeue-params.erb +0 -0
  48. data/lib/{resque → resque_admin}/scheduler/server/views/scheduler.erb +7 -7
  49. data/lib/resque_admin/scheduler/server/views/search.erb +72 -0
  50. data/lib/{resque → resque_admin}/scheduler/server/views/search_form.erb +0 -0
  51. data/lib/resque_admin/scheduler/server.rb +268 -0
  52. data/lib/resque_admin/scheduler/signal_handling.rb +40 -0
  53. data/lib/resque_admin/scheduler/tasks.rb +25 -0
  54. data/lib/resque_admin/scheduler/util.rb +39 -0
  55. data/lib/{resque → resque_admin}/scheduler/version.rb +2 -2
  56. data/lib/{resque → resque_admin}/scheduler.rb +44 -44
  57. data/tasks/resque_admin_scheduler.rake +2 -0
  58. metadata +47 -85
  59. data/AUTHORS.md +0 -81
  60. data/CHANGELOG.md +0 -456
  61. data/CODE_OF_CONDUCT.md +0 -74
  62. data/CONTRIBUTING.md +0 -6
  63. data/Gemfile +0 -4
  64. data/LICENSE +0 -23
  65. data/README.md +0 -691
  66. data/Rakefile +0 -21
  67. data/lib/resque-scheduler.rb +0 -4
  68. data/resque-scheduler.gemspec +0 -60
@@ -0,0 +1,72 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'mono_logger'
4
+
5
+ module ResqueAdmin
6
+ module Scheduler
7
+ # Just builds a logger, with specified verbosity and destination.
8
+ # The simplest example:
9
+ #
10
+ # ResqueAdmin::Scheduler::LoggerBuilder.new.build
11
+ class LoggerBuilder
12
+ # Initializes new instance of the builder
13
+ #
14
+ # Pass :opts Hash with
15
+ # - :quiet if logger needs to be silent for all levels. Default - false
16
+ # - :verbose if there is a need in debug messages. Default - false
17
+ # - :log_dev to output logs into a desired file. Default - STDOUT
18
+ # - :format log format, either 'text' or 'json'. Default - 'text'
19
+ #
20
+ # Example:
21
+ #
22
+ # LoggerBuilder.new(
23
+ # :quiet => false, :verbose => true, :log_dev => 'log/scheduler.log'
24
+ # )
25
+ def initialize(opts = {})
26
+ @quiet = !!opts[:quiet]
27
+ @verbose = !!opts[:verbose]
28
+ @log_dev = opts[:log_dev] || $stdout
29
+ @format = opts[:format] || 'text'
30
+ end
31
+
32
+ # Returns an instance of MonoLogger
33
+ def build
34
+ logger = MonoLogger.new(@log_dev)
35
+ logger.level = level
36
+ logger.formatter = send(:"#{@format}_formatter")
37
+ logger
38
+ end
39
+
40
+ private
41
+
42
+ def level
43
+ if @verbose && !@quiet
44
+ MonoLogger::DEBUG
45
+ elsif !@quiet
46
+ MonoLogger::INFO
47
+ else
48
+ MonoLogger::FATAL
49
+ end
50
+ end
51
+
52
+ def text_formatter
53
+ proc do |severity, datetime, _progname, msg|
54
+ "resque_admin-scheduler: [#{severity}] #{datetime.iso8601}: #{msg}\n"
55
+ end
56
+ end
57
+
58
+ def json_formatter
59
+ proc do |severity, datetime, progname, msg|
60
+ require 'json'
61
+ JSON.dump(
62
+ name: 'resque_admin-scheduler',
63
+ progname: progname,
64
+ level: severity,
65
+ timestamp: datetime.iso8601,
66
+ msg: msg
67
+ ) + "\n"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,6 +1,6 @@
1
1
  # vim:fileencoding=utf-8
2
2
 
3
- module Resque
3
+ module ResqueAdmin
4
4
  module Scheduler
5
5
  module Plugin
6
6
  def self.hooks(job, pattern)
@@ -0,0 +1,141 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ module ResqueAdmin
4
+ module Scheduler
5
+ module SchedulingExtensions
6
+ # Accepts a new schedule configuration of the form:
7
+ #
8
+ # {
9
+ # "MakeTea" => {
10
+ # "every" => "1m" },
11
+ # "some_name" => {
12
+ # "cron" => "5/* * * *",
13
+ # "class" => "DoSomeWork",
14
+ # "args" => "work on this string",
15
+ # "description" => "this thing works it"s butter off" },
16
+ # ...
17
+ # }
18
+ #
19
+ # Hash keys can be anything and are used to describe and reference
20
+ # the scheduled job. If the "class" argument is missing, the key
21
+ # is used implicitly as "class" argument - in the "MakeTea" example,
22
+ # "MakeTea" is used both as job name and resque_admin worker class.
23
+ #
24
+ # Any jobs that were in the old schedule, but are not
25
+ # present in the new schedule, will be removed.
26
+ #
27
+ # :cron can be any cron scheduling string
28
+ #
29
+ # :every can be used in lieu of :cron. see rufus-scheduler's 'every'
30
+ # usage for valid syntax. If :cron is present it will take precedence
31
+ # over :every.
32
+ #
33
+ # :class must be a resque_admin worker class. If it is missing, the job name
34
+ # (hash key) will be used as :class.
35
+ #
36
+ # :args can be any yaml which will be converted to a ruby literal and
37
+ # passed in a params. (optional)
38
+ #
39
+ # :rails_envs is the list of envs where the job gets loaded. Envs are
40
+ # comma separated (optional)
41
+ #
42
+ # :description is just that, a description of the job (optional). If
43
+ # params is an array, each element in the array is passed as a separate
44
+ # param, otherwise params is passed in as the only parameter to
45
+ # perform.
46
+ def schedule=(schedule_hash)
47
+ @non_persistent_schedules = nil
48
+ prepared_schedules = prepare_schedules(schedule_hash)
49
+
50
+ prepared_schedules.each do |schedule, config|
51
+ set_schedule(schedule, config, false)
52
+ end
53
+
54
+ # ensure only return the successfully saved data!
55
+ reload_schedule!
56
+ end
57
+
58
+ # Returns the schedule hash
59
+ def schedule
60
+ @schedule ||= all_schedules
61
+ @schedule || {}
62
+ end
63
+
64
+ # reloads the schedule from redis and memory
65
+ def reload_schedule!
66
+ @schedule = all_schedules
67
+ end
68
+
69
+ # gets the schedules as it exists in redis
70
+ def all_schedules
71
+ non_persistent_schedules.merge(persistent_schedules)
72
+ end
73
+
74
+ # Create or update a schedule with the provided name and configuration.
75
+ #
76
+ # Note: values for class and custom_job_class need to be strings,
77
+ # not constants.
78
+ #
79
+ # ResqueAdmin.set_schedule('some_job', {:class => 'SomeJob',
80
+ # :every => '15mins',
81
+ # :queue => 'high',
82
+ # :args => '/tmp/poop'})
83
+ #
84
+ # Preventing a reload is optional and available to batch operations
85
+ def set_schedule(name, config, reload = true)
86
+ persist = config.delete(:persist) || config.delete('persist')
87
+
88
+ if persist
89
+ redis.hset(:persistent_schedules, name, encode(config))
90
+ else
91
+ non_persistent_schedules[name] = decode(encode(config))
92
+ end
93
+
94
+ redis.sadd(:schedules_changed, name)
95
+ reload_schedule! if reload
96
+ end
97
+
98
+ # retrive the schedule configuration for the given name
99
+ def fetch_schedule(name)
100
+ schedule[name]
101
+ end
102
+
103
+ # remove a given schedule by name
104
+ def remove_schedule(name)
105
+ non_persistent_schedules.delete(name)
106
+ redis.hdel(:persistent_schedules, name)
107
+ redis.sadd(:schedules_changed, name)
108
+
109
+ reload_schedule!
110
+ end
111
+
112
+ private
113
+
114
+ # we store our non-persistent schedules in this hash
115
+ def non_persistent_schedules
116
+ @non_persistent_schedules ||= {}
117
+ end
118
+
119
+ # reads the persistent schedules from redis
120
+ def persistent_schedules
121
+ redis.hgetall(:persistent_schedules).tap do |h|
122
+ h.each do |name, config|
123
+ h[name] = decode(config)
124
+ end
125
+ end
126
+ end
127
+
128
+ def prepare_schedules(schedule_hash)
129
+ prepared_hash = {}
130
+ schedule_hash.each do |name, job_spec|
131
+ job_spec = job_spec.dup
132
+ unless job_spec.key?('class') || job_spec.key?(:class)
133
+ job_spec['class'] = name
134
+ end
135
+ prepared_hash[name] = job_spec
136
+ end
137
+ prepared_hash
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,63 @@
1
+ <h1>Delayed Jobs</h1>
2
+ <%- size = resque_admin.delayed_queue_schedule_size %>
3
+
4
+ <%= scheduler_view :search_form, layout: false %>
5
+
6
+ <p style="font-color: red; font-weight: bold;">
7
+ <%= @error_message %>
8
+ </p>
9
+
10
+ <p class='intro'>
11
+ This list below contains the timestamps for scheduled delayed jobs.
12
+ Server local time: <%= Time.now %>
13
+ </p>
14
+
15
+ <p class='sub'>
16
+ Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%= size %></b> timestamps
17
+ </p>
18
+
19
+ <table>
20
+ <tr>
21
+ <th></th>
22
+ <th>Timestamp</th>
23
+ <th>Job count</th>
24
+ <th>Class</th>
25
+ <th>Args</th>
26
+ <th>All schedules</th>
27
+ </tr>
28
+ <% resque_admin.delayed_queue_peek(start, 20).each do |timestamp| %>
29
+ <tr>
30
+ <td>
31
+ <form action="<%= u "/delayed/queue_now" %>" method="post">
32
+ <input type="hidden" name="timestamp" value="<%= timestamp.to_i %>">
33
+ <input type="submit" value="Queue now">
34
+ </form>
35
+ </td>
36
+ <td><a href="<%= u "delayed/#{timestamp}" %>"><%= format_time(Time.at(timestamp)) %></a></td>
37
+ <td><%= delayed_timestamp_size = resque_admin.delayed_timestamp_size(timestamp) %></td>
38
+ <% job = resque_admin.delayed_timestamp_peek(timestamp, 0, 1).first %>
39
+ <td>
40
+ <% if job && delayed_timestamp_size == 1 %>
41
+ <%= h(job['class']) %>
42
+ <% else %>
43
+ <a href="<%= u "delayed/#{timestamp}" %>">see details</a>
44
+ <% end %>
45
+ </td>
46
+ <td><%= h(show_job_arguments(job['args'])) if job && delayed_timestamp_size == 1 %></td>
47
+ <td>
48
+ <% if job %>
49
+ <a href="<%=u URI("/delayed/jobs/#{job['class']}?args=" + URI.encode(job['args'].to_json)) %>">All schedules</a>
50
+ <% end %>
51
+ </td>
52
+ </tr>
53
+ <% end %>
54
+ </table>
55
+
56
+ <% if size > 0 %>
57
+ <br>
58
+ <form method="POST" action="<%=u 'delayed/clear'%>" class='clear-delayed'>
59
+ <input type='submit' name='' value='Clear Delayed Jobs' />
60
+ </form>
61
+ <% end %>
62
+
63
+ <%= partial :next_more, :start => start, :size => size %>
@@ -2,14 +2,14 @@
2
2
 
3
3
  <h1>Delayed jobs scheduled for <%= format_time(Time.at(timestamp)) %></h1>
4
4
 
5
- <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_timestamp_size(timestamp)%></b> jobs</p>
5
+ <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque_admin.delayed_timestamp_size(timestamp)%></b> jobs</p>
6
6
 
7
7
  <table class='jobs'>
8
8
  <tr>
9
9
  <th>Class</th>
10
10
  <th>Args</th>
11
11
  </tr>
12
- <% jobs = resque.delayed_timestamp_peek(timestamp, start, 20) %>
12
+ <% jobs = resque_admin.delayed_timestamp_peek(timestamp, start, 20) %>
13
13
  <% jobs.each do |job| %>
14
14
  <tr>
15
15
  <td class='class'><%= job['class'] %></td>
@@ -4,8 +4,8 @@
4
4
  The list below contains all scheduled jobs. Click &quot;Queue now&quot; to queue
5
5
  a job immediately.
6
6
  <br/> Server local time: <%= Time.now %>
7
- <br/> Server Environment: <%= Resque::Scheduler.env %>
8
- <br/> Current master: <%= Resque.redis.get(Resque::Scheduler.master_lock.key) %>
7
+ <br/> Server Environment: <%= ResqueAdmin::Scheduler.env %>
8
+ <br/> Current master: <%= ResqueAdmin.redis.get(ResqueAdmin::Scheduler.master_lock.key) %>
9
9
  </p>
10
10
  <p class='intro'>
11
11
  The highlighted jobs are skipped for current environment.
@@ -14,7 +14,7 @@
14
14
  <table>
15
15
  <tr>
16
16
  <th>Index</th>
17
- <% if Resque::Scheduler.dynamic %>
17
+ <% if ResqueAdmin::Scheduler.dynamic %>
18
18
  <th></th>
19
19
  <% end %>
20
20
  <th></th>
@@ -26,11 +26,11 @@
26
26
  <th>Arguments</th>
27
27
  <th>Last Enqueued</th>
28
28
  </tr>
29
- <% Resque.schedule.keys.sort.each_with_index do |name, index| %>
30
- <% config = Resque.schedule[name] %>
29
+ <% ResqueAdmin.schedule.keys.sort.each_with_index do |name, index| %>
30
+ <% config = ResqueAdmin.schedule[name] %>
31
31
  <tr style="<%= scheduled_in_this_env?(name) ? '' : 'color: #9F6000;background: #FEEFB3;' %>">
32
32
  <td style="padding-left: 15px;"><%= index+ 1 %>.</td>
33
- <% if Resque::Scheduler.dynamic %>
33
+ <% if ResqueAdmin::Scheduler.dynamic %>
34
34
  <td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
35
35
  <form action="<%= u "/schedule" %>" method="post" style="margin-left: 0">
36
36
  <input type="hidden" name="job_name" value="<%= h name %>">
@@ -51,7 +51,7 @@
51
51
  <td><%= h schedule_class(config) %></td>
52
52
  <td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
53
53
  <td><%= h show_job_arguments(config['args']) %></td>
54
- <td><%= h Resque.get_last_enqueued_at(name) || 'Never' %></td>
54
+ <td><%= h ResqueAdmin.get_last_enqueued_at(name) || 'Never' %></td>
55
55
  </tr>
56
56
  <% end %>
57
57
  </table>
@@ -0,0 +1,72 @@
1
+ <h1>Search Results</h1>
2
+ <%= scheduler_view :search_form, layout: false %>
3
+ <hr>
4
+ <% delayed = @jobs.select { |j| j['where_at'] == 'delayed' } %>
5
+ <h1>Delayed jobs</h1>
6
+ <table class='jobs'>
7
+ <tr>
8
+ <th></th>
9
+ <th></th>
10
+ <th>Timestamp</th>
11
+ <th>Class</th>
12
+ <th>Args</th>
13
+ </tr>
14
+ <% delayed.each do |job| %>
15
+ <tr>
16
+ <td>
17
+ <form action="<%= u "/delayed/queue_now" %>" method="post">
18
+ <input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
19
+ <input type="submit" value="Queue now">
20
+ </form>
21
+ </td>
22
+ <td>
23
+ <form action="<%= u "/delayed/cancel_now" %>" method="post">
24
+ <input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
25
+ <input type="hidden" name="klass" value="<%= job['class'] %>">
26
+ <input type="hidden" name="args" value="<%= h(ResqueAdmin.encode job['args']) %>">
27
+ <input type="submit" value="Cancel Job">
28
+ </form>
29
+ </td>
30
+ <td class='args'><%= format_time(Time.at(job['timestamp'])) %></td>
31
+ <td class='class'><%= job['class'] %></td>
32
+ <td class='args'><%= h job['args'].inspect %></td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+ </h1>
37
+
38
+ <% queued = @jobs.select { |j| j['where_at'] == 'queued' } %>
39
+ <h1>Queued jobs</h1>
40
+ <table class='jobs'>
41
+ <tr>
42
+ <th>Queue</th>
43
+ <th>Class</th>
44
+ <th>Args</th>
45
+ </tr>
46
+ <% queued.each do |job| %>
47
+ <tr>
48
+ <td class='class'><%= job['queue'] %></td>
49
+ <td class='class'><%= job['class'] %></td>
50
+ <td class='args'><%= h job['args'].inspect %></td>
51
+ </tr>
52
+ <% end %>
53
+ </table>
54
+
55
+ <% working = @jobs.select { |j| j['where_at'] == 'working' } %>
56
+ <h1>Working jobs</h1>
57
+ <table class='jobs'>
58
+ <tr>
59
+ <th>Queue</th>
60
+ <th>Class</th>
61
+ <th>Args</th>
62
+ </tr>
63
+ <% working.each do |job| %>
64
+ <tr>
65
+ <td class='class'><%= job['queue'] %></td>
66
+ <td class='class'><%= job['class'] %></td>
67
+ <td class='args'><%= h job['args'].inspect %></td>
68
+ </tr>
69
+ <% end %>
70
+ </table>
71
+
72
+