aspera-cli 4.14.0 → 4.16.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.
- 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
|