rest-ftp-daemon 0.30.1 → 0.41
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 +8 -8
- data/Gemfile.lock +11 -4
- data/README.md +30 -6
- data/bin/rest-ftp-daemon +16 -9
- data/config.ru +9 -1
- data/lib/rest-ftp-daemon.rb +13 -1
- data/lib/rest-ftp-daemon/api/defaults.rb +58 -0
- data/lib/rest-ftp-daemon/api/jobs.rb +151 -0
- data/lib/rest-ftp-daemon/api/root.rb +79 -0
- data/lib/rest-ftp-daemon/common.rb +49 -0
- data/lib/rest-ftp-daemon/config.rb +9 -2
- data/lib/rest-ftp-daemon/exceptions.rb +6 -1
- data/lib/rest-ftp-daemon/job.rb +69 -23
- data/lib/rest-ftp-daemon/job_queue.rb +105 -0
- data/lib/rest-ftp-daemon/logger.rb +7 -0
- data/lib/rest-ftp-daemon/notification.rb +80 -0
- data/lib/rest-ftp-daemon/worker_pool.rb +63 -0
- data/lib/rest-ftp-daemon/www.rb +6 -0
- data/rest-ftp-daemon.gemspec +2 -2
- metadata +15 -7
- data/lib/rest-ftp-daemon/server.rb +0 -237
@@ -0,0 +1,49 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
|
3
|
+
class Common
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
# Logger
|
9
|
+
@logger = ActiveSupport::Logger.new APP_LOGTO, 'daily'
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
end
|
14
|
+
|
15
|
+
def progname
|
16
|
+
end
|
17
|
+
|
18
|
+
def info message, level = 0
|
19
|
+
# progname = "Job [#{id}]" unless id.nil?
|
20
|
+
# progname = "Worker [#{id}]" unless worker_id.nil?
|
21
|
+
@logger.add(Logger::INFO, "#{' '*(level+1)} #{message}", progname)
|
22
|
+
end
|
23
|
+
|
24
|
+
def notify signal, error = 0, status = {}
|
25
|
+
# Skip is not callback URL defined
|
26
|
+
url = get :notify
|
27
|
+
if url.nil?
|
28
|
+
info "Skipping notification (no valid url provided) sig[#{signal}] e[#{error}] s#{status.inspect}"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
# Build notification
|
33
|
+
n = RestFtpDaemon::Notification.new
|
34
|
+
n.job_id = id
|
35
|
+
n.url = url
|
36
|
+
n.signal = signal
|
37
|
+
n.error = error.inspect
|
38
|
+
n.status = status
|
39
|
+
|
40
|
+
# Now, send the notification
|
41
|
+
info "Queuing notification key[#{n.key}] sig[#{signal}] url[#{url}]"
|
42
|
+
Thread.new(n) do |thread|
|
43
|
+
n.notify
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -1,8 +1,15 @@
|
|
1
1
|
module RestFtpDaemon
|
2
2
|
# Global config
|
3
|
-
|
3
|
+
NAME = 'rest-ftp-daemon'
|
4
|
+
VERSION = "0.41"
|
5
|
+
PORT = 3000
|
4
6
|
|
5
7
|
# Transfer config
|
6
8
|
TRANSFER_CHUNK_SIZE = 100000
|
7
|
-
THREAD_SLEEP_BEFORE_DIE =
|
9
|
+
THREAD_SLEEP_BEFORE_DIE = 10
|
10
|
+
|
11
|
+
# Logging
|
12
|
+
APP_LOGTO = "/tmp/#{NAME}.log"
|
13
|
+
LOG_TRIM_PROGNAME = 18
|
14
|
+
|
8
15
|
end
|
@@ -16,6 +16,11 @@ module RestFtpDaemon
|
|
16
16
|
class JobSourceNotFound < RestFtpDaemonException; end
|
17
17
|
class JobTargetMissing < RestFtpDaemonException; end
|
18
18
|
class JobTargetUnparseable < RestFtpDaemonException; end
|
19
|
-
class JobTargetPermission < RestFtpDaemonException; end
|
19
|
+
#class JobTargetPermission < RestFtpDaemonException; end
|
20
|
+
class JobTargetFileExists < RestFtpDaemonException; end
|
21
|
+
|
22
|
+
class NotificationMissingUrl < RestFtpDaemonException; end
|
23
|
+
class NotificationMissingSignal < RestFtpDaemonException; end
|
24
|
+
|
20
25
|
|
21
26
|
end
|
data/lib/rest-ftp-daemon/job.rb
CHANGED
@@ -1,25 +1,50 @@
|
|
1
1
|
module RestFtpDaemon
|
2
|
-
class Job
|
2
|
+
class Job < RestFtpDaemon::Common
|
3
3
|
|
4
4
|
def initialize(id, params={})
|
5
|
+
# Call super
|
6
|
+
super()
|
7
|
+
|
5
8
|
# Grab params
|
6
9
|
@params = params
|
7
10
|
@target = nil
|
8
11
|
@source = nil
|
9
12
|
|
13
|
+
# Logger
|
14
|
+
#@logger = ActiveSupport::Logger.new APP_LOGTO, 'daily'
|
15
|
+
|
10
16
|
# Init context
|
11
17
|
set :id, id
|
12
18
|
set :started_at, Time.now
|
13
19
|
set :status, :initialized
|
20
|
+
|
21
|
+
# Send first notification
|
22
|
+
notify "rftpd.queued"
|
23
|
+
end
|
24
|
+
|
25
|
+
# def job_id
|
26
|
+
# get :id
|
27
|
+
# end
|
28
|
+
|
29
|
+
def progname
|
30
|
+
job_id = get(:id)
|
31
|
+
"JOB #{job_id}"
|
14
32
|
end
|
15
33
|
|
16
34
|
def id
|
17
|
-
|
35
|
+
get :id
|
36
|
+
end
|
37
|
+
|
38
|
+
def priority
|
39
|
+
get :priority
|
40
|
+
end
|
41
|
+
def get_status
|
42
|
+
get :status
|
18
43
|
end
|
19
44
|
|
20
45
|
def process
|
21
46
|
# Init
|
22
|
-
"process
|
47
|
+
info "Job.process starting"
|
23
48
|
set :status, :starting
|
24
49
|
set :error, 0
|
25
50
|
|
@@ -30,11 +55,13 @@ module RestFtpDaemon
|
|
30
55
|
# Process
|
31
56
|
transfer
|
32
57
|
|
33
|
-
rescue Net::FTPPermError
|
58
|
+
rescue Net::FTPPermError => exception
|
59
|
+
info "Job.process failed [Net::FTPPermError]"
|
34
60
|
set :status, :failed
|
35
61
|
set :error, exception.class
|
36
62
|
|
37
63
|
rescue RestFtpDaemonException => exception
|
64
|
+
info "Job.process failed [RestFtpDaemonException::#{exception.class}]"
|
38
65
|
set :status, :failed
|
39
66
|
set :error, exception.class
|
40
67
|
|
@@ -43,8 +70,14 @@ module RestFtpDaemon
|
|
43
70
|
# set :error, exception.class
|
44
71
|
|
45
72
|
else
|
73
|
+
info "Job.process finished"
|
74
|
+
# set :error, 0
|
75
|
+
#set :status, :wandering
|
76
|
+
|
77
|
+
# Wait for a few seconds before marking the job as finished
|
78
|
+
# info "#{prefix} wander for #{RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE} sec"
|
79
|
+
# wander RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE
|
46
80
|
set :status, :finished
|
47
|
-
set :error, 0
|
48
81
|
end
|
49
82
|
|
50
83
|
end
|
@@ -62,12 +95,6 @@ module RestFtpDaemon
|
|
62
95
|
@params
|
63
96
|
end
|
64
97
|
|
65
|
-
def wander time
|
66
|
-
@wander_for = time
|
67
|
-
@wander_started = Time.now
|
68
|
-
sleep time
|
69
|
-
end
|
70
|
-
|
71
98
|
def status text
|
72
99
|
@status = text
|
73
100
|
end
|
@@ -79,20 +106,33 @@ module RestFtpDaemon
|
|
79
106
|
Time.now - @params[:started_at]
|
80
107
|
end
|
81
108
|
|
109
|
+
def wander time
|
110
|
+
info "Job.wander #{time}"
|
111
|
+
@wander_for = time
|
112
|
+
@wander_started = Time.now
|
113
|
+
sleep time
|
114
|
+
info "Job.wandered ok"
|
115
|
+
end
|
116
|
+
|
82
117
|
def wandering_time
|
83
118
|
return if @wander_started.nil? || @wander_for.nil?
|
84
119
|
@wander_for.to_f - (Time.now - @wander_started)
|
85
120
|
end
|
86
121
|
|
87
|
-
def exception_handler(actor, reason)
|
88
|
-
|
89
|
-
|
90
|
-
end
|
122
|
+
# def exception_handler(actor, reason)
|
123
|
+
# set :status, :crashed
|
124
|
+
# set :error, reason
|
125
|
+
# end
|
91
126
|
|
92
127
|
def set attribute, value
|
93
128
|
return unless @params.is_a? Enumerable
|
94
129
|
@params[:updated_at] = Time.now
|
95
|
-
@params[attribute] = value
|
130
|
+
@params[attribute.to_s] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
def get attribute
|
134
|
+
return nil unless @params.is_a? Enumerable
|
135
|
+
@params[attribute.to_s]
|
96
136
|
end
|
97
137
|
|
98
138
|
def prepare
|
@@ -124,8 +164,9 @@ module RestFtpDaemon
|
|
124
164
|
end
|
125
165
|
|
126
166
|
def transfer
|
127
|
-
#
|
167
|
+
# Send first notification
|
128
168
|
transferred = 0
|
169
|
+
notify "rftpd.started"
|
129
170
|
|
130
171
|
# Ensure @source and @target are there
|
131
172
|
set :status, :checking_source
|
@@ -142,15 +183,19 @@ module RestFtpDaemon
|
|
142
183
|
set :status, :checking_target
|
143
184
|
ftp = Net::FTP.new(@target.host)
|
144
185
|
ftp.passive = true
|
145
|
-
ftp.login
|
186
|
+
ftp.login @target.user, @target.password
|
146
187
|
ftp.chdir(target_path)
|
147
188
|
|
148
189
|
# Check for target file presence
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
190
|
+
if get(:overwrite).nil?
|
191
|
+
results = ftp.list(target_name)
|
192
|
+
#results = ftp.list()
|
193
|
+
|
194
|
+
unless results.count.zero?
|
195
|
+
ftp.close
|
196
|
+
notify "rftpd.ended", RestFtpDaemon::JobTargetFileExists
|
197
|
+
raise RestFtpDaemon::JobTargetFileExists
|
198
|
+
end
|
154
199
|
end
|
155
200
|
|
156
201
|
# Do transfer
|
@@ -166,6 +211,7 @@ module RestFtpDaemon
|
|
166
211
|
end
|
167
212
|
|
168
213
|
# Close FTP connexion
|
214
|
+
notify "rftpd.ended"
|
169
215
|
ftp.close
|
170
216
|
end
|
171
217
|
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module RestFtpDaemon
|
4
|
+
class JobQueue < Queue
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@queued = []
|
8
|
+
@popped = []
|
9
|
+
|
10
|
+
@waiting = []
|
11
|
+
@queued.taint # enable tainted communication
|
12
|
+
@waiting.taint
|
13
|
+
self.taint
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def queued
|
18
|
+
@queued
|
19
|
+
end
|
20
|
+
def queued_size
|
21
|
+
@queued.length
|
22
|
+
end
|
23
|
+
|
24
|
+
def popped
|
25
|
+
@popped
|
26
|
+
end
|
27
|
+
def popped_size
|
28
|
+
@popped.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
@queued + @popped
|
33
|
+
end
|
34
|
+
def all_size
|
35
|
+
popped_size + queued_size
|
36
|
+
end
|
37
|
+
|
38
|
+
def push(obj)
|
39
|
+
# Check that itme responds to "priorty" method
|
40
|
+
raise "object should respond to priority method" unless obj.respond_to? :priority
|
41
|
+
|
42
|
+
@mutex.synchronize{
|
43
|
+
@queued.push obj
|
44
|
+
begin
|
45
|
+
t = @waiting.shift
|
46
|
+
t.wakeup if t
|
47
|
+
rescue ThreadError
|
48
|
+
retry
|
49
|
+
end
|
50
|
+
}
|
51
|
+
end
|
52
|
+
alias << push
|
53
|
+
alias enq push
|
54
|
+
|
55
|
+
#
|
56
|
+
# Retrieves data from the queue. If the queue is empty, the calling thread is
|
57
|
+
# suspended until data is pushed onto the queue. If +non_block+ is true, the
|
58
|
+
# thread isn't suspended, and an exception is raised.
|
59
|
+
#
|
60
|
+
def pop(non_block=false)
|
61
|
+
@mutex.synchronize{
|
62
|
+
while true
|
63
|
+
if @queued.empty?
|
64
|
+
raise ThreadError, "queue empty" if non_block
|
65
|
+
@waiting.push Thread.current
|
66
|
+
@mutex.sleep
|
67
|
+
else
|
68
|
+
return pick
|
69
|
+
end
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
alias shift pop
|
74
|
+
alias deq pop
|
75
|
+
|
76
|
+
def empty?
|
77
|
+
@queued.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def clear
|
81
|
+
@queued.clear
|
82
|
+
end
|
83
|
+
|
84
|
+
def num_waiting
|
85
|
+
@waiting.size
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def pick
|
91
|
+
# Sort jobs by priority and get the biggest one
|
92
|
+
picked = @queued.sort { |a,b| a.priority.to_i <=> b.priority.to_i }.last
|
93
|
+
|
94
|
+
# Delete it from the queue
|
95
|
+
@queued.delete_if { |item| item == picked } unless picked.nil?
|
96
|
+
|
97
|
+
# Stack it to popped items
|
98
|
+
@popped.push picked
|
99
|
+
|
100
|
+
# Return picked
|
101
|
+
picked
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
class Notification < RestFtpDaemon::Common
|
3
|
+
attr_accessor :job_id
|
4
|
+
attr_accessor :signal
|
5
|
+
attr_accessor :error
|
6
|
+
attr_accessor :message
|
7
|
+
attr_accessor :status
|
8
|
+
attr_accessor :url
|
9
|
+
attr_accessor :job
|
10
|
+
attr_accessor :key
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
#def initialize(job, signal, error, status)
|
14
|
+
# Grab params
|
15
|
+
#@job = job
|
16
|
+
# @signal = signal
|
17
|
+
# @error = error
|
18
|
+
# @status = status
|
19
|
+
@status = {}
|
20
|
+
@error = 0
|
21
|
+
@message = nil
|
22
|
+
|
23
|
+
# Generate a random key
|
24
|
+
@key = SecureRandom.hex(2)
|
25
|
+
|
26
|
+
# Call super
|
27
|
+
super
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def progname
|
32
|
+
"NOTIF #{@key}"
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# def status key, val
|
37
|
+
# @status[key.to_s] = val
|
38
|
+
# end
|
39
|
+
|
40
|
+
def notify
|
41
|
+
# Check context
|
42
|
+
raise NotificationMissingUrl unless @url
|
43
|
+
raise NotificationMissingSignal unless @signal
|
44
|
+
#sraise NotifImpossible unless @status
|
45
|
+
|
46
|
+
# Params
|
47
|
+
params = {
|
48
|
+
id: @job_id,
|
49
|
+
host: get_hostname,
|
50
|
+
signal: @signal,
|
51
|
+
error: @error,
|
52
|
+
}
|
53
|
+
|
54
|
+
# Add status only if present
|
55
|
+
params["status"] = @status unless @status.empty?
|
56
|
+
params["message"] = @message unless @message.to_s.blank?
|
57
|
+
|
58
|
+
# Log this notification
|
59
|
+
info "notify params: #{params.inspect}"
|
60
|
+
|
61
|
+
# Prepare query
|
62
|
+
uri = URI(@url)
|
63
|
+
headers = {"Content-Type" => "application/json",
|
64
|
+
"Accept" => "application/json"}
|
65
|
+
|
66
|
+
# Post the notification
|
67
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
68
|
+
response = http.post(uri.path, params.to_json, headers)
|
69
|
+
|
70
|
+
info "notify reply: #{response.body.strip}"
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def get_hostname
|
76
|
+
`hostname`.chomp
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|