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
38 from itertools import product
41 from collections import OrderedDict
43 from kamaki.clients.commisioning.utils.ordereddict import OrderedDict
45 from kamaki.clients import ClientError
46 from kamaki.clients.pithos import PithosClient, PithosRestClient
49 rest_pkg = 'kamaki.clients.pithos.rest_api.PithosRestClient'
50 pithos_pkg = 'kamaki.clients.pithos.PithosClient'
52 user_id = 'ac0un7-1d-5tr1ng'
56 'content-language': 'en-us',
57 'content-type': 'text/html; charset=utf-8',
58 'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
59 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
60 'server': 'gunicorn/0.14.5',
61 'vary': 'Accept-Language',
62 'x-account-bytes-used': '751615526',
63 'x-account-container-count': 7,
64 'x-account-policy-quota': 53687091200,
65 'x-account-policy-versioning': 'auto'}
67 'content-language': 'en-us',
68 'content-type': 'text/html; charset=utf-8',
69 'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
70 'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
71 'server': 'gunicorn/0.14.5',
72 'vary': 'Accept-Language',
73 'x-container-block-hash': 'sha256',
74 'x-container-block-size': 4194304,
75 'x-container-bytes-used': 309528938,
76 'x-container-object-count': 14,
77 'x-container-object-meta': '',
78 'x-container-policy-quota': 53687091200,
79 'x-container-policy-versioning': 'auto'}
81 'content-language': 'en-us',
82 'content-length': 254965,
83 'content-type': 'application/octet-stream',
84 'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
86 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
87 'server': 'gunicorn/0.14.5',
88 'vary': 'Accept-Language',
89 'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
90 'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
91 'x-object-version': '525996',
92 'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
93 'x-object-meta-k1': 'v1',
94 'x-object-meta-k2': 'v2'}
98 last_modified="2013-02-27T11:56:09.893033+00:00",
101 x_container_policy=dict(quota="21474836480", versioning="auto")),
104 last_modified="2012-10-23T12:25:17.229187+00:00",
107 x_container_policy=dict(quota="21474836480", versioning="auto"))]
110 name="The_Secret_Garden.zip",
111 x_object_public="/public/wdp9p",
113 x_object_version_timestamp="1360237915.7027509",
114 x_object_uuid="s0m3uu1df0r0bj0n3",
115 last_modified="2013-02-07T11:51:55.702751+00:00",
116 content_type="application/octet-stream",
117 x_object_hash="0afdf29f71cd53126225c3f54ca",
118 x_object_version=17737,
119 x_object_modified_by=user_id),
121 name="The_Revealed_Garden.zip",
122 x_object_public="/public/wpd7p",
124 x_object_version_timestamp="13602915.7027509",
125 x_object_uuid="s0m3uu1df0r0bj70w",
126 last_modified="2013-02-07T11:51:55.702751+00:00",
127 content_type="application/octet-stream",
128 x_object_hash="0afdf29f71cd53126225c3f54ca",
129 x_object_version=17737,
130 x_object_modified_by=user_id)]
131 object_hashmap = dict(
132 block_hash="sha256", block_size=4194304, bytes=33554432,
134 "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
135 "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
136 "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
137 "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
138 "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
139 "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
140 "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
141 "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
143 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b1a-82d5"),
144 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b2a-f2d5"),
145 dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="2b1a-82d6")]
149 """FR stands for Fake Response"""
160 class PithosRest(TestCase):
163 self.url = 'https://www.example.com/pithos'
164 self.token = 'p17h0570k3n'
165 self.client = PithosRestClient(self.url, self.token)
166 self.client.account = user_id
167 self.client.container = 'c0nt@1n3r_i'
174 @patch('%s.set_param' % rest_pkg)
175 @patch('%s.set_header' % rest_pkg)
176 @patch('%s.head' % rest_pkg, return_value=FR())
177 def test_account_head(self, head, SH, SP):
178 for params in product(
180 (None, '50m3-07h3r-d473'),
181 (None, 'y37-4n7h3r-d473'),
182 ((), ('someval',), ('v1', 'v2',)),
183 (dict(), dict(success=200), dict(k='v', v='k'))):
184 args, kwargs = params[-2], params[-1]
186 self.client.account_head(*(params + args), **kwargs)
188 self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
189 IMS, IUS = params[1], params[2]
190 self.assertEqual(SH.mock_calls[-2:], [
191 call('If-Modified-Since', IMS),
192 call('If-Unmodified-Since', IUS)])
193 self.assertEqual(head.mock_calls[-1], call(
194 '/%s' % self.client.account,
196 success=kwargs.pop('success', 204),
199 @patch('%s.set_param' % rest_pkg)
200 @patch('%s.set_header' % rest_pkg)
201 @patch('%s.get' % rest_pkg, return_value=FR())
202 def test_account_get(self, get, SH, SP):
203 keys = ('limit', 'marker', 'format', 'shared', 'until')
204 for params in product(
210 (None, '50m3-07h3r-d473'),
211 (None, 'y37-4n7h3r-d473'),
212 ((), ('someval',), ('v1', 'v2',)),
213 (dict(), dict(success=200), dict(k='v', v='k'))):
214 args, kwargs = params[-2], params[-1]
216 self.client.account_get(*(params + args), **kwargs)
217 self.assertEqual(SP.mock_calls[-5:],
218 [call(keys[i], iff=X) if (
220 keys[i], X, iff=X) for i, X in enumerate(params[:5])])
221 IMS, IUS = params[5], params[6]
222 self.assertEqual(SH.mock_calls[-2:], [
223 call('If-Modified-Since', IMS),
224 call('If-Unmodified-Since', IUS)])
225 self.assertEqual(get.mock_calls[-1], call(
226 '/%s' % self.client.account,
228 success=kwargs.pop('success', (200, 204)),
231 @patch('%s.set_param' % rest_pkg)
232 @patch('%s.set_header' % rest_pkg)
233 @patch('%s.post' % rest_pkg, return_value=FR())
234 def test_account_post(self, post, SH, SP):
235 #keys = ('update', 'groups', 'metadata', 'quota', 'versioning')
238 ({}, dict(g=['u1', 'u2']), dict(g1=[], g2=['u1', 'u2'])),
239 (None, dict(k1='v1', k2='v2', k3='v2'), dict(k='v')),
241 (None, 'v3r510n1ng'),
242 ((), ('someval',), ('v1', 'v2',)),
243 (dict(), dict(success=200), dict(k='v', v='k'))):
244 args, kwargs = pm[-2:]
246 self.client.account_post(*(pm + args), **kwargs)
248 self.assertEqual(SP.mock_calls[-1], call('update', iff=upd))
252 call('X-Account-Group-%s' % k, v) for k, v in pm[1].items()]
255 call('X-Account-Meta-%s' % k, v) for k, v in pm[2].items()]
257 call('X-Account-Policy-Quota', pm[3]),
258 call('X-Account-Policy-Versioning', pm[4])]
259 self.assertEqual(SH.mock_calls[- len(expected):], expected)
260 self.assertEqual(post.mock_calls[-1], call(
261 '/%s' % self.client.account,
263 success=kwargs.pop('success', 202),
266 @patch('%s.set_param' % rest_pkg)
267 @patch('%s.set_header' % rest_pkg)
268 @patch('%s.head' % rest_pkg, return_value=FR())
269 def test_container_head(self, head, SH, SP):
272 (None, '47h3r-d473'),
273 (None, 'y37-4n47h3r'),
275 (dict(), dict(success=200), dict(k='v', v='k'))):
276 args, kwargs = pm[-2:]
278 self.client.container_head(*(pm + args), **kwargs)
279 unt, ims, ius = pm[0:3]
280 self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
281 self.assertEqual(SH.mock_calls[-2:], [
282 call('If-Modified-Since', ims),
283 call('If-Unmodified-Since', ius)])
284 self.assertEqual(head.mock_calls[-1], call(
285 '/%s/%s' % (self.client.account, self.client.container),
287 success=kwargs.pop('success', 204),
290 @patch('%s.set_param' % rest_pkg)
291 @patch('%s.set_header' % rest_pkg)
292 @patch('%s.get' % rest_pkg, return_value=FR())
293 def test_container_get(self, get, SH, SP):
297 (None, 'some/prefix'),
299 (None, '/some/path'),
300 ('json', 'some-format'),
301 ([], ['k1', 'k2', 'k3']),
303 (None, 'unt1l-d473'),
304 (None, 'y37-4n47h3r'),
305 (None, '4n47h3r-d473'),
307 (dict(), dict(success=400), dict(k='v', v='k'))):
308 args, kwargs = pm[-2:]
310 self.client.container_get(*(pm + args), **kwargs)
311 lmt, mrk, prfx, dlm, path, frmt, meta, shr, unt = pm[:-2]
312 exp = [call('limit', lmt, iff=lmt), call('marker', mrk, iff=mrk)]
313 exp += [call('path', path)] if path else [
314 call('prefix', prfx, iff=prfx),
315 call('delimiter', dlm, iff=dlm)]
316 exp += [call('format', frmt, iff=frmt), call('shared', iff=shr)]
318 exp += [call('meta', ','.join(meta))]
319 exp += [call('until', unt, iff=unt)]
320 self.assertEqual(SP.mock_calls[- len(exp):], exp)
322 self.assertEqual(SH.mock_calls[-2:], [
323 call('If-Modified-Since', ims),
324 call('If-Unmodified-Since', ius)])
325 self.assertEqual(get.mock_calls[-1], call(
326 '/%s/%s' % (self.client.account, self.client.container),
328 success=kwargs.pop('success', 200),
331 @patch('%s.set_header' % rest_pkg)
332 @patch('%s.put' % rest_pkg, return_value=FR())
333 def test_container_put(self, put, SH):
336 (None, 'v3r51on1ng'),
337 (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
339 (dict(), dict(success=400), dict(k='v', v='k'))):
340 args, kwargs = pm[-2:]
342 self.client.container_put(*(pm + args), **kwargs)
343 quota, versioning, metas = pm[-3:]
345 call('X-Container-Policy-Quota', quota),
346 call('X-Container-Policy-Versioning', versioning)] + [
347 call('X-Container-Meta-%s' % k, v) for k, v in metas.items()]
348 self.assertEqual(SH.mock_calls[- len(exp):], exp)
349 self.assertEqual(put.mock_calls[-1], call(
350 '/%s/%s' % (self.client.account, self.client.container),
352 success=kwargs.pop('success', (201, 202)),
355 @patch('%s.set_param' % rest_pkg)
356 @patch('%s.set_header' % rest_pkg)
357 @patch('%s.post' % rest_pkg, return_value=FR())
358 def test_container_post(self, post, SH, SP):
361 ('json', 'some-format'),
363 (None, 'v3r51on1ng'),
364 (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
365 (None, 'content-type'),
367 (None, 'transfer-encoding'),
369 (dict(), dict(success=400), dict(k='v', v='k'))):
370 args, kwargs = pm[-2:]
372 self.client.container_post(*(pm + args), **kwargs)
374 self.assertEqual(SP.mock_calls[-2:], [
375 call('update', iff=upd),
376 call('format', frmt, iff=frmt)])
377 qta, vrs, metas, ctype, clen, trenc = pm[2:]
378 prfx = 'X-Container-Meta-'
380 call('X-Container-Policy-Quota', qta),
381 call('X-Container-Policy-Versioning', vrs)] + [
382 call('%s%s' % (prfx, k), v) for k, v in metas.items()] + [
383 call('Content-Type', ctype),
384 call('Content-Length', clen),
385 call('Transfer-Encoding', trenc)]
386 self.assertEqual(SH.mock_calls[- len(exp):], exp)
388 self.assertEqual(post.mock_calls[-1], call(
389 '/%s/%s' % (self.client.account, self.client.container),
391 success=kwargs.pop('success', 202),
395 class Pithos(TestCase):
399 def _create_temp_file(self, num_of_blocks):
400 self.files.append(NamedTemporaryFile())
401 tmpFile = self.files[-1]
402 file_size = num_of_blocks * 4 * 1024 * 1024
403 print('\n\tCreate tmp file')
404 tmpFile.write(urandom(file_size))
410 def assert_dicts_are_equal(self, d1, d2):
411 for k, v in d1.items():
412 self.assertTrue(k in d2)
413 if isinstance(v, dict):
414 self.assert_dicts_are_equal(v, d2[k])
416 self.assertEqual(unicode(v), unicode(d2[k]))
419 self.url = 'https://www.example.com/pithos'
420 self.token = 'p17h0570k3n'
421 self.client = PithosClient(self.url, self.token)
422 self.client.account = user_id
423 self.client.container = 'c0nt@1n3r_i'
433 # Pithos+ methods that extend storage API
435 @patch('%s.account_head' % pithos_pkg, return_value=FR())
436 def test_get_account_info(self, AH):
437 FR.headers = account_info
438 for until in (None, 'un71L-d473'):
439 r = self.client.get_account_info(until=until)
440 self.assert_dicts_are_equal(r, account_info)
441 self.assertEqual(AH.mock_calls[-1], call(until=until))
443 self.assertRaises(ClientError, self.client.get_account_info)
445 @patch('%s.account_post' % pithos_pkg, return_value=FR())
446 def test_del_account_meta(self, AP):
447 keys = ['k1', 'k2', 'k3']
449 self.client.del_account_meta(key)
452 call(update=True, metadata={key: ''}))
454 @patch('%s.container_head' % pithos_pkg, return_value=FR())
455 def test_get_container_info(self, CH):
456 FR.headers = container_info
457 r = self.client.get_container_info()
458 self.assert_dicts_are_equal(r, container_info)
460 r = self.client.get_container_info(until=u)
461 self.assertEqual(CH.mock_calls, [call(until=None), call(until=u)])
463 @patch('%s.account_get' % pithos_pkg, return_value=FR())
464 def test_list_containers(self, get):
465 FR.json = container_list
466 r = self.client.list_containers()
467 get.assert_called_once_with()
468 for i in range(len(r)):
469 self.assert_dicts_are_equal(r[i], container_list[i])
471 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
472 @patch('%s.container_post' % pithos_pkg, return_value=FR())
473 @patch('%s.object_put' % pithos_pkg, return_value=FR())
474 def test_upload_object(self, OP, CP, GCI):
476 tmpFile = self._create_temp_file(num_of_blocks)
479 self.client.upload_object(obj, tmpFile)
480 self.assertEqual(GCI.mock_calls[-1], call())
481 [call1, call2] = OP.mock_calls
483 (args1, kwargs1) = call1[1:3]
484 (args2, kwargs2) = call2[1:3]
485 self.assertEqual(args1, (obj,))
491 hashes=['s0m3h@5h'] * num_of_blocks,
492 bytes=num_of_blocks * 4 * 1024 * 1024),
494 content_encoding=None,
495 content_type='application/octet-stream',
496 content_disposition=None,
499 for k, v in expected1.items():
501 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
502 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
504 self.assertEqual(v, kwargs1[k])
506 (args2, kwargs2) = call2[1:3]
507 self.assertEqual(args2, (obj,))
510 hashes=['s0m3h@5h'] * num_of_blocks,
511 bytes=num_of_blocks * 4 * 1024 * 1024),
512 content_type='application/octet-stream',
516 for k, v in expected2.items():
518 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
519 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
521 self.assertEqual(v, kwargs2[k])
527 from progress.bar import ShadyBar
528 blck_bar = ShadyBar('Mock blck calc.')
529 upld_bar = ShadyBar('Mock uplds')
534 if blck_bar and upld_bar:
537 for i in blck_bar.iter(range(n)):
542 for i in upld_bar.iter(range(n)):
547 self.client.upload_object(
549 hash_cb=blck_gen, upload_cb=upld_gen)
551 for i, c in enumerate(OP.mock_calls[-mock_offset:]):
552 self.assertEqual(OP.mock_calls[i], c)
557 sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
558 self.client.upload_object(obj, tmpFile,
559 content_type=ctype, sharing=sharing)
560 self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
561 self.assert_dicts_are_equal(
562 OP.mock_calls[-2][2]['permissions'],
570 content_disposition=ctype + 'd15p051710n',
572 content_encoding='802.11')
573 self.client.upload_object(obj, tmpFile, **kwargs)
574 for arg, val in kwargs.items():
575 self.assertEqual(OP.mock_calls[-2][2][arg], val)
577 def test_get_object_info(self):
578 FR.headers = object_info
581 PithosClient, 'object_head',
582 return_value=FR()) as head:
583 r = self.client.get_object_info(obj)
584 self.assertEqual(r, object_info)
585 r = self.client.get_object_info(obj, version=version)
586 self.assertEqual(head.mock_calls, [
587 call(obj, version=None),
588 call(obj, version=version)])
590 PithosClient, 'object_head',
591 side_effect=ClientError('Obj not found', 404)):
594 self.client.get_object_info,
595 obj, version=version)
597 @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
598 def test_get_object_meta(self, GOI):
599 for version in (None, 'v3r510n'):
600 r = self.client.get_object_meta(obj, version)
601 for k in [k for k in object_info if k.startswith('x-object-meta')]:
602 self.assertEqual(r.pop(k), object_info[k])
603 self.assertFalse(len(r))
604 self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
606 @patch('%s.object_post' % pithos_pkg, return_value=FR())
607 def test_del_object_meta(self, post):
608 metakey = '50m3m3t4k3y'
609 self.client.del_object_meta(obj, metakey)
610 post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
612 @patch('%s.object_put' % pithos_pkg, return_value=FR())
613 def test_copy_object(self, put):
614 src_cont = 'src-c0nt41n3r'
616 dst_cont = 'dst-c0nt41n3r'
623 copy_from='/%s/%s' % (src_cont, src_obj),
628 self.client.copy_object(src_cont, src_obj, dst_cont)
629 self.assertEqual(put.mock_calls[-1], expected)
630 self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
631 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
633 source_version='src-v3r510n',
634 source_account='src-4cc0un7',
636 content_type='c0n73n7Typ3',
638 self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
639 for k, v in kwargs.items():
640 self.assertEqual(v, put.mock_calls[-1][2][k])
642 @patch('%s.object_put' % pithos_pkg, return_value=FR())
643 def test_move_object(self, put):
644 src_cont = 'src-c0nt41n3r'
646 dst_cont = 'dst-c0nt41n3r'
653 move_from='/%s/%s' % (src_cont, src_obj),
658 self.client.move_object(src_cont, src_obj, dst_cont)
659 self.assertEqual(put.mock_calls[-1], expected)
660 self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
661 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
663 source_version='src-v3r510n',
664 source_account='src-4cc0un7',
666 content_type='c0n73n7Typ3',
668 self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
669 for k, v in kwargs.items():
670 self.assertEqual(v, put.mock_calls[-1][2][k])
672 # Pithos+ only methods
674 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
675 def test_purge_container(self, CD):
676 self.client.purge_container()
677 self.assertTrue('until' in CD.mock_calls[-1][2])
678 cont = self.client.container
679 self.client.purge_container('another-container')
680 self.assertEqual(self.client.container, cont)
682 @patch('%s.object_put' % pithos_pkg, return_value=FR())
683 def test_upload_object_unchunked(self, put):
685 tmpFile = self._create_temp_file(num_of_blocks)
688 data=num_of_blocks * 4 * 1024 * 1024,
690 content_encoding='some content_encoding',
691 content_type='some content-type',
692 content_disposition='some content_disposition',
694 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
695 self.client.upload_object_unchunked(obj, tmpFile)
696 self.assertEqual(put.mock_calls[-1][1], (obj,))
698 sorted(put.mock_calls[-1][2].keys()),
699 sorted(expected.keys()))
700 kwargs = dict(expected)
701 kwargs.pop('success')
702 kwargs['size'] = kwargs.pop('data')
703 kwargs['sharing'] = kwargs.pop('permissions')
705 self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
706 pmc = put.mock_calls[-1][2]
707 for k, v in expected.items():
709 self.assertEqual(len(pmc[k]), v)
711 self.assertEqual(pmc[k], v)
714 self.client.upload_object_unchunked,
715 obj, tmpFile, withHashFile=True)
717 @patch('%s.object_put' % pithos_pkg, return_value=FR())
718 def test_create_object_by_manifestation(self, put):
719 manifest = '%s/%s' % (self.client.container, obj)
722 content_encoding='some content_encoding',
723 content_type='some content-type',
724 content_disposition='some content_disposition',
726 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
727 self.client.create_object_by_manifestation(obj)
728 expected = dict(content_length=0, manifest=manifest)
730 expected['permissions' if k == 'sharing' else k] = None
731 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
732 self.client.create_object_by_manifestation(obj, **kwargs)
733 expected.update(kwargs)
734 expected['permissions'] = expected.pop('sharing')
735 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
737 @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
738 @patch('%s.object_get' % pithos_pkg, return_value=FR())
739 def test_download_object(self, GET, GOH):
741 tmpFile = self._create_temp_file(num_of_blocks)
742 FR.content = tmpFile.read(4 * 1024 * 1024)
743 tmpFile = self._create_temp_file(num_of_blocks)
744 num_of_blocks = len(object_hashmap['hashes'])
749 if_match='if and only if',
750 if_none_match='if and only not',
751 if_modified_since='what if not?',
752 if_unmodified_since='this happens if not!',
753 async_headers=dict(Range='bytes=0-88888888'))
755 self.client.download_object(obj, tmpFile)
756 self.assertEqual(len(GET.mock_calls), num_of_blocks)
757 self.assertEqual(GET.mock_calls[-1][1], (obj,))
758 for k, v in kwargs.items():
759 if k == 'async_headers':
760 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
761 elif k in ('resume', 'range_str'):
764 self.assertEqual(GET.mock_calls[-1][2][k], None)
766 # Check ranges are consecutive
769 for c in GET.mock_calls:
770 rng_str = c[2]['async_headers']['Range']
771 (start, rng_str) = rng_str.split('=')
772 (start, end) = rng_str.split('-')
776 for i, start in enumerate(sorted(starts)):
778 int(ends[i - 1]) == int(start) - 1
782 from progress.bar import ShadyBar
783 dl_bar = ShadyBar('Mock dl')
790 for i in dl_bar.iter(range(n)):
795 self.client.download_object(obj, tmpFile, download_cb=blck_gen)
796 self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
799 kwargs.pop('async_headers')
801 self.client.download_object(obj, tmpFile, **kwargs)
802 for k, v in kwargs.items():
805 GET.mock_calls[-1][2]['data_range'],
808 self.assertEqual(GET.mock_calls[-1][2][k], v)
810 # ALl options on no tty
817 self.client.download_object(obj, tmpFile, **kwargs)
818 for k, v in kwargs.items():
820 self.assertTrue('data_range' in GET.mock_calls[-1][2])
822 self.assertEqual(GET.mock_calls[-1][2][k], v)
824 def test_get_object_hashmap(self):
825 FR.json = object_hashmap
826 for empty in (304, 412):
828 PithosClient, 'object_get',
829 side_effect=ClientError('Empty', status=empty)):
830 r = self.client.get_object_hashmap(obj)
831 self.assertEqual(r, {})
837 if_etag_not_match=None,
838 if_modified_since=None,
839 if_unmodified_since=None)
841 version='s0m3v3r51on',
843 if_none_match='if non match',
844 if_modified_since='some date here',
845 if_unmodified_since='some date here',
848 PithosClient, 'object_get',
849 return_value=FR()) as get:
850 r = self.client.get_object_hashmap(obj)
851 self.assertEqual(r, object_hashmap)
852 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
853 r = self.client.get_object_hashmap(obj, **kwargs)
854 exp_args['if_etag_match'] = kwargs.pop('if_match')
855 exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
856 exp_args.update(kwargs)
857 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
859 @patch('%s.account_post' % pithos_pkg, return_value=FR())
860 def test_set_account_group(self, post):
861 (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
862 self.client.set_account_group(group, usernames)
863 post.assert_called_once_with(update=True, groups={group: usernames})
865 @patch('%s.account_post' % pithos_pkg, return_value=FR())
866 def test_del_account_group(self, post):
868 self.client.del_account_group(group)
869 post.assert_called_once_with(update=True, groups={group: []})
871 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
872 def test_get_account_quota(self, GAI):
873 key = 'x-account-policy-quota'
874 r = self.client.get_account_quota()
875 GAI.assert_called_once_with()
876 self.assertEqual(r[key], account_info[key])
878 @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
879 def test_get_account_versioning(self, GAI):
880 key = 'x-account-policy-versioning'
881 r = self.client.get_account_versioning()
882 GAI.assert_called_once_with()
883 self.assertEqual(r[key], account_info[key])
885 def test_get_account_meta(self):
886 key = 'x-account-meta-'
888 PithosClient, 'get_account_info',
889 return_value=account_info):
890 r = self.client.get_account_meta()
891 keys = [k for k in r if k.startswith(key)]
892 self.assertFalse(keys)
893 acc_info = dict(account_info)
894 acc_info['%sk1' % key] = 'v1'
895 acc_info['%sk2' % key] = 'v2'
896 acc_info['%sk3' % key] = 'v3'
898 PithosClient, 'get_account_info',
899 return_value=acc_info):
900 r = self.client.get_account_meta()
901 for k in [k for k in acc_info if k.startswith(key)]:
902 self.assertEqual(r[k], acc_info[k])
904 def test_get_account_group(self):
905 key = 'x-account-group-'
907 PithosClient, 'get_account_info',
908 return_value=account_info):
909 r = self.client.get_account_group()
910 keys = [k for k in r if k.startswith(key)]
911 self.assertFalse(keys)
912 acc_info = dict(account_info)
913 acc_info['%sk1' % key] = 'g1'
914 acc_info['%sk2' % key] = 'g2'
915 acc_info['%sk3' % key] = 'g3'
917 PithosClient, 'get_account_info',
918 return_value=acc_info):
919 r = self.client.get_account_group()
920 for k in [k for k in acc_info if k.startswith(key)]:
921 self.assertEqual(r[k], acc_info[k])
923 @patch('%s.account_post' % pithos_pkg, return_value=FR())
924 def test_set_account_meta(self, post):
925 metas = dict(k1='v1', k2='v2', k3='v3')
926 self.client.set_account_meta(metas)
927 post.assert_called_once_with(update=True, metadata=metas)
929 @patch('%s.account_post' % pithos_pkg, return_value=FR())
930 def test_set_account_quota(self, post):
932 self.client.set_account_quota(qu)
933 post.assert_called_once_with(update=True, quota=qu)
935 @patch('%s.account_post' % pithos_pkg, return_value=FR())
936 def test_set_account_versioning(self, post):
937 vrs = 'n3wV3r51on1ngTyp3'
938 self.client.set_account_versioning(vrs)
939 post.assert_called_once_with(update=True, versioning=vrs)
941 @patch('%s.container_delete' % pithos_pkg, return_value=FR())
942 def test_del_container(self, delete):
944 dict(delimiter=None, until=None),
945 dict(delimiter='X', until='50m3d473')):
946 self.client.del_container(**kwarg)
947 expected = dict(kwarg)
948 expected['success'] = (204, 404, 409)
949 self.assertEqual(delete.mock_calls[-1], call(**expected))
950 for status_code in (404, 409):
951 FR.status_code = status_code
952 self.assertRaises(ClientError, self.client.del_container)
954 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
955 def test_get_container_versioning(self, GCI):
956 key = 'x-container-policy-versioning'
958 bu_cnt = self.client.container
959 for container in (None, cont):
960 r = self.client.get_container_versioning(container=container)
961 self.assertEqual(r[key], container_info[key])
962 self.assertEqual(GCI.mock_calls[-1], call())
963 self.assertEqual(bu_cnt, self.client.container)
965 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
966 def test_get_container_quota(self, GCI):
967 key = 'x-container-policy-quota'
969 bu_cnt = self.client.container
970 for container in (None, cont):
971 r = self.client.get_container_quota(container=container)
972 self.assertEqual(r[key], container_info[key])
973 self.assertEqual(GCI.mock_calls[-1], call())
974 self.assertEqual(bu_cnt, self.client.container)
976 def test_get_container_meta(self):
977 somedate = '50m3d473'
978 key = 'x-container-meta'
979 metaval = '50m3m374v41'
980 container_plus = dict(container_info)
981 container_plus[key] = metaval
982 for ret in ((container_info, {}), (container_plus, {key: metaval})):
985 'get_container_info',
986 return_value=ret[0]) as GCI:
987 for until in (None, somedate):
988 r = self.client.get_container_meta(until=until)
989 self.assertEqual(r, ret[1])
990 self.assertEqual(GCI.mock_calls[-1], call(until=until))
992 def test_get_container_object_meta(self):
993 somedate = '50m3d473'
994 key = 'x-container-object-meta'
995 metaval = '50m3m374v41'
996 container_plus = dict(container_info)
997 container_plus[key] = metaval
999 (container_info, {key: ''}),
1000 (container_plus, {key: metaval})):
1003 'get_container_info',
1004 return_value=ret[0]) as GCI:
1005 for until in (None, somedate):
1006 r = self.client.get_container_object_meta(until=until)
1007 self.assertEqual(r, ret[1])
1008 self.assertEqual(GCI.mock_calls[-1], call(until=until))
1010 @patch('%s.container_post' % pithos_pkg, return_value=FR())
1011 def test_set_container_meta(self, post):
1012 metas = dict(k1='v1', k2='v2', k3='v3')
1013 self.client.set_container_meta(metas)
1014 post.assert_called_once_with(update=True, metadata=metas)
1016 @patch('%s.container_post' % pithos_pkg, return_value=FR())
1017 def test_del_container_meta(self, AP):
1018 self.client.del_container_meta('somekey')
1019 AP.assert_called_once_with(update=True, metadata={'somekey': ''})
1021 @patch('%s.container_post' % pithos_pkg, return_value=FR())
1022 def test_set_container_quota(self, post):
1024 self.client.set_container_quota(qu)
1025 post.assert_called_once_with(update=True, quota=qu)
1027 @patch('%s.container_post' % pithos_pkg, return_value=FR())
1028 def test_set_container_versioning(self, post):
1029 vrs = 'n3wV3r51on1ngTyp3'
1030 self.client.set_container_versioning(vrs)
1031 post.assert_called_once_with(update=True, versioning=vrs)
1033 @patch('%s.object_delete' % pithos_pkg, return_value=FR())
1034 def test_del_object(self, delete):
1036 dict(delimiter=None, until=None),
1037 dict(delimiter='X', until='50m3d473')):
1038 self.client.del_object(obj, **kwarg)
1039 self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
1041 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1042 def test_set_object_meta(self, post):
1043 metas = dict(k1='v1', k2='v2', k3='v3')
1046 self.client.set_object_meta,
1047 obj, 'Non dict arg')
1048 self.client.set_object_meta(obj, metas)
1049 post.assert_called_once_with(obj, update=True, metadata=metas)
1051 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1052 def test_publish_object(self, post):
1053 oinfo = dict(object_info)
1055 oinfo['x-object-public'] = val
1057 PithosClient, 'get_object_info',
1058 return_value=oinfo) as GOF:
1059 r = self.client.publish_object(obj)
1061 post.mock_calls[-1],
1062 call(obj, public=True, update=True))
1063 self.assertEqual(GOF.mock_calls[-1], call(obj))
1064 self.assertEqual(r, '%s%s' % (self.url[:-6], val))
1066 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1067 def test_unpublish_object(self, post):
1068 self.client.unpublish_object(obj)
1069 post.assert_called_once_with(obj, public=False, update=True)
1071 def test_get_object_sharing(self):
1072 info = dict(object_info)
1073 expected = dict(read='u1,g1,u2', write='u1')
1074 info['x-object-sharing'] = '; '.join(
1075 ['%s=%s' % (k, v) for k, v in expected.items()])
1077 PithosClient, 'get_object_info',
1078 return_value=info) as GOF:
1079 r = self.client.get_object_sharing(obj)
1080 self.assertEqual(GOF.mock_calls[-1], call(obj))
1081 self.assert_dicts_are_equal(r, expected)
1082 info['x-object-sharing'] = '//'.join(
1083 ['%s=%s' % (k, v) for k, v in expected.items()])
1086 self.client.get_object_sharing,
1088 info['x-object-sharing'] = '; '.join(
1089 ['%s:%s' % (k, v) for k, v in expected.items()])
1092 self.client.get_object_sharing,
1094 info['x-object-sharing'] = 'read=%s' % expected['read']
1095 r = self.client.get_object_sharing(obj)
1096 expected.pop('write')
1097 self.assert_dicts_are_equal(r, expected)
1099 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1100 def test_set_object_sharing(self, OP):
1101 read_perms = ['u1', 'g1', 'u2', 'g2']
1102 write_perms = ['u1', 'g1']
1104 dict(read_permition=read_perms, write_permition=write_perms),
1105 dict(read_permition=read_perms),
1106 dict(write_permition=write_perms),
1108 self.client.set_object_sharing(obj, **kwargs)
1109 kwargs['read'] = kwargs.pop('read_permition', '')
1110 kwargs['write'] = kwargs.pop('write_permition', '')
1113 call(obj, update=True, permissions=kwargs))
1115 @patch('%s.set_object_sharing' % pithos_pkg)
1116 def test_del_object_sharing(self, SOS):
1117 self.client.del_object_sharing(obj)
1118 SOS.assert_called_once_with(obj)
1120 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1121 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1122 def test_append_object(self, post, GCI):
1124 tmpFile = self._create_temp_file(num_of_blocks)
1126 file_size = tmpFile.tell()
1127 for turn in range(2):
1131 from progress.bar import ShadyBar
1132 apn_bar = ShadyBar('Mock append')
1139 for i in apn_bar.iter(range(n)):
1146 self.client.append_object(
1148 upload_cb=append_gen if turn else None)
1149 self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1150 (args, kwargs) = post.mock_calls[-1][1:3]
1151 self.assertEqual(args, (obj,))
1152 self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1153 fsize = num_of_blocks * int(kwargs['content_length'])
1154 self.assertEqual(fsize, file_size)
1155 self.assertEqual(kwargs['content_range'], 'bytes */*')
1156 exp = 'application/octet-stream'
1157 self.assertEqual(kwargs['content_type'], exp)
1158 self.assertEqual(kwargs['update'], True)
1160 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1161 def test_truncate_object(self, post):
1163 self.client.truncate_object(obj, upto_bytes)
1164 post.assert_called_once_with(
1167 object_bytes=upto_bytes,
1168 content_range='bytes 0-%s/*' % upto_bytes,
1169 content_type='application/octet-stream',
1170 source_object='/%s/%s' % (self.client.container, obj))
1172 @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1173 @patch('%s.object_post' % pithos_pkg, return_value=FR())
1174 def test_overwrite_object(self, post, GCI):
1176 tmpFile = self._create_temp_file(num_of_blocks)
1178 file_size = tmpFile.tell()
1179 info = dict(object_info)
1180 info['content-length'] = file_size
1181 block_size = container_info['x-container-block-size']
1183 PithosClient, 'get_object_info',
1184 return_value=info) as GOI:
1187 (file_size + 1, file_size + 2)):
1191 self.client.overwrite_object,
1192 obj, start, end, tmpFile)
1193 for start, end in ((0, 144), (144, 233), (233, file_size)):
1196 exp_size = end - start + 1
1197 if not start or exp_size > block_size:
1199 from progress.bar import ShadyBar
1200 owr_bar = ShadyBar('Mock append')
1207 for i in owr_bar.iter(range(n)):
1211 if exp_size > block_size:
1212 exp_size = exp_size % block_size or block_size
1214 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1215 self.assertEqual(GOI.mock_calls[-1], call(obj))
1216 self.assertEqual(GCI.mock_calls[-1], call())
1217 (args, kwargs) = post.mock_calls[-1][1:3]
1218 self.assertEqual(args, (obj,))
1219 self.assertEqual(len(kwargs['data']), exp_size)
1220 self.assertEqual(kwargs['content_length'], exp_size)
1221 self.assertEqual(kwargs['update'], True)
1222 exp = 'application/octet-stream'
1223 self.assertEqual(kwargs['content_type'], exp)
1225 @patch('%s.set_param' % pithos_pkg)
1226 @patch('%s.get' % pithos_pkg, return_value=FR())
1227 def test_get_sharing_accounts(self, get, SP):
1231 dict(limit='50m3-11m17'),
1233 dict(limit='50m3-11m17', marker='X')):
1234 r = self.client.get_sharing_accounts(**kws)
1235 self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1236 self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1237 limit, marker = kws.get('limit', None), kws.get('marker', None)
1238 self.assertEqual(SP.mock_calls[-2], call(
1240 iff=limit is not None))
1241 self.assertEqual(SP.mock_calls[-1], call(
1243 iff=marker is not None))
1244 for i in range(len(r)):
1245 self.assert_dicts_are_equal(r[i], sharers[i])
1247 @patch('%s.object_get' % pithos_pkg, return_value=FR())
1248 def test_get_object_versionlist(self, get):
1249 info = dict(object_info)
1250 info['versions'] = ['v1', 'v2']
1252 r = self.client.get_object_versionlist(obj)
1253 get.assert_called_once_with(obj, format='json', version='list')
1254 self.assertEqual(r, info['versions'])
1256 if __name__ == '__main__':
1257 from sys import argv
1258 from kamaki.clients.test import runTestCase
1260 if not argv[1:] or argv[1] == 'Pithos':
1262 runTestCase(Pithos, 'Pithos Client', argv[2:])
1263 if not argv[1:] or argv[1] == 'PithosRest':
1265 runTestCase(PithosRest, 'PithosRest Client', argv[2:])
1267 print('TestCase %s not found' % argv[1])