Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37.4 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(PC, 'get_object_info', return_value=object_info):
431
            r = self.client.get_object_meta(obj)
432
            self.assert_dicts_are_equal(r, expected)
433

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

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

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

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

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

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

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

    
558
    #  Pithos+ only methods
559

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

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

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

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

    
646
        self.client.download_object(obj, tmpFile)
647
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
648
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
649
        for k, v in kwargs.items():
650
            if k == 'async_headers':
651
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
652
            elif k in ('resume', 'range_str'):
653
                continue
654
            else:
655
                self.assertEqual(GET.mock_calls[-1][2][k], None)
656

    
657
        #  Check ranges are consecutive
658
        starts = []
659
        ends = []
660
        for c in GET.mock_calls:
661
            rng_str = c[2]['async_headers']['Range']
662
            (start, rng_str) = rng_str.split('=')
663
            (start, end) = rng_str.split('-')
664
            starts.append(start)
665
            ends.append(end)
666
        ends = sorted(ends)
667
        for i, start in enumerate(sorted(starts)):
668
            if i:
669
                int(ends[i - 1]) == int(start) - 1
670

    
671
        #  With progress bars
672
        try:
673
            from progress.bar import ShadyBar
674
            dl_bar = ShadyBar('Mock dl')
675
        except ImportError:
676
            dl_bar = None
677

    
678
        if dl_bar:
679

    
680
            def blck_gen(n):
681
                for i in dl_bar.iter(range(n)):
682
                    yield
683
                yield
684

    
685
            tmpFile.seek(0)
686
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
687
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
688

    
689
        tmpFile.seek(0)
690
        kwargs.pop('async_headers')
691
        kwargs.pop('resume')
692
        self.client.download_object(obj, tmpFile, **kwargs)
693
        for k, v in kwargs.items():
694
            if k == 'range_str':
695
                self.assertEqual(
696
                    GET.mock_calls[-1][2]['data_range'],
697
                    'bytes=%s' % v)
698
            else:
699
                self.assertEqual(GET.mock_calls[-1][2][k], v)
700

    
701
        #  ALl options on no tty
702

    
703
        def foo():
704
            return True
705

    
706
        tmpFile.seek(0)
707
        tmpFile.isatty = foo
708
        self.client.download_object(obj, tmpFile, **kwargs)
709
        for k, v in kwargs.items():
710
            if k == 'range_str':
711
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
712
            else:
713
                self.assertEqual(GET.mock_calls[-1][2][k], v)
714

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

    
748
    def test_set_account_group(self):
749
        group = 'aU53rGr0up'
750
        usernames = ['u1', 'u2', 'u3']
751
        with patch.object(PC, 'account_post', return_value=FR()) as post:
752
            self.client.set_account_group(group, usernames)
753
            self.assertEqual(
754
                post.mock_calls[-1],
755
                call(update=True, groups={group: usernames}))
756

    
757
    def test_del_account_group(self):
758
        group = 'aU53rGr0up'
759
        with patch.object(PC, 'account_post', return_value=FR()) as post:
760
            self.client.del_account_group(group)
761
            self.assertEqual(
762
                post.mock_calls[-1],
763
                call(update=True, groups={group: []}))
764

    
765
    def test_get_account_quota(self):
766
        key = 'x-account-policy-quota'
767
        with patch.object(PC, 'get_account_info', return_value=account_info):
768
            r = self.client.get_account_quota()
769
            self.assertEqual(r[key], account_info[key])
770

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

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

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

    
807
    def test_set_account_meta(self):
808
        metas = dict(k1='v1', k2='v2', k3='v3')
809
        with patch.object(PC, 'account_post', return_value=FR()) as post:
810
            self.client.set_account_meta(metas)
811
            self.assertEqual(
812
                post.mock_calls[-1],
813
                call(update=True, metadata=metas))
814

    
815
    def test_set_account_quota(self):
816
        qu = 1024
817
        with patch.object(PC, 'account_post', return_value=FR()) as post:
818
            self.client.set_account_quota(qu)
819
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
820

    
821
    def test_set_account_versioning(self):
822
        vrs = 'n3wV3r51on1ngTyp3'
823
        with patch.object(PC, 'account_post', return_value=FR()) as post:
824
            self.client.set_account_versioning(vrs)
825
            self.assertEqual(
826
                post.mock_calls[-1],
827
                call(update=True, versioning=vrs))
828

    
829
    def test_del_container(self):
