Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test / pithos.py @ 5c72ad76

History | View | Annotate | Download (37.1 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
user_id = 'ac0un7-1d-5tr1ng'
44
obj = 'obj3c7N4m3'
45

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

    
134

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

    
143
    def release(self):
144
        pass
145

    
146

    
147
class Pithos(TestCase):
148

    
149
    files = []
150

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

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

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

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

    
185
    #  Pithos+ methods that extend storage API
186

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

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

    
219
    def test_del_account_meta(self):
220
        keys = ['k1', 'k2', 'k3']
221
        with patch.object(PC, 'account_post', return_value=FR()) as ap:
222
            expected = []
223
            for key in keys:
224
                self.client.del_account_meta(key)
225
                expected.append(call(update=True, metadata={key: ''}))
226
            self.assertEqual(ap.mock_calls, expected)
227

    
228
    def test_create_container(self):
229
        FR.status_code = 201
230
        with patch.object(PC, 'put', return_value=FR()) as put:
231
            cont = 's0m3c0n731n3r'
232
            self.client.create_container(cont)
233
            expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
234
            self.assertEqual(put.mock_calls, expected)
235
            FR.status_code = 202
236
            self.assertRaises(ClientError, self.client.create_container, cont)
237

    
238
    def test_get_container_info(self):
239
        FR.headers = container_info
240
        with patch.object(PC, 'container_head', return_value=FR()) as ch:
241
            r = self.client.get_container_info()
242
            self.assert_dicts_are_equal(r, container_info)
243
            u = 'some date'
244
            r = self.client.get_container_info(until=u)
245
            self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
246

    
247
    def test_delete_container(self):
248
        FR.status_code = 204
249
        with patch.object(PC, 'delete', return_value=FR()) as delete:
250
            cont = 's0m3c0n731n3r'
251
            self.client.delete_container(cont)
252
            FR.status_code = 404
253
            self.assertRaises(ClientError, self.client.delete_container, cont)
254
            FR.status_code = 409
255
            self.assertRaises(ClientError, self.client.delete_container, cont)
256
            acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
257
            self.assertEqual(delete.mock_calls, [acall] * 3)
258

    
259
    def test_list_containers(self):
260
        FR.json = container_list
261
        with patch.object(PC, 'account_get', return_value=FR()):
262
            r = self.client.list_containers()
263
            for i in range(len(r)):
264
                self.assert_dicts_are_equal(r[i], container_list[i])
265

    
266
    @patch(
267
        'kamaki.clients.pithos.PithosClient.get_container_info',
268
        return_value=container_info)
269
    @patch(
270
        'kamaki.clients.pithos.PithosClient.container_post',
271
        return_value=FR())
272
    @patch(
273
        'kamaki.clients.pithos.PithosClient.object_put',
274
        return_value=FR())
275
    def test_upload_object(self, CI, CP, OP):
276
        num_of_blocks = 8
277
        tmpFile = self._create_temp_file(num_of_blocks)
278

    
279
        # Without kwargs
280
        self.client.upload_object(obj, tmpFile)
281
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
282
        [call1, call2] = PC.object_put.mock_calls
283

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

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

    
324
        OP = PC.object_put
325
        mock_offset = 2
326

    
327
        #  With progress bars
328
        try:
329
            from progress.bar import ShadyBar
330
            blck_bar = ShadyBar('Mock blck calc.')
331
            upld_bar = ShadyBar('Mock uplds')
332
        except ImportError:
333
            blck_bar = None
334
            upld_bar = None
335

    
336
        if blck_bar and upld_bar:
337

    
338
            def blck_gen(n):
339
                for i in blck_bar.iter(range(n)):
340
                    yield
341
                yield
342

    
343
            def upld_gen(n):
344
                for i in upld_bar.iter(range(n)):
345
                    yield
346
                yield
347

    
348
            tmpFile.seek(0)
349
            self.client.upload_object(
350
                obj, tmpFile,
351
                hash_cb=blck_gen, upload_cb=upld_gen)
352

    
353
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
354
                self.assertEqual(OP.mock_calls[i], c)
355

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

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

    
379
    @patch('kamaki.clients.Client.set_header')
380
    def test_create_object(self, SH):
381
        cont = self.client.container
382
        ctype = 'c0n73n7/typ3'
383
        exp_shd = [
384
            call('Content-Type', 'application/octet-stream'),
385
            call('Content-length', '0'),
386
            call('Content-Type', ctype), call('Content-length', '42')]
387
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
388
        with patch.object(PC, 'put', return_value=FR()) as put:
389
            self.client.create_object(obj)
390
            self.client.create_object(obj,
391
                content_type=ctype, content_length=42)
392
            self.assertEqual(PC.set_header.mock_calls, exp_shd)
393
            self.assertEqual(put.mock_calls, exp_put)
394

    
395
    @patch('kamaki.clients.Client.set_header')
396
    def test_create_directory(self, SH):
397
        cont = self.client.container
398
        exp_shd = [
399
            call('Content-Type', 'application/directory'),
400
            call('Content-length', '0')]
401
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
402
        with patch.object(PC, 'put', return_value=FR()) as put:
403
            self.client.create_directory(obj)
404
            self.assertEqual(PC.set_header.mock_calls, exp_shd)
405
            self.assertEqual(put.mock_calls, exp_put)
406

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

    
426
    def test_get_object_meta(self):
427
        expected = dict()
428
        for k, v in object_info.items():
429
            expected[k] = v
430
        with patch.object(
431
                PC,
432
                'get_object_info',
433
                return_value=object_info):
434
            r = self.client.get_object_meta(obj)
435
            self.assert_dicts_are_equal(r, expected)
436

    
437
    def test_del_object_meta(self):
438
        metakey = '50m3m3t4k3y'
439
        with patch.object(PC, 'object_post', return_value=FR()) as post:
440
            self.client.del_object_meta(obj, metakey)
441
            self.assertEqual(
442
                post.mock_calls,
443
                [call(obj, update=True, metadata={metakey: ''})])
444

    
445
    @patch('kamaki.clients.Client.set_header')
446
    def test_replace_object_meta(self, SH):
447
        metas = dict(k1='new1', k2='new2', k3='new3')
448
        cont = self.client.container
449
        with patch.object(PC, 'post', return_value=FR()) as post:
450
            self.client.replace_object_meta(metas)
451
            self.assertEqual(post.mock_calls, [
452
                call('/%s/%s' % (user_id, cont),
453
                success=202)])
454
            prfx = 'X-Object-Meta-'
455
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
456
            self.assertEqual(PC.set_header.mock_calls, expected)
457

    
458
    def test_copy_object(self):
459
        src_cont = 'src-c0nt41n3r'
460
        src_obj = 'src-0bj'
461
        dst_cont = 'dst-c0nt41n3r'
462
        dst_obj = 'dst-0bj'
463
        expected = call(
464
            src_obj,
465
            content_length=0,
466
            source_account=None,
467
            success=201,
468
            copy_from='/%s/%s' % (src_cont, src_obj),
469
            delimiter=None,
470
            content_type=None,
471
            source_version=None,
472
            public=False)
473
        with patch.object(PC, 'object_put', return_value=FR()) as put:
474
            self.client.copy_object(src_cont, src_obj, dst_cont)
475
            self.assertEqual(put.mock_calls[-1], expected)
476
            self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
477
            self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
478
            kwargs = dict(
479
                source_version='src-v3r510n',
480
                source_account='src-4cc0un7',
481
                public=True,
482
                content_type='c0n73n7Typ3',
483
                delimiter='5')
484
            self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
485
            for k, v in kwargs.items():
486
                self.assertEqual(v, put.mock_calls[-1][2][k])
487

    
488
    def test_move_object(self):
489
        src_cont = 'src-c0nt41n3r'
490
        src_obj = 'src-0bj'
491
        dst_cont = 'dst-c0nt41n3r'
492
        dst_obj = 'dst-0bj'
493
        expected = call(
494
            src_obj,
495
            content_length=0,
496
            source_account=None,
497
            success=201,
498
            move_from='/%s/%s' % (src_cont, src_obj),
499
            delimiter=None,
500
            content_type=None,
501
            source_version=None,
502
            public=False)
503
        with patch.object(PC, 'object_put', return_value=FR()) as put:
504
            self.client.move_object(src_cont, src_obj, dst_cont)
505
            self.assertEqual(put.mock_calls[-1], expected)
506
            self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
507
            self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
508
            kwargs = dict(
509
                source_version='src-v3r510n',
510
                source_account='src-4cc0un7',
511
                public=True,
512
                content_type='c0n73n7Typ3',
513
                delimiter='5')
514
            self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
515
            for k, v in kwargs.items():
516
                self.assertEqual(v, put.mock_calls[-1][2][k])
517

    
518
    def test_delete_object(self):
519
        cont = self.client.container
520
        with patch.object(PC, 'delete', return_value=FR()) as delete:
521
            self.client.delete_object(obj)
522
            self.assertEqual(delete.mock_calls, [
523
                call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404))])
