rest-ftp-daemon 0.72b → 0.85.2
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 +5 -13
- data/.ruby-version +1 -0
- data/Gemfile.lock +6 -4
- data/README.md +58 -93
- data/bin/rest-ftp-daemon +96 -24
- data/config.ru +12 -9
- data/lib/rest-ftp-daemon.rb +2 -1
- data/lib/rest-ftp-daemon/api/defaults.rb +1 -10
- data/lib/rest-ftp-daemon/api/jobs.rb +20 -18
- data/lib/rest-ftp-daemon/api/root.rb +37 -25
- data/lib/rest-ftp-daemon/common.rb +2 -37
- data/lib/rest-ftp-daemon/config.rb +14 -19
- data/lib/rest-ftp-daemon/constants.rb +21 -0
- data/lib/rest-ftp-daemon/exceptions.rb +16 -29
- data/lib/rest-ftp-daemon/helpers.rb +55 -0
- data/lib/rest-ftp-daemon/job.rb +274 -150
- data/lib/rest-ftp-daemon/job_queue.rb +112 -17
- data/lib/rest-ftp-daemon/logger.rb +29 -5
- data/lib/rest-ftp-daemon/notification.rb +33 -48
- data/lib/rest-ftp-daemon/static/css/bootstrap.css +4490 -0
- data/lib/rest-ftp-daemon/static/css/{bootstrap.min.css → bootstrap.min.old1.css} +1 -1
- data/lib/rest-ftp-daemon/uri.rb +7 -1
- data/lib/rest-ftp-daemon/views/dashboard.haml +10 -10
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +20 -26
- data/lib/rest-ftp-daemon/views/dashboard_tokens.haml +1 -1
- data/lib/rest-ftp-daemon/views/dashboard_workers.haml +45 -0
- data/lib/rest-ftp-daemon/worker_pool.rb +66 -29
- data/rest-ftp-daemon.gemspec +9 -8
- data/rest-ftp-daemon.yml.sample +15 -4
- metadata +48 -30
data/lib/rest-ftp-daemon.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# Global libs
|
2
2
|
require 'rubygems'
|
3
3
|
require 'json'
|
4
|
-
require 'securerandom'
|
5
4
|
# require 'celluloid/autostart'
|
6
5
|
|
7
6
|
# My libs
|
7
|
+
require 'rest-ftp-daemon/constants'
|
8
8
|
require 'rest-ftp-daemon/config'
|
9
9
|
require 'rest-ftp-daemon/exceptions'
|
10
10
|
require 'rest-ftp-daemon/common'
|
11
|
+
require 'rest-ftp-daemon/helpers'
|
11
12
|
require 'rest-ftp-daemon/uri'
|
12
13
|
require 'rest-ftp-daemon/job_queue'
|
13
14
|
require 'rest-ftp-daemon/worker_pool'
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'grape'
|
3
|
-
SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB" ]
|
4
|
-
|
5
3
|
|
6
4
|
module RestFtpDaemon
|
7
5
|
module API
|
@@ -45,13 +43,6 @@ module RestFtpDaemon
|
|
45
43
|
# end
|
46
44
|
|
47
45
|
helpers do
|
48
|
-
def format_nice_bytes( number )
|
49
|
-
return "Ø" if number.nil? || number.zero?
|
50
|
-
index = ( Math.log( number ) / Math.log( 2 ) ).to_i / 10
|
51
|
-
converted = number.to_i / ( 1024 ** index )
|
52
|
-
"#{converted} #{SIZE_UNITS[index]}"
|
53
|
-
end
|
54
|
-
|
55
46
|
def api_error exception
|
56
47
|
{
|
57
48
|
:error => exception.class,
|
@@ -62,7 +53,7 @@ module RestFtpDaemon
|
|
62
53
|
end
|
63
54
|
|
64
55
|
def render name, values={}
|
65
|
-
template = File.read("#{
|
56
|
+
template = File.read("#{APP_LIBS}/views/#{name.to_s}.haml")
|
66
57
|
haml_engine = Haml::Engine.new(template)
|
67
58
|
haml_engine.render(binding, values)
|
68
59
|
end
|
@@ -6,22 +6,19 @@ module RestFtpDaemon
|
|
6
6
|
|
7
7
|
####### CLASS CONFIG
|
8
8
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
params do
|
13
|
-
optional :overwrite, type: Integer, default: false
|
14
|
-
end
|
9
|
+
# params do
|
10
|
+
# optional :overwrite, type: Integer, default: false
|
11
|
+
# end
|
15
12
|
|
16
13
|
|
17
14
|
####### INITIALIZATION
|
18
15
|
|
19
16
|
def initialize
|
20
|
-
|
17
|
+
#$last_worker_id = 0
|
21
18
|
|
22
19
|
# Check that Queue and Pool are available
|
23
20
|
raise RestFtpDaemon::MissingQueue unless defined? $queue
|
24
|
-
raise RestFtpDaemon::
|
21
|
+
raise RestFtpDaemon::MissingPool unless defined? $pool
|
25
22
|
|
26
23
|
super
|
27
24
|
end
|
@@ -31,6 +28,13 @@ module RestFtpDaemon
|
|
31
28
|
|
32
29
|
helpers do
|
33
30
|
|
31
|
+
def next_job_id
|
32
|
+
@@last_worker_id ||= 0
|
33
|
+
@@last_worker_id += 1
|
34
|
+
#Helpers.identifier(IDENT_JOB_BYTES)
|
35
|
+
#SecureRandom.hex(IDENT_JOB_BYTES)
|
36
|
+
end
|
37
|
+
|
34
38
|
def threads_with_id job_id
|
35
39
|
$threads.list.select do |thread|
|
36
40
|
next unless thread[:job].is_a? Job
|
@@ -39,7 +43,7 @@ module RestFtpDaemon
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def job_describe job_id
|
42
|
-
raise RestFtpDaemon::JobNotFound if ($queue.
|
46
|
+
raise RestFtpDaemon::JobNotFound if ($queue.all_size==0)
|
43
47
|
|
44
48
|
# Find job with this id
|
45
49
|
found = $queue.all.select { |job| job.id == job_id }.first
|
@@ -136,18 +140,16 @@ module RestFtpDaemon
|
|
136
140
|
begin
|
137
141
|
# Extract params
|
138
142
|
request.body.rewind
|
139
|
-
params = JSON.parse
|
143
|
+
params = JSON.parse(request.body.read, symbolize_names: true)
|
140
144
|
|
141
145
|
# Create a new job
|
142
|
-
job_id = $last_worker_id += 1
|
146
|
+
# job_id = $last_worker_id += 1
|
147
|
+
job_id = next_job_id
|
143
148
|
job = Job.new(job_id, params)
|
144
149
|
|
145
|
-
# And
|
150
|
+
# And push it to the queue
|
146
151
|
$queue.push job
|
147
152
|
|
148
|
-
# Later: start it asynchronously
|
149
|
-
#job.future.process
|
150
|
-
|
151
153
|
rescue JSON::ParserError => exception
|
152
154
|
status 406
|
153
155
|
api_error exception
|
@@ -165,9 +167,9 @@ module RestFtpDaemon
|
|
165
167
|
|
166
168
|
protected
|
167
169
|
|
168
|
-
def progname
|
169
|
-
|
170
|
-
end
|
170
|
+
# def progname
|
171
|
+
# "API::Jobs"
|
172
|
+
# end
|
171
173
|
|
172
174
|
end
|
173
175
|
end
|
@@ -10,11 +10,11 @@ module RestFtpDaemon
|
|
10
10
|
|
11
11
|
|
12
12
|
####### CLASS CONFIG
|
13
|
-
include RestFtpDaemon::API::Defaults
|
14
|
-
logger ActiveSupport::Logger.new(Settings.logs.api, 'daily') unless Settings.logs.api.nil?
|
15
|
-
#add_swagger_documentation
|
16
13
|
|
14
|
+
include RestFtpDaemon::API::Defaults
|
15
|
+
logger RestFtpDaemon::Logger.new(:api, "API")
|
17
16
|
mount RestFtpDaemon::API::Jobs => '/jobs'
|
17
|
+
#add_swagger_documentation
|
18
18
|
# mount RestFtpDaemon::API::Workers => '/workers'
|
19
19
|
|
20
20
|
|
@@ -22,7 +22,7 @@ module RestFtpDaemon
|
|
22
22
|
|
23
23
|
helpers do
|
24
24
|
def info message, level = 0
|
25
|
-
Root.logger.
|
25
|
+
Root.logger.info(message, level)
|
26
26
|
end
|
27
27
|
|
28
28
|
def job_list_by_status
|
@@ -32,7 +32,6 @@ module RestFtpDaemon
|
|
32
32
|
statuses[item.get_status] ||= 0
|
33
33
|
statuses[item.get_status] +=1
|
34
34
|
end
|
35
|
-
|
36
35
|
statuses
|
37
36
|
end
|
38
37
|
|
@@ -42,12 +41,9 @@ module RestFtpDaemon
|
|
42
41
|
####### INITIALIZATION
|
43
42
|
|
44
43
|
def initialize
|
45
|
-
$last_worker_id = 0
|
46
|
-
|
47
44
|
# Check that Queue and Pool are available
|
48
45
|
raise RestFtpDaemon::MissingQueue unless defined? $queue
|
49
46
|
raise RestFtpDaemon::MissingQueue unless defined? $pool
|
50
|
-
|
51
47
|
super
|
52
48
|
end
|
53
49
|
|
@@ -56,10 +52,7 @@ module RestFtpDaemon
|
|
56
52
|
|
57
53
|
# Server global status
|
58
54
|
get '/' do
|
59
|
-
|
60
|
-
@jobs_all = $queue.all
|
61
|
-
#@jobs_all_size = $queue.all_size
|
62
|
-
#@jobs_all = $queue.all_size
|
55
|
+
info "GET /"
|
63
56
|
|
64
57
|
# Initialize UsageWatch
|
65
58
|
Facter.loadfacts
|
@@ -68,24 +61,42 @@ module RestFtpDaemon
|
|
68
61
|
@info_ipaddr = Facter.value(:ipaddress)
|
69
62
|
@info_memfree = Facter.value(:memoryfree)
|
70
63
|
|
71
|
-
|
72
64
|
# Compute normalized load
|
73
|
-
# puts "info_procs: #{info_procs}"
|
74
65
|
if @info_procs.zero?
|
75
66
|
@info_norm = "N/A"
|
76
67
|
else
|
77
68
|
@info_norm = (100 * @info_load / @info_procs).round(1)
|
78
69
|
end
|
79
70
|
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@
|
71
|
+
# Jobs to display
|
72
|
+
all_jobs_in_queue = $queue.all
|
73
|
+
|
74
|
+
if params["only"].nil? || params["only"].blank?
|
75
|
+
@only = nil
|
76
|
+
else
|
77
|
+
@only = params["only"].to_sym
|
85
78
|
end
|
86
79
|
|
80
|
+
case @only
|
81
|
+
when nil
|
82
|
+
@jobs = all_jobs_in_queue
|
83
|
+
when :queue
|
84
|
+
@jobs = $queue.queued
|
85
|
+
else
|
86
|
+
@jobs = $queue.by_status (@only)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Count jobs for each status
|
90
|
+
@counts = {}
|
91
|
+
grouped = all_jobs_in_queue.group_by { |job| job.get(:status) }
|
92
|
+
grouped.each do |status, jobs|
|
93
|
+
@counts[status] = jobs.size
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get workers status
|
97
|
+
@gworker_statuses = $pool.get_worker_statuses
|
98
|
+
|
87
99
|
# Compile haml template
|
88
|
-
@name = "Test"
|
89
100
|
output = render :dashboard
|
90
101
|
|
91
102
|
# Send response
|
@@ -99,14 +110,14 @@ module RestFtpDaemon
|
|
99
110
|
|
100
111
|
# Server global status
|
101
112
|
get '/status' do
|
102
|
-
info "GET /"
|
113
|
+
info "GET /status"
|
103
114
|
status 200
|
104
115
|
return {
|
105
116
|
hostname: `hostname`.chomp,
|
106
|
-
version:
|
117
|
+
version: APP_VER,
|
107
118
|
config: Settings.to_hash,
|
108
|
-
started:
|
109
|
-
uptime: (Time.now -
|
119
|
+
started: APP_STARTED,
|
120
|
+
uptime: (Time.now - APP_STARTED).round(1),
|
110
121
|
status: job_list_by_status,
|
111
122
|
queue_size: $queue.all_size,
|
112
123
|
jobs_queued: $queue.queued.collect(&:id),
|
@@ -117,7 +128,8 @@ module RestFtpDaemon
|
|
117
128
|
|
118
129
|
# Server test
|
119
130
|
get '/debug' do
|
120
|
-
info "GET /debug
|
131
|
+
info "GET /debug"
|
132
|
+
|
121
133
|
begin
|
122
134
|
raise RestFtpDaemon::DummyException
|
123
135
|
rescue RestFtpDaemon::RestFtpDaemonException => exception
|
@@ -4,45 +4,10 @@ module RestFtpDaemon
|
|
4
4
|
|
5
5
|
protected
|
6
6
|
|
7
|
-
|
8
|
-
# Logger
|
9
|
-
@logger = ActiveSupport::Logger.new Settings.logs.workers, 'daily' unless Settings.logs.workers.nil?
|
10
|
-
end
|
11
|
-
|
12
|
-
def id
|
13
|
-
end
|
14
|
-
|
15
|
-
def progname
|
16
|
-
end
|
7
|
+
# FIXME: should be moved to class itself to get rid of this parent class
|
17
8
|
|
18
9
|
def info message, level = 0
|
19
|
-
|
20
|
-
# progname = "Worker [#{id}]" unless worker_id.nil?
|
21
|
-
@logger.add(Logger::INFO, "#{' '*(level+1)} #{message}", progname) unless @logger.nil?
|
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
|
-
|
10
|
+
@logger.info(message, level) unless @logger.nil?
|
46
11
|
end
|
47
12
|
|
48
13
|
end
|
@@ -1,26 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
APP_DEV = ARGV.include?("development") ? true : false
|
1
|
+
# Try to load Settingslogic
|
2
|
+
begin
|
3
|
+
require "settingslogic"
|
4
|
+
rescue LoadError
|
5
|
+
raise "config.rb warning: Settingslogic is needed to provide configuration values to the Gemspec file"
|
6
|
+
end
|
8
7
|
|
8
|
+
# Configuration class
|
9
9
|
class Settings < Settingslogic
|
10
10
|
# Read configuration
|
11
|
-
|
12
|
-
|
11
|
+
namespace (defined?(APP_ENV) ? APP_ENV : "production")
|
12
|
+
source ((File.exists? APP_CONF) ? APP_CONF : Hash.new)
|
13
13
|
suppress_errors true
|
14
14
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
self[:app_ver] = "0.72b"
|
20
|
-
self[:app_started] = Time.now
|
21
|
-
self[:default_trim_progname] = "18"
|
15
|
+
# Compute my PID filename
|
16
|
+
def pidfile
|
17
|
+
self["pidfile"] || "/tmp/#{APP_NAME}.port#{self['port'].to_s}.pid"
|
18
|
+
end
|
22
19
|
|
23
|
-
# Some defaults
|
24
|
-
self[:default_chunk_size] = "1000000"
|
25
|
-
self[:default_notify_size] = "10000000"
|
26
20
|
end
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Terrific constants
|
2
|
+
APP_NAME = "rest-ftp-daemon"
|
3
|
+
APP_CONF = "/etc/#{APP_NAME}.yml"
|
4
|
+
APP_VER = "0.85.2"
|
5
|
+
|
6
|
+
|
7
|
+
# Some global constants
|
8
|
+
IDENT_NOTIF_LEN = 4
|
9
|
+
IDENT_RANDOM_LEN = 8
|
10
|
+
|
11
|
+
|
12
|
+
# Some defaults
|
13
|
+
DEFAULT_CONNECT_TIMEOUT_SEC = 30
|
14
|
+
DEFAULT_UPDATE_EVERY_KB = 2048
|
15
|
+
DEFAULT_WORKERS = 1
|
16
|
+
DEFAULT_LOGS_PROGNAME_TRIM = 9
|
17
|
+
|
18
|
+
|
19
|
+
# Initialize markers
|
20
|
+
APP_STARTED = Time.now
|
21
|
+
APP_LIBS = File.dirname(__FILE__)
|
@@ -1,33 +1,20 @@
|
|
1
1
|
module RestFtpDaemon
|
2
2
|
|
3
|
-
class RestFtpDaemonException
|
4
|
-
|
5
|
-
class DummyException
|
6
|
-
|
7
|
-
class MissingQueue
|
8
|
-
class MissingPool
|
9
|
-
|
10
|
-
|
11
|
-
class
|
12
|
-
class
|
13
|
-
class
|
14
|
-
class
|
15
|
-
|
16
|
-
class
|
17
|
-
|
18
|
-
class
|
19
|
-
class JobSourceMissing < RestFtpDaemonException; end
|
20
|
-
class JobSourceNotFound < RestFtpDaemonException; end
|
21
|
-
class JobTargetMissing < RestFtpDaemonException; end
|
22
|
-
class JobTargetUnsupported < RestFtpDaemonException; end
|
23
|
-
class JobTargetUnparseable < RestFtpDaemonException; end
|
24
|
-
#class JobTargetPermission < RestFtpDaemonException; end
|
25
|
-
class JobTargetFileExists < RestFtpDaemonException; end
|
26
|
-
class JobPrerequisitesNotMet < RestFtpDaemonException; end
|
27
|
-
|
28
|
-
|
29
|
-
class NotificationMissingUrl < RestFtpDaemonException; end
|
30
|
-
class NotificationMissingSignal < RestFtpDaemonException; end
|
31
|
-
|
3
|
+
class RestFtpDaemonException < StandardError; end
|
4
|
+
|
5
|
+
class DummyException < RestFtpDaemonException; end
|
6
|
+
|
7
|
+
class MissingQueue < RestFtpDaemonException; end
|
8
|
+
class MissingPool < RestFtpDaemonException; end
|
9
|
+
|
10
|
+
class JobException < RestFtpDaemonException; end
|
11
|
+
class JobNotFound < RestFtpDaemonException; end
|
12
|
+
class JobAssertionFailed < RestFtpDaemonException; end
|
13
|
+
class JobMissingAttribute < RestFtpDaemonException; end
|
14
|
+
class JobSourceNotFound < RestFtpDaemonException; end
|
15
|
+
class JobTargetUnsupported < RestFtpDaemonException; end
|
16
|
+
class JobTargetUnparseable < RestFtpDaemonException; end
|
17
|
+
class JobTargetFileExists < RestFtpDaemonException; end
|
18
|
+
class JobTooManyOpenFiles < RestFtpDaemonException; end
|
32
19
|
|
33
20
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
|
4
|
+
module RestFtpDaemon
|
5
|
+
class Helpers
|
6
|
+
|
7
|
+
def self.format_bytes number, unit=""
|
8
|
+
return "Ø" if number.nil? || number.zero?
|
9
|
+
|
10
|
+
units = ["", "k", "M", "G", "T", "P" ]
|
11
|
+
index = ( Math.log( number ) / Math.log( 2 ) ).to_i / 10
|
12
|
+
converted = number.to_i / ( 1024 ** index )
|
13
|
+
"#{converted} #{units[index]}#{unit}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.identifier len
|
17
|
+
rand(36**len).to_s(36)
|
18
|
+
end
|
19
|
+
#SecureRandom.hex(IDENT_JOB_BYTES)
|
20
|
+
|
21
|
+
def self.tokenize(item)
|
22
|
+
"[#{item}]"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.local_port_used? port
|
26
|
+
ip = '0.0.0.0'
|
27
|
+
timeout = 1
|
28
|
+
begin
|
29
|
+
Timeout::timeout(timeout) do
|
30
|
+
begin
|
31
|
+
TCPSocket.new(ip, port).close
|
32
|
+
true
|
33
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
34
|
+
false
|
35
|
+
rescue Errno::EADDRNOTAVAIL
|
36
|
+
"Settings.local_port_used: Errno::EADDRNOTAVAIL"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
rescue Timeout::Error
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# def snakecase
|
45
|
+
# gsub(/::/, '/').
|
46
|
+
# gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
47
|
+
# gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
48
|
+
# tr('-', '_').
|
49
|
+
# gsub(/\s/, '_').
|
50
|
+
# gsub(/__+/, '_').
|
51
|
+
# downcase
|
52
|
+
# end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|