Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.6 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

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

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

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

    
128

    
129
class FR(object):
130
    """FR stands for Fake Response"""
131
    json = dict()
132
    headers = dict()
133
    content = json
134
    status = None
135
    status_code = 200
136

    
137
    def release(self):
138
        pass
139

    
140

    
141
class Storage(TestCase):
142

    
143
    files = []
144

    
145
    def _create_temp_file(self, num_of_blocks):
146
        self.files.append(NamedTemporaryFile())
147
        tmpFile = self.files[-1]
148
        file_size = num_of_blocks * 4 * 1024 * 1024
149
        print('\n\tCreate tmp file')
150
        tmpFile.write(urandom(file_size))
151
        tmpFile.flush()
152
        tmpFile.seek(0)
153
        print('\t\tDone')
154
        return tmpFile
155

    
156
    def assert_dicts_are_equal(self, d1, d2):
157
        for k, v in d1.items():
158
            self.assertTrue(k in d2)
159
            if isinstance(v, dict):
160
                self.assert_dicts_are_equal(v, d2[k])
161
            else:
162
                self.assertEqual(unicode(v), unicode(d2[k]))
163

    
164
    def setUp(self):
165
        self.url = 'https://www.example.com/pithos'
166
        self.token = 'p17h0570k3n'
167
        self.client = SC(self.url, self.token)
168
        self.client.account = user_id
169
        self.client.container = 'c0nt@1n3r_i'
170

    
171
    def tearDown(self):
172
        FR.headers = dict()
173
        FR.status_code = 200
174
        FR.json = dict()
175
        FR.content = FR.json
176
        for f in self.files:
177
            f.close()
178

    
179
    #  Pithos+ methods that extend storage API
180

    
181
    @patch('%s.head' % client_pkg, return_value=FR())
182
    def test_get_account_info(self, head):
183
        FR.headers = account_info
184
        r = self.client.get_account_info()
185
        self.assert_dicts_are_equal(account_info, r)
186
        self.assertEqual(
187
            head.mock_calls[-1],
188
            call('/%s' % self.client.account, success=(204, 401)))
189
        FR.status_code = 401
190
        self.assertRaises(ClientError, self.client.get_account_info)
191

    
192
    @patch('%s.post' % storage_pkg, return_value=FR())
193
    @patch('%s.set_header' % storage_pkg)
194
    def test_replace_account_meta(self, SH, post):
195
        metas = dict(k1='v1', k2='v2', k3='v3')
196
        self.client.replace_account_meta(metas)
197
        prfx = 'X-Account-Meta-'
198
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
199
        self.assertEqual(SH.mock_calls, expected)
200
        self.assertEqual(
201
            post.mock_calls[-1],
202
            call('/%s' % self.client.account, success=202))
203

    
204
    @patch('%s.post' % storage_pkg, return_value=FR())
205
    @patch('%s.get_account_info' % storage_pkg, return_value=account_info)
206
    def test_del_account_meta(self, GAI, post):
207
        prfx = 'x-account-meta-'
208
        keys = [k[len(prfx):] for k in account_info if k.startswith(prfx)]
209
        for key in keys:
210
            self.client.del_account_meta(key)
211
            self.assertEqual(
212
                post.mock_calls[-1],
213
                call('/%s' % self.client.account, success=202))
214
        self.assertEqual(len(keys), len(post.mock_calls))
215
        self.assertRaises(ClientError, self.client.del_account_meta, 'k4')
216

    
217
    @patch('%s.put' % storage_pkg, return_value=FR())
218
    def test_create_container(self, put):
219
        cont = 's0m3c0n731n3r'
220
        self.client.create_container(cont)
221
        expected = call('/%s/%s' % (user_id, cont), success=(201, 202))
222
        self.assertEqual(put.mock_calls[-1], expected)
223
        FR.status_code = 202
224
        self.assertRaises(ClientError, self.client.create_container, cont)
225

    
226
    @patch('%s.head' % storage_pkg, return_value=FR())
227
    def test_get_container_info(self, head):
228
        FR.headers = container_info
229
        cont = self.client.container
230
        r = self.client.get_container_info(cont)
231
        self.assert_dicts_are_equal(r, container_info)
232
        path = '/%s/%s' % (self.client.account, cont)
233
        self.assertEqual(head.mock_calls[-1], call(path, success=(204, 404)))
234
        FR.status_code = 404
