aspera-cli 4.2.0 → 4.4.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
- data/README.md +749 -353
- data/docs/Makefile +4 -4
- data/docs/README.erb.md +743 -283
- data/docs/doc_tools.rb +58 -0
- data/docs/test_env.conf +9 -1
- data/examples/aoc.rb +14 -3
- data/examples/faspex4.rb +89 -0
- data/lib/aspera/aoc.rb +24 -22
- data/lib/aspera/cli/main.rb +48 -20
- data/lib/aspera/cli/plugin.rb +13 -6
- data/lib/aspera/cli/plugins/aoc.rb +117 -78
- data/lib/aspera/cli/plugins/config.rb +127 -80
- data/lib/aspera/cli/plugins/faspex.rb +112 -63
- data/lib/aspera/cli/plugins/faspex5.rb +29 -25
- data/lib/aspera/cli/plugins/node.rb +54 -25
- data/lib/aspera/cli/plugins/preview.rb +94 -68
- data/lib/aspera/cli/plugins/server.rb +16 -5
- data/lib/aspera/cli/transfer_agent.rb +92 -72
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +48 -31
- data/lib/aspera/cos_node.rb +4 -3
- data/lib/aspera/fasp/http_gw.rb +47 -26
- data/lib/aspera/fasp/local.rb +31 -24
- data/lib/aspera/fasp/manager.rb +3 -0
- data/lib/aspera/fasp/node.rb +23 -1
- data/lib/aspera/fasp/parameters.rb +72 -89
- data/lib/aspera/fasp/parameters.yaml +531 -0
- data/lib/aspera/fasp/uri.rb +1 -1
- data/lib/aspera/faspex_gw.rb +10 -9
- data/lib/aspera/id_generator.rb +22 -0
- data/lib/aspera/node.rb +11 -3
- data/lib/aspera/oauth.rb +131 -135
- data/lib/aspera/persistency_action_once.rb +11 -7
- data/lib/aspera/persistency_folder.rb +6 -26
- data/lib/aspera/rest.rb +1 -1
- data/lib/aspera/sync.rb +40 -35
- data/lib/aspera/timer_limiter.rb +22 -0
- data/lib/aspera/web_auth.rb +105 -0
- metadata +22 -4
- data/docs/transfer_spec.html +0 -99
- data/lib/aspera/fasp/aoc.rb +0 -24
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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[:
|
|
40
|
+
return options[:clvarname]
|
|
17
41
|
end
|
|
18
42
|
|
|
19
43
|
public
|
|
20
44
|
|
|
21
|
-
|
|
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[:
|
|
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
|
|
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
|
-
|
|
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([
|
|
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([
|
|
148
|
+
parameter_value.each{|v|add_command_line_options([options[:option_switch],v])}
|
|
132
149
|
else
|
|
133
150
|
raise "Error"
|
|
134
151
|
end
|
data/lib/aspera/cos_node.rb
CHANGED
|
@@ -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' => {
|
|
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=>
|
|
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'][
|
|
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
|
data/lib/aspera/fasp/http_gw.rb
CHANGED
|
@@ -4,15 +4,14 @@ require 'aspera/log'
|
|
|
4
4
|
require 'aspera/rest'
|
|
5
5
|
require 'websocket-client-simple'
|
|
6
6
|
require 'securerandom'
|
|
7
|
-
require 'openssl'
|
|
8
7
|
require 'base64'
|
|
9
8
|
require 'json'
|
|
10
|
-
require 'uri'
|
|
11
9
|
|
|
12
10
|
# ref: https://api.ibm.com/explorer/catalog/aspera/product/ibm-aspera/api/http-gateway-api/doc/guides-toc
|
|
11
|
+
# https://developer.ibm.com/apis/catalog?search=%22aspera%20http%22
|
|
13
12
|
module Aspera
|
|
14
13
|
module Fasp
|
|
15
|
-
#
|
|
14
|
+
# start a transfer using Aspera HTTP Gateway, using web socket session
|
|
16
15
|
class HttpGW < Manager
|
|
17
16
|
# message returned by HTTP GW in case of success
|
|
18
17
|
OK_MESSAGE='end upload'
|
|
@@ -25,20 +24,36 @@ module Aspera
|
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def upload(transfer_spec)
|
|
28
|
-
#
|
|
27
|
+
# total size of all files
|
|
29
28
|
total_size=0
|
|
30
|
-
#
|
|
31
|
-
|
|
29
|
+
# we need to keep track of actual file path because transfer spec is modified to be sent in web socket
|
|
30
|
+
source_paths=[]
|
|
31
|
+
# get source root or nil
|
|
32
|
+
source_root = (transfer_spec.has_key?('source_root') and !transfer_spec['source_root'].empty?) ? transfer_spec['source_root'] : nil
|
|
33
|
+
# source root is ignored by GW, used only here
|
|
34
|
+
transfer_spec.delete('source_root')
|
|
35
|
+
# compute total size of files to upload (for progress)
|
|
36
|
+
# modify transfer spec to be suitable for GW
|
|
32
37
|
transfer_spec['paths'].each do |item|
|
|
33
|
-
|
|
34
|
-
item['source']
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
# save actual file location to be able read contents later
|
|
39
|
+
full_src_filepath=item['source']
|
|
40
|
+
# add source root if needed
|
|
41
|
+
full_src_filepath=File.join(source_root,full_src_filepath) unless source_root.nil?
|
|
42
|
+
# GW expects a simple file name in 'source' but if user wants to change the name, we take it
|
|
43
|
+
item['source']=File.basename(item['destination'].nil? ? item['source'] : item['destination'])
|
|
44
|
+
item['file_size']=File.size(full_src_filepath)
|
|
45
|
+
total_size+=item['file_size']
|
|
46
|
+
# save so that we can actually read the file later
|
|
47
|
+
source_paths.push(full_src_filepath)
|
|
37
48
|
end
|
|
49
|
+
|
|
38
50
|
session_id=SecureRandom.uuid
|
|
39
51
|
ws=::WebSocket::Client::Simple::Client.new
|
|
52
|
+
# error message if any in callback
|
|
40
53
|
error=nil
|
|
54
|
+
# number of files totally sent
|
|
41
55
|
received=0
|
|
56
|
+
# setup callbacks on websocket
|
|
42
57
|
ws.on :message do |msg|
|
|
43
58
|
Log.log.info("ws: message: #{msg.data}")
|
|
44
59
|
message=msg.data
|
|
@@ -64,44 +79,49 @@ module Aspera
|
|
|
64
79
|
end
|
|
65
80
|
# open web socket to end point
|
|
66
81
|
ws.connect("#{@gw_api.params[:base_url]}/upload")
|
|
82
|
+
# async wait ready
|
|
67
83
|
while !ws.open? and error.nil? do
|
|
68
84
|
Log.log.info("ws: wait")
|
|
69
85
|
sleep(0.2)
|
|
70
86
|
end
|
|
87
|
+
# notify progress bar
|
|
71
88
|
notify_begin(session_id,total_size)
|
|
89
|
+
# first step send transfer spec
|
|
90
|
+
Log.dump(:ws_spec,transfer_spec)
|
|
72
91
|
ws_send(ws,:transfer_spec,transfer_spec)
|
|
73
92
|
# current file index
|
|
74
|
-
|
|
93
|
+
file_index=0
|
|
75
94
|
# aggregate size sent
|
|
76
95
|
sent_bytes=0
|
|
77
96
|
# last progress event
|
|
78
|
-
lastevent=
|
|
97
|
+
lastevent=nil
|
|
79
98
|
transfer_spec['paths'].each do |item|
|
|
80
|
-
# TODO: on destination write same path?
|
|
81
|
-
destination_path=item['source']
|
|
82
99
|
# TODO: get mime type?
|
|
83
100
|
file_mime_type=''
|
|
84
|
-
|
|
101
|
+
file_size=item['file_size']
|
|
102
|
+
file_name=File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
|
85
103
|
# compute total number of slices
|
|
86
|
-
numslices=1+(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
numslices=1+(file_size-1)/@upload_chunksize
|
|
105
|
+
File.open(source_paths[file_index]) do |file|
|
|
106
|
+
# current slice index
|
|
107
|
+
slicenum=0
|
|
90
108
|
while !file.eof? do
|
|
91
109
|
data=file.read(@upload_chunksize)
|
|
92
110
|
slice_data={
|
|
93
|
-
name:
|
|
111
|
+
name: file_name,
|
|
94
112
|
type: file_mime_type,
|
|
95
|
-
size:
|
|
113
|
+
size: file_size,
|
|
96
114
|
data: Base64.strict_encode64(data),
|
|
97
115
|
slice: slicenum,
|
|
98
116
|
total_slices: numslices,
|
|
99
|
-
fileIndex:
|
|
117
|
+
fileIndex: file_index
|
|
100
118
|
}
|
|
119
|
+
# log without data
|
|
120
|
+
Log.dump(:slide_data,slice_data.keys.inject({}){|m,i|m[i]=i.eql?(:data)?'base64 data':slice_data[i];m}) if slicenum.eql?(0)
|
|
101
121
|
ws_send(ws,:slice_upload, slice_data)
|
|
102
122
|
sent_bytes+=data.length
|
|
103
123
|
currenttime=Time.now
|
|
104
|
-
if (currenttime-lastevent)>UPLOAD_REFRESH_SEC
|
|
124
|
+
if lastevent.nil? or (currenttime-lastevent)>UPLOAD_REFRESH_SEC
|
|
105
125
|
notify_progress(session_id,sent_bytes)
|
|
106
126
|
lastevent=currenttime
|
|
107
127
|
end
|
|
@@ -109,7 +129,7 @@ module Aspera
|
|
|
109
129
|
raise error unless error.nil?
|
|
110
130
|
end
|
|
111
131
|
end
|
|
112
|
-
|
|
132
|
+
file_index+=1
|
|
113
133
|
end
|
|
114
134
|
ws.close
|
|
115
135
|
notify_end(session_id)
|
|
@@ -150,7 +170,8 @@ module Aspera
|
|
|
150
170
|
raise "GW URL must be set" unless !@gw_api.nil?
|
|
151
171
|
raise "option: must be hash (or nil)" unless options.is_a?(Hash)
|
|
152
172
|
raise "paths: must be Array" unless transfer_spec['paths'].is_a?(Array)
|
|
153
|
-
raise "
|
|
173
|
+
raise "only token based transfer is supported in GW" unless transfer_spec['token'].is_a?(String)
|
|
174
|
+
Log.dump(:user_spec,transfer_spec)
|
|
154
175
|
transfer_spec['authentication']||='token'
|
|
155
176
|
case transfer_spec['direction']
|
|
156
177
|
when 'send'
|
|
@@ -188,6 +209,6 @@ module Aspera
|
|
|
188
209
|
@upload_chunksize=128000 # TODO: configurable ?
|
|
189
210
|
end
|
|
190
211
|
|
|
191
|
-
end #
|
|
212
|
+
end # HttpGW
|
|
192
213
|
end
|
|
193
214
|
end
|
data/lib/aspera/fasp/local.rb
CHANGED
|
@@ -17,15 +17,14 @@ require 'securerandom'
|
|
|
17
17
|
|
|
18
18
|
module Aspera
|
|
19
19
|
module Fasp
|
|
20
|
-
# (public) default transfer username for access key based transfers
|
|
21
|
-
ACCESS_KEY_TRANSFER_USER='xfer'
|
|
22
20
|
# executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
|
|
23
21
|
class Local < Manager
|
|
24
|
-
# options for initialize
|
|
22
|
+
# options for initialize (same as values in option transfer_info)
|
|
25
23
|
DEFAULT_OPTIONS = {
|
|
26
24
|
:spawn_timeout_sec => 3,
|
|
27
25
|
:spawn_delay_sec => 2,
|
|
28
26
|
:wss => false,
|
|
27
|
+
:multi_incr_udp => true,
|
|
29
28
|
:resume => {}
|
|
30
29
|
}
|
|
31
30
|
DEFAULT_UDP_PORT=33001
|
|
@@ -64,21 +63,27 @@ module Aspera
|
|
|
64
63
|
transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
|
|
65
64
|
end
|
|
66
65
|
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
multi_session_number=0
|
|
66
|
+
# Compute this before using transfer spec because it potentially modifies the transfer spec
|
|
67
|
+
# (even if the var is not used in single session)
|
|
68
|
+
multi_session_info=nil
|
|
71
69
|
if transfer_spec.has_key?('multi_session')
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
multi_session_info={
|
|
71
|
+
count: transfer_spec['multi_session'].to_i,
|
|
72
|
+
}
|
|
73
|
+
# Managed by multi-session, so delete from transfer spec
|
|
74
|
+
transfer_spec.delete('multi_session')
|
|
75
|
+
if multi_session_info[:count] < 0
|
|
74
76
|
Log.log.error("multi_session(#{transfer_spec['multi_session']}) shall be integer >= 0")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if
|
|
81
|
-
|
|
77
|
+
multi_session_info = nil
|
|
78
|
+
elsif multi_session_info[:count].eql?(0)
|
|
79
|
+
Log.log.debug("multi_session count is zero: no multisession")
|
|
80
|
+
multi_session_info = nil
|
|
81
|
+
else # multi_session_info[:count] > 0
|
|
82
|
+
# if option not true: keep default udp port for all sessions
|
|
83
|
+
if @options[:multi_incr_udp]
|
|
84
|
+
# override if specified, else use default value
|
|
85
|
+
multi_session_info[:udp_base]=transfer_spec.has_key?('fasp_port') ? transfer_spec['fasp_port'] : DEFAULT_UDP_PORT
|
|
86
|
+
# delete from original transfer spec, as we will increment values
|
|
82
87
|
transfer_spec.delete('fasp_port')
|
|
83
88
|
end
|
|
84
89
|
end
|
|
@@ -111,22 +116,23 @@ module Aspera
|
|
|
111
116
|
:options => job_options # [Hash]
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
if
|
|
119
|
+
if multi_session_info.nil?
|
|
115
120
|
Log.log.debug('Starting single session thread')
|
|
116
121
|
# single session for transfer : simple
|
|
117
122
|
session[:thread] = Thread.new(session) {|s|transfer_thread_entry(s)}
|
|
118
123
|
xfer_job[:sessions].push(session)
|
|
119
124
|
else
|
|
120
125
|
Log.log.debug('Starting multi session threads')
|
|
121
|
-
1.upto(
|
|
126
|
+
1.upto(multi_session_info[:count]) do |i|
|
|
127
|
+
# do not delay the first session
|
|
122
128
|
sleep(@options[:spawn_delay_sec]) unless i.eql?(1)
|
|
123
129
|
# do deep copy (each thread has its own copy because it is modified here below and in thread)
|
|
124
130
|
this_session=session.clone()
|
|
125
131
|
this_session[:env_args]=this_session[:env_args].clone()
|
|
126
132
|
this_session[:env_args][:args]=this_session[:env_args][:args].clone()
|
|
127
|
-
this_session[:env_args][:args].unshift("-C#{i}:#{
|
|
128
|
-
#
|
|
129
|
-
this_session[:env_args][:args].unshift('-O',"#{
|
|
133
|
+
this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_info[:count]}")
|
|
134
|
+
# option: increment (default as per ascp manual) or not (cluster on other side ?)
|
|
135
|
+
this_session[:env_args][:args].unshift('-O',"#{multi_session_info[:udp_base]+i-1}") if @options[:multi_incr_udp]
|
|
130
136
|
this_session[:thread] = Thread.new(this_session) {|s|transfer_thread_entry(s)}
|
|
131
137
|
xfer_job[:sessions].push(this_session)
|
|
132
138
|
end
|
|
@@ -260,6 +266,7 @@ module Aspera
|
|
|
260
266
|
Log.log.error('need to regenerate token'.red)
|
|
261
267
|
if session[:options].is_a?(Hash) and session[:options].has_key?(:regenerate_token)
|
|
262
268
|
# regenerate token here, expired, or error on it
|
|
269
|
+
# Note: in multi-session, each session will have a different one.
|
|
263
270
|
env_args[:env]['ASPERA_SCP_TOKEN']=session[:options][:regenerate_token].call(true)
|
|
264
271
|
end
|
|
265
272
|
end
|
|
@@ -323,7 +330,7 @@ module Aspera
|
|
|
323
330
|
|
|
324
331
|
private
|
|
325
332
|
|
|
326
|
-
# @param options : keys(symbol):
|
|
333
|
+
# @param options : keys(symbol): see DEFAULT_OPTIONS
|
|
327
334
|
def initialize(options=nil)
|
|
328
335
|
super()
|
|
329
336
|
# by default no interactive progress bar
|
|
@@ -332,7 +339,7 @@ module Aspera
|
|
|
332
339
|
@jobs={}
|
|
333
340
|
# mutex protects global data accessed by threads
|
|
334
341
|
@mutex=Mutex.new
|
|
335
|
-
#
|
|
342
|
+
# set default options and override if specified
|
|
336
343
|
@options=DEFAULT_OPTIONS.clone
|
|
337
344
|
if !options.nil?
|
|
338
345
|
raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
|
|
@@ -340,7 +347,7 @@ module Aspera
|
|
|
340
347
|
if DEFAULT_OPTIONS.has_key?(k)
|
|
341
348
|
@options[k]=v
|
|
342
349
|
else
|
|
343
|
-
raise "
|
|
350
|
+
raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map{|i|i.to_s}.join(",")}"
|
|
344
351
|
end
|
|
345
352
|
end
|
|
346
353
|
end
|
data/lib/aspera/fasp/manager.rb
CHANGED
|
@@ -72,6 +72,9 @@ module Aspera
|
|
|
72
72
|
# start_transfer(transfer_spec,options) : start and wait for completion
|
|
73
73
|
# wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
|
|
74
74
|
# optional: shutdown
|
|
75
|
+
|
|
76
|
+
# This checks the validity of the value returned by wait_for_transfers_completion
|
|
77
|
+
# it must be a list of :success or exception
|
|
75
78
|
def self.validate_status_list(statuses)
|
|
76
79
|
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
|
77
80
|
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
|
data/lib/aspera/fasp/node.rb
CHANGED
|
@@ -7,9 +7,12 @@ module Aspera
|
|
|
7
7
|
# this singleton class is used by the CLI to provide a common interface to start a transfer
|
|
8
8
|
# before using it, the use must set the `node_api` member.
|
|
9
9
|
class Node < Manager
|
|
10
|
-
|
|
10
|
+
# option include: root_id if the node is an access key
|
|
11
|
+
attr_writer :options
|
|
12
|
+
def initialize(node_api,options={})
|
|
11
13
|
super()
|
|
12
14
|
@node_api=node_api
|
|
15
|
+
@options=options
|
|
13
16
|
# TODO: currently only supports one transfer. This is bad shortcut. but ok for CLI.
|
|
14
17
|
@transfer_id=nil
|
|
15
18
|
end
|
|
@@ -32,6 +35,25 @@ module Aspera
|
|
|
32
35
|
|
|
33
36
|
# generic method
|
|
34
37
|
def start_transfer(transfer_spec,options=nil)
|
|
38
|
+
# add root id if access key
|
|
39
|
+
if @options.has_key?(:root_id)
|
|
40
|
+
case transfer_spec['direction']
|
|
41
|
+
when 'send';transfer_spec['source_root_id']=@options[:root_id]
|
|
42
|
+
when 'receive';transfer_spec['destination_root_id']=@options[:root_id]
|
|
43
|
+
else raise "unexpected direction in ts: #{transfer_spec['direction']}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# manage special additional parameter
|
|
47
|
+
if transfer_spec.has_key?('EX_ssh_key_paths') and transfer_spec['EX_ssh_key_paths'].is_a?(Array) and !transfer_spec['EX_ssh_key_paths'].empty?
|
|
48
|
+
# not standard, so place standard field
|
|
49
|
+
if transfer_spec.has_key?('ssh_private_key')
|
|
50
|
+
Log.log.warn('Both ssh_private_key and EX_ssh_key_paths are present, using ssh_private_key')
|
|
51
|
+
else
|
|
52
|
+
Log.log.warn('EX_ssh_key_paths has multiple keys, using first one only') unless transfer_spec['EX_ssh_key_paths'].length.eql?(1)
|
|
53
|
+
transfer_spec['ssh_private_key']=File.read(transfer_spec['EX_ssh_key_paths'].first)
|
|
54
|
+
transfer_spec.delete('EX_ssh_key_paths')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
35
57
|
if transfer_spec['tags'].is_a?(Hash) and transfer_spec['tags']['aspera'].is_a?(Hash)
|
|
36
58
|
transfer_spec['tags']['aspera']['xfer_retry']||=150
|
|
37
59
|
end
|