524
            FR.status_code = 404
525
            self.assertRaises(ClientError, self.client.delete_object, obj)
526

    
527
    @patch('kamaki.clients.Client.set_param')
528
    def test_list_objects(self, SP):
529
        FR.json = object_list
530
        acc = self.client.account
531
        cont = self.client.container
532
        SP = PC.set_param
533
        with patch.object(PC, 'get', return_value=FR()) as get:
534
            r = self.client.list_objects()
535
            for i in range(len(r)):
536
                self.assert_dicts_are_equal(r[i], object_list[i])
537
            self.assertEqual(get.mock_calls, [
538
                call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
539
            self.assertEqual(SP.mock_calls, [call('format', 'json')])
540
            FR.status_code = 304
541
            self.assertEqual(self.client.list_objects(), [])
542
            FR.status_code = 404
543
            self.assertRaises(ClientError, self.client.list_objects)
544

    
545
    @patch('kamaki.clients.Client.set_param')
546
    def test_list_objects_in_path(self, SP):
547
        FR.json = object_list
548
        path = '/some/awsome/path'
549
        acc = self.client.account
550
        cont = self.client.container
551
        SP = PC.set_param
552
        with patch.object(PC, 'get', return_value=FR()) as get:
553
            self.client.list_objects_in_path(path)
554
            self.assertEqual(get.mock_calls, [
555
                call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
556
            self.assertEqual(SP.mock_calls, [
557
                call('format', 'json'), call('path', path)])
558
            FR.status_code = 404
559
            self.assertRaises(ClientError, self.client.list_objects)
560

    
561
    #  Pithos+ only methods
562

    
563
    def test_purge_container(self):
564
        with patch.object(
565
                PC,
566
                'container_delete',
567
                return_value=FR()) as cd:
568
            self.client.purge_container()
569
            self.assertTrue('until' in cd.mock_calls[-1][2])
570
            cont = self.client.container
571
            self.client.purge_container('another-container')
572
            self.assertEqual(self.client.container, cont)
573

    
574
    def test_upload_object_unchunked(self):
575
        num_of_blocks = 8
576
        tmpFile = self._create_temp_file(num_of_blocks)
577
        expected = dict(
578
                success=201,
579
                data=num_of_blocks * 4 * 1024 * 1024,
580
                etag='some-etag',
581
                content_encoding='some content_encoding',
582
                content_type='some content-type',
583
                content_disposition='some content_disposition',
584
                public=True,
585
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
586
        with patch.object(PC, 'object_put', return_value=FR()) as put:
587
            self.client.upload_object_unchunked(obj, tmpFile)
588
            self.assertEqual(put.mock_calls[-1][1], (obj,))
589
            self.assertEqual(
590
                sorted(put.mock_calls[-1][2].keys()),
591
                sorted(expected.keys()))
592
            kwargs = dict(expected)
593
            kwargs.pop('success')
594
            kwargs['size'] = kwargs.pop('data')
595
            kwargs['sharing'] = kwargs.pop('permissions')
596
            tmpFile.seek(0)
597
            self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
598
            pmc = put.mock_calls[-1][2]
599
            for k, v in expected.items():
600
                if k == 'data':
601
                    self.assertEqual(len(pmc[k]), v)
602
                else:
603
                    self.assertEqual(pmc[k], v)
604
            self.assertRaises(
605
                ClientError,
606
                self.client.upload_object_unchunked,
607
                obj, tmpFile, withHashFile=True)
608

    
609
    def test_create_object_by_manifestation(self):
610
        manifest = '%s/%s' % (self.client.container, obj)
611
        kwargs = dict(
612
                etag='some-etag',
613
                content_encoding='some content_encoding',
614
                content_type='some content-type',
615
                content_disposition='some content_disposition',
616
                public=True,
617
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
618
        with patch.object(PC, 'object_put', return_value=FR()) as put:
619
            self.client.create_object_by_manifestation(obj)
620
            expected = dict(content_length=0, manifest=manifest)
621
            for k in kwargs:
622
                expected['permissions' if k == 'sharing' else k] = None
623
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
624
            self.client.create_object_by_manifestation(obj, **kwargs)
625
            expected.update(kwargs)
626
            expected['permissions'] = expected.pop('sharing')
627
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
628

    
629
    @patch(
630
        'kamaki.clients.pithos.PithosClient.get_object_hashmap',
631
        return_value=object_hashmap)
632
    @patch(
633
        'kamaki.clients.pithos.PithosClient.object_get',
634
        return_value=FR())
635
    def test_download_object(self, GOH, GET):
636
        num_of_blocks = 8
637
        tmpFile = self._create_temp_file(num_of_blocks)
638
        FR.content = tmpFile.read(4 * 1024 * 1024)
639
        tmpFile = self._create_temp_file(num_of_blocks)
640
        GET = PC.object_get
641
        num_of_blocks = len(object_hashmap['hashes'])
642
        kwargs = dict(
643
            resume=True,
644
            version='version',
645
            range_str='10-20',
646
            if_match='if and only if',
647
            if_none_match='if and only not',
648
            if_modified_since='what if not?',
649
            if_unmodified_since='this happens if not!',
650
            async_headers=dict(Range='bytes=0-88888888'))
651

    
652
        self.client.download_object(obj, tmpFile)
653
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
654
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
655
        for k, v in kwargs.items():
656
            if k == 'async_headers':
657
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
658
            elif k in ('resume', 'range_str'):
659
                continue
660
            else:
661
                self.assertEqual(GET.mock_calls[-1][2][k], None)
662

    
663
        #  Check ranges are consecutive
664
        starts = []
665
        ends = []
666
        for c in GET.mock_calls:
667
            rng_str = c[2]['async_headers']['Range']
668
            (start, rng_str) = rng_str.split('=')
669
            (start, end) = rng_str.split('-')
670
            starts.append(start)
671
            ends.append(end)
672
        ends = sorted(ends)
673
        for i, start in enumerate(sorted(starts)):
674
            if i:
675
                int(ends[i - 1]) == int(start) - 1
676

    
677
        #  With progress bars
678
        try:
679
            from progress.bar import ShadyBar
680
            dl_bar = ShadyBar('Mock dl')
681
        except ImportError:
682
            dl_bar = None
683

    
684
        if dl_bar:
685

    
686
            def blck_gen(n):
687
                for i in dl_bar.iter(range(n)):
688
                    yield
689
                yield
690

    
691
            tmpFile.seek(0)
692
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
693
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
694

    
695
        tmpFile.seek(0)
696
        kwargs.pop('async_headers')
697
        kwargs.pop('resume')
698
        self.client.download_object(obj, tmpFile, **kwargs)
699
        for k, v in kwargs.items():
700
            if k == 'range_str':
701
                self.assertEqual(
702
                    GET.mock_calls[-1][2]['data_range'],
703
                    'bytes=%s' % v)
704
            else:
705
                self.assertEqual(GET.mock_calls[-1][2][k], v)
706

    
707
        #  ALl options on no tty
708

    
709
        def foo():
710
            return True
711

    
712
        tmpFile.seek(0)
713
        tmpFile.isatty = foo
714
        self.client.download_object(obj, tmpFile, **kwargs)
715
        for k, v in kwargs.items():
716
            if k == 'range_str':
717
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
718
            else:
719
                self.assertEqual(GET.mock_calls[-1][2][k], v)
720

    
721
    def test_get_object_hashmap(self):
722
        FR.json = object_hashmap
723
        for empty in (304, 412):
724
            with patch.object(
725
                    PC, 'object_get',
726
                    side_effect=ClientError('Empty', status=empty)):
727
                r = self.client.get_object_hashmap(obj)
728
                self.assertEqual(r, {})
729
        exp_args = dict(
730
            hashmap=True,
731
            data_range=None,
732
            version=None,
733
            if_etag_match=None,
734
            if_etag_not_match=None,
735
            if_modified_since=None,
736
            if_unmodified_since=None)
737
        kwargs = dict(
738
            version='s0m3v3r51on',
739
            if_match='if match',
740
            if_none_match='if non match',
741
            if_modified_since='some date here',
742
            if_unmodified_since='some date here',
743
            data_range='10-20')
744
        with patch.object(PC, 'object_get', return_value=FR()) as get:
745
            r = self.client.get_object_hashmap(obj)
746
            self.assertEqual(r, object_hashmap)
747
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
748
            r = self.client.get_object_hashmap(obj, **kwargs)
749
            exp_args['if_etag_match'] = kwargs.pop('if_match')
750
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
751
            exp_args.update(kwargs)
752
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
753

    
754
    def test_set_account_group(self):
755
        group = 'aU53rGr0up'
756
        usernames = ['u1', 'u2', 'u3']
757
        with patch.object(PC, 'account_post', return_value=FR()) as post:
758
            self.client.set_account_group(group, usernames)
759
            self.assertEqual(
760
                post.mock_calls[-1],
761
                call(update=True, groups={group: usernames}))
762

    
763
    def test_del_account_group(self):
764
        group = 'aU53rGr0up'
765
        with patch.object(PC, 'account_post', return_value=FR()) as post:
766
            self.client.del_account_group(group)
767
            self.assertEqual(
768
                post.mock_calls[-1],
769
                call(update=True, groups={group: []}))
770

    
771
    def test_get_account_quota(self):
772
        key = 'x-account-policy-quota'
773
        with patch.object(PC, 'get_account_info', return_value=account_info):
774
            r = self.client.get_account_quota()
775
            self.assertEqual(r[key], account_info[key])
776

    
777
    def test_get_account_versioning(self):
778
        key = 'x-account-policy-versioning'
779
        with patch.object(PC, 'get_account_info', return_value=account_info):
780
            r = self.client.get_account_versioning()
781
            self.assertEqual(r[key], account_info[key])
782

    
783
    def test_get_account_meta(self):
784
        key = 'x-account-meta-'
785
        with patch.object(PC, 'get_account_info', return_value=account_info):
786
            r = self.client.get_account_meta()
787
            keys = [k for k in r if k.startswith(key)]
788
            self.assertFalse(keys)
789
        acc_info = dict(account_info)
790
        acc_info['%sk1' % key] = 'v1'
791
        acc_info['%sk2' % key] = 'v2'
792
        acc_info['%sk3' % key] = 'v3'
793
        with patch.object(PC, 'get_account_info', return_value=acc_info):
794
            r = self.client.get_account_meta()
795
            for k in [k for k in acc_info if k.startswith(key)]:
796
                self.assertEqual(r[k], acc_info[k])
797

    
798
    def test_get_account_group(self):
799
        key = 'x-account-group-'
800
        with patch.object(PC, 'get_account_info', return_value=account_info):
801
            r = self.client.get_account_group()
802
            keys = [k for k in r if k.startswith(key)]
803
            self.assertFalse(keys)
804
        acc_info = dict(account_info)
805
        acc_info['%sk1' % key] = 'g1'
806
        acc_info['%sk2' % key] = 'g2'
807
        acc_info['%sk3' % key] = 'g3'
808
        with patch.object(PC, 'get_account_info', return_value=acc_info):
809
            r = self.client.get_account_group()
810
            for k in [k for k in acc_info if k.startswith(key)]:
811
                self.assertEqual(r[k], acc_info[k])
812

    
813
    def test_set_account_meta(self):
814
        metas = dict(k1='v1', k2='v2', k3='v3')
815
        with patch.object(PC, 'account_post', return_value=FR()) as post:
816
            self.client.set_account_meta(metas)
817
            self.assertEqual(
818
                post.mock_calls[-1],
819
                call(update=True, metadata=metas))
820

    
821
    def test_set_account_quota(self):
822
        qu = 1024
823
        with patch.object(PC, 'account_post', return_value=FR()) as post:
824
            self.client.set_account_quota(qu)
825
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
826

    
827
    def test_set_account_versioning(self):
828
        vrs = 'n3wV3r51on1ngTyp3'
829
        with patch.object(PC, 'account_post', return_value=FR()) as post:
830
            self.client.set_account_versioning(vrs)
831
            self.assertEqual(
832
                post.mock_calls[-1],
833
                call(update=True, versioning=vrs))
834

    
835
    def test_del_container(self):
836
        kwarg_list = [
837
            dict(delimiter=None, until=None),
838
            dict(delimiter='X', until='50m3d473')]
839
        with patch.object(
840
                PC,
841
                'container_delete',
842
                return_value=FR()) as delete:
843
            for kwarg in kwarg_list:
844
                self.client.del_container(**kwarg)
845
                expected = dict(kwarg)
846
                expected['success'] = (204, 404, 409)
847
                self.assertEqual(delete.mock_calls[-1], call(**expected))
848
            for status_code in (404, 409):
849
                FR.status_code = status_code
850
                self.assertRaises(ClientError, self.client.del_container)
851

    
852
    def test_get_container_versioning(self):
853
        key = 'x-container-policy-versioning'
854
        cont = 'c0n7-417'
855
        bu_cnt = self.client.container
856
        with patch.object(
857
                PC,
858
                'get_container_info',
859
                return_value=container_info) as gci:
860
            for container in (None, cont):
861
                r = self.client.get_container_versioning(container=container)
862
                self.assertEqual(r[key], container_info[key])
863
                self.assertEqual(gci.mock_calls[-1], call())
864
                self.assertEqual(bu_cnt, self.client.container)
865

    
866
    def test_get_container_quota(self):
867
        key = 'x-container-policy-quota'
868
        cont = 'c0n7-417'
869
        bu_cnt = self.client.container
870
        with patch.object(
871
                PC,
872
                'get_container_info',
873
                return_value=container_info) as gci:
874
            for container in (None, cont):
875
                r = self.client.get_container_quota(container=container)
876
                self.assertEqual(r[key], container_info[key])
877
                self.assertEqual(gci.mock_calls[-1], call())
878
                self.assertEqual(bu_cnt, self.client.container)
879

    
880
    def test_get_container_meta(self):
881
        somedate = '50m3d473'
882
        key = 'x-container-meta'
883
        metaval = '50m3m374v41'
884
        container_plus = dict(container_info)
885
        container_plus[key] = metaval
886
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
887
            with patch.object(
888
                    PC,
889
                    'get_container_info',
890
                    return_value=ret[0]) as gci:
891
                for until in (None, somedate):
892
                    r = self.client.get_container_meta(until=until)
893
                    self.assertEqual(r, ret[1])
894
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
895

    
896
    def test_get_container_object_meta(self):
897
        somedate = '50m3d473'
898
        key = 'x-container-object-meta'
899
        metaval = '50m3m374v41'
900
        container_plus = dict(container_info)
901
        container_plus[key] = metaval
902
        for ret in (
903
                (container_info, {key: ''}),
904
                (container_plus, {key: metaval})):
905
            with patch.object(
906
                    PC,
907
                    'get_container_info',
908
                    return_value=ret[0]) as gci:
909
                for until in (None, somedate):
910
                    r = self.client.get_container_object_meta(until=until)
911
                    self.assertEqual(r, ret[1])
912
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
913

    
914
    def test_set_container_meta(self):
915
        metas = dict(k1='v1', k2='v2', k3='v3')
916
        with patch.object(PC, 'container_post', return_value=FR()) as post:
917
            self.client.set_container_meta(metas)
918
            self.assertEqual(
919
                post.mock_calls[-1],
920
                call(update=True, metadata=metas))
921

    
922
    def test_del_container_meta(self):
923
        with patch.object(PC, 'container_post', return_value=FR()) as ap:
924
            self.client.del_container_meta('somekey')
925
            expected = [call(update=True, metadata={'somekey': ''})]
926
            self.assertEqual(ap.mock_calls, expected)
927

    
928
    def test_set_container_quota(self):
929
        qu = 1024
930
        with patch.object(PC, 'container_post', return_value=FR()) as post:
931
            self.client.set_container_quota(qu)
932
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
933

    
934
    def test_set_container_versioning(self):
935
        vrs = 'n3wV3r51on1ngTyp3'
936
        with patch.object(PC, 'container_post', return_value=FR()) as post:
937
            self.client.set_container_versioning(vrs)
938
            self.assertEqual(
939
                post.mock_calls[-1],
940
                call(update=True, versioning=vrs))
941

    
942
    def test_del_object(self):
943
        kwargs = [
944
            dict(delimiter=None, until=None),
945
            dict(delimiter='X', until='50m3d473')]
946
        with patch.object(PC, 'object_delete', return_value=FR()) as delete:
947
            for kwarg in kwargs:
948
                self.client.del_object(obj, **kwarg)
949
                self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))