Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test / pithos.py @ 39c737d4

History | View | Annotate | Download (20.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, Mock
36

    
37
from kamaki.clients import ClientError
38
from kamaki.clients.pithos import PithosClient as PC
39
from kamaki.clients.astakos import AstakosClient
40
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
41

    
42
user_id = 'ac0un7-1d-5tr1ng'
43

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

    
121

    
122
class Pithos(TestCase):
123

    
124
    class FR(object):
125
        """FR stands for Fake Response"""
126
        json = dict()
127
        headers = dict()
128
        content = json
129
        status = None
130
        status_code = 200
131

    
132
        def release(self):
133
            pass
134

    
135
    files = []
136

    
137
    def assert_dicts_are_equal(self, d1, d2):
138
        for k, v in d1.items():
139
            self.assertTrue(k in d2)
140
            if isinstance(v, dict):
141
                self.assert_dicts_are_equal(v, d2[k])
142
            else:
143
                self.assertEqual(unicode(v), unicode(d2[k]))
144

    
145
    def setUp(self):
146
        self.url = 'https://www.example.com/pithos'
147
        self.token = 'p17h0570k3n'
148
        self.client = PC(self.url, self.token)
149
        self.client.account = user_id
150
        self.client.container = 'c0nt@1n3r_i'
151

    
152
    def tearDown(self):
153
        self.FR.headers = dict()
154
        self.FR.status_code = 200
155
        self.FR.json = dict()
156
        for f in self.files:
157
            f.close()
158

    
159
    def test_get_account_info(self):
160
        self.FR.headers = account_info
161
        self.FR.status_code = 204
162
        with patch.object(C, 'perform_request', return_value=self.FR()):
163
            r = self.client.get_account_info()
164
            self.assertEqual(self.client.http_client.url, self.url)
165
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
166
            self.assert_dicts_are_equal(r, account_info)
167
            PC.set_param = Mock()
168
            untils = ['date 1', 'date 2', 'date 3']
169
            for unt in untils:
170
                r = self.client.get_account_info(until=unt)
171
                self.assert_dicts_are_equal(r, account_info)
172
            for i in range(len(untils)):
173
                self.assertEqual(
174
                    PC.set_param.mock_calls[i],
175
                    call('until', untils[i], iff=untils[i]))
176
            self.FR.status_code = 401
177
            self.assertRaises(ClientError, self.client.get_account_info)
178

    
179
    def test_replace_account_meta(self):
180
        self.FR.status_code = 202
181
        metas = dict(k1='v1', k2='v2', k3='v3')
182
        PC.set_header = Mock()
183
        with patch.object(C, 'perform_request', return_value=self.FR()):
184
            self.client.replace_account_meta(metas)
185
            self.assertEqual(self.client.http_client.url, self.url)
186
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
187
            prfx = 'X-Account-Meta-'
188
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
189
            self.assertEqual(PC.set_header.mock_calls, expected)
190

    
191
    def test_del_account_meta(self):
192
        keys = ['k1', 'k2', 'k3']
193
        with patch.object(PC, 'account_post', return_value=self.FR()) as ap:
194
            expected = []
195
            for key in keys:
196
                self.client.del_account_meta(key)
197
                expected.append(call(update=True, metadata={key: ''}))
198
            self.assertEqual(ap.mock_calls, expected)
199

    
200
    def test_create_container(self):
201
        self.FR.status_code = 201
202
        with patch.object(PC, 'put', return_value=self.FR()) as put:
203
            cont = 's0m3c0n731n3r'
204
            self.client.create_container(cont)
205
            expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
206
            self.assertEqual(put.mock_calls, expected)
207
            self.FR.status_code = 202
208
            self.assertRaises(ClientError, self.client.create_container, cont)
209

    
210
    def test_get_container_info(self):
211
        self.FR.headers = container_info
212
        with patch.object(PC, 'container_head', return_value=self.FR()) as ch:
213
            r = self.client.get_container_info()
214
            self.assert_dicts_are_equal(r, container_info)
215
            u = 'some date'
216
            r = self.client.get_container_info(until=u)
217
            self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
218

    
219
    def test_delete_container(self):
220
        self.FR.status_code = 204
221
        with patch.object(PC, 'delete', return_value=self.FR()) as delete:
222
            cont = 's0m3c0n731n3r'
223
            self.client.delete_container(cont)
224
            self.FR.status_code = 404
225
            self.assertRaises(ClientError, self.client.delete_container, cont)
226
            self.FR.status_code = 409
227
            self.assertRaises(ClientError, self.client.delete_container, cont)
228
            acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
229
            self.assertEqual(delete.mock_calls, [acall] * 3)
230

    
231
    def test_list_containers(self):
232
        self.FR.json = container_list
233
        with patch.object(PC, 'account_get', return_value=self.FR()):
234
            r = self.client.list_containers()
235
            for i in range(len(r)):
236
                self.assert_dicts_are_equal(r[i], container_list[i])
237

    
238
    def test_upload_object(self):
239
        PC.get_container_info = Mock(return_value=container_info)
240
        PC.container_post = Mock(return_value=self.FR())
241
        PC.object_put = Mock(return_value=self.FR())
242
        from tempfile import NamedTemporaryFile
243
        from os import urandom
244
        self.files.append(NamedTemporaryFile())
245
        tmpFile = self.files[-1]
246
        num_of_blocks = 8
247
        file_size = num_of_blocks * 4 * 1024 * 1024
248
        print('\n\tCreate tmp file')
249
        tmpFile.write(urandom(file_size))
250
        tmpFile.flush()
251
        tmpFile.seek(0)
252
        print('\t\tDone')
253
        obj = 'objectName'
254

    
255
        # No special args
256
        self.client.upload_object(obj, tmpFile)
257
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
258
        [call1, call2] = PC.object_put.mock_calls
259

    
260
        (args1, kwargs1) = call1[1:3]
261
        (args2, kwargs2) = call2[1:3]
262
        self.assertEqual(args1, (obj,))
263
        expected1 = dict(
264
            hashmap=True,
265
            success=(201, 409),
266
            format='json',
267
            json=dict(
268
                hashes=['s0m3h@5h'] * num_of_blocks,
269
                bytes=file_size),
270
            etag=None,
271
            content_encoding=None,
272
            content_type='application/octet-stream',
273
            content_disposition=None,
274
            public=None,
275
            permissions=None)
276
        for k, v in expected1.items():
277
            if k == 'json':
278
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
279
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
280
            else:
281
                self.assertEqual(v, kwargs1[k])
282

    
283
        (args2, kwargs2) = call2[1:3]
284
        self.assertEqual(args2, (obj,))
285
        expected2 = dict(
286
            json=dict(
287
                hashes=['s0m3h@5h'] * num_of_blocks,
288
                bytes=file_size),
289
            content_type='application/octet-stream',
290
            hashmap=True,
291
            success=201,
292
            format='json')
293
        for k, v in expected2.items():
294
            if k == 'json':
295
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
296
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
297
            else:
298
                self.assertEqual(v, kwargs2[k])
299

    
300
        OP = PC.object_put
301
        mock_offset = 2
302

    
303
        #  With progress bars
304
        try:
305
            from progress.bar import ShadyBar
306
            blck_bar = ShadyBar('Mock blck calc.')
307
            upld_bar = ShadyBar('Mock uplds')
308
        except ImportError:
309
            blck_bar = None
310
            upld_bar = None
311

    
312
        if blck_bar and upld_bar:
313

    
314
            def blck_gen(n):
315
                for i in blck_bar.iter(range(n)):
316
                    yield
317
                yield
318

    
319
            def upld_gen(n):
320
                for i in upld_bar.iter(range(n)):
321
                    yield
322
                yield
323

    
324
            tmpFile.seek(0)
325
            self.client.upload_object(
326
                obj, tmpFile,
327
                hash_cb=blck_gen, upload_cb=upld_gen)
328

    
329
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
330
                self.assertEqual(OP.mock_calls[i], c)
331

    
332
        #  With content-type
333
        tmpFile.seek(0)
334
        ctype = 'video/mpeg'
335
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
336
        self.client.upload_object(obj, tmpFile,
337
            content_type=ctype, sharing=sharing)
338
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
339
        self.assert_dicts_are_equal(
340
            OP.mock_calls[-2][2]['permissions'],
341
            sharing)
342

    
343
        # With other args
344
        tmpFile.seek(0)
345
        kwargs = dict(
346
            etag='s0m3E74g',
347
            content_type=ctype,
348
            content_disposition=ctype + 'd15p051710n',
349
            public=True,
350
            content_encoding='802.11')
351
        self.client.upload_object(obj, tmpFile, **kwargs)
352
        for arg, val in kwargs.items():
353
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
354

    
355
    def test_create_object(self):
356
        PC.set_header = Mock()
357
        obj = 'r4nd0m0bj3c7'
358
        cont = self.client.container
359
        ctype = 'c0n73n7/typ3'
360
        exp_shd = [
361
            call('Content-Type', 'application/octet-stream'),
362
            call('Content-length', '0'),
363
            call('Content-Type', ctype), call('Content-length', '42')]
364
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
365
        with patch.object(PC, 'put', return_value=self.FR()) as put:
366
            self.client.create_object(obj)
367
            self.client.create_object(obj,
368
                content_type=ctype, content_length=42)
369
            self.assertEqual(PC.set_header.mock_calls, exp_shd)
370
            self.assertEqual(put.mock_calls, exp_put)
371

    
372
    def test_create_directory(self):
373
        PC.set_header = Mock()
374
        obj = 'r4nd0m0bj3c7'
375
        cont = self.client.container
376
        exp_shd = [
377
            call('Content-Type', 'application/directory'),
378
            call('Content-length', '0')]
379
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
380
        with patch.object(PC, 'put', return_value=self.FR()) as put:
381
            self.client.create_directory(obj)
382
            self.assertEqual(PC.set_header.mock_calls, exp_shd)
383
            self.assertEqual(put.mock_calls, exp_put)
384

    
385
    def test_get_object_info(self):
386
        self.FR.headers = object_info
387
        obj = 'r4nd0m0bj3c7'
388
        version = 'v3r510n'
389
        with patch.object(PC, 'object_head', return_value=self.FR()) as head:
390
            r = self.client.get_object_info(obj)
391
            self.assertEqual(r, object_info)
392
            r = self.client.get_object_info(obj, version=version)
393
            self.assertEqual(head.mock_calls, [
394
                call(obj, version=None),
395
                call(obj, version=version)])
396
        with patch.object(
397
                PC,
398
                'object_head',
399
                side_effect=ClientError('Obj not found', 404)):
400
            self.assertRaises(
401
                ClientError,
402
                self.client.get_object_info,
403
                obj, version=version)
404

    
405
    def test_get_object_meta(self):
406
        obj = 'r4nd0m0bj3c7'
407
        expected = dict()
408
        for k, v in object_info.items():
409
            expected[k] = v
410
        with patch.object(
411
                PC,
412
                'get_object_info',
413
                return_value=object_info):
414
            r = self.client.get_object_meta(obj)
415
            self.assert_dicts_are_equal(r, expected)
416

    
417
    def test_del_object_meta(self):
418
        obj = 'r4nd0m0bj3c7'
419
        metakey = '50m3m3t4k3y'
420
        with patch.object(PC, 'object_post', return_value=self.FR()) as post:
421
            self.client.del_object_meta(obj, metakey)
422
            self.assertEqual(
423
                post.mock_calls,
424
                [call(obj, update=True, metadata={metakey: ''})])
425

    
426
    def test_replace_object_meta(self):
427
        PC.set_header = Mock()
428
        metas = dict(k1='new1', k2='new2', k3='new3')
429
        cont = self.client.container
430
        with patch.object(PC, 'post', return_value=self.FR()) as post:
431
            self.client.replace_object_meta(metas)
432
            self.assertEqual(post.mock_calls, [
433
                call('/%s/%s' % (user_id, cont),
434
                success=202)])
435
            prfx = 'X-Object-Meta-'
436
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
437
            self.assertEqual(PC.set_header.mock_calls, expected)
438

    
439
    def test_copy_object(self):
440
        src_cont = 'src-c0nt41n3r'
441
        src_obj = 'src-0bj'
442
        dst_cont = 'dst-c0nt41n3r'
443
        dst_obj = 'dst-0bj'
444
        expected = call(
445
            src_obj,
446
            content_length=0,
447
            source_account=None,
448
            success=201,
449
            copy_from='/%s/%s' % (src_cont, src_obj),
450
            delimiter=None,
451
            content_type=None,
452
            source_version=None,
453
            public=False)
454
        with patch.object(PC, 'object_put', return_value=self.FR()) as put:
455
            self.client.copy_object(src_cont, src_obj, dst_cont)
456
            self.assertEqual(put.mock_calls[-1], expected)
457
            self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
458
            self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
459
            kwargs = dict(
460
                source_version='src-v3r510n',
461
                source_account='src-4cc0un7',
462
                public=True,
463
                content_type='c0n73n7Typ3',
464
                delimiter='5')
465
            self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
466
            for k, v in kwargs.items():
467
                self.assertEqual(v, put.mock_calls[-1][2][k])
468

    
469
    def test_move_object(self):
470
        src_cont = 'src-c0nt41n3r'
471
        src_obj = 'src-0bj'
472
        dst_cont = 'dst-c0nt41n3r'
473
        dst_obj = 'dst-0bj'
474
        expected = call(
475
            src_obj,
476
            content_length=0,
477
            source_account=None,
478
            success=201,
479
            move_from='/%s/%s' % (src_cont, src_obj),
480
            delimiter=None,
481
            content_type=None,
482
            source_version=None,
483
            public=False)
484
        with patch.object(PC, 'object_put', return_value=self.FR()) as put:
485
            self.client.move_object(src_cont, src_obj, dst_cont)
486
            self.assertEqual(put.mock_calls[-1], expected)
487
            self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
488
            self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
489
            kwargs = dict(
490
                source_version='src-v3r510n',
491
                source_account='src-4cc0un7',
492
                public=True,
493
                content_type='c0n73n7Typ3',
494
                delimiter='5')
495
            self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
496
            for k, v in kwargs.items():
497
                self.assertEqual(v, put.mock_calls[-1][2][k])
498

    
499
    def test_delete_object(self):
500
        obj = 's0m30bj3c7'
501
        cont = self.client.container
502
        with patch.object(PC, 'delete', return_value=self.FR()) as delete:
503
            self.client.delete_object(obj)
504
            self.assertEqual(delete.mock_calls, [
505
                call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404))])
506
            self.FR.status_code = 404
507
            self.assertRaises(ClientError, self.client.delete_object, obj)
508

    
509
    def test_list_objects(self):
510
        self.FR.json = object_list
511
        acc = self.client.account
512
        cont = self.client.container
513
        PC.set_param = Mock()
514
        SP = PC.set_param
515
        with patch.object(PC, 'get', return_value=self.FR()) as get:
516
            r = self.client.list_objects()
517
            for i in range(len(r)):
518
                self.assert_dicts_are_equal(r[i], object_list[i])
519
            self.assertEqual(get.mock_calls, [
520
                call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
521
            self.assertEqual(SP.mock_calls, [call('format', 'json')])
522
            self.FR.status_code = 304
523
            self.assertEqual(self.client.list_objects(), [])
524
            self.FR.status_code = 404
525
            self.assertRaises(ClientError, self.client.list_objects)