rest-ftp-daemon 0.300.3 → 0.302.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/Gemfile.lock +12 -12
- data/README.md +12 -3
- data/bin/rest-ftp-daemon +14 -15
- data/config.ru +2 -8
- data/defaults.yml +83 -59
- data/lib/rest-ftp-daemon.rb +4 -8
- data/lib/rest-ftp-daemon/api/config.rb +3 -1
- data/lib/rest-ftp-daemon/api/dashboard.rb +1 -0
- data/lib/rest-ftp-daemon/api/debug.rb +2 -0
- data/lib/rest-ftp-daemon/api/job_presenter.rb +2 -0
- data/lib/rest-ftp-daemon/api/jobs.rb +2 -0
- data/lib/rest-ftp-daemon/api/root.rb +2 -0
- data/lib/rest-ftp-daemon/api/status.rb +1 -0
- data/lib/rest-ftp-daemon/constants.rb +5 -5
- data/lib/rest-ftp-daemon/job.rb +37 -37
- data/lib/rest-ftp-daemon/logger_pool.rb +39 -16
- data/lib/rest-ftp-daemon/notification.rb +26 -19
- data/lib/rest-ftp-daemon/remote.rb +16 -15
- data/lib/rest-ftp-daemon/remote_ftp.rb +2 -7
- data/lib/rest-ftp-daemon/remote_sftp.rb +1 -4
- data/lib/rest-ftp-daemon/worker_pool.rb +5 -15
- data/lib/rest-ftp-daemon/{worker_conchita.rb → workers/conchita.rb} +20 -22
- data/lib/rest-ftp-daemon/{worker_reporter.rb → workers/reporter.rb} +23 -30
- data/lib/rest-ftp-daemon/{worker_job.rb → workers/transfer.rb} +25 -35
- data/lib/shared/conf.rb +47 -41
- data/lib/{rest-ftp-daemon/worker.rb → shared/worker_base.rb} +42 -28
- data/rest-ftp-daemon.gemspec +5 -3
- data/spec/rest-ftp-daemon/features/dashboard_spec.rb +5 -5
- data/spec/spec_helper.rb +2 -2
- metadata +36 -24
- data/rest-ftp-daemon.sample.yml +0 -71
- data/spec/support/config.yml +0 -25
@@ -6,7 +6,7 @@ DEFAULT_POOL = "default"
|
|
6
6
|
DEFAULT_SFTP_TIMEOUT = 600 # 10mn
|
7
7
|
DEFAULT_FTP_CHUNK = 1024 # 1 MB
|
8
8
|
DEFAULT_PAGE_SIZE = 50 # 50 lines
|
9
|
-
|
9
|
+
DEFAULT_RETRY_AFTER = 10 # 10s
|
10
10
|
|
11
11
|
|
12
12
|
# Internal job constants
|
@@ -21,7 +21,7 @@ LOG_ROTATION = "daily"
|
|
21
21
|
LOG_FORMAT_PROGNAME = "%d\t%s"
|
22
22
|
|
23
23
|
LOG_HEADER_TIME = "%Y-%m-%d %H:%M:%S"
|
24
|
-
LOG_HEADER_FORMAT = "%s \t%d\t%-8s %-
|
24
|
+
LOG_HEADER_FORMAT = "%s \t%d\t%-8s %-10s "
|
25
25
|
LOG_MESSAGE_TRIM = 200
|
26
26
|
LOG_MESSAGE_TEXT = "%s%s"
|
27
27
|
LOG_MESSAGE_ARRAY = "%s - %s"
|
@@ -29,9 +29,9 @@ LOG_MESSAGE_HASH = "%s * %-20s %s"
|
|
29
29
|
|
30
30
|
# Constants: logger app-specific prefix
|
31
31
|
LOG_PREFIX_WID = 8
|
32
|
-
LOG_PREFIX_JID = JOB_IDENT_LEN +
|
33
|
-
LOG_PREFIX_ID =
|
34
|
-
LOG_PREFIX_FORMAT = "%#{-LOG_PREFIX_WID.to_i}s %#{-LOG_PREFIX_JID.to_i}s %#{-LOG_PREFIX_ID.to_i}s"
|
32
|
+
LOG_PREFIX_JID = JOB_IDENT_LEN + 4
|
33
|
+
LOG_PREFIX_ID = 5
|
34
|
+
LOG_PREFIX_FORMAT = "%#{-LOG_PREFIX_WID.to_i}s %#{-LOG_PREFIX_JID.to_i}s %#{-LOG_PREFIX_ID.to_i}s "
|
35
35
|
|
36
36
|
|
37
37
|
# Constants: logger to be cleaned up
|
data/lib/rest-ftp-daemon/job.rb
CHANGED
@@ -24,6 +24,8 @@ module RestFtpDaemon
|
|
24
24
|
attr_reader :infos
|
25
25
|
attr_reader :pool
|
26
26
|
|
27
|
+
attr_accessor :config
|
28
|
+
|
27
29
|
FIELDS.each do |name|
|
28
30
|
attr_reader name
|
29
31
|
end
|
@@ -43,8 +45,13 @@ module RestFtpDaemon
|
|
43
45
|
@runs = 0
|
44
46
|
@wid = nil
|
45
47
|
|
48
|
+
# Prepare configuration
|
49
|
+
@config = Conf[:transfer] || {}
|
50
|
+
@endpoints = Conf[:endpoints] || {}
|
51
|
+
@pools = Conf[:pools] || {}
|
52
|
+
|
46
53
|
# Logger
|
47
|
-
@logger = RestFtpDaemon::LoggerPool.instance.get :
|
54
|
+
@logger = RestFtpDaemon::LoggerPool.instance.get :transfer
|
48
55
|
|
49
56
|
# Protect with a mutex
|
50
57
|
@mutex = Mutex.new
|
@@ -54,34 +61,24 @@ module RestFtpDaemon
|
|
54
61
|
instance_variable_set "@#{name}", params[name]
|
55
62
|
end
|
56
63
|
|
57
|
-
# Set pool
|
58
|
-
pools = (Conf[:pools] || {})
|
59
64
|
# Check if pool name exists
|
60
|
-
if (pools.keys.include? params[:pool])
|
65
|
+
if (@pools.keys.include? params[:pool])
|
61
66
|
@pool = params[:pool].to_s
|
62
67
|
else
|
63
68
|
@pool = DEFAULT_POOL
|
64
69
|
end
|
65
70
|
|
66
71
|
# Set job queue, thus reset
|
67
|
-
reset
|
68
|
-
|
69
|
-
# Read source file size and parameters
|
70
|
-
@notify_after_sec = Conf.at(:transfer, :notify_after_sec) rescue nil
|
72
|
+
# reset
|
71
73
|
end
|
72
74
|
|
73
75
|
def reset
|
74
|
-
# Set super-default flags
|
75
|
-
flag_default :mkdir, false
|
76
|
-
flag_default :overwrite, false
|
77
|
-
flag_default :tempfile, false
|
78
|
-
|
79
76
|
# Flag current job
|
80
77
|
@queued_at = Time.now
|
81
78
|
@updated_at = Time.now
|
82
79
|
|
83
80
|
# Send first notification
|
84
|
-
log_info "Job.initialize notify[queued]
|
81
|
+
log_info "Job.initialize notify[queued]}]"
|
85
82
|
client_notify :queued
|
86
83
|
|
87
84
|
# Update job status
|
@@ -91,8 +88,7 @@ module RestFtpDaemon
|
|
91
88
|
end
|
92
89
|
|
93
90
|
def process
|
94
|
-
|
95
|
-
log_info "Job.process"
|
91
|
+
log_info "Job.process update_interval[#{JOB_UPDATE_INTERVAL}]"
|
96
92
|
|
97
93
|
# Prepare job
|
98
94
|
begin
|
@@ -259,10 +255,6 @@ module RestFtpDaemon
|
|
259
255
|
|
260
256
|
protected
|
261
257
|
|
262
|
-
def expand_path path
|
263
|
-
File.expand_path replace_tokens(path)
|
264
|
-
end
|
265
|
-
|
266
258
|
def expand_url path
|
267
259
|
URI.parse replace_tokens(path)
|
268
260
|
end
|
@@ -273,8 +265,8 @@ module RestFtpDaemon
|
|
273
265
|
|
274
266
|
def replace_tokens path
|
275
267
|
# Ensure endpoints are not a nil value
|
276
|
-
return path unless
|
277
|
-
vectors =
|
268
|
+
return path unless @endpoints.is_a? Enumerable
|
269
|
+
vectors = @endpoints.clone
|
278
270
|
|
279
271
|
# Stack RANDOM into tokens
|
280
272
|
vectors["RANDOM"] = SecureRandom.hex(JOB_RANDOM_LEN)
|
@@ -294,16 +286,21 @@ module RestFtpDaemon
|
|
294
286
|
end
|
295
287
|
|
296
288
|
def prepare
|
289
|
+
# Init
|
290
|
+
@source_path = nil
|
291
|
+
|
292
|
+
# Prepare flags
|
293
|
+
flag_prepare :mkdir, false
|
294
|
+
flag_prepare :overwrite, false
|
295
|
+
flag_prepare :tempfile, true
|
296
|
+
|
297
297
|
# Update job status
|
298
298
|
set_status JOB_STATUS_PREPARING
|
299
299
|
@runs += 1
|
300
300
|
|
301
|
-
# Init
|
302
|
-
@source_path = nil
|
303
|
-
|
304
301
|
# Prepare source
|
305
302
|
raise RestFtpDaemon::JobMissingAttribute unless @source
|
306
|
-
@source_path = expand_path @source
|
303
|
+
@source_path = File.expand_path replace_tokens(@source)
|
307
304
|
set_info :source, :path, @source_path
|
308
305
|
set_info :source, :method, JOB_METHOD_FILE
|
309
306
|
|
@@ -324,21 +321,21 @@ module RestFtpDaemon
|
|
324
321
|
# set_info :target, :method, :ftp
|
325
322
|
set_info :target, :method, JOB_METHOD_FTP
|
326
323
|
#@target_method = :ftp
|
327
|
-
@remote = RemoteFTP.new target_uri, log_prefix
|
324
|
+
@remote = RemoteFTP.new target_uri, log_prefix, debug: @config[:debug_ftp]
|
328
325
|
|
329
326
|
elsif (target_uri.is_a? URI::FTPES) || (target_uri.is_a? URI::FTPS)
|
330
327
|
log_info "Job.prepare target_method FTPES"
|
331
328
|
# set_info :target, :method, :ftpes
|
332
329
|
set_info :target, :method, JOB_METHOD_FTPS
|
333
330
|
# @target_method = :ftpes
|
334
|
-
@remote = RemoteFTP.new target_uri, log_prefix, ftpes: true
|
331
|
+
@remote = RemoteFTP.new target_uri, log_prefix, debug: @config[:debug_sftp], ftpes: true
|
335
332
|
|
336
333
|
elsif target_uri.is_a? URI::SFTP
|
337
334
|
log_info "Job.prepare target_method SFTP"
|
338
335
|
# set_info :target, :method, :sftp
|
339
336
|
set_info :target, :method, JOB_METHOD_SFTP
|
340
337
|
# @target_method = :sftp
|
341
|
-
@remote = RemoteSFTP.new target_uri, log_prefix
|
338
|
+
@remote = RemoteSFTP.new target_uri, log_prefix, debug: @config[:debug_sftp]
|
342
339
|
|
343
340
|
else
|
344
341
|
log_info "Job.prepare unknown scheme [#{target_uri.scheme}]"
|
@@ -460,15 +457,17 @@ module RestFtpDaemon
|
|
460
457
|
touch_job
|
461
458
|
end
|
462
459
|
|
463
|
-
def
|
460
|
+
def flag_prepare name, default
|
464
461
|
# build the flag instance var name
|
465
462
|
variable = "@#{name}"
|
466
463
|
|
467
|
-
|
468
|
-
|
464
|
+
[config[name], default].each do |alt_value|
|
465
|
+
# If it's already true or false, that's ok
|
466
|
+
return if [true, false].include? instance_variable_get(variable)
|
469
467
|
|
470
|
-
|
471
|
-
|
468
|
+
# Otherwise, set it to the new alt_value
|
469
|
+
instance_variable_set variable, alt_value
|
470
|
+
end
|
472
471
|
end
|
473
472
|
|
474
473
|
def finalize
|
@@ -539,6 +538,7 @@ module RestFtpDaemon
|
|
539
538
|
def progress transferred, name = ""
|
540
539
|
# What's current time ?
|
541
540
|
now = Time.now
|
541
|
+
notify_after = @config[:notify_after]
|
542
542
|
|
543
543
|
# Update counters
|
544
544
|
@transfer_sent += transferred
|
@@ -561,7 +561,7 @@ module RestFtpDaemon
|
|
561
561
|
stack << (Helpers.format_bytes @transfer_total, "B")
|
562
562
|
stack << (Helpers.format_bytes @current_bitrate.round(0), "bps")
|
563
563
|
stack2 = stack.map { |txt| ("%#{LOG_PIPE_LEN.to_i}s" % txt) }.join("\t")
|
564
|
-
log_debug "
|
564
|
+
log_debug "progress #{stack2} \t#{name}"
|
565
565
|
|
566
566
|
# Remember when we last did it
|
567
567
|
@progress_at = now
|
@@ -569,13 +569,13 @@ module RestFtpDaemon
|
|
569
569
|
|
570
570
|
# Notify if requested
|
571
571
|
notified_ago = (now.to_f - @notified_at.to_f)
|
572
|
-
if (
|
572
|
+
if (!notify_after.nil?) && (notified_ago > notify_after)
|
573
573
|
# Prepare and send notification
|
574
574
|
notif_status = {
|
575
575
|
progress: percent0,
|
576
576
|
transfer_sent: @transfer_sent,
|
577
577
|
transfer_total: @transfer_total,
|
578
|
-
transfer_bitrate: @current_bitrate,
|
578
|
+
transfer_bitrate: @current_bitrate.round(0),
|
579
579
|
}
|
580
580
|
client_notify :progress, status: notif_status
|
581
581
|
|
@@ -1,37 +1,60 @@
|
|
1
1
|
require "logger"
|
2
|
+
require "singleton"
|
2
3
|
|
4
|
+
# Logger interface class to access logger though symbolic names
|
3
5
|
module RestFtpDaemon
|
4
|
-
|
5
|
-
# Logger interface class to access logger though symbolic names
|
6
6
|
class LoggerPool
|
7
7
|
include Singleton
|
8
8
|
|
9
|
-
def initialize
|
10
|
-
@loggers = {}
|
11
|
-
end
|
12
|
-
|
13
9
|
def get pipe
|
10
|
+
@loggers ||= {}
|
14
11
|
@loggers[pipe] ||= create(pipe)
|
15
12
|
end
|
16
13
|
|
17
14
|
def create pipe
|
18
|
-
# Compute
|
19
|
-
|
20
|
-
logfile ||= STDERR
|
21
|
-
|
22
|
-
# Check if we can write to than file
|
23
|
-
unless File.writable?(logfile)
|
24
|
-
puts "LoggerPool [#{pipe}] logging to hyperspace (cannot write to #{logfile})"
|
25
|
-
logfile = nil
|
26
|
-
end
|
15
|
+
# Compute logfile or STDERR, and declare what we're doing
|
16
|
+
filename = logfile(pipe)
|
27
17
|
|
28
18
|
# Create the logger and return it
|
29
|
-
logger = Logger.new(
|
19
|
+
logger = Logger.new(filename, LOG_ROTATION) #, 10, 1024000)
|
30
20
|
logger.progname = pipe.to_s.downcase
|
31
21
|
logger.formatter = Shared::LoggerFormatter
|
32
22
|
|
33
23
|
# Finally return this logger
|
34
24
|
logger
|
25
|
+
|
26
|
+
rescue Errno::EACCES
|
27
|
+
puts "LoggerPool [#{pipe}] failed: access error"
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def logfile pipe
|
33
|
+
# Disabled if no valid config
|
34
|
+
return nil unless Conf[:logs].is_a?(Hash)
|
35
|
+
|
36
|
+
# Compute logfile and check if we can write there
|
37
|
+
logfile = File.expand_path(Conf[:logs][pipe], Conf[:logs][:path])
|
38
|
+
|
39
|
+
# Check that we'll be able to create logfiles
|
40
|
+
if File.exists?(logfile)
|
41
|
+
# File is there, is it writable ?
|
42
|
+
unless File.writable?(logfile)
|
43
|
+
puts "LoggerPool [#{pipe}] disabled: file not writable [#{logfile}]"
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
else
|
47
|
+
# No file here, can we create it ?
|
48
|
+
logdir = File.dirname(logfile)
|
49
|
+
unless File.writable?(logdir)
|
50
|
+
puts "LoggerPool [#{pipe}] disabled: directory not writable [#{logdir}]"
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# OK, return a clean file path
|
56
|
+
puts "LoggerPool [#{pipe}] logging to [#{logfile}]"
|
57
|
+
return logfile
|
35
58
|
end
|
36
59
|
|
37
60
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'api_auth'
|
2
|
+
require 'rest_client'
|
3
|
+
|
1
4
|
module RestFtpDaemon
|
2
5
|
|
3
6
|
# Handles a notification POST using a dedicated thread
|
@@ -61,20 +64,24 @@ module RestFtpDaemon
|
|
61
64
|
|
62
65
|
def send flags
|
63
66
|
# Prepare query
|
64
|
-
headers = {
|
65
|
-
"Content-Type" => "application/json",
|
66
|
-
"Accept" => "application/json",
|
67
|
-
"User-Agent" => "#{Conf.app_name}/v#{Conf.app_ver}"
|
68
|
-
}
|
69
|
-
data = flags.to_json
|
70
|
-
|
71
|
-
# Send notification through HTTP
|
72
67
|
uri = URI @url
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
68
|
+
# uri = URI(rule[:relay])
|
69
|
+
#http = Net::HTTP.new uri.host, uri.port
|
70
|
+
|
71
|
+
# Prepare request
|
72
|
+
request = RestClient::Request.new url: uri.to_s,
|
73
|
+
method: :post,
|
74
|
+
payload: JSON.pretty_generate(flags),
|
75
|
+
headers: {
|
76
|
+
content_type: :json,
|
77
|
+
accept: :json,
|
78
|
+
user_agent: Conf.generate(:user_agent),
|
79
|
+
}
|
80
|
+
|
81
|
+
# Execure request
|
82
|
+
log_info "posting #{flags.to_json}"
|
83
|
+
# response = http.post uri.path, data, headers
|
84
|
+
response = request.execute
|
78
85
|
|
79
86
|
# Log reponse body
|
80
87
|
response_lines = response.body.lines
|
@@ -86,14 +93,14 @@ module RestFtpDaemon
|
|
86
93
|
end
|
87
94
|
|
88
95
|
# Handle exceptions
|
89
|
-
rescue Net::OpenTimeout =>
|
90
|
-
log_error "Net::OpenTimeout: #{
|
96
|
+
rescue Net::OpenTimeout => e
|
97
|
+
log_error "Net::OpenTimeout: #{e.message}"
|
91
98
|
|
92
|
-
rescue SocketError =>
|
93
|
-
log_error "SocketError: #{
|
99
|
+
rescue SocketError => e
|
100
|
+
log_error "SocketError: #{e.message}"
|
94
101
|
|
95
|
-
rescue StandardError =>
|
96
|
-
log_error "UNHANDLED EXCEPTION: #{
|
102
|
+
rescue StandardError => e
|
103
|
+
log_error "UNHANDLED EXCEPTION: #{e.message}", e.backtrace
|
97
104
|
end
|
98
105
|
|
99
106
|
def log_prefix
|
@@ -7,9 +7,12 @@ module RestFtpDaemon
|
|
7
7
|
attr_reader :log_prefix
|
8
8
|
|
9
9
|
def initialize url, log_prefix, options = {}
|
10
|
+
# Options
|
11
|
+
@debug = !!options[:debug]
|
12
|
+
|
10
13
|
# Logger
|
11
14
|
@log_prefix = log_prefix || {}
|
12
|
-
@logger = RestFtpDaemon::LoggerPool.instance.get :
|
15
|
+
@logger = RestFtpDaemon::LoggerPool.instance.get :transfer
|
13
16
|
|
14
17
|
# Extract URL parts
|
15
18
|
@url = url
|
@@ -21,12 +24,22 @@ module RestFtpDaemon
|
|
21
24
|
|
22
25
|
def connect
|
23
26
|
# Debug mode ?
|
24
|
-
|
27
|
+
return unless @debug
|
28
|
+
puts
|
29
|
+
puts "-------------------- SESSION STARTING -------------------------"
|
30
|
+
puts "class\t #{myname}"
|
31
|
+
puts "host\t #{@url.host}"
|
32
|
+
puts "user\t #{@url.user}"
|
33
|
+
puts "port\t #{@url.port}"
|
34
|
+
puts "options\t #{@options.inspect}"
|
35
|
+
puts "---------------------------------------------------------------"
|
36
|
+
|
25
37
|
end
|
26
38
|
|
27
39
|
def close
|
28
40
|
# Debug mode ?
|
29
|
-
|
41
|
+
return unless @debug
|
42
|
+
puts "-------------------- SESSION CLOSING --------------------------"
|
30
43
|
end
|
31
44
|
|
32
45
|
private
|
@@ -35,17 +48,5 @@ module RestFtpDaemon
|
|
35
48
|
self.class.to_s
|
36
49
|
end
|
37
50
|
|
38
|
-
def debug_header
|
39
|
-
# Output header to STDOUT
|
40
|
-
puts
|
41
|
-
puts "-------------------- SESSION STARTING -------------------------"
|
42
|
-
puts "class\t #{myname}"
|
43
|
-
puts "host\t #{@url.host}"
|
44
|
-
puts "user\t #{@url.user}"
|
45
|
-
puts "port\t #{@url.port}"
|
46
|
-
puts "options\t #{@options.inspect}"
|
47
|
-
puts "---------------------------------------------------------------"
|
48
|
-
end
|
49
|
-
|
50
51
|
end
|
51
52
|
end
|
@@ -10,9 +10,6 @@ module RestFtpDaemon
|
|
10
10
|
# Call super
|
11
11
|
super
|
12
12
|
|
13
|
-
# Use debug ?
|
14
|
-
@debug = (Conf.at :debug, :ftp) == true
|
15
|
-
|
16
13
|
# Create FTP object
|
17
14
|
if options[:ftpes]
|
18
15
|
prepare_ftpes
|
@@ -20,7 +17,7 @@ module RestFtpDaemon
|
|
20
17
|
prepare_ftp
|
21
18
|
end
|
22
19
|
@ftp.passive = true
|
23
|
-
@ftp.debug_mode =
|
20
|
+
@ftp.debug_mode = @debug
|
24
21
|
|
25
22
|
# Config
|
26
23
|
@chunk_size = DEFAULT_FTP_CHUNK.to_i * 1024
|
@@ -30,10 +27,8 @@ module RestFtpDaemon
|
|
30
27
|
end
|
31
28
|
|
32
29
|
def connect
|
33
|
-
# Connect init
|
34
|
-
super
|
35
|
-
|
36
30
|
# Connect remote server
|
31
|
+
super
|
37
32
|
@ftp.connect @url.host, @url.port
|
38
33
|
@ftp.login @url.user, @url.password
|
39
34
|
end
|