830
        kwarg_list = [
831
            dict(delimiter=None, until=None),
832
            dict(delimiter='X', until='50m3d473')]
833
        with patch.object(PC, 'container_delete', return_value=FR()) as delete:
834
            for kwarg in kwarg_list:
835
                self.client.del_container(**kwarg)
836
                expected = dict(kwarg)
837
                expected['success'] = (204, 404, 409)
838
                self.assertEqual(delete.mock_calls[-1], call(**expected))
839
            for status_code in (404, 409):
840
                FR.status_code = status_code
841
                self.assertRaises(ClientError, self.client.del_container)
842

    
843
    @patch(
844
        'kamaki.clients.pithos.PithosClient.get_container_info',
845
        return_value=container_info)
846
    def test_get_container_versioning(self, GCI):
847
        key = 'x-container-policy-versioning'
848
        cont = 'c0n7-417'
849
        bu_cnt = self.client.container
850
        for container in (None, cont):
851
            r = self.client.get_container_versioning(container=container)
852
            self.assertEqual(r[key], container_info[key])
853
            self.assertEqual(GCI.mock_calls[-1], call())
854
            self.assertEqual(bu_cnt, self.client.container)
855

    
856
    @patch(
857
        'kamaki.clients.pithos.PithosClient.get_container_info',
858
        return_value=container_info)
859
    def test_get_container_quota(self, GCI):
860
        key = 'x-container-policy-quota'
861
        cont = 'c0n7-417'
862
        bu_cnt = self.client.container
863
        for container in (None, cont):
864
            r = self.client.get_container_quota(container=container)
865
            self.assertEqual(r[key], container_info[key])
866
            self.assertEqual(GCI.mock_calls[-1], call())
867
            self.assertEqual(bu_cnt, self.client.container)
868

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

    
885
    def test_get_container_object_meta(self):
886
        somedate = '50m3d473'
887
        key = 'x-container-object-meta'
888
        metaval = '50m3m374v41'
889
        container_plus = dict(container_info)
890
        container_plus[key] = metaval
891
        for ret in (
892
                (container_info, {key: ''}),
893
                (container_plus, {key: metaval})):
894
            with patch.object(
895
                    PC,
896
                    'get_container_info',
897
                    return_value=ret[0]) as gci:
898
                for until in (None, somedate):
899
                    r = self.client.get_container_object_meta(until=until)
900
                    self.assertEqual(r, ret[1])
901
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
902

    
903
    def test_set_container_meta(self):
904
        metas = dict(k1='v1', k2='v2', k3='v3')
905
        with patch.object(PC, 'container_post', return_value=FR()) as post:
906
            self.client.set_container_meta(metas)
907
            self.assertEqual(
908
                post.mock_calls[-1],
909
                call(update=True, metadata=metas))
910

    
911
    def test_del_container_meta(self):
912
        with patch.object(PC, 'container_post', return_value=FR()) as ap:
913
            self.client.del_container_meta('somekey')
914
            expected = [call(update=True, metadata={'somekey': ''})]
915
            self.assertEqual(ap.mock_calls, expected)
916

    
917
    def test_set_container_quota(self):
918
        qu = 1024
919
        with patch.object(PC, 'container_post', return_value=FR()) as post:
920
            self.client.set_container_quota(qu)
921
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
922

    
923
    def test_set_container_versioning(self):
924
        vrs = 'n3wV3r51on1ngTyp3'
925
        with patch.object(PC, 'container_post', return_value=FR()) as post:
926
            self.client.set_container_versioning(vrs)
927
            self.assertEqual(
928
                post.mock_calls[-1],
929
                call(update=True, versioning=vrs))
930

    
931
    def test_del_object(self):
932
        kwargs = [
933
            dict(delimiter=None, until=None),
934
            dict(delimiter='X', until='50m3d473')]
935
        with patch.object(PC, 'object_delete', return_value=FR()) as delete:
936
            for kwarg in kwargs:
937
                self.client.del_object(obj, **kwarg)
938
                self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
939

    
940
    def test_set_object_meta(self):
941
        metas = dict(k1='v1', k2='v2', k3='v3')
942
        with patch.object(PC, 'object_post', return_value=FR()) as post:
943
            self.assertRaises(
944
                AssertionError,
945
                self.client.set_object_meta,
946
                obj, 'Non dict arg')
947
            self.client.set_object_meta(obj, metas)
948
            self.assertEqual(
949
                post.mock_calls[-1],
950
                call(obj, update=True, metadata=metas))