rest-ftp-daemon 0.214.0 → 0.220.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 +1 -0
- data/Gemfile.lock +1 -1
- data/bin/rest-ftp-daemon +1 -1
- data/config.ru +1 -1
- data/lib/rest-ftp-daemon.rb +9 -5
- data/lib/rest-ftp-daemon/api/dashboard.rb +1 -1
- data/lib/rest-ftp-daemon/api/debug.rb +6 -19
- data/lib/rest-ftp-daemon/api/jobs.rb +11 -11
- data/lib/rest-ftp-daemon/api/root.rb +8 -5
- data/lib/rest-ftp-daemon/api/status.rb +1 -1
- data/lib/rest-ftp-daemon/constants.rb +7 -3
- data/lib/rest-ftp-daemon/exceptions.rb +1 -0
- data/lib/rest-ftp-daemon/job.rb +40 -39
- data/lib/rest-ftp-daemon/job_queue.rb +10 -17
- data/lib/rest-ftp-daemon/logger.rb +58 -17
- data/lib/rest-ftp-daemon/logger_helper.rb +26 -0
- data/lib/rest-ftp-daemon/logger_pool.rb +13 -13
- data/lib/rest-ftp-daemon/notification.rb +14 -14
- data/lib/rest-ftp-daemon/settings.rb +3 -3
- data/lib/rest-ftp-daemon/worker.rb +58 -0
- data/lib/rest-ftp-daemon/worker_conchita.rb +49 -0
- data/lib/rest-ftp-daemon/worker_job.rb +64 -0
- data/lib/rest-ftp-daemon/worker_pool.rb +48 -104
- data/rest-ftp-daemon.yml.sample +1 -4
- metadata +5 -3
- data/lib/rest-ftp-daemon/api/routes.rb +0 -16
- data/lib/rest-ftp-daemon/conchita.rb +0 -65
@@ -1,9 +1,12 @@
|
|
1
1
|
module RestFtpDaemon
|
2
|
-
class JobQueue
|
2
|
+
class JobQueue
|
3
|
+
include LoggerHelper
|
4
|
+
attr_reader :logger
|
3
5
|
|
4
6
|
attr_reader :queue
|
5
7
|
attr_reader :jobs
|
6
8
|
|
9
|
+
|
7
10
|
if Settings.newrelic_enabled?
|
8
11
|
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
9
12
|
end
|
@@ -24,7 +27,7 @@ module RestFtpDaemon
|
|
24
27
|
# Identifiers generator
|
25
28
|
@last_id = 0
|
26
29
|
@prefix = Helpers.identifier JOB_IDENT_LEN
|
27
|
-
|
30
|
+
log_info "JobQueue initialized (prefix: #{@prefix})"
|
28
31
|
|
29
32
|
# Mutex for counters
|
30
33
|
@counters = {}
|
@@ -32,9 +35,7 @@ module RestFtpDaemon
|
|
32
35
|
end
|
33
36
|
|
34
37
|
def generate_id
|
35
|
-
# rand(36**8).to_s(36)
|
36
38
|
@mutex.synchronize do
|
37
|
-
@last_id ||= 0
|
38
39
|
@last_id += 1
|
39
40
|
end
|
40
41
|
prefixed_id @last_id
|
@@ -100,7 +101,7 @@ module RestFtpDaemon
|
|
100
101
|
def find_by_id id, prefixed = false
|
101
102
|
# Build a prefixed id if expected
|
102
103
|
id = prefixed_id(id) if prefixed
|
103
|
-
|
104
|
+
log_info "find_by_id (#{id}, #{prefixed}) > #{id}"
|
104
105
|
|
105
106
|
# Search in jobs queues
|
106
107
|
@jobs.select { |item| item.id == id }.last
|
@@ -184,8 +185,8 @@ module RestFtpDaemon
|
|
184
185
|
next if job.updated_at > before
|
185
186
|
|
186
187
|
# Ok, we have to clean it up ..
|
187
|
-
|
188
|
-
|
188
|
+
log_info "expire [#{status.to_s}] [#{maxage}] > [#{job.id}] [#{job.updated_at}]"
|
189
|
+
log_info " + unqueued" if @queue.delete(job)
|
189
190
|
|
190
191
|
true
|
191
192
|
end
|
@@ -206,21 +207,13 @@ module RestFtpDaemon
|
|
206
207
|
end
|
207
208
|
end
|
208
209
|
|
209
|
-
def info message, lines = []
|
210
|
-
return if @logger.nil?
|
211
|
-
|
212
|
-
# Forward to logger
|
213
|
-
@logger.info_with_id message,
|
214
|
-
id: @id,
|
215
|
-
lines: lines,
|
216
|
-
origin: self.class.to_s
|
217
|
-
end
|
218
|
-
|
219
210
|
if Settings.newrelic_enabled?
|
220
211
|
add_transaction_tracer :push, :category => :task
|
221
212
|
add_transaction_tracer :pop, :category => :task
|
222
213
|
add_transaction_tracer :sort_queue!, :category => :task
|
223
214
|
add_transaction_tracer :expire, :category => :task
|
215
|
+
add_transaction_tracer :counts_by_status, :category => :task
|
216
|
+
add_transaction_tracer :filter_jobs, :category => :task
|
224
217
|
end
|
225
218
|
|
226
219
|
end
|
@@ -1,29 +1,70 @@
|
|
1
1
|
class Logger
|
2
2
|
|
3
3
|
def info_with_id message, context = {}
|
4
|
-
# Ensure context is a hash of options
|
4
|
+
# Ensure context is a hash of options and init
|
5
5
|
context = {} unless context.is_a? Hash
|
6
|
+
context[:level] ||= Logger::DEBUG
|
6
7
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
8
|
+
# Build prefixes depending on this context
|
9
|
+
prefix1 = build_prefix(context)
|
10
|
+
prefix2 = build_prefix() + ' | '
|
10
11
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
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)}"
|
12
|
+
# # Build output lines
|
13
|
+
# output = []
|
14
|
+
# output << prefix1 + message.strip
|
16
15
|
|
17
|
-
#
|
18
|
-
|
16
|
+
# # Add optional lines
|
17
|
+
# context[:lines].each do |line|
|
18
|
+
# # line.strip!
|
19
|
+
# # next if line.empty?
|
20
|
+
# output << prefix2 + line[0..LOG_TRIM_LINE]
|
21
|
+
# end if context[:lines].is_a? Enumerable
|
19
22
|
|
20
|
-
#
|
21
|
-
context[:lines]
|
22
|
-
line.strip!
|
23
|
-
next if line.empty?
|
24
|
-
add Logger::INFO, prefix + ' | ' + line[0..LOG_TRIM_LINE]
|
25
|
-
end if context[:lines].is_a? Enumerable
|
23
|
+
# Use "context[:lines]" according to its type
|
24
|
+
lines = context[:lines]
|
26
25
|
|
26
|
+
if lines.is_a? Hash
|
27
|
+
output = build_from_hash prefix2, lines
|
28
|
+
|
29
|
+
elsif lines.is_a? Array
|
30
|
+
output = build_from_array prefix2, lines
|
31
|
+
|
32
|
+
else
|
33
|
+
output = []
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Prepend plain message to output
|
39
|
+
output.unshift (prefix1 + message.strip)
|
40
|
+
|
41
|
+
# Send all this to logger
|
42
|
+
add context[:level], output
|
27
43
|
end
|
28
44
|
|
45
|
+
def build_prefix context = {}
|
46
|
+
LOG_FORMAT_MESSAGE % [
|
47
|
+
context[:wid].to_s,
|
48
|
+
context[:jid].to_s,
|
49
|
+
context[:id].to_s,
|
50
|
+
context[:level].to_i+1,
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def build_from_array prefix, lines
|
57
|
+
lines.map do |value|
|
58
|
+
text = value.to_s.strip[0..LOG_TRIM_LINE]
|
59
|
+
"#{prefix}#{text}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_from_hash prefix, lines
|
64
|
+
lines.map do |name, value|
|
65
|
+
text = value.to_s.strip[0..LOG_TRIM_LINE]
|
66
|
+
"#{prefix}#{name}: #{text}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
29
70
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
module LoggerHelper
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def log_info message, lines = []
|
7
|
+
logger.info_with_id message, log_context.merge({
|
8
|
+
from: self.class.to_s,
|
9
|
+
lines: lines,
|
10
|
+
level: Logger::INFO
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
def log_error message, lines = []
|
15
|
+
logger.info_with_id message, log_context.merge({
|
16
|
+
from: self.class.to_s,
|
17
|
+
lines: lines,
|
18
|
+
level: Logger::ERROR
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
def log_context
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
require 'logger'
|
3
|
-
|
4
1
|
module RestFtpDaemon
|
5
2
|
class LoggerPool
|
6
3
|
include Singleton
|
@@ -19,26 +16,29 @@ module RestFtpDaemon
|
|
19
16
|
logfile ||= STDERR
|
20
17
|
|
21
18
|
# Create the logger and return it
|
22
|
-
logger = Logger.new(logfile,
|
19
|
+
logger = Logger.new(logfile, LOG_ROTATION) #, 10, 1024000)
|
23
20
|
logger.progname = pipe.to_s.upcase
|
24
21
|
|
25
22
|
# And the formatter
|
26
|
-
logger.formatter = proc do |severity, datetime, progname,
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
datetime.strftime("%Y-%m-%d %H:%M:%S"),
|
23
|
+
logger.formatter = proc do |severity, datetime, progname, messages|
|
24
|
+
# Build common line prefix
|
25
|
+
prefix = LOG_FORMAT_PREFIX % [
|
26
|
+
datetime.strftime(LOG_FORMAT_TIME),
|
27
|
+
severity,
|
32
28
|
progname,
|
33
|
-
message,
|
34
29
|
]
|
30
|
+
|
31
|
+
# If we have a bunch of lines, prefix them and send them together
|
32
|
+
if messages.is_a? Array
|
33
|
+
messages.map { |line| prefix + line + LOG_NEWLINE}.join
|
34
|
+
else
|
35
|
+
prefix + messages.to_s + LOG_NEWLINE
|
36
|
+
end
|
35
37
|
end
|
36
38
|
|
37
39
|
# Finally return this logger
|
38
40
|
logger
|
39
41
|
end
|
40
42
|
|
41
|
-
private
|
42
|
-
|
43
43
|
end
|
44
44
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module RestFtpDaemon
|
2
2
|
class Notification
|
3
|
+
include LoggerHelper
|
4
|
+
attr_reader :logger
|
5
|
+
|
3
6
|
attr_accessor :job_id
|
4
7
|
attr_accessor :signal
|
5
8
|
attr_accessor :error
|
@@ -18,10 +21,10 @@ module RestFtpDaemon
|
|
18
21
|
|
19
22
|
# Check context
|
20
23
|
if url.nil?
|
21
|
-
|
24
|
+
log_info "skipping (missing url): #{params.inspect}"
|
22
25
|
return
|
23
26
|
elsif params[:event].nil?
|
24
|
-
|
27
|
+
log_info "skipping (missing event): #{params.inspect}"
|
25
28
|
return
|
26
29
|
end
|
27
30
|
|
@@ -34,7 +37,7 @@ module RestFtpDaemon
|
|
34
37
|
}
|
35
38
|
body[:status] = params[:status] if params[:status].is_a? Enumerable
|
36
39
|
@jid = params[:id]
|
37
|
-
|
40
|
+
log_info "initialized"
|
38
41
|
|
39
42
|
|
40
43
|
# Send message in a thread
|
@@ -47,7 +50,7 @@ module RestFtpDaemon
|
|
47
50
|
'User-Agent' => "#{APP_NAME} - #{APP_VER}"
|
48
51
|
}
|
49
52
|
data = body.to_json
|
50
|
-
|
53
|
+
log_info "sending #{data}"
|
51
54
|
|
52
55
|
|
53
56
|
# Prepare HTTP client
|
@@ -63,9 +66,9 @@ module RestFtpDaemon
|
|
63
66
|
if response_lines.size > 1
|
64
67
|
human_size = Helpers.format_bytes(response.body.bytesize, "B")
|
65
68
|
#human_size = 0
|
66
|
-
|
69
|
+
log_info "received [#{response.code}] #{human_size} (#{response_lines.size} lines)", response_lines
|
67
70
|
else
|
68
|
-
|
71
|
+
log_info "received [#{response.code}] #{response.body.strip}"
|
69
72
|
end
|
70
73
|
|
71
74
|
end
|
@@ -74,14 +77,11 @@ module RestFtpDaemon
|
|
74
77
|
|
75
78
|
protected
|
76
79
|
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
@
|
81
|
-
|
82
|
-
jid: @jid,
|
83
|
-
lines: lines,
|
84
|
-
origin: self.class.to_s
|
80
|
+
def log_context
|
81
|
+
{
|
82
|
+
id: @id,
|
83
|
+
jid: @jid
|
84
|
+
}
|
85
85
|
end
|
86
86
|
|
87
87
|
end
|
@@ -25,7 +25,7 @@ class Settings < Settingslogic
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def newrelic_enabled?
|
28
|
-
Settings.
|
28
|
+
Settings.at(:debug, :newrelic)
|
29
29
|
end
|
30
30
|
|
31
31
|
def init_newrelic
|
@@ -38,10 +38,10 @@ class Settings < Settingslogic
|
|
38
38
|
#Settings['newrelic']['enabled'] = true
|
39
39
|
|
40
40
|
# License
|
41
|
-
ENV['NEW_RELIC_LICENSE_KEY'] = Settings.at(:
|
41
|
+
ENV['NEW_RELIC_LICENSE_KEY'] = Settings.at(:debug, :newrelic)
|
42
42
|
|
43
43
|
# Appname
|
44
|
-
ENV['NEW_RELIC_APP_NAME'] =
|
44
|
+
ENV['NEW_RELIC_APP_NAME'] = "#{APP_NICK}-#{Settings.host}-#{APP_ENV}"
|
45
45
|
|
46
46
|
# Logfile
|
47
47
|
ENV['NEW_RELIC_LOG'] = Settings.at(:logs, :newrelic)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
class Worker
|
3
|
+
include LoggerHelper
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
if Settings.newrelic_enabled?
|
7
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize wid
|
11
|
+
# Logger
|
12
|
+
@logger = RestFtpDaemon::LoggerPool.instance.get :workers
|
13
|
+
|
14
|
+
# Worker name
|
15
|
+
@wid = wid
|
16
|
+
|
17
|
+
# Set thread context
|
18
|
+
Thread.current.thread_variable_set :wid, wid
|
19
|
+
Thread.current.thread_variable_set :started_at, Time.now
|
20
|
+
worker_status :starting
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def log_context
|
26
|
+
{
|
27
|
+
wid: @wid,
|
28
|
+
tag_1_worker_object: true
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
loop do
|
34
|
+
begin
|
35
|
+
work
|
36
|
+
rescue Exception => e
|
37
|
+
log_error "WORKER EXCEPTION: #{e.inspect}"
|
38
|
+
sleep 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def worker_status status
|
44
|
+
Thread.current.thread_variable_set :status, status
|
45
|
+
Thread.current.thread_variable_set :updted_at, Time.now
|
46
|
+
end
|
47
|
+
|
48
|
+
def worker_jid jid
|
49
|
+
Thread.current.thread_variable_set :jid, jid
|
50
|
+
Thread.current.thread_variable_set :updted_at, Time.now
|
51
|
+
end
|
52
|
+
|
53
|
+
if Settings.newrelic_enabled?
|
54
|
+
add_transaction_tracer :work, :category => :task
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
class ConchitaWorker < Worker
|
3
|
+
|
4
|
+
def initialize wid = :conchita
|
5
|
+
# Generic worker initialize
|
6
|
+
super
|
7
|
+
|
8
|
+
# Conchita configuration
|
9
|
+
@conchita = Settings.conchita
|
10
|
+
if !@conchita.is_a? Hash
|
11
|
+
return log_info "ConchitaWorker: missing conchita.* configuration"
|
12
|
+
elsif @conchita[:timer].nil?
|
13
|
+
return log_info "ConchitaWorker: missing conchita.timer value"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Start main loop
|
17
|
+
log_info "ConchitaWorker starting", @conchita
|
18
|
+
start
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def work
|
24
|
+
worker_status :cleaning
|
25
|
+
|
26
|
+
# Cleanup queues according to configured max-age
|
27
|
+
$queue.expire JOB_STATUS_FINISHED, maxage(JOB_STATUS_FINISHED)
|
28
|
+
$queue.expire JOB_STATUS_FAILED, maxage(JOB_STATUS_FAILED)
|
29
|
+
$queue.expire JOB_STATUS_QUEUED, maxage(JOB_STATUS_QUEUED)
|
30
|
+
|
31
|
+
# Force garbage collector
|
32
|
+
worker_status :collecting
|
33
|
+
GC.start if @conchita["garbage_collector"]
|
34
|
+
|
35
|
+
rescue Exception => e
|
36
|
+
log_error "EXCEPTION: #{e.inspect}"
|
37
|
+
sleep 1
|
38
|
+
else
|
39
|
+
# Sleep for a few seconds
|
40
|
+
worker_status :sleeping
|
41
|
+
sleep @conchita[:timer]
|
42
|
+
end
|
43
|
+
|
44
|
+
def maxage status
|
45
|
+
@conchita["clean_#{status.to_s}"] || 0
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
class JobWorker < Worker
|
3
|
+
|
4
|
+
def initialize wid
|
5
|
+
# Generic worker initialize
|
6
|
+
super
|
7
|
+
|
8
|
+
# Timeout config
|
9
|
+
@timeout = (Settings.transfer.timeout rescue nil) || DEFAULT_WORKER_TIMEOUT
|
10
|
+
|
11
|
+
# Start main loop
|
12
|
+
log_info "JobWorker starting", ["timeout: #{@timeout}"]
|
13
|
+
start
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def work
|
19
|
+
# Wait for a job to come into the queue
|
20
|
+
worker_status :waiting
|
21
|
+
log_info "waiting for a job"
|
22
|
+
job = $queue.pop
|
23
|
+
|
24
|
+
# Prepare the job for processing
|
25
|
+
worker_status :working
|
26
|
+
worker_jid job.id
|
27
|
+
log_info "working with job [#{job.id}]"
|
28
|
+
job.wid = Thread.current.thread_variable_get :wid
|
29
|
+
|
30
|
+
# Processs this job protected by a timeout
|
31
|
+
Timeout::timeout(@timeout, RestFtpDaemon::JobTimeout) do
|
32
|
+
job.process
|
33
|
+
end
|
34
|
+
|
35
|
+
# Processing done
|
36
|
+
worker_status :finished
|
37
|
+
log_info "finished with job [#{job.id}]"
|
38
|
+
worker_jid nil
|
39
|
+
job.wid = nil
|
40
|
+
|
41
|
+
# Increment total processed jobs count
|
42
|
+
$queue.counter_inc :jobs_processed
|
43
|
+
|
44
|
+
rescue RestFtpDaemon::JobTimeout => ex
|
45
|
+
log_error "JOB TIMED OUT", lines: ex.backtrace
|
46
|
+
worker_status :timeout
|
47
|
+
job.oops_you_stop_now ex unless job.nil?
|
48
|
+
sleep 1
|
49
|
+
|
50
|
+
rescue Exception => ex
|
51
|
+
log_error "JOB UNHDNALED EXCEPTION: #{ex.message}", lines: ex.backtrace
|
52
|
+
worker_status :crashed
|
53
|
+
job.oops_after_crash ex unless job.nil?
|
54
|
+
sleep 1
|
55
|
+
|
56
|
+
else
|
57
|
+
# Clean job status
|
58
|
+
worker_status :free
|
59
|
+
job.wid = nil
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|