1 # Copyright 2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from unittest import TestCase
35 from mock import patch, call
36 from tempfile import NamedTemporaryFile
37 from os import urandom
39 from kamaki.clients import ClientError
40 from kamaki.clients.pithos import PithosClient as PC
41 from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
43 client_pkg = 'kamaki.clients.Client'
44 pithos_pkg = 'kamaki.clients.pithos.PithosClient'
46 user_id = 'ac0un7-1d-5tr1ng'
50 'content-language': 'en-us',
51 'content-type': 'text/html; charset=utf-8',
52 'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
53 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
54 'server': 'gunicorn/0.14.5',
55 'vary': 'Accept-Language',
56 'x-account-bytes-used': '751615526',
57 'x-account-container-count': 7,
58 'x-account-policy-quota': 53687091200,
59 'x-account-policy-versioning': 'auto'}
61 'content-language': 'en-us',
62 'content-type': 'text/html; charset=utf-8',
63 'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
64 'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
65 'server': 'gunicorn/0.14.5',
66 'vary': 'Accept-Language',
67 'x-container-block-hash': 'sha256',
68 'x-container-block-size': 4194304,
69 'x-container-bytes-used': 309528938,
70 'x-container-object-count': 14,
71 'x-container-object-meta': '',
72 'x-container-policy-quota': 53687091200,
73 'x-container-policy-versioning': 'auto'}
75 'content-language': 'en-us',
76 'content-length': 254965,
77 'content-type': 'application/octet-stream',
78 'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
80 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
81 'server': 'gunicorn/0.14.5',
82 'vary': 'Accept-Language',
83 'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
84 'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
85 'x-object-version': '525996',
86 'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
87 'x-object-meta-k1': 'v1',
88 'x-object-meta-k2': 'v2'}
92 last_modified="2013-02-27T11:56:09.893033+00:00",
95 x_container_policy=dict(quota="21474836480", versioning="auto")),
98 last_modified="2012-10-23T12:25:17.229187+00:00",
101 x_container_policy=dict(quota="21474836480", versioning="auto"))]
104 name="The_Secret_Garden.zip",
105 x_object_public="/public/wdp9p",
107 x_object_version_timestamp="1360237915.7027509",
108 x_object_uuid="s0m3uu1df0r0bj0n3",
109 last_modified="2013-02-07T11:51:55.702751+00:00",
110 content_type="application/octet-stream",
111 x_object_hash="0afdf29f71cd53126225c3f54ca",
112 x_object_version=17737,
113 x_object_modified_by=user_id),
115 name="The_Revealed_Garden.zip",
116 x_object_public="/public/wpd7p",
118 x_object_version_timestamp="13602915.7027509",
119 x_object_uuid="s0m3uu1df0r0bj70w",
120 last_modified="2013-02-07T11:51:55.702751+00:00",
121 content_type="application/octet-stream",
122 x_object_hash="0afdf29f71cd53126225c3f54ca",
123 x_object_version=17737,
124 x_object_modified_by=user_id)]
125 object_hashmap = dict(
126 block_hash="sha256", block_size=4194304, bytes=33554432,
128 "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
129 "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
130 "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
131 "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
132 "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
133 "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
134 "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
135 "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
137 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b1a-82d5"),
138 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b2a-f2d5"),
139 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="2b1a-82d6")]
143 """FR stands for Fake Response"""
154 class Pithos(TestCase):
158 def _create_temp_file(self, num_of_blocks):
159 self.files.append(NamedTemporaryFile())
160 tmpFile = self.files[-1]
161 file_size = num_of_blocks * 4 * 1024 * 1024
162 print('\n\tCreate tmp file')
163 tmpFile.write(urandom(file_size))
169 def assert_dicts_are_equal(self, d1, d2):
170 for k, v in d1.items():
171 self.assertTrue(k in d2)
172 if isinstance(v, dict):
173 self.assert_dicts_are_equal(v, d2[k])
175 self.assertEqual(unicode(v), unicode(d2[k]))
178 self.url = 'https://www.example.com/pithos'
179 self.token = 'p17h0570k3n'
180 self.client = PC(self.url, self.token)
181 self.client.account = user_id
182 self.client.container = 'c0nt@1n3r_i'
192 # Pithos+ methods that extend storage API
194 @patch('%s.account_head' % pithos_pkg, return_value=FR())
195 def test_get_account_info(self, AH):
196 FR.headers = account_info
197 r = self.client.get_account_info()
198 self.assert_dicts_are_equal(r, account_info)
199 self.assertEqual(AH.mock_calls[-1], call(until=None))
201 r = self.client.get_account_info(until=unt)
202 self.assert_dicts_are_equal(r, account_info)
203 self.assertEqual(AH.mock_calls[-1], call(until=unt))
205 self.assertRaises(ClientError, self.client.get_account_info)
207 @patch('%s.account_post' % pithos_pkg, return_value=FR())
208 def test_del_account_meta(self, ap):
209 keys = ['k1', 'k2', 'k3']
212 self.client.del_account_meta(key)
213 expected.append(call(update=True, metadata={key: ''}))
214 self.assertEqual(ap.mock_calls, expected)
216 @patch('%s.container_head' % pithos_pkg, return_value=FR())
217 def test_get_container_info(self, ch):
218 FR.headers = container_info
219 r = self.client.get_container_info()
220 self.assert_dicts_are_equal(r, container_info)
222 r = self.client.get_container_info(until=u)
223 self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
225 @patch('%s.account_get' % pithos_pkg, return_value=FR())
226 def test_list_containers(self, get):
227 FR.json = container_list
228 r = self.client.list_containers()
229 for i in range(len(r)):
230 self.assert_dicts_are_equal(r[i], container_list[i])
232 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
233 @patch('%s.container_post' % pithos_pkg, return_value=FR())
234 @patch('%s.object_put' % pithos_pkg, return_value=FR())
235 def test_upload_object(self, CI, CP, OP):
237 tmpFile = self._create_temp_file(num_of_blocks)
240 self.client.upload_object(obj, tmpFile)
241 self.assertEqual(PC.get_container_info.mock_calls, [call()])
242 [call1, call2] = PC.object_put.mock_calls
244 (args1, kwargs1) = call1[1:3]
245 (args2, kwargs2) = call2[1:3]
246 self.assertEqual(args1, (obj,))
252 hashes=['s0m3h@5h'] * num_of_blocks,
253 bytes=num_of_blocks * 4 * 1024 * 1024),
255 content_encoding=None,
256 content_type='application/octet-stream',
257 content_disposition=None,
260 for k, v in expected1.items():
262 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
263 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
265 self.assertEqual(v, kwargs1[k])
267 (args2, kwargs2) = call2[1:3]
268 self.assertEqual(args2, (obj,))
271 hashes=['s0m3h@5h'] * num_of_blocks,
272 bytes=num_of_blocks * 4 * 1024 * 1024),
273 content_type='application/octet-stream',
277 for k, v in expected2.items():
279 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
280 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
282 self.assertEqual(v, kwargs2[k])
289 from progress.bar import ShadyBar
290 blck_bar = ShadyBar('Mock blck calc.')
291 upld_bar = ShadyBar('Mock uplds')
296 if blck_bar and upld_bar:
299 for i in blck_bar.iter(range(n)):
304 for i in upld_bar.iter(range(n)):
309 self.client.upload_object(
311 hash_cb=blck_gen, upload_cb=upld_gen)
313 for i, c in enumerate(OP.mock_calls[-mock_offset:]):
314 self.assertEqual(OP.mock_calls[i], c)
319 sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
320 self.client.upload_object(obj, tmpFile,
321 content_type=ctype, sharing=sharing)
322 self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
323 self.assert_dicts_are_equal(
324 OP.mock_calls[-2][2]['permissions'],
332 content_disposition=ctype + 'd15p051710n',
334 content_encoding='802.11')
335 self.client.upload_object(obj, tmpFile, **kwargs)
336 for arg, val in kwargs.items():
337 self.assertEqual(OP.mock_calls[-2][2][arg], val)
339 @patch('%s.put' % pithos_pkg, return_value=FR())
340 @patch('%s.set_header' % client_pkg)
341 def test_create_directory(self, SH, put):
342 cont = self.client.container
344 call('Content-Type', 'application/directory'),
345 call('Content-length', '0')]
346 exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
347 self.client.create_directory(obj)
348 self.assertEqual(PC.set_header.mock_calls, exp_shd)
349 self.assertEqual(put.mock_calls, exp_put)
351 def test_get_object_info(self):
352 FR.headers = object_info
354 with patch.object(PC, 'object_head', return_value=FR()) as head:
355 r = self.client.get_object_info(obj)
356 self.assertEqual(r, object_info)
357 r = self.client.get_object_info(obj, version=version)
358 self.assertEqual(head.mock_calls, [
359 call(obj, version=None),
360 call(obj, version=version)])
364 side_effect=ClientError('Obj not found', 404)):
367 self.client.get_object_info,
368 obj, version=version)
370 @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
371 def test_get_object_meta(self, GOI):
373 for k, v in object_info.items():
375 r = self.client.get_object_meta(obj)
376 self.assert_dicts_are_equal(r, expected)
378 @patch('%s.object_post' % pithos_pkg, return_value=FR())
379 def test_del_object_meta(self, post):
380 metakey = '50m3m3t4k3y'
381 self.client.del_object_meta(obj, metakey)
382 expected = call(obj, update=True, metadata={metakey: ''})
383 self.assertEqual(post.mock_calls[-1], expected)
385 @patch('%s.post' % client_pkg, return_value=FR())
386 @patch('%s.set_header' % client_pkg)
387 def test_replace_object_meta(self, SH, post):
388 metas = dict(k1='new1', k2='new2', k3='new3')
389 cont = self.client.container
390 self.client.replace_object_meta(metas)
391 expected = call('/%s/%s' % (user_id, cont), success=202)
392 self.assertEqual(post.mock_calls[-1], expected)
393 prfx = 'X-Object-Meta-'
394 expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
395 self.assertEqual(PC.set_header.mock_calls, expected)
397 @patch('%s.object_put' % pithos_pkg, return_value=FR())
398 def test_copy_object(self, put):
399 src_cont = 'src-c0nt41n3r'
401 dst_cont = 'dst-c0nt41n3r'
408 copy_from='/%s/%s' % (src_cont, src_obj),
413 self.client.copy_object(src_cont, src_obj, dst_cont)
414 self.assertEqual(put.mock_calls[-1], expected)
415 self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
416 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
418 source_version='src-v3r510n',
419 source_account='src-4cc0un7',
421 content_type='c0n73n7Typ3',
423 self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
424 for k, v in kwargs.items():
425 self.assertEqual(v, put.mock_calls[-1][2][k])
427 @patch('%s.object_put' % pithos_pkg, return_value=FR())
428 def test_move_object(self, put):
429 src_cont = 'src-c0nt41n3r'
431 dst_cont = 'dst-c0nt41n3r'
438 move_from='/%s/%s' % (src_cont, src_obj),
443 self.client.move_object(src_cont, src_obj, dst_cont)
444 self.assertEqual(put.mock_calls[-1], expected)
445 self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
446 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
448 source_version='src-v3r510n',
449 source_account='src-4cc0un7',
451 content_type='c0n73n7Typ3',
453 self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
454 for k, v in kwargs.items():
455 self.assertEqual(v, put.mock_calls[-1][2][k])
457 @patch('%s.delete' % client_pkg, return_value=FR())
458 def test_delete_object(self, delete):
459 cont = self.client.container
460 self.client.delete_object(obj)
462 delete.mock_calls[-1],
463 call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
465 self.assertRaises(ClientError, self.client.delete_object, obj)
467 @patch('%s.get' % client_pkg, return_value=FR())
468 @patch('%s.set_param' % client_pkg)
469 def test_list_objects(self, SP, get):
470 FR.json = object_list
471 acc = self.client.account
472 cont = self.client.container
474 r = self.client.list_objects()
475 for i in range(len(r)):
476 self.assert_dicts_are_equal(r[i], object_list[i])
477 self.assertEqual(get.mock_calls, [
478 call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
479 self.assertEqual(SP.mock_calls, [call('format', 'json')])
481 self.assertEqual(self.client.list_objects(), [])
483 self.assertRaises(ClientError, self.client.list_objects)
485 @patch('%s.get' % client_pkg, return_value=FR())
486 @patch('%s.set_param' % client_pkg)
487 def test_list_objects_in_path(self, SP, get):
488 FR.json = object_list
489 path = '/some/awsome/path'
490 acc = self.client.account
491 cont = self.client.container
493 self.client.list_objects_in_path(path)
494 self.assertEqual(get.mock_calls, [
495 call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
496 self.assertEqual(SP.mock_calls, [
497 call('format', 'json'), call('path', path)])
499 self.assertRaises(ClientError, self.client.list_objects)
501 # Pithos+ only methods
503 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
504 def test_purge_container(self, cd):
505 self.client.purge_container()
506 self.assertTrue('until' in cd.mock_calls[-1][2])
507 cont = self.client.container
508 self.client.purge_container('another-container')
509 self.assertEqual(self.client.container, cont)
511 @patch('%s.object_put' % pithos_pkg, return_value=FR())
512 def test_upload_object_unchunked(self, put):
514 tmpFile = self._create_temp_file(num_of_blocks)
517 data=num_of_blocks * 4 * 1024 * 1024,
519 content_encoding='some content_encoding',
520 content_type='some content-type',
521 content_disposition='some content_disposition',
523 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
524 self.client.upload_object_unchunked(obj, tmpFile)
525 self.assertEqual(put.mock_calls[-1][1], (obj,))
527 sorted(put.mock_calls[-1][2].keys()),
528 sorted(expected.keys()))
529 kwargs = dict(expected)
530 kwargs.pop('success')
531 kwargs['size'] = kwargs.pop('data')
532 kwargs['sharing'] = kwargs.pop('permissions')
534 self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
535 pmc = put.mock_calls[-1][2]
536 for k, v in expected.items():
538 self.assertEqual(len(pmc[k]), v)
540 self.assertEqual(pmc[k], v)
543 self.client.upload_object_unchunked,
544 obj, tmpFile, withHashFile=True)
546 @patch('%s.object_put' % pithos_pkg, return_value=FR())
547 def test_create_object_by_manifestation(self, put):
548 manifest = '%s/%s' % (self.client.container, obj)
551 content_encoding='some content_encoding',
552 content_type='some content-type',
553 content_disposition='some content_disposition',
555 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
556 self.client.create_object_by_manifestation(obj)
557 expected = dict(content_length=0, manifest=manifest)
559 expected['permissions' if k == 'sharing' else k] = None
560 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
561 self.client.create_object_by_manifestation(obj, **kwargs)
562 expected.update(kwargs)
563 expected['permissions'] = expected.pop('sharing')
564 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
566 @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
567 @patch('%s.object_get' % pithos_pkg, return_value=FR())
568 def test_download_object(self, GOH, GET):
570 tmpFile = self._create_temp_file(num_of_blocks)
571 FR.content = tmpFile.read(4 * 1024 * 1024)
572 tmpFile = self._create_temp_file(num_of_blocks)
574 num_of_blocks = len(object_hashmap['hashes'])
579 if_match='if and only if',
580 if_none_match='if and only not',
581 if_modified_since='what if not?',
582 if_unmodified_since='this happens if not!',
583 async_headers=dict(Range='bytes=0-88888888'))
585 self.client.download_object(obj, tmpFile)
586 self.assertEqual(len(GET.mock_calls), num_of_blocks)
587 self.assertEqual(GET.mock_calls[-1][1], (obj,))
588 for k, v in kwargs.items():
589 if k == 'async_headers':
590 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
591 elif k in ('resume', 'range_str'):
594 self.assertEqual(GET.mock_calls[-1][2][k], None)
596 # Check ranges are consecutive
599 for c in GET.mock_calls:
600 rng_str = c[2]['async_headers']['Range']
601 (start, rng_str) = rng_str.split('=')
602 (start, end) = rng_str.split('-')
606 for i, start in enumerate(sorted(starts)):
608 int(ends[i - 1]) == int(start) - 1
612 from progress.bar import ShadyBar
613 dl_bar = ShadyBar('Mock dl')
620 for i in dl_bar.iter(range(n)):
625 self.client.download_object(obj, tmpFile, download_cb=blck_gen)
626 self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
629 kwargs.pop('async_headers')
631 self.client.download_object(obj, tmpFile, **kwargs)
632 for k, v in kwargs.items():
635 GET.mock_calls[-1][2]['data_range'],
638 self.assertEqual(GET.mock_calls[-1][2][k], v)
640 # ALl options on no tty
647 self.client.download_object(obj, tmpFile, **kwargs)
648 for k, v in kwargs.items():
650 self.assertTrue('data_range' in GET.mock_calls[-1][2])
652 self.assertEqual(GET.mock_calls[-1][2][k], v)
654 def test_get_object_hashmap(self):
655 FR.json = object_hashmap
656 for empty in (304, 412):
659 side_effect=ClientError('Empty', status=empty)):
660 r = self.client.get_object_hashmap(obj)
661 self.assertEqual(r, {})
667 if_etag_not_match=None,
668 if_modified_since=None,
669 if_unmodified_since=None)
671 version='s0m3v3r51on',
673 if_none_match='if non match',
674 if_modified_since='some date here',
675 if_unmodified_since='some date here',
677 with patch.object(PC, 'object_get', return_value=FR()) as get:
678 r = self.client.get_object_hashmap(obj)
679 self.assertEqual(r, object_hashmap)
680 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
681 r = self.client.get_object_hashmap(obj, **kwargs)
682 exp_args['if_etag_match'] = kwargs.pop('if_match')
683 exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
684 exp_args.update(kwargs)
685 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
687 @patch('%s.account_post' % pithos_pkg, return_value=FR())
688 def test_set_account_group(self, post):
689 (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
690 self.client.set_account_group(group, usernames)
693 call(update=True, groups={group: usernames}))
695 @patch('%s.account_post' % pithos_pkg, return_value=FR())
696 def test_del_account_group(self, post):
698 self.client.del_account_group(group)
701 call(update=True, groups={group: []}))
703 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
704 def test_get_account_quota(self, GAI):
705 key = 'x-account-policy-quota'
706 r = self.client.get_account_quota()
707 self.assertEqual(r[key], account_info[key])
709 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
710 def test_get_account_versioning(self, GAI):
711 key = 'x-account-policy-versioning'
712 r = self.client.get_account_versioning()
713 self.assertEqual(r[key], account_info[key])
715 def test_get_account_meta(self):
716 key = 'x-account-meta-'
717 with patch.object(PC, 'get_account_info', return_value=account_info):
718 r = self.client.get_account_meta()
719 keys = [k for k in r if k.startswith(key)]
720 self.assertFalse(keys)
721 acc_info = dict(account_info)
722 acc_info['%sk1' % key] = 'v1'
723 acc_info['%sk2' % key] = 'v2'
724 acc_info['%sk3' % key] = 'v3'
725 with patch.object(PC, 'get_account_info', return_value=acc_info):
726 r = self.client.get_account_meta()
727 for k in [k for k in acc_info if k.startswith(key)]:
728 self.assertEqual(r[k], acc_info[k])
730 def test_get_account_group(self):
731 key = 'x-account-group-'
732 with patch.object(PC, 'get_account_info', return_value=account_info):
733 r = self.client.get_account_group()
734 keys = [k for k in r if k.startswith(key)]
735 self.assertFalse(keys)
736 acc_info = dict(account_info)
737 acc_info['%sk1' % key] = 'g1'
738 acc_info['%sk2' % key] = 'g2'
739 acc_info['%sk3' % key] = 'g3'
740 with patch.object(PC, 'get_account_info', return_value=acc_info):
741 r = self.client.get_account_group()
742 for k in [k for k in acc_info if k.startswith(key)]:
743 self.assertEqual(r[k], acc_info[k])
745 @patch('%s.account_post' % pithos_pkg, return_value=FR())
746 def test_set_account_meta(self, post):
747 metas = dict(k1='v1', k2='v2', k3='v3')
748 self.client.set_account_meta(metas)
751 call(update=True, metadata=metas))
753 @patch('%s.account_post' % pithos_pkg, return_value=FR())
754 def test_set_account_quota(self, post):
756 self.client.set_account_quota(qu)
757 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
759 @patch('%s.account_post' % pithos_pkg, return_value=FR())
760 def test_set_account_versioning(self, post):
761 vrs = 'n3wV3r51on1ngTyp3'
762 self.client.set_account_versioning(vrs)
765 call(update=True, versioning=vrs))
767 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
768 def test_del_container(self, delete):
770 dict(delimiter=None, until=None),
771 dict(delimiter='X', until='50m3d473')):
772 self.client.del_container(**kwarg)
773 expected = dict(kwarg)
774 expected['success'] = (204, 404, 409)
775 self.assertEqual(delete.mock_calls[-1], call(**expected))
776 for status_code in (404, 409):
777 FR.status_code = status_code
778 self.assertRaises(ClientError, self.client.del_container)
780 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
781 def test_get_container_versioning(self, GCI):
782 key = 'x-container-policy-versioning'
784 bu_cnt = self.client.container
785 for container in (None, cont):
786 r = self.client.get_container_versioning(container=container)
787 self.assertEqual(r[key], container_info[key])
788 self.assertEqual(GCI.mock_calls[-1], call())
789 self.assertEqual(bu_cnt, self.client.container)
791 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
792 def test_get_container_quota(self, GCI):
793 key = 'x-container-policy-quota'
795 bu_cnt = self.client.container
796 for container in (None, cont):
797 r = self.client.get_container_quota(container=container)
798 self.assertEqual(r[key], container_info[key])
799 self.assertEqual(GCI.mock_calls[-1], call())
800 self.assertEqual(bu_cnt, self.client.container)
802 def test_get_container_meta(self):
803 somedate = '50m3d473'
804 key = 'x-container-meta'
805 metaval = '50m3m374v41'
806 container_plus = dict(container_info)
807 container_plus[key] = metaval
808 for ret in ((container_info, {}), (container_plus, {key: metaval})):
811 'get_container_info',
812 return_value=ret[0]) as gci:
813 for until in (None, somedate):
814 r = self.client.get_container_meta(until=until)
815 self.assertEqual(r, ret[1])
816 self.assertEqual(gci.mock_calls[-1], call(until=until))
818 def test_get_container_object_meta(self):
819 somedate = '50m3d473'
820 key = 'x-container-object-meta'
821 metaval = '50m3m374v41'
822 container_plus = dict(container_info)
823 container_plus[key] = metaval
825 (container_info, {key: ''}),
826 (container_plus, {key: metaval})):
829 'get_container_info',
830 return_value=ret[0]) as gci:
831 for until in (None, somedate):
832 r = self.client.get_container_object_meta(until=until)
833 self.assertEqual(r, ret[1])
834 self.assertEqual(gci.mock_calls[-1], call(until=until))
836 @patch('%s.container_post' % pithos_pkg, return_value=FR())
837 def test_set_container_meta(self, post):
838 metas = dict(k1='v1', k2='v2', k3='v3')
839 self.client.set_container_meta(metas)
842 call(update=True, metadata=metas))
844 @patch('%s.container_post' % pithos_pkg, return_value=FR())
845 def test_del_container_meta(self, ap):
846 self.client.del_container_meta('somekey')
847 expected = [call(update=True, metadata={'somekey': ''})]
848 self.assertEqual(ap.mock_calls, expected)
850 @patch('%s.container_post' % pithos_pkg, return_value=FR())
851 def test_set_container_quota(self, post):
853 self.client.set_container_quota(qu)
854 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
856 @patch('%s.container_post' % pithos_pkg, return_value=FR())
857 def test_set_container_versioning(self, post):
858 vrs = 'n3wV3r51on1ngTyp3'
859 self.client.set_container_versioning(vrs)
862 call(update=True, versioning=vrs))
864 @patch('%s.object_delete' % pithos_pkg, return_value=FR())
865 def test_del_object(self, delete):
867 dict(delimiter=None, until=None),
868 dict(delimiter='X', until='50m3d473')):
869 self.client.del_object(obj, **kwarg)
870 self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
872 @patch('%s.object_post' % pithos_pkg, return_value=FR())
873 def test_set_object_meta(self, post):
874 metas = dict(k1='v1', k2='v2', k3='v3')
877 self.client.set_object_meta,
879 self.client.set_object_meta(obj, metas)
882 call(obj, update=True, metadata=metas))
884 @patch('%s.object_post' % pithos_pkg, return_value=FR())
885 def test_publish_object(self, post):
886 oinfo = dict(object_info)
888 oinfo['x-object-public'] = val
889 with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
890 r = self.client.publish_object(obj)
893 call(obj, public=True, update=True))
894 self.assertEqual(gof.mock_calls[-1], call(obj))
895 self.assertEqual(r, '%s%s' % (self.url[:-6], val))
897 @patch('%s.object_post' % pithos_pkg, return_value=FR())
898 def test_unpublish_object(self, post):
899 self.client.unpublish_object(obj)
902 call(obj, public=False, update=True))
904 def test_get_object_sharing(self):
905 info = dict(object_info)
906 expected = dict(read='u1,g1,u2', write='u1')
907 info['x-object-sharing'] = '; '.join(
908 ['%s=%s' % (k, v) for k, v in expected.items()])
909 with patch.object(PC, 'get_object_info', return_value=info) as GOF:
910 r = self.client.get_object_sharing(obj)
911 self.assertEqual(GOF.mock_calls[-1], call(obj))
912 self.assert_dicts_are_equal(r, expected)
913 info['x-object-sharing'] = '//'.join(
914 ['%s=%s' % (k, v) for k, v in expected.items()])
917 self.client.get_object_sharing,
919 info['x-object-sharing'] = '; '.join(
920 ['%s:%s' % (k, v) for k, v in expected.items()])
923 self.client.get_object_sharing,
925 info['x-object-sharing'] = 'read=%s' % expected['read']
926 r = self.client.get_object_sharing(obj)
927 expected.pop('write')
928 self.assert_dicts_are_equal(r, expected)
930 @patch('%s.object_post' % pithos_pkg, return_value=FR())
931 def test_set_object_sharing(self, POST):
932 read_perms = ['u1', 'g1', 'u2', 'g2']
933 write_perms = ['u1', 'g1']
935 dict(read_permition=read_perms, write_permition=write_perms),
936 dict(read_permition=read_perms),
937 dict(write_permition=write_perms),
939 self.client.set_object_sharing(obj, **kwargs)
940 kwargs['read'] = kwargs.pop('read_permition', '')
941 kwargs['write'] = kwargs.pop('write_permition', '')
944 call(obj, update=True, permissions=kwargs))
946 @patch('%s.set_object_sharing' % pithos_pkg)
947 def test_del_object_sharing(self, SOS):
948 self.client.del_object_sharing(obj)
949 self.assertEqual(SOS.mock_calls[-1], call(obj))
951 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
952 @patch('%s.object_post' % pithos_pkg, return_value=FR())
953 def test_append_object(self, post, GCI):
955 tmpFile = self._create_temp_file(num_of_blocks)
957 file_size = tmpFile.tell()
958 for turn in range(2):
962 from progress.bar import ShadyBar
963 apn_bar = ShadyBar('Mock append')
970 for i in apn_bar.iter(range(n)):
977 self.client.append_object(
979 upload_cb=append_gen if turn else None)
980 self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
981 (args, kwargs) = post.mock_calls[-1][1:3]
982 self.assertEqual(args, (obj,))
983 self.assertEqual(kwargs['content_length'], len(kwargs['data']))
984 fsize = num_of_blocks * int(kwargs['content_length'])
985 self.assertEqual(fsize, file_size)
986 self.assertEqual(kwargs['content_range'], 'bytes */*')
987 exp = 'application/octet-stream'
988 self.assertEqual(kwargs['content_type'], exp)
989 self.assertEqual(kwargs['update'], True)
991 @patch('%s.object_post' % pithos_pkg, return_value=FR())
992 def test_truncate_object(self, post):
994 self.client.truncate_object(obj, upto_bytes)
995 self.assertEqual(post.mock_calls[-1], call(
998 object_bytes=upto_bytes,
999 content_range='bytes 0-%s/*' % upto_bytes,
1000 content_type='application/octet-stream',
1001 source_object='/%s/%s' % (self.client.container, obj)))
1003 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1004 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1005 def test_overwrite_object(self, post, GCI):
1007 tmpFile = self._create_temp_file(num_of_blocks)
1009 file_size = tmpFile.tell()
1010 info = dict(object_info)
1011 info['content-length'] = file_size
1012 block_size = container_info['x-container-block-size']
1013 with patch.object(PC, 'get_object_info', return_value=info) as GOI:
1016 (file_size + 1, file_size + 2)):
1020 self.client.overwrite_object,
1021 obj, start, end, tmpFile)
1022 for start, end in ((0, 144), (144, 233), (233, file_size)):
1025 exp_size = end - start + 1
1026 if not start or exp_size > block_size:
1028 from progress.bar import ShadyBar
1029 owr_bar = ShadyBar('Mock append')
1036 for i in owr_bar.iter(range(n)):
1040 if exp_size > block_size:
1041 exp_size = exp_size % block_size or block_size
1043 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1044 self.assertEqual(GOI.mock_calls[-1], call(obj))
1045 self.assertEqual(GCI.mock_calls[-1], call())
1046 (args, kwargs) = post.mock_calls[-1][1:3]
1047 self.assertEqual(args, (obj,))
1048 self.assertEqual(len(kwargs['data']), exp_size)
1049 self.assertEqual(kwargs['content_length'], exp_size)
1050 self.assertEqual(kwargs['update'], True)
1051 exp = 'application/octet-stream'
1052 self.assertEqual(kwargs['content_type'], exp)
1054 @patch('%s.set_param' % client_pkg)
1055 @patch('%s.get' % pithos_pkg, return_value=FR())
1056 def test_get_sharing_accounts(self, get, SP):
1060 dict(limit='50m3-11m17'),
1062 dict(limit='50m3-11m17', marker='X')):
1063 r = self.client.get_sharing_accounts(**kws)
1064 self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1065 limit, marker = kws.get('limit', None), kws.get('marker', None)
1066 self.assertEqual(SP.mock_calls[-2], call(
1068 iff=limit is not None))
1069 self.assertEqual(SP.mock_calls[-1], call(
1071 iff=marker is not None))
1072 self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1073 for i in range(len(r)):
1074 self.assert_dicts_are_equal(r[i], sharers[i])
1076 @patch('%s.object_get' % pithos_pkg, return_value=FR())
1077 def test_get_object_versionlist(self, get):
1078 info = dict(object_info)
1079 info['versions'] = ['v1', 'v2']
1081 r = self.client.get_object_versionlist(obj)
1084 call(obj, format='json', version='list'))
1085 self.assertEqual(r, info['versions'])
1087 if __name__ == '__main__':
1088 from sys import argv
1089 from kamaki.clients.test import runTestCase
1090 runTestCase(Pithos, 'Pithos+ Client', argv[1:])