1 # Copyright 2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from unittest import TestCase
35 from mock import patch, call, Mock
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
42 user_id = 'ac0un7-1d-5tr1ng'
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'}
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'}
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',
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'}
87 last_modified="2013-02-27T11:56:09.893033+00:00",
90 x_container_policy=dict(quota="21474836480", versioning="auto")),
93 last_modified="2012-10-23T12:25:17.229187+00:00",
96 x_container_policy=dict(quota="21474836480", versioning="auto"))]
99 class Pithos(TestCase):
102 """FR stands for Fake Response"""
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])
120 self.assertEqual(unicode(v), unicode(d2[k]))
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'
130 self.FR.headers = dict()
131 self.FR.status_code = 200
132 self.FR.json = dict()
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']
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)):
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)
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)
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:
173 self.client.del_account_meta(key)
174 expected.append(call(update=True, metadata={key: ''}))
175 self.assertEqual(ap.mock_calls, expected)
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)
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)
193 r = self.client.get_container_info(until=u)
194 self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
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)
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])
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]
224 file_size = num_of_blocks * 4 * 1024 * 1024
225 print('\n\tCreate tmp file')
226 tmpFile.write(urandom(file_size))
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
237 (args1, kwargs1) = call1[1:3]
238 (args2, kwargs2) = call2[1:3]
239 self.assertEqual(args1, (obj,))
245 hashes=['s0m3h@5h'] * num_of_blocks,
248 content_encoding=None,
249 content_type='application/octet-stream',
250 content_disposition=None,
253 for k, v in expected1.items():
255 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
256 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
258 self.assertEqual(v, kwargs1[k])
260 (args2, kwargs2) = call2[1:3]
261 self.assertEqual(args2, (obj,))
264 hashes=['s0m3h@5h'] * num_of_blocks,
266 content_type='application/octet-stream',
270 for k, v in expected2.items():
272 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
273 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
275 self.assertEqual(v, kwargs2[k])
282 from progress.bar import ShadyBar
283 blck_bar = ShadyBar('Mock blck calc.')
284 upld_bar = ShadyBar('Mock uplds')
289 if blck_bar and upld_bar:
292 for i in blck_bar.iter(range(n)):
297 for i in upld_bar.iter(range(n)):
302 self.client.upload_object(
304 hash_cb=blck_gen, upload_cb=upld_gen)
306 for i, c in enumerate(OP.mock_calls[-mock_offset:]):
307 self.assertEqual(OP.mock_calls[i], c)
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'],
325 content_disposition=ctype + 'd15p051710n',
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)
332 def test_create_object(self):
333 PC.set_header = Mock()
335 cont = self.client.container
336 ctype = 'c0n73n7/typ3'
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)
349 def test_create_directory(self):
350 PC.set_header = Mock()
352 cont = self.client.container
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)
362 def test_get_object_info(self):
363 self.FR.headers = object_info
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)])
376 side_effect=ClientError('Obj not found', 404)):
379 self.client.get_object_info,
380 obj, version=version)
382 def test_get_object_meta(self):
385 for k, v in object_info.items():
390 return_value=object_info):
391 r = self.client.get_object_meta(obj)
392 self.assert_dicts_are_equal(r, expected)
394 def test_del_object_meta(self):
396 metakey = '50m3m3t4k3y'
397 with patch.object(PC, 'object_post', return_value=self.FR()) as post:
398 self.client.del_object_meta(obj, metakey)
401 [call(obj, update=True, metadata={metakey: ''})])
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),
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)
416 def test_copy_object(self):
417 src_cont = 'src-c0nt41n3r'
419 dst_cont = 'dst-c0nt41n3r'
426 copy_from='/%s/%s' % (src_cont, src_obj),
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,))
437 source_version='src-v3r510n',
438 source_account='src-4cc0un7',
440 content_type='c0n73n7Typ3',
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])
446 def test_move_object(self):
447 src_cont = 'src-c0nt41n3r'
449 dst_cont = 'dst-c0nt41n3r'
456 move_from='/%s/%s' % (src_cont, src_obj),
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,))
467 source_version='src-v3r510n',
468 source_account='src-4cc0un7',
470 content_type='c0n73n7Typ3',
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])