aliyun-sdk 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,217 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module Aliyun
8
+ module OSS
9
+
10
+ describe "Resumable download" do
11
+
12
+ before :all do
13
+ @endpoint = 'oss-cn-hangzhou.aliyuncs.com'
14
+ @bucket_name = 'rubysdk-bucket'
15
+ @object_key = 'resumable_file'
16
+ @bucket = Client.new(
17
+ :endpoint => @endpoint,
18
+ :access_key_id => 'xxx',
19
+ :access_key_secret => 'yyy').get_bucket(@bucket_name)
20
+
21
+ @file = './download_file'
22
+ end
23
+
24
+ before :each do
25
+ File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt")
26
+ end
27
+
28
+ def object_url
29
+ "#{@bucket_name}.#{@endpoint}/#{@object_key}"
30
+ end
31
+
32
+ def mock_object(i)
33
+ i.to_s.rjust(9, '0') + "\n"
34
+ end
35
+
36
+ def mock_range(i)
37
+ "bytes=#{(i-1)*10}-#{i*10 - 1}"
38
+ end
39
+
40
+ def mock_error(code, message)
41
+ Nokogiri::XML::Builder.new do |xml|
42
+ xml.Error {
43
+ xml.Code code
44
+ xml.Message message
45
+ xml.RequestId '0000'
46
+ }
47
+ end.to_xml
48
+ end
49
+
50
+ it "should download file when all goes well" do
51
+ return_headers = {
52
+ 'x-oss-object-type' => 'Normal',
53
+ 'ETag' => 'xxxyyyzzz',
54
+ 'Content-Length' => 100,
55
+ 'Last-Modified' => Time.now.rfc822
56
+ }
57
+
58
+ # get object meta
59
+ stub_request(:head, object_url).to_return(:headers => return_headers)
60
+
61
+ # get object by range
62
+ stub_request(:get, object_url)
63
+ .to_return((1..10).map{ |i| {:body => mock_object(i)} })
64
+
65
+ prg = []
66
+ @bucket.resumable_download(
67
+ @object_key, @file, :part_size => 10) { |p| prg << p }
68
+
69
+ ranges = []
70
+ expect(WebMock).to have_requested(:get, object_url).with{ |req|
71
+ ranges << req.headers['Range']
72
+ }.times(10)
73
+
74
+ expect(ranges).to match_array((1..10).map{ |i| mock_range(i) })
75
+ expect(File.exist?("#{@file}.cpt")).to be false
76
+ expect(Dir.glob("#{@file}.part.*").empty?).to be true
77
+
78
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
79
+ expect(prg).to match_array((1..10).map {|i| i.to_f / 10 })
80
+ end
81
+
82
+ it "should resume when download part fails" do
83
+ return_headers = {
84
+ 'x-oss-object-type' => 'Normal',
85
+ 'ETag' => 'xxxyyyzzz',
86
+ 'Content-Length' => 100,
87
+ 'Last-Modified' => Time.now.rfc822
88
+ }
89
+
90
+ # get object meta
91
+ stub_request(:head, object_url).to_return(:headers => return_headers)
92
+
93
+ code = 'Timeout'
94
+ message = 'Request timeout.'
95
+ # upload part
96
+ stub_request(:get, object_url)
97
+ .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then
98
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
99
+ .to_return((4..9).map{ |i| {:body => mock_object(i)} }).then
100
+ .to_return(:status => 500, :body => mock_error(code, message)).then
101
+ .to_return((10..10).map{ |i| {:body => mock_object(i)} })
102
+
103
+ success = false
104
+ 4.times do
105
+ begin
106
+ @bucket.resumable_download(@object_key, @file, :part_size => 10)
107
+ success = true
108
+ rescue
109
+ # pass
110
+ end
111
+ end
112
+
113
+ expect(success).to be true
114
+ ranges = []
115
+ expect(WebMock).to have_requested(:get, object_url).with{ |req|
116
+ ranges << req.headers['Range']
117
+ }.times(13)
118
+
119
+ expect(ranges.uniq).to match_array((1..10).map{ |i| mock_range(i) })
120
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
121
+ end
122
+
123
+ it "should resume when checkpoint fails" do
124
+ # Monkey patch to inject failures
125
+ class ::Aliyun::OSS::Multipart::Download
126
+ alias :old_checkpoint :checkpoint
127
+
128
+ def checkpoint_fails
129
+ @@fail_injections ||= [false, false, true, true, false, true, false]
130
+ @@fail_injections.shift
131
+ end
132
+
133
+ def checkpoint
134
+ t = checkpoint_fails
135
+ if t == true
136
+ raise ClientError.new("fail injection")
137
+ end
138
+
139
+ old_checkpoint
140
+ end
141
+ end
142
+
143
+ return_headers = {
144
+ 'x-oss-object-type' => 'Normal',
145
+ 'ETag' => 'xxxyyyzzz',
146
+ 'Content-Length' => 100,
147
+ 'Last-Modified' => Time.now.rfc822
148
+ }
149
+
150
+ # get object meta
151
+ stub_request(:head, object_url).to_return(:headers => return_headers)
152
+
153
+ # get object by range
154
+ returns = [1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10]
155
+ stub_request(:get, object_url)
156
+ .to_return(returns.map{ |i| {:body => mock_object(i)} })
157
+
158
+ success = false
159
+ 4.times do
160
+ begin
161
+ @bucket.resumable_download(@object_key, @file, :part_size => 10)
162
+ success = true
163
+ rescue
164
+ # pass
165
+ end
166
+ end
167
+
168
+ expect(success).to be true
169
+ expect(WebMock).to have_requested(:get, object_url).times(13)
170
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
171
+ end
172
+
173
+ it "should not resume when specify disable_cpt" do
174
+ return_headers = {
175
+ 'x-oss-object-type' => 'Normal',
176
+ 'ETag' => 'xxxyyyzzz',
177
+ 'Content-Length' => 100,
178
+ 'Last-Modified' => Time.now.rfc822
179
+ }
180
+
181
+ # get object meta
182
+ stub_request(:head, object_url).to_return(:headers => return_headers)
183
+
184
+ code = 'Timeout'
185
+ message = 'Request timeout.'
186
+ # upload part
187
+ stub_request(:get, object_url)
188
+ .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then
189
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
190
+ .to_return((1..9).map{ |i| {:body => mock_object(i)} }).then
191
+ .to_return(:status => 500, :body => mock_error(code, message)).then
192
+ .to_return((1..10).map{ |i| {:body => mock_object(i)} })
193
+
194
+ cpt_file = "#{File.expand_path(@file)}.cpt"
195
+ success = false
196
+ 4.times do
197
+ begin
198
+ @bucket.resumable_download(
199
+ @object_key, @file, :part_size => 10,
200
+ :cpt_file => cpt_file, :disable_cpt => true)
201
+ success = true
202
+ rescue
203
+ # pass
204
+ end
205
+
206
+ expect(File.exists?(cpt_file)).to be false
207
+ end
208
+
209
+ expect(success).to be true
210
+ expect(WebMock).to have_requested(:get, object_url).times(25)
211
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
212
+ expect(Dir.glob("#{@file}.part.*").empty?).to be true
213
+ end
214
+
215
+ end # Resumable upload
216
+ end # OSS
217
+ end # Aliyun
@@ -0,0 +1,318 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module Aliyun
8
+ module OSS
9
+
10
+ describe "Resumable upload" do
11
+
12
+ before :all do
13
+ @endpoint = 'oss-cn-hangzhou.aliyuncs.com'
14
+ @bucket_name = 'rubysdk-bucket'
15
+ @object_key = 'resumable_file'
16
+ @bucket = Client.new(
17
+ :endpoint => @endpoint,
18
+ :access_key_id => 'xxx',
19
+ :access_key_secret => 'yyy').get_bucket(@bucket_name)
20
+
21
+ @file = './file_to_upload'
22
+ # write 100B data
23
+ File.open(@file, 'w') do |f|
24
+ (1..10).each do |i|
25
+ f.puts i.to_s.rjust(9, '0')
26
+ end
27
+ end
28
+ end
29
+
30
+ before :each do
31
+ File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt")
32
+ end
33
+
34
+ def object_url
35
+ "#{@bucket_name}.#{@endpoint}/#{@object_key}"
36
+ end
37
+
38
+ def parse_query_from_uri(uri)
39
+ query = {}
40
+ str = uri.to_s[uri.to_s.index('?')+1..-1]
41
+ str.split("&").each do |q|
42
+ v = q.split('=')
43
+ query[v.at(0)] = v.at(1)
44
+ end
45
+
46
+ query
47
+ end
48
+
49
+ def mock_txn_id(txn_id)
50
+ Nokogiri::XML::Builder.new do |xml|
51
+ xml.InitiateMultipartUploadResult {
52
+ xml.Bucket @bucket
53
+ xml.Key @object
54
+ xml.UploadId txn_id
55
+ }
56
+ end.to_xml
57
+ end
58
+
59
+ def mock_error(code, message)
60
+ Nokogiri::XML::Builder.new do |xml|
61
+ xml.Error {
62
+ xml.Code code
63
+ xml.Message message
64
+ xml.RequestId '0000'
65
+ }
66
+ end.to_xml
67
+ end
68
+
69
+ it "should upload file when all goes well" do
70
+ stub_request(:post, /#{object_url}\?uploads.*/)
71
+ .to_return(:body => mock_txn_id('upload_id'))
72
+ stub_request(:put, /#{object_url}\?partNumber.*/)
73
+ stub_request(:post, /#{object_url}\?uploadId.*/)
74
+
75
+ prg = []
76
+ @bucket.resumable_upload(
77
+ @object_key, @file, :part_size => 10) { |p| prg << p }
78
+
79
+ expect(WebMock).to have_requested(
80
+ :post, /#{object_url}\?uploads.*/).times(1)
81
+
82
+ part_numbers = Set.new([])
83
+ upload_ids = Set.new([])
84
+
85
+ expect(WebMock).to have_requested(
86
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
87
+ query = parse_query_from_uri(req.uri)
88
+ part_numbers << query['partNumber']
89
+ upload_ids << query['uploadId']
90
+ }.times(10)
91
+
92
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
93
+ expect(upload_ids.to_a).to match_array(['upload_id'])
94
+
95
+ expect(WebMock).to have_requested(
96
+ :post, /#{object_url}\?uploadId.*/).times(1)
97
+
98
+ expect(File.exist?("#{@file}.cpt")).to be false
99
+ expect(prg).to match_array((1..10).map {|i| i.to_f / 10 })
100
+ end
101
+
102
+ it "should restart when begin txn fails" do
103
+ code = 'Timeout'
104
+ message = 'Request timeout.'
105
+
106
+ stub_request(:post, /#{object_url}\?uploads.*/)
107
+ .to_return(:status => 500, :body => mock_error(code, message)).then
108
+ .to_return(:body => mock_txn_id('upload_id'))
109
+ stub_request(:put, /#{object_url}\?partNumber.*/)
110
+ stub_request(:post, /#{object_url}\?uploadId.*/)
111
+
112
+ success = false
113
+ 2.times do
114
+ begin
115
+ @bucket.resumable_upload(@object_key, @file, :part_size => 10)
116
+ success = true
117
+ rescue
118
+ # pass
119
+ end
120
+ end
121
+
122
+ expect(success).to be true
123
+ expect(WebMock).to have_requested(
124
+ :post, /#{object_url}\?uploads.*/).times(2)
125
+ expect(WebMock).to have_requested(
126
+ :put, /#{object_url}\?partNumber.*/).times(10)
127
+ expect(WebMock).to have_requested(
128
+ :post, /#{object_url}\?uploadId.*/).times(1)
129
+ end
130
+
131
+ it "should resume when upload part fails" do
132
+ # begin multipart
133
+ stub_request(:post, /#{object_url}\?uploads.*/)
134
+ .to_return(:body => mock_txn_id('upload_id'))
135
+
136
+ # commit multipart
137
+ stub_request(:post, /#{object_url}\?uploadId.*/)
138
+
139
+ code = 'Timeout'
140
+ message = 'Request timeout.'
141
+ # upload part
142
+ stub_request(:put, /#{object_url}\?partNumber.*/)
143
+ .to_return(:status => 200).times(3).then
144
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
145
+ .to_return(:status => 200).times(6).then
146
+ .to_return(:status => 500, :body => mock_error(code, message)).then
147
+ .to_return(:status => 200)
148
+
149
+ success = false
150
+ 4.times do
151
+ begin
152
+ @bucket.resumable_upload(@object_key, @file, :part_size => 10)
153
+ success = true
154
+ rescue
155
+ # pass
156
+ end
157
+ end
158
+
159
+ expect(success).to be true
160
+
161
+ expect(WebMock).to have_requested(
162
+ :post, /#{object_url}\?uploads.*/).times(1)
163
+
164
+ part_numbers = Set.new([])
165
+ upload_ids = Set.new([])
166
+
167
+ expect(WebMock).to have_requested(
168
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
169
+ query = parse_query_from_uri(req.uri)
170
+ part_numbers << query['partNumber']
171
+ upload_ids << query['uploadId']
172
+ }.times(13)
173
+
174
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
175
+ expect(upload_ids.to_a).to match_array(['upload_id'])
176
+
177
+ expect(WebMock).to have_requested(
178
+ :post, /#{object_url}\?uploadId.*/).times(1)
179
+ end
180
+
181
+ it "should resume when checkpoint fails" do
182
+ # Monkey patch to inject failures
183
+ class ::Aliyun::OSS::Multipart::Upload
184
+ alias :old_checkpoint :checkpoint
185
+
186
+ def checkpoint_fails
187
+ @@fail_injections ||= [false, false, true, true, false, true, false]
188
+ @@fail_injections.shift
189
+ end
190
+
191
+ def checkpoint
192
+ t = checkpoint_fails
193
+ if t == true
194
+ raise ClientError.new("fail injection")
195
+ end
196
+
197
+ old_checkpoint
198
+ end
199
+ end
200
+
201
+ stub_request(:post, /#{object_url}\?uploads.*/)
202
+ .to_return(:body => mock_txn_id('upload_id'))
203
+ stub_request(:put, /#{object_url}\?partNumber.*/)
204
+ stub_request(:post, /#{object_url}\?uploadId.*/)
205
+
206
+ success = false
207
+ 4.times do
208
+ begin
209
+ @bucket.resumable_upload(@object_key, @file, :part_size => 10)
210
+ success = true
211
+ rescue
212
+ # pass
213
+ end
214
+ end
215
+
216
+ expect(success).to be true
217
+
218
+ expect(WebMock).to have_requested(
219
+ :post, /#{object_url}\?uploads.*/).times(1)
220
+
221
+ part_numbers = Set.new([])
222
+
223
+ expect(WebMock).to have_requested(
224
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
225
+ query = parse_query_from_uri(req.uri)
226
+ part_numbers << query['partNumber']
227
+ query['uploadId'] == 'upload_id'
228
+ }.times(13)
229
+
230
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
231
+
232
+ expect(WebMock).to have_requested(
233
+ :post, /#{object_url}\?uploadId.*/).times(1)
234
+ end
235
+
236
+ it "should resume when commit txn fails" do
237
+ # begin multipart
238
+ stub_request(:post, /#{object_url}\?uploads.*/)
239
+ .to_return(:body => mock_txn_id('upload_id'))
240
+
241
+ # upload part
242
+ stub_request(:put, /#{object_url}\?partNumber.*/)
243
+
244
+ code = 'Timeout'
245
+ message = 'Request timeout.'
246
+ # commit multipart
247
+ stub_request(:post, /#{object_url}\?uploadId.*/)
248
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
249
+ .to_return(:status => 200)
250
+
251
+ success = false
252
+ 3.times do
253
+ begin
254
+ @bucket.resumable_upload(@object_key, @file, :part_size => 10)
255
+ success = true
256
+ rescue
257
+ # pass
258
+ end
259
+ end
260
+
261
+ expect(success).to be true
262
+ expect(WebMock).to have_requested(
263
+ :post, /#{object_url}\?uploads.*/).times(1)
264
+ expect(WebMock).to have_requested(
265
+ :put, /#{object_url}\?partNumber.*/).times(10)
266
+ expect(WebMock).to have_requested(
267
+ :post, /#{object_url}\?uploadId.*/).with{ |req|
268
+ query = parse_query_from_uri(req.uri)
269
+ query['uploadId'] == 'upload_id'
270
+ }.times(3)
271
+ end
272
+
273
+ it "should not write checkpoint when specify disable_cpt" do
274
+ # begin multipart
275
+ stub_request(:post, /#{object_url}\?uploads.*/)
276
+ .to_return(:body => mock_txn_id('upload_id'))
277
+
278
+ # upload part
279
+ stub_request(:put, /#{object_url}\?partNumber.*/)
280
+
281
+ code = 'Timeout'
282
+ message = 'Request timeout.'
283
+ # commit multipart
284
+ stub_request(:post, /#{object_url}\?uploadId.*/)
285
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
286
+ .to_return(:status => 200)
287
+
288
+ cpt_file = "#{File.expand_path(@file)}.cpt"
289
+ success = false
290
+ 3.times do
291
+ begin
292
+ @bucket.resumable_upload(
293
+ @object_key, @file, :part_size => 10,
294
+ :cpt_file => cpt_file, :disable_cpt => true)
295
+ success = true
296
+ rescue
297
+ # pass
298
+ end
299
+
300
+ expect(File.exists?(cpt_file)).to be false
301
+ end
302
+
303
+ expect(success).to be true
304
+ expect(WebMock).to have_requested(
305
+ :post, /#{object_url}\?uploads.*/).times(3)
306
+ expect(WebMock).to have_requested(
307
+ :put, /#{object_url}\?partNumber.*/).times(30)
308
+ expect(WebMock).to have_requested(
309
+ :post, /#{object_url}\?uploadId.*/).with{ |req|
310
+ query = parse_query_from_uri(req.uri)
311
+ query['uploadId'] == 'upload_id'
312
+ }.times(3)
313
+ end
314
+
315
+ end # Resumable upload
316
+
317
+ end # OSS
318
+ end # Aliyun