att-swift 1.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 +7 -0
- checksums.yaml.gz.sig +6 -0
- data.tar.gz.sig +0 -0
- data/.autotest +17 -0
- data/.gemtest +0 -0
- data/History.rdoc +5 -0
- data/LICENSE.rdoc +20 -0
- data/Manifest.txt +9 -0
- data/README.rdoc +33 -0
- data/Rakefile +21 -0
- data/bin/swift_dump +27 -0
- data/lib/att/swift.rb +582 -0
- data/test/test_att_swift.rb +564 -0
- metadata +144 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,564 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'att/swift'
|
3
|
+
|
4
|
+
class TestATTSwift < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@auth_uri = URI 'http://example/auth/'
|
8
|
+
@store_uri = URI 'http://storage.example/'
|
9
|
+
|
10
|
+
@swift = ATT::Swift.new @auth_uri, 'username', 'key'
|
11
|
+
@swift.http = @http = FakeHTTP.new
|
12
|
+
|
13
|
+
@http.expected_response(200, nil,
|
14
|
+
'X-Auth-Token' => 'token',
|
15
|
+
'X-Storage-Url' => 'http://storage.example/')
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_initialize
|
19
|
+
swift = ATT::Swift.new @auth_uri.to_s, 'username', 'key'
|
20
|
+
swift.http = @http
|
21
|
+
|
22
|
+
swift.authenticate
|
23
|
+
|
24
|
+
assert_equal 'token', swift.auth_token
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_authenticate
|
28
|
+
@swift.authenticate
|
29
|
+
|
30
|
+
assert_equal 'token', @swift.auth_token
|
31
|
+
assert_equal URI('http://storage.example/'), @swift.storage_uri
|
32
|
+
assert_equal 'token', @http.override_headers['X-Auth-Token']
|
33
|
+
|
34
|
+
uri, req = @http.requests.shift
|
35
|
+
|
36
|
+
assert_equal @auth_uri + 'v1.0', uri
|
37
|
+
|
38
|
+
assert_equal '/auth/v1.0', req.path
|
39
|
+
assert_equal %w[username], req.to_hash['x-auth-user']
|
40
|
+
assert_equal %w[key], req.to_hash['x-auth-key']
|
41
|
+
|
42
|
+
@swift.authenticate
|
43
|
+
|
44
|
+
assert_empty @http.requests, 'authenticate did not cache the auth token'
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_chunk_objects
|
48
|
+
expected_body_1 = [
|
49
|
+
{ 'name' => 'hello', 'hash' => '5d41402abc4b2a76b9719d911017c592',
|
50
|
+
'bytes' => 5 }
|
51
|
+
]
|
52
|
+
|
53
|
+
expected_body_2 = [
|
54
|
+
{ 'name' => 'world', 'hash' => '7d793037a0760186574b0282f2f435e7',
|
55
|
+
'bytes' => 5 },
|
56
|
+
]
|
57
|
+
|
58
|
+
@http.expected_response 200, JSON.dump(expected_body_1)
|
59
|
+
@http.expected_response 200, JSON.dump(expected_body_2)
|
60
|
+
@http.expected_response 200, JSON.dump([])
|
61
|
+
|
62
|
+
chunks = []
|
63
|
+
|
64
|
+
@swift.chunk_objects 'container', nil, 1 do |objects|
|
65
|
+
chunks << objects
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_equal [expected_body_1, expected_body_2], chunks
|
69
|
+
|
70
|
+
@http.requests.shift # auth
|
71
|
+
|
72
|
+
uri, = @http.requests.shift
|
73
|
+
assert_equal @store_uri + 'container?format=json&limit=1', uri
|
74
|
+
|
75
|
+
uri, = @http.requests.shift
|
76
|
+
assert_equal @store_uri + 'container?format=json&marker=hello&limit=1', uri
|
77
|
+
|
78
|
+
uri, = @http.requests.shift
|
79
|
+
assert_equal @store_uri + 'container?format=json&marker=world&limit=1', uri
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_containers
|
83
|
+
expected_body = [
|
84
|
+
{ 'name' => 'a', 'count' => 12, 'bytes' => 345 },
|
85
|
+
]
|
86
|
+
|
87
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
88
|
+
|
89
|
+
containers = @swift.containers
|
90
|
+
|
91
|
+
assert_equal expected_body, containers
|
92
|
+
|
93
|
+
@http.requests.shift # auth
|
94
|
+
|
95
|
+
uri, = @http.requests.shift
|
96
|
+
|
97
|
+
assert_equal @store_uri + '?format=json', uri
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_containers_limit
|
101
|
+
expected_body = [
|
102
|
+
{ 'name' => 'b', 'count' => 12, 'bytes' => 345 },
|
103
|
+
]
|
104
|
+
|
105
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
106
|
+
|
107
|
+
@swift.containers nil, 2
|
108
|
+
|
109
|
+
@http.requests.shift # auth
|
110
|
+
|
111
|
+
uri, = @http.requests.shift
|
112
|
+
|
113
|
+
assert_equal @store_uri + '?format=json&limit=2', uri
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_containers_marker
|
117
|
+
expected_body = [
|
118
|
+
{ 'name' => 'b', 'count' => 12, 'bytes' => 345 },
|
119
|
+
]
|
120
|
+
|
121
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
122
|
+
|
123
|
+
@swift.containers 'a'
|
124
|
+
|
125
|
+
@http.requests.shift # auth
|
126
|
+
|
127
|
+
uri, = @http.requests.shift
|
128
|
+
|
129
|
+
assert_equal @store_uri + '?format=json&marker=a', uri
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_copy_object
|
133
|
+
@http.expected_response 201
|
134
|
+
|
135
|
+
assert @swift.copy_object('src_container', 'src_object',
|
136
|
+
'dst_container', 'dst_object')
|
137
|
+
|
138
|
+
@http.requests.shift # auth
|
139
|
+
|
140
|
+
uri, req = @http.requests.shift
|
141
|
+
|
142
|
+
assert_equal @store_uri + 'dst_container/dst_object', uri
|
143
|
+
|
144
|
+
assert_kind_of Net::HTTP::Put, req
|
145
|
+
assert_equal '/src_container/src_object', req['x-copy-from']
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_create_container
|
149
|
+
@http.expected_response 201
|
150
|
+
|
151
|
+
assert @swift.create_container 'test'
|
152
|
+
|
153
|
+
@http.requests.shift # auth
|
154
|
+
|
155
|
+
uri, req = @http.requests.shift
|
156
|
+
|
157
|
+
assert_equal @store_uri + 'test', uri
|
158
|
+
|
159
|
+
assert_kind_of Net::HTTP::Put, req
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_create_container_exists
|
163
|
+
@http.expected_response 200
|
164
|
+
|
165
|
+
refute @swift.create_container 'test'
|
166
|
+
|
167
|
+
@http.requests.shift # auth
|
168
|
+
|
169
|
+
uri, req = @http.requests.shift
|
170
|
+
|
171
|
+
assert_equal @store_uri + 'test', uri
|
172
|
+
|
173
|
+
assert_kind_of Net::HTTP::Put, req
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_delete_container
|
177
|
+
@http.expected_response 204
|
178
|
+
|
179
|
+
assert @swift.delete_container 'test'
|
180
|
+
|
181
|
+
@http.requests.shift # auth
|
182
|
+
|
183
|
+
uri, req = @http.requests.shift
|
184
|
+
|
185
|
+
assert_equal @store_uri + 'test', uri
|
186
|
+
|
187
|
+
assert_kind_of Net::HTTP::Delete, req
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_delete_container_nonexistent
|
191
|
+
@http.expected_response 404
|
192
|
+
|
193
|
+
refute @swift.delete_container 'test'
|
194
|
+
|
195
|
+
@http.requests.shift # auth
|
196
|
+
|
197
|
+
uri, req = @http.requests.shift
|
198
|
+
|
199
|
+
assert_equal @store_uri + 'test', uri
|
200
|
+
|
201
|
+
assert_kind_of Net::HTTP::Delete, req
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_delete_container_not_empty
|
205
|
+
@http.expected_response 409
|
206
|
+
|
207
|
+
e = assert_raises ATT::Swift::Error do
|
208
|
+
@swift.delete_container 'test'
|
209
|
+
end
|
210
|
+
|
211
|
+
assert_equal 'container test is not empty - 409', e.message
|
212
|
+
|
213
|
+
@http.requests.shift # auth
|
214
|
+
|
215
|
+
uri, req = @http.requests.shift
|
216
|
+
|
217
|
+
assert_equal @store_uri + 'test', uri
|
218
|
+
|
219
|
+
assert_kind_of Net::HTTP::Delete, req
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_delete_object
|
223
|
+
@http.expected_response 204
|
224
|
+
|
225
|
+
assert @swift.delete_object 'container', 'object'
|
226
|
+
|
227
|
+
@http.requests.shift # auth
|
228
|
+
|
229
|
+
uri, req = @http.requests.shift
|
230
|
+
|
231
|
+
assert_equal @store_uri + 'container/object', uri
|
232
|
+
|
233
|
+
assert_kind_of Net::HTTP::Delete, req
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_delete_object_nonexistent
|
237
|
+
@http.expected_response 404
|
238
|
+
|
239
|
+
refute @swift.delete_object 'container', 'object'
|
240
|
+
|
241
|
+
@http.requests.shift # auth
|
242
|
+
|
243
|
+
uri, req = @http.requests.shift
|
244
|
+
|
245
|
+
assert_equal @store_uri + 'container/object', uri
|
246
|
+
|
247
|
+
assert_kind_of Net::HTTP::Delete, req
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_objects
|
251
|
+
expected_body = [
|
252
|
+
{ 'name' => 'hello', 'hash' => '5d41402abc4b2a76b9719d911017c592',
|
253
|
+
'bytes' => 5 },
|
254
|
+
{ 'name' => 'world', 'hash' => '7d793037a0760186574b0282f2f435e7',
|
255
|
+
'bytes' => 5 },
|
256
|
+
]
|
257
|
+
|
258
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
259
|
+
|
260
|
+
objects = @swift.objects 'container'
|
261
|
+
|
262
|
+
assert_equal expected_body, objects
|
263
|
+
|
264
|
+
@http.requests.shift # auth
|
265
|
+
|
266
|
+
uri, = @http.requests.shift
|
267
|
+
|
268
|
+
assert_equal @store_uri + 'container?format=json', uri
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_objects_limit
|
272
|
+
expected_body = [
|
273
|
+
{ 'name' => 'hello', 'hash' => '5d41402abc4b2a76b9719d911017c592',
|
274
|
+
'bytes' => 5 },
|
275
|
+
]
|
276
|
+
|
277
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
278
|
+
|
279
|
+
objects = @swift.objects 'container', nil, 2
|
280
|
+
|
281
|
+
assert_equal expected_body, objects
|
282
|
+
|
283
|
+
@http.requests.shift # auth
|
284
|
+
|
285
|
+
uri, = @http.requests.shift
|
286
|
+
|
287
|
+
assert_equal @store_uri + 'container?format=json&limit=2', uri
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_objects_marker
|
291
|
+
expected_body = [
|
292
|
+
{ 'name' => 'hello', 'hash' => '5d41402abc4b2a76b9719d911017c592',
|
293
|
+
'bytes' => 5 },
|
294
|
+
]
|
295
|
+
|
296
|
+
@http.expected_response 200, JSON.dump(expected_body)
|
297
|
+
|
298
|
+
objects = @swift.objects 'container', 'h'
|
299
|
+
|
300
|
+
assert_equal expected_body, objects
|
301
|
+
|
302
|
+
@http.requests.shift # auth
|
303
|
+
|
304
|
+
uri, = @http.requests.shift
|
305
|
+
|
306
|
+
assert_equal @store_uri + 'container?format=json&marker=h', uri
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_object_info
|
310
|
+
@http.expected_response(200, nil,
|
311
|
+
'Content-Length' => '3072',
|
312
|
+
'Content-Type' => 'application/octet-stream',
|
313
|
+
'ETag' => 'a2a5648d09a83c6a85ddd62e4a22309c',
|
314
|
+
'Last-Modified' => 'Wed, 22 Aug 2012 22:51:28 GMT',
|
315
|
+
'X-Object-Meta-Meat' => 'Bacon')
|
316
|
+
|
317
|
+
info = @swift.object_info 'container', 'object'
|
318
|
+
|
319
|
+
expected = {
|
320
|
+
'metadata' => {
|
321
|
+
'meat' => 'Bacon',
|
322
|
+
},
|
323
|
+
|
324
|
+
'content-length' => '3072',
|
325
|
+
'content-type' => 'application/octet-stream',
|
326
|
+
'etag' => 'a2a5648d09a83c6a85ddd62e4a22309c',
|
327
|
+
'last-modified' => 'Wed, 22 Aug 2012 22:51:28 GMT',
|
328
|
+
}
|
329
|
+
|
330
|
+
assert_equal expected, info
|
331
|
+
|
332
|
+
@http.requests.shift # auth
|
333
|
+
|
334
|
+
uri, req = @http.requests.shift
|
335
|
+
|
336
|
+
assert_equal @store_uri + 'container/object', uri
|
337
|
+
assert_kind_of Net::HTTP::Head, req
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_object_info_missing
|
341
|
+
@http.expected_response 404
|
342
|
+
|
343
|
+
info = @swift.object_info 'container', 'object'
|
344
|
+
|
345
|
+
assert_nil info
|
346
|
+
|
347
|
+
@http.requests.shift # auth
|
348
|
+
|
349
|
+
uri, req = @http.requests.shift
|
350
|
+
|
351
|
+
assert_equal @store_uri + 'container/object', uri
|
352
|
+
assert_kind_of Net::HTTP::Head, req
|
353
|
+
end
|
354
|
+
|
355
|
+
def test_object_metadata
|
356
|
+
@http.expected_response(200, nil,
|
357
|
+
'Content-Length' => '3072',
|
358
|
+
'Content-Type' => 'application/octet-stream',
|
359
|
+
'ETag' => 'a2a5648d09a83c6a85ddd62e4a22309c',
|
360
|
+
'Last-Modified' => 'Wed, 22 Aug 2012 22:51:28 GMT',
|
361
|
+
'X-Object-Meta-Meat' => 'Bacon')
|
362
|
+
|
363
|
+
metadata = @swift.object_metadata 'container', 'object'
|
364
|
+
|
365
|
+
expected = {
|
366
|
+
'meat' => 'Bacon',
|
367
|
+
}
|
368
|
+
|
369
|
+
assert_equal expected, metadata
|
370
|
+
|
371
|
+
@http.requests.shift # auth
|
372
|
+
|
373
|
+
uri, req = @http.requests.shift
|
374
|
+
|
375
|
+
assert_equal @store_uri + 'container/object', uri
|
376
|
+
assert_kind_of Net::HTTP::Head, req
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_object_metadata_missing
|
380
|
+
@http.expected_response 404
|
381
|
+
|
382
|
+
metadata = @swift.object_metadata 'container', 'object'
|
383
|
+
|
384
|
+
assert_nil metadata
|
385
|
+
|
386
|
+
@http.requests.shift # auth
|
387
|
+
|
388
|
+
uri, req = @http.requests.shift
|
389
|
+
|
390
|
+
assert_equal @store_uri + 'container/object', uri
|
391
|
+
assert_kind_of Net::HTTP::Head, req
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_paginate_objects
|
395
|
+
expected_body_1 = [
|
396
|
+
{ 'name' => 'hello', 'hash' => '5d41402abc4b2a76b9719d911017c592',
|
397
|
+
'bytes' => 5 }
|
398
|
+
]
|
399
|
+
|
400
|
+
expected_body_2 = [
|
401
|
+
{ 'name' => 'world', 'hash' => '7d793037a0760186574b0282f2f435e7',
|
402
|
+
'bytes' => 5 },
|
403
|
+
]
|
404
|
+
|
405
|
+
@http.expected_response 200, JSON.dump(expected_body_1)
|
406
|
+
@http.expected_response 200, JSON.dump(expected_body_2)
|
407
|
+
@http.expected_response 200, JSON.dump([])
|
408
|
+
|
409
|
+
paginated = []
|
410
|
+
|
411
|
+
@swift.paginate_objects 'container', nil, 1 do |object|
|
412
|
+
paginated << object
|
413
|
+
end
|
414
|
+
|
415
|
+
assert_equal [expected_body_1, expected_body_2].flatten, paginated
|
416
|
+
|
417
|
+
@http.requests.shift # auth
|
418
|
+
|
419
|
+
uri, = @http.requests.shift
|
420
|
+
assert_equal @store_uri + 'container?format=json&limit=1', uri
|
421
|
+
|
422
|
+
uri, = @http.requests.shift
|
423
|
+
assert_equal @store_uri + 'container?format=json&marker=hello&limit=1', uri
|
424
|
+
|
425
|
+
uri, = @http.requests.shift
|
426
|
+
assert_equal @store_uri + 'container?format=json&marker=world&limit=1', uri
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_read_object
|
430
|
+
@http.expected_response(200, 'hello',
|
431
|
+
'ETag' => Digest::MD5.hexdigest('hello'))
|
432
|
+
|
433
|
+
object = @swift.read_object 'container', 'object'
|
434
|
+
|
435
|
+
assert_equal 'hello', object
|
436
|
+
|
437
|
+
@http.requests.shift # auth
|
438
|
+
|
439
|
+
uri, = @http.requests.shift
|
440
|
+
|
441
|
+
assert_equal @store_uri + 'container/object', uri
|
442
|
+
end
|
443
|
+
|
444
|
+
def test_read_object_not_found
|
445
|
+
@http.expected_response 404
|
446
|
+
|
447
|
+
e = assert_raises ATT::Swift::Error do
|
448
|
+
@swift.read_object 'container', 'object'
|
449
|
+
end
|
450
|
+
|
451
|
+
assert_equal 'object object in container not found - 404', e.message
|
452
|
+
|
453
|
+
@http.requests.shift # auth
|
454
|
+
|
455
|
+
uri, = @http.requests.shift
|
456
|
+
|
457
|
+
assert_equal @store_uri + 'container/object', uri
|
458
|
+
end
|
459
|
+
|
460
|
+
def test_set_object_metadata
|
461
|
+
@http.expected_response 202
|
462
|
+
|
463
|
+
@swift.set_object_metadata 'container', 'object', 'meat' => 'bacon'
|
464
|
+
|
465
|
+
@http.requests.shift # auth
|
466
|
+
|
467
|
+
uri, req = @http.requests.shift
|
468
|
+
|
469
|
+
assert_equal @store_uri + 'container/object', uri
|
470
|
+
assert_kind_of Net::HTTP::Post, req
|
471
|
+
|
472
|
+
expected = {
|
473
|
+
'x-object-meta-meat' => %w[bacon]
|
474
|
+
}
|
475
|
+
|
476
|
+
assert_equal expected,
|
477
|
+
req.to_hash.select { |key,| key =~ /^x-object-meta-/ }
|
478
|
+
end
|
479
|
+
|
480
|
+
def test_write_object
|
481
|
+
digest = Digest::MD5.hexdigest 'hello'
|
482
|
+
@http.expected_response 201, nil, 'ETag' => digest
|
483
|
+
|
484
|
+
md5 = @swift.write_object 'container', 'object', 'hello'
|
485
|
+
|
486
|
+
assert_equal digest, md5
|
487
|
+
|
488
|
+
@http.requests.shift # auth
|
489
|
+
|
490
|
+
uri, req = @http.requests.shift
|
491
|
+
|
492
|
+
assert_equal @store_uri + 'container/object', uri
|
493
|
+
assert_kind_of Net::HTTP::Put, req
|
494
|
+
assert_equal 'hello', req.body
|
495
|
+
end
|
496
|
+
|
497
|
+
def test_write_object_block
|
498
|
+
digest = Digest::MD5.hexdigest 'hello'
|
499
|
+
@http.expected_response 201, nil, 'ETag' => digest
|
500
|
+
|
501
|
+
md5 = @swift.write_object 'container', 'object' do |io|
|
502
|
+
io.write 'hello'
|
503
|
+
end
|
504
|
+
|
505
|
+
assert_equal digest, md5
|
506
|
+
|
507
|
+
@http.requests.shift # auth
|
508
|
+
|
509
|
+
uri, req = @http.requests.shift
|
510
|
+
|
511
|
+
assert_equal @store_uri + 'container/object', uri
|
512
|
+
assert_kind_of Net::HTTP::Put, req
|
513
|
+
assert_kind_of IO, req.body_stream
|
514
|
+
end
|
515
|
+
|
516
|
+
class FakeHTTP
|
517
|
+
attr_accessor :requests, :responses, :override_headers
|
518
|
+
|
519
|
+
def initialize
|
520
|
+
@requests = []
|
521
|
+
@responses = []
|
522
|
+
|
523
|
+
@override_headers = {}
|
524
|
+
end
|
525
|
+
|
526
|
+
def debug_output= ignored
|
527
|
+
end
|
528
|
+
|
529
|
+
def expected_response code, body = nil, headers = {}
|
530
|
+
code = code.to_s
|
531
|
+
klass = Net::HTTPResponse::CODE_TO_OBJ[code]
|
532
|
+
|
533
|
+
response = klass.new '1.1', code, ''
|
534
|
+
response.initialize_http_header headers
|
535
|
+
|
536
|
+
@override_headers.each do |name, value|
|
537
|
+
response[name] = value
|
538
|
+
end
|
539
|
+
|
540
|
+
if body then
|
541
|
+
response.instance_variable_set :@read, true
|
542
|
+
response.instance_variable_set :@body, body
|
543
|
+
end
|
544
|
+
|
545
|
+
@responses << response
|
546
|
+
end
|
547
|
+
|
548
|
+
def request uri, req = nil
|
549
|
+
@requests << [uri, req]
|
550
|
+
|
551
|
+
raise "response list empty for #{uri}" if @responses.empty?
|
552
|
+
|
553
|
+
response = @responses.shift
|
554
|
+
|
555
|
+
if block_given? then
|
556
|
+
yield response
|
557
|
+
else
|
558
|
+
response
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
end
|
564
|
+
|