aspera-cli 4.2.1 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1580 -946
  3. data/bin/ascli +1 -1
  4. data/bin/asession +3 -5
  5. data/docs/Makefile +8 -11
  6. data/docs/README.erb.md +1521 -829
  7. data/docs/doc_tools.rb +58 -0
  8. data/docs/test_env.conf +3 -1
  9. data/examples/faspex4.rb +28 -19
  10. data/examples/transfer.rb +2 -2
  11. data/lib/aspera/aoc.rb +157 -134
  12. data/lib/aspera/cli/listener/progress_multi.rb +5 -5
  13. data/lib/aspera/cli/main.rb +106 -48
  14. data/lib/aspera/cli/manager.rb +19 -20
  15. data/lib/aspera/cli/plugin.rb +22 -7
  16. data/lib/aspera/cli/plugins/aoc.rb +260 -208
  17. data/lib/aspera/cli/plugins/ats.rb +11 -10
  18. data/lib/aspera/cli/plugins/bss.rb +2 -2
  19. data/lib/aspera/cli/plugins/config.rb +360 -189
  20. data/lib/aspera/cli/plugins/faspex.rb +119 -56
  21. data/lib/aspera/cli/plugins/faspex5.rb +32 -17
  22. data/lib/aspera/cli/plugins/node.rb +72 -31
  23. data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
  24. data/lib/aspera/cli/plugins/preview.rb +94 -68
  25. data/lib/aspera/cli/plugins/server.rb +16 -5
  26. data/lib/aspera/cli/plugins/shares.rb +17 -0
  27. data/lib/aspera/cli/transfer_agent.rb +64 -82
  28. data/lib/aspera/cli/version.rb +1 -1
  29. data/lib/aspera/command_line_builder.rb +48 -31
  30. data/lib/aspera/cos_node.rb +4 -3
  31. data/lib/aspera/environment.rb +4 -4
  32. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
  33. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
  34. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +42 -38
  35. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +50 -29
  36. data/lib/aspera/fasp/{node.rb → agent_node.rb} +43 -4
  37. data/lib/aspera/fasp/agent_trsdk.rb +106 -0
  38. data/lib/aspera/fasp/default.rb +17 -0
  39. data/lib/aspera/fasp/installation.rb +64 -48
  40. data/lib/aspera/fasp/parameters.rb +78 -91
  41. data/lib/aspera/fasp/parameters.yaml +531 -0
  42. data/lib/aspera/fasp/uri.rb +1 -1
  43. data/lib/aspera/faspex_gw.rb +12 -11
  44. data/lib/aspera/id_generator.rb +22 -0
  45. data/lib/aspera/keychain/encrypted_hash.rb +120 -0
  46. data/lib/aspera/keychain/macos_security.rb +94 -0
  47. data/lib/aspera/log.rb +45 -32
  48. data/lib/aspera/node.rb +9 -4
  49. data/lib/aspera/oauth.rb +116 -100
  50. data/lib/aspera/persistency_action_once.rb +11 -7
  51. data/lib/aspera/persistency_folder.rb +6 -26
  52. data/lib/aspera/rest.rb +66 -50
  53. data/lib/aspera/sync.rb +40 -35
  54. data/lib/aspera/timer_limiter.rb +22 -0
  55. metadata +86 -29
  56. data/docs/transfer_spec.html +0 -99
  57. data/lib/aspera/api_detector.rb +0 -60
  58. data/lib/aspera/fasp/aoc.rb +0 -24
  59. data/lib/aspera/secrets.rb +0 -20
@@ -4,21 +4,45 @@ module Aspera
4
4
  # process_param is called repeatedly with all known parameters
5
5
  # add_env_args is called to get resulting param list and env var (also checks that all params were used)
6
6
  class CommandLineBuilder
7
+ # transform yes/no to trye/false
8
+ def self.yes_to_true(value)
9
+ case value
10
+ when 'yes'; return true
11
+ when 'no'; return false
12
+ end
13
+ raise "unsupported value: #{value}"
14
+ end
7
15
 