235
        self.assertRaises(ClientError, self.client.get_container_info, cont)
236

    
237
    @patch('%s.delete' % storage_pkg, return_value=FR())
238
    def test_delete_container(self, delete):
239
        FR.status_code = 204
240
        cont = 's0m3c0n731n3r'
241
        self.client.delete_container(cont)
242
        for err_code in (404, 409):
243
            FR.status_code = err_code
244
            self.assertRaises(ClientError, self.client.delete_container, cont)
245
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
246
        self.assertEqual(delete.mock_calls, [acall] * 3)
247

    
248
    @patch('%s.get' % storage_pkg, return_value=FR())
249
    @patch('%s.set_param' % storage_pkg)
250
    def test_list_containers(self, SP, get):
251
        FR.json = container_list
252
        r = self.client.list_containers()
253
        for i in range(len(r)):
254
            self.assert_dicts_are_equal(r[i], container_list[i])
255
        self.assertEqual(SP.mock_calls[-1], call('format', 'json'))
256
        self.assertEqual(
257
            get.mock_calls[-1],
258
            call('/%s' % self.client.account, success=(200., 204)))
259

    
260
    @patch('%s.put' % storage_pkg, return_value=FR())
261
    def test_upload_object(self, put):
262
        (acc, cont) = (self.client.account, self.client.container)
263
        num_of_blocks = 4
264
        tmpFile = self._create_temp_file(num_of_blocks)
265
        tmpFile.seek(0, 2)
266
        sizes = [None, (tmpFile.tell() / num_of_blocks) / 2]
267
        for size in sizes:
268
            tmpFile.seek(0)
269
            self.client.upload_object(obj, tmpFile, size)
270
            tmpFile.seek(0)
271
            self.assertEqual(put.mock_calls[-1], call(
272
                '/%s/%s/%s' % (acc, cont, obj),
273
                data=tmpFile.read(size) if size else tmpFile.read(),
274
                success=201))
275

    
276
    @patch('%s.put' % storage_pkg, return_value=FR())
277
    @patch('%s.set_header' % storage_pkg)
278
    def test_create_object(self, SH, put):
279
        cont = self.client.container
280
        ctype = 'c0n73n7/typ3'
281
        exp_shd = [
282
            call('Content-Type', 'application/octet-stream'),
283
            call('Content-length', '0'),
284
            call('Content-Type', ctype), call('Content-length', '42')]
285
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
286
        self.client.create_object(obj)
287
        self.client.create_object(obj, content_type=ctype, content_length=42)
288
        self.assertEqual(SH.mock_calls, exp_shd)
289
        self.assertEqual(put.mock_calls, exp_put)
290

    
291
    @patch('%s.put' % storage_pkg, return_value=FR())
292
    @patch('%s.set_header' % client_pkg)
293
    def test_create_directory(self, SH, put):
294
        cont = self.client.container
295
        exp_shd = [
296
            call('Content-Type', 'application/directory'),
297
            call('Content-length', '0')]
298
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
299
        self.client.create_directory(obj)
300
        self.assertEqual(SH.mock_calls, exp_shd)
301
        self.assertEqual(put.mock_calls, exp_put)
302

    
303
    @patch('%s.head' % storage_pkg, return_value=FR())
304
    def test_get_object_info(self, head):
305
        FR.headers = object_info
306
        path = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
307
        r = self.client.get_object_info(obj)
308
        self.assertEqual(r, object_info)
309
        self.assertEqual(head.mock_calls[-1], call(path, success=200))
310

    
311
    @patch('%s.get_object_info' % storage_pkg, return_value=object_info)
312
    def test_get_object_meta(self, GOI):
313
        r = self.client.get_object_meta(obj)
314
        prfx = 'x-object-meta-'
315
        for k in [k for k in object_info if k.startswith(prfx)]:
316
            self.assertEqual(r.pop(k[len(prfx):]), object_info[k])
317
        self.assertFalse(len(r))
318
        self.assertEqual(GOI.mock_calls[-1], call(obj))
319

    
320
    @patch('%s.post' % storage_pkg, return_value=FR())
321
    @patch('%s.set_header' % storage_pkg)
322
    def test_del_object_meta(self, SH, post):
323
        key = '50m3m3t4k3y'
324
        self.client.del_object_meta(obj, key)
325
        prfx = 'X-Object-Meta-'
326
        self.assertEqual(SH.mock_calls[-1], call('%s%s' % (prfx, key), ''))
