td-client 0.8.85 → 0.9.0dev1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/td/client.rb +8 -16
- data/lib/td/client/api.rb +46 -62
- data/lib/td/client/api/bulk_import.rb +2 -1
- data/lib/td/client/api/bulk_load.rb +3 -3
- data/lib/td/client/api/export.rb +0 -12
- data/lib/td/client/api/import.rb +2 -3
- data/lib/td/client/api/job.rb +71 -145
- data/lib/td/client/api/schedule.rb +1 -1
- data/lib/td/client/api_error.rb +0 -5
- data/lib/td/client/model.rb +28 -91
- data/lib/td/client/version.rb +1 -1
- data/spec/spec_helper.rb +5 -5
- data/spec/td/client/account_api_spec.rb +5 -5
- data/spec/td/client/api_spec.rb +51 -69
- data/spec/td/client/api_ssl_connection_spec.rb +1 -1
- data/spec/td/client/bulk_import_spec.rb +29 -28
- data/spec/td/client/bulk_load_spec.rb +35 -60
- data/spec/td/client/db_api_spec.rb +1 -1
- data/spec/td/client/export_api_spec.rb +1 -11
- data/spec/td/client/import_api_spec.rb +10 -85
- data/spec/td/client/job_api_spec.rb +61 -567
- data/spec/td/client/model_job_spec.rb +10 -27
- data/spec/td/client/model_schedule_spec.rb +2 -2
- data/spec/td/client/partial_delete_api_spec.rb +1 -1
- data/spec/td/client/result_api_spec.rb +3 -3
- data/spec/td/client/sched_api_spec.rb +4 -12
- data/spec/td/client/server_status_api_spec.rb +2 -2
- data/spec/td/client/spec_resources.rb +0 -1
- data/spec/td/client/table_api_spec.rb +14 -14
- data/spec/td/client/user_api_spec.rb +12 -12
- data/spec/td/client_sched_spec.rb +6 -31
- data/spec/td/client_spec.rb +0 -1
- metadata +97 -42
- data/spec/td/client/api_error_spec.rb +0 -77
- data/spec/td/client/model_schema_spec.rb +0 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbabfd3204808160bd8fd4f5a7cd3c5c48eb1b11
|
4
|
+
data.tar.gz: bd585ffc36231bc199d78b36f0e67a8205d6939a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d88e942c5fa4cdb4aa793419d6213ef80b048449e9383eef3cced73e30dc4f8b0a4f8ba39e2ea13ad5ce4fafb462b13f5e48acd213c87e3e9cf52b02a3f9470c
|
7
|
+
data.tar.gz: 356cfac4a8a32e3c574b0f5928ad80bf75e040952fc438c632b7ef3e3e7310950253103870c62277b75632299febb670cf3baaade3aecb67f45123010587d13c
|
data/lib/td/client.rb
CHANGED
@@ -182,10 +182,10 @@ class Client
|
|
182
182
|
results = @api.list_jobs(from, to, status, conditions)
|
183
183
|
results.map {|job_id, type, status, query, start_at, end_at, cpu_time,
|
184
184
|
result_size, result_url, priority, retry_limit, org, db,
|
185
|
-
duration
|
185
|
+
duration|
|
186
186
|
Job.new(self, job_id, type, query, status, nil, nil, start_at, end_at, cpu_time,
|
187
187
|
result_size, nil, result_url, nil, priority, retry_limit, org, db,
|
188
|
-
duration
|
188
|
+
duration)
|
189
189
|
}
|
190
190
|
end
|
191
191
|
|
@@ -194,9 +194,9 @@ class Client
|
|
194
194
|
def job(job_id)
|
195
195
|
job_id = job_id.to_s
|
196
196
|
type, query, status, url, debug, start_at, end_at, cpu_time,
|
197
|
-
result_size, result_url, hive_result_schema, priority, retry_limit, org, db
|
197
|
+
result_size, result_url, hive_result_schema, priority, retry_limit, org, db = @api.show_job(job_id)
|
198
198
|
Job.new(self, job_id, type, query, status, url, debug, start_at, end_at, cpu_time,
|
199
|
-
result_size, nil, result_url, hive_result_schema, priority, retry_limit, org, db
|
199
|
+
result_size, nil, result_url, hive_result_schema, priority, retry_limit, org, db)
|
200
200
|
end
|
201
201
|
|
202
202
|
# @param [String] job_id
|
@@ -254,14 +254,6 @@ class Client
|
|
254
254
|
Job.new(self, job_id, :export, nil)
|
255
255
|
end
|
256
256
|
|
257
|
-
# @param [String] target_job_id
|
258
|
-
# @param [Hash] opts
|
259
|
-
# @return [Job]
|
260
|
-
def result_export(target_job_id, opts={})
|
261
|
-
job_id = @api.result_export(target_job_id, opts)
|
262
|
-
Job.new(self, job_id, :result_export, nil)
|
263
|
-
end
|
264
|
-
|
265
257
|
# @param [String] db_name
|
266
258
|
# @param [String] table_name
|
267
259
|
# @param [Fixnum] to
|
@@ -364,7 +356,7 @@ class Client
|
|
364
356
|
raise ArgumentError, "'cron' option is required" unless opts[:cron] || opts['cron']
|
365
357
|
raise ArgumentError, "'query' option is required" unless opts[:query] || opts['query']
|
366
358
|
start = @api.create_schedule(name, opts)
|
367
|
-
return
|
359
|
+
return Time.parse(start)
|
368
360
|
end
|
369
361
|
|
370
362
|
# @param [String] name
|
@@ -582,9 +574,9 @@ class Client
|
|
582
574
|
@api.bulk_load_show(name)
|
583
575
|
end
|
584
576
|
|
585
|
-
# name: String,
|
586
|
-
def bulk_load_update(name,
|
587
|
-
@api.bulk_load_update(name,
|
577
|
+
# name: String, job: BulkLoad -> BulkLoad
|
578
|
+
def bulk_load_update(name, job)
|
579
|
+
@api.bulk_load_update(name, job)
|
588
580
|
end
|
589
581
|
|
590
582
|
# name: String -> BulkLoad
|
data/lib/td/client/api.rb
CHANGED
@@ -36,19 +36,16 @@ class API
|
|
36
36
|
include API::Table
|
37
37
|
include API::User
|
38
38
|
|
39
|
-
DEFAULT_ENDPOINT = 'api.
|
40
|
-
DEFAULT_IMPORT_ENDPOINT = 'api-import.
|
39
|
+
DEFAULT_ENDPOINT = 'api.treasure-data.com'
|
40
|
+
DEFAULT_IMPORT_ENDPOINT = 'api-import.treasure-data.com'
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
NEW_DEFAULT_IMPORT_ENDPOINT = DEFAULT_IMPORT_ENDPOINT
|
45
|
-
OLD_ENDPOINT = 'api.treasure-data.com'
|
42
|
+
NEW_DEFAULT_ENDPOINT = 'api.treasuredata.com'
|
43
|
+
NEW_DEFAULT_IMPORT_ENDPOINT = 'api-import.treasuredata.com'
|
46
44
|
|
47
45
|
class IncompleteError < APIError; end
|
48
46
|
|
49
47
|
# @param [String] apikey
|
50
48
|
# @param [Hash] opts
|
51
|
-
# for backward compatibility
|
52
49
|
def initialize(apikey, opts={})
|
53
50
|
require 'json'
|
54
51
|
require 'time'
|
@@ -95,18 +92,12 @@ class API
|
|
95
92
|
# generic URI
|
96
93
|
@host, @port = endpoint.split(':', 2)
|
97
94
|
@port = @port.to_i
|
98
|
-
if opts[:ssl]
|
99
|
-
# for backward compatibility, old endpoint specified without ssl option, use http
|
100
|
-
#
|
101
|
-
# opts[:ssl] would be nil if user doesn't specify ssl options,
|
102
|
-
# but connecting to https is the new default behavior (since 0.9)
|
103
|
-
# so check ssl option by `if opts[:ssl] === false` instead of `if opts[:ssl]`
|
104
|
-
# that means if user desire to use http, give `:ssl => false` for initializer such as API.new("APIKEY", :ssl => false)
|
105
|
-
@port = 80 if @port == 0
|
106
|
-
@ssl = false
|
107
|
-
else
|
95
|
+
if opts[:ssl]
|
108
96
|
@port = 443 if @port == 0
|
109
97
|
@ssl = true
|
98
|
+
else
|
99
|
+
@port = 80 if @port == 0
|
100
|
+
@ssl = false
|
110
101
|
end
|
111
102
|
@base_path = ''
|
112
103
|
end
|
@@ -149,27 +140,15 @@ class API
|
|
149
140
|
end
|
150
141
|
|
151
142
|
name = name.to_s
|
152
|
-
if
|
153
|
-
|
154
|
-
raise ParameterValidationError,
|
155
|
-
"#{target.capitalize} name must be between #{min_len} and #{max_len} characters long. Got #{name.length} " +
|
156
|
-
(name.length == 1 ? "character" : "characters") + "."
|
157
|
-
end
|
158
|
-
else
|
159
|
-
if min_len == 1
|
160
|
-
if name.empty?
|
161
|
-
raise ParameterValidationError,
|
143
|
+
if name.empty?
|
144
|
+
raise ParameterValidationError,
|
162
145
|
"Empty #{target} name is not allowed"
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
"#{target.capitalize} name must be longer than #{min_len} characters. Got #{name.length} " +
|
146
|
+
end
|
147
|
+
if name.length < min_len || name.length > max_len
|
148
|
+
raise ParameterValidationError,
|
149
|
+
"#{target.capitalize} name must be between #{min_len} and #{max_len} characters long. Got #{name.length} " +
|
168
150
|
(name.length == 1 ? "character" : "characters") + "."
|
169
|
-
end
|
170
|
-
end
|
171
151
|
end
|
172
|
-
|
173
152
|
unless name =~ /^([a-z0-9_]+)$/
|
174
153
|
raise ParameterValidationError,
|
175
154
|
"#{target.capitalize} name must only consist of lower-case alpha-numeric characters and '_'."
|
@@ -195,18 +174,7 @@ class API
|
|
195
174
|
|
196
175
|
# @param [String] name
|
197
176
|
def self.validate_column_name(name)
|
198
|
-
|
199
|
-
name = name.to_s
|
200
|
-
if name.empty?
|
201
|
-
raise ParameterValidationError,
|
202
|
-
"Empty #{target} name is not allowed"
|
203
|
-
end
|
204
|
-
name
|
205
|
-
end
|
206
|
-
|
207
|
-
# @param [String] name
|
208
|
-
def self.validate_sql_alias_name(name)
|
209
|
-
validate_name("sql_alias", 1, nil, name)
|
177
|
+
validate_name("column", 1, 255, name)
|
210
178
|
end
|
211
179
|
|
212
180
|
# @param [String] name
|
@@ -231,6 +199,25 @@ class API
|
|
231
199
|
normalize_database_name(name)
|
232
200
|
end
|
233
201
|
|
202
|
+
# TODO support array types
|
203
|
+
# @param [String] name
|
204
|
+
def self.normalize_type_name(name)
|
205
|
+
case name
|
206
|
+
when /int/i, /integer/i
|
207
|
+
"int"
|
208
|
+
when /long/i, /bigint/i
|
209
|
+
"long"
|
210
|
+
when /string/i
|
211
|
+
"string"
|
212
|
+
when /float/i
|
213
|
+
"float"
|
214
|
+
when /double/i
|
215
|
+
"double"
|
216
|
+
else
|
217
|
+
raise "Type name must either of int, long, string float or double"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
234
221
|
# for fluent-plugin-td / td command to check table existence with import onlt user
|
235
222
|
# @return [String]
|
236
223
|
def self.create_empty_gz_data
|
@@ -254,7 +241,6 @@ private
|
|
254
241
|
do_get(url, params, &block)
|
255
242
|
end
|
256
243
|
end
|
257
|
-
|
258
244
|
# @param [String] url
|
259
245
|
# @param [Hash] params
|
260
246
|
# @yield [response]
|
@@ -309,7 +295,7 @@ private
|
|
309
295
|
retry_delay *= 2
|
310
296
|
redo # restart from beginning of do-while loop
|
311
297
|
end
|
312
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, SocketError, IncompleteError
|
298
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, SocketError, IncompleteError => e
|
313
299
|
if block_given?
|
314
300
|
raise e
|
315
301
|
end
|
@@ -605,15 +591,18 @@ private
|
|
605
591
|
end
|
606
592
|
|
607
593
|
def parse_error_response(res)
|
594
|
+
error = {}
|
595
|
+
|
608
596
|
begin
|
609
|
-
|
610
|
-
if
|
611
|
-
error['message']
|
597
|
+
js = JSON.load(res.body)
|
598
|
+
if js.nil?
|
599
|
+
error['message'] = res.reason
|
612
600
|
else
|
613
|
-
error
|
601
|
+
error['message'] = js['message'] || js['error']
|
602
|
+
error['stacktrace'] = js['stacktrace']
|
614
603
|
end
|
615
604
|
rescue JSON::ParserError
|
616
|
-
error
|
605
|
+
error['message'] = res.body
|
617
606
|
end
|
618
607
|
|
619
608
|
error
|
@@ -635,7 +624,6 @@ private
|
|
635
624
|
when "404"
|
636
625
|
NotFoundError
|
637
626
|
when "409"
|
638
|
-
message = "#{message}: conflicts_with job:#{error["details"]["conflicts_with"]}" if error["details"] && error["details"]["conflicts_with"]
|
639
627
|
AlreadyExistsError
|
640
628
|
when "401"
|
641
629
|
AuthError
|
@@ -647,15 +635,11 @@ private
|
|
647
635
|
end
|
648
636
|
end
|
649
637
|
|
650
|
-
|
651
|
-
|
652
|
-
exc = error_class.new(message, error['stacktrace'], error["details"]["conflicts_with"])
|
653
|
-
elsif error_class.method_defined?(:api_backtrace)
|
654
|
-
exc = error_class.new(message, error['stacktrace'])
|
638
|
+
if error_class.method_defined?(:api_backtrace)
|
639
|
+
raise error_class.new(message, error['stacktrace'])
|
655
640
|
else
|
656
|
-
|
641
|
+
raise error_class, message
|
657
642
|
end
|
658
|
-
raise exc
|
659
643
|
end
|
660
644
|
|
661
645
|
if ''.respond_to?(:encode)
|
@@ -161,7 +161,8 @@ module BulkImport
|
|
161
161
|
end
|
162
162
|
end
|
163
163
|
require File.expand_path('../compat_gzip_reader', File.dirname(__FILE__))
|
164
|
-
|
164
|
+
io = StringIO.new(Zlib::GzipReader.new(StringIO.new(body)).read)
|
165
|
+
u = MessagePack::Unpacker.new(io)
|
165
166
|
if block
|
166
167
|
begin
|
167
168
|
u.each(&block)
|
@@ -116,10 +116,10 @@ module BulkLoad
|
|
116
116
|
JSON.load(res.body)
|
117
117
|
end
|
118
118
|
|
119
|
-
# name: String,
|
120
|
-
def bulk_load_update(name,
|
119
|
+
# name: String, job: Hash -> Hash
|
120
|
+
def bulk_load_update(name, job)
|
121
121
|
path = session_path(name)
|
122
|
-
res = api { put(path,
|
122
|
+
res = api { put(path, job.to_json) }
|
123
123
|
unless res.ok?
|
124
124
|
raise_error("BulkLoadSession: #{name} update failed", res)
|
125
125
|
end
|
data/lib/td/client/api/export.rb
CHANGED
@@ -22,17 +22,5 @@ module Export
|
|
22
22
|
return js['job_id'].to_s
|
23
23
|
end
|
24
24
|
|
25
|
-
# => jobId:String
|
26
|
-
# @param [String] target_job_id
|
27
|
-
# @param [Hash] opts
|
28
|
-
# @return [String] job_id
|
29
|
-
def result_export(target_job_id, opts={})
|
30
|
-
code, body, res = post("/v3/job/result_export/#{target_job_id}", opts)
|
31
|
-
if code != "200"
|
32
|
-
raise_error("Result Export failed", res)
|
33
|
-
end
|
34
|
-
js = checked_json(body, %w[job_id])
|
35
|
-
return js['job_id'].to_s
|
36
|
-
end
|
37
25
|
end
|
38
26
|
end
|
data/lib/td/client/api/import.rb
CHANGED
@@ -21,9 +21,8 @@ module Import
|
|
21
21
|
opts = {}
|
22
22
|
if @host == DEFAULT_ENDPOINT
|
23
23
|
opts[:host] = DEFAULT_IMPORT_ENDPOINT
|
24
|
-
elsif @host ==
|
25
|
-
opts[:host] =
|
26
|
-
opts[:ssl] = false
|
24
|
+
elsif @host == NEW_DEFAULT_ENDPOINT
|
25
|
+
opts[:host] = NEW_DEFAULT_IMPORT_ENDPOINT
|
27
26
|
end
|
28
27
|
code, body, res = put(path, stream, size, opts)
|
29
28
|
if code[0] != ?2
|
data/lib/td/client/api/job.rb
CHANGED
@@ -36,10 +36,9 @@ module Job
|
|
36
36
|
priority = m['priority']
|
37
37
|
retry_limit = m['retry_limit']
|
38
38
|
duration = m['duration']
|
39
|
-
num_records = m['num_records']
|
40
39
|
result << [job_id, type, status, query, start_at, end_at, cpu_time,
|
41
40
|
result_size, result_url, priority, retry_limit, nil, database,
|
42
|
-
duration
|
41
|
+
duration]
|
43
42
|
}
|
44
43
|
return result
|
45
44
|
end
|
@@ -64,7 +63,6 @@ module Job
|
|
64
63
|
end_at = js['end_at']
|
65
64
|
cpu_time = js['cpu_time']
|
66
65
|
result_size = js['result_size'] # compressed result size in msgpack.gz format
|
67
|
-
num_records = js['num_records']
|
68
66
|
result = js['result'] # result target URL
|
69
67
|
hive_result_schema = (js['hive_result_schema'] || '')
|
70
68
|
if hive_result_schema.empty?
|
@@ -99,7 +97,7 @@ module Job
|
|
99
97
|
priority = js['priority']
|
100
98
|
retry_limit = js['retry_limit']
|
101
99
|
return [type, query, status, url, debug, start_at, end_at, cpu_time,
|
102
|
-
result_size, result, hive_result_schema, priority, retry_limit, nil, database
|
100
|
+
result_size, result, hive_result_schema, priority, retry_limit, nil, database]
|
103
101
|
end
|
104
102
|
|
105
103
|
# @param [String] job_id
|
@@ -117,13 +115,14 @@ module Job
|
|
117
115
|
# @param [String] job_id
|
118
116
|
# @return [Array]
|
119
117
|
def job_result(job_id)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
unpacker.feed_each(chunk) do |row|
|
124
|
-
result << row
|
125
|
-
end
|
118
|
+
code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'})
|
119
|
+
if code != "200"
|
120
|
+
raise_error("Get job result failed", res)
|
126
121
|
end
|
122
|
+
result = []
|
123
|
+
MessagePack::Unpacker.new.feed_each(body) {|row|
|
124
|
+
result << row
|
125
|
+
}
|
127
126
|
return result
|
128
127
|
end
|
129
128
|
|
@@ -134,17 +133,24 @@ module Job
|
|
134
133
|
# @param [IO] io
|
135
134
|
# @param [Proc] block
|
136
135
|
# @return [nil, String]
|
137
|
-
def job_result_format(job_id, format, io=nil)
|
136
|
+
def job_result_format(job_id, format, io=nil, &block)
|
138
137
|
if io
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
138
|
+
infl = nil
|
139
|
+
code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format}) {|res, chunk, current_total_chunk_size|
|
140
|
+
if res.code != 200
|
141
|
+
raise_error("Get job result failed", res)
|
142
|
+
end
|
143
|
+
|
144
|
+
infl ||= create_inflalte_or_null_inflate(res)
|
145
|
+
|
146
|
+
io.write infl.inflate(chunk)
|
147
|
+
block.call(current_total_chunk_size) if block_given?
|
148
|
+
}
|
143
149
|
nil
|
144
150
|
else
|
145
|
-
body =
|
146
|
-
|
147
|
-
|
151
|
+
code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
|
152
|
+
if code != "200"
|
153
|
+
raise_error("Get job result failed", res)
|
148
154
|
end
|
149
155
|
body
|
150
156
|
end
|
@@ -157,11 +163,22 @@ module Job
|
|
157
163
|
# @return [nil]
|
158
164
|
def job_result_each(job_id, &block)
|
159
165
|
upkr = MessagePack::Unpacker.new
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
166
|
+
infl = nil
|
167
|
+
|
168
|
+
get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'}) {|res, chunk, current_total_chunk_size|
|
169
|
+
if res.code != 200
|
170
|
+
raise_error("Get job result failed", res)
|
171
|
+
end
|
172
|
+
|
173
|
+
# default to decompressing the response since format is fixed to 'msgpack'
|
174
|
+
infl ||= create_inflate(res)
|
175
|
+
|
176
|
+
inflated_fragment = infl.inflate(chunk)
|
177
|
+
upkr.feed_each(inflated_fragment, &block)
|
178
|
+
}
|
164
179
|
nil
|
180
|
+
ensure
|
181
|
+
infl.close if infl
|
165
182
|
end
|
166
183
|
|
167
184
|
# block is optional and must accept 1 argument
|
@@ -169,30 +186,50 @@ module Job
|
|
169
186
|
# @param [String] job_id
|
170
187
|
# @param [Proc] block
|
171
188
|
# @return [nil]
|
172
|
-
def job_result_each_with_compr_size(job_id)
|
189
|
+
def job_result_each_with_compr_size(job_id, &block)
|
173
190
|
upkr = MessagePack::Unpacker.new
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
191
|
+
infl = nil
|
192
|
+
|
193
|
+
get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'}) {|res, chunk, current_total_chunk_size|
|
194
|
+
if res.code != 200
|
195
|
+
raise_error("Get job result failed", res)
|
196
|
+
end
|
197
|
+
|
198
|
+
# default to decompressing the response since format is fixed to 'msgpack'
|
199
|
+
infl ||= create_inflate(res)
|
200
|
+
|
201
|
+
inflated_fragment = infl.inflate(chunk)
|
202
|
+
upkr.feed_each(inflated_fragment) {|unpacked|
|
203
|
+
block.call(unpacked, current_total_chunk_size) if block_given?
|
178
204
|
}
|
179
|
-
|
205
|
+
}
|
180
206
|
nil
|
207
|
+
ensure
|
208
|
+
infl.close if infl
|
181
209
|
end
|
182
210
|
|
183
211
|
# @param [String] job_id
|
184
212
|
# @param [String] format
|
185
213
|
# @return [String]
|
186
|
-
def job_result_raw(job_id, format, io = nil)
|
187
|
-
body =
|
188
|
-
|
214
|
+
def job_result_raw(job_id, format, io = nil, &block)
|
215
|
+
body = nil
|
216
|
+
|
217
|
+
get("/v3/job/result/#{e job_id}", {'format'=>format}) {|res, chunk, current_total_chunk_size|
|
218
|
+
if res.code != 200
|
219
|
+
raise_error("Get job result failed", res)
|
220
|
+
end
|
221
|
+
|
189
222
|
if io
|
190
223
|
io.write(chunk)
|
191
|
-
|
224
|
+
block.call(current_total_chunk_size) if block_given?
|
192
225
|
else
|
193
|
-
body
|
226
|
+
if body
|
227
|
+
body += chunk
|
228
|
+
else
|
229
|
+
body = chunk
|
230
|
+
end
|
194
231
|
end
|
195
|
-
|
232
|
+
}
|
196
233
|
body
|
197
234
|
end
|
198
235
|
|
@@ -250,117 +287,6 @@ module Job
|
|
250
287
|
|
251
288
|
private
|
252
289
|
|
253
|
-
def validate_content_length_with_range(response, current_total_chunk_size)
|
254
|
-
if expected_size = response.header['Content-Range'][0]
|
255
|
-
expected_size = expected_size[/\d+$/].to_i
|
256
|
-
elsif expected_size = response.header['Content-Length'][0]
|
257
|
-
expected_size = expected_size.to_i
|
258
|
-
end
|
259
|
-
|
260
|
-
if expected_size.nil?
|
261
|
-
elsif current_total_chunk_size < expected_size
|
262
|
-
# too small
|
263
|
-
# NOTE:
|
264
|
-
# ext/openssl raises EOFError in case where underlying connection
|
265
|
-
# causes an error, but httpclient ignores it.
|
266
|
-
# https://github.com/nahi/httpclient/blob/v3.2.8/lib/httpclient/session.rb#L1003
|
267
|
-
raise EOFError, 'httpclient IncompleteError'
|
268
|
-
elsif current_total_chunk_size > expected_size
|
269
|
-
# too large
|
270
|
-
raise_error("Get job result failed", response)
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
def job_result_download(job_id, format='msgpack', autodecode=true)
|
275
|
-
client, header = new_client
|
276
|
-
client.send_timeout = @send_timeout
|
277
|
-
client.receive_timeout = @read_timeout
|
278
|
-
header['Accept-Encoding'] = 'deflate, gzip'
|
279
|
-
|
280
|
-
url = build_endpoint("/v3/job/result/#{e job_id}", @host)
|
281
|
-
params = {'format' => format}
|
282
|
-
|
283
|
-
unless ENV['TD_CLIENT_DEBUG'].nil?
|
284
|
-
puts "DEBUG: REST GET call:"
|
285
|
-
puts "DEBUG: header: " + header.to_s
|
286
|
-
puts "DEBUG: url: " + url.to_s
|
287
|
-
puts "DEBUG: params: " + params.to_s
|
288
|
-
end
|
289
|
-
|
290
|
-
# up to 7 retries with exponential (base 2) back-off starting at 'retry_delay'
|
291
|
-
retry_delay = @retry_delay
|
292
|
-
cumul_retry_delay = 0
|
293
|
-
current_total_chunk_size = 0
|
294
|
-
infl = nil
|
295
|
-
begin # LOOP of Network/Server errors
|
296
|
-
response = nil
|
297
|
-
client.get(url, params, header) do |res, chunk|
|
298
|
-
unless response
|
299
|
-
case res.status
|
300
|
-
when 200
|
301
|
-
if current_total_chunk_size != 0
|
302
|
-
# try to resume but the server returns 200
|
303
|
-
raise_error("Get job result failed", res)
|
304
|
-
end
|
305
|
-
when 206 # resuming
|
306
|
-
else
|
307
|
-
if res.status/100 == 5 && cumul_retry_delay < @max_cumul_retry_delay
|
308
|
-
$stderr.puts "Error #{res.status}: #{get_error(res)}. Retrying after #{retry_delay} seconds..."
|
309
|
-
sleep retry_delay
|
310
|
-
cumul_retry_delay += retry_delay
|
311
|
-
retry_delay *= 2
|
312
|
-
redo
|
313
|
-
end
|
314
|
-
raise_error("Get job result failed", res)
|
315
|
-
end
|
316
|
-
if infl.nil? && autodecode
|
317
|
-
case res.header['Content-Encoding'][0].to_s.downcase
|
318
|
-
when 'gzip'
|
319
|
-
infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
|
320
|
-
when 'deflate'
|
321
|
-
infl = Zlib::Inflate.new
|
322
|
-
end
|
323
|
-
end
|
324
|
-
end
|
325
|
-
response = res
|
326
|
-
current_total_chunk_size += chunk.bytesize
|
327
|
-
chunk = infl.inflate(chunk) if infl
|
328
|
-
yield chunk, current_total_chunk_size
|
329
|
-
end
|
330
|
-
|
331
|
-
# completed?
|
332
|
-
validate_content_length_with_range(response, current_total_chunk_size)
|
333
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, SocketError => e
|
334
|
-
if response # at least a chunk is downloaded
|
335
|
-
if etag = response.header['ETag'][0]
|
336
|
-
header['If-Range'] = etag
|
337
|
-
header['Range'] = "bytes=#{current_total_chunk_size}-"
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
$stderr.print "#{e.class}: #{e.message}. "
|
342
|
-
if cumul_retry_delay < @max_cumul_retry_delay
|
343
|
-
$stderr.puts "Retrying after #{retry_delay} seconds..."
|
344
|
-
sleep retry_delay
|
345
|
-
cumul_retry_delay += retry_delay
|
346
|
-
retry_delay *= 2
|
347
|
-
retry
|
348
|
-
end
|
349
|
-
raise
|
350
|
-
end
|
351
|
-
|
352
|
-
unless ENV['TD_CLIENT_DEBUG'].nil?
|
353
|
-
puts "DEBUG: REST GET response:"
|
354
|
-
puts "DEBUG: header: " + response.header.to_s
|
355
|
-
puts "DEBUG: status: " + response.code.to_s
|
356
|
-
puts "DEBUG: body: " + response.body.to_s
|
357
|
-
end
|
358
|
-
|
359
|
-
nil
|
360
|
-
ensure
|
361
|
-
infl.close if infl
|
362
|
-
end
|
363
|
-
|
364
290
|
class NullInflate
|
365
291
|
def inflate(chunk)
|
366
292
|
chunk
|