rest-ftp-daemon 0.30.1 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- VERSION = "0.30.1"
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 = 30
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
@@ -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
- @params[:id]
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 [#{@id}] starting"
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
- set :status, :crashed
89
- set :error, reason
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
- # Init
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
- results = ftp.list(target_name)
150
- #info "ftp.list: #{results}"
151
- unless results.count.zero?
152
- ftp.close
153
- raise JobTargetPermission
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,7 @@
1
+ class Logger
2
+ def format_message(severity, timestamp, progname, msg)
3
+ stamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
4
+ progname = "%-#{RestFtpDaemon::LOG_TRIM_PROGNAME}s" % progname
5
+ "#{stamp} #{severity} #{progname} #{msg}\n"
6
+ end
7
+ 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