aliyun-sdk 0.1.1

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.
@@ -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