691542a3f632622347e5e191cbd0bd0da546d00b
[kamaki] / kamaki / clients / test / pithos.py
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
98
99 class Pithos(TestCase):
100
101     class FR(object):
102         """FR stands for Fake Response"""
103         json = dict()
104         headers = dict()
105         content = json
106         status = None
107         status_code = 200
108
109         def release(self):
110             pass
111
112     files = []
113
114     def assert_dicts_are_equal(self, d1, d2):
115         for k, v in d1.items():
116             self.assertTrue(k in d2)
117             if isinstance(v, dict):
118                 self.assert_dicts_are_equal(v, d2[k])
119             else:
120                 self.assertEqual(unicode(v), unicode(d2[k]))
121
122     def setUp(self):
123         self.url = 'https://www.example.com/pithos'
124         self.token = 'p17h0570k3n'
125         self.client = PC(self.url, self.token)
126         self.client.account = user_id
127         self.client.container = 'c0nt@1n3r_i'
128
129     def tearDown(self):
130         self.FR.headers = dict()
131         self.FR.status_code = 200
132         self.FR.json = dict()
133         for f in self.files:
134             f.close()
135
136     def test_get_account_info(self):
137         self.FR.headers = account_info
138         self.FR.status_code = 204
139         with patch.object(C, 'perform_request', return_value=self.FR()):
140             r = self.client.get_account_info()
141             self.assertEqual(self.client.http_client.url, self.url)
142             self.assertEqual(self.client.http_client.path, '/%s' % user_id)
143             self.assert_dicts_are_equal(r, account_info)
144             PC.set_param = Mock()
145             untils = ['date 1', 'date 2', 'date 3']
146             for unt in untils:
147                 r = self.client.get_account_info(until=unt)
148                 self.assert_dicts_are_equal(r, account_info)
149             for i in range(len(untils)):
150                 self.assertEqual(
151                     PC.set_param.mock_calls[i],
152                     call('until', untils[i], iff=untils[i]))
153             self.FR.status_code = 401
154             self.assertRaises(ClientError, self.client.get_account_info)
155
156     def test_replace_account_meta(self):
157         self.FR.status_code = 202
158         metas = dict(k1='v1', k2='v2', k3='v3')
159         PC.set_header = Mock()
160         with patch.object(C, 'perform_request', return_value=self.FR()):
161             self.client.replace_account_meta(metas)
162             self.assertEqual(self.client.http_client.url, self.url)
163             self.assertEqual(self.client.http_client.path, '/%s' % user_id)
164             prfx = 'X-Account-Meta-'
165             expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
166             self.assertEqual(PC.set_header.mock_calls, expected)
167
168     def test_del_account_meta(self):
169         keys = ['k1', 'k2', 'k3']
170         with patch.object(PC, 'account_post', return_value=self.FR()) as ap:
171             expected = []
172             for key in keys:
173                 self.client.del_account_meta(key)
174                 expected.append(call(update=True, metadata={key: ''}))
175             self.assertEqual(ap.mock_calls, expected)
176
177     def test_create_container(self):
178         self.FR.status_code = 201
179         with patch.object(PC, 'put', return_value=self.FR()) as put:
180             cont = 's0m3c0n731n3r'
181             self.client.create_container(cont)
182             expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
183             self.assertEqual(put.mock_calls, expected)
184             self.FR.status_code = 202
185             self.assertRaises(ClientError, self.client.create_container, cont)
186
187     def test_get_container_info(self):
188         self.FR.headers = container_info
189         with patch.object(PC, 'container_head', return_value=self.FR()) as ch:
190             r = self.client.get_container_info()
191             self.assert_dicts_are_equal(r, container_info)
192             u = 'some date'
193             r = self.client.get_container_info(until=u)
194             self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
195
196     def test_delete_container(self):
197         self.FR.status_code = 204
198         with patch.object(PC, 'delete', return_value=self.FR()) as delete:
199             cont = 's0m3c0n731n3r'
200             self.client.delete_container(cont)
201             self.FR.status_code = 404
202             self.assertRaises(ClientError, self.client.delete_container, cont)
203             self.FR.status_code = 409
204             self.assertRaises(ClientError, self.client.delete_container, cont)
205             acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
206             self.assertEqual(delete.mock_calls, [acall] * 3)
207
208     def test_list_containers(self):
209         self.FR.json = container_list
210         with patch.object(PC, 'account_get', return_value=self.FR()):
211             r = self.client.list_containers()
212             for i in range(len(r)):
213                 self.assert_dicts_are_equal(r[i], container_list[i])
214
215     def test_upload_object(self):
216         PC.get_container_info = Mock(return_value=container_info)
217         PC.container_post = Mock(return_value=self.FR())
218         PC.object_put = Mock(return_value=self.FR())
219         from tempfile import NamedTemporaryFile
220         from os import urandom
221         self.files.append(NamedTemporaryFile())
222         tmpFile = self.files[-1]
223         num_of_blocks = 8
224         file_size = num_of_blocks * 4 * 1024 * 1024
225         print('\n\tCreate tmp file')
226         tmpFile.write(urandom(file_size))
227         tmpFile.flush()
228         tmpFile.seek(0)
229         print('\t\tDone')
230         obj = 'objectName'
231
232         # No special args
233         self.client.upload_object(obj, tmpFile)
234         self.assertEqual(PC.get_container_info.mock_calls, [call()])
235         [call1, call2] = PC.object_put.mock_calls
236
237         (args1, kwargs1) = call1[1:3]
238         (args2, kwargs2) = call2[1:3]
239         self.assertEqual(args1, (obj,))
240         expected1 = dict(
241             hashmap=True,
242             success=(201, 409),
243             format='json',
244             json=dict(
245                 hashes=['s0m3h@5h'] * num_of_blocks,
246                 bytes=file_size),
247             etag=None,
248             content_encoding=None,
249             content_type='application/octet-stream',
250             content_disposition=None,
251             public=None,
252             permissions=None)
253         for k, v in expected1.items():
254             if k == 'json':
255                 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
256                 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
257             else:
258                 self.assertEqual(v, kwargs1[k])
259
260         (args2, kwargs2) = call2[1:3]
261         self.assertEqual(args2, (obj,))
262         expected2 = dict(
263             json=dict(
264                 hashes=['s0m3h@5h'] * num_of_blocks,
265                 bytes=file_size),
266             content_type='application/octet-stream',
267             hashmap=True,
268             success=201,
269             format='json')
270         for k, v in expected2.items():
271             if k == 'json':
272                 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
273                 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
274             else:
275                 self.assertEqual(v, kwargs2[k])
276
277         OP = PC.object_put
278         mock_offset = 2
279
280         #  With progress bars
281         try:
282             from progress.bar import ShadyBar
283             blck_bar = ShadyBar('Mock blck calc.')
284             upld_bar = ShadyBar('Mock uplds')
285         except ImportError:
286             blck_bar = None
287             upld_bar = None
288
289         if blck_bar and upld_bar:
290
291             def blck_gen(n):
292                 for i in blck_bar.iter(range(n)):
293                     yield
294                 yield
295
296             def upld_gen(n):
297                 for i in upld_bar.iter(range(n)):
298                     yield
299                 yield
300
301             tmpFile.seek(0)
302             self.client.upload_object(
303                 obj, tmpFile,
304                 hash_cb=blck_gen, upload_cb=upld_gen)
305
306             for i, c in enumerate(OP.mock_calls[-mock_offset:]):
307                 self.assertEqual(OP.mock_calls[i], c)
308
309         #  With content-type
310         tmpFile.seek(0)
311         ctype = 'video/mpeg'
312         sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
313         self.client.upload_object(obj, tmpFile,
314             content_type=ctype, sharing=sharing)
315         self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
316         self.assert_dicts_are_equal(
317             OP.mock_calls[-2][2]['permissions'],
318             sharing)
319
320         # With other args
321         tmpFile.seek(0)
322         kwargs = dict(
323             etag='s0m3E74g',
324             content_type=ctype,
325             content_disposition=ctype + 'd15p051710n',
326             public=True,
327             content_encoding='802.11')
328         self.client.upload_object(obj, tmpFile, **kwargs)
329         for arg, val in kwargs.items():
330             self.assertEqual(OP.mock_calls[-2][2][arg], val)
331
332     def test_create_object(self):
333         PC.set_header = Mock()
334         obj = 'r4nd0m0bj3c7'
335         cont = self.client.container
336         ctype = 'c0n73n7/typ3'
337         exp_shd = [
338             call('Content-Type', 'application/octet-stream'),
339             call('Content-length', '0'),
340             call('Content-Type', ctype), call('Content-length', '42')]
341         exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
342         with patch.object(PC, 'put', return_value=self.FR()) as put:
343             self.client.create_object(obj)
344             self.client.create_object(obj,
345                 content_type=ctype, content_length=42)
346             self.assertEqual(PC.set_header.mock_calls, exp_shd)
347             self.assertEqual(put.mock_calls, exp_put)
348
349     def test_create_directory(self):
350         PC.set_header = Mock()
351         obj = 'r4nd0m0bj3c7'
352         cont = self.client.container
353         exp_shd = [
354             call('Content-Type', 'application/directory'),
355             call('Content-length', '0')]
356         exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
357         with patch.object(PC, 'put', return_value=self.FR()) as put:
358             self.client.create_directory(obj)
359             self.assertEqual(PC.set_header.mock_calls, exp_shd)
360             self.assertEqual(put.mock_calls, exp_put)
361
362     def test_get_object_info(self):
363         self.FR.headers = object_info
364         obj = 'r4nd0m0bj3c7'
365         version = 'v3r510n'
366         with patch.object(PC, 'object_head', return_value=self.FR()) as head:
367             r = self.client.get_object_info(obj)
368             self.assertEqual(r, object_info)
369             r = self.client.get_object_info(obj, version=version)
370             self.assertEqual(head.mock_calls, [
371                 call(obj, version=None),
372                 call(obj, version=version)])
373         with patch.object(
374                 PC,
375                 'object_head',
376                 side_effect=ClientError('Obj not found', 404)):
377             self.assertRaises(
378                 ClientError,
379                 self.client.get_object_info,
380                 obj, version=version)
381
382     def test_get_object_meta(self):
383         obj = 'r4nd0m0bj3c7'
384         expected = dict()
385         for k, v in object_info.items():
386             expected[k] = v
387         with patch.object(
388                 PC,
389                 'get_object_info',
390                 return_value=object_info):
391             r = self.client.get_object_meta(obj)
392             self.assert_dicts_are_equal(r, expected)
393
394     def test_del_object_meta(self):
395         obj = 'r4nd0m0bj3c7'
396         metakey = '50m3m3t4k3y'
397         with patch.object(PC, 'object_post', return_value=self.FR()) as post:
398             self.client.del_object_meta(obj, metakey)
399             self.assertEqual(
400                 post.mock_calls,
401                 [call(obj, update=True, metadata={metakey: ''})])
402
403     def test_replace_object_meta(self):
404         PC.set_header = Mock()
405         metas = dict(k1='new1', k2='new2', k3='new3')
406         cont = self.client.container
407         with patch.object(PC, 'post', return_value=self.FR()) as post:
408             self.client.replace_object_meta(metas)
409             self.assertEqual(post.mock_calls, [
410                 call('/%s/%s' % (user_id, cont),
411                 success=202)])
412             prfx = 'X-Object-Meta-'
413             expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
414             self.assertEqual(PC.set_header.mock_calls, expected)
415
416     def test_copy_object(self):
417         src_cont = 'src-c0nt41n3r'
418         src_obj = 'src-0bj'
419         dst_cont = 'dst-c0nt41n3r'
420         dst_obj = 'dst-0bj'
421         expected = call(
422             src_obj,
423             content_length=0,
424             source_account=None,
425             success=201,
426             copy_from='/%s/%s' % (src_cont, src_obj),
427             delimiter=None,
428             content_type=None,
429             source_version=None,
430             public=False)
431         with patch.object(PC, 'object_put', return_value=self.FR()) as put:
432             self.client.copy_object(src_cont, src_obj, dst_cont)
433             self.assertEqual(put.mock_calls[-1], expected)
434             self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
435             self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
436             kwargs = dict(
437                 source_version='src-v3r510n',
438                 source_account='src-4cc0un7',
439                 public=True,
440                 content_type='c0n73n7Typ3',
441                 delimiter='5')
442             self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
443             for k, v in kwargs.items():
444                 self.assertEqual(v, put.mock_calls[-1][2][k])
445
446     def test_move_object(self):
447         src_cont = 'src-c0nt41n3r'
448         src_obj = 'src-0bj'
449         dst_cont = 'dst-c0nt41n3r'
450         dst_obj = 'dst-0bj'
451         expected = call(
452             src_obj,
453             content_length=0,
454             source_account=None,
455             success=201,
456             move_from='/%s/%s' % (src_cont, src_obj),
457             delimiter=None,
458             content_type=None,
459             source_version=None,
460             public=False)
461         with patch.object(PC, 'object_put', return_value=self.FR()) as put:
462             self.client.move_object(src_cont, src_obj, dst_cont)
463             self.assertEqual(put.mock_calls[-1], expected)
464             self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
465             self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
466             kwargs = dict(
467                 source_version='src-v3r510n',
468                 source_account='src-4cc0un7',
469                 public=True,
470                 content_type='c0n73n7Typ3',
471                 delimiter='5')
472             self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
473             for k, v in kwargs.items():
474                 self.assertEqual(v, put.mock_calls[-1][2][k])