qless 0.9.3 → 0.10.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.
- 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
|