rest-ftp-daemon 0.202.2 → 0.210.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -2
- data/lib/rest-ftp-daemon.rb +8 -1
- data/lib/rest-ftp-daemon/api/dashboard.rb +14 -4
- data/lib/rest-ftp-daemon/api/jobs.rb +6 -4
- data/lib/rest-ftp-daemon/api/root.rb +4 -9
- data/lib/rest-ftp-daemon/api/status.rb +3 -4
- data/lib/rest-ftp-daemon/constants.rb +41 -19
- data/lib/rest-ftp-daemon/exceptions.rb +1 -0
- data/lib/rest-ftp-daemon/helpers.rb +3 -5
- data/lib/rest-ftp-daemon/job.rb +110 -92
- data/lib/rest-ftp-daemon/job_queue.rb +88 -80
- data/lib/rest-ftp-daemon/logger.rb +5 -3
- data/lib/rest-ftp-daemon/logger_pool.rb +3 -2
- data/lib/rest-ftp-daemon/notification.rb +13 -18
- data/lib/rest-ftp-daemon/views/dashboard.haml +1 -1
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +18 -6
- data/lib/rest-ftp-daemon/views/dashboard_table.haml +5 -11
- data/lib/rest-ftp-daemon/views/dashboard_workers.haml +14 -12
- data/lib/rest-ftp-daemon/worker_pool.rb +73 -83
- data/rest-ftp-daemon.yml.sample +6 -4
- metadata +2 -2
@@ -3,15 +3,18 @@ require 'securerandom'
|
|
3
3
|
|
4
4
|
module RestFtpDaemon
|
5
5
|
class JobQueue < Queue
|
6
|
-
attr_reader :queued
|
7
|
-
attr_reader :popped
|
6
|
+
# attr_reader :queued
|
7
|
+
# attr_reader :popped
|
8
|
+
|
9
|
+
attr_reader :queue
|
10
|
+
attr_reader :jobs
|
8
11
|
|
9
12
|
def initialize
|
10
13
|
# Instance variables
|
11
|
-
@
|
12
|
-
@
|
14
|
+
@queue = []
|
15
|
+
@jobs = []
|
13
16
|
@waiting = []
|
14
|
-
@
|
17
|
+
@queue.taint # enable tainted communication
|
15
18
|
@waiting.taint
|
16
19
|
self.taint
|
17
20
|
@mutex = Mutex.new
|
@@ -47,9 +50,11 @@ module RestFtpDaemon
|
|
47
50
|
end
|
48
51
|
|
49
52
|
def generate_id
|
50
|
-
rand(36**8).to_s(36)
|
51
|
-
@
|
52
|
-
|
53
|
+
# rand(36**8).to_s(36)
|
54
|
+
@mutex.synchronize do
|
55
|
+
@last_id ||= 0
|
56
|
+
@last_id += 1
|
57
|
+
end
|
53
58
|
prefixed_id @last_id
|
54
59
|
end
|
55
60
|
|
@@ -76,33 +81,38 @@ module RestFtpDaemon
|
|
76
81
|
end
|
77
82
|
end
|
78
83
|
|
79
|
-
def
|
80
|
-
#
|
84
|
+
def filter_jobs status
|
85
|
+
# No status filter: return all execept queued
|
81
86
|
if status.empty?
|
82
|
-
|
87
|
+
@jobs.reject { |job| job.status == JOB_STATUS_QUEUED }
|
88
|
+
|
89
|
+
# Status filtering: only those jobs
|
83
90
|
else
|
84
|
-
|
85
|
-
end
|
91
|
+
@jobs.select { |job| job.status == status.to_sym }
|
86
92
|
|
87
|
-
# Sort these elements
|
88
|
-
elements.sort_by do |item|
|
89
|
-
w = JOB_WEIGHTS[item.status] || 0
|
90
|
-
[ w, item.wid.to_s, item.updated_at.to_s]
|
91
93
|
end
|
92
|
-
|
93
94
|
end
|
94
95
|
|
95
96
|
def counts_by_status
|
96
97
|
statuses = {}
|
97
|
-
|
98
|
+
@jobs.group_by { |job| job.status }.map { |status, jobs| statuses[status] = jobs.size }
|
98
99
|
statuses
|
99
100
|
end
|
100
101
|
|
101
|
-
def
|
102
|
-
@
|
102
|
+
def jobs # change for accessor
|
103
|
+
@jobs
|
104
|
+
end
|
105
|
+
|
106
|
+
def jobs_count
|
107
|
+
@jobs.length
|
103
108
|
end
|
104
|
-
|
105
|
-
|
109
|
+
|
110
|
+
def queued_ids
|
111
|
+
@queue.collect(&:id)
|
112
|
+
end
|
113
|
+
|
114
|
+
def jobs_ids
|
115
|
+
@jobs.collect(&:id)
|
106
116
|
end
|
107
117
|
|
108
118
|
def find_by_id id, prefixed = false
|
@@ -110,8 +120,8 @@ module RestFtpDaemon
|
|
110
120
|
id = prefixed_id(id) if prefixed
|
111
121
|
info "find_by_id (#{id}, #{prefixed}) > #{id}"
|
112
122
|
|
113
|
-
# Search in
|
114
|
-
@
|
123
|
+
# Search in jobs queues
|
124
|
+
@jobs.select { |item| item.id == id }.last
|
115
125
|
end
|
116
126
|
|
117
127
|
def push job
|
@@ -121,11 +131,17 @@ module RestFtpDaemon
|
|
121
131
|
|
122
132
|
@mutex.synchronize do
|
123
133
|
# Push job into the queue
|
124
|
-
@
|
134
|
+
@queue.push job
|
135
|
+
|
136
|
+
# Store the job into the global jobs list
|
137
|
+
@jobs.push job
|
125
138
|
|
126
|
-
#
|
139
|
+
# Inform the job that it's been queued
|
127
140
|
job.set_queued if job.respond_to? :set_queued
|
128
141
|
|
142
|
+
# Refresh queue order
|
143
|
+
sort_queue!
|
144
|
+
|
129
145
|
# Try to wake a worker up
|
130
146
|
begin
|
131
147
|
t = @waiting.shift
|
@@ -139,17 +155,19 @@ module RestFtpDaemon
|
|
139
155
|
alias enq push
|
140
156
|
|
141
157
|
def pop(non_block=false)
|
142
|
-
# info "JobQueue.pop"
|
143
158
|
@mutex.synchronize do
|
144
159
|
while true
|
145
|
-
if @
|
160
|
+
if @queue.empty?
|
146
161
|
# info "JobQueue.pop: empty"
|
147
162
|
raise ThreadError, "queue empty" if non_block
|
148
163
|
@waiting.push Thread.current
|
149
164
|
@mutex.sleep
|
150
165
|
else
|
151
|
-
#
|
152
|
-
|
166
|
+
# Refresh queue order
|
167
|
+
# sort_queue!
|
168
|
+
|
169
|
+
# Extract the heaviest item in the queue
|
170
|
+
return @queue.pop
|
153
171
|
end
|
154
172
|
end
|
155
173
|
end
|
@@ -158,99 +176,89 @@ module RestFtpDaemon
|
|
158
176
|
alias deq pop
|
159
177
|
|
160
178
|
def empty?
|
161
|
-
@
|
179
|
+
@queue.empty?
|
162
180
|
end
|
163
181
|
|
164
182
|
def clear
|
165
|
-
@
|
183
|
+
@queue.clear
|
166
184
|
end
|
167
185
|
|
168
186
|
def num_waiting
|
169
187
|
@waiting.size
|
170
188
|
end
|
171
189
|
|
172
|
-
def ordered_queue
|
173
|
-
@mutex_counters.synchronize do
|
174
|
-
@queued.sort_by { |item| [item.priority.to_i, - item.id.to_i] }
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# def ordered_popped
|
179
|
-
# @mutex_counters.synchronize do
|
180
|
-
# @popped.sort_by { |item| [ item.wid.to_s, item.updated_at] }
|
181
|
-
# # @popped.sort_by { |item| [item.status.to_s, item.wid.to_s, item.updated_at, - item.id.to_i] }
|
182
|
-
# end
|
183
|
-
# end
|
184
|
-
|
185
190
|
protected
|
186
191
|
|
187
192
|
def prefixed_id id
|
188
193
|
"#{@prefix}.#{id}"
|
189
194
|
end
|
190
195
|
|
196
|
+
def sort_queue!
|
197
|
+
@mutex_counters.synchronize do
|
198
|
+
@queue.sort_by! &:weight
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
191
202
|
def conchita_loop
|
192
203
|
info "conchita starting with: #{@conchita.inspect}"
|
193
204
|
loop do
|
194
|
-
# Do the cleanup
|
195
|
-
|
196
|
-
|
205
|
+
# Do the cleanup locking the queues
|
206
|
+
# info "conchita: cleanup expired jobs"
|
207
|
+
@mutex.synchronize do
|
208
|
+
conchita_clean JOB_STATUS_FINISHED
|
209
|
+
conchita_clean JOB_STATUS_FAILED
|
210
|
+
conchita_clean JOB_STATUS_QUEUED
|
211
|
+
end
|
197
212
|
sleep @conchita[:timer]
|
198
213
|
end
|
199
214
|
end
|
200
215
|
|
201
|
-
def conchita_clean status
|
216
|
+
def conchita_clean status # FIXME: clean both @jobs and @queue
|
202
217
|
# Init
|
203
218
|
return if status.nil?
|
204
|
-
key = "clean_#{status.to_s}"
|
205
219
|
|
206
220
|
# Read config state
|
207
|
-
|
208
|
-
|
221
|
+
maxage = @conchita["clean_#{status.to_s}"] || 0
|
222
|
+
#info "conchita_clean status[#{status.to_s}] \t maxage[#{maxage}] s"
|
223
|
+
return unless maxage > 0
|
209
224
|
|
210
225
|
# Delete jobs from the queue if their status is (status)
|
211
|
-
@
|
212
|
-
|
226
|
+
@jobs.delete_if do |job|
|
227
|
+
|
228
|
+
# Skip if wrong status
|
213
229
|
next unless job.status == status.to_sym
|
214
230
|
|
215
|
-
# Skip
|
216
|
-
|
217
|
-
next if updated_at.nil?
|
231
|
+
# Skip if updated_at invalid
|
232
|
+
next if job.updated_at.nil?
|
218
233
|
|
219
|
-
# Skip
|
220
|
-
age = Time.now - updated_at
|
221
|
-
next if age <
|
234
|
+
# Skip if not aged enough yet
|
235
|
+
age = Time.now - job.updated_at
|
236
|
+
next if age < maxage
|
222
237
|
|
223
238
|
# Ok, we have to clean it up ..
|
224
|
-
info "conchita_clean #{status.
|
239
|
+
info "conchita_clean status[#{status.to_s}] maxage[#{maxage}] job[#{job.id}] age[#{age}]"
|
240
|
+
|
241
|
+
# Remove it from the queue if present
|
242
|
+
job_in_queue = @queue.delete job
|
243
|
+
info " removed queued job [#{job.id}]" unless job_in_queue.nil?
|
244
|
+
|
245
|
+
# Accept to delete it from @jobs
|
225
246
|
true
|
226
247
|
end
|
227
248
|
|
228
249
|
end
|
229
250
|
|
230
|
-
def pick_one # called inside a mutex/sync
|
231
|
-
# Sort jobs by priority and get the biggest one
|
232
|
-
picked = ordered_queue.last
|
233
|
-
return nil if picked.nil?
|
234
|
-
|
235
|
-
# Move it away from the queue to the @popped array
|
236
|
-
@queued.delete_if { |item| item == picked }
|
237
|
-
@popped.push picked
|
238
|
-
|
239
|
-
# Return picked
|
240
|
-
#info "JobQueue.pick_one: #{picked.id}"
|
241
|
-
picked
|
242
|
-
end
|
243
251
|
|
244
252
|
private
|
245
253
|
|
246
|
-
def info message,
|
254
|
+
def info message, lines = []
|
247
255
|
return if @logger.nil?
|
248
256
|
|
249
|
-
# Inject context
|
250
|
-
context[:origin] = self.class
|
251
|
-
|
252
257
|
# Forward to logger
|
253
|
-
@logger.info_with_id message,
|
258
|
+
@logger.info_with_id message,
|
259
|
+
id: @id,
|
260
|
+
lines: lines,
|
261
|
+
origin: self.class.to_s
|
254
262
|
end
|
255
263
|
|
256
264
|
end
|
@@ -9,8 +9,10 @@ class Logger
|
|
9
9
|
context[:level] ||= 0
|
10
10
|
|
11
11
|
# Common message header
|
12
|
-
|
13
|
-
|
12
|
+
field_wid = "%#{-LOG_COL_WID.to_i}s" % context[:wid].to_s
|
13
|
+
field_jid = "%#{-LOG_COL_JID.to_i}s" % context[:jid].to_s
|
14
|
+
field_id = "%#{-LOG_COL_ID.to_i}s" % context[:id].to_s
|
15
|
+
prefix = "#{field_wid} \t#{field_jid} \t#{field_id}\t#{' '*(context[:level].to_i+1)}"
|
14
16
|
|
15
17
|
# Send main message
|
16
18
|
add Logger::INFO, prefix + message.to_s
|
@@ -19,7 +21,7 @@ class Logger
|
|
19
21
|
context[:lines].each do |line|
|
20
22
|
line.strip!
|
21
23
|
next if line.empty?
|
22
|
-
add Logger::INFO, prefix + ' | ' + line[0..
|
24
|
+
add Logger::INFO, prefix + ' | ' + line[0..LOG_TRIM_LINE]
|
23
25
|
end if context[:lines].is_a? Enumerable
|
24
26
|
|
25
27
|
end
|
@@ -21,10 +21,11 @@ module RestFtpDaemon
|
|
21
21
|
# Create the logger and return it
|
22
22
|
logger = Logger.new(logfile, 'daily') #, 10, 1024000)
|
23
23
|
logger.progname = pipe.to_s.upcase
|
24
|
+
|
25
|
+
# And the formatter
|
24
26
|
logger.formatter = proc do |severity, datetime, progname, message|
|
25
|
-
# stamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
26
27
|
stamp = datetime.strftime("%Y-%m-%d %H:%M:%S")
|
27
|
-
field_pipe = "%-#{
|
28
|
+
field_pipe = "%-#{LOG_PIPE_LEN.to_i}s" % progname
|
28
29
|
"#{stamp}\t#{field_pipe}\t#{message}\n"
|
29
30
|
end
|
30
31
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
|
3
1
|
module RestFtpDaemon
|
4
2
|
class Notification
|
5
3
|
attr_accessor :job_id
|
@@ -13,28 +11,21 @@ module RestFtpDaemon
|
|
13
11
|
def initialize url, params
|
14
12
|
# Generate a random key
|
15
13
|
@id = Helpers.identifier(NOTIFY_IDENTIFIER_LEN)
|
14
|
+
@jid = nil
|
16
15
|
|
17
16
|
# Logger
|
18
17
|
@logger = RestFtpDaemon::LoggerPool.instance.get :notify
|
19
18
|
|
20
19
|
# Check context
|
21
|
-
|
22
20
|
if url.nil?
|
23
21
|
info "skipping (missing url): #{params.inspect}"
|
24
22
|
return
|
25
|
-
|
26
23
|
elsif params[:event].nil?
|
27
24
|
info "skipping (missing event): #{params.inspect}"
|
28
25
|
return
|
29
|
-
|
30
|
-
else
|
31
|
-
#info "created: OK"
|
32
|
-
# info "created: #{params.class}"
|
33
|
-
info "created #{params.inspect}"
|
34
|
-
|
35
26
|
end
|
36
27
|
|
37
|
-
#
|
28
|
+
# Build body and extract job ID if provided
|
38
29
|
body = {
|
39
30
|
id: params[:id].to_s,
|
40
31
|
signal: "#{NOTIFY_PREFIX}.#{params[:event].to_s}",
|
@@ -42,6 +33,9 @@ module RestFtpDaemon
|
|
42
33
|
host: Settings.host.to_s,
|
43
34
|
}
|
44
35
|
body[:status] = params[:status] if params[:status].is_a? Enumerable
|
36
|
+
@jid = params[:id]
|
37
|
+
info "initialized"
|
38
|
+
|
45
39
|
|
46
40
|
# Send message in a thread
|
47
41
|
Thread.new do |thread|
|
@@ -55,6 +49,7 @@ module RestFtpDaemon
|
|
55
49
|
data = body.to_json
|
56
50
|
info "sending #{data}"
|
57
51
|
|
52
|
+
|
58
53
|
# Prepare HTTP client
|
59
54
|
http = Net::HTTP.new uri.host, uri.port
|
60
55
|
# http.initialize_http_header({'User-Agent' => APP_NAME})
|
@@ -68,7 +63,7 @@ module RestFtpDaemon
|
|
68
63
|
if response_lines.size > 1
|
69
64
|
human_size = Helpers.format_bytes(response.body.bytesize, "B")
|
70
65
|
#human_size = 0
|
71
|
-
info "received [#{response.code}] #{human_size} (#{response_lines.size} lines)",
|
66
|
+
info "received [#{response.code}] #{human_size} (#{response_lines.size} lines)", response_lines
|
72
67
|
else
|
73
68
|
info "received [#{response.code}] #{response.body.strip}"
|
74
69
|
end
|
@@ -79,14 +74,14 @@ module RestFtpDaemon
|
|
79
74
|
|
80
75
|
protected
|
81
76
|
|
82
|
-
def info message,
|
77
|
+
def info message, lines = []
|
83
78
|
return if @logger.nil?
|
84
79
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
80
|
+
@logger.info_with_id message,
|
81
|
+
id: @id,
|
82
|
+
jid: @jid,
|
83
|
+
lines: lines,
|
84
|
+
origin: self.class.to_s
|
90
85
|
end
|
91
86
|
|
92
87
|
end
|
@@ -1,9 +1,10 @@
|
|
1
|
-
- count_all = $queue.
|
1
|
+
- count_all = $queue.jobs_count
|
2
2
|
- counts_by_status = $queue.counts_by_status
|
3
|
+
-# jobs_without_queue = jobs
|
3
4
|
|
4
5
|
|
5
6
|
%h2
|
6
|
-
Jobs
|
7
|
+
Jobs table
|
7
8
|
|
8
9
|
.btn-group.btn-group-md
|
9
10
|
- klass = only.empty? ? "btn-info" : ""
|
@@ -16,6 +17,7 @@
|
|
16
17
|
#{status} (#{count})
|
17
18
|
|
18
19
|
%table.table.table-striped.table-hover.table-condensed#jobs
|
20
|
+
|
19
21
|
%thead
|
20
22
|
%tr
|
21
23
|
%th ID
|
@@ -23,13 +25,23 @@
|
|
23
25
|
%th source
|
24
26
|
%th <=>
|
25
27
|
%th target
|
26
|
-
%th
|
28
|
+
%th queued
|
27
29
|
%th{width: 220} status
|
28
30
|
%th{width: 150} error
|
29
31
|
%th.text-right size
|
30
32
|
%th.text-right bitrate
|
31
33
|
%th info
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
|
36
|
+
- unless queue.empty?
|
37
|
+
%tbody.jobs
|
38
|
+
= render :dashboard_table, {jobs: queue.reverse}
|
39
|
+
|
40
|
+
%thead
|
41
|
+
%tr
|
42
|
+
%td{colspan: 13}
|
43
|
+
%br
|
44
|
+
|
45
|
+
- unless current.empty?
|
46
|
+
%tbody.jobs
|
47
|
+
= render :dashboard_table, {jobs: current.reverse}
|