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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. 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: 3,
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
- quiet: true # by default no native ascp progress bar
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
- private_constant :DEFAULT_OPTIONS
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
- # start ascp transfer (non blocking), single or multi-session
33
- # job information added to @jobs
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 is aspera tags
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
- env_args: env_args # env vars and args to ascp (from transfer spec)
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
- xfer_job[:sessions].push(session)
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
- xfer_job[:sessions].push(this_session)
116
+ @sessions.push(this_session)
130
117
  end
131
118
  end
132
- Log.log.debug('started session thread(s)')
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
- @jobs.each do |_id, job|
148
- job[:sessions].each do |session|
149
- Log.log.debug{"join #{session[:thread]}"}
150
- session[:thread].join
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
- @jobs.clear
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
- raise 'env_args must be Hash' unless env_args.is_a?(Hash)
175
- raise 'session must be Hash' unless session.is_a?(Hash)
176
- # by default we assume an exception will be raised (for ensure block)
177
- exception_raised = true
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
- Log.log.debug{"env_args=#{env_args.inspect}"}
180
- # get location of ascp executable
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
- mgt_sock = TCPServer.new('127.0.0.1', 0)
188
- # clone arguments as we eed to modify with mgt port
189
- ascp_arguments = env_args[:args].clone
190
- # add management port on the selected local port
191
- ascp_arguments.unshift('-M', mgt_sock.addr[1].to_s)
192
- # start ascp in sub process
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
- # in parent, wait for connection to socket max 3 seconds
204
- Log.log.debug{"before accept for pid (#{ascp_pid})"}
205
- # init management socket
206
- ascp_mgt_io = nil
207
- Timeout.timeout(@options[:spawn_timeout_sec]) do
208
- ascp_mgt_io = mgt_sock.accept
209
- # management messages include file names which may be utf8
210
- # by default socket is US-ASCII
211
- # TODO: use same value as Encoding.default_external
212
- ascp_mgt_io.set_encoding(Encoding::UTF_8)
213
- end
214
- Log.log.debug{"after accept (#{ascp_mgt_io})"}
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
- # exact text for event, with \n
217
- current_event_text = ''
218
- # parsed event (hash)
219
- current_event_data = nil
220
- # this is the last full status
221
- last_status_event = nil
222
- # read management port
223
- loop do
224
- # TODO: timeout here ?
225
- line = ascp_mgt_io.gets
226
- # nil when ascp process exits
227
- break if line.nil?
228
- current_event_text += line
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 last_status_event.is_a?(Hash)
258
- case last_status_event['Type']
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
- Log.log.error{"code: #{last_status_event['Code']}"}
264
- if /bearer token/i.match?(last_status_event['Description'])
265
- Log.log.error('need to regenerate token'.red)
266
- if session[:token_regenerator].respond_to?(:refreshed_transfer_token)
267
- # regenerate token here, expired, or error on it
268
- # Note: in multi-session, each session will have a different one.
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
- # cannot resolve address
273
- # if last_status_event['Code'].to_i.eql?(14)
274
- # Log.log.warn{"host: #{}"}
275
- # end
276
- raise Fasp::Error.new(last_status_event['Description'], last_status_event['Code'].to_i)
277
- else # case
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 with code #{status.exitstatus}"
301
- # raise error only if there was not already an exception
302
- raise Fasp::Error, message unless exception_raised
303
- # else just debug, as main exception is already here
304
- Log.log.debug(message)
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
- # send command of management port to ascp session
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, session_index, data)
317
- job = @jobs[job_id]
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('FASPMGR 2')
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=nil)
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
- @jobs = {}
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