8
- private
9
- # default value for command line based on option name
10
- def switch_name(param_name,options)
11
- return options[:option_switch] if options.has_key?(:option_switch)
12
- return '--'+param_name.to_s.gsub('_','-')
16
+ # Called by provider of definition before constructor of this class so that params_definition has all mandatory fields
17
+ def self.normalize_description(d)
18
+ d.each do |param_name,options|
19
+ raise "Expecting Hash, but have #{options.class} in #{param_name}" unless options.is_a?(Hash)
20
+ #options[:accepted_types]=:bool if options[:cltype].eql?(:envvar) and !options.has_key?(:accepted_types)
21
+ # by default : not mandatory
22
+ options[:mandatory]||=false
23
+ options[:desc]||=''
24
+ # by default : string, unless it's without arg
25
+ if ! options.has_key?(:accepted_types)
26
+ options[:accepted_types]=options[:cltype].eql?(:opt_without_arg) ? :bool : :string
27
+ end
28
+ # single type is placed in array
29
+ options[:accepted_types]=[options[:accepted_types]] unless options[:accepted_types].is_a?(Array)
30
+ if !options.has_key?(:option_switch) and options.has_key?(:cltype) and [:opt_without_arg,:opt_with_arg].include?(options[:cltype])
31
+ options[:option_switch]='--'+param_name.to_s.gsub('_','-')
32
+ end
33
+ end
13
34
  end
14
35
 
36
+ private
37
+
38
+ # clvarname : command line variable name
15
39
  def env_name(param_name,options)
16
- return options[:variable]
40
+ return options[:clvarname]
17
41
  end
18
42
 
19
43
  public
20
44
 
21
- BOOLEAN_CLASSES=[TrueClass,FalseClass]
45
+ attr_reader :params_definition
22
46
 
23
47
  # @param param_hash
24
48
  def initialize(param_hash,params_definition)
@@ -44,15 +68,6 @@ module Aspera
44
68
  return nil
45
69
  end
46
70
 
47
- # transform yes/no to trye/false
48
- def self.yes_to_true(value)
49
- case value
50
- when 'yes'; return true
51
- when 'no'; return false
52
- end
53
- raise "unsupported value: #{value}"
54
- end
55
-
56
71
  # add options directly to ascp command line
57
72
  def add_command_line_options(options)
58
73
  return if options.nil?
@@ -71,24 +86,25 @@ module Aspera
71
86
  # @param options : options for type
72
87
  def process_param(param_name,action=nil)
73
88
  options=@params_definition[param_name]
74
- action=options[:type] if action.nil?
89
+ action=options[:cltype] if action.nil?
75
90
  # should not happen
76
91
  raise "Internal error: ask processing of param #{param_name}" if options.nil?
77
- # by default : not mandatory
78
- options[:mandatory]||=false
79
- if options.has_key?(:accepted_types)
80
- # single type is placed in array
81
- options[:accepted_types]=[options[:accepted_types]] unless options[:accepted_types].is_a?(Array)
82
- else
83
- # by default : string, unless it's without arg
84
- options[:accepted_types]=action.eql?(:opt_without_arg) ? BOOLEAN_CLASSES : [String]
85
- end
86
92
  # check mandatory parameter (nil is valid value)
87
93
  raise Fasp::Error.new("mandatory parameter: #{param_name}") if options[:mandatory] and !@param_hash.has_key?(param_name)
88
94
  parameter_value=@param_hash[param_name]
89
- parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
95
+ #parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
96
+ expected_classes=options[:accepted_types].map do |s|
97
+ case s
98
+ when :string; String
99
+ when :array; Array
100
+ when :hash; Hash
101
+ when :int; Integer
102
+ when :bool; [TrueClass,FalseClass]
103
+ else raise "INTERNAL: unexpected value: #{s}"
104
+ end
105
+ end.flatten
90
106
  # check provided type