327
        self.assertEqual(post.mock_calls[-1], call(
328
            '/%s/%s/%s' % (self.client.account, self.client.container, obj),
329
            success=202))
330

    
331
    @patch('%s.post' % client_pkg, return_value=FR())
332
    @patch('%s.set_header' % client_pkg)
333
    def test_replace_object_meta(self, SH, post):
334
        metas = dict(k1='new1', k2='new2', k3='new3')
335
        cont = self.client.container
336
        self.client.replace_object_meta(metas)
337
        expected = call('/%s/%s' % (user_id, cont), success=202)
338
        self.assertEqual(post.mock_calls[-1], expected)
339
        prfx = 'X-Object-Meta-'
340
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
341
        self.assertEqual(SH.mock_calls, expected)
342

    
343
    @patch('%s.put' % storage_pkg, return_value=FR())
344
    @patch('%s.set_header' % storage_pkg)
345
    def test_copy_object(self, SH, put):
346
        src_cont = 'src-c0nt41n3r'
347
        src_obj = 'src-0bj'
348
        dst_cont = 'dst-c0nt41n3r'
349
        for dst_obj in (None, 'dst-0bj'):
350
            dst_path = dst_obj or src_obj
351
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
352
            self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
353
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
354
            kwargs = {
355
                'X-Copy-From': '/%s/%s' % (src_cont, src_obj),
356
                'Content-Length': 0}
357
            self.assertEqual(
358
                SH.mock_calls[-2:],
359
                [call(k, v) for k, v in kwargs.items()])
360

    
361
    @patch('%s.put' % storage_pkg, return_value=FR())
362
    @patch('%s.set_header' % storage_pkg)
363
    def test_move_object(self, SH, put):
364
        src_cont = 'src-c0nt41n3r'
365
        src_obj = 'src-0bj'
366
        dst_cont = 'dst-c0nt41n3r'
367
        for dst_obj in (None, 'dst-0bj'):
368
            dst_path = dst_obj or src_obj
369
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
370
            self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
371
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
372
            kwargs = {
373
                'X-Move-From': '/%s/%s' % (src_cont, src_obj),
374
                'Content-Length': 0}
375
            self.assertEqual(
376
                SH.mock_calls[-2:],
377
                [call(k, v) for k, v in kwargs.items()])
378

    
379
    @patch('%s.delete' % storage_pkg, return_value=FR())
380
    def test_delete_object(self, delete):
381
        cont = self.client.container
382
        self.client.delete_object(obj)
383
        self.assertEqual(
384
            delete.mock_calls[-1],
385
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
386
        FR.status_code = 404
387
        self.assertRaises(ClientError, self.client.delete_object, obj)
388

    
389
    @patch('%s.get' % client_pkg, return_value=FR())
390
    @patch('%s.set_param' % client_pkg)
391
    def test_list_objects(self, SP, get):
392
        FR.json = object_list
393
        acc, cont = self.client.account, self.client.container
394
        r = self.client.list_objects()
395
        for i in range(len(r)):
396
            self.assert_dicts_are_equal(r[i], object_list[i])
397
        self.assertEqual(
398
            get.mock_calls[-1],
399
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404)))
400
        self.assertEqual(SP.mock_calls[-1], call('format', 'json'))
401
        FR.status_code = 304
402
        self.assertEqual(self.client.list_objects(), [])
403
        FR.status_code = 404
404
        self.assertRaises(ClientError, self.client.list_objects)
405

    
406
    @patch('%s.get' % client_pkg, return_value=FR())
407
    @patch('%s.set_param' % client_pkg)
408
    def test_list_objects_in_path(self, SP, get):
409
        FR.json = object_list
410
        path = '/some/awsome/path'
411
        acc, cont = self.client.account, self.client.container
412
        self.client.list_objects_in_path(path)
413
        self.assertEqual(
414
            get.mock_calls[-1],
415
            call('/%s/%s' % (acc, cont), success=(200, 204, 404)))
416
        self.assertEqual(SP.mock_calls, [
417
            call('format', 'json'), call('path', path)])
418
        FR.status_code = 404
419
        self.assertRaises(ClientError, self.client.list_objects)
420

    
421
if __name__ == '__main__':
422
    from sys import argv
423
    from kamaki.clients.test import runTestCase
424
    runTestCase(Storage, 'Storage Client', argv[1:])