Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / test.py @ aa9c7402

History | View | Annotate | Download (18.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.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
    @patch('%s.put' % storage_pkg, return_value=FR())
308
    @patch('%s.set_header' % client_pkg)
309
    def test_create_directory(self, SH, put):
310
        cont = self.client.container
311
        exp_shd = [
312
            call('Content-Type', 'application/directory'),
313
            call('Content-length', '0')]
314
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
315
        self.client.create_directory(obj)
316
        self.assertEqual(SH.mock_calls, exp_shd)
317
        self.assertEqual(put.mock_calls, exp_put)
318

    
319
    @patch('%s.head' % storage_pkg, return_value=FR())
320
    def test_get_object_info(self, head):
321
        FR.headers = object_info
322
        path = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
323
        r = self.client.get_object_info(obj)
324
        self.assertEqual(r, object_info)
325
        self.assertEqual(head.mock_calls[-1], call(path, success=200))
326

    
327
    """
328
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
329
    def test_get_object_meta(self, GOI):
330
        expected = dict()
331
        for k, v in object_info.items():
332
            expected[k] = v
333
        r = self.client.get_object_meta(obj)
334
        self.assert_dicts_are_equal(r, expected)
335

336
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
337
    def test_del_object_meta(self, post):
338
        metakey = '50m3m3t4k3y'
339
        self.client.del_object_meta(obj, metakey)
340
        expected = call(obj, update=True, metadata={metakey: ''})
341
        self.assertEqual(post.mock_calls[-1], expected)
342

343
    @patch('%s.post' % client_pkg, return_value=FR())
344
    @patch('%s.set_header' % client_pkg)
345
    def test_replace_object_meta(self, SH, post):
346
        metas = dict(k1='new1', k2='new2', k3='new3')
347
        cont = self.client.container
348
        self.client.replace_object_meta(metas)
349
        expected = call('/%s/%s' % (user_id, cont), success=202)
350
        self.assertEqual(post.mock_calls[-1], expected)
351
        prfx = 'X-Object-Meta-'
352
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
353
        self.assertEqual(PC.set_header.mock_calls, expected)
354

355
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
356
    def test_copy_object(self, put):
357
        src_cont = 'src-c0nt41n3r'
358
        src_obj = 'src-0bj'
359
        dst_cont = 'dst-c0nt41n3r'
360
        dst_obj = 'dst-0bj'
361
        expected = call(
362
            src_obj,
363
            content_length=0,
364
            source_account=None,
365
            success=201,
366
            copy_from='/%s/%s' % (src_cont, src_obj),
367
            delimiter=None,
368
            content_type=None,
369
            source_version=None,
370
            public=False)
371
        self.client.copy_object(src_cont, src_obj, dst_cont)
372
        self.assertEqual(put.mock_calls[-1], expected)
373
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
374
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
375
        kwargs = dict(
376
            source_version='src-v3r510n',
377
            source_account='src-4cc0un7',
378
            public=True,
379
            content_type='c0n73n7Typ3',
380
            delimiter='5')
381
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
382
        for k, v in kwargs.items():
383
            self.assertEqual(v, put.mock_calls[-1][2][k])
384

385
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
386
    def test_move_object(self, put):
387
        src_cont = 'src-c0nt41n3r'
388
        src_obj = 'src-0bj'
389
        dst_cont = 'dst-c0nt41n3r'
390
        dst_obj = 'dst-0bj'
391
        expected = call(
392
            src_obj,
393
            content_length=0,
394
            source_account=None,
395
            success=201,
396
            move_from='/%s/%s' % (src_cont, src_obj),
397
            delimiter=None,
398
            content_type=None,
399
            source_version=None,
400
            public=False)
401
        self.client.move_object(src_cont, src_obj, dst_cont)
402
        self.assertEqual(put.mock_calls[-1], expected)
403
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
404
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
405
        kwargs = dict(
406
            source_version='src-v3r510n',
407
            source_account='src-4cc0un7',
408
            public=True,
409
            content_type='c0n73n7Typ3',
410
            delimiter='5')
411
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
412
        for k, v in kwargs.items():
413
            self.assertEqual(v, put.mock_calls[-1][2][k])
414

415
    @patch('%s.delete' % client_pkg, return_value=FR())
416
    def test_delete_object(self, delete):
417
        cont = self.client.container
418
        self.client.delete_object(obj)
419
        self.assertEqual(
420
            delete.mock_calls[-1],
421
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
422
        FR.status_code = 404
423
        self.assertRaises(ClientError, self.client.delete_object, obj)
424

425
    @patch('%s.get' % client_pkg, return_value=FR())
426
    @patch('%s.set_param' % client_pkg)
427
    def test_list_objects(self, SP, get):
428
        FR.json = object_list
429
        acc = self.client.account
430
        cont = self.client.container
431
        SP = PC.set_param
432
        r = self.client.list_objects()
433
        for i in range(len(r)):
434
            self.assert_dicts_are_equal(r[i], object_list[i])
435
        self.assertEqual(get.mock_calls, [
436
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
437
        self.assertEqual(SP.mock_calls, [call('format', 'json')])
438
        FR.status_code = 304
439
        self.assertEqual(self.client.list_objects(), [])
440
        FR.status_code = 404
441
        self.assertRaises(ClientError, self.client.list_objects)
442

443
    @patch('%s.get' % client_pkg, return_value=FR())
444
    @patch('%s.set_param' % client_pkg)
445
    def test_list_objects_in_path(self, SP, get):
446
        FR.json = object_list
447
        path = '/some/awsome/path'
448
        acc = self.client.account
449
        cont = self.client.container
450
        SP = PC.set_param
451
        self.client.list_objects_in_path(path)
452
        self.assertEqual(get.mock_calls, [
453
            call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
454
        self.assertEqual(SP.mock_calls, [
455
            call('format', 'json'), call('path', path)])
456
        FR.status_code = 404
457
        self.assertRaises(ClientError, self.client.list_objects)
458
    """
459

    
460
if __name__ == '__main__':
461
    from sys import argv
462
    from kamaki.clients.test import runTestCase
463
    runTestCase(Storage, 'Storage Client', argv[1:])