91
- raise Fasp::Error.new("#{param_name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, ") unless parameter_value.nil? or options[:accepted_types].inject(false){|m,v|m or parameter_value.is_a?(v)}
107
+ raise Fasp::Error.new("#{param_name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, ") unless parameter_value.nil? or expected_classes.include?(parameter_value.class)
92
108
  @used_param_names.push(param_name) unless action.eql?(:defer)
93
109
 
94
110
  # process only non-nil values
@@ -102,7 +118,8 @@ module Aspera
102
118
  end
103
119
  raise "unsupported value: #{parameter_value}" unless options[:accepted_values].nil? or options[:accepted_values].include?(parameter_value)
104
120
  if options[:encode]
105
- newvalue=options[:encode].call(parameter_value)
121
+ # :encode has name of class with encoding method
122
+ newvalue=Kernel.const_get(options[:encode]).send("encode_#{param_name}",parameter_value)
106
123
  raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}") if newvalue.nil?
107
124
  parameter_value=newvalue
108
125
  end
@@ -123,12 +140,12 @@ module Aspera
123
140
  else raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}")
124
141
  end
125
142
  add_param=!add_param if options[:add_on_false]
126
- add_command_line_options([switch_name(param_name,options)]) if add_param
143
+ add_command_line_options([options[:option_switch]]) if add_param
127
144
  when :opt_with_arg # transform into command line option with value
128
145
  #parameter_value=parameter_value.to_s if parameter_value.is_a?(Integer)
129
146
  parameter_value=[parameter_value] unless parameter_value.is_a?(Array)
130
147
  # if transfer_spec value is an array, applies option many times
131
- parameter_value.each{|v|add_command_line_options([switch_name(param_name,options),v])}
148
+ parameter_value.each{|v|add_command_line_options([options[:option_switch],v])}
132
149
  else
133
150
  raise "Error"
134
151
  end
@@ -6,6 +6,7 @@ module Aspera
6
6
  class CosNode < Rest
7
7
  attr_reader :add_ts
8
8
  IBM_CLOUD_TOKEN_URL='https://iam.cloud.ibm.com/identity'
9
+ TOKEN_FIELD='delegated_refresh_token'
9
10
  def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url=IBM_CLOUD_TOKEN_URL)
10
11
  @auth_url=auth_url
11
12
  @api_key=api_key
@@ -32,7 +33,7 @@ module Aspera
32
33
  # prepare transfer spec addition
33
34
  @add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>{
34
35
  'type' => 'token',
35
- 'token' => {'delegated_refresh_token'=>nil}
36
+ 'token' => {TOKEN_FIELD=>nil}
36
37
  }}}}}
37
38
  generate_token
38
39
  end
@@ -45,10 +46,10 @@ module Aspera
45
46
  :base_url => @auth_url,
46
47
  :grant => :delegated_refresh,
47
48
  :api_key => @api_key,
48
- :token_field=> 'delegated_refresh_token'
49
+ :token_field=> TOKEN_FIELD
49
50
  })
50
51
  # get delagated token to be placed in rest call header and in transfer tags
51
- @add_ts['tags']['aspera']['node']['storage_credentials']['token']['delegated_refresh_token']=delegated_oauth.get_authorization().gsub(/^Bearer /,'')
52
+ @add_ts['tags']['aspera']['node']['storage_credentials']['token'][TOKEN_FIELD]=delegated_oauth.get_authorization().gsub(/^Bearer /,'')
52
53
  @params[:headers]={'X-Aspera-Storage-Credentials'=>JSON.generate(@add_ts['tags']['aspera']['node']['storage_credentials'])}
53
54
  end
54
55
  end
@@ -33,12 +33,12 @@ module Aspera
33
33
  def self.cpu
34
34
  case RbConfig::CONFIG['host_cpu']
35
35
  when /x86_64/,/x64/
