rest-ftp-daemon 0.6 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -1
- data/Gemfile.lock +57 -54
- data/README.md +26 -116
- data/Rakefile +49 -1
- data/VERSION +1 -0
- data/bin/rest-ftp-daemon +17 -37
- data/lib/config.rb +3 -0
- data/lib/config.ru +11 -0
- data/lib/errors.rb +12 -0
- data/lib/extend_threads.rb +14 -0
- data/lib/rest-ftp-daemon.rb +304 -18
- data/rest-ftp-daemon.gemspec +62 -31
- metadata +46 -100
- checksums.yaml +0 -15
- data/.gitignore +0 -6
- data/config.ru +0 -14
- data/lib/rest-ftp-daemon/api/defaults.rb +0 -76
- data/lib/rest-ftp-daemon/api/jobs.rb +0 -151
- data/lib/rest-ftp-daemon/api/root.rb +0 -129
- data/lib/rest-ftp-daemon/api/workers.rb +0 -49
- data/lib/rest-ftp-daemon/common.rb +0 -49
- data/lib/rest-ftp-daemon/config.rb +0 -24
- data/lib/rest-ftp-daemon/exceptions.rb +0 -26
- data/lib/rest-ftp-daemon/job.rb +0 -246
- data/lib/rest-ftp-daemon/job_queue.rb +0 -105
- data/lib/rest-ftp-daemon/logger.rb +0 -7
- data/lib/rest-ftp-daemon/notification.rb +0 -82
- data/lib/rest-ftp-daemon/static/css/bootstrap.min.css +0 -5
- data/lib/rest-ftp-daemon/views/dashboard.haml +0 -86
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +0 -75
- data/lib/rest-ftp-daemon/views/index.haml +0 -116
- data/lib/rest-ftp-daemon/worker_pool.rb +0 -63
- data/rest-ftp-daemon.yml.sample +0 -15
data/lib/rest-ftp-daemon/job.rb
DELETED
@@ -1,246 +0,0 @@
|
|
1
|
-
require 'net/ftp'
|
2
|
-
|
3
|
-
|
4
|
-
module RestFtpDaemon
|
5
|
-
class Job < RestFtpDaemon::Common
|
6
|
-
|
7
|
-
def initialize(id, params={})
|
8
|
-
# Call super
|
9
|
-
super()
|
10
|
-
|
11
|
-
# Grab params
|
12
|
-
@params = params
|
13
|
-
@target = nil
|
14
|
-
@source = nil
|
15
|
-
|
16
|
-
# Init context
|
17
|
-
set :id, id
|
18
|
-
set :started_at, Time.now
|
19
|
-
set :status, :created
|
20
|
-
|
21
|
-
# Send first notification
|
22
|
-
notify "rftpd.queued"
|
23
|
-
end
|
24
|
-
|
25
|
-
def progname
|
26
|
-
job_id = get(:id)
|
27
|
-
"JOB #{job_id}"
|
28
|
-
end
|
29
|
-
|
30
|
-
def id
|
31
|
-
get :id
|
32
|
-
end
|
33
|
-
|
34
|
-
def priority
|
35
|
-
get :priority
|
36
|
-
end
|
37
|
-
def get_status
|
38
|
-
get :status
|
39
|
-
end
|
40
|
-
|
41
|
-
def process
|
42
|
-
# Init
|
43
|
-
info "Job.process starting"
|
44
|
-
set :status, :starting
|
45
|
-
set :error, 0
|
46
|
-
|
47
|
-
begin
|
48
|
-
# Validate job and params
|
49
|
-
prepare
|
50
|
-
|
51
|
-
# Process
|
52
|
-
transfer
|
53
|
-
|
54
|
-
rescue Net::FTPPermError => exception
|
55
|
-
info "Job.process failed [Net::FTPPermError]"
|
56
|
-
set :status, :failed
|
57
|
-
set :error, exception.class
|
58
|
-
|
59
|
-
rescue RestFtpDaemonException => exception
|
60
|
-
info "Job.process failed [RestFtpDaemonException::#{exception.class}]"
|
61
|
-
set :status, :failed
|
62
|
-
set :error, exception.class
|
63
|
-
|
64
|
-
# rescue Exception => exception
|
65
|
-
# set :status, :crashed
|
66
|
-
# set :error, exception.class
|
67
|
-
|
68
|
-
else
|
69
|
-
info "Job.process finished"
|
70
|
-
# set :error, 0
|
71
|
-
#set :status, :wandering
|
72
|
-
|
73
|
-
# Wait for a few seconds before marking the job as finished
|
74
|
-
# info "#{prefix} wander for #{RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE} sec"
|
75
|
-
# wander RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE
|
76
|
-
set :status, :finished
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
def describe
|
82
|
-
# Update realtime info
|
83
|
-
w = wandering_time
|
84
|
-
set :wandering, w.round(2) unless w.nil?
|
85
|
-
|
86
|
-
# Update realtime info
|
87
|
-
u = up_time
|
88
|
-
set :uptime, u.round(2) unless u.nil?
|
89
|
-
|
90
|
-
# Return the whole structure
|
91
|
-
@params
|
92
|
-
end
|
93
|
-
|
94
|
-
def status text
|
95
|
-
@status = text
|
96
|
-
end
|
97
|
-
|
98
|
-
def get attribute
|
99
|
-
return nil unless @params.is_a? Enumerable
|
100
|
-
@params[attribute.to_s]
|
101
|
-
end
|
102
|
-
|
103
|
-
protected
|
104
|
-
|
105
|
-
def up_time
|
106
|
-
return if @params[:started_at].nil?
|
107
|
-
Time.now - @params[:started_at]
|
108
|
-
end
|
109
|
-
|
110
|
-
def wander time
|
111
|
-
info "Job.wander #{time}"
|
112
|
-
@wander_for = time
|
113
|
-
@wander_started = Time.now
|
114
|
-
sleep time
|
115
|
-
info "Job.wandered ok"
|
116
|
-
end
|
117
|
-
|
118
|
-
def wandering_time
|
119
|
-
return if @wander_started.nil? || @wander_for.nil?
|
120
|
-
@wander_for.to_f - (Time.now - @wander_started)
|
121
|
-
end
|
122
|
-
|
123
|
-
# def exception_handler(actor, reason)
|
124
|
-
# set :status, :crashed
|
125
|
-
# set :error, reason
|
126
|
-
# end
|
127
|
-
|
128
|
-
def set attribute, value
|
129
|
-
return unless @params.is_a? Enumerable
|
130
|
-
@params[:updated_at] = Time.now
|
131
|
-
@params[attribute.to_s] = value
|
132
|
-
end
|
133
|
-
|
134
|
-
def expand_path_from path
|
135
|
-
File.expand_path replace_token_in_path(path)
|
136
|
-
end
|
137
|
-
|
138
|
-
def expand_url_from path
|
139
|
-
URI replace_token_in_path(path) rescue nil
|
140
|
-
end
|
141
|
-
|
142
|
-
def replace_token_in_path path
|
143
|
-
# Ensure endpoints are not a nil value
|
144
|
-
return path unless Settings.endpoints.is_a? Enumerable
|
145
|
-
newpath = path.clone
|
146
|
-
|
147
|
-
# Replace endpoints defined in config
|
148
|
-
Settings.endpoints.each do |from, to|
|
149
|
-
newpath.gsub! "[#{from}]", to
|
150
|
-
end
|
151
|
-
|
152
|
-
# Replace with the special RAND token
|
153
|
-
newpath.gsub! "[RANDOM]", SecureRandom.hex(8)
|
154
|
-
|
155
|
-
return newpath
|
156
|
-
end
|
157
|
-
|
158
|
-
def prepare
|
159
|
-
# Init
|
160
|
-
set :status, :preparing
|
161
|
-
|
162
|
-
# Check source
|
163
|
-
raise JobSourceMissing unless @params["source"]
|
164
|
-
@source = expand_path_from @params["source"]
|
165
|
-
set :debug_source, @source
|
166
|
-
|
167
|
-
# Check target
|
168
|
-
raise JobTargetMissing unless @params["target"]
|
169
|
-
@target = expand_url_from @params["target"]
|
170
|
-
set :debug_target, @target.inspect
|
171
|
-
|
172
|
-
# Check compliance
|
173
|
-
raise JobTargetUnparseable if @target.nil?
|
174
|
-
raise JobSourceNotFound unless File.exists? @source
|
175
|
-
|
176
|
-
end
|
177
|
-
|
178
|
-
def transfer_fake
|
179
|
-
# Init
|
180
|
-
set :status, :faking
|
181
|
-
|
182
|
-
# Work
|
183
|
-
(0..9).each do |i|
|
184
|
-
set :faking, i
|
185
|
-
sleep 0.5
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def transfer
|
190
|
-
# Send first notification
|
191
|
-
transferred = 0
|
192
|
-
notify "rftpd.started"
|
193
|
-
|
194
|
-
# Ensure @source and @target are there
|
195
|
-
set :status, :checking_source
|
196
|
-
raise JobPrerequisitesNotMet unless @source
|
197
|
-
raise JobPrerequisitesNotMet unless @source
|
198
|
-
target_path = File.dirname @target.path
|
199
|
-
target_name = File.basename @target.path
|
200
|
-
|
201
|
-
# Read source file size
|
202
|
-
source_size = File.size @source
|
203
|
-
set :file_size, source_size
|
204
|
-
|
205
|
-
# Prepare FTP transfer
|
206
|
-
set :status, :checking_target
|
207
|
-
ftp = Net::FTP.new(@target.host)
|
208
|
-
ftp.passive = true
|
209
|
-
ftp.login @target.user, @target.password
|
210
|
-
ftp.chdir(target_path)
|
211
|
-
|
212
|
-
# Check for target file presence
|
213
|
-
if get(:overwrite).nil?
|
214
|
-
results = ftp.list(target_name)
|
215
|
-
#results = ftp.list()
|
216
|
-
|
217
|
-
unless results.count.zero?
|
218
|
-
ftp.close
|
219
|
-
notify "rftpd.ended", RestFtpDaemon::JobTargetFileExists
|
220
|
-
raise RestFtpDaemon::JobTargetFileExists
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
# Do transfer
|
225
|
-
set :status, :uploading
|
226
|
-
chunk_size = Settings.transfer.chunk_size || Settings[:default_chunk_size]
|
227
|
-
ftp.putbinaryfile(@source, target_name, chunk_size) do |block|
|
228
|
-
# Update counters
|
229
|
-
transferred += block.bytesize
|
230
|
-
|
231
|
-
# Update job info
|
232
|
-
percent = (100.0 * transferred / source_size).round(1)
|
233
|
-
set :progress, percent
|
234
|
-
set :file_sent, transferred
|
235
|
-
end
|
236
|
-
|
237
|
-
# Close FTP connexion
|
238
|
-
notify "rftpd.ended"
|
239
|
-
set :progress, nil
|
240
|
-
ftp.close
|
241
|
-
end
|
242
|
-
|
243
|
-
private
|
244
|
-
|
245
|
-
end
|
246
|
-
end
|
@@ -1,105 +0,0 @@
|
|
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
|
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
|
3
|
-
module RestFtpDaemon
|
4
|
-
class Notification < RestFtpDaemon::Common
|
5
|
-
attr_accessor :job_id
|
6
|
-
attr_accessor :signal
|
7
|
-
attr_accessor :error
|
8
|
-
attr_accessor :message
|
9
|
-
attr_accessor :status
|
10
|
-
attr_accessor :url
|
11
|
-
attr_accessor :job
|
12
|
-
attr_accessor :key
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
#def initialize(job, signal, error, status)
|
16
|
-
# Grab params
|
17
|
-
#@job = job
|
18
|
-
# @signal = signal
|
19
|
-
# @error = error
|
20
|
-
# @status = status
|
21
|
-
@status = {}
|
22
|
-
@error = 0
|
23
|
-
@message = nil
|
24
|
-
|
25
|
-
# Generate a random key
|
26
|
-
@key = SecureRandom.hex(2)
|
27
|
-
|
28
|
-
# Call super
|
29
|
-
super
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
def progname
|
34
|
-
"NOTIF #{@key}"
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
# def status key, val
|
39
|
-
# @status[key.to_s] = val
|
40
|
-
# end
|
41
|
-
|
42
|
-
def notify
|
43
|
-
# Check context
|
44
|
-
raise NotificationMissingUrl unless @url
|
45
|
-
raise NotificationMissingSignal unless @signal
|
46
|
-
#sraise NotifImpossible unless @status
|
47
|
-
|
48
|
-
# Params
|
49
|
-
params = {
|
50
|
-
id: @job_id,
|
51
|
-
host: get_hostname,
|
52
|
-
signal: @signal,
|
53
|
-
error: @error,
|
54
|
-
}
|
55
|
-
|
56
|
-
# Add status only if present
|
57
|
-
params["status"] = @status unless @status.empty?
|
58
|
-
params["message"] = @message unless @message.to_s.blank?
|
59
|
-
|
60
|
-
# Log this notification
|
61
|
-
info "notify params: #{params.inspect}"
|
62
|
-
|
63
|
-
# Prepare query
|
64
|
-
uri = URI(@url)
|
65
|
-
headers = {"Content-Type" => "application/json",
|
66
|
-
"Accept" => "application/json"}
|
67
|
-
|
68
|
-
# Post the notification
|
69
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
70
|
-
response = http.post(uri.path, params.to_json, headers)
|
71
|
-
|
72
|
-
info "notify reply: #{response.body.strip}"
|
73
|
-
end
|
74
|
-
|
75
|
-
protected
|
76
|
-
|
77
|
-
def get_hostname
|
78
|
-
`hostname`.chomp
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
end
|