rest-ftp-daemon 0.6 → 0.9.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.
- 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
|