Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / test.py @ 52228b49

History | View | Annotate | Download (18.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.storage import StorageClient as SC
41
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
42

    
43
client_pkg = 'kamaki.clients.Client'
44
storage_pkg = 'kamaki.clients.storage.StorageClient'
45

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

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

    
144

    
145
class FR(object):
146
    """FR stands for Fake Response"""
147
    json = dict()
148
    headers = dict()
149
    content = json
150
    status = None
151
    status_code = 200
152

    
153
    def release(self):
154
        pass
155

    
156

    
157
class Storage(TestCase):
158

    
159
    files = []
160

    
161
    def _create_temp_file(self, num_of_blocks):
162
        self.files.append(NamedTemporaryFile())
163
        tmpFile = self.files[-1]
164
        file_size = num_of_blocks * 4 * 1024 * 1024
165
        print('\n\tCreate tmp file')
166
        tmpFile.write(urandom(file_size))
167
        tmpFile.flush()
168
        tmpFile.seek(0)
169
        print('\t\tDone')
170
        return tmpFile
171

    
172
    def assert_dicts_are_equal(self, d1, d2):
173
        for k, v in d1.items():
174
            self.assertTrue(k in d2)
175
            if isinstance(v, dict):
176
                self.assert_dicts_are_equal(v, d2[k])
177
            else:
178
                self.assertEqual(unicode(v), unicode(d2[k]))
179

    
180
    def setUp(self):
181
        self.url = 'https://www.example.com/pithos'
182
        self.token = 'p17h0570k3n'
183
        self.client = SC(self.url, self.token)
184
        self.client.account = user_id
185
        self.client.container = 'c0nt@1n3r_i'
186

    
187
    def tearDown(self):
188
        FR.headers = dict()
189
        FR.status_code = 200
190
        FR.json = dict()
191
        FR.content = FR.json
192
        for f in self.files:
193
            f.close()
194

    
195
    #  Pithos+ methods that extend storage API
196

    
197
    @patch('%s.head' % client_pkg, return_value=FR())
198
    def test_get_account_info(self, head):
199
        FR.headers = account_info
200
        r = self.client.get_account_info()
201
        self.assert_dicts_are_equal(account_info, r)
202
        self.assertEqual(
203
            head.mock_calls[-1],
204
            call('/%s' % self.client.account, success=(204, 401)))
205
        FR.status_code = 401
206
        self.assertRaises(ClientError, self.client.get_account_info)
207

    
208
    @patch('%s.post' % storage_pkg, return_value=FR())
209
    @patch('%s.set_header' % storage_pkg)
210
    def test_replace_account_meta(self, SH, post):
211
        metas = dict(k1='v1', k2='v2', k3='v3')
212
        self.client.replace_account_meta(metas)
213
        prfx = 'X-Account-Meta-'
214
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
215
        self.assertEqual(SH.mock_calls, expected)
216
        self.assertEqual(
217
            post.mock_calls[-1],
218
            call('/%s' % self.client.account, success=202))
219

    
220
    @patch('%s.post' % storage_pkg, return_value=FR())
221
    @patch('%s.get_account_info' % storage_pkg, return_value=account_info)
222
    def test_del_account_meta(self, GAI, post):
223
        prfx = 'x-account-meta-'
224
        keys = [k[len(prfx):] for k in account_info if k.startswith(prfx)]
225
        for key in keys:
226
            self.client.del_account_meta(key)
227
            self.assertEqual(
228
                post.mock_calls[-1],
229
                call('/%s' % self.client.account, success=202))
230
        self.assertEqual(len(keys), len(post.mock_calls))
231
        self.assertRaises(ClientError, self.client.del_account_meta, 'k4')
232

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

    
242
    @patch('%s.head' % storage_pkg, return_value=FR())
243
    def test_get_container_info(self, head):
244
        FR.headers = container_info
245
        cont = self.client.container
246
        r = self.client.get_container_info(cont)
247
        self.assert_dicts_are_equal(r, container_info)
248
        path = '/%s/%s' % (self.client.account, cont)
249
        self.assertEqual(head.mock_calls[-1], call(path, success=(204, 404)))
250
        FR.status_code = 404
251
        self.assertRaises(ClientError, self.client.get_container_info, cont)
252

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

    
264
    @patch('%s.get' % storage_pkg, return_value=FR())
265
    @patch('%s.set_param' % storage_pkg)
266
    def test_list_containers(self, SP, get):
267
        FR.json = container_list
268
        r = self.client.list_containers()
269
        for i in range(len(r)):
270
            self.assert_dicts_are_equal(r[i], container_list[i])
271
        self.assertEqual(SP.mock_calls[-1], call('format', 'json'))
272
        self.assertEqual(
273
            get.mock_calls[-1],
274
            call('/%s' % self.client.account, success=(200., 204)))
275

    
276
    @patch('%s.put' % storage_pkg, return_value=FR())
277
    def test_upload_object(self, put):
278
        (acc, cont) = (self.client.account, self.client.container)
279
        num_of_blocks = 4
280
        tmpFile = self._create_temp_file(num_of_blocks)
281
        tmpFile.seek(0, 2)
282
        sizes = [None, (tmpFile.tell() / num_of_blocks) / 2]
283
        for size in sizes:
284
            tmpFile.seek(0)
285
            self.client.upload_object(obj, tmpFile, size)
286
            tmpFile.seek(0)
287
            self.assertEqual(put.mock_calls[-1], call(
288
                '/%s/%s/%s' % (acc, cont, obj),
289
                data=tmpFile.read(size) if size else tmpFile.read(),
290
                success=201))
291

    
292
    @patch('%s.put' % storage_pkg, return_value=FR())
293
    @patch('%s.set_header' % storage_pkg)
294
    def test_create_object(self, SH, put):
295
        cont = self.client.container
296
        ctype = 'c0n73n7/typ3'
297
        exp_shd = [
298
            call('Content-Type', 'application/octet-stream'),
299
            call('Content-length', '0'),
300
            call('Content-Type', ctype), call('Content-length', '42')]
301
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
302
        self.client.create_object(obj)
303
        self.client.create_object(obj, content_type=ctype, content_length=42)
304
        self.assertEqual(SH.mock_calls, exp_shd)
305
        self.assertEqual(put.mock_calls, exp_put)
306

    
307
    """
308
    @patch('%s.put' % pithos_pkg, return_value=FR())
309
    @patch('%s.set_header' % client_pkg)
310
    def test_create_directory(self, SH, put):
311
        cont = self.client.container
312
        exp_shd = [
313
            call('Content-Type', 'application/directory'),
314
            call('Content-length', '0')]
315
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
316
        self.client.create_directory(obj)
317
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
318
        self.assertEqual(put.mock_calls, exp_put)
319

320
    def test_get_object_info(self):
321
        FR.headers = object_info
322
        version = 'v3r510n'
323
        with patch.object(PC, 'object_head', return_value=FR()) as head:
324
            r = self.client.get_object_info(obj)
325
            self.assertEqual(r, object_info)
326
            r = self.client.get_object_info(obj, version=version)
327
            self.assertEqual(head.mock_calls, [
328
                call(obj, version=None),
329
                call(obj, version=version)])
330
        with patch.object(
331
                PC,
332
                'object_head',
333
                side_effect=ClientError('Obj not found', 404)):
334
            self.assertRaises(
335
                ClientError,
336
                self.client.get_object_info,
337
                obj, version=version)
338

339
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
340
    def test_get_object_meta(self, GOI):
341
        expected = dict()
342
        for k, v in object_info.items():
343
            expected[k] = v
344
        r = self.client.get_object_meta(obj)
345
        self.assert_dicts_are_equal(r, expected)
346

347
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
348
    def test_del_object_meta(self, post):
349
        metakey = '50m3m3t4k3y'
350
        self.client.del_object_meta(obj, metakey)
351
        expected = call(obj, update=True, metadata={metakey: ''})
352
        self.assertEqual(post.mock_calls[-1], expected)
353

354
    @patch('%s.post' % client_pkg, return_value=FR())
355
    @patch('%s.set_header' % client_pkg)
356
    def test_replace_object_meta(self, SH, post):
357
        metas = dict(k1='new1', k2='new2', k3='new3')
358
        cont = self.client.container
359
        self.client.replace_object_meta(metas)
360
        expected = call('/%s/%s' % (user_id, cont), success=202)
361
        self.assertEqual(post.mock_calls[-1], expected)
362
        prfx = 'X-Object-Meta-'
363
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
364
        self.assertEqual(PC.set_header.mock_calls, expected)
365

366
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
367
    def test_copy_object(self, put):
368
        src_cont = 'src-c0nt41n3r'
369
        src_obj = 'src-0bj'
370
        dst_cont = 'dst-c0nt41n3r'
371
        dst_obj = 'dst-0bj'
372
        expected = call(
373
            src_obj,
374
            content_length=0,
375
            source_account=None,
376
            success=201,
377
            copy_from='/%s/%s' % (src_cont, src_obj),
378
            delimiter=None,
379
            content_type=None,
380
            source_version=None,
381
            public=False)
382
        self.client.copy_object(src_cont, src_obj, dst_cont)
383
        self.assertEqual(put.mock_calls[-1], expected)
384
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
385
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
386
        kwargs = dict(
387
            source_version='src-v3r510n',
388
            source_account='src-4cc0un7',
389
            public=True,
390
            content_type='c0n73n7Typ3',
391
            delimiter='5')
392
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
393
        for k, v in kwargs.items():
394
            self.assertEqual(v, put.mock_calls[-1][2][k])
395

396
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
397
    def test_move_object(self, put):
398
        src_cont = 'src-c0nt41n3r'
399
        src_obj = 'src-0bj'
400
        dst_cont = 'dst-c0nt41n3r'
401
        dst_obj = 'dst-0bj'
402
        expected = call(
403
            src_obj,
404
            content_length=0,
405
            source_account=None,
406
            success=201,
407
            move_from='/%s/%s' % (src_cont, src_obj),
408
            delimiter=None,
409
            content_type=None,
410
            source_version=None,
411
            public=False)
412
        self.client.move_object(src_cont, src_obj, dst_cont)
413
        self.assertEqual(put.mock_calls[-1], expected)
414
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
415
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
416
        kwargs = dict(
417
            source_version='src-v3r510n',
418
            source_account='src-4cc0un7',
419
            public=True,
420
            content_type='c0n73n7Typ3',
421
            delimiter='5')
422
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
423
        for k, v in kwargs.items():
424
            self.assertEqual(v, put.mock_calls[-1][2][k])
425

426
    @patch('%s.delete' % client_pkg, return_value=FR())
427
    def test_delete_object(self, delete):
428
        cont = self.client.container
429
        self.client.delete_object(obj)
430
        self.assertEqual(
431
            delete.mock_calls[-1],
432
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
433
        FR.status_code = 404
434
        self.assertRaises(ClientError, self.client.delete_object, obj)
435

436
    @patch('%s.get' % client_pkg, return_value=FR())
437
    @patch('%s.set_param' % client_pkg)
438
    def test_list_objects(self, SP, get):
439
        FR.json = object_list
440
        acc = self.client.account
441
        cont = self.client.container
442
        SP = PC.set_param
443
        r = self.client.list_objects()
444
        for i in range(len(r)):
445
            self.assert_dicts_are_equal(r[i], object_list[i])
446
        self.assertEqual(get.mock_calls, [
447
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
448
        self.assertEqual(SP.mock_calls, [call('format', 'json')])
449
        FR.status_code = 304
450
        self.assertEqual(self.client.list_objects(), [])
451
        FR.status_code = 404
452
        self.assertRaises(ClientError, self.client.list_objects)
453

454
    @patch('%s.get' % client_pkg, return_value=FR())
455
    @patch('%s.set_param' % client_pkg)
456
    def test_list_objects_in_path(self, SP, get):
457
        FR.json = object_list
458
        path = '/some/awsome/path'
459
        acc = self.client.account
460
        cont = self.client.container
461
        SP = PC.set_param
462
        self.client.list_objects_in_path(path)
463
        self.assertEqual(get.mock_calls, [
464
            call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
465
        self.assertEqual(SP.mock_calls, [
466
            call('format', 'json'), call('path', path)])
467
        FR.status_code = 404
468
        self.assertRaises(ClientError, self.client.list_objects)
469
    """
470

    
471
if __name__ == '__main__':
472
    from sys import argv
473
    from kamaki.clients.test import runTestCase
474
    runTestCase(Storage, 'Storage Client', argv[1:])