36
- return :x86_64
36
+ return CPU_X86_64
37
37
  when /powerpc/
38
- return :ppc64le if os.eql?(OS_LINUX)
39
- return :ppc64
38
+ return CPU_PPC64LE if os.eql?(OS_LINUX)
39
+ return CPU_PPC64
40
40
  when /s390/
41
- return :s390
41
+ return CPU_S390
42
42
  else # other
43
43
  raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
44
44
  end
@@ -2,7 +2,7 @@ module Aspera
2
2
  module Fasp
3
3
  # Base class for FASP transfer agents
4
4
  # sub classes shall implement start_transfer and shutdown
5
- class Manager
5
+ class AgentBase
6
6
 
7
7
  private
8
8
 
@@ -68,17 +68,18 @@ module Aspera
68
68
  self
69
69
  end
70
70
 
71
- # the following methods must be implemented by subclass:
72
- # start_transfer(transfer_spec,options) : start and wait for completion
73
- # wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
74
- # optional: shutdown
75
-
76
71
  # This checks the validity of the value returned by wait_for_transfers_completion
77
72
  # it must be a list of :success or exception
78
73
  def self.validate_status_list(statuses)
79
74
  raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
80
75
  raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
81
76
  end
77
+
78
+ # the following methods must be implemented by subclass:
79
+ # start_transfer(transfer_spec,options) : start and wait for completion
80
+ # wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
81
+ # optional: shutdown
82
+
82
83
  end
83
84
  end
84
85
  end
@@ -1,4 +1,4 @@
1
- require 'aspera/fasp/manager'
1
+ require 'aspera/fasp/agent_base'
2
2
  require 'aspera/rest'
3
3
  require 'aspera/open_application'
4
4
  require 'securerandom'
@@ -6,19 +6,15 @@ require 'tty-spinner'
6
6
 
7
7
  module Aspera
8
8
  module Fasp
9
- class Connect < Manager
9
+ class AgentConnect < AgentBase
10
10
  MAX_CONNECT_START_RETRY=3
11
11
  SLEEP_SEC_BETWEEN_RETRY=2
12
12
  private_constant :MAX_CONNECT_START_RETRY,:SLEEP_SEC_BETWEEN_RETRY
13
- def initialize
14
- super
13
+ def initialize(options)
14
+ super()
15
15
  @connect_settings={
16
16
  'app_id' => SecureRandom.uuid
17
17
  }
18
- # TODO: start here and create monitor
19
- end
20
-
21
- def start_transfer(transfer_spec,options=nil)
22
18
  raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
23
19
  trynumber=0
24
20
  begin
@@ -26,6 +22,7 @@ module Aspera
26
22
  Log.log.debug("found: #{connect_url}")
27
23
  @connect_api=Rest.new({base_url: "#{connect_url}/v5/connect",headers: {'Origin'=>Rest.user_agent}}) # could use v6 also now
28
24
  cinfo=@connect_api.read('info/version')[:data]
25
+ Log.dump(:connect_version,cinfo)
29
26
  rescue => e # Errno::ECONNREFUSED
30
27
  raise StandardError,"Unable to start connect after #{trynumber} try" if trynumber >= MAX_CONNECT_START_RETRY
31
28
  Log.log.warn("connect is not started. Retry ##{trynumber}, err=#{e}")
@@ -37,6 +34,9 @@ module Aspera
37
34
  sleep(SLEEP_SEC_BETWEEN_RETRY)
38
35
  retry
39
36
  end
37
+ end
38
+
39
+ def start_transfer(transfer_spec,options=nil)
40
40
  if transfer_spec['direction'] == 'send'
