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.post' % pithos_pkg, return_value=FR())
372 @patch('%s.set_header' % pithos_pkg)
373 def test_replace_object_meta(self, SH, post):
374 metas = dict(k1='new1', k2='new2', k3='new3')
375 cont = self.client.container
376 self.client.replace_object_meta(metas)
377 expected = call('/%s/%s' % (user_id, cont), success=202)
378 self.assertEqual(post.mock_calls[-1], expected)
379 prfx = 'X-Object-Meta-'
380 expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
381 self.assertEqual(PC.set_header.mock_calls, expected)
383 @patch('%s.object_put' % pithos_pkg, return_value=FR())
384 def test_copy_object(self, put):
385 src_cont = 'src-c0nt41n3r'
387 dst_cont = 'dst-c0nt41n3r'
394 copy_from='/%s/%s' % (src_cont, src_obj),
399 self.client.copy_object(src_cont, src_obj, dst_cont)
400 self.assertEqual(put.mock_calls[-1], expected)
401 self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
402 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
404 source_version='src-v3r510n',
405 source_account='src-4cc0un7',
407 content_type='c0n73n7Typ3',
409 self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
410 for k, v in kwargs.items():
411 self.assertEqual(v, put.mock_calls[-1][2][k])
413 @patch('%s.object_put' % pithos_pkg, return_value=FR())
414 def test_move_object(self, put):
415 src_cont = 'src-c0nt41n3r'
417 dst_cont = 'dst-c0nt41n3r'
424 move_from='/%s/%s' % (src_cont, src_obj),
429 self.client.move_object(src_cont, src_obj, dst_cont)
430 self.assertEqual(put.mock_calls[-1], expected)
431 self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
432 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
434 source_version='src-v3r510n',
435 source_account='src-4cc0un7',
437 content_type='c0n73n7Typ3',
439 self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
440 for k, v in kwargs.items():
441 self.assertEqual(v, put.mock_calls[-1][2][k])
443 @patch('%s.delete' % pithos_pkg, return_value=FR())
444 def test_delete_object(self, delete):
445 cont = self.client.container
446 self.client.delete_object(obj)
448 delete.mock_calls[-1],
449 call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
451 self.assertRaises(ClientError, self.client.delete_object, obj)
453 @patch('%s.get' % pithos_pkg, return_value=FR())
454 @patch('%s.set_param' % pithos_pkg)
455 def test_list_objects(self, SP, get):
456 FR.json = object_list
457 acc = self.client.account
458 cont = self.client.container
460 r = self.client.list_objects()
461 for i in range(len(r)):
462 self.assert_dicts_are_equal(r[i], object_list[i])
463 self.assertEqual(get.mock_calls, [
464 call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
465 self.assertEqual(SP.mock_calls, [call('format', 'json')])
467 self.assertEqual(self.client.list_objects(), [])
469 self.assertRaises(ClientError, self.client.list_objects)
471 @patch('%s.get' % pithos_pkg, return_value=FR())
472 @patch('%s.set_param' % pithos_pkg)
473 def test_list_objects_in_path(self, SP, get):
474 FR.json = object_list
475 path = '/some/awsome/path'
476 acc = self.client.account
477 cont = self.client.container
479 self.client.list_objects_in_path(path)
480 self.assertEqual(get.mock_calls, [
481 call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
482 self.assertEqual(SP.mock_calls, [
483 call('format', 'json'), call('path', path)])
485 self.assertRaises(ClientError, self.client.list_objects)
487 # Pithos+ only methods
489 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
490 def test_purge_container(self, cd):
491 self.client.purge_container()
492 self.assertTrue('until' in cd.mock_calls[-1][2])
493 cont = self.client.container
494 self.client.purge_container('another-container')
495 self.assertEqual(self.client.container, cont)
497 @patch('%s.object_put' % pithos_pkg, return_value=FR())
498 def test_upload_object_unchunked(self, put):
500 tmpFile = self._create_temp_file(num_of_blocks)
503 data=num_of_blocks * 4 * 1024 * 1024,
505 content_encoding='some content_encoding',
506 content_type='some content-type',
507 content_disposition='some content_disposition',
509 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
510 self.client.upload_object_unchunked(obj, tmpFile)
511 self.assertEqual(put.mock_calls[-1][1], (obj,))
513 sorted(put.mock_calls[-1][2].keys()),
514 sorted(expected.keys()))
515 kwargs = dict(expected)
516 kwargs.pop('success')
517 kwargs['size'] = kwargs.pop('data')
518 kwargs['sharing'] = kwargs.pop('permissions')
520 self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
521 pmc = put.mock_calls[-1][2]
522 for k, v in expected.items():
524 self.assertEqual(len(pmc[k]), v)
526 self.assertEqual(pmc[k], v)
529 self.client.upload_object_unchunked,
530 obj, tmpFile, withHashFile=True)
532 @patch('%s.object_put' % pithos_pkg, return_value=FR())
533 def test_create_object_by_manifestation(self, put):
534 manifest = '%s/%s' % (self.client.container, obj)
537 content_encoding='some content_encoding',
538 content_type='some content-type',
539 content_disposition='some content_disposition',
541 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
542 self.client.create_object_by_manifestation(obj)
543 expected = dict(content_length=0, manifest=manifest)
545 expected['permissions' if k == 'sharing' else k] = None
546 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
547 self.client.create_object_by_manifestation(obj, **kwargs)
548 expected.update(kwargs)
549 expected['permissions'] = expected.pop('sharing')
550 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
552 @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
553 @patch('%s.object_get' % pithos_pkg, return_value=FR())
554 def test_download_object(self, GOH, GET):
556 tmpFile = self._create_temp_file(num_of_blocks)
557 FR.content = tmpFile.read(4 * 1024 * 1024)
558 tmpFile = self._create_temp_file(num_of_blocks)
560 num_of_blocks = len(object_hashmap['hashes'])
565 if_match='if and only if',
566 if_none_match='if and only not',
567 if_modified_since='what if not?',
568 if_unmodified_since='this happens if not!',
569 async_headers=dict(Range='bytes=0-88888888'))
571 self.client.download_object(obj, tmpFile)
572 self.assertEqual(len(GET.mock_calls), num_of_blocks)
573 self.assertEqual(GET.mock_calls[-1][1], (obj,))
574 for k, v in kwargs.items():
575 if k == 'async_headers':
576 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
577 elif k in ('resume', 'range_str'):
580 self.assertEqual(GET.mock_calls[-1][2][k], None)
582 # Check ranges are consecutive
585 for c in GET.mock_calls:
586 rng_str = c[2]['async_headers']['Range']
587 (start, rng_str) = rng_str.split('=')
588 (start, end) = rng_str.split('-')
592 for i, start in enumerate(sorted(starts)):
594 int(ends[i - 1]) == int(start) - 1
598 from progress.bar import ShadyBar
599 dl_bar = ShadyBar('Mock dl')
606 for i in dl_bar.iter(range(n)):
611 self.client.download_object(obj, tmpFile, download_cb=blck_gen)
612 self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
615 kwargs.pop('async_headers')
617 self.client.download_object(obj, tmpFile, **kwargs)
618 for k, v in kwargs.items():
621 GET.mock_calls[-1][2]['data_range'],
624 self.assertEqual(GET.mock_calls[-1][2][k], v)
626 # ALl options on no tty
633 self.client.download_object(obj, tmpFile, **kwargs)
634 for k, v in kwargs.items():
636 self.assertTrue('data_range' in GET.mock_calls[-1][2])
638 self.assertEqual(GET.mock_calls[-1][2][k], v)
640 def test_get_object_hashmap(self):
641 FR.json = object_hashmap
642 for empty in (304, 412):
645 side_effect=ClientError('Empty', status=empty)):
646 r = self.client.get_object_hashmap(obj)
647 self.assertEqual(r, {})
653 if_etag_not_match=None,
654 if_modified_since=None,
655 if_unmodified_since=None)
657 version='s0m3v3r51on',
659 if_none_match='if non match',
660 if_modified_since='some date here',
661 if_unmodified_since='some date here',
663 with patch.object(PC, 'object_get', return_value=FR()) as get:
664 r = self.client.get_object_hashmap(obj)
665 self.assertEqual(r, object_hashmap)
666 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
667 r = self.client.get_object_hashmap(obj, **kwargs)
668 exp_args['if_etag_match'] = kwargs.pop('if_match')
669 exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
670 exp_args.update(kwargs)
671 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
673 @patch('%s.account_post' % pithos_pkg, return_value=FR())
674 def test_set_account_group(self, post):
675 (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
676 self.client.set_account_group(group, usernames)
679 call(update=True, groups={group: usernames}))
681 @patch('%s.account_post' % pithos_pkg, return_value=FR())
682 def test_del_account_group(self, post):
684 self.client.del_account_group(group)
687 call(update=True, groups={group: []}))
689 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
690 def test_get_account_quota(self, GAI):
691 key = 'x-account-policy-quota'
692 r = self.client.get_account_quota()
693 self.assertEqual(r[key], account_info[key])
695 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
696 def test_get_account_versioning(self, GAI):
697 key = 'x-account-policy-versioning'
698 r = self.client.get_account_versioning()
699 self.assertEqual(r[key], account_info[key])
701 def test_get_account_meta(self):
702 key = 'x-account-meta-'
703 with patch.object(PC, 'get_account_info', return_value=account_info):
704 r = self.client.get_account_meta()
705 keys = [k for k in r if k.startswith(key)]
706 self.assertFalse(keys)
707 acc_info = dict(account_info)
708 acc_info['%sk1' % key] = 'v1'
709 acc_info['%sk2' % key] = 'v2'
710 acc_info['%sk3' % key] = 'v3'
711 with patch.object(PC, 'get_account_info', return_value=acc_info):
712 r = self.client.get_account_meta()
713 for k in [k for k in acc_info if k.startswith(key)]:
714 self.assertEqual(r[k], acc_info[k])
716 def test_get_account_group(self):
717 key = 'x-account-group-'
718 with patch.object(PC, 'get_account_info', return_value=account_info):
719 r = self.client.get_account_group()
720 keys = [k for k in r if k.startswith(key)]
721 self.assertFalse(keys)
722 acc_info = dict(account_info)
723 acc_info['%sk1' % key] = 'g1'
724 acc_info['%sk2' % key] = 'g2'
725 acc_info['%sk3' % key] = 'g3'
726 with patch.object(PC, 'get_account_info', return_value=acc_info):
727 r = self.client.get_account_group()
728 for k in [k for k in acc_info if k.startswith(key)]:
729 self.assertEqual(r[k], acc_info[k])
731 @patch('%s.account_post' % pithos_pkg, return_value=FR())
732 def test_set_account_meta(self, post):
733 metas = dict(k1='v1', k2='v2', k3='v3')
734 self.client.set_account_meta(metas)
737 call(update=True, metadata=metas))
739 @patch('%s.account_post' % pithos_pkg, return_value=FR())
740 def test_set_account_quota(self, post):
742 self.client.set_account_quota(qu)
743 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
745 @patch('%s.account_post' % pithos_pkg, return_value=FR())
746 def test_set_account_versioning(self, post):
747 vrs = 'n3wV3r51on1ngTyp3'
748 self.client.set_account_versioning(vrs)
751 call(update=True, versioning=vrs))
753 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
754 def test_del_container(self, delete):
756 dict(delimiter=None, until=None),
757 dict(delimiter='X', until='50m3d473')):
758 self.client.del_container(**kwarg)
759 expected = dict(kwarg)
760 expected['success'] = (204, 404, 409)
761 self.assertEqual(delete.mock_calls[-1], call(**expected))
762 for status_code in (404, 409):
763 FR.status_code = status_code
764 self.assertRaises(ClientError, self.client.del_container)
766 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
767 def test_get_container_versioning(self, GCI):
768 key = 'x-container-policy-versioning'
770 bu_cnt = self.client.container
771 for container in (None, cont):
772 r = self.client.get_container_versioning(container=container)
773 self.assertEqual(r[key], container_info[key])
774 self.assertEqual(GCI.mock_calls[-1], call())
775 self.assertEqual(bu_cnt, self.client.container)
777 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
778 def test_get_container_quota(self, GCI):
779 key = 'x-container-policy-quota'
781 bu_cnt = self.client.container
782 for container in (None, cont):
783 r = self.client.get_container_quota(container=container)
784 self.assertEqual(r[key], container_info[key])
785 self.assertEqual(GCI.mock_calls[-1], call())
786 self.assertEqual(bu_cnt, self.client.container)
788 def test_get_container_meta(self):
789 somedate = '50m3d473'
790 key = 'x-container-meta'
791 metaval = '50m3m374v41'
792 container_plus = dict(container_info)
793 container_plus[key] = metaval
794 for ret in ((container_info, {}), (container_plus, {key: metaval})):
797 'get_container_info',
798 return_value=ret[0]) as gci:
799 for until in (None, somedate):
800 r = self.client.get_container_meta(until=until)
801 self.assertEqual(r, ret[1])
802 self.assertEqual(gci.mock_calls[-1], call(until=until))
804 def test_get_container_object_meta(self):
805 somedate = '50m3d473'
806 key = 'x-container-object-meta'
807 metaval = '50m3m374v41'
808 container_plus = dict(container_info)
809 container_plus[key] = metaval
811 (container_info, {key: ''}),
812 (container_plus, {key: metaval})):
815 'get_container_info',
816 return_value=ret[0]) as gci:
817 for until in (None, somedate):
818 r = self.client.get_container_object_meta(until=until)
819 self.assertEqual(r, ret[1])
820 self.assertEqual(gci.mock_calls[-1], call(until=until))
822 @patch('%s.container_post' % pithos_pkg, return_value=FR())
823 def test_set_container_meta(self, post):
824 metas = dict(k1='v1', k2='v2', k3='v3')
825 self.client.set_container_meta(metas)
828 call(update=True, metadata=metas))
830 @patch('%s.container_post' % pithos_pkg, return_value=FR())
831 def test_del_container_meta(self, ap):
832 self.client.del_container_meta('somekey')
833 expected = [call(update=True, metadata={'somekey': ''})]
834 self.assertEqual(ap.mock_calls, expected)
836 @patch('%s.container_post' % pithos_pkg, return_value=FR())
837 def test_set_container_quota(self, post):
839 self.client.set_container_quota(qu)
840 self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
842 @patch('%s.container_post' % pithos_pkg, return_value=FR())
843 def test_set_container_versioning(self, post):
844 vrs = 'n3wV3r51on1ngTyp3'
845 self.client.set_container_versioning(vrs)
848 call(update=True, versioning=vrs))
850 @patch('%s.object_delete' % pithos_pkg, return_value=FR())
851 def test_del_object(self, delete):
853 dict(delimiter=None, until=None),
854 dict(delimiter='X', until='50m3d473')):
855 self.client.del_object(obj, **kwarg)
856 self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
858 @patch('%s.object_post' % pithos_pkg, return_value=FR())
859 def test_set_object_meta(self, post):
860 metas = dict(k1='v1', k2='v2', k3='v3')
863 self.client.set_object_meta,
865 self.client.set_object_meta(obj, metas)
868 call(obj, update=True, metadata=metas))
870 @patch('%s.object_post' % pithos_pkg, return_value=FR())
871 def test_publish_object(self, post):
872 oinfo = dict(object_info)
874 oinfo['x-object-public'] = val
875 with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
876 r = self.client.publish_object(obj)
879 call(obj, public=True, update=True))
880 self.assertEqual(gof.mock_calls[-1], call(obj))
881 self.assertEqual(r, '%s%s' % (self.url[:-6], val))
883 @patch('%s.object_post' % pithos_pkg, return_value=FR())
884 def test_unpublish_object(self, post):
885 self.client.unpublish_object(obj)
888 call(obj, public=False, update=True))
890 def test_get_object_sharing(self):
891 info = dict(object_info)
892 expected = dict(read='u1,g1,u2', write='u1')
893 info['x-object-sharing'] = '; '.join(
894 ['%s=%s' % (k, v) for k, v in expected.items()])
895 with patch.object(PC, 'get_object_info', return_value=info) as GOF:
896 r = self.client.get_object_sharing(obj)
897 self.assertEqual(GOF.mock_calls[-1], call(obj))
898 self.assert_dicts_are_equal(r, expected)
899 info['x-object-sharing'] = '//'.join(
900 ['%s=%s' % (k, v) for k, v in expected.items()])
903 self.client.get_object_sharing,
905 info['x-object-sharing'] = '; '.join(
906 ['%s:%s' % (k, v) for k, v in expected.items()])
909 self.client.get_object_sharing,
911 info['x-object-sharing'] = 'read=%s' % expected['read']
912 r = self.client.get_object_sharing(obj)
913 expected.pop('write')
914 self.assert_dicts_are_equal(r, expected)
916 @patch('%s.object_post' % pithos_pkg, return_value=FR())
917 def test_set_object_sharing(self, POST):
918 read_perms = ['u1', 'g1', 'u2', 'g2']
919 write_perms = ['u1', 'g1']
921 dict(read_permition=read_perms, write_permition=write_perms),
922 dict(read_permition=read_perms),
923 dict(write_permition=write_perms),
925 self.client.set_object_sharing(obj, **kwargs)
926 kwargs['read'] = kwargs.pop('read_permition', '')
927 kwargs['write'] = kwargs.pop('write_permition', '')
930 call(obj, update=True, permissions=kwargs))
932 @patch('%s.set_object_sharing' % pithos_pkg)
933 def test_del_object_sharing(self, SOS):
934 self.client.del_object_sharing(obj)
935 self.assertEqual(SOS.mock_calls[-1], call(obj))
937 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
938 @patch('%s.object_post' % pithos_pkg, return_value=FR())
939 def test_append_object(self, post, GCI):
941 tmpFile = self._create_temp_file(num_of_blocks)
943 file_size = tmpFile.tell()
944 for turn in range(2):
948 from progress.bar import ShadyBar
949 apn_bar = ShadyBar('Mock append')
956 for i in apn_bar.iter(range(n)):
963 self.client.append_object(
965 upload_cb=append_gen if turn else None)
966 self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
967 (args, kwargs) = post.mock_calls[-1][1:3]
968 self.assertEqual(args, (obj,))
969 self.assertEqual(kwargs['content_length'], len(kwargs['data']))
970 fsize = num_of_blocks * int(kwargs['content_length'])
971 self.assertEqual(fsize, file_size)
972 self.assertEqual(kwargs['content_range'], 'bytes */*')
973 exp = 'application/octet-stream'
974 self.assertEqual(kwargs['content_type'], exp)
975 self.assertEqual(kwargs['update'], True)
977 @patch('%s.object_post' % pithos_pkg, return_value=FR())
978 def test_truncate_object(self, post):
980 self.client.truncate_object(obj, upto_bytes)
981 self.assertEqual(post.mock_calls[-1], call(
984 object_bytes=upto_bytes,
985 content_range='bytes 0-%s/*' % upto_bytes,
986 content_type='application/octet-stream',
987 source_object='/%s/%s' % (self.client.container, obj)))
989 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
990 @patch('%s.object_post' % pithos_pkg, return_value=FR())
991 def test_overwrite_object(self, post, GCI):
993 tmpFile = self._create_temp_file(num_of_blocks)
995 file_size = tmpFile.tell()
996 info = dict(object_info)
997 info['content-length'] = file_size
998 block_size = container_info['x-container-block-size']
999 with patch.object(PC, 'get_object_info', return_value=info) as GOI:
1002 (file_size + 1, file_size + 2)):
1006 self.client.overwrite_object,
1007 obj, start, end, tmpFile)
1008 for start, end in ((0, 144), (144, 233), (233, file_size)):
1011 exp_size = end - start + 1
1012 if not start or exp_size > block_size:
1014 from progress.bar import ShadyBar
1015 owr_bar = ShadyBar('Mock append')
1022 for i in owr_bar.iter(range(n)):
1026 if exp_size > block_size:
1027 exp_size = exp_size % block_size or block_size
1029 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1030 self.assertEqual(GOI.mock_calls[-1], call(obj))
1031 self.assertEqual(GCI.mock_calls[-1], call())
1032 (args, kwargs) = post.mock_calls[-1][1:3]
1033 self.assertEqual(args, (obj,))
1034 self.assertEqual(len(kwargs['data']), exp_size)
1035 self.assertEqual(kwargs['content_length'], exp_size)
1036 self.assertEqual(kwargs['update'], True)
1037 exp = 'application/octet-stream'
1038 self.assertEqual(kwargs['content_type'], exp)
1040 @patch('%s.set_param' % pithos_pkg)
1041 @patch('%s.get' % pithos_pkg, return_value=FR())
1042 def test_get_sharing_accounts(self, get, SP):
1046 dict(limit='50m3-11m17'),
1048 dict(limit='50m3-11m17', marker='X')):
1049 r = self.client.get_sharing_accounts(**kws)
1050 self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1051 limit, marker = kws.get('limit', None), kws.get('marker', None)
1052 self.assertEqual(SP.mock_calls[-2], call(
1054 iff=limit is not None))
1055 self.assertEqual(SP.mock_calls[-1], call(
1057 iff=marker is not None))
1058 self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1059 for i in range(len(r)):
1060 self.assert_dicts_are_equal(r[i], sharers[i])
1062 @patch('%s.object_get' % pithos_pkg, return_value=FR())
1063 def test_get_object_versionlist(self, get):
1064 info = dict(object_info)
1065 info['versions'] = ['v1', 'v2']
1067 r = self.client.get_object_versionlist(obj)
1070 call(obj, format='json', version='list'))
1071 self.assertEqual(r, info['versions'])
1073 if __name__ == '__main__':
1074 from sys import argv
1075 from kamaki.clients.test import runTestCase
1076 runTestCase(Pithos, 'Pithos+ Client', argv[1:])