aspera-cli 4.0.0.pre1
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 +7 -0
- data/README.md +3592 -0
- data/bin/ascli +7 -0
- data/bin/asession +89 -0
- data/docs/Makefile +59 -0
- data/docs/README.erb.md +3012 -0
- data/docs/README.md +13 -0
- data/docs/diagrams.txt +49 -0
- data/docs/secrets.make +38 -0
- data/docs/test_env.conf +117 -0
- data/docs/transfer_spec.html +99 -0
- data/examples/aoc.rb +17 -0
- data/examples/proxy.pac +60 -0
- data/examples/transfer.rb +115 -0
- data/lib/aspera/api_detector.rb +60 -0
- data/lib/aspera/ascmd.rb +151 -0
- data/lib/aspera/ats_api.rb +43 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
- data/lib/aspera/cli/extended_value.rb +88 -0
- data/lib/aspera/cli/formater.rb +238 -0
- data/lib/aspera/cli/listener/line_dump.rb +17 -0
- data/lib/aspera/cli/listener/logger.rb +20 -0
- data/lib/aspera/cli/listener/progress.rb +52 -0
- data/lib/aspera/cli/listener/progress_multi.rb +91 -0
- data/lib/aspera/cli/main.rb +304 -0
- data/lib/aspera/cli/manager.rb +440 -0
- data/lib/aspera/cli/plugin.rb +90 -0
- data/lib/aspera/cli/plugins/alee.rb +24 -0
- data/lib/aspera/cli/plugins/ats.rb +231 -0
- data/lib/aspera/cli/plugins/bss.rb +71 -0
- data/lib/aspera/cli/plugins/config.rb +806 -0
- data/lib/aspera/cli/plugins/console.rb +62 -0
- data/lib/aspera/cli/plugins/cos.rb +106 -0
- data/lib/aspera/cli/plugins/faspex.rb +377 -0
- data/lib/aspera/cli/plugins/faspex5.rb +93 -0
- data/lib/aspera/cli/plugins/node.rb +438 -0
- data/lib/aspera/cli/plugins/oncloud.rb +937 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
- data/lib/aspera/cli/plugins/preview.rb +464 -0
- data/lib/aspera/cli/plugins/server.rb +216 -0
- data/lib/aspera/cli/plugins/shares.rb +63 -0
- data/lib/aspera/cli/plugins/shares2.rb +114 -0
- data/lib/aspera/cli/plugins/sync.rb +65 -0
- data/lib/aspera/cli/plugins/xnode.rb +115 -0
- data/lib/aspera/cli/transfer_agent.rb +251 -0
- data/lib/aspera/cli/version.rb +5 -0
- data/lib/aspera/colors.rb +39 -0
- data/lib/aspera/command_line_builder.rb +137 -0
- data/lib/aspera/fasp/aoc.rb +24 -0
- data/lib/aspera/fasp/connect.rb +99 -0
- data/lib/aspera/fasp/error.rb +21 -0
- data/lib/aspera/fasp/error_info.rb +60 -0
- data/lib/aspera/fasp/http_gw.rb +81 -0
- data/lib/aspera/fasp/installation.rb +240 -0
- data/lib/aspera/fasp/listener.rb +11 -0
- data/lib/aspera/fasp/local.rb +377 -0
- data/lib/aspera/fasp/manager.rb +69 -0
- data/lib/aspera/fasp/node.rb +88 -0
- data/lib/aspera/fasp/parameters.rb +235 -0
- data/lib/aspera/fasp/resume_policy.rb +76 -0
- data/lib/aspera/fasp/uri.rb +51 -0
- data/lib/aspera/faspex_gw.rb +196 -0
- data/lib/aspera/hash_ext.rb +28 -0
- data/lib/aspera/log.rb +80 -0
- data/lib/aspera/nagios.rb +71 -0
- data/lib/aspera/node.rb +14 -0
- data/lib/aspera/oauth.rb +319 -0
- data/lib/aspera/on_cloud.rb +421 -0
- data/lib/aspera/open_application.rb +72 -0
- data/lib/aspera/persistency_action_once.rb +42 -0
- data/lib/aspera/persistency_folder.rb +91 -0
- data/lib/aspera/preview/file_types.rb +300 -0
- data/lib/aspera/preview/generator.rb +258 -0
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +35 -0
- data/lib/aspera/preview/utils.rb +131 -0
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.erb.js +287 -0
- data/lib/aspera/proxy_auto_config.rb +34 -0
- data/lib/aspera/rest.rb +296 -0
- data/lib/aspera/rest_call_error.rb +13 -0
- data/lib/aspera/rest_error_analyzer.rb +98 -0
- data/lib/aspera/rest_errors_aspera.rb +58 -0
- data/lib/aspera/ssh.rb +53 -0
- data/lib/aspera/sync.rb +82 -0
- data/lib/aspera/temp_file_manager.rb +37 -0
- data/lib/aspera/uri_reader.rb +25 -0
- metadata +288 -0
@@ -0,0 +1,377 @@
|
|
1
|
+
#!/bin/echo this is a ruby class:
|
2
|
+
#
|
3
|
+
# FASP manager for Ruby
|
4
|
+
# Aspera 2016
|
5
|
+
# Laurent Martin
|
6
|
+
#
|
7
|
+
##############################################################################
|
8
|
+
require 'aspera/fasp/manager'
|
9
|
+
require 'aspera/fasp/error'
|
10
|
+
require 'aspera/fasp/parameters'
|
11
|
+
require 'aspera/fasp/installation'
|
12
|
+
require 'aspera/fasp/resume_policy'
|
13
|
+
require 'aspera/log'
|
14
|
+
require 'socket'
|
15
|
+
require 'timeout'
|
16
|
+
require 'securerandom'
|
17
|
+
|
18
|
+
module Aspera
|
19
|
+
module Fasp
|
20
|
+
# default transfer username for access key based transfers
|
21
|
+
ACCESS_KEY_TRANSFER_USER='xfer'
|
22
|
+
# executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
|
23
|
+
class Local < Manager
|
24
|
+
# set to false to keep ascp progress bar display (basically: removes ascp's option -q)
|
25
|
+
attr_accessor :quiet
|
26
|
+
# start FASP transfer based on transfer spec (hash table)
|
27
|
+
# note that it is asynchronous
|
28
|
+
def start_transfer(transfer_spec,options={})
|
29
|
+
raise "option: must be hash (or nil)" unless options.is_a?(Hash)
|
30
|
+
job_id=options[:job_id] || SecureRandom.uuid
|
31
|
+
# if there is aspera tags
|
32
|
+
if transfer_spec['tags'].is_a?(Hash) and transfer_spec['tags']['aspera'].is_a?(Hash)
|
33
|
+
# TODO: what is this for ? only on local ascp ?
|
34
|
+
# NOTE: important: transfer id must be unique: generate random id
|
35
|
+
# using a non unique id results in discard of tags in AoC, and a package is never finalized
|
36
|
+
transfer_spec['tags']['aspera']['xfer_id']||=SecureRandom.uuid
|
37
|
+
Log.log.debug("xfer id=#{transfer_spec['xfer_id']}")
|
38
|
+
# TODO: useful ? node only ?
|
39
|
+
transfer_spec['tags']['aspera']['xfer_retry']||=3600
|
40
|
+
end
|
41
|
+
Log.dump('ts',transfer_spec)
|
42
|
+
# add bypass keys when authentication is token and no auth is provided
|
43
|
+
if transfer_spec.has_key?('token') and
|
44
|
+
!transfer_spec.has_key?('remote_password') and
|
45
|
+
!transfer_spec.has_key?('EX_ssh_key_paths')
|
46
|
+
keys=Installation.instance.bypass_keys
|
47
|
+
transfer_spec['remote_password'] = keys.shift
|
48
|
+
transfer_spec['EX_ssh_key_paths'] = keys
|
49
|
+
end
|
50
|
+
|
51
|
+
# compute known args
|
52
|
+
env_args=Parameters.ts_to_env_args(transfer_spec,wss: @enable_wss)
|
53
|
+
|
54
|
+
# add fallback cert and key as arguments if needed
|
55
|
+
if ['1','force'].include?(transfer_spec['http_fallback'])
|
56
|
+
env_args[:args].unshift('-Y',Installation.instance.path(:fallback_key))
|
57
|
+
env_args[:args].unshift('-I',Installation.instance.path(:fallback_cert))
|
58
|
+
end
|
59
|
+
|
60
|
+
env_args[:args].unshift('-q') if @quiet
|
61
|
+
|
62
|
+
# transfer job can be multi session
|
63
|
+
xfer_job={
|
64
|
+
:id => job_id,
|
65
|
+
:sessions => []
|
66
|
+
}
|
67
|
+
|
68
|
+
# generic session information
|
69
|
+
session={
|
70
|
+
:state => :initial, # :initial, :started, :success, :failed
|
71
|
+
:env_args => env_args,
|
72
|
+
:resumer => options['resume_policy'] || @resume_policy,
|
73
|
+
:options => options
|
74
|
+
}
|
75
|
+
|
76
|
+
Log.log.debug("starting session thread(s)")
|
77
|
+
if !transfer_spec.has_key?('multi_session')
|
78
|
+
# single session for transfer : simple
|
79
|
+
session[:thread] = Thread.new(session) {|s|transfer_thread_entry(s)}
|
80
|
+
xfer_job[:sessions].push(session)
|
81
|
+
else
|
82
|
+
# default value overriden by fasp_port
|
83
|
+
multi_session_udp_port_base=33001
|
84
|
+
multi_session=transfer_spec['multi_session'].to_i
|
85
|
+
raise "multi_session(#{transfer_spec['multi_session']}) shall be integer > 1" unless multi_session >= 1
|
86
|
+
# managed here, so delete from transfer spec
|
87
|
+
transfer_spec.delete('multi_session')
|
88
|
+
# TODO: check if changing fasp(UDP) port is really necessary, not clear from doc
|
89
|
+
if transfer_spec.has_key?('fasp_port')
|
90
|
+
multi_session_udp_port_base=transfer_spec['fasp_port']
|
91
|
+
transfer_spec.delete('fasp_port')
|
92
|
+
end
|
93
|
+
1.upto(multi_session) do |i|
|
94
|
+
# do deep copy (each thread has its own copy because it is modified here below and in thread)
|
95
|
+
this_session=session.clone()
|
96
|
+
this_session[:env_args]=this_session[:env_args].clone()
|
97
|
+
this_session[:env_args][:args]=this_session[:env_args][:args].clone()
|
98
|
+
this_session[:env_args][:args].unshift("-C#{i}:#{multi_session}")
|
99
|
+
# necessary only if server is not linux, i.e. server does not support port re-use
|
100
|
+
this_session[:env_args][:args].unshift("-O","#{multi_session_udp_port_base+i-1}")
|
101
|
+
this_session[:thread] = Thread.new(this_session) {|s|transfer_thread_entry(s)}
|
102
|
+
xfer_job[:sessions].push(this_session)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
Log.log.debug("started session thread(s)")
|
106
|
+
|
107
|
+
# add job to list of jobs
|
108
|
+
@jobs[job_id]=xfer_job
|
109
|
+
|
110
|
+
Log.log.debug("jobs: #{@jobs.keys.count}")
|
111
|
+
return job_id
|
112
|
+
end # start_transfer
|
113
|
+
|
114
|
+
# wait for completion of all jobs started
|
115
|
+
# @return list of :success or error message
|
116
|
+
def wait_for_transfers_completion
|
117
|
+
Log.log.debug("wait_for_sessions: #{@jobs.values.inject(0){|m,j|m+j[:sessions].count}}")
|
118
|
+
@mutex.synchronize do
|
119
|
+
loop do
|
120
|
+
running=0
|
121
|
+
result=[]
|
122
|
+
@jobs.each do |id,job|
|
123
|
+
job[:sessions].each do |session|
|
124
|
+
case session[:state]
|
125
|
+
when :failed; result.push(session[:error])
|
126
|
+
when :success; result.push(:success)
|
127
|
+
else running+=1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if running.eql?(0)
|
132
|
+
# since all are finished and we return the result, clear statuses
|
133
|
+
@jobs.clear
|
134
|
+
return result
|
135
|
+
end
|
136
|
+
Log.log.debug("wait for completed: running: #{running}")
|
137
|
+
# wait for session termination
|
138
|
+
@cond_var.wait(@mutex)
|
139
|
+
end # loop
|
140
|
+
end # mutex
|
141
|
+
# never reach here
|
142
|
+
raise "internal error"
|
143
|
+
end
|
144
|
+
|
145
|
+
# terminates monitor thread
|
146
|
+
def shutdown
|
147
|
+
Log.log.debug("fasp local shutdown")
|
148
|
+
Log.log.debug("send signal to monitor")
|
149
|
+
# tell monitor to stop
|
150
|
+
@mutex.synchronize do
|
151
|
+
@monitor_stop=true
|
152
|
+
@cond_var.broadcast
|
153
|
+
end
|
154
|
+
# wait for thread termination
|
155
|
+
@monitor_thread.join
|
156
|
+
@monitor_thread=nil
|
157
|
+
Log.log.debug("joined monitor")
|
158
|
+
end
|
159
|
+
|
160
|
+
# This is the low level method to start FASP
|
161
|
+
# currently, relies on command line arguments
|
162
|
+
# start ascp with management port.
|
163
|
+
# raises FaspError on error
|
164
|
+
# if there is a thread info: set and broadcast session id
|
165
|
+
# @param env_args a hash containing :args :env :ascp_version
|
166
|
+
# cloud be private method
|
167
|
+
def start_transfer_with_args_env(env_args,session=nil)
|
168
|
+
begin
|
169
|
+
Log.log.debug("env_args=#{env_args.inspect}")
|
170
|
+
ascp_path=Fasp::Installation.instance.path(env_args[:ascp_version])
|
171
|
+
raise Fasp::Error.new("no such file: #{ascp_path}") unless File.exist?(ascp_path)
|
172
|
+
ascp_pid=nil
|
173
|
+
ascp_arguments=env_args[:args].clone
|
174
|
+
# open random local TCP port listening
|
175
|
+
mgt_sock = TCPServer.new('127.0.0.1',0 )
|
176
|
+
# add management port
|
177
|
+
ascp_arguments.unshift('-M', mgt_sock.addr[1].to_s)
|
178
|
+
# start ascp in sub process
|
179
|
+
Log.log.debug("execute: #{env_args[:env].map{|k,v| "#{k}=\"#{v}\""}.join(' ')} \"#{ascp_path}\" \"#{ascp_arguments.join('" "')}\"")
|
180
|
+
# start process
|
181
|
+
ascp_pid = Process.spawn(env_args[:env],[ascp_path,ascp_path],*ascp_arguments)
|
182
|
+
# in parent, wait for connection to socket max 3 seconds
|
183
|
+
Log.log.debug("before accept for pid (#{ascp_pid})")
|
184
|
+
ascp_mgt_io=nil
|
185
|
+
Timeout.timeout( 3 ) do
|
186
|
+
ascp_mgt_io = mgt_sock.accept
|
187
|
+
# management messages include file names which may be utf8
|
188
|
+
# by default socket is US-ASCII
|
189
|
+
# TODO: use same value as Encoding.default_external
|
190
|
+
ascp_mgt_io.set_encoding(Encoding::UTF_8)
|
191
|
+
end
|
192
|
+
Log.log.debug("after accept (#{ascp_mgt_io})")
|
193
|
+
|
194
|
+
unless session.nil?
|
195
|
+
@mutex.synchronize do
|
196
|
+
session[:io]=ascp_mgt_io
|
197
|
+
@cond_var.broadcast
|
198
|
+
end
|
199
|
+
end
|
200
|
+
# exact text for event, with \n
|
201
|
+
current_event_text=''
|
202
|
+
# parsed event (hash)
|
203
|
+
current_event_data=nil
|
204
|
+
|
205
|
+
# this is the last full status
|
206
|
+
last_status_event=nil
|
207
|
+
|
208
|
+
# read management port
|
209
|
+
loop do
|
210
|
+
# TODO: timeout here ?
|
211
|
+
line = ascp_mgt_io.gets
|
212
|
+
# nil when ascp process exits
|
213
|
+
break if line.nil?
|
214
|
+
current_event_text=current_event_text+line
|
215
|
+
line.chomp!
|
216
|
+
Log.log.debug("line=[#{line}]")
|
217
|
+
case line
|
218
|
+
when 'FASPMGR 2'
|
219
|
+
# begin event
|
220
|
+
current_event_data = Hash.new
|
221
|
+
current_event_text = ''
|
222
|
+
when /^([^:]+): (.*)$/
|
223
|
+
# event field
|
224
|
+
current_event_data[$1] = $2
|
225
|
+
when ''
|
226
|
+
# end event
|
227
|
+
raise "unexpected empty line" if current_event_data.nil?
|
228
|
+
current_event_data[Manager::LISTENER_SESSION_ID_B]=ascp_pid
|
229
|
+
notify_listeners(current_event_text,current_event_data)
|
230
|
+
# TODO: check if this is always the last event
|
231
|
+
case current_event_data['Type']
|
232
|
+
when 'DONE','ERROR'
|
233
|
+
last_status_event = current_event_data
|
234
|
+
when 'INIT'
|
235
|
+
unless session.nil?
|
236
|
+
@mutex.synchronize do
|
237
|
+
session[:state]=:started
|
238
|
+
session[:id]=current_event_data['SessionId']
|
239
|
+
Log.log.debug("session id: #{session[:id]}")
|
240
|
+
@cond_var.broadcast
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end # event type
|
244
|
+
else
|
245
|
+
raise "unexpected line:[#{line}]"
|
246
|
+
end # case
|
247
|
+
end # loop
|
248
|
+
# check that last status was received before process exit
|
249
|
+
raise "INTERNAL: nil last status" if last_status_event.nil?
|
250
|
+
case last_status_event['Type']
|
251
|
+
when 'DONE'
|
252
|
+
return
|
253
|
+
when 'ERROR'
|
254
|
+
Log.log.error("code: #{last_status_event['Code']}")
|
255
|
+
if last_status_event['Description'] =~ /bearer token/i
|
256
|
+
Log.log.error("need to regenerate token".red)
|
257
|
+
if !session.nil? and session[:options].is_a?(Hash) and session[:options].has_key?(:regenerate_token)
|
258
|
+
# regenerate token here, expired, or error on it
|
259
|
+
env_args[:env]['ASPERA_SCP_TOKEN']=session[:options][:regenerate_token].call(true)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
raise Fasp::Error.new(last_status_event['Description'],last_status_event['Code'].to_i)
|
263
|
+
else
|
264
|
+
raise "INTERNAL ERROR: unexpected last event"
|
265
|
+
end
|
266
|
+
rescue SystemCallError => e
|
267
|
+
# Process.spawn
|
268
|
+
raise Fasp::Error.new(e.message)
|
269
|
+
rescue Timeout::Error => e
|
270
|
+
raise Fasp::Error.new('timeout waiting mgt port connect')
|
271
|
+
rescue Interrupt => e
|
272
|
+
raise Fasp::Error.new('transfer interrupted by user')
|
273
|
+
ensure
|
274
|
+
# ensure there is no ascp left running
|
275
|
+
unless ascp_pid.nil?
|
276
|
+
begin
|
277
|
+
Process.kill('INT',ascp_pid)
|
278
|
+
rescue
|
279
|
+
end
|
280
|
+
# avoid zombie
|
281
|
+
Process.wait(ascp_pid)
|
282
|
+
ascp_pid=nil
|
283
|
+
session.delete(:io)
|
284
|
+
end
|
285
|
+
end # begin-ensure
|
286
|
+
end # start_transfer_with_args_env
|
287
|
+
|
288
|
+
# send command on mgt port, examples:
|
289
|
+
# {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
290
|
+
# {'type'=>'DONE'}
|
291
|
+
def send_command(job_id,session_index,data)
|
292
|
+
@mutex.synchronize do
|
293
|
+
job=@jobs[job_id]
|
294
|
+
raise "no such job" if job.nil?
|
295
|
+
session=job[:sessions][session_index]
|
296
|
+
raise "no such session" if session.nil?
|
297
|
+
Log.log.debug("command: #{data}")
|
298
|
+
command=data.
|
299
|
+
keys.
|
300
|
+
map{|k|"#{k.capitalize}: #{data[k]}"}.
|
301
|
+
unshift('FASPMGR 2').
|
302
|
+
push('','').
|
303
|
+
join("\n")
|
304
|
+
session[:io].puts(command)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def initialize(agent_options=nil)
|
311
|
+
agent_options||={}
|
312
|
+
super()
|
313
|
+
# by default no interactive progress bar
|
314
|
+
@quiet=true
|
315
|
+
# shared data between transfer threads and others: protected by mutex, CV on change
|
316
|
+
@jobs={}
|
317
|
+
# mutex protects jobs data
|
318
|
+
@mutex=Mutex.new
|
319
|
+
# cond var is waited or broadcast on jobs data change
|
320
|
+
@cond_var=ConditionVariable.new
|
321
|
+
# must be set before starting monitor, set to false to stop thread. also shared and protected by mutex
|
322
|
+
@monitor_stop=false
|
323
|
+
@monitor_thread=Thread.new{monitor_thread_entry}
|
324
|
+
@resume_policy=ResumePolicy.new(agent_options)
|
325
|
+
@enable_wss = agent_options[:wss] || false
|
326
|
+
end
|
327
|
+
|
328
|
+
# transfer thread entry
|
329
|
+
# implements resumable transfer
|
330
|
+
# TODO: extract resume algorithm in a specific object
|
331
|
+
def transfer_thread_entry(session)
|
332
|
+
begin
|
333
|
+
# set name for logging
|
334
|
+
Thread.current[:name]="transfer"
|
335
|
+
# update state once in thread
|
336
|
+
session[:state]=:started
|
337
|
+
Log.log.debug("ENTER (#{Thread.current[:name]})")
|
338
|
+
# start transfer with selected resumer policy
|
339
|
+
session[:resumer].process do
|
340
|
+
start_transfer_with_args_env(session[:env_args],session)
|
341
|
+
end
|
342
|
+
Log.log.debug('transfer ok'.bg_green)
|
343
|
+
session[:state]=:success
|
344
|
+
rescue => e
|
345
|
+
session[:state]=:failed
|
346
|
+
session[:error]=e
|
347
|
+
Log.log.error("#{e.class}:\n#{e.message}:\n#{e.backtrace.join("\n")}".red) if Log.instance.level.eql?(:debug)
|
348
|
+
ensure
|
349
|
+
@mutex.synchronize do
|
350
|
+
# ensure id is set to unblock start procedure
|
351
|
+
session[:id]||=nil
|
352
|
+
@cond_var.broadcast
|
353
|
+
end
|
354
|
+
end
|
355
|
+
Log.log.debug("EXIT (#{Thread.current[:name]})")
|
356
|
+
end
|
357
|
+
|
358
|
+
# main thread method for monitor
|
359
|
+
# currently: just joins started threads
|
360
|
+
def monitor_thread_entry
|
361
|
+
Thread.current[:name]="monitor"
|
362
|
+
@mutex.synchronize do
|
363
|
+
until @monitor_stop do
|
364
|
+
# wait for session termination
|
365
|
+
@cond_var.wait(@mutex)
|
366
|
+
@jobs.values do |job|
|
367
|
+
job[:sessions].each do |session|
|
368
|
+
session[:thread].join if [:success,:failed].include?(session[:state])
|
369
|
+
end # sessions
|
370
|
+
end # jobs
|
371
|
+
end # monitor run
|
372
|
+
end # sync
|
373
|
+
Log.log.debug("EXIT (#{Thread.current[:name]})")
|
374
|
+
end # monitor_thread_entry
|
375
|
+
end # Local
|
376
|
+
end
|
377
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Aspera
|
2
|
+
module Fasp
|
3
|
+
# Base class for FASP transfer agents
|
4
|
+
# sub classes shall implement start_transfer and shutdown
|
5
|
+
class Manager
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# fields description for JSON generation
|
10
|
+
IntegerFields=['Bytescont','FaspFileArgIndex','StartByte','Rate','MinRate','Port','Priority','RateCap','MinRateCap','TCPPort','CreatePolicy','TimePolicy','DatagramSize','XoptFlags','VLinkVersion','PeerVLinkVersion','DSPipelineDepth','PeerDSPipelineDepth','ReadBlockSize','WriteBlockSize','ClusterNumNodes','ClusterNodeId','Size','Written','Loss','FileBytes','PreTransferBytes','TransferBytes','PMTU','Elapsedusec','ArgScansAttempted','ArgScansCompleted','PathScansAttempted','FileScansCompleted','TransfersAttempted','TransfersPassed','Delay']
|
11
|
+
BooleanFields=['Encryption','Remote','RateLock','MinRateLock','PolicyLock','FilesEncrypt','FilesDecrypt','VLinkLocalEnabled','VLinkRemoteEnabled','MoveRange','Keepalive','TestLogin','UseProxy','Precalc','RTTAutocorrect']
|
12
|
+
ExpectedMethod=[:text,:struct,:enhanced]
|
13
|
+
|
14
|
+
# translates legacy event into enhanced (JSON) event
|
15
|
+
def enhanced_event_format(event)
|
16
|
+
return event.keys.inject({}) do |h,e|
|
17
|
+
# capital_to_snake_case
|
18
|
+
new_name=e.
|
19
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
20
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
21
|
+
gsub(/([a-z\d])(usec)$/,'\1_\2').
|
22
|
+
downcase
|
23
|
+
value=event[e]
|
24
|
+
value=value.to_i if IntegerFields.include?(e)
|
25
|
+
value=value.eql?('Yes') ? true : false if BooleanFields.include?(e)
|
26
|
+
h[new_name]=value
|
27
|
+
h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@listeners=[]
|
33
|
+
end
|
34
|
+
|
35
|
+
def notify_listeners(current_event_text,current_event_data)
|
36
|
+
Log.log.debug("send event to listeners")
|
37
|
+
enhanced_event=nil
|
38
|
+
@listeners.each do |listener|
|
39
|
+
listener.send(:event_text,current_event_text) if listener.respond_to?(:event_text)
|
40
|
+
listener.send(:event_struct,current_event_data) if listener.respond_to?(:event_struct)
|
41
|
+
if listener.respond_to?(:event_enhanced)
|
42
|
+
enhanced_event=enhanced_event_format(current_event_data) if enhanced_event.nil?
|
43
|
+
listener.send(:event_enhanced,enhanced_event)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end # notify_listeners
|
47
|
+
|
48
|
+
public
|
49
|
+
LISTENER_SESSION_ID_B='ListenerSessionId'
|
50
|
+
LISTENER_SESSION_ID_S='listener_session_id'
|
51
|
+
|
52
|
+
# listener receives events
|
53
|
+
def add_listener(listener)
|
54
|
+
raise "expect one of #{ExpectedMethod}" if ExpectedMethod.inject(0){|m,e|m+=listener.respond_to?("event_#{e}")?1:0;m}.eql?(0)
|
55
|
+
@listeners.push(listener)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# the following methods must be implemented by subclass:
|
60
|
+
# start_transfer(transfer_spec,options) : start and wait for completion
|
61
|
+
# wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
|
62
|
+
# optional: shutdown
|
63
|
+
def self.validate_status_list(statuses)
|
64
|
+
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
65
|
+
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|