41
41
  Log.log.warn("Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red)
42
42
  transfer_spec.delete('paths')
@@ -47,7 +47,6 @@ module Aspera
47
47
  # if there is a token, we ask connect client to use well known ssh private keys
48
48
  # instead of asking password
49
49
  transfer_spec['authentication']='token' if transfer_spec.has_key?('token')
50
- connect_settings=
51
50
  connect_transfer_args={
52
51
  'aspera_connect_settings'=>@connect_settings.merge({
53
52
  'request_id' =>@request_id,
@@ -65,42 +64,50 @@ module Aspera
65
64
  connect_activity_args={'aspera_connect_settings'=>@connect_settings}
66
65
  started=false
67
66
  spinner=nil
68
- loop do
69
- tr_info=@connect_api.create("transfers/info/#{@xfer_id}",connect_activity_args)[:data]
70
- if tr_info['transfer_info'].is_a?(Hash)
71
- trdata=tr_info['transfer_info']
72
- if trdata.nil?
73
- Log.log.warn("no session in Connect")
74
- break
75
- end
76
- # TODO: get session id
77
- case trdata['status']
78
- when 'completed'
79
- notify_end(@connect_settings['app_id'])
80
- break
81
- when 'initiating','queued'
82
- if spinner.nil?
83
- spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
84
- spinner.start
67
+ begin
68
+ loop do
69
+ tr_info=@connect_api.create("transfers/info/#{@xfer_id}",connect_activity_args)[:data]
70
+ if tr_info['transfer_info'].is_a?(Hash)
71
+ trdata=tr_info['transfer_info']
72
+ if trdata.nil?
73
+ Log.log.warn("no session in Connect")
74
+ break
85
75
  end
86
- spinner.update(title: trdata['status'])
87
- spinner.spin
88
- when 'running'
89
- #puts "running: sessions:#{trdata['sessions'].length}, #{trdata['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
90
- if !started and trdata['bytes_expected'] != 0
91
- notify_begin(@connect_settings['app_id'],trdata['bytes_expected'])
92
- started=true
76
+ # TODO: get session id
77
+ case trdata['status']
78
+ when 'completed'
79
+ notify_end(@connect_settings['app_id'])
80
+ break
81
+ when 'initiating','queued'
82
+ if spinner.nil?
83
+ spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
84
+ spinner.start
85
+ end
86
+ spinner.update(title: trdata['status'])
87
+ spinner.spin
88
+ when 'running'
89
+ #puts "running: sessions:#{trdata['sessions'].length}, #{trdata['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
90
+ if !started and trdata['bytes_expected'] != 0
91
+ spinner.success unless spinner.nil?
92
+ notify_begin(@connect_settings['app_id'],trdata['bytes_expected'])
93
+ started=true
94
+ else
95
+ notify_progress(@connect_settings['app_id'],trdata['bytes_written'])
96
+ end
97
+ when 'failed'
98
+ spinner.error unless spinner.nil?
99
+ raise Fasp::Error.new(trdata['error_desc'])
93
100
  else
94
- notify_progress(@connect_settings['app_id'],trdata['bytes_written'])
101
+ raise Fasp::Error.new("unknown status: #{trdata['status']}: #{trdata['error_desc']}")
95
102
  end
96
- else
97
- raise Fasp::Error.new("unknown status: #{trdata['status']}: #{trdata['error_desc']}")
98
103
  end
104
+ sleep 1
99
105
  end
100
- sleep 1
106
+ rescue => e
107
+ return [e]
101
108
  end
102
- return [] #TODO
109
+ return [:success]
103
110
  end # wait
104
- end # Connect
111
+ end # AgentConnect
105
112
  end
106
113
  end
@@ -5,11 +5,12 @@
5
5
  # Laurent Martin
6
6
  #
7
7
  ##############################################################################
8
- require 'aspera/fasp/manager'
8
+ require 'aspera/fasp/agent_base'
9
9
  require 'aspera/fasp/error'
10
10
  require 'aspera/fasp/parameters'
11
11
  require 'aspera/fasp/installation'
12
12
  require 'aspera/fasp/resume_policy'
13
+ require 'aspera/fasp/default'
13
14
  require 'aspera/log'
14
15
  require 'socket'
15
16
  require 'timeout'
@@ -17,21 +18,18 @@ require 'securerandom'
17
18
 
18
19
  module Aspera
19
20
  module Fasp
20
- # (public) default transfer username for access key based transfers
21
- ACCESS_KEY_TRANSFER_USER='xfer'
22
21
  # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
23
- class Local < Manager
24
- # options for initialize
22
+ class AgentDirect < AgentBase
23
+ # options for initialize (same as values in option transfer_info)
25
24
  DEFAULT_OPTIONS = {
26
- :spawn_timeout_sec => 3,
27
- :spawn_delay_sec => 2,
28
- :wss => false,
29
- :resume => {}
25
+ spawn_timeout_sec: 3,
26
+ spawn_delay_sec: 2,
27
+ wss: false,
28
+ multi_incr_udp: true,
29
+ resume: {},
30
+ quiet: true # by default no interactive progress bar
30
31
  }
31
- DEFAULT_UDP_PORT=33001
32
32
  private_constant :DEFAULT_OPTIONS
33
- # set to false to keep ascp progress bar display ("true" adds ascp's option -q)
34
- attr_accessor :quiet
35
33
 
36
34
  # start ascp transfer (non blocking), single or multi-session
37
35
  # job information added to @jobs
@@ -64,21 +62,27 @@ module Aspera
64
62
  transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
65
63
  end
66
64
 
67
- # TODO: check if changing fasp(UDP) port is really necessary, not clear from doc
68
- # compute this before using transfer spec, even if the var is not used in single session
69
- multi_session_udp_port_base=DEFAULT_UDP_PORT
70
- multi_session_number=0
65
+ # Compute this before using transfer spec because it potentially modifies the transfer spec
66
+ # (even if the var is not used in single session)
67
+ multi_session_info=nil
71
68
  if transfer_spec.has_key?('multi_session')
72
- multi_session_number=transfer_spec['multi_session'].to_i
73
- if multi_session_number < 0
69
+ multi_session_info={
70
+ count: transfer_spec['multi_session'].to_i,
71
+ }
72
+ # Managed by multi-session, so delete from transfer spec
73
+ transfer_spec.delete('multi_session')
74
+ if multi_session_info[:count] < 0
74
75
  Log.log.error("multi_session(#{transfer_spec['multi_session']}) shall be integer >= 0")
75
- multi_session_number = 0
76
- end
77
- if multi_session_number > 0
78
- # managed here, so delete from transfer spec
79
- transfer_spec.delete('multi_session')
80
- if transfer_spec.has_key?('fasp_port')
81
- multi_session_udp_port_base=transfer_spec['fasp_port']
76
+ multi_session_info = nil
77
+ elsif multi_session_info[:count].eql?(0)
78
+ Log.log.debug("multi_session count is zero: no multisession")
79
+ multi_session_info = nil
80
+ else # multi_session_info[:count] > 0
81
+ # if option not true: keep default udp port for all sessions
82
+ if @options[:multi_incr_udp]
83
+ # override if specified, else use default value
84
+ multi_session_info[:udp_base]=transfer_spec.has_key?('fasp_port') ? transfer_spec['fasp_port'] : Default::UDP_PORT
85
+ # delete from original transfer spec, as we will increment values
82
86
  transfer_spec.delete('fasp_port')
83
87
  end
84
88
  end
@@ -93,7 +97,7 @@ module Aspera
93
97
  env_args[:args].unshift('-I',Installation.instance.path(:fallback_cert))
94
98
  end
95
99
 
96
- env_args[:args].unshift('-q') if @quiet
100
+ env_args[:args].unshift('-q') if @options[:quiet]
97
101
 
98
102
  # transfer job can be multi session
99
103
  xfer_job={
@@ -111,22 +115,23 @@ module Aspera
111
115
  :options => job_options # [Hash]
112
116
  }
113
117
 
114
- if multi_session_number <= 1
118
+ if multi_session_info.nil?
115
119
  Log.log.debug('Starting single session thread')
116
120
  # single session for transfer : simple
117
121
  session[:thread] = Thread.new(session) {|s|transfer_thread_entry(s)}
118
122
  xfer_job[:sessions].push(session)
119
123
  else
120
124
  Log.log.debug('Starting multi session threads')
121
- 1.upto(multi_session_number) do |i|
125
+ 1.upto(multi_session_info[:count]) do |i|
126
+ # do not delay the first session
122
127
  sleep(@options[:spawn_delay_sec]) unless i.eql?(1)
123
128
  # do deep copy (each thread has its own copy because it is modified here below and in thread)
124
129
  this_session=session.clone()
125
130
  this_session[:env_args]=this_session[:env_args].clone()
126
131
  this_session[:env_args][:args]=this_session[:env_args][:args].clone()
127
- this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_number}")
128
- # necessary only if server is not linux, i.e. server does not support port re-use
129
- this_session[:env_args][:args].unshift('-O',"#{multi_session_udp_port_base+i-1}")
132
+ this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_info[:count]}")
133
+ # option: increment (default as per ascp manual) or not (cluster on other side ?)
134
+ this_session[:env_args][:args].unshift('-O',"#{multi_session_info[:udp_base]+i-1}") if @options[:multi_incr_udp]
130
135
  this_session[:thread] = Thread.new(this_session) {|s|transfer_thread_entry(s)}
131
136
  xfer_job[:sessions].push(this_session)
132
137
  end
@@ -234,7 +239,7 @@ module Aspera
234
239
  when ''
235
240
  # empty line is separator to end event information
236
241
  raise 'unexpected empty line' if current_event_data.nil?
237
- current_event_data[Manager::LISTENER_SESSION_ID_B]=ascp_pid
242
+ current_event_data[AgentBase::LISTENER_SESSION_ID_B]=ascp_pid
238
243
  notify_listeners(current_event_text,current_event_data)
239
244
  case current_event_data['Type']
240
245
  when 'INIT'
@@ -260,6 +265,7 @@ module Aspera
260
265
  Log.log.error('need to regenerate token'.red)
261
266
  if session[:options].is_a?(Hash) and session[:options].has_key?(:regenerate_token)
262
267
  # regenerate token here, expired, or error on it
268
+ # Note: in multi-session, each session will have a different one.
263
269
  env_args[:env]['ASPERA_SCP_TOKEN']=session[:options][:regenerate_token].call(true)
264
270
  end
265
271
  end
@@ -323,16 +329,14 @@ module Aspera
323
329
 
324
330
  private
325
331
 
326
- # @param options : keys(symbol): wss, resume
332
+ # @param options : keys(symbol): see DEFAULT_OPTIONS
327
333
  def initialize(options=nil)
328
334
  super()
329
- # by default no interactive progress bar
330
- @quiet=true
331
335
  # all transfer jobs, key = SecureRandom.uuid, protected by mutex, condvar on change
332
336
  @jobs={}
333
337
  # mutex protects global data accessed by threads
334
338
  @mutex=Mutex.new
335
- # manage options
339
+ # set default options and override if specified
336
340
  @options=DEFAULT_OPTIONS.clone
337
341
  if !options.nil?
338
342
  raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
@@ -340,7 +344,7 @@ module Aspera
340
344
  if DEFAULT_OPTIONS.has_key?(k)
341
345
  @options[k]=v
342
346
  else
343
- raise "unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map{|i|i.to_s}.join(",")}"
347
+ raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map{|i|i.to_s}.join(",")}"
344
348
  end
345
349
  end
346
350
  end
@@ -367,6 +371,6 @@ module Aspera
367
371
  Log.log.debug("EXIT (#{Thread.current[:name]})")
368
372
  end
369
373
 
370
- end # Local
374
+ end # AgentDirect
371
375
  end
372
376
  end