aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
@@ -1,17 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'English'
|
4
3
|
require 'aspera/fasp/agent_base'
|
5
4
|
require 'aspera/fasp/error'
|
6
5
|
require 'aspera/fasp/parameters'
|
7
6
|
require 'aspera/fasp/installation'
|
8
7
|
require 'aspera/fasp/resume_policy'
|
9
8
|
require 'aspera/fasp/transfer_spec'
|
9
|
+
require 'aspera/fasp/management'
|
10
10
|
require 'aspera/log'
|
11
|
+
require 'aspera/assert'
|
11
12
|
require 'socket'
|
12
|
-
require 'timeout'
|
13
13
|
require 'securerandom'
|
14
14
|
require 'shellwords'
|
15
|
+
require 'English'
|
15
16
|
|
16
17
|
module Aspera
|
17
18
|
module Fasp
|
@@ -19,24 +20,30 @@ module Aspera
|
|
19
20
|
class AgentDirect < Aspera::Fasp::AgentBase
|
20
21
|
# options for initialize (same as values in option transfer_info)
|
21
22
|
DEFAULT_OPTIONS = {
|
22
|
-
spawn_timeout_sec:
|
23
|
-
spawn_delay_sec: 2,
|
23
|
+
spawn_timeout_sec: 2,
|
24
|
+
spawn_delay_sec: 2, # optional delay to start between sessions
|
24
25
|
wss: true, # true: if both SSH and wss in ts: prefer wss
|
25
26
|
multi_incr_udp: true,
|
26
27
|
resume: {},
|
27
28
|
ascp_args: [],
|
28
|
-
|
29
|
+
check_ignore: nil, # callback with host,port
|
30
|
+
quiet: true, # by default no native ascp progress bar
|
31
|
+
trusted_certs: [] # list of files with trusted certificates (stores)
|
29
32
|
}.freeze
|
30
|
-
|
33
|
+
LISTEN_ADDRESS = '127.0.0.1'
|
34
|
+
LISTEN_AVAILABLE_PORT = 0 # 0 means any available port
|
35
|
+
# spellchecker: enable
|
36
|
+
private_constant :DEFAULT_OPTIONS, :LISTEN_ADDRESS
|
31
37
|
|
32
|
-
#
|
33
|
-
#
|
38
|
+
# method of Aspera::Fasp::AgentBase
|
39
|
+
# start ascp transfer(s) (non blocking), single or multi-session
|
40
|
+
# session information added to @sessions
|
34
41
|
# @param transfer_spec [Hash] aspera transfer specification
|
42
|
+
# @param token_regenerator [Object] object with method refreshed_transfer_token
|
35
43
|
def start_transfer(transfer_spec, token_regenerator: nil)
|
36
|
-
the_job_id = SecureRandom.uuid
|
37
44
|
# clone transfer spec because we modify it (first level keys)
|
38
45
|
transfer_spec = transfer_spec.clone
|
39
|
-
# if there
|
46
|
+
# if there are aspera tags
|
40
47
|
if transfer_spec['tags'].is_a?(Hash) && transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED].is_a?(Hash)
|
41
48
|
# TODO: what is this for ? only on local ascp ?
|
42
49
|
# NOTE: important: transfer id must be unique: generate random id
|
@@ -44,19 +51,10 @@ module Aspera
|
|
44
51
|
# all sessions in a multi-session transfer must have the same xfer_id (see admin manual)
|
45
52
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_id'] ||= SecureRandom.uuid
|
46
53
|
Log.log.debug{"xfer id=#{transfer_spec['xfer_id']}"}
|
47
|
-
# TODO: useful ? node only ?
|
54
|
+
# TODO: useful ? node only ? seems to be a timeout for retry in node
|
48
55
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_retry'] ||= 3600
|
49
56
|
end
|
50
|
-
Log.dump('ts', transfer_spec)
|
51
|
-
|
52
|
-
# add bypass keys when authentication is token and no auth is provided
|
53
|
-
if transfer_spec.key?('token') &&
|
54
|
-
!transfer_spec.key?('remote_password') &&
|
55
|
-
!transfer_spec.key?('EX_ssh_key_paths')
|
56
|
-
# transfer_spec['remote_password'] = Installation.instance.bypass_pass # not used: no passphrase
|
57
|
-
transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
|
58
|
-
end
|
59
|
-
|
57
|
+
Log.log.debug{Log.dump('ts', transfer_spec)}
|
60
58
|
# Compute this before using transfer spec because it potentially modifies the transfer spec
|
61
59
|
# (even if the var is not used in single session)
|
62
60
|
multi_session_info = nil
|
@@ -81,38 +79,24 @@ module Aspera
|
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
84
|
-
# compute known args
|
85
|
-
env_args = Parameters.new(transfer_spec, @options).ascp_args
|
86
|
-
|
87
|
-
# add fallback cert and key as arguments if needed
|
88
|
-
if ['1', 1, true, 'force'].include?(transfer_spec['http_fallback'])
|
89
|
-
env_args[:args].unshift('-Y', Installation.instance.path(:fallback_cert_privkey))
|
90
|
-
env_args[:args].unshift('-I', Installation.instance.path(:fallback_certificate))
|
91
|
-
end
|
92
|
-
|
93
|
-
env_args[:args].unshift('-q') if @options[:quiet]
|
94
|
-
|
95
|
-
# transfer job can be multi session
|
96
|
-
xfer_job = {
|
97
|
-
id: the_job_id,
|
98
|
-
sessions: [] # all sessions as below
|
99
|
-
}
|
100
|
-
|
101
82
|
# generic session information
|
102
83
|
session = {
|
84
|
+
id: nil, # SessionId from INIT message in mgt port
|
85
|
+
job_id: SecureRandom.uuid, # job id (regroup sessions)
|
86
|
+
ts: transfer_spec, # transfer spec
|
103
87
|
thread: nil, # Thread object monitoring management port, not nil when pushed to :sessions
|
104
88
|
error: nil, # exception if failed
|
105
89
|
io: nil, # management port server socket
|
106
|
-
id: nil, # SessionId from INIT message in mgt port
|
107
90
|
token_regenerator: token_regenerator, # regenerate bearer token with oauth
|
108
|
-
|
91
|
+
# env vars and args to ascp (from transfer spec)
|
92
|
+
env_args: Parameters.new(transfer_spec, @options).ascp_args
|
109
93
|
}
|
110
94
|
|
111
95
|
if multi_session_info.nil?
|
112
96
|
Log.log.debug('Starting single session thread')
|
113
97
|
# single session for transfer : simple
|
114
98
|
session[:thread] = Thread.new(session) {|s|transfer_thread_entry(s)}
|
115
|
-
|
99
|
+
@sessions.push(session)
|
116
100
|
else
|
117
101
|
Log.log.debug('Starting multi session threads')
|
118
102
|
1.upto(multi_session_info[:count]) do |i|
|
@@ -120,22 +104,19 @@ module Aspera
|
|
120
104
|
sleep(@options[:spawn_delay_sec]) unless i.eql?(1)
|
121
105
|
# do deep copy (each thread has its own copy because it is modified here below and in thread)
|
122
106
|
this_session = session.clone
|
107
|
+
this_session[:ts] = this_session[:ts].clone
|
123
108
|
this_session[:env_args] = this_session[:env_args].clone
|
124
109
|
this_session[:env_args][:args] = this_session[:env_args][:args].clone
|
110
|
+
# set multi session part
|
125
111
|
this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_info[:count]}")
|
126
112
|
# option: increment (default as per ascp manual) or not (cluster on other side ?)
|
127
113
|
this_session[:env_args][:args].unshift('-O', (multi_session_info[:udp_base] + i - 1).to_s) if @options[:multi_incr_udp]
|
114
|
+
# finally start the thread
|
128
115
|
this_session[:thread] = Thread.new(this_session) {|s|transfer_thread_entry(s)}
|
129
|
-
|
116
|
+
@sessions.push(this_session)
|
130
117
|
end
|
131
118
|
end
|
132
|
-
|
133
|
-
|
134
|
-
# add job to list of jobs
|
135
|
-
@jobs[the_job_id] = xfer_job
|
136
|
-
Log.log.debug{"jobs: #{@jobs.keys.count}"}
|
137
|
-
|
138
|
-
return the_job_id
|
119
|
+
return session[:job_id]
|
139
120
|
end # start_transfer
|
140
121
|
|
141
122
|
# wait for completion of all jobs started
|
@@ -144,16 +125,14 @@ module Aspera
|
|
144
125
|
Log.log.debug('wait_for_transfers_completion')
|
145
126
|
# set to non-nil to exit loop
|
146
127
|
result = []
|
147
|
-
@
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
result.push(session[:error] || :success)
|
152
|
-
end
|
128
|
+
@sessions.each do |session|
|
129
|
+
Log.log.debug{"join #{session[:thread]}"}
|
130
|
+
session[:thread].join
|
131
|
+
result.push(session[:error] || :success)
|
153
132
|
end
|
154
133
|
Log.log.debug('all transfers joined')
|
155
134
|
# since all are finished and we return the result, clear statuses
|
156
|
-
@
|
135
|
+
@sessions.clear
|
157
136
|
return result
|
158
137
|
end
|
159
138
|
|
@@ -162,34 +141,75 @@ module Aspera
|
|
162
141
|
Log.log.debug('fasp local shutdown')
|
163
142
|
end
|
164
143
|
|
144
|
+
# cspell:disable
|
145
|
+
# begin 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size
|
146
|
+
# progress 'Type' => 'STATS', 'Bytescont' => size
|
147
|
+
# end 'Type' => 'DONE'
|
148
|
+
# cspell:enable
|
149
|
+
|
150
|
+
# @param event management port event
|
151
|
+
def process_progress(event)
|
152
|
+
session_id = event['SessionId']
|
153
|
+
case event['Type']
|
154
|
+
when 'INIT'
|
155
|
+
@pre_calc_sent = false
|
156
|
+
@pre_calc_last_size = nil
|
157
|
+
notify_progress(session_id: session_id, type: :session_start)
|
158
|
+
when 'NOTIFICATION' # sent from remote
|
159
|
+
if event.key?('PreTransferBytes')
|
160
|
+
@pre_calc_sent = true
|
161
|
+
notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
|
162
|
+
end
|
163
|
+
when 'STATS' # during transfer
|
164
|
+
@pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
|
165
|
+
notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
|
166
|
+
when 'DONE', 'ERROR' # end of session
|
167
|
+
total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
|
168
|
+
if !@pre_calc_sent && !total_size.zero?
|
169
|
+
notify_progress(session_id: session_id, type: :session_size, info: total_size)
|
170
|
+
end
|
171
|
+
if @pre_calc_last_size != total_size
|
172
|
+
notify_progress(session_id: session_id, type: :transfer, info: total_size)
|
173
|
+
end
|
174
|
+
notify_progress(session_id: session_id, type: :end)
|
175
|
+
# cspell:disable
|
176
|
+
when 'SESSION'
|
177
|
+
when 'ARGSTOP'
|
178
|
+
when 'FILEERROR'
|
179
|
+
when 'STOP'
|
180
|
+
# cspell:enable
|
181
|
+
# stop event when one file is completed
|
182
|
+
else
|
183
|
+
Log.log.debug{"unknown event type #{event['Type']}"}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
165
187
|
# This is the low level method to start the "ascp" process
|
166
188
|
# currently, relies on command line arguments
|
167
189
|
# start ascp with management port.
|
168
190
|
# raises FaspError on error
|
169
191
|
# if there is a thread info: set and broadcast session id
|
192
|
+
# runs in separate thread
|
170
193
|
# @param env_args a hash containing :args :env :ascp_version
|
171
194
|
# @param session this session information
|
172
195
|
# could be private method
|
173
196
|
def start_transfer_with_args_env(env_args, session)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
197
|
+
assert_type(env_args, Hash)
|
198
|
+
assert_type(session, Hash)
|
199
|
+
Log.log.debug{"env_args=#{env_args.inspect}"}
|
200
|
+
notify_progress(session_id: nil, type: :pre_start, info: 'starting')
|
178
201
|
begin
|
179
|
-
|
180
|
-
|
181
|
-
ascp_path = @mutex.synchronize do
|
182
|
-
Fasp::Installation.instance.path(env_args[:ascp_version])
|
183
|
-
end
|
184
|
-
# (optional) check it exists
|
185
|
-
raise Fasp::Error, "no such file: #{ascp_path}" unless File.exist?(ascp_path)
|
202
|
+
# we use Socket directly, instead of TCPServer, s it gives access to lower level options
|
203
|
+
mgt_server_socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
186
204
|
# open an available (0) local TCP port as ascp management
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
#
|
205
|
+
# Socket.pack_sockaddr_in(LISTEN_AVAILABLE_PORT, LISTEN_ADDRESS)
|
206
|
+
mgt_server_socket.bind(Addrinfo.tcp(LISTEN_ADDRESS, LISTEN_AVAILABLE_PORT))
|
207
|
+
# clone arguments and add mgt port
|
208
|
+
ascp_arguments = ['-M', mgt_server_socket.local_address.ip_port.to_s].concat(env_args[:args])
|
209
|
+
# mgt_server_socket.addr[1]
|
210
|
+
# get location of ascp executable
|
211
|
+
ascp_path = Fasp::Installation.instance.path(env_args[:ascp_version])
|
212
|
+
# display ascp command line
|
193
213
|
Log.log.debug do
|
194
214
|
[
|
195
215
|
'execute:',
|
@@ -198,97 +218,63 @@ module Aspera
|
|
198
218
|
ascp_arguments.map{|a|Shellwords.shellescape(a)}
|
199
219
|
].flatten.join(' ')
|
200
220
|
end
|
201
|
-
# start process
|
202
|
-
ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments)
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
221
|
+
# start ascp in separate process
|
222
|
+
ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments, close_others: true)
|
223
|
+
Log.log.debug{"spawned pid #{ascp_pid}"}
|
224
|
+
notify_progress(session_id: nil, type: :pre_start, info: 'waiting for ascp')
|
225
|
+
mgt_server_socket.listen(1)
|
226
|
+
# TODO: timeout does not work when Process.spawn is used... until process exits, then it works
|
227
|
+
Log.log.debug{"before select, timeout: #{@options[:spawn_timeout_sec]}"}
|
228
|
+
readable, _, _ = IO.select([mgt_server_socket], nil, nil, @options[:spawn_timeout_sec])
|
229
|
+
Log.log.debug('after select, before accept')
|
230
|
+
assert(readable, exception_class: Fasp::Error){'timeout waiting mgt port connect (select not readable)'}
|
231
|
+
# There is a connection to accept
|
232
|
+
client_socket, _client_addrinfo = mgt_server_socket.accept
|
233
|
+
Log.log.debug('after accept')
|
234
|
+
ascp_mgt_io = client_socket.to_io
|
235
|
+
# management messages include file names which may be utf8
|
236
|
+
# by default socket is US-ASCII
|
237
|
+
# TODO: use same value as Encoding.default_external
|
238
|
+
ascp_mgt_io.set_encoding(Encoding::UTF_8)
|
215
239
|
session[:io] = ascp_mgt_io
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
line.chomp!
|
230
|
-
Log.log.debug{"line=[#{line}]"}
|
231
|
-
case line
|
232
|
-
when 'FASPMGR 2'
|
233
|
-
# begin event
|
234
|
-
current_event_data = {}
|
235
|
-
current_event_text = ''
|
236
|
-
when /^([^:]+): (.*)$/
|
237
|
-
# event field
|
238
|
-
current_event_data[Regexp.last_match(1)] = Regexp.last_match(2)
|
239
|
-
when ''
|
240
|
-
# empty line is separator to end event information
|
241
|
-
raise 'unexpected empty line' if current_event_data.nil?
|
242
|
-
current_event_data[AgentBase::LISTENER_SESSION_ID_B] = ascp_pid
|
243
|
-
notify_listeners(current_event_text, current_event_data)
|
244
|
-
case current_event_data['Type']
|
245
|
-
when 'INIT'
|
246
|
-
session[:id] = current_event_data['SessionId']
|
247
|
-
Log.log.debug{"session id: #{session[:id]}"}
|
248
|
-
when 'DONE', 'ERROR'
|
249
|
-
# TODO: check if this is always the last event
|
250
|
-
last_status_event = current_event_data
|
251
|
-
end # event type
|
252
|
-
else
|
253
|
-
raise "unexpected line:[#{line}]"
|
254
|
-
end # case
|
255
|
-
end # loop (process mgt port lines)
|
240
|
+
processor = Management.new
|
241
|
+
# read management port, until socket is closed (gets returns nil)
|
242
|
+
while (line = ascp_mgt_io.gets)
|
243
|
+
event = processor.process_line(line.chomp)
|
244
|
+
next unless event
|
245
|
+
# event is ready
|
246
|
+
Log.log.trace1{Log.dump(:management_port, event)}
|
247
|
+
# Log.log.trace1{"event: #{JSON.generate(Management.enhanced_event_format(event))}"}
|
248
|
+
process_progress(event)
|
249
|
+
Log.log.error((event['Description']).to_s) if event['Type'].eql?('FILEERROR') # cspell:disable-line
|
250
|
+
end
|
251
|
+
Log.log.debug('management io closed')
|
252
|
+
last_event = processor.last_event
|
256
253
|
# check that last status was received before process exit
|
257
|
-
if
|
258
|
-
case
|
259
|
-
when 'DONE'
|
260
|
-
# all went well
|
261
|
-
exception_raised = false
|
254
|
+
if last_event.is_a?(Hash)
|
255
|
+
case last_event['Type']
|
262
256
|
when 'ERROR'
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
|
270
|
-
end
|
257
|
+
if /bearer token/i.match?(last_event['Description']) &&
|
258
|
+
session[:token_regenerator].respond_to?(:refreshed_transfer_token)
|
259
|
+
# regenerate token here, expired, or error on it
|
260
|
+
# Note: in multi-session, each session will have a different one.
|
261
|
+
Log.log.warn('Regenerating token for transfer')
|
262
|
+
env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
|
271
263
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
raise
|
277
|
-
|
278
|
-
raise "unexpected last event type: #{last_status_event['Type']}"
|
279
|
-
end
|
280
|
-
else
|
281
|
-
exception_raised = false
|
282
|
-
Log.log.debug('no status read from ascp mgt port')
|
264
|
+
raise Fasp::Error.new(last_event['Description'], last_event['Code'].to_i)
|
265
|
+
when 'DONE'
|
266
|
+
nil
|
267
|
+
else
|
268
|
+
raise "unexpected last event type: #{last_event['Type']}"
|
269
|
+
end # case
|
283
270
|
end
|
284
271
|
rescue SystemCallError => e
|
285
272
|
# Process.spawn
|
286
273
|
raise Fasp::Error, e.message
|
287
|
-
rescue Timeout::Error
|
288
|
-
raise Fasp::Error, 'timeout waiting mgt port connect'
|
289
274
|
rescue Interrupt
|
290
275
|
raise Fasp::Error, 'transfer interrupted by user'
|
291
276
|
ensure
|
277
|
+
mgt_server_socket.close
|
292
278
|
# if ascp was successfully started
|
293
279
|
unless ascp_pid.nil?
|
294
280
|
# "wait" for process to avoid zombie
|
@@ -297,58 +283,62 @@ module Aspera
|
|
297
283
|
ascp_pid = nil
|
298
284
|
session.delete(:io)
|
299
285
|
if !status.success?
|
300
|
-
message = "ascp failed
|
301
|
-
# raise error only if there was not already an exception
|
302
|
-
raise Fasp::Error, message unless
|
303
|
-
# else
|
304
|
-
Log.log.
|
286
|
+
message = "ascp failed: #{status}"
|
287
|
+
# raise error only if there was not already an exception (ERROR_INFO)
|
288
|
+
raise Fasp::Error, message unless $ERROR_INFO
|
289
|
+
# else display this message also, as main exception is already here
|
290
|
+
Log.log.error(message)
|
305
291
|
end
|
306
292
|
end
|
307
293
|
end # begin-ensure
|
308
294
|
end # start_transfer_with_args_env
|
309
295
|
|
310
|
-
#
|
296
|
+
# @return [Array] list of sessions for a job
|
297
|
+
def sessions_by_job(job_id)
|
298
|
+
@sessions.select{|s| s[:job_id].eql?(job_id)}
|
299
|
+
end
|
300
|
+
|
301
|
+
# @return [Hash] session information
|
302
|
+
def session_by_id(id)
|
303
|
+
matches = @sessions.select{|s| s[:id].eql?(id)}
|
304
|
+
raise 'no such session' if matches.empty?
|
305
|
+
raise 'more than one session' if matches.length > 1
|
306
|
+
return matches.first
|
307
|
+
end
|
308
|
+
|
309
|
+
# send command of management port to ascp session (used in `asession)
|
311
310
|
# @param job_id identified transfer process
|
312
311
|
# @param session_index index of session (for multi session)
|
313
312
|
# @param data command on mgt port, examples:
|
314
313
|
# {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
315
314
|
# {'type'=>'DONE'}
|
316
|
-
def send_command(job_id,
|
317
|
-
|
318
|
-
raise 'no such job' if job.nil?
|
319
|
-
session = job[:sessions][session_index]
|
320
|
-
raise 'no such session' if session.nil?
|
315
|
+
def send_command(job_id, data)
|
316
|
+
session = session_by_id(job_id)
|
321
317
|
Log.log.debug{"command: #{data}"}
|
322
318
|
# build command
|
323
319
|
command = data
|
324
320
|
.keys
|
325
321
|
.map{|k|"#{k.capitalize}: #{data[k]}"}
|
326
|
-
.unshift(
|
322
|
+
.unshift(MGT_HEADER)
|
327
323
|
.push('', '')
|
328
324
|
.join("\n")
|
329
325
|
session[:io].puts(command)
|
330
326
|
end
|
327
|
+
attr_reader :sessions
|
331
328
|
|
332
329
|
private
|
333
330
|
|
334
331
|
# @param options : keys(symbol): see DEFAULT_OPTIONS
|
335
|
-
def initialize(options=
|
336
|
-
super()
|
332
|
+
def initialize(options={})
|
333
|
+
super(options)
|
334
|
+
# set default options and override if specified
|
335
|
+
@options = AgentBase.options(default: DEFAULT_OPTIONS, options: options)
|
336
|
+
Log.log.debug{Log.dump(:agent_options, @options)}
|
337
|
+
@resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
|
337
338
|
# all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
|
338
|
-
@
|
339
|
+
@sessions = []
|
339
340
|
# mutex protects global data accessed by threads
|
340
341
|
@mutex = Mutex.new
|
341
|
-
# set default options and override if specified
|
342
|
-
@options = DEFAULT_OPTIONS.dup
|
343
|
-
if !options.nil?
|
344
|
-
raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
|
345
|
-
options.each do |k, v|
|
346
|
-
raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.key?(k)
|
347
|
-
@options[k] = v
|
348
|
-
end
|
349
|
-
end
|
350
|
-
Log.log.debug{"local options= #{options}"}
|
351
|
-
@resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
|
352
342
|
end
|
353
343
|
|
354
344
|
# transfer thread entry
|