aspera-cli 4.15.0 → 4.17.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 +375 -280
- data/CONTRIBUTING.md +71 -18
- data/README.md +1978 -1656
- data/bin/ascli +13 -31
- data/bin/asession +32 -22
- data/examples/dascli +2 -2
- data/lib/aspera/agent/alpha.rb +117 -0
- data/lib/aspera/agent/base.rb +61 -0
- data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
- data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
- data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
- data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
- data/lib/aspera/agent/trsdk.rb +188 -0
- data/lib/aspera/api/aoc.rb +586 -0
- data/lib/aspera/api/ats.rb +46 -0
- data/lib/aspera/api/cos_node.rb +95 -0
- data/lib/aspera/api/node.rb +344 -0
- data/lib/aspera/ascmd.rb +47 -14
- data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
- data/lib/aspera/{fasp → ascp}/management.rb +14 -14
- data/lib/aspera/{fasp → ascp}/products.rb +1 -1
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formatter.rb +27 -14
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/main.rb +49 -29
- data/lib/aspera/cli/manager.rb +46 -36
- data/lib/aspera/cli/plugin.rb +34 -20
- data/lib/aspera/cli/plugin_factory.rb +61 -0
- data/lib/aspera/cli/plugins/alee.rb +7 -7
- data/lib/aspera/cli/plugins/aoc.rb +168 -132
- data/lib/aspera/cli/plugins/ats.rb +33 -33
- data/lib/aspera/cli/plugins/bss.rb +3 -4
- data/lib/aspera/cli/plugins/config.rb +250 -272
- data/lib/aspera/cli/plugins/console.rb +8 -6
- data/lib/aspera/cli/plugins/cos.rb +20 -19
- data/lib/aspera/cli/plugins/faspex.rb +71 -60
- data/lib/aspera/cli/plugins/faspex5.rb +212 -133
- data/lib/aspera/cli/plugins/node.rb +83 -75
- data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
- data/lib/aspera/cli/plugins/preview.rb +33 -31
- data/lib/aspera/cli/plugins/server.rb +33 -32
- data/lib/aspera/cli/plugins/shares.rb +39 -33
- data/lib/aspera/cli/sync_actions.rb +9 -9
- data/lib/aspera/cli/transfer_agent.rb +45 -25
- data/lib/aspera/cli/transfer_progress.rb +2 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +5 -0
- data/lib/aspera/command_line_builder.rb +16 -14
- data/lib/aspera/coverage.rb +21 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +5 -4
- data/lib/aspera/faspex_gw.rb +13 -11
- data/lib/aspera/faspex_postproc.rb +6 -5
- data/lib/aspera/id_generator.rb +4 -2
- data/lib/aspera/json_rpc.rb +10 -8
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +29 -22
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node_simulator.rb +213 -0
- data/lib/aspera/oauth/base.rb +143 -0
- data/lib/aspera/oauth/factory.rb +124 -0
- data/lib/aspera/oauth/generic.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +51 -0
- data/lib/aspera/oauth/url_json.rb +31 -0
- data/lib/aspera/oauth/web.rb +50 -0
- data/lib/aspera/oauth.rb +5 -328
- data/lib/aspera/open_application.rb +7 -7
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/preview/file_types.rb +53 -267
- data/lib/aspera/preview/generator.rb +7 -5
- data/lib/aspera/preview/terminal.rb +17 -7
- data/lib/aspera/preview/utils.rb +8 -7
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +187 -140
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +5 -3
- data/lib/aspera/resumer.rb +77 -0
- data/lib/aspera/secret_hider.rb +5 -2
- data/lib/aspera/ssh.rb +15 -8
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/{fasp → transfer}/error.rb +3 -3
- data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
- data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
- data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
- data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
- data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
- data/lib/aspera/transfer/sync.rb +273 -0
- data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
- data/lib/aspera/web_server_simple.rb +12 -3
- data.tar.gz.sig +0 -0
- metadata +92 -68
- metadata.gz.sig +0 -0
- data/lib/aspera/aoc.rb +0 -606
- data/lib/aspera/ats_api.rb +0 -47
- data/lib/aspera/cos_node.rb +0 -93
- data/lib/aspera/fasp/agent_aspera.rb +0 -126
- data/lib/aspera/fasp/agent_base.rb +0 -48
- data/lib/aspera/fasp/agent_trsdk.rb +0 -146
- data/lib/aspera/fasp/resume_policy.rb +0 -77
- data/lib/aspera/node.rb +0 -338
- data/lib/aspera/sync.rb +0 -219
data/lib/aspera/cos_node.rb
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'aspera/log'
|
|
4
|
-
require 'aspera/rest'
|
|
5
|
-
require 'aspera/oauth'
|
|
6
|
-
require 'xmlsimple'
|
|
7
|
-
|
|
8
|
-
module Aspera
|
|
9
|
-
class CosNode < Aspera::Node
|
|
10
|
-
class << self
|
|
11
|
-
def parameters_from_svc_credentials(service_credentials, bucket_region)
|
|
12
|
-
# check necessary contents
|
|
13
|
-
raise 'service_credentials must be a Hash' unless service_credentials.is_a?(Hash)
|
|
14
|
-
%w[apikey resource_instance_id endpoints].each do |field|
|
|
15
|
-
raise "service_credentials must have a field: #{field}" unless service_credentials.key?(field)
|
|
16
|
-
end
|
|
17
|
-
Aspera::Log.dump('service_credentials', service_credentials)
|
|
18
|
-
# read endpoints from service provided in service credentials
|
|
19
|
-
endpoints = Aspera::Rest.new({base_url: service_credentials['endpoints']}).read('')[:data]
|
|
20
|
-
Aspera::Log.dump('endpoints', endpoints)
|
|
21
|
-
storage_endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
|
|
22
|
-
raise "no such region: #{bucket_region}" if storage_endpoint.nil?
|
|
23
|
-
return {
|
|
24
|
-
instance_id: service_credentials['resource_instance_id'],
|
|
25
|
-
service_api_key: service_credentials['apikey'],
|
|
26
|
-
storage_endpoint: "https://#{storage_endpoint}"
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
IBM_CLOUD_TOKEN_URL = 'https://iam.cloud.ibm.com/identity'
|
|
31
|
-
TOKEN_FIELD = 'delegated_refresh_token'
|
|
32
|
-
|
|
33
|
-
def initialize(bucket_name, storage_endpoint, instance_id, api_key, auth_url= IBM_CLOUD_TOKEN_URL)
|
|
34
|
-
@auth_url = auth_url
|
|
35
|
-
@api_key = api_key
|
|
36
|
-
s3_api = Aspera::Rest.new({
|
|
37
|
-
base_url: storage_endpoint,
|
|
38
|
-
not_auth_codes: %w[401 403], # error codes when not authorized
|
|
39
|
-
headers: {'ibm-service-instance-id' => instance_id},
|
|
40
|
-
auth: {
|
|
41
|
-
type: :oauth2,
|
|
42
|
-
base_url: @auth_url,
|
|
43
|
-
grant_method: :generic,
|
|
44
|
-
generic: {
|
|
45
|
-
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
|
46
|
-
response_type: 'cloud_iam',
|
|
47
|
-
apikey: @api_key
|
|
48
|
-
}}})
|
|
49
|
-
# read FASP connection information for bucket
|
|
50
|
-
xml_result_text = s3_api.call(
|
|
51
|
-
operation: 'GET',
|
|
52
|
-
subpath: bucket_name,
|
|
53
|
-
headers: {'Accept' => 'application/xml'},
|
|
54
|
-
url_params: {'faspConnectionInfo' => nil}
|
|
55
|
-
)[:http].body
|
|
56
|
-
ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
|
|
57
|
-
Aspera::Log.dump('ats_info', ats_info)
|
|
58
|
-
@storage_credentials = {
|
|
59
|
-
'type' => 'token',
|
|
60
|
-
'token' => {TOKEN_FIELD => nil}
|
|
61
|
-
}
|
|
62
|
-
super(
|
|
63
|
-
params: {
|
|
64
|
-
base_url: ats_info['ATSEndpoint'],
|
|
65
|
-
auth: {
|
|
66
|
-
type: :basic,
|
|
67
|
-
username: ats_info['AccessKey']['Id'],
|
|
68
|
-
password: ats_info['AccessKey']['Secret']}},
|
|
69
|
-
add_tspec: {'tags'=>{Fasp::TransferSpec::TAG_RESERVED=>{'node'=>{'storage_credentials'=>@storage_credentials}}}})
|
|
70
|
-
# update storage_credentials AND Rest params
|
|
71
|
-
generate_token
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# potentially call this if delegated token is expired
|
|
75
|
-
def generate_token
|
|
76
|
-
# OAuth API to get delegated token
|
|
77
|
-
delegated_oauth = Oauth.new({
|
|
78
|
-
type: :oauth2,
|
|
79
|
-
base_url: @auth_url,
|
|
80
|
-
token_field: TOKEN_FIELD,
|
|
81
|
-
grant_method: :generic,
|
|
82
|
-
generic: {
|
|
83
|
-
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
|
84
|
-
response_type: 'delegated_refresh_token',
|
|
85
|
-
apikey: @api_key,
|
|
86
|
-
receiver_client_ids: 'aspera_ats'
|
|
87
|
-
}})
|
|
88
|
-
# get delegated token to be placed in rest call header and in transfer tags
|
|
89
|
-
@storage_credentials['token'][TOKEN_FIELD] = Oauth.bearer_extract(delegated_oauth.get_authorization)
|
|
90
|
-
@params[:headers] = {'X-Aspera-Storage-Credentials' => JSON.generate(@storage_credentials)}
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'aspera/fasp/agent_base'
|
|
4
|
-
require 'aspera/rest'
|
|
5
|
-
require 'aspera/json_rpc'
|
|
6
|
-
require 'aspera/open_application'
|
|
7
|
-
require 'securerandom'
|
|
8
|
-
|
|
9
|
-
module Aspera
|
|
10
|
-
module Fasp
|
|
11
|
-
class AgentAspera < Aspera::Fasp::AgentBase
|
|
12
|
-
# try twice the main init url in sequence
|
|
13
|
-
START_URIS = ['aspera://']
|
|
14
|
-
# delay between each try to start connect
|
|
15
|
-
SLEEP_SEC_BETWEEN_RETRY = 3
|
|
16
|
-
private_constant :START_URIS, :SLEEP_SEC_BETWEEN_RETRY
|
|
17
|
-
def initialize(options)
|
|
18
|
-
@application_id = SecureRandom.uuid
|
|
19
|
-
super(options)
|
|
20
|
-
raise 'Using client requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
|
|
21
|
-
method_index = 0
|
|
22
|
-
begin
|
|
23
|
-
@client_app_api = Aspera::JsonRpcClient.new(Aspera::Rest.new(base_url: aspera_client_api_url))
|
|
24
|
-
client_info = @client_app_api.get_info
|
|
25
|
-
Log.log.debug{Log.dump(:client_version, client_info)}
|
|
26
|
-
# my_transfer_id = '0513fe85-65cf-465b-ad5f-18fd40d8c69f'
|
|
27
|
-
# @client_app_api.get_all_transfers({app_id: @application_id})
|
|
28
|
-
# @client_app_api.get_transfer(app_id: @application_id, transfer_id: my_transfer_id)
|
|
29
|
-
# @client_app_api.start_transfer(app_id: @application_id,transfer_spec: {})
|
|
30
|
-
# @client_app_api.remove_transfer
|
|
31
|
-
# @client_app_api.stop_transfer
|
|
32
|
-
# @client_app_api.modify_transfer
|
|
33
|
-
# @client_app_api.show_directory({app_id: @application_id, transfer_id: my_transfer_id})
|
|
34
|
-
# @client_app_api.get_files_list({app_id: @application_id, transfer_id: my_transfer_id})
|
|
35
|
-
Log.log.info('Client was reached') if method_index > 0
|
|
36
|
-
rescue StandardError => e # Errno::ECONNREFUSED
|
|
37
|
-
start_url = START_URIS[method_index]
|
|
38
|
-
method_index += 1
|
|
39
|
-
raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
|
|
40
|
-
Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
|
|
41
|
-
if !OpenApplication.uri_graphical(start_url)
|
|
42
|
-
OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
|
|
43
|
-
raise StandardError, 'Connect is not installed'
|
|
44
|
-
end
|
|
45
|
-
sleep(SLEEP_SEC_BETWEEN_RETRY)
|
|
46
|
-
retry
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def aspera_client_api_url
|
|
51
|
-
log_file = File.join(Dir.home, 'Library', 'Logs', 'IBM Aspera', 'ibm-aspera-desktop.log')
|
|
52
|
-
url = nil
|
|
53
|
-
File.open(log_file, 'r') do |file|
|
|
54
|
-
file.each_line do |line|
|
|
55
|
-
line = line.chomp
|
|
56
|
-
if (m = line.match(/JSON-RPC server listening on (.*)/))
|
|
57
|
-
url = "http://#{m[1]}"
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
return url
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def start_transfer(transfer_spec, token_regenerator: nil)
|
|
65
|
-
@request_id = SecureRandom.uuid
|
|
66
|
-
# if there is a token, we ask connect client to use well known ssh private keys
|
|
67
|
-
# instead of asking password
|
|
68
|
-
transfer_spec['authentication'] = 'token' if transfer_spec.key?('token')
|
|
69
|
-
@client_app_api.start_transfer(app_id: @application_id,transfer_spec: transfer_spec)
|
|
70
|
-
# @xfer_id = res['transfer_specs'].first['transfer_spec']['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_id']
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def wait_for_transfers_completion
|
|
74
|
-
client_activity_args = {'aspera_client_settings' => @client_settings}
|
|
75
|
-
started = false
|
|
76
|
-
pre_calc = false
|
|
77
|
-
session_id = @xfer_id
|
|
78
|
-
begin
|
|
79
|
-
loop do
|
|
80
|
-
tr_info = @client_api.create("transfers/info/#{@xfer_id}", client_activity_args)[:data]
|
|
81
|
-
Log.log.trace1{Log.dump(:tr_info, tr_info)}
|
|
82
|
-
if tr_info['transfer_info'].is_a?(Hash)
|
|
83
|
-
transfer = tr_info['transfer_info']
|
|
84
|
-
if transfer.nil?
|
|
85
|
-
Log.log.warn('no session in Connect')
|
|
86
|
-
break
|
|
87
|
-
end
|
|
88
|
-
# TODO: get session id
|
|
89
|
-
case transfer['status']
|
|
90
|
-
when 'initiating', 'queued'
|
|
91
|
-
notify_progress(session_id: nil, type: :pre_start, info: transfer['status'])
|
|
92
|
-
when 'running'
|
|
93
|
-
if !started
|
|
94
|
-
notify_progress(session_id: session_id, type: :session_start)
|
|
95
|
-
started = true
|
|
96
|
-
end
|
|
97
|
-
if !pre_calc && (transfer['bytes_expected'] != 0)
|
|
98
|
-
notify_progress(type: :session_size, session_id: session_id, info: transfer['bytes_expected'])
|
|
99
|
-
pre_calc = true
|
|
100
|
-
else
|
|
101
|
-
notify_progress(type: :transfer, session_id: session_id, info: transfer['bytes_written'])
|
|
102
|
-
end
|
|
103
|
-
when 'completed'
|
|
104
|
-
notify_progress(type: :end, session_id: session_id)
|
|
105
|
-
break
|
|
106
|
-
when 'failed'
|
|
107
|
-
notify_progress(type: :end, session_id: session_id)
|
|
108
|
-
raise Fasp::Error, transfer['error_desc']
|
|
109
|
-
when 'cancelled'
|
|
110
|
-
notify_progress(type: :end, session_id: session_id)
|
|
111
|
-
raise Fasp::Error, 'Transfer cancelled by user'
|
|
112
|
-
else
|
|
113
|
-
notify_progress(type: :end, session_id: session_id)
|
|
114
|
-
raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
sleep(1)
|
|
118
|
-
end
|
|
119
|
-
rescue StandardError => e
|
|
120
|
-
return [e]
|
|
121
|
-
end
|
|
122
|
-
return [:success]
|
|
123
|
-
end # wait
|
|
124
|
-
end # AgentAspera
|
|
125
|
-
end
|
|
126
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Aspera
|
|
4
|
-
module Fasp
|
|
5
|
-
# Base class for transfer agents
|
|
6
|
-
class AgentBase
|
|
7
|
-
class << self
|
|
8
|
-
# compute options from user provided and default options
|
|
9
|
-
def options(default:, options:)
|
|
10
|
-
result = options.symbolize_keys
|
|
11
|
-
available = default.map{|k, v|"#{k}(#{v})"}.join(', ')
|
|
12
|
-
result.each do |k, _v|
|
|
13
|
-
raise "Unknown transfer agent parameter: #{k}, expect one of #{available}" unless default.key?(k)
|
|
14
|
-
end
|
|
15
|
-
default.each do |k, v|
|
|
16
|
-
raise "Missing required agent parameter: #{k}. Parameters: #{available}" if v.eql?(:required) && !result.key?(k)
|
|
17
|
-
result[k] = v unless result.key?(k)
|
|
18
|
-
end
|
|
19
|
-
return result
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
def wait_for_completion
|
|
23
|
-
# list of: :success or "error message string"
|
|
24
|
-
statuses = wait_for_transfers_completion
|
|
25
|
-
@progress&.reset
|
|
26
|
-
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
|
27
|
-
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?
|
|
28
|
-
return statuses
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def initialize(options)
|
|
34
|
-
raise 'internal error' unless respond_to?(:start_transfer)
|
|
35
|
-
raise 'internal error' unless respond_to?(:wait_for_transfers_completion)
|
|
36
|
-
# method `shutdown` is optional
|
|
37
|
-
Log.log.debug{Log.dump(:agent_options, options)}
|
|
38
|
-
raise "transfer agent options expecting Hash, but have #{options.class}" unless options.is_a?(Hash)
|
|
39
|
-
@progress = options[:progress]
|
|
40
|
-
options.delete(:progress)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def notify_progress(**parameters)
|
|
44
|
-
@progress&.event(**parameters)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'aspera/fasp/agent_base'
|
|
4
|
-
require 'aspera/fasp/installation'
|
|
5
|
-
require 'json'
|
|
6
|
-
require 'uri'
|
|
7
|
-
|
|
8
|
-
module Aspera
|
|
9
|
-
module Fasp
|
|
10
|
-
class AgentTrsdk < Aspera::Fasp::AgentBase
|
|
11
|
-
DEFAULT_OPTIONS = {
|
|
12
|
-
url: 'grpc://127.0.0.1:0',
|
|
13
|
-
external: false,
|
|
14
|
-
keep: false
|
|
15
|
-
}.freeze
|
|
16
|
-
private_constant :DEFAULT_OPTIONS
|
|
17
|
-
|
|
18
|
-
# options come from transfer_info
|
|
19
|
-
def initialize(user_opts={})
|
|
20
|
-
super(user_opts)
|
|
21
|
-
@options = AgentBase.options(default: DEFAULT_OPTIONS, options: user_opts)
|
|
22
|
-
daemon_uri = URI.parse(@options[:url])
|
|
23
|
-
raise Fasp::Error, "invalid url #{@options[:url]}" unless daemon_uri.scheme.eql?('grpc')
|
|
24
|
-
Log.log.debug{Log.dump(:agent_options, @options)}
|
|
25
|
-
# load and create SDK stub
|
|
26
|
-
$LOAD_PATH.unshift(Installation.instance.sdk_ruby_folder)
|
|
27
|
-
require 'transfer_services_pb'
|
|
28
|
-
# it stays
|
|
29
|
-
@daemon_pid = nil
|
|
30
|
-
begin
|
|
31
|
-
@transfer_client = Transfersdk::TransferService::Stub.new("#{daemon_uri.host}:#{daemon_uri.port}", :this_channel_is_insecure)
|
|
32
|
-
get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
|
|
33
|
-
Log.log.debug{"daemon info: #{get_info_response}"}
|
|
34
|
-
Log.log.warn{'attached to existing daemon'} unless @options[:external] || @options[:keep]
|
|
35
|
-
at_exit{shutdown}
|
|
36
|
-
rescue GRPC::Unavailable
|
|
37
|
-
raise if @options[:external]
|
|
38
|
-
raise "daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_uri}}" unless @daemon_pid.nil?
|
|
39
|
-
Log.log.warn('no daemon present, starting daemon...') if @options[:external]
|
|
40
|
-
# location of daemon binary
|
|
41
|
-
bin_folder = File.realpath(File.join(Installation.instance.sdk_ruby_folder, '..'))
|
|
42
|
-
# config file and logs are created in same folder
|
|
43
|
-
generated_config_file_path = File.join(bin_folder, 'sdk.conf')
|
|
44
|
-
log_base = File.join(bin_folder, 'transferd')
|
|
45
|
-
# create a config file for daemon
|
|
46
|
-
config = {
|
|
47
|
-
address: daemon_uri.host,
|
|
48
|
-
port: daemon_uri.port,
|
|
49
|
-
fasp_runtime: {
|
|
50
|
-
use_embedded: false,
|
|
51
|
-
user_defined: {
|
|
52
|
-
bin: bin_folder,
|
|
53
|
-
etc: bin_folder
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
File.write(generated_config_file_path, config.to_json)
|
|
58
|
-
@daemon_pid = Process.spawn(Installation.instance.path(:transferd), '--config', generated_config_file_path, out: "#{log_base}.out", err: "#{log_base}.err")
|
|
59
|
-
begin
|
|
60
|
-
# wait for process to initialize
|
|
61
|
-
Timeout.timeout(2.0) do
|
|
62
|
-
_, status = Process.wait2(@daemon_pid)
|
|
63
|
-
raise "transfer daemon exited with status #{status.exitstatus}. Check files: #{log_base}.out #{log_base}.err"
|
|
64
|
-
end
|
|
65
|
-
rescue Timeout::Error
|
|
66
|
-
nil
|
|
67
|
-
end
|
|
68
|
-
Log.log.debug{"daemon started with pid #{@daemon_pid}"}
|
|
69
|
-
Process.detach(@daemon_pid) if @options[:keep]
|
|
70
|
-
if daemon_uri.port.eql?(0)
|
|
71
|
-
# if port is zero, a dynamic port was created, get it
|
|
72
|
-
File.open("#{log_base}.out", 'r') do |file|
|
|
73
|
-
file.each_line do |line|
|
|
74
|
-
if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
|
|
75
|
-
daemon_uri.port = m[2].to_i
|
|
76
|
-
# no "break" , need to keep last one
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
retry
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def start_transfer(transfer_spec, token_regenerator: nil)
|
|
86
|
-
# create a transfer request
|
|
87
|
-
transfer_request = Transfersdk::TransferRequest.new(
|
|
88
|
-
transferType: Transfersdk::TransferType::FILE_REGULAR, # transfer type (file/stream)
|
|
89
|
-
config: Transfersdk::TransferConfig.new, # transfer configuration
|
|
90
|
-
transferSpec: transfer_spec.to_json) # transfer definition
|
|
91
|
-
# send start transfer request to the transfer manager daemon
|
|
92
|
-
start_transfer_response = @transfer_client.start_transfer(transfer_request)
|
|
93
|
-
Log.log.debug{"start transfer response #{start_transfer_response}"}
|
|
94
|
-
@transfer_id = start_transfer_response.transferId
|
|
95
|
-
Log.log.debug{"transfer started with id #{@transfer_id}"}
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def wait_for_transfers_completion
|
|
99
|
-
# set to true when we know the total size of the transfer
|
|
100
|
-
session_started = false
|
|
101
|
-
bytes_expected = nil
|
|
102
|
-
# monitor transfer status
|
|
103
|
-
@transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
|
|
104
|
-
Log.log.debug{Log.dump(:response, response.to_h)}
|
|
105
|
-
# Log.log.debug{"#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}"}
|
|
106
|
-
case response.status
|
|
107
|
-
when :RUNNING
|
|
108
|
-
if !session_started
|
|
109
|
-
notify_progress(session_id: @transfer_id, type: :session_start)
|
|
110
|
-
session_started = true
|
|
111
|
-
end
|
|
112
|
-
if bytes_expected.nil? &&
|
|
113
|
-
!response.sessionInfo.preTransferBytes.eql?(0)
|
|
114
|
-
bytes_expected = response.sessionInfo.preTransferBytes
|
|
115
|
-
notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
|
|
116
|
-
end
|
|
117
|
-
notify_progress(type: :transfer, session_id: @transfer_id, info: response.transferInfo.bytesTransferred)
|
|
118
|
-
when :COMPLETED
|
|
119
|
-
notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
|
|
120
|
-
notify_progress(type: :end, session_id: @transfer_id)
|
|
121
|
-
break
|
|
122
|
-
when :FAILED, :CANCELED
|
|
123
|
-
notify_progress(type: :end, session_id: @transfer_id)
|
|
124
|
-
raise Fasp::Error, JSON.parse(response.message)['Description']
|
|
125
|
-
when :QUEUED, :UNKNOWN_STATUS, :PAUSED, :ORPHANED
|
|
126
|
-
notify_progress(session_id: nil, type: :pre_start, info: response.status.to_s.downcase)
|
|
127
|
-
else
|
|
128
|
-
Log.log.error{"unknown status#{response.status}"}
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
# TODO: return status
|
|
132
|
-
return []
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def shutdown
|
|
136
|
-
if !@options[:keep] && !@daemon_pid.nil?
|
|
137
|
-
Log.log.debug("stopping daemon #{@daemon_pid}")
|
|
138
|
-
Process.kill('INT', @daemon_pid)
|
|
139
|
-
_, status = Process.wait2(@daemon_pid)
|
|
140
|
-
Log.log.debug("daemon stopped #{status}")
|
|
141
|
-
@daemon_pid = nil
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'singleton'
|
|
4
|
-
require 'aspera/log'
|
|
5
|
-
|
|
6
|
-
module Aspera
|
|
7
|
-
module Fasp
|
|
8
|
-
# implements a simple resume policy
|
|
9
|
-
class ResumePolicy
|
|
10
|
-
# list of supported parameters and default values
|
|
11
|
-
DEFAULTS = {
|
|
12
|
-
iter_max: 7,
|
|
13
|
-
sleep_initial: 2,
|
|
14
|
-
sleep_factor: 2,
|
|
15
|
-
sleep_max: 60
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
|
-
# @param params see DEFAULTS
|
|
19
|
-
def initialize(params=nil)
|
|
20
|
-
@parameters = DEFAULTS.dup
|
|
21
|
-
if !params.nil?
|
|
22
|
-
raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
|
|
23
|
-
params.each do |k, v|
|
|
24
|
-
raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.key?(k)
|
|
25
|
-
raise "#{k} must be Integer" unless v.is_a?(Integer)
|
|
26
|
-
@parameters[k] = v
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
Log.log.debug{"resume params=#{@parameters}"}
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# calls block a number of times (resumes) until success or limit reached
|
|
33
|
-
# this is re-entrant, one resumer can handle multiple transfers in //
|
|
34
|
-
def execute_with_resume
|
|
35
|
-
raise 'block mandatory' unless block_given?
|
|
36
|
-
# maximum of retry
|
|
37
|
-
remaining_resumes = @parameters[:iter_max]
|
|
38
|
-
sleep_seconds = @parameters[:sleep_initial]
|
|
39
|
-
Log.log.debug{"retries=#{remaining_resumes}"}
|
|
40
|
-
# try to send the file until ascp is successful
|
|
41
|
-
loop do
|
|
42
|
-
Log.log.debug('transfer starting')
|
|
43
|
-
begin
|
|
44
|
-
# call provided block
|
|
45
|
-
yield
|
|
46
|
-
break
|
|
47
|
-
rescue Fasp::Error => e
|
|
48
|
-
Log.log.warn{"An error occurred: #{e.message}"}
|
|
49
|
-
# failure in ascp
|
|
50
|
-
if e.retryable?
|
|
51
|
-
# exit if we exceed the max number of retry
|
|
52
|
-
raise Fasp::Error, "Maximum number of retry reached (#{@parameters[:iter_max]})" if remaining_resumes <= 0
|
|
53
|
-
else
|
|
54
|
-
# give one chance only to non retryable errors
|
|
55
|
-
unless remaining_resumes.eql?(@parameters[:iter_max])
|
|
56
|
-
Log.log.error('non-retryable error'.red.blink)
|
|
57
|
-
raise e
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# take this retry in account
|
|
63
|
-
remaining_resumes -= 1
|
|
64
|
-
Log.log.warn{"Resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})"}
|
|
65
|
-
|
|
66
|
-
# wait a bit before retrying, maybe network condition will be better
|
|
67
|
-
sleep(sleep_seconds)
|
|
68
|
-
|
|
69
|
-
# increase retry period
|
|
70
|
-
sleep_seconds *= @parameters[:sleep_factor]
|
|
71
|
-
# cap value
|
|
72
|
-
sleep_seconds = @parameters[:sleep_max] if sleep_seconds > @parameters[:sleep_max]
|
|
73
|
-
end # loop
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|