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
42 pithos_pkg = 'kamaki.clients.pithos.PithosClient'
44 user_id = 'ac0un7-1d-5tr1ng'
48 'content-language': 'en-us',
49 'content-type': 'text/html; charset=utf-8',
50 'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
51 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
52 'server': 'gunicorn/0.14.5',
53 'vary': 'Accept-Language',
54 'x-account-bytes-used': '751615526',
55 'x-account-container-count': 7,
56 'x-account-policy-quota': 53687091200,
57 'x-account-policy-versioning': 'auto'}
59 'content-language': 'en-us',
60 'content-type': 'text/html; charset=utf-8',
61 'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
62 'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
63 'server': 'gunicorn/0.14.5',
64 'vary': 'Accept-Language',
65 'x-container-block-hash': 'sha256',
66 'x-container-block-size': 4194304,
67 'x-container-bytes-used': 309528938,
68 'x-container-object-count': 14,
69 'x-container-object-meta': '',
70 'x-container-policy-quota': 53687091200,
71 'x-container-policy-versioning': 'auto'}
73 'content-language': 'en-us',
74 'content-length': 254965,
75 'content-type': 'application/octet-stream',
76 'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
78 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
79 'server': 'gunicorn/0.14.5',
80 'vary': 'Accept-Language',
81 'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
82 'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
83 'x-object-version': '525996',
84 'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
85 'x-object-meta-k1': 'v1',
86 'x-object-meta-k2': 'v2'}
90 last_modified="2013-02-27T11:56:09.893033+00:00",
93 x_container_policy=dict(quota="21474836480", versioning="auto")),
96 last_modified="2012-10-23T12:25:17.229187+00:00",
99 x_container_policy=dict(quota="21474836480", versioning="auto"))]
102 name="The_Secret_Garden.zip",
103 x_object_public="/public/wdp9p",
105 x_object_version_timestamp="1360237915.7027509",
106 x_object_uuid="s0m3uu1df0r0bj0n3",
107 last_modified="2013-02-07T11:51:55.702751+00:00",
108 content_type="application/octet-stream",
109 x_object_hash="0afdf29f71cd53126225c3f54ca",
110 x_object_version=17737,
111 x_object_modified_by=user_id),
113 name="The_Revealed_Garden.zip",
114 x_object_public="/public/wpd7p",
116 x_object_version_timestamp="13602915.7027509",
117 x_object_uuid="s0m3uu1df0r0bj70w",
118 last_modified="2013-02-07T11:51:55.702751+00:00",
119 content_type="application/octet-stream",
120 x_object_hash="0afdf29f71cd53126225c3f54ca",
121 x_object_version=17737,
122 x_object_modified_by=user_id)]
123 object_hashmap = dict(
124 block_hash="sha256", block_size=4194304, bytes=33554432,
126 "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
127 "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
128 "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
129 "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
130 "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
131 "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
132 "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
133 "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
135 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b1a-82d5"),
136 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b2a-f2d5"),
137 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="2b1a-82d6")]
141 """FR stands for Fake Response"""
152 class Pithos(TestCase):
156 def _create_temp_file(self, num_of_blocks):
157 self.files.append(NamedTemporaryFile())
158 tmpFile = self.files[-1]
159 file_size = num_of_blocks * 4 * 1024 * 1024
160 print('\n\tCreate tmp file')
161 tmpFile.write(urandom(file_size))
167 def assert_dicts_are_equal(self, d1, d2):
168 for k, v in d1.items():
169 self.assertTrue(k in d2)
170 if isinstance(v, dict):
171 self.assert_dicts_are_equal(v, d2[k])
173 self.assertEqual(unicode(v), unicode(d2[k]))
176 self.url = 'https://www.example.com/pithos'
177 self.token = 'p17h0570k3n'
178 self.client = PC(self.url, self.token)
179 self.client.account = user_id
180 self.client.container = 'c0nt@1n3r_i'
190 # Pithos+ methods that extend storage API
192 @patch('%s.account_head' % pithos_pkg, return_value=FR())
193 def test_get_account_info(self, AH):
194 FR.headers = account_info
195 r = self.client.get_account_info()
196 self.assert_dicts_are_equal(r, account_info)
197 self.assertEqual(AH.mock_calls[-1], call(until=None))
199 r = self.client.get_account_info(until=unt)
200 self.assert_dicts_are_equal(r, account_info)
201 self.assertEqual(AH.mock_calls[-1], call(until=unt))
203 self.assertRaises(ClientError, self.client.get_account_info)
205 @patch('%s.account_post' % pithos_pkg, return_value=FR())
206 def test_del_account_meta(self, ap):
207 keys = ['k1', 'k2', 'k3']
210 self.client.del_account_meta(key)
211 expected.append(call(update=True, metadata={key: ''}))
212 self.assertEqual(ap.mock_calls, expected)
214 @patch('%s.container_head' % pithos_pkg, return_value=FR())
215 def test_get_container_info(self, ch):
216 FR.headers = container_info
217 r = self.client.get_container_info()
218 self.assert_dicts_are_equal(r, container_info)
220 r = self.client.get_container_info(until=u)
221 self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
223 @patch('%s.account_get' % pithos_pkg, return_value=FR())
224 def test_list_containers(self, get):
225 FR.json = container_list
226 r = self.client.list_containers()
227 for i in range(len(r)):
228 self.assert_dicts_are_equal(r[i], container_list[i])
230 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
231 @patch('%s.container_post' % pithos_pkg, return_value=FR())
232 @patch('%s.object_put' % pithos_pkg, return_value=FR())
233 def test_upload_object(self, CI, CP, OP):
235 tmpFile = self._create_temp_file(num_of_blocks)
238 self.client.upload_object(obj, tmpFile)
239 self.assertEqual(PC.get_container_info.mock_calls, [call()])
240 [call1, call2] = PC.object_put.mock_calls
242 (args1, kwargs1) = call1[1:3]
243 (args2, kwargs2) = call2[1:3]
244 self.assertEqual(args1, (obj,))
250 hashes=['s0m3h@5h'] * num_of_blocks,
251 bytes=num_of_blocks * 4 * 1024 * 1024),
253 content_encoding=None,
254 content_type='application/octet-stream',
255 content_disposition=None,
258 for k, v in expected1.items():
260 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
261 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
263 self.assertEqual(v, kwargs1[k])
265 (args2, kwargs2) = call2[1:3]
266 self.assertEqual(args2, (obj,))
269 hashes=['s0m3h@5h'] * num_of_blocks,
270 bytes=num_of_blocks * 4 * 1024 * 1024),
271 content_type='application/octet-stream',
275 for k, v in expected2.items():
277 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
278 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
280 self.assertEqual(v, kwargs2[k])
287 from progress.bar import ShadyBar
288 blck_bar = ShadyBar('Mock blck calc.')
289 upld_bar = ShadyBar('Mock uplds')
294 if blck_bar and upld_bar:
297 for i in blck_bar.iter(range(n)):
302 for i in upld_bar.iter(range(n)):
307 self.client.upload_object(
309 hash_cb=blck_gen, upload_cb=upld_gen)
311 for i, c in enumerate(OP.mock_calls[-mock_offset:]):
312 self.assertEqual(OP.mock_calls[i], c)
317 sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
318 self.client.upload_object(obj, tmpFile,
319 content_type=ctype, sharing=sharing)
320 self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
321 self.assert_dicts_are_equal(
322 OP.mock_calls[-2][2]['permissions'],
330 content_disposition=ctype + 'd15p051710n',
332 content_encoding='802.11')
333 self.client.upload_object(obj, tmpFile, **kwargs)
334 for arg, val in kwargs.items():
335 self.assertEqual(OP.mock_calls[-2][2][arg], val)
337 def test_get_object_info(self):
338 FR.headers = object_info
340 with patch.object(PC, 'object_head', return_value=FR()) as head:
341 r = self.client.get_object_info(obj)
342 self.assertEqual(r, object_info)
343 r = self.client.get_object_info(obj, version=version)
344 self.assertEqual(head.mock_calls, [
345 call(obj, version=None),
346 call(obj, version=version)])
349 side_effect=ClientError('Obj not found', 404)):
352 self.client.get_object_info,
353 obj, version=version)
355 @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
356 def test_get_object_meta(self, GOI):
357 for version in (None, 'v3r510n'):
358 r = self.client.get_object_meta(obj, version)
359 for k in [k for k in object_info if k.startswith('x-object-meta')]:
360 self.assertEqual(r.pop(k), object_info[k])
361 self.assertFalse(len(r))
362 self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
364 @patch('%s.object_post' % pithos_pkg, return_value=FR())
365 def test_del_object_meta(self, post):
366 metakey = '50m3m3t4k3y'
367 self.client.del_object_meta(obj, metakey)
368 expected = call(obj, update=True, metadata={metakey: ''})
369 self.assertEqual(post.mock_calls[-1], expected)
371 @patch('%s.object_put' % pithos_pkg, return_value=FR())
372 def test_copy_object(self, put):
373 src_cont = 'src-c0nt41n3r'
375 dst_cont = 'dst-c0nt41n3r'
382 copy_from='/%s/%s' % (src_cont, src_obj),
387 self.client.copy_object(src_cont, src_obj, dst_cont)
388 self.assertEqual(put.mock_calls[-1], expected)
389 self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
390 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
392 source_version='src-v3r510n',
393 source_account='src-4cc0un7',
395 content_type='c0n73n7Typ3',
397 self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
398 for k, v in kwargs.items():
399 self.assertEqual(v, put.mock_calls[-1][2][k])
401 @patch('%s.object_put' % pithos_pkg, return_value=FR())
402 def test_move_object(self, put):
403 src_cont = 'src-c0nt41n3r'
405 dst_cont = 'dst-c0nt41n3r'
412 move_from='/%s/%s' % (src_cont, src_obj),
417 self.client.move_object(src_cont, src_obj, dst_cont)
418 self.assertEqual(put.mock_calls[-1], expected)
419 self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
420 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
422 source_version='src-v3r510n',
423 source_account='src-4cc0un7',
425 content_type='c0n73n7Typ3',
427 self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
428 for k, v in kwargs.items():
429 self.assertEqual(v, put.mock_calls[-1][2][k])
431 # Pithos+ only methods
433 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
434 def test_purge_container(self, cd):
435 self.client.purge_container()
436 self.assertTrue('until' in cd.mock_calls[-1][2])
437 cont = self.client.container
438 self.client.purge_container('another-container')
439 self.assertEqual(self.client.container, cont)
441 @patch('%s.object_put' % pithos_pkg, return_value=FR())
442 def test_upload_object_unchunked(self, put):
444 tmpFile = self._create_temp_file(num_of_blocks)
447 data=num_of_blocks * 4 * 1024 * 1024,
449 content_encoding='some content_encoding',
450 content_type='some content-type',
451 content_disposition='some content_disposition',
453 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
454 self.client.upload_object_unchunked(obj, tmpFile)
455 self.assertEqual(put.mock_calls[-1][1], (obj,))
457 sorted(put.mock_calls[-1][2].keys()),
458 sorted(expected.keys()))
459 kwargs = dict(expected)
460 kwargs.pop('success')
461 kwargs['size'] = kwargs.pop('data')
462 kwargs['sharing'] = kwargs.pop('permissions')
464 self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
465 pmc = put.mock_calls[-1][2]
466 for k, v in expected.items():
468 self.assertEqual(len(pmc[k]), v)
470 self.assertEqual(pmc[k], v)
473 self.client.upload_object_unchunked,
474 obj, tmpFile, withHashFile=True)
476 @patch('%s.object_put' % pithos_pkg, return_value=FR())
477 def test_create_object_by_manifestation(self, put):
478 manifest = '%s/%s' % (self.client.container, obj)
481 content_encoding='some content_encoding',
482 content_type='some content-type',
483 content_disposition='some content_disposition',
485 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
486 self.client.create_object_by_manifestation(obj)
487 expected = dict(content_length=0, manifest=manifest)
489 expected['permissions' if k == 'sharing' else k] = None
490 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
491 self.client.create_object_by_manifestation(obj, **kwargs)
492 expected.update(kwargs)
493 expected['permissions'] = expected.pop('sharing')
494 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
496 @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
497 @patch('%s.object_get' % pithos_pkg, return_value=FR())
498 def test_download_object(self, GOH, GET):
500 tmpFile = self._create_temp_file(num_of_blocks)
501 FR.content = tmpFile.read(4 * 1024 * 1024)
502 tmpFile = self._create_temp_file(num_of_blocks)
504 num_of_blocks = len(object_hashmap['hashes'])
509 if_match='if and only if',
510 if_none_match='if and only not',
511 if_modified_since='what if not?',
512 if_unmodified_since='this happens if not!',
513 async_headers=dict(Range='bytes=0-88888888'))
515 self.client.download_object(obj, tmpFile)
516 self.assertEqual(len(GET.mock_calls), num_of_blocks)
517 self.assertEqual(GET.mock_calls[-1][1], (obj,))
518 for k, v in kwargs.items():
519 if k == 'async_headers':
520 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
521 elif k in ('resume', 'range_str'):
524 self.assertEqual(GET.mock_calls[-1][2][k], None)
526 # Check ranges are consecutive
529 for c in GET.mock_calls:
530 rng_str = c[2]['async_headers']['Range']
531 (start, rng_str) = rng_str.split('=')
532 (start, end) = rng_str.split('-')
536 for i, start in enumerate(sorted(starts)):
538 int(ends[i - 1]) == int(start) - 1
542 from progress.bar import ShadyBar
543 dl_bar = ShadyBar('Mock dl')
550 for i in dl_bar.iter(range(n)):
555 self.client.download_object(obj, tmpFile, download_cb=blck_gen)
556 self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
559 kwargs.pop('async_headers')
561 self.client.download_object(obj, tmpFile, **kwargs)
562 for k, v in kwargs.items():
565 GET.mock_calls[-1][2]['data_range'],
568 self.assertEqual(GET.mock_calls[-1][2][k], v)
570 # ALl options on no tty
577 self.client.download_object(obj, tmpFile, **kwargs)
578 for k, v in kwargs.items():
580 self.assertTrue('data_range' in GET.mock_calls[-1][2])
582 self.assertEqual(GET.mock_calls[-1][2][k], v)
584 def test_get_object_hashmap(self):
585 FR.json = object_hashmap
586 for empty in (304, 412):
589 side_effect=ClientError('Empty', status=empty)):
590 r = self.client.get_object_hashmap(obj)
591 self.assertEqual(r, {})
597 if_etag_not_match=None,
598 if_modified_since=None,
599 if_unmodified_since=None)
601 version='s0m3v3r51on',
603 if_none_match='if non match',
604 if_modified_since='some date here',
605 if_unmodified_since='some date here',
607 with patch.object(PC, 'object_get', return_value=FR()) as get:
608 r = self.client.get_object_hashmap(obj)
609 self.assertEqual(r, object_hashmap)
610 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
611 r = self.client.get_object_hashmap(obj, **kwargs)
612 exp_args['if_etag_match'] = kwargs.pop('if_match')
613 exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
614 exp_args.update(kwargs)
615 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
617 @patch('%s.account_post' % pithos_pkg, return_value=FR())
618 def test_set_account_group(self, post):
619 (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
620 self.client.set_account_group(group, usernames)
623 call(update=True, groups={group: usernames}))
625 @patch('%s.account_post' % pithos_pkg, return_value=FR())
626 def test_del_account_group(self, post):
628 self.client.del_account_group(group)
631 call(update=True, groups={group: []}))
633 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
634 def test_get_account_quota(self, GAI):
635 key = 'x-account-policy-quota'
636 r = self.client.get_account_quota()
637 self.assertEqual(r[key], account_info[key])
639 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
640 def test_get_account_versioning(self, GAI):
641 key = 'x-account-policy-versioning'
642 r = self.client.get_account_versioning()
643 self.assertEqual(r[key], account_info[key])
645 def test_get_account_meta(self):
646 key = 'x-account-meta-'
647 with patch.object(PC, 'get_account_info', return_value=account_info):
648 r = self.client.get_account_meta()
649 keys = [k for k in r if k.startswith(key)]
650 self.assertFalse(keys)
651 acc_info = dict(account_info)
652 acc_info['%sk1' % key] = 'v1'
653 acc_info['%sk2' % key] = 'v2'
654 acc_info['%sk3' % key] = 'v3'
655 with patch.object(PC, 'get_account_info', return_value=acc_info):
656 r = self.client.get_account_meta()
657 for k in [k for k in acc_info if k.startswith(key)]:
658 self.assertEqual(r[k], acc_info[k])
660 def test_get_account_group(self):
661 key = 'x-account-group-'
662 with patch.object(PC, 'get_account_info', return_value=account_info):
663 r = self.client.get_account_group()
664 keys = [k for k in r if k.startswith(key)]
665 self.assertFalse(keys)
666 acc_info = dict(account_info)
667 acc_info['%sk1' % key] = 'g1'
668 acc_info['%sk2' % key] = 'g2'
669 acc_info['%sk3' % key] = 'g3'
670 with patch.object(PC, 'get_account_info', return_value=acc_info):
671 r = self.client.get_account_group()
672 for k in [k for k in acc_info if k.startswith(key)]:
673 self.assertEqual(r[k], acc_info[k])
675 @patch('%s.account_post' % pithos_pkg, return_value=FR())
676 def test_set_account_meta(self, post):
677 metas = dict(k1='v1', k2='v2', k3='v3')
678 self.client.set_account_meta(metas)
681 call(update=True, metadata=metas))
683 @patch('%s.account_post' % pithos_pkg, return_value=FR())
684 def test_set_account_quota(self, post):
686 self.client.set_account_quota(qu)
687 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
689 @patch('%s.account_post' % pithos_pkg, return_value=FR())
690 def test_set_account_versioning(self, post):
691 vrs = 'n3wV3r51on1ngTyp3'
692 self.client.set_account_versioning(vrs)
695 call(update=True, versioning=vrs))
697 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
698 def test_del_container(self, delete):
700 dict(delimiter=None, until=None),
701 dict(delimiter='X', until='50m3d473')):
702 self.client.del_container(**kwarg)
703 expected = dict(kwarg)
704 expected['success'] = (204, 404, 409)
705 self.assertEqual(delete.mock_calls[-1], call(**expected))
706 for status_code in (404, 409):
707 FR.status_code = status_code
708 self.assertRaises(ClientError, self.client.del_container)
710 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
711 def test_get_container_versioning(self, GCI):
712 key = 'x-container-policy-versioning'
714 bu_cnt = self.client.container
715 for container in (None, cont):
716 r = self.client.get_container_versioning(container=container)
717 self.assertEqual(r[key], container_info[key])
718 self.assertEqual(GCI.mock_calls[-1], call())
719 self.assertEqual(bu_cnt, self.client.container)
721 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
722 def test_get_container_quota(self, GCI):
723 key = 'x-container-policy-quota'
725 bu_cnt = self.client.container
726 for container in (None, cont):
727 r = self.client.get_container_quota(container=container)
728 self.assertEqual(r[key], container_info[key])
729 self.assertEqual(GCI.mock_calls[-1], call())
730 self.assertEqual(bu_cnt, self.client.container)
732 def test_get_container_meta(self):
733 somedate = '50m3d473'
734 key = 'x-container-meta'
735 metaval = '50m3m374v41'
736 container_plus = dict(container_info)
737 container_plus[key] = metaval
738 for ret in ((container_info, {}), (container_plus, {key: metaval})):
741 'get_container_info',
742 return_value=ret[0]) as gci:
743 for until in (None, somedate):
744 r = self.client.get_container_meta(until=until)
745 self.assertEqual(r, ret[1])
746 self.assertEqual(gci.mock_calls[-1], call(until=until))
748 def test_get_container_object_meta(self):
749 somedate = '50m3d473'
750 key = 'x-container-object-meta'
751 metaval = '50m3m374v41'
752 container_plus = dict(container_info)
753 container_plus[key] = metaval
755 (container_info, {key: ''}),
756 (container_plus, {key: metaval})):
759 'get_container_info',
760 return_value=ret[0]) as gci:
761 for until in (None, somedate):
762 r = self.client.get_container_object_meta(until=until)
763 self.assertEqual(r, ret[1])
764 self.assertEqual(gci.mock_calls[-1], call(until=until))
766 @patch('%s.container_post' % pithos_pkg, return_value=FR())
767 def test_set_container_meta(self, post):
768 metas = dict(k1='v1', k2='v2', k3='v3')
769 self.client.set_container_meta(metas)
772 call(update=True, metadata=metas))
774 @patch('%s.container_post' % pithos_pkg, return_value=FR())
775 def test_del_container_meta(self, ap):
776 self.client.del_container_meta('somekey')
777 expected = [call(update=True, metadata={'somekey': ''})]
778 self.assertEqual(ap.mock_calls, expected)
780 @patch('%s.container_post' % pithos_pkg, return_value=FR())
781 def test_set_container_quota(self, post):
783 self.client.set_container_quota(qu)
784 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
786 @patch('%s.container_post' % pithos_pkg, return_value=FR())
787 def test_set_container_versioning(self, post):
788 vrs = 'n3wV3r51on1ngTyp3'
789 self.client.set_container_versioning(vrs)
792 call(update=True, versioning=vrs))
794 @patch('%s.object_delete' % pithos_pkg, return_value=FR())
795 def test_del_object(self, delete):
797 dict(delimiter=None, until=None),
798 dict(delimiter='X', until='50m3d473')):
799 self.client.del_object(obj, **kwarg)
800 self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
802 @patch('%s.object_post' % pithos_pkg, return_value=FR())
803 def test_set_object_meta(self, post):
804 metas = dict(k1='v1', k2='v2', k3='v3')
807 self.client.set_object_meta,
809 self.client.set_object_meta(obj, metas)
812 call(obj, update=True, metadata=metas))
814 @patch('%s.object_post' % pithos_pkg, return_value=FR())
815 def test_publish_object(self, post):
816 oinfo = dict(object_info)
818 oinfo['x-object-public'] = val
819 with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
820 r = self.client.publish_object(obj)
823 call(obj, public=True, update=True))
824 self.assertEqual(gof.mock_calls[-1], call(obj))
825 self.assertEqual(r, '%s%s' % (self.url[:-6], val))
827 @patch('%s.object_post' % pithos_pkg, return_value=FR())
828 def test_unpublish_object(self, post):
829 self.client.unpublish_object(obj)
832 call(obj, public=False, update=True))
834 def test_get_object_sharing(self):
835 info = dict(object_info)
836 expected = dict(read='u1,g1,u2', write='u1')
837 info['x-object-sharing'] = '; '.join(
838 ['%s=%s' % (k, v) for k, v in expected.items()])
839 with patch.object(PC, 'get_object_info', return_value=info) as GOF:
840 r = self.client.get_object_sharing(obj)
841 self.assertEqual(GOF.mock_calls[-1], call(obj))
842 self.assert_dicts_are_equal(r, expected)
843 info['x-object-sharing'] = '//'.join(
844 ['%s=%s' % (k, v) for k, v in expected.items()])
847 self.client.get_object_sharing,
849 info['x-object-sharing'] = '; '.join(
850 ['%s:%s' % (k, v) for k, v in expected.items()])
853 self.client.get_object_sharing,
855 info['x-object-sharing'] = 'read=%s' % expected['read']
856 r = self.client.get_object_sharing(obj)
857 expected.pop('write')
858 self.assert_dicts_are_equal(r, expected)
860 @patch('%s.object_post' % pithos_pkg, return_value=FR())
861 def test_set_object_sharing(self, POST):
862 read_perms = ['u1', 'g1', 'u2', 'g2']
863 write_perms = ['u1', 'g1']
865 dict(read_permition=read_perms, write_permition=write_perms),
866 dict(read_permition=read_perms),
867 dict(write_permition=write_perms),
869 self.client.set_object_sharing(obj, **kwargs)
870 kwargs['read'] = kwargs.pop('read_permition', '')
871 kwargs['write'] = kwargs.pop('write_permition', '')
874 call(obj, update=True, permissions=kwargs))
876 @patch('%s.set_object_sharing' % pithos_pkg)
877 def test_del_object_sharing(self, SOS):
878 self.client.del_object_sharing(obj)
879 self.assertEqual(SOS.mock_calls[-1], call(obj))
881 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
882 @patch('%s.object_post' % pithos_pkg, return_value=FR())
883 def test_append_object(self, post, GCI):
885 tmpFile = self._create_temp_file(num_of_blocks)
887 file_size = tmpFile.tell()
888 for turn in range(2):
892 from progress.bar import ShadyBar
893 apn_bar = ShadyBar('Mock append')
900 for i in apn_bar.iter(range(n)):
907 self.client.append_object(
909 upload_cb=append_gen if turn else None)
910 self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
911 (args, kwargs) = post.mock_calls[-1][1:3]
912 self.assertEqual(args, (obj,))
913 self.assertEqual(kwargs['content_length'], len(kwargs['data']))
914 fsize = num_of_blocks * int(kwargs['content_length'])
915 self.assertEqual(fsize, file_size)
916 self.assertEqual(kwargs['content_range'], 'bytes */*')
917 exp = 'application/octet-stream'
918 self.assertEqual(kwargs['content_type'], exp)
919 self.assertEqual(kwargs['update'], True)
921 @patch('%s.object_post' % pithos_pkg, return_value=FR())
922 def test_truncate_object(self, post):
924 self.client.truncate_object(obj, upto_bytes)
925 self.assertEqual(post.mock_calls[-1], call(
928 object_bytes=upto_bytes,
929 content_range='bytes 0-%s/*' % upto_bytes,
930 content_type='application/octet-stream',
931 source_object='/%s/%s' % (self.client.container, obj)))
933 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
934 @patch('%s.object_post' % pithos_pkg, return_value=FR())
935 def test_overwrite_object(self, post, GCI):
937 tmpFile = self._create_temp_file(num_of_blocks)
939 file_size = tmpFile.tell()
940 info = dict(object_info)
941 info['content-length'] = file_size
942 block_size = container_info['x-container-block-size']
943 with patch.object(PC, 'get_object_info', return_value=info) as GOI:
946 (file_size + 1, file_size + 2)):
950 self.client.overwrite_object,
951 obj, start, end, tmpFile)
952 for start, end in ((0, 144), (144, 233), (233, file_size)):
955 exp_size = end - start + 1
956 if not start or exp_size > block_size:
958 from progress.bar import ShadyBar
959 owr_bar = ShadyBar('Mock append')
966 for i in owr_bar.iter(range(n)):
970 if exp_size > block_size:
971 exp_size = exp_size % block_size or block_size
973 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
974 self.assertEqual(GOI.mock_calls[-1], call(obj))
975 self.assertEqual(GCI.mock_calls[-1], call())
976 (args, kwargs) = post.mock_calls[-1][1:3]
977 self.assertEqual(args, (obj,))
978 self.assertEqual(len(kwargs['data']), exp_size)
979 self.assertEqual(kwargs['content_length'], exp_size)
980 self.assertEqual(kwargs['update'], True)
981 exp = 'application/octet-stream'
982 self.assertEqual(kwargs['content_type'], exp)
984 @patch('%s.set_param' % pithos_pkg)
985 @patch('%s.get' % pithos_pkg, return_value=FR())
986 def test_get_sharing_accounts(self, get, SP):
990 dict(limit='50m3-11m17'),
992 dict(limit='50m3-11m17', marker='X')):
993 r = self.client.get_sharing_accounts(**kws)
994 self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
995 limit, marker = kws.get('limit', None), kws.get('marker', None)
996 self.assertEqual(SP.mock_calls[-2], call(
998 iff=limit is not None))
999 self.assertEqual(SP.mock_calls[-1], call(
1001 iff=marker is not None))
1002 self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1003 for i in range(len(r)):
1004 self.assert_dicts_are_equal(r[i], sharers[i])
1006 @patch('%s.object_get' % pithos_pkg, return_value=FR())
1007 def test_get_object_versionlist(self, get):
1008 info = dict(object_info)
1009 info['versions'] = ['v1', 'v2']
1011 r = self.client.get_object_versionlist(obj)
1014 call(obj, format='json', version='list'))
1015 self.assertEqual(r, info['versions'])
1017 if __name__ == '__main__':
1018 from sys import argv
1019 from kamaki.clients.test import runTestCase
1020 runTestCase(Pithos, 'Pithos+ Client', argv[1:])