td-client 0.8.80 → 0.8.81
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/lib/td/client/api.rb +24 -49
- data/lib/td/client/api/job.rb +141 -69
- data/lib/td/client/version.rb +1 -1
- data/spec/td/client/api_spec.rb +2 -2
- data/spec/td/client/job_api_spec.rb +311 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6639c3d87e9a018b4d8270c64165afc64ddb5c1
|
4
|
+
data.tar.gz: f0d2a64b5cb60ec6bc0b43f53f6ea0f8136e236f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fe4f5336064479b42b5f867bd54014530aae360b175e3953aa3d82ee06822361d2c41219b9f64179791c08e918102de712ad323de39ef8f5e06ea2990d17e95
|
7
|
+
data.tar.gz: dd8dffeca422ea5d9e57ece36ed2655b4a2b57ef072dcf697dddd6f6c01d8a8a8ba04fd1f946d442982242d1028101830a8c6aefed676350ce168708c8d90324
|
data/lib/td/client/api.rb
CHANGED
@@ -235,19 +235,17 @@ private
|
|
235
235
|
|
236
236
|
# @param [String] url
|
237
237
|
# @param [Hash] params
|
238
|
-
# @param [Hash] opt
|
239
238
|
# @yield [response]
|
240
|
-
def get(url, params=nil,
|
239
|
+
def get(url, params=nil, &block)
|
241
240
|
guard_no_sslv3 do
|
242
|
-
do_get(url, params,
|
241
|
+
do_get(url, params, &block)
|
243
242
|
end
|
244
243
|
end
|
245
244
|
|
246
245
|
# @param [String] url
|
247
246
|
# @param [Hash] params
|
248
|
-
# @param [Hash] opt
|
249
247
|
# @yield [response]
|
250
|
-
def do_get(url, params=nil,
|
248
|
+
def do_get(url, params=nil, &block)
|
251
249
|
client, header = new_client
|
252
250
|
client.send_timeout = @send_timeout
|
253
251
|
client.receive_timeout = @read_timeout
|
@@ -270,49 +268,23 @@ private
|
|
270
268
|
# for both exceptions and 500+ errors retrying is enabled by default.
|
271
269
|
# The total number of retries cumulatively should not exceed 10 minutes / 600 seconds
|
272
270
|
response = nil
|
273
|
-
etag = nil
|
274
|
-
current_total_chunk_size = 0
|
275
|
-
body = String.new unless block_given?
|
276
271
|
begin # this block is to allow retry (redo) in the begin part of the begin-rescue block
|
277
272
|
begin
|
278
|
-
if
|
279
|
-
header['If-Range'] = etag
|
280
|
-
header['Range'] = "bytes=#{current_total_chunk_size}-"
|
281
|
-
else
|
282
|
-
etag = nil
|
273
|
+
if block
|
283
274
|
current_total_chunk_size = 0
|
284
|
-
body.clear if body
|
285
|
-
end
|
286
|
-
|
287
|
-
if block_given?
|
288
275
|
response = client.get(target, params, header) {|res, chunk|
|
289
|
-
current_total_chunk_size += chunk.bytesize
|
290
|
-
|
276
|
+
current_total_chunk_size += chunk.bytesize
|
277
|
+
block.call(res, chunk, current_total_chunk_size)
|
291
278
|
}
|
279
|
+
|
280
|
+
# XXX ext/openssl raises EOFError in case where underlying connection causes an error,
|
281
|
+
# and msgpack-ruby that used in block handles it as an end of stream == no exception.
|
282
|
+
# Therefor, check content size.
|
283
|
+
validate_content_length!(response, current_total_chunk_size) if @ssl
|
292
284
|
else
|
293
285
|
response = client.get(target, params, header)
|
294
|
-
if response.status == 200
|
295
|
-
current_total_chunk_size += response.body.bytesize
|
296
|
-
body << response.body
|
297
|
-
end
|
298
|
-
end
|
299
286
|
|
300
|
-
|
301
|
-
# but httpclient ignores it. Therefor, check content size.
|
302
|
-
# https://github.com/nahi/httpclient/issues/296
|
303
|
-
if expected_size = response.header['Content-Range'].first
|
304
|
-
expected_size = expected_size[/\d+$/]
|
305
|
-
else
|
306
|
-
expected_size = response.header['Content-Length'].first
|
307
|
-
end
|
308
|
-
if expected_size
|
309
|
-
expected_size = expected_size.to_i
|
310
|
-
if expected_size != current_total_chunk_size
|
311
|
-
if expected_size < current_total_chunk_size
|
312
|
-
etag = false
|
313
|
-
end
|
314
|
-
raise IncompleteError, "#{expected_size} bytes expected, but got #{current_total_chunk_size} bytes"
|
315
|
-
end
|
287
|
+
validate_content_length!(response, response.body.bytesize) if @ssl
|
316
288
|
end
|
317
289
|
|
318
290
|
status = response.code
|
@@ -325,9 +297,7 @@ private
|
|
325
297
|
redo # restart from beginning of do-while loop
|
326
298
|
end
|
327
299
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, SocketError, IncompleteError => e
|
328
|
-
if
|
329
|
-
etag = response.header['ETag'].first if etag != false
|
330
|
-
elsif block_given?
|
300
|
+
if block_given?
|
331
301
|
raise e
|
332
302
|
end
|
333
303
|
$stderr.print "#{e.class}: #{e.message}. "
|
@@ -353,24 +323,29 @@ private
|
|
353
323
|
puts "DEBUG: body: " + response.body.to_s
|
354
324
|
end
|
355
325
|
|
356
|
-
body = inflate_body(response
|
326
|
+
body = block ? response.body : inflate_body(response)
|
357
327
|
|
358
328
|
return [response.code.to_s, body, response]
|
359
329
|
end
|
360
330
|
|
361
|
-
def
|
362
|
-
|
331
|
+
def validate_content_length!(response, body_size)
|
332
|
+
content_length = response.header['Content-Length'].first
|
333
|
+
raise IncompleteError if @ssl && content_length && content_length.to_i != body_size
|
334
|
+
end
|
335
|
+
|
336
|
+
def inflate_body(response)
|
337
|
+
return response.body if (ce = response.header['Content-Encoding']).empty?
|
363
338
|
|
364
339
|
if ce.include?('gzip')
|
365
340
|
infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
|
366
341
|
begin
|
367
|
-
infl.inflate(body)
|
342
|
+
infl.inflate(response.body)
|
368
343
|
ensure
|
369
344
|
infl.close
|
370
345
|
end
|
371
346
|
else
|
372
347
|
# NOTE maybe for content-encoding is msgpack.gz ?
|
373
|
-
Zlib::Inflate.inflate(body)
|
348
|
+
Zlib::Inflate.inflate(response.body)
|
374
349
|
end
|
375
350
|
end
|
376
351
|
|
@@ -628,7 +603,7 @@ private
|
|
628
603
|
error['stacktrace'] = js['stacktrace']
|
629
604
|
end
|
630
605
|
rescue JSON::ParserError
|
631
|
-
error['message'] = res.body
|
606
|
+
error['message'] = res.body[0,1000].dump
|
632
607
|
end
|
633
608
|
|
634
609
|
error
|
data/lib/td/client/api/job.rb
CHANGED
@@ -115,14 +115,13 @@ module Job
|
|
115
115
|
# @param [String] job_id
|
116
116
|
# @return [Array]
|
117
117
|
def job_result(job_id)
|
118
|
-
code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'}, {:resume => true})
|
119
|
-
if code != "200"
|
120
|
-
raise_error("Get job result failed", res)
|
121
|
-
end
|
122
118
|
result = []
|
123
|
-
MessagePack::Unpacker.new
|
124
|
-
|
125
|
-
|
119
|
+
unpacker = MessagePack::Unpacker.new
|
120
|
+
job_result_download(job_id) do |chunk|
|
121
|
+
unpacker.feed_each(chunk) do |row|
|
122
|
+
result << row
|
123
|
+
end
|
124
|
+
end
|
126
125
|
return result
|
127
126
|
end
|
128
127
|
|
@@ -133,24 +132,17 @@ module Job
|
|
133
132
|
# @param [IO] io
|
134
133
|
# @param [Proc] block
|
135
134
|
# @return [nil, String]
|
136
|
-
def job_result_format(job_id, format, io=nil
|
135
|
+
def job_result_format(job_id, format, io=nil)
|
137
136
|
if io
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
}
|
137
|
+
job_result_download(job_id, format) do |chunk, total|
|
138
|
+
io.write chunk
|
139
|
+
yield total if block_given?
|
140
|
+
end
|
149
141
|
nil
|
150
142
|
else
|
151
|
-
|
152
|
-
|
153
|
-
|
143
|
+
body = String.new
|
144
|
+
job_result_download(job_id, format) do |chunk|
|
145
|
+
body << chunk
|
154
146
|
end
|
155
147
|
body
|
156
148
|
end
|
@@ -163,22 +155,11 @@ module Job
|
|
163
155
|
# @return [nil]
|
164
156
|
def job_result_each(job_id, &block)
|
165
157
|
upkr = MessagePack::Unpacker.new
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
}
|
158
|
+
# default to decompressing the response since format is fixed to 'msgpack'
|
159
|
+
job_result_download(job_id) do |chunk|
|
160
|
+
upkr.feed_each(chunk, &block)
|
161
|
+
end
|
179
162
|
nil
|
180
|
-
ensure
|
181
|
-
infl.close if infl
|
182
163
|
end
|
183
164
|
|
184
165
|
# block is optional and must accept 1 argument
|
@@ -186,50 +167,30 @@ module Job
|
|
186
167
|
# @param [String] job_id
|
187
168
|
# @param [Proc] block
|
188
169
|
# @return [nil]
|
189
|
-
def job_result_each_with_compr_size(job_id
|
170
|
+
def job_result_each_with_compr_size(job_id)
|
190
171
|
upkr = MessagePack::Unpacker.new
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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?
|
172
|
+
# default to decompressing the response since format is fixed to 'msgpack'
|
173
|
+
job_result_download(job_id) do |chunk, total|
|
174
|
+
upkr.feed_each(chunk) {|unpacked|
|
175
|
+
yield unpacked, total if block_given?
|
204
176
|
}
|
205
|
-
|
177
|
+
end
|
206
178
|
nil
|
207
|
-
ensure
|
208
|
-
infl.close if infl
|
209
179
|
end
|
210
180
|
|
211
181
|
# @param [String] job_id
|
212
182
|
# @param [String] format
|
213
183
|
# @return [String]
|
214
|
-
def job_result_raw(job_id, format, io = nil
|
215
|
-
body = nil
|
216
|
-
|
217
|
-
get("/v3/job/result/#{e job_id}", {'format'=>format}, {:resume => true}) {|res, chunk, current_total_chunk_size|
|
218
|
-
unless res.ok?
|
219
|
-
raise_error("Get job result failed", res)
|
220
|
-
end
|
221
|
-
|
184
|
+
def job_result_raw(job_id, format, io = nil)
|
185
|
+
body = io ? nil : String.new
|
186
|
+
job_result_download(job_id, format, false) do |chunk, total|
|
222
187
|
if io
|
223
188
|
io.write(chunk)
|
224
|
-
|
189
|
+
yield total if block_given?
|
225
190
|
else
|
226
|
-
|
227
|
-
body += chunk
|
228
|
-
else
|
229
|
-
body = chunk
|
230
|
-
end
|
191
|
+
body << chunk
|
231
192
|
end
|
232
|
-
|
193
|
+
end
|
233
194
|
body
|
234
195
|
end
|
235
196
|
|
@@ -287,6 +248,117 @@ module Job
|
|
287
248
|
|
288
249
|
private
|
289
250
|
|
251
|
+
def validate_content_length_with_range(response, current_total_chunk_size)
|
252
|
+
if expected_size = response.header['Content-Range'][0]
|
253
|
+
expected_size = expected_size[/\d+$/].to_i
|
254
|
+
elsif expected_size = response.header['Content-Length'][0]
|
255
|
+
expected_size = expected_size.to_i
|
256
|
+
end
|
257
|
+
|
258
|
+
if expected_size.nil?
|
259
|
+
elsif current_total_chunk_size < expected_size
|
260
|
+
# too small
|
261
|
+
# NOTE:
|
262
|
+
# ext/openssl raises EOFError in case where underlying connection
|
263
|
+
# causes an error, but httpclient ignores it.
|
264
|
+
# https://github.com/nahi/httpclient/blob/v3.2.8/lib/httpclient/session.rb#L1003
|
265
|
+
raise EOFError, 'httpclient IncompleteError'
|
266
|
+
elsif current_total_chunk_size > expected_size
|
267
|
+
# too large
|
268
|
+
raise_error("Get job result failed", response)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def job_result_download(job_id, format='msgpack', autodecode=true)
|
273
|
+
client, header = new_client
|
274
|
+
client.send_timeout = @send_timeout
|
275
|
+
client.receive_timeout = @read_timeout
|
276
|
+
header['Accept-Encoding'] = 'deflate, gzip'
|
277
|
+
|
278
|
+
url = build_endpoint("/v3/job/result/#{e job_id}", @host)
|
279
|
+
params = {'format' => format}
|
280
|
+
|
281
|
+
unless ENV['TD_CLIENT_DEBUG'].nil?
|
282
|
+
puts "DEBUG: REST GET call:"
|
283
|
+
puts "DEBUG: header: " + header.to_s
|
284
|
+
puts "DEBUG: url: " + url.to_s
|
285
|
+
puts "DEBUG: params: " + params.to_s
|
286
|
+
end
|
287
|
+
|
288
|
+
# up to 7 retries with exponential (base 2) back-off starting at 'retry_delay'
|
289
|
+
retry_delay = @retry_delay
|
290
|
+
cumul_retry_delay = 0
|
291
|
+
current_total_chunk_size = 0
|
292
|
+
infl = nil
|
293
|
+
begin # LOOP of Network/Server errors
|
294
|
+
response = nil
|
295
|
+
client.get(url, params, header) do |res, chunk|
|
296
|
+
unless response
|
297
|
+
case res.status
|
298
|
+
when 200
|
299
|
+
if current_total_chunk_size != 0
|
300
|
+
# try to resume but the server returns 200
|
301
|
+
raise_error("Get job result failed", res)
|
302
|
+
end
|
303
|
+
when 206 # resuming
|
304
|
+
else
|
305
|
+
if res.status/100 == 5 && cumul_retry_delay < @max_cumul_retry_delay
|
306
|
+
$stderr.puts "Error #{res.status}: #{get_error(res)}. Retrying after #{retry_delay} seconds..."
|
307
|
+
sleep retry_delay
|
308
|
+
cumul_retry_delay += retry_delay
|
309
|
+
retry_delay *= 2
|
310
|
+
redo
|
311
|
+
end
|
312
|
+
raise_error("Get job result failed", res)
|
313
|
+
end
|
314
|
+
if infl.nil? && autodecode
|
315
|
+
case res.header['Content-Encoding'][0].to_s.downcase
|
316
|
+
when 'gzip'
|
317
|
+
infl = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
|
318
|
+
when 'deflate'
|
319
|
+
infl = Zlib::Inflate.new
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
response = res
|
324
|
+
current_total_chunk_size += chunk.bytesize
|
325
|
+
chunk = infl.inflate(chunk) if infl
|
326
|
+
yield chunk, current_total_chunk_size
|
327
|
+
end
|
328
|
+
|
329
|
+
# completed?
|
330
|
+
validate_content_length_with_range(response, current_total_chunk_size)
|
331
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, SocketError => e
|
332
|
+
if response # at least a chunk is downloaded
|
333
|
+
if etag = response.header['ETag'][0]
|
334
|
+
header['If-Range'] = etag
|
335
|
+
header['Range'] = "bytes=#{current_total_chunk_size}-"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
$stderr.print "#{e.class}: #{e.message}. "
|
340
|
+
if cumul_retry_delay < @max_cumul_retry_delay
|
341
|
+
$stderr.puts "Retrying after #{retry_delay} seconds..."
|
342
|
+
sleep retry_delay
|
343
|
+
cumul_retry_delay += retry_delay
|
344
|
+
retry_delay *= 2
|
345
|
+
retry
|
346
|
+
end
|
347
|
+
raise
|
348
|
+
end
|
349
|
+
|
350
|
+
unless ENV['TD_CLIENT_DEBUG'].nil?
|
351
|
+
puts "DEBUG: REST GET response:"
|
352
|
+
puts "DEBUG: header: " + response.header.to_s
|
353
|
+
puts "DEBUG: status: " + response.code.to_s
|
354
|
+
puts "DEBUG: body: " + response.body.to_s
|
355
|
+
end
|
356
|
+
|
357
|
+
nil
|
358
|
+
ensure
|
359
|
+
infl.close if infl
|
360
|
+
end
|
361
|
+
|
290
362
|
class NullInflate
|
291
363
|
def inflate(chunk)
|
292
364
|
chunk
|
data/lib/td/client/version.rb
CHANGED
data/spec/td/client/api_spec.rb
CHANGED
@@ -178,7 +178,7 @@ describe API do
|
|
178
178
|
|
179
179
|
let(:api) { API.new(nil, endpoint: endpoint) }
|
180
180
|
let :packed do
|
181
|
-
s = StringIO.new
|
181
|
+
s = StringIO.new(String.new)
|
182
182
|
Zlib::GzipWriter.wrap(s) do |f|
|
183
183
|
f << ['hello', 'world'].to_json
|
184
184
|
end
|
@@ -195,7 +195,7 @@ describe API do
|
|
195
195
|
end
|
196
196
|
|
197
197
|
subject (:get_api_call) {
|
198
|
-
api.job_result_format(12345, 'json', StringIO.new)
|
198
|
+
api.job_result_format(12345, 'json', StringIO.new(String.new))
|
199
199
|
}
|
200
200
|
|
201
201
|
context 'without ssl' do
|
@@ -166,7 +166,7 @@ describe 'Job API' do
|
|
166
166
|
|
167
167
|
describe 'job_result' do
|
168
168
|
let :packed do
|
169
|
-
s = StringIO.new
|
169
|
+
s = StringIO.new(String.new)
|
170
170
|
pk = MessagePack::Packer.new(s)
|
171
171
|
pk.write('hello')
|
172
172
|
pk.write('world')
|
@@ -181,6 +181,74 @@ describe 'Job API' do
|
|
181
181
|
expect(api.job_result(12345)).to eq(['hello', 'world'])
|
182
182
|
end
|
183
183
|
|
184
|
+
it '200->200 cannot resume' do
|
185
|
+
sz = packed.bytesize / 3
|
186
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
187
|
+
with(:query => {'format' => 'msgpack'}).
|
188
|
+
to_return(
|
189
|
+
:headers => {
|
190
|
+
'Content-Length' => packed.bytesize,
|
191
|
+
'Etag' => '"abcdefghijklmn"',
|
192
|
+
},
|
193
|
+
:body => packed[0, sz]
|
194
|
+
)
|
195
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
196
|
+
with(
|
197
|
+
:headers => {
|
198
|
+
'If-Range' => '"abcdefghijklmn"',
|
199
|
+
'Range' => "bytes=#{sz}-",
|
200
|
+
},
|
201
|
+
:query => {'format' => 'msgpack'}
|
202
|
+
).
|
203
|
+
to_return(
|
204
|
+
:status => 200,
|
205
|
+
:headers => {
|
206
|
+
'Content-Length' => packed.bytesize - sz,
|
207
|
+
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
208
|
+
'Etag' => '"abcdefghijklmn"',
|
209
|
+
},
|
210
|
+
:body => packed
|
211
|
+
)
|
212
|
+
expect(api).to receive(:sleep).once
|
213
|
+
expect($stderr).to receive(:print)
|
214
|
+
expect($stderr).to receive(:puts)
|
215
|
+
expect{api.job_result(12345)}.to raise_error(TreasureData::APIError)
|
216
|
+
end
|
217
|
+
|
218
|
+
it '200->403 cannot resume' do
|
219
|
+
sz = packed.bytesize / 3
|
220
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
221
|
+
with(:query => {'format' => 'msgpack'}).
|
222
|
+
to_return(
|
223
|
+
:headers => {
|
224
|
+
'Content-Length' => packed.bytesize,
|
225
|
+
'Etag' => '"abcdefghijklmn"',
|
226
|
+
},
|
227
|
+
:body => packed[0, sz]
|
228
|
+
)
|
229
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
230
|
+
with(
|
231
|
+
:headers => {
|
232
|
+
'If-Range' => '"abcdefghijklmn"',
|
233
|
+
'Range' => "bytes=#{sz}-",
|
234
|
+
},
|
235
|
+
:query => {'format' => 'msgpack'}
|
236
|
+
).
|
237
|
+
to_return(
|
238
|
+
:status => 403,
|
239
|
+
:headers => {
|
240
|
+
'Content-Length' => packed.bytesize - sz,
|
241
|
+
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
242
|
+
'Etag' => '"abcdefghijklmn"',
|
243
|
+
},
|
244
|
+
:body => packed
|
245
|
+
)
|
246
|
+
expect(api).to receive(:sleep).once
|
247
|
+
expect($stderr).to receive(:print)
|
248
|
+
expect($stderr).to receive(:puts)
|
249
|
+
expect{api.job_result(12345)}.to raise_error(TreasureData::APIError)
|
250
|
+
end
|
251
|
+
|
184
252
|
it 'can resume' do
|
185
253
|
sz = packed.bytesize / 3
|
186
254
|
stub_api_request(:get, '/v3/job/result/12345').
|
@@ -201,6 +269,7 @@ describe 'Job API' do
|
|
201
269
|
:query => {'format' => 'msgpack'}
|
202
270
|
).
|
203
271
|
to_return(
|
272
|
+
:status => 206,
|
204
273
|
:headers => {
|
205
274
|
'Content-Length' => packed.bytesize - sz,
|
206
275
|
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
@@ -213,11 +282,63 @@ describe 'Job API' do
|
|
213
282
|
expect($stderr).to receive(:puts)
|
214
283
|
expect(api.job_result(12345)).to eq ['hello', 'world']
|
215
284
|
end
|
285
|
+
|
286
|
+
it '200->500->206 can resume' do
|
287
|
+
sz = packed.bytesize / 3
|
288
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
289
|
+
with(:query => {'format' => 'msgpack'}).
|
290
|
+
to_return(
|
291
|
+
:headers => {
|
292
|
+
'Content-Length' => packed.bytesize,
|
293
|
+
'Etag' => '"abcdefghijklmn"',
|
294
|
+
},
|
295
|
+
:body => packed[0, sz]
|
296
|
+
)
|
297
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
298
|
+
with(
|
299
|
+
:headers => {
|
300
|
+
'If-Range' => '"abcdefghijklmn"',
|
301
|
+
'Range' => "bytes=#{sz}-",
|
302
|
+
},
|
303
|
+
:query => {'format' => 'msgpack'}
|
304
|
+
).
|
305
|
+
to_return(
|
306
|
+
:status => 500,
|
307
|
+
:headers => {
|
308
|
+
'Content-Length' => packed.bytesize - sz,
|
309
|
+
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
310
|
+
'Etag' => '"abcdefghijklmn"',
|
311
|
+
},
|
312
|
+
:body => packed
|
313
|
+
)
|
314
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
315
|
+
with(
|
316
|
+
:headers => {
|
317
|
+
'If-Range' => '"abcdefghijklmn"',
|
318
|
+
'Range' => "bytes=#{sz}-",
|
319
|
+
},
|
320
|
+
:query => {'format' => 'msgpack'}
|
321
|
+
).
|
322
|
+
to_return(
|
323
|
+
:status => 206,
|
324
|
+
:headers => {
|
325
|
+
'Content-Length' => packed.bytesize - sz,
|
326
|
+
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
327
|
+
'Etag' => '"abcdefghijklmn"',
|
328
|
+
},
|
329
|
+
:body => packed[sz, packed.bytesize - sz]
|
330
|
+
)
|
331
|
+
expect(api).to receive(:sleep).once
|
332
|
+
expect($stderr).to receive(:print)
|
333
|
+
expect($stderr).to receive(:puts)
|
334
|
+
expect(api.job_result(12345)).to eq ['hello', 'world']
|
335
|
+
end
|
336
|
+
|
216
337
|
end
|
217
338
|
|
218
339
|
describe 'job_result_format' do
|
219
340
|
let :packed do
|
220
|
-
s = StringIO.new
|
341
|
+
s = StringIO.new(String.new)
|
221
342
|
Zlib::GzipWriter.wrap(s) do |f|
|
222
343
|
f << ['hello', 'world'].to_json
|
223
344
|
end
|
@@ -286,6 +407,7 @@ describe 'Job API' do
|
|
286
407
|
:query => {'format' => 'json'}
|
287
408
|
).
|
288
409
|
to_return(
|
410
|
+
:status => 206,
|
289
411
|
:headers => {
|
290
412
|
'Content-Encoding' => 'gzip',
|
291
413
|
'Content-Length' => packed.bytesize-sz,
|
@@ -312,7 +434,7 @@ describe 'Job API' do
|
|
312
434
|
|
313
435
|
describe 'job_result_each' do
|
314
436
|
let :packed do
|
315
|
-
s = StringIO.new
|
437
|
+
s = StringIO.new(String.new)
|
316
438
|
Zlib::GzipWriter.wrap(s) do |f|
|
317
439
|
pk = MessagePack::Packer.new(f)
|
318
440
|
pk.write('hello')
|
@@ -357,6 +479,7 @@ describe 'Job API' do
|
|
357
479
|
:query => {'format' => 'msgpack'}
|
358
480
|
).
|
359
481
|
to_return(
|
482
|
+
:status => 206,
|
360
483
|
:headers => {
|
361
484
|
'Content-Length' => packed.bytesize-sz,
|
362
485
|
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
@@ -386,7 +509,7 @@ describe 'Job API' do
|
|
386
509
|
# pk.flush
|
387
510
|
# end
|
388
511
|
# s.string
|
389
|
-
"\
|
512
|
+
"\x1F\x8B\b\x00#\xA1\x93T\x00\x03[\x9A\x91\x9A\x93\x93\xBF\xB4<\xBF('\x05\x00e 0\xB3\f\x00\x00\x00".force_encoding(Encoding::ASCII_8BIT)
|
390
513
|
end
|
391
514
|
|
392
515
|
it 'yields job result for each row with progress' do
|
@@ -424,6 +547,7 @@ describe 'Job API' do
|
|
424
547
|
:query => {'format' => 'msgpack'}
|
425
548
|
).
|
426
549
|
to_return(
|
550
|
+
:status => 206,
|
427
551
|
:headers => {
|
428
552
|
'Content-Length' => packed.bytesize - sz,
|
429
553
|
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
@@ -498,6 +622,7 @@ describe 'Job API' do
|
|
498
622
|
:query => {'format' => 'msgpack.gz'}
|
499
623
|
).
|
500
624
|
to_return(
|
625
|
+
:status => 206,
|
501
626
|
:headers => {
|
502
627
|
'Content-Length' => packed.bytesize - sz,
|
503
628
|
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
@@ -508,7 +633,7 @@ describe 'Job API' do
|
|
508
633
|
expect(api).to receive(:sleep).once
|
509
634
|
expect($stderr).to receive(:print)
|
510
635
|
expect($stderr).to receive(:puts)
|
511
|
-
sio = StringIO.new(
|
636
|
+
sio = StringIO.new(String.new)
|
512
637
|
api.job_result_raw(12345, 'msgpack.gz', sio)
|
513
638
|
expect(sio.string).to eq(packed)
|
514
639
|
end
|
@@ -521,4 +646,185 @@ describe 'Job API' do
|
|
521
646
|
expect(api.kill(12345)).to eq('status')
|
522
647
|
end
|
523
648
|
end
|
649
|
+
|
650
|
+
describe 'job_result_download' do
|
651
|
+
let (:data){ [[1, 'hello', nil], [2, 'world', true], [3, '!', false]] }
|
652
|
+
let :formatted do
|
653
|
+
case format
|
654
|
+
when 'json'
|
655
|
+
data.map{|a| JSON(a) }.join("\n")
|
656
|
+
when 'msgpack'
|
657
|
+
pk = MessagePack::Packer.new
|
658
|
+
data.each{|x| pk.write(x) }
|
659
|
+
pk.to_str
|
660
|
+
else
|
661
|
+
raise
|
662
|
+
end
|
663
|
+
end
|
664
|
+
let :gziped do
|
665
|
+
s = StringIO.new(String.new)
|
666
|
+
Zlib::GzipWriter.wrap(s) do |f|
|
667
|
+
f.write formatted
|
668
|
+
end
|
669
|
+
s.string
|
670
|
+
end
|
671
|
+
let :deflated do
|
672
|
+
Zlib.deflate(formatted)
|
673
|
+
end
|
674
|
+
subject do
|
675
|
+
str = ''
|
676
|
+
api.__send__(:job_result_download, 12345, format){|x| str << x }
|
677
|
+
str
|
678
|
+
end
|
679
|
+
context '200' do
|
680
|
+
before do
|
681
|
+
sz = packed.bytesize / 3
|
682
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
683
|
+
with(:query => {'format' => format}).
|
684
|
+
to_return(
|
685
|
+
:headers => {
|
686
|
+
'Content-Encoding' => content_encoding,
|
687
|
+
'Content-Length' => packed.bytesize,
|
688
|
+
'Etag' => '"abcdefghijklmn"',
|
689
|
+
},
|
690
|
+
:body => packed
|
691
|
+
)
|
692
|
+
expect(api).not_to receive(:sleep)
|
693
|
+
expect($stderr).not_to receive(:print)
|
694
|
+
expect($stderr).not_to receive(:puts)
|
695
|
+
end
|
696
|
+
context 'Content-Encoding: gzip' do
|
697
|
+
let (:content_encoding){ 'gzip' }
|
698
|
+
let (:packed){ gziped }
|
699
|
+
context 'msgpack' do
|
700
|
+
let (:format){ 'msgpack' }
|
701
|
+
it { is_expected.to eq formatted }
|
702
|
+
end
|
703
|
+
context 'json' do
|
704
|
+
let (:format){ 'json' }
|
705
|
+
it { is_expected.to eq formatted }
|
706
|
+
end
|
707
|
+
end
|
708
|
+
context 'Content-Encoding: deflate' do
|
709
|
+
let (:content_encoding){ 'deflate' }
|
710
|
+
let (:packed){ deflated }
|
711
|
+
context 'msgpack' do
|
712
|
+
let (:format){ 'msgpack' }
|
713
|
+
it { is_expected.to eq formatted }
|
714
|
+
end
|
715
|
+
context 'json' do
|
716
|
+
let (:format){ 'json' }
|
717
|
+
it { is_expected.to eq formatted }
|
718
|
+
end
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
context '200 -> 206' do
|
723
|
+
before do
|
724
|
+
sz = packed.bytesize / 3
|
725
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
726
|
+
with(:query => {'format' => format}).
|
727
|
+
to_return(
|
728
|
+
:headers => {
|
729
|
+
'Content-Encoding' => content_encoding,
|
730
|
+
'Content-Length' => packed.bytesize,
|
731
|
+
'Etag' => '"abcdefghijklmn"',
|
732
|
+
},
|
733
|
+
:body => packed[0, sz]
|
734
|
+
)
|
735
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
736
|
+
with(
|
737
|
+
:headers => {
|
738
|
+
'If-Range' => '"abcdefghijklmn"',
|
739
|
+
'Range' => "bytes=#{sz}-",
|
740
|
+
},
|
741
|
+
:query => {'format' => format}
|
742
|
+
).
|
743
|
+
to_return(
|
744
|
+
:status => 206,
|
745
|
+
:headers => {
|
746
|
+
'Content-Encoding' => content_encoding,
|
747
|
+
'Content-Length' => packed.bytesize - sz,
|
748
|
+
'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
|
749
|
+
'Etag' => '"abcdefghijklmn"',
|
750
|
+
},
|
751
|
+
:body => packed[sz, packed.bytesize - sz]
|
752
|
+
)
|
753
|
+
expect(api).to receive(:sleep).once
|
754
|
+
allow($stderr).to receive(:print)
|
755
|
+
allow($stderr).to receive(:puts)
|
756
|
+
end
|
757
|
+
context 'Content-Encoding: gzip' do
|
758
|
+
let (:content_encoding){ 'gzip' }
|
759
|
+
let (:packed){ gziped }
|
760
|
+
context 'msgpack' do
|
761
|
+
let (:format){ 'msgpack' }
|
762
|
+
it { is_expected.to eq formatted }
|
763
|
+
end
|
764
|
+
context 'json' do
|
765
|
+
let (:format){ 'json' }
|
766
|
+
it { is_expected.to eq formatted }
|
767
|
+
end
|
768
|
+
end
|
769
|
+
context 'Content-Encoding: deflate' do
|
770
|
+
let (:content_encoding){ 'deflate' }
|
771
|
+
let (:packed){ deflated }
|
772
|
+
context 'msgpack' do
|
773
|
+
let (:format){ 'msgpack' }
|
774
|
+
it { is_expected.to eq formatted }
|
775
|
+
end
|
776
|
+
context 'json' do
|
777
|
+
let (:format){ 'json' }
|
778
|
+
it { is_expected.to eq formatted }
|
779
|
+
end
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
context 'without autodecode' do
|
784
|
+
before do
|
785
|
+
sz = packed.bytesize / 3
|
786
|
+
stub_api_request(:get, '/v3/job/result/12345').
|
787
|
+
with(:query => {'format' => format}).
|
788
|
+
to_return(
|
789
|
+
:headers => {
|
790
|
+
'Content-Length' => packed.bytesize,
|
791
|
+
'Etag' => '"abcdefghijklmn"',
|
792
|
+
},
|
793
|
+
:body => packed
|
794
|
+
)
|
795
|
+
expect(api).not_to receive(:sleep)
|
796
|
+
expect($stderr).not_to receive(:print)
|
797
|
+
expect($stderr).not_to receive(:puts)
|
798
|
+
end
|
799
|
+
subject do
|
800
|
+
str = ''
|
801
|
+
api.__send__(:job_result_download, 12345, format, false){|x| str << x }
|
802
|
+
str
|
803
|
+
end
|
804
|
+
context 'Content-Encoding: gzip' do
|
805
|
+
let (:content_encoding){ 'gzip' }
|
806
|
+
let (:packed){ gziped }
|
807
|
+
context 'msgpack' do
|
808
|
+
let (:format){ 'msgpack' }
|
809
|
+
it { is_expected.to eq packed }
|
810
|
+
end
|
811
|
+
context 'json' do
|
812
|
+
let (:format){ 'json' }
|
813
|
+
it { is_expected.to eq packed }
|
814
|
+
end
|
815
|
+
end
|
816
|
+
context 'Content-Encoding: deflate' do
|
817
|
+
let (:content_encoding){ 'deflate' }
|
818
|
+
let (:packed){ deflated }
|
819
|
+
context 'msgpack' do
|
820
|
+
let (:format){ 'msgpack' }
|
821
|
+
it { is_expected.to eq packed }
|
822
|
+
end
|
823
|
+
context 'json' do
|
824
|
+
let (:format){ 'json' }
|
825
|
+
it { is_expected.to eq packed }
|
826
|
+
end
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
524
830
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: td-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.81
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Treasure Data, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|