qless 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -3
- data/README.md +70 -25
- data/Rakefile +125 -9
- data/exe/install_phantomjs +21 -0
- data/lib/qless.rb +115 -76
- data/lib/qless/config.rb +11 -9
- data/lib/qless/failure_formatter.rb +43 -0
- data/lib/qless/job.rb +201 -102
- data/lib/qless/job_reservers/ordered.rb +7 -1
- data/lib/qless/job_reservers/round_robin.rb +16 -6
- data/lib/qless/job_reservers/shuffled_round_robin.rb +9 -2
- data/lib/qless/lua/qless-lib.lua +2463 -0
- data/lib/qless/lua/qless.lua +2012 -0
- data/lib/qless/lua_script.rb +63 -12
- data/lib/qless/middleware/memory_usage_monitor.rb +62 -0
- data/lib/qless/middleware/metriks.rb +45 -0
- data/lib/qless/middleware/redis_reconnect.rb +6 -3
- data/lib/qless/middleware/requeue_exceptions.rb +94 -0
- data/lib/qless/middleware/retry_exceptions.rb +38 -9
- data/lib/qless/middleware/sentry.rb +3 -7
- data/lib/qless/middleware/timeout.rb +64 -0
- data/lib/qless/queue.rb +90 -55
- data/lib/qless/server.rb +177 -130
- data/lib/qless/server/views/_job.erb +33 -15
- data/lib/qless/server/views/completed.erb +11 -0
- data/lib/qless/server/views/layout.erb +70 -11
- data/lib/qless/server/views/overview.erb +93 -53
- data/lib/qless/server/views/queue.erb +9 -8
- data/lib/qless/server/views/queues.erb +18 -1
- data/lib/qless/subscriber.rb +37 -22
- data/lib/qless/tasks.rb +5 -10
- data/lib/qless/test_helpers/worker_helpers.rb +55 -0
- data/lib/qless/version.rb +3 -1
- data/lib/qless/worker.rb +4 -413
- data/lib/qless/worker/base.rb +247 -0
- data/lib/qless/worker/forking.rb +245 -0
- data/lib/qless/worker/serial.rb +41 -0
- metadata +135 -52
- data/lib/qless/qless-core/cancel.lua +0 -101
- data/lib/qless/qless-core/complete.lua +0 -233
- data/lib/qless/qless-core/config.lua +0 -56
- data/lib/qless/qless-core/depends.lua +0 -65
- data/lib/qless/qless-core/deregister_workers.lua +0 -12
- data/lib/qless/qless-core/fail.lua +0 -117
- data/lib/qless/qless-core/failed.lua +0 -83
- data/lib/qless/qless-core/get.lua +0 -37
- data/lib/qless/qless-core/heartbeat.lua +0 -51
- data/lib/qless/qless-core/jobs.lua +0 -41
- data/lib/qless/qless-core/pause.lua +0 -18
- data/lib/qless/qless-core/peek.lua +0 -165
- data/lib/qless/qless-core/pop.lua +0 -314
- data/lib/qless/qless-core/priority.lua +0 -32
- data/lib/qless/qless-core/put.lua +0 -169
- data/lib/qless/qless-core/qless-lib.lua +0 -2354
- data/lib/qless/qless-core/qless.lua +0 -1862
- data/lib/qless/qless-core/queues.lua +0 -58
- data/lib/qless/qless-core/recur.lua +0 -190
- data/lib/qless/qless-core/retry.lua +0 -73
- data/lib/qless/qless-core/stats.lua +0 -92
- data/lib/qless/qless-core/tag.lua +0 -100
- data/lib/qless/qless-core/track.lua +0 -79
- data/lib/qless/qless-core/unfail.lua +0 -54
- data/lib/qless/qless-core/unpause.lua +0 -12
- data/lib/qless/qless-core/workers.lua +0 -69
- data/lib/qless/wait_until.rb +0 -19
data/lib/qless/queue.rb
CHANGED
@@ -1,43 +1,52 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'qless/job'
|
4
|
+
require 'redis'
|
5
|
+
require 'json'
|
4
6
|
|
5
7
|
module Qless
|
8
|
+
# A class for interacting with jobs in different states in a queue. Not meant
|
9
|
+
# to be instantiated directly, it's accessed with Queue#jobs
|
6
10
|
class QueueJobs
|
7
11
|
def initialize(name, client)
|
8
12
|
@name = name
|
9
13
|
@client = client
|
10
14
|
end
|
11
15
|
|
12
|
-
def running(start=0, count=25)
|
13
|
-
@client.
|
16
|
+
def running(start = 0, count = 25)
|
17
|
+
@client.call('jobs', 'running', @name, start, count)
|
14
18
|
end
|
15
19
|
|
16
|
-
def stalled(start=0, count=25)
|
17
|
-
@client.
|
20
|
+
def stalled(start = 0, count = 25)
|
21
|
+
@client.call('jobs', 'stalled', @name, start, count)
|
18
22
|
end
|
19
23
|
|
20
|
-
def scheduled(start=0, count=25)
|
21
|
-
@client.
|
24
|
+
def scheduled(start = 0, count = 25)
|
25
|
+
@client.call('jobs', 'scheduled', @name, start, count)
|
22
26
|
end
|
23
27
|
|
24
|
-
def depends(start=0, count=25)
|
25
|
-
@client.
|
28
|
+
def depends(start = 0, count = 25)
|
29
|
+
@client.call('jobs', 'depends', @name, start, count)
|
26
30
|
end
|
27
31
|
|
28
|
-
def recurring(start=0, count=25)
|
29
|
-
@client.
|
32
|
+
def recurring(start = 0, count = 25)
|
33
|
+
@client.call('jobs', 'recurring', @name, start, count)
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
37
|
+
# A class for interacting with a specific queue. Not meant to be instantiated
|
38
|
+
# directly, it's accessed with Client#queues[...]
|
33
39
|
class Queue
|
34
40
|
attr_reader :name, :client
|
35
|
-
attr_accessor :worker_name
|
36
41
|
|
37
42
|
def initialize(name, client)
|
38
43
|
@client = client
|
39
44
|
@name = name
|
40
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
# Our worker name is the same as our client's
|
48
|
+
def worker_name
|
49
|
+
@client.worker_name
|
41
50
|
end
|
42
51
|
|
43
52
|
def jobs
|
@@ -45,7 +54,7 @@ module Qless
|
|
45
54
|
end
|
46
55
|
|
47
56
|
def counts
|
48
|
-
JSON.parse(@client.
|
57
|
+
JSON.parse(@client.call('queues', @name))
|
49
58
|
end
|
50
59
|
|
51
60
|
def heartbeat
|
@@ -57,20 +66,36 @@ module Qless
|
|
57
66
|
end
|
58
67
|
|
59
68
|
def max_concurrency
|
60
|
-
value = get_config(
|
69
|
+
value = get_config('max-concurrency')
|
61
70
|
value && Integer(value)
|
62
71
|
end
|
63
72
|
|
64
73
|
def max_concurrency=(value)
|
65
|
-
set_config
|
74
|
+
set_config 'max-concurrency', value
|
75
|
+
end
|
76
|
+
|
77
|
+
def paused?
|
78
|
+
counts['paused']
|
66
79
|
end
|
67
80
|
|
68
|
-
def pause
|
69
|
-
@client.
|
81
|
+
def pause(opts = {})
|
82
|
+
@client.call('pause', name)
|
83
|
+
@client.call('timeout', jobs.running(0, -1)) unless opts[:stopjobs].nil?
|
70
84
|
end
|
71
85
|
|
72
86
|
def unpause
|
73
|
-
@client.
|
87
|
+
@client.call('unpause', name)
|
88
|
+
end
|
89
|
+
|
90
|
+
QueueNotEmptyError = Class.new(StandardError)
|
91
|
+
|
92
|
+
def forget
|
93
|
+
job_count = length
|
94
|
+
if job_count.zero?
|
95
|
+
@client.call('queue.forget', name)
|
96
|
+
else
|
97
|
+
raise QueueNotEmptyError, "The queue is not empty. It has #{job_count} jobs."
|
98
|
+
end
|
74
99
|
end
|
75
100
|
|
76
101
|
# Put the described job in this queue
|
@@ -78,20 +103,18 @@ module Qless
|
|
78
103
|
# => priority (int)
|
79
104
|
# => tags (array of strings)
|
80
105
|
# => delay (int)
|
81
|
-
def put(klass, data, opts={})
|
106
|
+
def put(klass, data, opts = {})
|
82
107
|
opts = job_options(klass, data, opts)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
'depends', JSON.generate(opts.fetch(:depends, []))
|
94
|
-
])
|
108
|
+
@client.call('put', worker_name, @name,
|
109
|
+
(opts[:jid] || Qless.generate_jid),
|
110
|
+
klass.is_a?(String) ? klass : klass.name,
|
111
|
+
JSON.generate(data),
|
112
|
+
opts.fetch(:delay, 0),
|
113
|
+
'priority', opts.fetch(:priority, 0),
|
114
|
+
'tags', JSON.generate(opts.fetch(:tags, [])),
|
115
|
+
'retries', opts.fetch(:retries, 5),
|
116
|
+
'depends', JSON.generate(opts.fetch(:depends, []))
|
117
|
+
)
|
95
118
|
end
|
96
119
|
|
97
120
|
# Make a recurring job in this queue
|
@@ -100,52 +123,64 @@ module Qless
|
|
100
123
|
# => tags (array of strings)
|
101
124
|
# => retries (int)
|
102
125
|
# => offset (int)
|
103
|
-
def recur(klass, data, interval, opts={})
|
126
|
+
def recur(klass, data, interval, opts = {})
|
104
127
|
opts = job_options(klass, data, opts)
|
105
|
-
|
106
|
-
|
107
|
-
'on',
|
128
|
+
@client.call(
|
129
|
+
'recur',
|
108
130
|
@name,
|
109
|
-
(opts[:jid]
|
110
|
-
klass.
|
131
|
+
(opts[:jid] || Qless.generate_jid),
|
132
|
+
klass.is_a?(String) ? klass : klass.name,
|
111
133
|
JSON.generate(data),
|
112
|
-
Time.now.to_f,
|
113
134
|
'interval', interval, opts.fetch(:offset, 0),
|
114
135
|
'priority', opts.fetch(:priority, 0),
|
115
136
|
'tags', JSON.generate(opts.fetch(:tags, [])),
|
116
|
-
'retries', opts.fetch(:retries, 5)
|
117
|
-
|
137
|
+
'retries', opts.fetch(:retries, 5),
|
138
|
+
'backlog', opts.fetch(:backlog, 0)
|
139
|
+
)
|
118
140
|
end
|
119
141
|
|
120
142
|
# Pop a work item off the queue
|
121
|
-
def pop(count=nil)
|
122
|
-
|
123
|
-
|
143
|
+
def pop(count = nil)
|
144
|
+
jids = JSON.parse(@client.call('pop', @name, worker_name, (count || 1)))
|
145
|
+
jobs = jids.map { |j| Job.new(@client, j) }
|
146
|
+
count.nil? ? jobs[0] : jobs
|
124
147
|
end
|
125
148
|
|
126
149
|
# Peek at a work item
|
127
|
-
def peek(count=nil)
|
128
|
-
|
129
|
-
|
150
|
+
def peek(count = nil)
|
151
|
+
jids = JSON.parse(@client.call('peek', @name, (count || 1)))
|
152
|
+
jobs = jids.map { |j| Job.new(@client, j) }
|
153
|
+
count.nil? ? jobs[0] : jobs
|
130
154
|
end
|
131
155
|
|
132
|
-
def stats(date=nil)
|
133
|
-
JSON.parse(@client.
|
156
|
+
def stats(date = nil)
|
157
|
+
JSON.parse(@client.call('stats', @name, (date || Time.now.to_f)))
|
134
158
|
end
|
135
159
|
|
136
160
|
# How many items in the queue?
|
137
161
|
def length
|
138
162
|
(@client.redis.multi do
|
139
|
-
|
140
|
-
|
141
|
-
|
163
|
+
%w[ locks work scheduled depends ].each do |suffix|
|
164
|
+
@client.redis.zcard("ql:q:#{@name}-#{suffix}")
|
165
|
+
end
|
142
166
|
end).inject(0, :+)
|
143
167
|
end
|
144
168
|
|
145
169
|
def to_s
|
146
170
|
"#<Qless::Queue #{@name}>"
|
147
171
|
end
|
148
|
-
|
172
|
+
alias_method :inspect, :to_s
|
173
|
+
|
174
|
+
def ==(other)
|
175
|
+
self.class == other.class &&
|
176
|
+
client == other.client &&
|
177
|
+
name.to_s == other.name.to_s
|
178
|
+
end
|
179
|
+
alias eql? ==
|
180
|
+
|
181
|
+
def hash
|
182
|
+
self.class.hash ^ client.hash ^ name.to_s.hash
|
183
|
+
end
|
149
184
|
|
150
185
|
private
|
151
186
|
|
data/lib/qless/server.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
1
3
|
require 'sinatra/base'
|
2
4
|
require 'qless'
|
3
5
|
|
4
|
-
# Much of this is shamelessly poached from the resque web client
|
5
|
-
|
6
6
|
module Qless
|
7
|
+
# The Qless web interface
|
7
8
|
class Server < Sinatra::Base
|
8
9
|
# Path-y-ness
|
9
10
|
dir = File.dirname(File.expand_path(__FILE__))
|
@@ -27,7 +28,7 @@ module Qless
|
|
27
28
|
include Rack::Utils
|
28
29
|
|
29
30
|
def url_path(*path_parts)
|
30
|
-
[
|
31
|
+
[path_prefix, path_parts].join('/').squeeze('/')
|
31
32
|
end
|
32
33
|
alias_method :u, :url_path
|
33
34
|
|
@@ -49,11 +50,11 @@ module Qless
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def next_page_url
|
52
|
-
page_url
|
53
|
+
page_url(1)
|
53
54
|
end
|
54
55
|
|
55
56
|
def prev_page_url
|
56
|
-
page_url
|
57
|
+
page_url(-1)
|
57
58
|
end
|
58
59
|
|
59
60
|
def current_page
|
@@ -75,34 +76,35 @@ module Qless
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def tabs
|
78
|
-
|
79
|
-
{:
|
80
|
-
{:
|
81
|
-
{:
|
82
|
-
{:
|
83
|
-
{:
|
84
|
-
{:
|
79
|
+
[
|
80
|
+
{ name: 'Queues' , path: '/queues' },
|
81
|
+
{ name: 'Workers' , path: '/workers' },
|
82
|
+
{ name: 'Track' , path: '/track' },
|
83
|
+
{ name: 'Failed' , path: '/failed' },
|
84
|
+
{ name: 'Completed', path: '/completed'},
|
85
|
+
{ name: 'Config' , path: '/config' },
|
86
|
+
{ name: 'About' , path: '/about' }
|
85
87
|
]
|
86
88
|
end
|
87
89
|
|
88
90
|
def application_name
|
89
|
-
|
91
|
+
client.config['application']
|
90
92
|
end
|
91
93
|
|
92
94
|
def queues
|
93
|
-
|
95
|
+
client.queues.counts
|
94
96
|
end
|
95
97
|
|
96
98
|
def tracked
|
97
|
-
|
99
|
+
client.jobs.tracked
|
98
100
|
end
|
99
101
|
|
100
102
|
def workers
|
101
|
-
|
103
|
+
client.workers.counts
|
102
104
|
end
|
103
105
|
|
104
106
|
def failed
|
105
|
-
|
107
|
+
client.jobs.failed
|
106
108
|
end
|
107
109
|
|
108
110
|
# Return the supplied object back as JSON
|
@@ -113,45 +115,46 @@ module Qless
|
|
113
115
|
|
114
116
|
# Make the id acceptable as an id / att in HTML
|
115
117
|
def sanitize_attr(attr)
|
116
|
-
|
118
|
+
attr.gsub(/[^a-zA-Z\:\_]/, '-')
|
117
119
|
end
|
118
120
|
|
119
121
|
# What are the top tags? Since it might go on, say, every
|
120
122
|
# page, then we should probably be caching it
|
121
123
|
def top_tags
|
122
124
|
@top_tags ||= {
|
123
|
-
:
|
124
|
-
:
|
125
|
+
top: client.tags,
|
126
|
+
fetched: Time.now
|
125
127
|
}
|
126
|
-
if (Time.now - @top_tags[:fetched]) > 60
|
128
|
+
if (Time.now - @top_tags[:fetched]) > 60
|
127
129
|
@top_tags = {
|
128
|
-
:
|
129
|
-
:
|
130
|
+
top: client.tags,
|
131
|
+
fetched: Time.now
|
130
132
|
}
|
131
133
|
end
|
132
134
|
@top_tags[:top]
|
133
135
|
end
|
134
136
|
|
135
137
|
def strftime(t)
|
136
|
-
# From http://stackoverflow.com/questions/195740
|
138
|
+
# From http://stackoverflow.com/questions/195740
|
137
139
|
diff_seconds = Time.now - t
|
140
|
+
formatted = t.strftime('%b %e, %Y %H:%M:%S')
|
138
141
|
case diff_seconds
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
142
|
+
when 0 .. 59
|
143
|
+
"#{formatted} (#{diff_seconds.to_i} seconds ago)"
|
144
|
+
when 60 ... 3600
|
145
|
+
"#{formatted} (#{(diff_seconds / 60).to_i} minutes ago)"
|
146
|
+
when 3600 ... 3600 * 24
|
147
|
+
"#{formatted} (#{(diff_seconds / 3600).to_i} hours ago)"
|
148
|
+
when (3600 * 24) ... (3600 * 24 * 30)
|
149
|
+
"#{formatted} (#{(diff_seconds / (3600 * 24)).to_i} days ago)"
|
150
|
+
else
|
151
|
+
formatted
|
149
152
|
end
|
150
153
|
end
|
151
154
|
end
|
152
155
|
|
153
156
|
get '/?' do
|
154
|
-
erb :overview, :
|
157
|
+
erb :overview, layout: true, locals: { title: 'Overview' }
|
155
158
|
end
|
156
159
|
|
157
160
|
# Returns a JSON blob with the job counts for various queues
|
@@ -160,8 +163,8 @@ module Qless
|
|
160
163
|
end
|
161
164
|
|
162
165
|
get '/queues/?' do
|
163
|
-
erb :queues, :
|
164
|
-
:
|
166
|
+
erb :queues, layout: true, locals: {
|
167
|
+
title: 'Queues'
|
165
168
|
}
|
166
169
|
end
|
167
170
|
|
@@ -175,20 +178,19 @@ module Qless
|
|
175
178
|
queue = client.queues[params[:name]]
|
176
179
|
tab = params.fetch('tab', 'stats')
|
177
180
|
|
178
|
-
jobs =
|
179
|
-
|
181
|
+
jobs = []
|
182
|
+
if tab == 'waiting'
|
183
|
+
jobs = queue.peek(20)
|
180
184
|
elsif filtered_tabs.include?(tab)
|
181
|
-
paginated(queue.jobs, tab).map { |jid| client.jobs[jid] }
|
182
|
-
else
|
183
|
-
[]
|
185
|
+
jobs = paginated(queue.jobs, tab).map { |jid| client.jobs[jid] }
|
184
186
|
end
|
185
187
|
|
186
|
-
erb :queue, :
|
187
|
-
:
|
188
|
-
:
|
189
|
-
:
|
190
|
-
:
|
191
|
-
:
|
188
|
+
erb :queue, layout: true, locals: {
|
189
|
+
title: "Queue #{params[:name]}",
|
190
|
+
tab: tab,
|
191
|
+
jobs: jobs,
|
192
|
+
queue: client.queues[params[:name]].counts,
|
193
|
+
stats: queue.stats
|
192
194
|
}
|
193
195
|
end
|
194
196
|
|
@@ -200,106 +202,117 @@ module Qless
|
|
200
202
|
# qless-core doesn't provide functionality this way, so we'll
|
201
203
|
# do it ourselves. I'm not sure if this is how the core library
|
202
204
|
# should behave or not.
|
203
|
-
erb :failed, :
|
204
|
-
:
|
205
|
-
:
|
205
|
+
erb :failed, layout: true, locals: {
|
206
|
+
title: 'Failed',
|
207
|
+
failed: client.jobs.failed.keys.map do |t|
|
208
|
+
client.jobs.failed(t).tap { |f| f['type'] = t }
|
209
|
+
end
|
206
210
|
}
|
207
211
|
end
|
208
212
|
|
209
213
|
get '/failed/:type/?' do
|
210
|
-
erb :failed_type, :
|
211
|
-
:
|
212
|
-
:
|
213
|
-
:
|
214
|
+
erb :failed_type, layout: true, locals: {
|
215
|
+
title: 'Failed | ' + params[:type],
|
216
|
+
type: params[:type],
|
217
|
+
failed: paginated(client.jobs, :failed, params[:type])
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
get '/completed/?' do
|
222
|
+
completed = paginated(client.jobs, :complete)
|
223
|
+
erb :completed, layout: true, locals: {
|
224
|
+
title: 'Completed',
|
225
|
+
jobs: completed.map { |jid| client.jobs[jid] }
|
214
226
|
}
|
215
227
|
end
|
216
228
|
|
217
229
|
get '/track/?' do
|
218
|
-
erb :track, :
|
219
|
-
:
|
230
|
+
erb :track, layout: true, locals: {
|
231
|
+
title: 'Track'
|
220
232
|
}
|
221
233
|
end
|
222
234
|
|
223
235
|
get '/jobs/:jid' do
|
224
|
-
erb :job, :
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
236
|
+
erb :job, layout: true, locals: {
|
237
|
+
title: "Job | #{params[:jid]}",
|
238
|
+
jid: params[:jid],
|
239
|
+
job: client.jobs[params[:jid]]
|
228
240
|
}
|
229
241
|
end
|
230
242
|
|
231
243
|
get '/workers/?' do
|
232
|
-
erb :workers, :
|
233
|
-
:
|
244
|
+
erb :workers, layout: true, locals: {
|
245
|
+
title: 'Workers'
|
234
246
|
}
|
235
247
|
end
|
236
248
|
|
237
249
|
get '/workers/:worker' do
|
238
|
-
erb :worker, :
|
239
|
-
:
|
240
|
-
:
|
250
|
+
erb :worker, layout: true, locals: {
|
251
|
+
title: 'Worker | ' + params[:worker],
|
252
|
+
worker: client.workers[params[:worker]].tap do |w|
|
241
253
|
w['jobs'] = w['jobs'].map { |j| client.jobs[j] }
|
242
254
|
w['stalled'] = w['stalled'].map { |j| client.jobs[j] }
|
243
255
|
w['name'] = params[:worker]
|
244
|
-
|
256
|
+
end
|
245
257
|
}
|
246
258
|
end
|
247
259
|
|
248
260
|
get '/tag/?' do
|
249
261
|
jobs = paginated(client.jobs, :tagged, params[:tag])
|
250
|
-
erb :tag, :
|
251
|
-
:
|
252
|
-
:
|
253
|
-
:
|
254
|
-
:
|
262
|
+
erb :tag, layout: true, locals: {
|
263
|
+
title: "Tag | #{params[:tag]}",
|
264
|
+
tag: params[:tag],
|
265
|
+
jobs: jobs['jobs'].map { |jid| client.jobs[jid] },
|
266
|
+
total: jobs['total']
|
255
267
|
}
|
256
268
|
end
|
257
269
|
|
258
270
|
get '/config/?' do
|
259
|
-
erb :config, :
|
260
|
-
:
|
261
|
-
:
|
271
|
+
erb :config, layout: true, locals: {
|
272
|
+
title: 'Config',
|
273
|
+
options: client.config.all
|
262
274
|
}
|
263
275
|
end
|
264
276
|
|
265
277
|
get '/about/?' do
|
266
|
-
erb :about, :
|
267
|
-
:
|
278
|
+
erb :about, layout: true, locals: {
|
279
|
+
title: 'About'
|
268
280
|
}
|
269
281
|
end
|
270
282
|
|
271
283
|
# These are the bits where we accept AJAX requests
|
272
|
-
post
|
284
|
+
post '/track/?' do
|
273
285
|
# Expects a JSON-encoded hash with a job id, and optionally some tags
|
274
286
|
data = JSON.parse(request.body.read)
|
275
|
-
job = client.jobs[data[
|
276
|
-
if
|
277
|
-
data.fetch(
|
287
|
+
job = client.jobs[data['id']]
|
288
|
+
if !job.nil?
|
289
|
+
data.fetch('tags', false) ? job.track(*data['tags']) : job.track
|
278
290
|
if request.xhr?
|
279
|
-
json({ :
|
291
|
+
json({ tracked: [job.jid] })
|
280
292
|
else
|
281
293
|
redirect to('/track')
|
282
294
|
end
|
283
295
|
else
|
284
296
|
if request.xhr?
|
285
|
-
json({ :
|
297
|
+
json({ tracked: [] })
|
286
298
|
else
|
287
299
|
redirect to(request.referrer)
|
288
300
|
end
|
289
301
|
end
|
290
302
|
end
|
291
303
|
|
292
|
-
post
|
304
|
+
post '/untrack/?' do
|
293
305
|
# Expects a JSON-encoded array of job ids to stop tracking
|
294
|
-
jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
|
306
|
+
jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
|
307
|
+
jobs.compact!
|
295
308
|
# Go ahead and cancel all the jobs!
|
296
309
|
jobs.each do |job|
|
297
|
-
job.untrack
|
310
|
+
job.untrack
|
298
311
|
end
|
299
|
-
return json({ :
|
312
|
+
return json({ untracked: jobs.map { |job| job.jid } })
|
300
313
|
end
|
301
314
|
|
302
|
-
post
|
315
|
+
post '/priority/?' do
|
303
316
|
# Expects a JSON-encoded dictionary of jid => priority
|
304
317
|
response = Hash.new
|
305
318
|
r = JSON.parse(request.body.read)
|
@@ -314,7 +327,40 @@ module Qless
|
|
314
327
|
return json(response)
|
315
328
|
end
|
316
329
|
|
317
|
-
post
|
330
|
+
post '/pause/?' do
|
331
|
+
# Expects JSON blob: {'queue': <queue>}
|
332
|
+
r = JSON.parse(request.body.read)
|
333
|
+
if r['queue']
|
334
|
+
@client.queues[r['queue']].pause
|
335
|
+
return json({ queue: 'paused' })
|
336
|
+
else
|
337
|
+
raise 'No queue provided'
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
post '/unpause/?' do
|
342
|
+
# Expects JSON blob: {'queue': <queue>}
|
343
|
+
r = JSON.parse(request.body.read)
|
344
|
+
if r['queue']
|
345
|
+
@client.queues[r['queue']].unpause
|
346
|
+
return json({ queue: 'unpaused' })
|
347
|
+
else
|
348
|
+
raise 'No queue provided'
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
post '/timeout/?' do
|
353
|
+
# Expects JSON blob: {'jid': <jid>}
|
354
|
+
r = JSON.parse(request.body.read)
|
355
|
+
if r['jid']
|
356
|
+
@client.jobs[r['jid']].timeout
|
357
|
+
return json({ jid: r['jid'] })
|
358
|
+
else
|
359
|
+
raise 'No jid provided'
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
post '/tag/?' do
|
318
364
|
# Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
|
319
365
|
response = Hash.new
|
320
366
|
JSON.parse(request.body.read).each_pair do |jid, tags|
|
@@ -328,7 +374,7 @@ module Qless
|
|
328
374
|
return json(response)
|
329
375
|
end
|
330
376
|
|
331
|
-
post
|
377
|
+
post '/untag/?' do
|
332
378
|
# Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
|
333
379
|
response = Hash.new
|
334
380
|
JSON.parse(request.body.read).each_pair do |jid, tags|
|
@@ -342,99 +388,100 @@ module Qless
|
|
342
388
|
return json(response)
|
343
389
|
end
|
344
390
|
|
345
|
-
post
|
391
|
+
post '/move/?' do
|
346
392
|
# Expects a JSON-encoded hash of id: jid, and queue: queue_name
|
347
393
|
data = JSON.parse(request.body.read)
|
348
|
-
if data[
|
349
|
-
halt 400,
|
394
|
+
if data['id'].nil? || data['queue'].nil?
|
395
|
+
halt 400, 'Need id and queue arguments'
|
350
396
|
else
|
351
|
-
job = client.jobs[data[
|
397
|
+
job = client.jobs[data['id']]
|
352
398
|
if job.nil?
|
353
|
-
halt 404,
|
399
|
+
halt 404, 'Could not find job'
|
354
400
|
else
|
355
|
-
job.
|
356
|
-
return json({ :
|
401
|
+
job.requeue(data['queue'])
|
402
|
+
return json({ id: data['id'], queue: data['queue'] })
|
357
403
|
end
|
358
404
|
end
|
359
405
|
end
|
360
406
|
|
361
|
-
post
|
407
|
+
post '/undepend/?' do
|
362
408
|
# Expects a JSON-encoded hash of id: jid, and queue: queue_name
|
363
409
|
data = JSON.parse(request.body.read)
|
364
|
-
if data[
|
365
|
-
halt 400,
|
410
|
+
if data['id'].nil?
|
411
|
+
halt 400, 'Need id'
|
366
412
|
else
|
367
|
-
job = client.jobs[data[
|
413
|
+
job = client.jobs[data['id']]
|
368
414
|
if job.nil?
|
369
|
-
halt 404,
|
415
|
+
halt 404, 'Could not find job'
|
370
416
|
else
|
371
417
|
job.undepend(data['dependency'])
|
372
|
-
return json({:
|
418
|
+
return json({ id: data['id'] })
|
373
419
|
end
|
374
420
|
end
|
375
421
|
end
|
376
422
|
|
377
|
-
post
|
423
|
+
post '/retry/?' do
|
378
424
|
# Expects a JSON-encoded hash of id: jid, and queue: queue_name
|
379
425
|
data = JSON.parse(request.body.read)
|
380
|
-
if data[
|
381
|
-
halt 400,
|
426
|
+
if data['id'].nil?
|
427
|
+
halt 400, 'Need id'
|
382
428
|
else
|
383
|
-
job = client.jobs[data[
|
429
|
+
job = client.jobs[data['id']]
|
384
430
|
if job.nil?
|
385
|
-
halt 404,
|
431
|
+
halt 404, 'Could not find job'
|
386
432
|
else
|
387
|
-
|
388
|
-
job.
|
389
|
-
return json({ :id => data["id"], :queue => queue})
|
433
|
+
job.requeue(job.queue_name)
|
434
|
+
return json({ id: data['id'], queue: job.queue_name })
|
390
435
|
end
|
391
436
|
end
|
392
437
|
end
|
393
438
|
|
394
439
|
# Retry all the failures of a particular type
|
395
|
-
post
|
440
|
+
post '/retryall/?' do
|
396
441
|
# Expects a JSON-encoded hash of type: failure-type
|
397
442
|
data = JSON.parse(request.body.read)
|
398
|
-
if data[
|
399
|
-
halt 400,
|
443
|
+
if data['type'].nil?
|
444
|
+
halt 400, 'Neet type'
|
400
445
|
else
|
401
|
-
|
402
|
-
|
403
|
-
job.
|
404
|
-
{ :
|
405
|
-
end
|
446
|
+
jobs = client.jobs.failed(data['type'], 0, 500)['jobs']
|
447
|
+
results = jobs.map do |job|
|
448
|
+
job.requeue(job.queue_name)
|
449
|
+
{ id: job.jid, queue: job.queue_name }
|
450
|
+
end
|
451
|
+
return json(results)
|
406
452
|
end
|
407
453
|
end
|
408
454
|
|
409
|
-
post
|
455
|
+
post '/cancel/?' do
|
410
456
|
# Expects a JSON-encoded array of job ids to cancel
|
411
|
-
jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
|
457
|
+
jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
|
458
|
+
jobs.compact!
|
412
459
|
# Go ahead and cancel all the jobs!
|
413
460
|
jobs.each do |job|
|
414
|
-
job.cancel
|
461
|
+
job.cancel
|
415
462
|
end
|
416
463
|
|
417
464
|
if request.xhr?
|
418
|
-
return json({ :
|
465
|
+
return json({ canceled: jobs.map { |job| job.jid } })
|
419
466
|
else
|
420
467
|
redirect to(request.referrer)
|
421
468
|
end
|
422
469
|
end
|
423
470
|
|
424
|
-
post
|
471
|
+
post '/cancelall/?' do
|
425
472
|
# Expects a JSON-encoded hash of type: failure-type
|
426
473
|
data = JSON.parse(request.body.read)
|
427
|
-
if data[
|
428
|
-
halt 400,
|
474
|
+
if data['type'].nil?
|
475
|
+
halt 400, 'Neet type'
|
429
476
|
else
|
430
|
-
return json(client.jobs.failed(data[
|
431
|
-
job.cancel
|
432
|
-
{ :
|
477
|
+
return json(client.jobs.failed(data['type'])['jobs'].map do |job|
|
478
|
+
job.cancel
|
479
|
+
{ id: job.jid }
|
433
480
|
end)
|
434
481
|
end
|
435
482
|
end
|
436
483
|
|
437
484
|
# start the server if ruby file executed directly
|
438
|
-
run! if app_file == $
|
485
|
+
run! if app_file == $PROGRAM_NAME
|
439
486
|
end
|
440
487
|
end
|