Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test / pithos.py @ fe7278ee

History | View | Annotate | Download (43.2 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
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.
15
#
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.
28
#
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.
33

    
34
from unittest import TestCase
35
from mock import patch, call
36
from tempfile import NamedTemporaryFile
37
from os import urandom
38

    
39
from kamaki.clients import ClientError
40
from kamaki.clients.pithos import PithosClient as PC
41
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
42

    
43
client_pkg = 'kamaki.clients.Client'
44
pithos_pkg = 'kamaki.clients.pithos.PithosClient'
45

    
46
user_id = 'ac0un7-1d-5tr1ng'
47
obj = 'obj3c7N4m3'
48

    
49
account_info = {
50
    'content-language': 'en-us',
51
    'content-type': 'text/html; charset=utf-8',
52
    'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
53
    'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
54
    'server': 'gunicorn/0.14.5',
55
    'vary': 'Accept-Language',
56
    'x-account-bytes-used': '751615526',
57
    'x-account-container-count': 7,
58
    'x-account-policy-quota': 53687091200,
59
    'x-account-policy-versioning': 'auto'}
60
container_info = {
61
    'content-language': 'en-us',
62
    'content-type': 'text/html; charset=utf-8',
63
    'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
64
    'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
65
    'server': 'gunicorn/0.14.5',
66
    'vary': 'Accept-Language',
67
    'x-container-block-hash': 'sha256',
68
    'x-container-block-size': 4194304,
69
    'x-container-bytes-used': 309528938,
70
    'x-container-object-count': 14,
71
    'x-container-object-meta': '',
72
    'x-container-policy-quota': 53687091200,
73
    'x-container-policy-versioning': 'auto'}
74
object_info = {
75
    'content-language': 'en-us',
76
    'content-length': 254965,
77
    'content-type': 'application/octet-stream',
78
    'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
79
    'etag': '',
80
    'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
81
    'server': 'gunicorn/0.14.5',
82
    'vary': 'Accept-Language',
83
    'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
84
    'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
85
    'x-object-version': '525996',
86
    'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
87
    'x-object-meta-k1': 'v1',
88
    'x-object-meta-k2': 'v2'}
89
container_list = [
90
    dict(
91
        count=2,
92
        last_modified="2013-02-27T11:56:09.893033+00:00",
93
        bytes=677076979,
94
        name="pithos",
95
        x_container_policy=dict(quota="21474836480", versioning="auto")),
96
    dict(
97
        count=0,
98
        last_modified="2012-10-23T12:25:17.229187+00:00",
99
        bytes=0,
100
        name="trash",
101
        x_container_policy=dict(quota="21474836480", versioning="auto"))]
102
object_list = [
103
    dict(hash="",
104
        name="The_Secret_Garden.zip",
105
        x_object_public="/public/wdp9p",
106
        bytes=203304947,
107
        x_object_version_timestamp="1360237915.7027509",
108
        x_object_uuid="s0m3uu1df0r0bj0n3",
109
        last_modified="2013-02-07T11:51:55.702751+00:00",
110
        content_type="application/octet-stream",
111
        x_object_hash="0afdf29f71cd53126225c3f54ca",
112
        x_object_version=17737,
113
        x_object_modified_by=user_id),
114
    dict(hash="",
115
        name="The_Revealed_Garden.zip",
116
        x_object_public="/public/wpd7p",
117
        bytes=20330947,
118
        x_object_version_timestamp="13602915.7027509",
119
        x_object_uuid="s0m3uu1df0r0bj70w",
120
        last_modified="2013-02-07T11:51:55.702751+00:00",
121
        content_type="application/octet-stream",
122
        x_object_hash="0afdf29f71cd53126225c3f54ca",
123
        x_object_version=17737,
124
        x_object_modified_by=user_id)]
125
object_hashmap = dict(
126
    block_hash="sha256", block_size=4194304, bytes=33554432,
127
    hashes=[
128
        "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
129
        "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
130
        "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
131
        "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
132
        "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
133
        "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
134
        "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
135
        "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
136

    
137

    
138
class FR(object):
139
    """FR stands for Fake Response"""
140
    json = dict()
141
    headers = dict()
142
    content = json
143
    status = None
144
    status_code = 200
145

    
146
    def release(self):
147
        pass
148

    
149

    
150
class Pithos(TestCase):
151

    
152
    files = []
153

    
154
    def _create_temp_file(self, num_of_blocks):
155
        self.files.append(NamedTemporaryFile())
156
        tmpFile = self.files[-1]
157
        file_size = num_of_blocks * 4 * 1024 * 1024
158
        print('\n\tCreate tmp file')
159
        tmpFile.write(urandom(file_size))
160
        tmpFile.flush()
161
        tmpFile.seek(0)
162
        print('\t\tDone')
163
        return tmpFile
164

    
165
    def assert_dicts_are_equal(self, d1, d2):
166
        for k, v in d1.items():
167
            self.assertTrue(k in d2)
168
            if isinstance(v, dict):
169
                self.assert_dicts_are_equal(v, d2[k])
170
            else:
171
                self.assertEqual(unicode(v), unicode(d2[k]))
172

    
173
    def setUp(self):
174
        self.url = 'https://www.example.com/pithos'
175
        self.token = 'p17h0570k3n'
176
        self.client = PC(self.url, self.token)
177
        self.client.account = user_id
178
        self.client.container = 'c0nt@1n3r_i'
179

    
180
    def tearDown(self):
181
        FR.headers = dict()
182
        FR.status_code = 200
183
        FR.json = dict()
184
        FR.content = FR.json
185
        for f in self.files:
186
            f.close()
187

    
188
    #  Pithos+ methods that extend storage API
189

    
190
    @patch('%s.set_param' % client_pkg)
191
    def test_get_account_info(self, SP):
192
        FR.headers = account_info
193
        FR.status_code = 204
194
        with patch.object(C, 'perform_request', return_value=FR()):
195
            r = self.client.get_account_info()
196
            self.assertEqual(self.client.http_client.url, self.url)
197
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
198
            self.assert_dicts_are_equal(r, account_info)
199
            untils = ['date 1', 'date 2', 'date 3']
200
            for unt in untils:
201
                r = self.client.get_account_info(until=unt)
202
                self.assert_dicts_are_equal(r, account_info)
203
            for i in range(len(untils)):
204
                self.assertEqual(
205
                    PC.set_param.mock_calls[i + 1],
206
                    call('until', untils[i], iff=untils[i]))
207
            FR.status_code = 401
208
            self.assertRaises(ClientError, self.client.get_account_info)
209

    
210
    @patch('%s.set_header' % client_pkg)
211
    def test_replace_account_meta(self, SH):
212
        FR.status_code = 202
213
        metas = dict(k1='v1', k2='v2', k3='v3')
214
        with patch.object(C, 'perform_request', return_value=FR()):
215
            self.client.replace_account_meta(metas)
216
            self.assertEqual(self.client.http_client.url, self.url)
217
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
218
            prfx = 'X-Account-Meta-'
219
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
220
            self.assertEqual(PC.set_header.mock_calls, expected)
221

    
222
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
223
    def test_del_account_meta(self, ap):
224
        keys = ['k1', 'k2', 'k3']
225
        expected = []
226
        for key in keys:
227
            self.client.del_account_meta(key)
228
            expected.append(call(update=True, metadata={key: ''}))
229
        self.assertEqual(ap.mock_calls, expected)
230

    
231
    @patch('%s.put' % pithos_pkg, return_value=FR())
232
    def test_create_container(self, put):
233
        FR.status_code = 201
234
        cont = 's0m3c0n731n3r'
235
        self.client.create_container(cont)
236
        expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
237
        self.assertEqual(put.mock_calls, expected)
238
        FR.status_code = 202
239
        self.assertRaises(ClientError, self.client.create_container, cont)
240

    
241
    @patch('%s.container_head' % pithos_pkg, return_value=FR())
242
    def test_get_container_info(self, ch):
243
        FR.headers = container_info
244
        r = self.client.get_container_info()
245
        self.assert_dicts_are_equal(r, container_info)
246
        u = 'some date'
247
        r = self.client.get_container_info(until=u)
248
        self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
249

    
250
    @patch('%s.delete' % pithos_pkg, return_value=FR())
251
    def test_delete_container(self, delete):
252
        FR.status_code = 204
253
        cont = 's0m3c0n731n3r'
254
        self.client.delete_container(cont)
255
        for err_code in (404, 409):
256
            FR.status_code = err_code
257
            self.assertRaises(ClientError, self.client.delete_container, cont)
258
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
259
        self.assertEqual(delete.mock_calls, [acall] * 3)
260

    
261
    @patch('%s.account_get' % pithos_pkg, return_value=FR())
262
    def test_list_containers(self, get):
263
        FR.json = container_list
264
        r = self.client.list_containers()
265
        for i in range(len(r)):
266
            self.assert_dicts_are_equal(r[i], container_list[i])
267

    
268
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
269
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
270
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
271
    def test_upload_object(self, CI, CP, OP):
272
        num_of_blocks = 8
273
        tmpFile = self._create_temp_file(num_of_blocks)
274

    
275
        # Without kwargs
276
        self.client.upload_object(obj, tmpFile)
277
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
278
        [call1, call2] = PC.object_put.mock_calls
279

    
280
        (args1, kwargs1) = call1[1:3]
281
        (args2, kwargs2) = call2[1:3]
282
        self.assertEqual(args1, (obj,))
283
        expected1 = dict(
284
            hashmap=True,
285
            success=(201, 409),
286
            format='json',
287
            json=dict(
288
                hashes=['s0m3h@5h'] * num_of_blocks,
289
                bytes=num_of_blocks * 4 * 1024 * 1024),
290
            etag=None,
291
            content_encoding=None,
292
            content_type='application/octet-stream',
293
            content_disposition=None,
294
            public=None,
295
            permissions=None)
296
        for k, v in expected1.items():
297
            if k == 'json':
298
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
299
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
300
            else:
301
                self.assertEqual(v, kwargs1[k])
302

    
303
        (args2, kwargs2) = call2[1:3]
304
        self.assertEqual(args2, (obj,))
305
        expected2 = dict(
306
            json=dict(
307
                hashes=['s0m3h@5h'] * num_of_blocks,
308
                bytes=num_of_blocks * 4 * 1024 * 1024),
309
            content_type='application/octet-stream',
310
            hashmap=True,
311
            success=201,
312
            format='json')
313
        for k, v in expected2.items():
314
            if k == 'json':
315
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
316
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
317
            else:
318
                self.assertEqual(v, kwargs2[k])
319

    
320
        OP = PC.object_put
321
        mock_offset = 2
322

    
323
        #  With progress bars
324
        try:
325
            from progress.bar import ShadyBar
326
            blck_bar = ShadyBar('Mock blck calc.')
327
            upld_bar = ShadyBar('Mock uplds')
328
        except ImportError:
329
            blck_bar = None
330
            upld_bar = None
331

    
332
        if blck_bar and upld_bar:
333

    
334
            def blck_gen(n):
335
                for i in blck_bar.iter(range(n)):
336
                    yield
337
                yield
338

    
339
            def upld_gen(n):
340
                for i in upld_bar.iter(range(n)):
341
                    yield
342
                yield
343

    
344
            tmpFile.seek(0)
345
            self.client.upload_object(
346
                obj, tmpFile,
347
                hash_cb=blck_gen, upload_cb=upld_gen)
348

    
349
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
350
                self.assertEqual(OP.mock_calls[i], c)
351

    
352
        #  With content-type
353
        tmpFile.seek(0)
354
        ctype = 'video/mpeg'
355
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
356
        self.client.upload_object(obj, tmpFile,
357
            content_type=ctype, sharing=sharing)
358
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
359
        self.assert_dicts_are_equal(
360
            OP.mock_calls[-2][2]['permissions'],
361
            sharing)
362

    
363
        # With other args
364
        tmpFile.seek(0)
365
        kwargs = dict(
366
            etag='s0m3E74g',
367
            content_type=ctype,
368
            content_disposition=ctype + 'd15p051710n',
369
            public=True,
370
            content_encoding='802.11')
371
        self.client.upload_object(obj, tmpFile, **kwargs)
372
        for arg, val in kwargs.items():
373
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
374

    
375
    @patch('%s.put' % pithos_pkg, return_value=FR())
376
    @patch('%s.set_header' % client_pkg)
377
    def test_create_object(self, SH, put):
378
        cont = self.client.container
379
        ctype = 'c0n73n7/typ3'
380
        exp_shd = [
381
            call('Content-Type', 'application/octet-stream'),
382
            call('Content-length', '0'),
383
            call('Content-Type', ctype), call('Content-length', '42')]
384
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
385
        self.client.create_object(obj)
386
        self.client.create_object(obj, content_type=ctype, content_length=42)
387
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
388
        self.assertEqual(put.mock_calls, exp_put)
389

    
390
    @patch('%s.put' % pithos_pkg, return_value=FR())
391
    @patch('%s.set_header' % client_pkg)
392
    def test_create_directory(self, SH, put):
393
        cont = self.client.container
394
        exp_shd = [
395
            call('Content-Type', 'application/directory'),
396
            call('Content-length', '0')]
397
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
398
        self.client.create_directory(obj)
399
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
400
        self.assertEqual(put.mock_calls, exp_put)
401

    
402
    def test_get_object_info(self):
403
        FR.headers = object_info
404
        version = 'v3r510n'
405
        with patch.object(PC, 'object_head', return_value=FR()) as head:
406
            r = self.client.get_object_info(obj)
407
            self.assertEqual(r, object_info)
408
            r = self.client.get_object_info(obj, version=version)
409
            self.assertEqual(head.mock_calls, [
410
                call(obj, version=None),
411
                call(obj, version=version)])
412
        with patch.object(
413
                PC,
414
                'object_head',
415
                side_effect=ClientError('Obj not found', 404)):
416
            self.assertRaises(
417
                ClientError,
418
                self.client.get_object_info,
419
                obj, version=version)
420

    
421
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
422
    def test_get_object_meta(self, GOI):
423
        expected = dict()
424
        for k, v in object_info.items():
425
            expected[k] = v
426
        r = self.client.get_object_meta(obj)
427
        self.assert_dicts_are_equal(r, expected)
428

    
429
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
430
    def test_del_object_meta(self, post):
431
        metakey = '50m3m3t4k3y'
432
        self.client.del_object_meta(obj, metakey)
433
        expected = call(obj, update=True, metadata={metakey: ''})
434
        self.assertEqual(post.mock_calls[-1], expected)
435

    
436
    @patch('%s.post' % client_pkg, return_value=FR())
437
    @patch('%s.set_header' % client_pkg)
438
    def test_replace_object_meta(self, SH, post):
439
        metas = dict(k1='new1', k2='new2', k3='new3')
440
        cont = self.client.container
441
        self.client.replace_object_meta(metas)
442
        expected = call('/%s/%s' % (user_id, cont), success=202)
443
        self.assertEqual(post.mock_calls[-1], expected)
444
        prfx = 'X-Object-Meta-'
445
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
446
        self.assertEqual(PC.set_header.mock_calls, expected)
447

    
448
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
449
    def test_copy_object(self, put):
450
        src_cont = 'src-c0nt41n3r'
451
        src_obj = 'src-0bj'
452
        dst_cont = 'dst-c0nt41n3r'
453
        dst_obj = 'dst-0bj'
454
        expected = call(
455
            src_obj,
456
            content_length=0,
457
            source_account=None,
458
            success=201,
459
            copy_from='/%s/%s' % (src_cont, src_obj),
460
            delimiter=None,
461
            content_type=None,
462
            source_version=None,
463
            public=False)
464
        self.client.copy_object(src_cont, src_obj, dst_cont)
465
        self.assertEqual(put.mock_calls[-1], expected)
466
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
467
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
468
        kwargs = dict(
469
            source_version='src-v3r510n',
470
            source_account='src-4cc0un7',
471
            public=True,
472
            content_type='c0n73n7Typ3',
473
            delimiter='5')
474
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
475
        for k, v in kwargs.items():
476
            self.assertEqual(v, put.mock_calls[-1][2][k])
477

    
478
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
479
    def test_move_object(self, put):
480
        src_cont = 'src-c0nt41n3r'
481
        src_obj = 'src-0bj'
482
        dst_cont = 'dst-c0nt41n3r'
483
        dst_obj = 'dst-0bj'
484
        expected = call(
485
            src_obj,
486
            content_length=0,
487
            source_account=None,
488
            success=201,
489
            move_from='/%s/%s' % (src_cont, src_obj),
490
            delimiter=None,
491
            content_type=None,
492
            source_version=None,
493
            public=False)
494
        self.client.move_object(src_cont, src_obj, dst_cont)
495
        self.assertEqual(put.mock_calls[-1], expected)
496
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
497
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
498
        kwargs = dict(
499
            source_version='src-v3r510n',
500
            source_account='src-4cc0un7',
501
            public=True,
502
            content_type='c0n73n7Typ3',
503
            delimiter='5')
504
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
505
        for k, v in kwargs.items():
506
            self.assertEqual(v, put.mock_calls[-1][2][k])
507

    
508
    @patch('%s.delete' % client_pkg, return_value=FR())
509
    def test_delete_object(self, delete):
510
        cont = self.client.container
511
        self.client.delete_object(obj)
512
        self.assertEqual(
513
            delete.mock_calls[-1],
514
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
515
        FR.status_code = 404
516
        self.assertRaises(ClientError, self.client.delete_object, obj)
517

    
518
    @patch('%s.get' % client_pkg, return_value=FR())
519
    @patch('%s.set_param' % client_pkg)
520
    def test_list_objects(self, SP, get):
521
        FR.json = object_list
522
        acc = self.client.account
523
        cont = self.client.container
524
        SP = PC.set_param
525
        r = self.client.list_objects()
526
        for i in range(len(r)):
527
            self.assert_dicts_are_equal(r[i], object_list[i])
528
        self.assertEqual(get.mock_calls, [
529
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
530
        self.assertEqual(SP.mock_calls, [call('format', 'json')])
531
        FR.status_code = 304
532
        self.assertEqual(self.client.list_objects(), [])
533
        FR.status_code = 404
534
        self.assertRaises(ClientError, self.client.list_objects)
535

    
536
    @patch('%s.get' % client_pkg, return_value=FR())
537
    @patch('%s.set_param' % client_pkg)
538
    def test_list_objects_in_path(self, SP, get):
539
        FR.json = object_list
540
        path = '/some/awsome/path'
541
        acc = self.client.account
542
        cont = self.client.container
543
        SP = PC.set_param
544
        self.client.list_objects_in_path(path)
545
        self.assertEqual(get.mock_calls, [
546
            call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
547
        self.assertEqual(SP.mock_calls, [
548
            call('format', 'json'), call('path', path)])
549
        FR.status_code = 404
550
        self.assertRaises(ClientError, self.client.list_objects)
551

    
552
    #  Pithos+ only methods
553

    
554
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
555
    def test_purge_container(self, cd):
556
        self.client.purge_container()
557
        self.assertTrue('until' in cd.mock_calls[-1][2])
558
        cont = self.client.container
559
        self.client.purge_container('another-container')
560
        self.assertEqual(self.client.container, cont)
561

    
562
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
563
    def test_upload_object_unchunked(self, put):
564
        num_of_blocks = 8
565
        tmpFile = self._create_temp_file(num_of_blocks)
566
        expected = dict(
567
                success=201,
568
                data=num_of_blocks * 4 * 1024 * 1024,
569
                etag='some-etag',
570
                content_encoding='some content_encoding',
571
                content_type='some content-type',
572
                content_disposition='some content_disposition',
573
                public=True,
574
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
575
        self.client.upload_object_unchunked(obj, tmpFile)
576
        self.assertEqual(put.mock_calls[-1][1], (obj,))
577
        self.assertEqual(
578
            sorted(put.mock_calls[-1][2].keys()),
579
            sorted(expected.keys()))
580
        kwargs = dict(expected)
581
        kwargs.pop('success')
582
        kwargs['size'] = kwargs.pop('data')
583
        kwargs['sharing'] = kwargs.pop('permissions')
584
        tmpFile.seek(0)
585
        self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
586
        pmc = put.mock_calls[-1][2]
587
        for k, v in expected.items():
588
            if k == 'data':
589
                self.assertEqual(len(pmc[k]), v)
590
            else:
591
                self.assertEqual(pmc[k], v)
592
        self.assertRaises(
593
            ClientError,
594
            self.client.upload_object_unchunked,
595
            obj, tmpFile, withHashFile=True)
596

    
597
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
598
    def test_create_object_by_manifestation(self, put):
599
        manifest = '%s/%s' % (self.client.container, obj)
600
        kwargs = dict(
601
                etag='some-etag',
602
                content_encoding='some content_encoding',
603
                content_type='some content-type',
604
                content_disposition='some content_disposition',
605
                public=True,
606
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
607
        self.client.create_object_by_manifestation(obj)
608
        expected = dict(content_length=0, manifest=manifest)
609
        for k in kwargs:
610
            expected['permissions' if k == 'sharing' else k] = None
611
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
612
        self.client.create_object_by_manifestation(obj, **kwargs)
613
        expected.update(kwargs)
614
        expected['permissions'] = expected.pop('sharing')
615
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
616

    
617
    @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
618
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
619
    def test_download_object(self, GOH, GET):
620
        num_of_blocks = 8
621
        tmpFile = self._create_temp_file(num_of_blocks)
622
        FR.content = tmpFile.read(4 * 1024 * 1024)
623
        tmpFile = self._create_temp_file(num_of_blocks)
624
        GET = PC.object_get
625
        num_of_blocks = len(object_hashmap['hashes'])
626
        kwargs = dict(
627
            resume=True,
628
            version='version',
629
            range_str='10-20',
630
            if_match='if and only if',
631
            if_none_match='if and only not',
632
            if_modified_since='what if not?',
633
            if_unmodified_since='this happens if not!',
634
            async_headers=dict(Range='bytes=0-88888888'))
635

    
636
        self.client.download_object(obj, tmpFile)
637
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
638
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
639
        for k, v in kwargs.items():
640
            if k == 'async_headers':
641
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
642
            elif k in ('resume', 'range_str'):
643
                continue
644
            else:
645
                self.assertEqual(GET.mock_calls[-1][2][k], None)
646

    
647
        #  Check ranges are consecutive
648
        starts = []
649
        ends = []
650
        for c in GET.mock_calls:
651
            rng_str = c[2]['async_headers']['Range']
652
            (start, rng_str) = rng_str.split('=')
653
            (start, end) = rng_str.split('-')
654
            starts.append(start)
655
            ends.append(end)
656
        ends = sorted(ends)
657
        for i, start in enumerate(sorted(starts)):
658
            if i:
659
                int(ends[i - 1]) == int(start) - 1
660

    
661
        #  With progress bars
662
        try:
663
            from progress.bar import ShadyBar
664
            dl_bar = ShadyBar('Mock dl')
665
        except ImportError:
666
            dl_bar = None
667

    
668
        if dl_bar:
669

    
670
            def blck_gen(n):
671
                for i in dl_bar.iter(range(n)):
672
                    yield
673
                yield
674

    
675
            tmpFile.seek(0)
676
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
677
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
678

    
679
        tmpFile.seek(0)
680
        kwargs.pop('async_headers')
681
        kwargs.pop('resume')
682
        self.client.download_object(obj, tmpFile, **kwargs)
683
        for k, v in kwargs.items():
684
            if k == 'range_str':
685
                self.assertEqual(
686
                    GET.mock_calls[-1][2]['data_range'],
687
                    'bytes=%s' % v)
688
            else:
689
                self.assertEqual(GET.mock_calls[-1][2][k], v)
690

    
691
        #  ALl options on no tty
692

    
693
        def foo():
694
            return True
695

    
696
        tmpFile.seek(0)
697
        tmpFile.isatty = foo
698
        self.client.download_object(obj, tmpFile, **kwargs)
699
        for k, v in kwargs.items():
700
            if k == 'range_str':
701
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
702
            else:
703
                self.assertEqual(GET.mock_calls[-1][2][k], v)
704

    
705
    def test_get_object_hashmap(self):
706
        FR.json = object_hashmap
707
        for empty in (304, 412):
708
            with patch.object(
709
                    PC, 'object_get',
710
                    side_effect=ClientError('Empty', status=empty)):
711
                r = self.client.get_object_hashmap(obj)
712
                self.assertEqual(r, {})
713
        exp_args = dict(
714
            hashmap=True,
715
            data_range=None,
716
            version=None,
717
            if_etag_match=None,
718
            if_etag_not_match=None,
719
            if_modified_since=None,
720
            if_unmodified_since=None)
721
        kwargs = dict(
722
            version='s0m3v3r51on',
723
            if_match='if match',
724
            if_none_match='if non match',
725
            if_modified_since='some date here',
726
            if_unmodified_since='some date here',
727
            data_range='10-20')
728
        with patch.object(PC, 'object_get', return_value=FR()) as get:
729
            r = self.client.get_object_hashmap(obj)
730
            self.assertEqual(r, object_hashmap)
731
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
732
            r = self.client.get_object_hashmap(obj, **kwargs)
733
            exp_args['if_etag_match'] = kwargs.pop('if_match')
734
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
735
            exp_args.update(kwargs)
736
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
737

    
738
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
739
    def test_set_account_group(self, post):
740
        (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
741
        self.client.set_account_group(group, usernames)
742
        self.assertEqual(
743
            post.mock_calls[-1],
744
            call(update=True, groups={group: usernames}))
745

    
746
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
747
    def test_del_account_group(self, post):
748
        group = 'aU53rGr0up'
749
        self.client.del_account_group(group)
750
        self.assertEqual(
751
            post.mock_calls[-1],
752
            call(update=True, groups={group: []}))
753

    
754
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
755
    def test_get_account_quota(self, GAI):
756
        key = 'x-account-policy-quota'
757
        r = self.client.get_account_quota()
758
        self.assertEqual(r[key], account_info[key])
759

    
760
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
761
    def test_get_account_versioning(self, GAI):
762
        key = 'x-account-policy-versioning'
763
        r = self.client.get_account_versioning()
764
        self.assertEqual(r[key], account_info[key])
765

    
766
    def test_get_account_meta(self):
767
        key = 'x-account-meta-'
768
        with patch.object(PC, 'get_account_info', return_value=account_info):
769
            r = self.client.get_account_meta()
770
            keys = [k for k in r if k.startswith(key)]
771
            self.assertFalse(keys)
772
        acc_info = dict(account_info)
773
        acc_info['%sk1' % key] = 'v1'
774
        acc_info['%sk2' % key] = 'v2'
775
        acc_info['%sk3' % key] = 'v3'
776
        with patch.object(PC, 'get_account_info', return_value=acc_info):
777
            r = self.client.get_account_meta()
778
            for k in [k for k in acc_info if k.startswith(key)]:
779
                self.assertEqual(r[k], acc_info[k])
780

    
781
    def test_get_account_group(self):
782
        key = 'x-account-group-'
783
        with patch.object(PC, 'get_account_info', return_value=account_info):
784
            r = self.client.get_account_group()
785
            keys = [k for k in r if k.startswith(key)]
786
            self.assertFalse(keys)
787
        acc_info = dict(account_info)
788
        acc_info['%sk1' % key] = 'g1'
789
        acc_info['%sk2' % key] = 'g2'
790
        acc_info['%sk3' % key] = 'g3'
791
        with patch.object(PC, 'get_account_info', return_value=acc_info):
792
            r = self.client.get_account_group()
793
            for k in [k for k in acc_info if k.startswith(key)]:
794
                self.assertEqual(r[k], acc_info[k])
795

    
796
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
797
    def test_set_account_meta(self, post):
798
        metas = dict(k1='v1', k2='v2', k3='v3')
799
        self.client.set_account_meta(metas)
800
        self.assertEqual(
801
            post.mock_calls[-1],
802
            call(update=True, metadata=metas))
803

    
804
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
805
    def test_set_account_quota(self, post):
806
        qu = 1024
807
        self.client.set_account_quota(qu)
808
        self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
809

    
810
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
811
    def test_set_account_versioning(self, post):
812
        vrs = 'n3wV3r51on1ngTyp3'
813
        self.client.set_account_versioning(vrs)
814
        self.assertEqual(
815
            post.mock_calls[-1],
816
            call(update=True, versioning=vrs))
817

    
818
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
819
    def test_del_container(self, delete):
820
        for kwarg in (
821
                dict(delimiter=None, until=None),
822
                dict(delimiter='X', until='50m3d473')):
823
            self.client.del_container(**kwarg)
824
            expected = dict(kwarg)
825
            expected['success'] = (204, 404, 409)
826
            self.assertEqual(delete.mock_calls[-1], call(**expected))
827
        for status_code in (404, 409):
828
            FR.status_code = status_code
829
            self.assertRaises(ClientError, self.client.del_container)
830

    
831
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
832
    def test_get_container_versioning(self, GCI):
833
        key = 'x-container-policy-versioning'
834
        cont = 'c0n7-417'
835
        bu_cnt = self.client.container
836
        for container in (None, cont):
837
            r = self.client.get_container_versioning(container=container)
838
            self.assertEqual(r[key], container_info[key])
839
            self.assertEqual(GCI.mock_calls[-1], call())
840
            self.assertEqual(bu_cnt, self.client.container)
841

    
842
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
843
    def test_get_container_quota(self, GCI):
844
        key = 'x-container-policy-quota'
845
        cont = 'c0n7-417'
846
        bu_cnt = self.client.container
847
        for container in (None, cont):
848
            r = self.client.get_container_quota(container=container)
849
            self.assertEqual(r[key], container_info[key])
850
            self.assertEqual(GCI.mock_calls[-1], call())
851
            self.assertEqual(bu_cnt, self.client.container)
852

    
853
    def test_get_container_meta(self):
854
        somedate = '50m3d473'
855
        key = 'x-container-meta'
856
        metaval = '50m3m374v41'
857
        container_plus = dict(container_info)
858
        container_plus[key] = metaval
859
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
860
            with patch.object(
861
                    PC,
862
                    'get_container_info',
863
                    return_value=ret[0]) as gci:
864
                for until in (None, somedate):
865
                    r = self.client.get_container_meta(until=until)
866
                    self.assertEqual(r, ret[1])
867
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
868

    
869
    def test_get_container_object_meta(self):
870
        somedate = '50m3d473'
871
        key = 'x-container-object-meta'
872
        metaval = '50m3m374v41'
873
        container_plus = dict(container_info)
874
        container_plus[key] = metaval
875
        for ret in (
876
                (container_info, {key: ''}),
877
                (container_plus, {key: metaval})):
878
            with patch.object(
879
                    PC,
880
                    'get_container_info',
881
                    return_value=ret[0]) as gci:
882
                for until in (None, somedate):
883
                    r = self.client.get_container_object_meta(until=until)
884
                    self.assertEqual(r, ret[1])
885
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
886

    
887
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
888
    def test_set_container_meta(self, post):
889
        metas = dict(k1='v1', k2='v2', k3='v3')
890
        self.client.set_container_meta(metas)
891
        self.assertEqual(
892
            post.mock_calls[-1],
893
            call(update=True, metadata=metas))
894

    
895
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
896
    def test_del_container_meta(self, ap):
897
        self.client.del_container_meta('somekey')
898
        expected = [call(update=True, metadata={'somekey': ''})]
899
        self.assertEqual(ap.mock_calls, expected)
900

    
901
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
902
    def test_set_container_quota(self, post):
903
        qu = 1024
904
        self.client.set_container_quota(qu)
905
        self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
906

    
907
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
908
    def test_set_container_versioning(self, post):
909
        vrs = 'n3wV3r51on1ngTyp3'
910
        self.client.set_container_versioning(vrs)
911
        self.assertEqual(
912
            post.mock_calls[-1],
913
            call(update=True, versioning=vrs))
914

    
915
    @patch('%s.object_delete' % pithos_pkg, return_value=FR())
916
    def test_del_object(self, delete):
917
        for kwarg in (
918
                dict(delimiter=None, until=None),
919
                dict(delimiter='X', until='50m3d473')):
920
            self.client.del_object(obj, **kwarg)
921
            self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
922

    
923
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
924
    def test_set_object_meta(self, post):
925
        metas = dict(k1='v1', k2='v2', k3='v3')
926
        self.assertRaises(
927
            AssertionError,
928
            self.client.set_object_meta,
929
            obj, 'Non dict arg')
930
        self.client.set_object_meta(obj, metas)
931
        self.assertEqual(
932
            post.mock_calls[-1],
933
            call(obj, update=True, metadata=metas))
934

    
935
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
936
    def test_publish_object(self, post):
937
        oinfo = dict(object_info)
938
        val = 'pubL1c'
939
        oinfo['x-object-public'] = val
940
        with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
941
            r = self.client.publish_object(obj)
942
            self.assertEqual(
943
                post.mock_calls[-1],
944
                call(obj, public=True, update=True))
945
            self.assertEqual(gof.mock_calls[-1], call(obj))
946
            self.assertEqual(r, '%s%s' % (self.url[:-6], val))
947

    
948
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
949
    def test_unpublish_object(self, post):
950
        self.client.unpublish_object(obj)
951
        self.assertEqual(
952
            post.mock_calls[-1],
953
            call(obj, public=False, update=True))
954

    
955
    def test_get_object_sharing(self):
956
        info = dict(object_info)
957
        expected = dict(read='u1,g1,u2', write='u1')
958
        info['x-object-sharing'] = '; '.join(
959
            ['%s=%s' % (k, v) for k, v in expected.items()])
960
        with patch.object(PC, 'get_object_info', return_value=info) as GOF:
961
            r = self.client.get_object_sharing(obj)
962
            self.assertEqual(GOF.mock_calls[-1], call(obj))
963
            self.assert_dicts_are_equal(r, expected)
964
            info['x-object-sharing'] = '//'.join(
965
                ['%s=%s' % (k, v) for k, v in expected.items()])
966
            self.assertRaises(
967
                ValueError,
968
                self.client.get_object_sharing,
969
                obj)
970
            info['x-object-sharing'] = '; '.join(
971
                ['%s:%s' % (k, v) for k, v in expected.items()])
972
            self.assertRaises(
973
                ClientError,
974
                self.client.get_object_sharing,
975
                obj)
976
            info['x-object-sharing'] = 'read=%s' % expected['read']
977
            r = self.client.get_object_sharing(obj)
978
            expected.pop('write')
979
            self.assert_dicts_are_equal(r, expected)
980

    
981
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
982
    def test_set_object_sharing(self, POST):
983
        read_perms = ['u1', 'g1', 'u2', 'g2']
984
        write_perms = ['u1', 'g1']
985
        for kwargs in (
986
                dict(read_permition=read_perms, write_permition=write_perms),
987
                dict(read_permition=read_perms),
988
                dict(write_permition=write_perms),
989
                dict()):
990
            self.client.set_object_sharing(obj, **kwargs)
991
            kwargs['read'] = kwargs.pop('read_permition', '')
992
            kwargs['write'] = kwargs.pop('write_permition', '')
993
            self.assertEqual(
994
                POST.mock_calls[-1],
995
                call(obj, update=True, permissions=kwargs))
996

    
997
    @patch('%s.set_object_sharing' % pithos_pkg)
998
    def test_del_object_sharing(self, SOS):
999
        self.client.del_object_sharing(obj)
1000
        self.assertEqual(SOS.mock_calls[-1], call(obj))
1001

    
1002
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1003
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1004
    def test_append_object(self, post, GCI):
1005
        num_of_blocks = 4
1006
        tmpFile = self._create_temp_file(num_of_blocks)
1007
        tmpFile.seek(0, 2)
1008
        file_size = tmpFile.tell()
1009
        for turn in range(2):
1010
            tmpFile.seek(0, 0)
1011

    
1012
            try:
1013
                from progress.bar import ShadyBar
1014
                apn_bar = ShadyBar('Mock append')
1015
            except ImportError:
1016
                apn_bar = None
1017

    
1018
            if apn_bar:
1019

    
1020
                def append_gen(n):
1021
                    for i in apn_bar.iter(range(n)):
1022
                        yield
1023
                    yield
1024

    
1025
            else:
1026
                append_gen = None
1027

    
1028
            self.client.append_object(
1029
                obj, tmpFile,
1030
                upload_cb=append_gen if turn else None)
1031
            self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1032
            (args, kwargs) = post.mock_calls[-1][1:3]
1033
            self.assertEqual(args, (obj,))
1034
            self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1035
            fsize = num_of_blocks * int(kwargs['content_length'])
1036
            self.assertEqual(fsize, file_size)
1037
            self.assertEqual(kwargs['content_range'], 'bytes */*')
1038
            exp = 'application/octet-stream'
1039
            self.assertEqual(kwargs['content_type'], exp)
1040
            self.assertEqual(kwargs['update'], True)
1041

    
1042
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1043
    def test_truncate_object(self, post):
1044
        upto_bytes = 377
1045
        self.client.truncate_object(obj, upto_bytes)
1046
        self.assertEqual(post.mock_calls[-1], call(
1047
            obj,
1048
            update=True,
1049
            object_bytes=upto_bytes,
1050
            content_range='bytes 0-%s/*' % upto_bytes,
1051
            content_type='application/octet-stream',
1052
            source_object='/%s/%s' % (self.client.container, obj)))
1053

    
1054
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1055
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1056
    def test_overwrite_object(self, post, GCI):
1057
        num_of_blocks = 4
1058
        tmpFile = self._create_temp_file(num_of_blocks)
1059
        tmpFile.seek(0, 2)
1060
        file_size = tmpFile.tell()
1061
        info = dict(object_info)
1062
        info['content-length'] = file_size
1063
        block_size = container_info['x-container-block-size']
1064
        with patch.object(PC, 'get_object_info', return_value=info) as GOI:
1065
            for start, end in (
1066
                    (0, file_size + 1),
1067
                    (file_size + 1, file_size + 2)):
1068
                tmpFile.seek(0, 0)
1069
                self.assertRaises(
1070
                    ClientError,
1071
                    self.client.overwrite_object,
1072
                    obj, start, end, tmpFile)
1073
            for start, end in ((0, 144), (144, 233), (233, file_size)):
1074
                tmpFile.seek(0, 0)
1075
                owr_gen = None
1076
                exp_size = end - start + 1
1077
                if not start or exp_size > block_size:
1078
                    try:
1079
                        from progress.bar import ShadyBar
1080
                        owr_bar = ShadyBar('Mock append')
1081
                    except ImportError:
1082
                        owr_bar = None
1083

    
1084
                    if owr_bar:
1085

    
1086
                        def owr_gen(n):
1087
                            for i in owr_bar.iter(range(n)):
1088
                                yield
1089
                            yield
1090

    
1091
                    if exp_size > block_size:
1092
                        exp_size = exp_size % block_size or block_size
1093

    
1094
                self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1095
                self.assertEqual(GOI.mock_calls[-1], call(obj))
1096
                self.assertEqual(GCI.mock_calls[-1], call())
1097
                (args, kwargs) = post.mock_calls[-1][1:3]
1098
                self.assertEqual(args, (obj,))
1099
                self.assertEqual(len(kwargs['data']), exp_size)
1100
                self.assertEqual(kwargs['content_length'], exp_size)
1101
                self.assertEqual(kwargs['update'], True)
1102
                exp = 'application/octet-stream'
1103
                self.assertEqual(kwargs['content_type'], exp)