Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ 4288ade0

History | View | Annotate | Download (49.2 kB)

1
# Copyright 2011 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 django.test.client import Client
35
from django.test import TestCase
36
from django.utils import simplejson as json
37
from xml.dom import minidom
38
import types
39
import hashlib
40
import os
41
import mimetypes
42
import random
43
import datetime
44

    
45
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
46
                "%A, %d-%b-%y %H:%M:%S GMT",
47
                "%a, %d %b %Y %H:%M:%S GMT"]
48

    
49
class AaiClient(Client):
50
    def request(self, **request):
51
        request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
52
        return super(AaiClient, self).request(**request)
53

    
54
class BaseTestCase(TestCase):
55
    #TODO unauthorized request
56
    def setUp(self):
57
        self.client = AaiClient()
58
        self.headers = {
59
            'account':(
60
                'X-Account-Container-Count',
61
                'X-Account-Bytes-Used',
62
                'Last-Modified',
63
                'Content-Length',
64
                'Date',
65
                'Content-Type',),
66
            'container':(
67
                'X-Container-Object-Count',
68
                'X-Container-Bytes-Used',
69
                'Content-Type',
70
                'Last-Modified',
71
                'Content-Length',
72
                'Date',),
73
            'object':(
74
                'ETag',
75
                'Content-Length',
76
                'Content-Type',
77
                'Content-Encoding',
78
                'Last-Modified',
79
                'Date',
80
                'X-Object-Manifest',
81
                'Content-Range',)}
82
        self.contentTypes = {'xml':'application/xml',
83
                             'json':'application/json',
84
                             '':'text/plain'}
85
        self.extended = {
86
            'container':(
87
                'name',
88
                'count',
89
                'bytes',
90
                'last_modified'),
91
            'object':(
92
                'name',
93
                'hash',
94
                'bytes',
95
                'content_type',
96
                'content_encoding',
97
                'last_modified',)}
98
        self.return_codes = (400, 401, 404, 503,)
99
    
100
    def assertFault(self, response, status_code, name):
101
        self.assertEqual(response.status_code, status_code)
102
    
103
    def assertBadRequest(self, response):
104
        self.assertFault(response, 400, 'badRequest')
105
    
106
    def assertItemNotFound(self, response):
107
        self.assertFault(response, 404, 'itemNotFound')
108

    
109
    def assertUnauthorized(self, response):
110
        self.assertFault(response, 401, 'unauthorized')
111

    
112
    def assertServiceUnavailable(self, response):
113
        self.assertFault(response, 503, 'serviceUnavailable')
114

    
115
    def assertNonEmpty(self, response):
116
        self.assertFault(response, 409, 'nonEmpty')
117

    
118
    def assert_status(self, response, codes):
119
        l = [elem for elem in self.return_codes]
120
        if type(codes) == types.ListType:
121
            l.extend(codes)
122
        else:
123
            l.append(codes)
124
        self.assertTrue(response.status_code in l)
125

    
126
    def get_account_meta(self, account, exp_meta={}):
127
        path = '/v1/%s' % account
128
        response = self.client.head(path)
129
        self.assert_status(response, 204)
130
        self.assert_headers(response, 'account', exp_meta)
131
        return response
132

    
133
    def list_containers(self, account, limit=10000, marker='', format=''):
134
        params = locals()
135
        params.pop('self')
136
        params.pop('account')
137
        path = '/v1/%s' % account
138
        response = self.client.get(path, params)
139
        self.assert_status(response, [200, 204])
140
        response.content = response.content.strip()
141
        if format:
142
            self.assert_extended(response, format, 'container', limit)
143
        else:
144
            names = get_content_splitted(response)
145
            self.assertTrue(len(names) <= limit)
146
        return response
147

    
148
    def update_account_meta(self, account, **metadata):
149
        path = '/v1/%s' % account
150
        response = self.client.post(path, **metadata)
151
        response.content = response.content.strip()
152
        self.assert_status(response, 202)
153
        return response
154

    
155
    def get_container_meta(self, account, container, exp_meta={}):
156
        params = locals()
157
        params.pop('self')
158
        params.pop('account')
159
        params.pop('container')
160
        path = '/v1/%s/%s' %(account, container)
161
        response = self.client.head(path, params)
162
        response.content = response.content.strip()
163
        self.assert_status(response, 204)
164
        if response.status_code == 204:
165
            self.assert_headers(response, 'container', exp_meta)
166
        return response
167

    
168
    def list_objects(self, account, container, limit=10000, marker='', prefix='', format='', path='', delimiter='', meta=''):
169
        params = locals()
170
        params.pop('self')
171
        params.pop('account')
172
        params.pop('container')
173
        path = '/v1/%s/%s' % (account, container)
174
        response = self.client.get(path, params)
175
        response.content = response.content.strip()
176
        if format:
177
            self.assert_extended(response, format, 'object', limit)
178
        self.assert_status(response, [200, 204])
179
        return response
180

    
181
    def create_container(self, account, name, **meta):
182
        path = '/v1/%s/%s' %(account, name)
183
        response = self.client.put(path, **meta)
184
        response.content = response.content.strip()
185
        self.assert_status(response, [201, 202])
186
        return response
187

    
188
    def update_container_meta(self, account, name, **meta):
189
        path = '/v1/%s/%s' %(account, name)
190
        response = self.client.post(path, data={}, content_type='text/xml', follow=False, **meta)
191
        response.content = response.content.strip()
192
        self.assert_status(response, 202)
193
        return response
194

    
195
    def delete_container(self, account, container):
196
        path = '/v1/%s/%s' %(account, container)
197
        response = self.client.delete(path)
198
        response.content = response.content.strip()
199
        self.assert_status(response, [204, 409])
200
        return response
201

    
202
    def get_object_meta(self, account, container, name):
203
        path = '/v1/%s/%s/%s' %(account, container, name)
204
        response = self.client.head(path)
205
        response.content = response.content.strip()
206
        self.assert_status(response, 204)
207
        return response
208

    
209
    def get_object(self, account, container, name, exp_meta={}, **headers):
210
        path = '/v1/%s/%s/%s' %(account, container, name)
211
        response = self.client.get(path, **headers)
212
        response.content = response.content.strip()
213
        self.assert_status(response, [200, 206, 304, 412, 416])
214
        if response.status_code in [200, 206]:
215
            self.assert_headers(response, 'object')
216
        return response
217

    
218
    def upload_object(self, account, container, name, data, content_type='application/json', **headers):
219
        path = '/v1/%s/%s/%s' %(account, container, name)
220
        response = self.client.put(path, data, content_type, **headers)
221
        response.content = response.content.strip()
222
        self.assert_status(response, [201, 411, 422])
223
        if response.status_code == 201:
224
            self.assertTrue(response['Etag'])
225
        return response
226

    
227
    def copy_object(self, account, container, name, src, **headers):
228
        path = '/v1/%s/%s/%s' %(account, container, name)
229
        headers['HTTP_X_COPY_FROM'] = src
230
        response = self.client.put(path, **headers)
231
        response.content = response.content.strip()
232
        self.assert_status(response, 201)
233
        return response
234

    
235
    def move_object(self, account, container, name, src, **headers):
236
        path = '/v1/%s/%s/%s' % (account, container, name)
237
        headers['HTTP_X_MOVE_FROM'] = src
238
        response = self.client.put(path, **headers)
239
        response.content = response.content.strip()
240
        self.assert_status(response, 201)
241
        return response
242

    
243
    def update_object_meta(self, account, container, name, **headers):
244
        path = '/v1/%s/%s/%s' %(account, container, name)
245
        response = self.client.post(path, **headers)
246
        response.content = response.content.strip()
247
        self.assert_status(response, 202)
248
        return response
249

    
250
    def delete_object(self, account, container, name):
251
        path = '/v1/%s/%s/%s' %(account, container, name)
252
        response = self.client.delete(path)
253
        response.content = response.content.strip()
254
        self.assert_status(response, 204)
255
        return response
256

    
257
    def assert_headers(self, response, type, exp_meta={}):
258
        entities = ['Account', 'Container', 'Container-Object', 'Object']
259
        user_defined_meta = ['X-%s-Meta' %elem for elem in entities]
260
        headers = [item for item in response._headers.values()]
261
        system_headers = [h for h in headers if not h[0].startswith(tuple(user_defined_meta))]
262
        for h in system_headers:
263
            self.assertTrue(h[0] in self.headers[type])
264
            if exp_meta:
265
                self.assertEqual(h[1], exp_meta[h[0]])
266

    
267
    def assert_extended(self, response, format, type, size):
268
        self.assertEqual(response['Content-Type'].find(self.contentTypes[format]), 0)
269
        if format == 'xml':
270
            self.assert_xml(response, type, size)
271
        elif format == 'json':
272
            self.assert_json(response, type, size)
273

    
274
    def assert_json(self, response, type, size):
275
        convert = lambda s: s.lower()
276
        info = [convert(elem) for elem in self.extended[type]]
277
        data = json.loads(response.content)
278
        self.assertTrue(len(data) <= size)
279
        for item in info:
280
            for i in data:
281
                if 'subdir' in i.keys():
282
                    continue
283
                self.assertTrue(item in i.keys())
284

    
285
    def assert_xml(self, response, type, size):
286
        convert = lambda s: s.lower()
287
        info = [convert(elem) for elem in self.extended[type]]
288
        try:
289
            info.remove('content_encoding')
290
        except ValueError:
291
            pass
292
        xml = minidom.parseString(response.content)
293
        for item in info:
294
            nodes = xml.getElementsByTagName(item)
295
            self.assertTrue(nodes)
296
            self.assertTrue(len(nodes) <= size)
297
            
298

    
299
    def upload_os_file(self, account, container, fullpath, meta={}):
300
        try:
301
            f = open(fullpath, 'r')
302
            data = f.read()
303
            name = os.path.split(fullpath)[-1]
304
            return self.upload_data(account, container, name, data)    
305
        except IOError:
306
            return
307

    
308
    def upload_random_data(self, account, container, name, length=1024, meta={}):
309
        data = str(random.getrandbits(length))
310
        return self.upload_data(account, container, name, data, meta)
311

    
312
    def upload_data(self, account, container, name, data, meta={}):
313
        obj = {}
314
        obj['name'] = name
315
        try:
316
            obj['data'] = data
317
            obj['hash'] = compute_hash(obj['data'])
318
            meta.update({'HTTP_X_OBJECT_META_TEST':'test1',
319
                         'HTTP_ETAG':obj['hash']})
320
            meta['HTTP_CONTENT_TYPE'], enc = mimetypes.guess_type(name)
321
            if enc:
322
                meta['HTTP_CONTENT_TYPE'] = enc
323
            obj['meta'] = meta
324
            r = self.upload_object(account,
325
                               container,
326
                               obj['name'],
327
                               obj['data'],
328
                               meta['HTTP_CONTENT_TYPE'],
329
                               **meta)
330
            if r.status_code == 201:
331
                return obj
332
        except IOError:
333
            return
334

    
335
class ListContainers(BaseTestCase):
336
    def setUp(self):
337
        BaseTestCase.setUp(self)
338
        self.account = 'test'
339
        #create some containers
340
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
341
        for item in self.containers:
342
            self.create_container(self.account, item)
343

    
344
    def tearDown(self):
345
        for c in get_content_splitted(self.list_containers(self.account)):
346
            response = self.delete_container(self.account, c)
347

    
348
    def test_list(self):
349
        #list containers
350
        response = self.list_containers(self.account)
351
        containers = get_content_splitted(response)
352
        self.assertEquals(self.containers, containers)
353

    
354
    #def test_list_204(self):
355
    #    response = self.list_containers('non-existing-account')
356
    #    self.assertEqual(response.status_code, 204)
357

    
358
    def test_list_with_limit(self):
359
        limit = 2
360
        response = self.list_containers(self.account, limit=limit)
361
        containers = get_content_splitted(response)
362
        self.assertEquals(len(containers), limit)
363
        self.assertEquals(self.containers[:2], containers)
364

    
365
    def test_list_with_marker(self):
366
        limit = 2
367
        marker = 'bananas'
368
        response = self.list_containers(self.account, limit=limit, marker=marker)
369
        containers =  get_content_splitted(response)
370
        i = self.containers.index(marker) + 1
371
        self.assertEquals(self.containers[i:(i+limit)], containers)
372
        
373
        marker = 'oranges'
374
        response = self.list_containers(self.account, limit=limit, marker=marker)
375
        containers = get_content_splitted(response)
376
        i = self.containers.index(marker) + 1
377
        self.assertEquals(self.containers[i:(i+limit)], containers)
378

    
379
    #def test_extended_list(self):
380
    #    self.list_containers(self.account, limit=3, format='xml')
381
    #    self.list_containers(self.account, limit=3, format='json')
382

    
383
    def test_list_json_with_marker(self):
384
        limit = 2
385
        marker = 'bananas'
386
        response = self.list_containers(self.account, limit=limit, marker=marker, format='json')
387
        containers = json.loads(response.content)
388
        self.assertEqual(containers[0]['name'], 'kiwis')
389
        self.assertEqual(containers[1]['name'], 'oranges')
390

    
391
    def test_list_xml_with_marker(self):
392
        limit = 2
393
        marker = 'oranges'
394
        response = self.list_containers(self.account, limit=limit, marker=marker, format='xml')
395
        xml = minidom.parseString(response.content)
396
        nodes = xml.getElementsByTagName('name')
397
        self.assertEqual(len(nodes), 1)
398
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
399

    
400
class AccountMetadata(BaseTestCase):
401
    def setUp(self):
402
        BaseTestCase.setUp(self)
403
        self.account = 'test'
404
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
405
        for item in self.containers:
406
            self.create_container(self.account, item)
407

    
408
    def tearDown(self):
409
        for c in  get_content_splitted(self.list_containers(self.account)):
410
            self.delete_container(self.account, c)
411

    
412
    def test_get_account_meta(self):
413
        response = self.get_account_meta(self.account)
414
        r2 = self.list_containers(self.account)
415
        containers =  get_content_splitted(r2)
416
        self.assertEqual(response['X-Account-Container-Count'], str(len(containers)))
417
        size = 0
418
        for c in containers:
419
            r = self.get_container_meta(self.account, c)
420
            size = size + int(r['X-Container-Bytes-Used'])
421
        self.assertEqual(response['X-Account-Bytes-Used'], str(size))
422

    
423
    #def test_get_account_401(self):
424
    #    response = self.get_account_meta('non-existing-account')
425
    #    print response
426
    #    self.assertEqual(response.status_code, 401)
427

    
428
    def test_update_meta(self):
429
        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
430
        response = self.update_account_meta(self.account, **meta)
431
        response = self.get_account_meta(self.account)
432
        for k,v in meta.items():
433
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
434
            self.assertTrue(response[key])
435
            self.assertEqual(response[key], v)
436

    
437
    #def test_invalid_account_update_meta(self):
438
    #    with AssertInvariant(self.get_account_meta, self.account):
439
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
440
    #        response = self.update_account_meta('non-existing-account', **meta)
441

    
442
class ListObjects(BaseTestCase):
443
    def setUp(self):
444
        BaseTestCase.setUp(self)
445
        self.account = 'test'
446
        self.container = ['pears', 'apples']
447
        for c in self.container:
448
            self.create_container(self.account, c)
449
        self.obj = []
450
        for o in o_names[:8]:
451
            self.obj.append(self.upload_random_data(self.account,
452
                                                    self.container[0],
453
                                                    o))
454
        for o in o_names[8:]:
455
            self.obj.append(self.upload_random_data(self.account,
456
                                                    self.container[1],
457
                                                    o))
458

    
459
    def tearDown(self):
460
        for c in self.container:
461
            self.delete_container_recursively(c)
462

    
463
    def delete_container_recursively(self, c):
464
        for obj in get_content_splitted(self.list_objects(self.account, c)):
465
            self.delete_object(self.account, c, obj)
466
        self.delete_container(self.account, c)
467

    
468
    def test_list_objects(self):
469
        response = self.list_objects(self.account, self.container[0])
470
        objects = get_content_splitted(response)
471
        l = [elem['name'] for elem in self.obj[:8]]
472
        l.sort()
473
        self.assertEqual(objects, l)
474

    
475
    def test_list_objects_with_limit_marker(self):
476
        response = self.list_objects(self.account, self.container[0], limit=2)
477
        objects = get_content_splitted(response)
478
        l = [elem['name'] for elem in self.obj[:8]]
479
        l.sort()
480
        self.assertEqual(objects, l[:2])
481
        
482
        markers = ['How To Win Friends And Influence People.pdf',
483
                   'moms_birthday.jpg']
484
        limit = 4
485
        for m in markers:
486
            response = self.list_objects(self.account, self.container[0], limit=limit, marker=m)
487
            objects = get_content_splitted(response)
488
            l = [elem['name'] for elem in self.obj[:8]]
489
            l.sort()
490
            start = l.index(m) + 1
491
            end = start + limit
492
            end = len(l) >= end and end or len(l)
493
            self.assertEqual(objects, l[start:end])
494

    
495
    def test_list_pseudo_hierarchical_folders(self):
496
        response = self.list_objects(self.account, self.container[1], prefix='photos', delimiter='/')
497
        objects = get_content_splitted(response)
498
        self.assertEquals(['photos/animals/', 'photos/me.jpg', 'photos/plants/'], objects)
499
        
500
        response = self.list_objects(self.account, self.container[1], prefix='photos/animals', delimiter='/')
501
        objects = get_content_splitted(response)
502
        self.assertEquals(['photos/animals/cats/', 'photos/animals/dogs/'], objects)
503
        
504
        response = self.list_objects(self.account, self.container[1], path='photos')
505
        objects = get_content_splitted(response)
506
        self.assertEquals(['photos/me.jpg'], objects)
507

    
508
    def test_extended_list_json(self):
509
        response = self.list_objects(self.account, self.container[1], format='json', limit=2, prefix='photos/animals', delimiter='/')
510
        objects = json.loads(response.content)
511
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
512
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
513

    
514
    def test_extended_list_xml(self):
515
        response = self.list_objects(self.account, self.container[1], format='xml', limit=4, prefix='photos', delimiter='/')
516
        xml = minidom.parseString(response.content)
517
        dirs = xml.getElementsByTagName('subdir')
518
        self.assertEqual(len(dirs), 2)
519
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
520
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
521
        
522
        objects = xml.getElementsByTagName('name')
523
        self.assertEqual(len(objects), 1)
524
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
525

    
526
    def test_list_using_meta(self):
527
        meta = {'HTTP_X_OBJECT_META_QUALITY':'aaa'}
528
        for o in self.obj[:2]:
529
            r = self.update_object_meta(self.account,
530
                                    self.container[0],
531
                                    o['name'],
532
                                    **meta)
533
        meta = {'HTTP_X_OBJECT_META_STOCK':'true'}
534
        for o in self.obj[3:5]:
535
            r = self.update_object_meta(self.account,
536
                                    self.container[0],
537
                                    o['name'],
538
                                    **meta)
539
            
540
        r = self.list_objects(self.account,
541
                          self.container[0],
542
                          meta='Quality')
543
        self.assertEqual(r.status_code, 200)
544
        obj = get_content_splitted(r)
545
        self.assertEqual(len(obj), 2)
546
        
547
        # test case insensitive
548
        r = self.list_objects(self.account,
549
                          self.container[0],
550
                          meta='quality')
551
        self.assertEqual(r.status_code, 200)
552
        obj = get_content_splitted(r)
553
        self.assertEqual(len(obj), 2)
554
        
555
        # test multiple matches
556
        r = self.list_objects(self.account,
557
                          self.container[0],
558
                          meta='Quality, Stock')
559
        self.assertEqual(r.status_code, 200)
560
        obj = get_content_splitted(r)
561
        self.assertEqual(len(obj), 4)
562
        
563
        # test non 1-1 multiple match
564
        r = self.list_objects(self.account,
565
                          self.container[0],
566
                          meta='Quality, aaaa')
567
        self.assertEqual(r.status_code, 200)
568
        obj = get_content_splitted(r)
569
        self.assertEqual(len(obj), 2)
570
        
571

    
572
class ContainerMeta(BaseTestCase):
573
    def setUp(self):
574
        BaseTestCase.setUp(self)
575
        self.account = 'test'
576
        self.container = 'apples'
577
        self.create_container(self.account, self.container)
578

    
579
    def tearDown(self):
580
        for o in self.list_objects(self.account, self.container):
581
            self.delete_object(self.account, self.container, o)
582
        self.delete_container(self.account, self.container)
583
    
584
    def test_get_meta(self):
585
        headers = {'HTTP_X_OBJECT_META_TRASH':'true'}
586
        t1 = datetime.datetime.utcnow()
587
        o = self.upload_random_data(self.account,
588
                                self.container,
589
                                'McIntosh.jpg',
590
                                meta=headers)
591
        if o:
592
            r = self.get_container_meta(self.account,
593
                                        self.container)
594
            self.assertEqual(r['X-Container-Object-Count'], '1')
595
            self.assertEqual(r['X-Container-Bytes-Used'], str(len(o['data'])))
596
            t2 = datetime.datetime.strptime(r['Last-Modified'], DATE_FORMATS[2])
597
            delta = (t2 - t1)
598
            threashold = datetime.timedelta(seconds=1) 
599
            self.assertTrue(delta < threashold)
600
            self.assertTrue(r['X-Container-Object-Meta'])
601
            self.assertTrue('Trash' in r['X-Container-Object-Meta'])
602

    
603
    def test_update_meta(self):
604
        meta = {'HTTP_X_CONTAINER_META_TEST':'test33', 'HTTP_X_CONTAINER_META_TOST':'tost22'}
605
        response = self.update_container_meta(self.account, self.container, **meta)
606
        response = self.get_container_meta(self.account, self.container)
607
        for k,v in meta.items():
608
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
609
            self.assertTrue(response[key])
610
            self.assertEqual(response[key], v)
611

    
612
class CreateContainer(BaseTestCase):
613
    def setUp(self):
614
        BaseTestCase.setUp(self)
615
        self.account = 'test'
616
        self.containers = ['c1', 'c2']
617

    
618
    def tearDown(self):
619
        for c in self.containers:
620
            r = self.delete_container(self.account, c)
621

    
622
    def test_create(self):
623
        response = self.create_container(self.account, self.containers[0])
624
        if response.status_code == 201:
625
            response = self.list_containers(self.account)
626
            self.assertTrue(self.containers[0] in get_content_splitted(response))
627
            r = self.get_container_meta(self.account, self.containers[0])
628
            self.assertEqual(r.status_code, 204)
629

    
630
    def test_create_twice(self):
631
        response = self.create_container(self.account, self.containers[0])
632
        if response.status_code == 201:
633
            self.assertTrue(self.create_container(self.account, self.containers[0]).status_code, 202)
634

    
635
class DeleteContainer(BaseTestCase):
636
    def setUp(self):
637
        BaseTestCase.setUp(self)
638
        self.account = 'test'
639
        self.containers = ['c1', 'c2']
640
        for c in self.containers:
641
            self.create_container(self.account, c)
642
        self.upload_random_data(self.account,
643
                                self.containers[1],
644
                                'nice.jpg')
645

    
646
    def tearDown(self):
647
        for c in self.containers:
648
            for o in get_content_splitted(self.list_objects(self.account, c)):
649
                self.delete_object(self.account, c, o)
650
            self.delete_container(self.account, c)
651

    
652
    def test_delete(self):
653
        r = self.delete_container(self.account, self.containers[0])
654
        self.assertEqual(r.status_code, 204)
655

    
656
    def test_delete_non_empty(self):
657
        r = self.delete_container(self.account, self.containers[1])
658
        self.assertNonEmpty(r)
659

    
660
    def test_delete_invalid(self):
661
        self.assertItemNotFound(self.delete_container(self.account, 'c3'))
662

    
663
class GetObjects(BaseTestCase):
664
    def setUp(self):
665
        BaseTestCase.setUp(self)
666
        self.account = 'test'
667
        self.containers = ['c1', 'c2']
668
        #create some containers
669
        for c in self.containers:
670
            self.create_container(self.account, c)
671
        
672
        #upload a file
673
        self.objects = []
674
        self.objects.append(self.upload_os_file(self.account,
675
                            self.containers[1],
676
                            './api/tests.py'))
677
        self.objects.append(self.upload_os_file(self.account,
678
                            self.containers[1],
679
                            'settings.py'))
680

    
681
    def tearDown(self):
682
        for c in self.containers:
683
            for o in get_content_splitted(self.list_objects(self.account, c)):
684
                self.delete_object(self.account, c, o)
685
            self.delete_container(self.account, c)
686

    
687
    def test_get(self):
688
        #perform get
689
        r = self.get_object(self.account,
690
                            self.containers[1],
691
                            self.objects[0]['name'],
692
                            self.objects[0]['meta'])
693
        #assert success
694
        self.assertEqual(r.status_code, 200)
695

    
696
    def test_get_invalid(self):
697
        r = self.get_object(self.account,
698
                            self.containers[0],
699
                            self.objects[0]['name'])
700
        self.assertItemNotFound(r)
701

    
702
    def test_get_partial(self):
703
        #perform get with range
704
        headers = {'HTTP_RANGE':'bytes=0-499'}
705
        r = self.get_object(self.account,
706
                        self.containers[1],
707
                        self.objects[0]['name'],
708
                        **headers)
709
        
710
        #assert successful partial content
711
        self.assertEqual(r.status_code, 206)
712
        
713
        #assert content length
714
        self.assertEqual(int(r['Content-Length']), 500)
715
        
716
        #assert content
717
        self.assertEqual(self.objects[0]['data'][:500], r.content)
718

    
719
    def test_get_final_500(self):
720
        #perform get with range
721
        headers = {'HTTP_RANGE':'bytes=-500'}
722
        r = self.get_object(self.account,
723
                        self.containers[1],
724
                        self.objects[0]['name'],
725
                        **headers)
726
        
727
        #assert successful partial content
728
        self.assertEqual(r.status_code, 206)
729
        
730
        #assert content length
731
        self.assertEqual(int(r['Content-Length']), 500)
732
        
733
        #assert content
734
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
735

    
736
    def test_get_rest(self):
737
        #perform get with range
738
        offset = len(self.objects[0]['data']) - 500
739
        headers = {'HTTP_RANGE':'bytes=%s-' %offset}
740
        r = self.get_object(self.account,
741
                        self.containers[1],
742
                        self.objects[0]['name'],
743
                        **headers)
744
        
745
        #assert successful partial content
746
        self.assertEqual(r.status_code, 206)
747
        
748
        #assert content length
749
        self.assertEqual(int(r['Content-Length']), 500)
750
        
751
        #assert content
752
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
753
        
754
    def test_get_range_not_satisfiable(self):
755
        #perform get with range
756
        offset = len(self.objects[0]['data']) + 1
757
        headers = {'HTTP_RANGE':'bytes=0-%s' %offset}
758
        r = self.get_object(self.account,
759
                        self.containers[1],
760
                        self.objects[0]['name'],
761
                        **headers)
762
        
763
        #assert Range Not Satisfiable
764
        self.assertEqual(r.status_code, 416)
765

    
766
    def test_multiple_range(self):
767
        #perform get with multiple range
768
        ranges = ['0-499', '-500', '1000-']
769
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
770
        r = self.get_object(self.account,
771
                        self.containers[1],
772
                        self.objects[0]['name'],
773
                        **headers)
774
        
775
        # assert partial content
776
        self.assertEqual(r.status_code, 206)
777
        
778
        # assert Content-Type of the reply will be multipart/byteranges
779
        self.assertTrue(r['Content-Type'])
780
        content_type_parts = r['Content-Type'].split()
781
        self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
782
        
783
        boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
784
        cparts = r.content.split(boundary)[1:-1]
785
        
786
        # assert content parts are exactly 2
787
        self.assertEqual(len(cparts), len(ranges))
788
        
789
        # for each content part assert headers
790
        i = 0
791
        for cpart in cparts:
792
            content = cpart.split('\r\n')
793
            headers = content[1:3]
794
            content_range = headers[0].split(': ')
795
            self.assertEqual(content_range[0], 'Content-Range')
796
            
797
            r = ranges[i].split('-')
798
            if not r[0] and not r[1]:
799
                pass
800
            elif not r[0]:
801
                start = len(self.objects[0]['data']) - int(r[1])
802
                end = len(self.objects[0]['data'])
803
            elif not r[1]:
804
                start = int(r[0])
805
                end = len(self.objects[0]['data'])
806
            else:
807
                start = int(r[0])
808
                end = int(r[1]) + 1
809
            fdata = self.objects[0]['data'][start:end]
810
            sdata = '\r\n'.join(content[4:-1])
811
            self.assertEqual(len(fdata), len(sdata))
812
            self.assertEquals(fdata, sdata)
813
            i+=1
814

    
815
    def test_multiple_range_not_satisfiable(self):
816
        #perform get with multiple range
817
        out_of_range = len(self.objects[0]['data']) + 1
818
        ranges = ['0-499', '-500', '%d-' %out_of_range]
819
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
820
        r = self.get_object(self.account,
821
                        self.containers[1],
822
                        self.objects[0]['name'],
823
                        **headers)
824
        
825
        # assert partial content
826
        self.assertEqual(r.status_code, 416)
827

    
828
    def test_get_with_if_match(self):
829
        #perform get with If-Match
830
        headers = {'HTTP_IF_MATCH':self.objects[0]['hash']}
831
        r = self.get_object(self.account,
832
                        self.containers[1],
833
                        self.objects[0]['name'],
834
                        **headers)
835
        #assert get success
836
        self.assertEqual(r.status_code, 200)
837
        
838
        #assert response content
839
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
840

    
841
    def test_get_with_if_match_star(self):
842
        #perform get with If-Match *
843
        headers = {'HTTP_IF_MATCH':'*'}
844
        r = self.get_object(self.account,
845
                        self.containers[1],
846
                        self.objects[0]['name'],
847
                        **headers)
848
        #assert get success
849
        self.assertEqual(r.status_code, 200)
850
        
851
        #assert response content
852
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
853

    
854
    def test_get_with_multiple_if_match(self):
855
        #perform get with If-Match
856
        etags = [i['hash'] for i in self.objects if i]
857
        etags = ','.join('"%s"' % etag for etag in etags)
858
        headers = {'HTTP_IF_MATCH':etags}
859
        r = self.get_object(self.account,
860
                        self.containers[1],
861
                        self.objects[0]['name'],
862
                        **headers)
863
        #assert get success
864
        self.assertEqual(r.status_code, 200)
865
        
866
        #assert response content
867
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
868

    
869
    def test_if_match_precondition_failed(self):
870
        #perform get with If-Match
871
        headers = {'HTTP_IF_MATCH':'123'}
872
        r = self.get_object(self.account,
873
                        self.containers[1],
874
                        self.objects[0]['name'],
875
                        **headers)
876
        
877
        #assert precondition failed 
878
        self.assertEqual(r.status_code, 412)
879

    
880
    def test_if_none_match(self):
881
        #perform get with If-Match
882
        headers = {'HTTP_IF_NONE_MATCH':'123'}
883
        r = self.get_object(self.account,
884
                        self.containers[1],
885
                        self.objects[0]['name'],
886
                        **headers)
887
        
888
        #assert get success
889
        self.assertEqual(r.status_code, 200)
890

    
891
    def test_if_none_match(self):
892
        #perform get with If-Match *
893
        headers = {'HTTP_IF_NONE_MATCH':'*'}
894
        r = self.get_object(self.account,
895
                        self.containers[1],
896
                        self.objects[0]['name'],
897
                        **headers)
898
        
899
        #assert get success
900
        self.assertEqual(r.status_code, 304)
901
        
902
    def test_if_none_match_not_modified(self):
903
        #perform get with If-Match
904
        headers = {'HTTP_IF_NONE_MATCH':'%s' %self.objects[0]['hash']}
905
        r = self.get_object(self.account,
906
                        self.containers[1],
907
                        self.objects[0]['name'],
908
                        **headers)
909
        
910
        #assert not modified
911
        self.assertEqual(r.status_code, 304)
912
        self.assertEqual(r['ETag'], self.objects[0]['hash'])
913
        
914
    def test_get_with_if_modified_since(self):
915
        t = datetime.datetime.utcnow()
916
        t2 = t - datetime.timedelta(minutes=10)
917
        
918
        #modify the object
919
        self.upload_object(self.account,
920
                           self.containers[1],
921
                           self.objects[0]['name'],
922
                           self.objects[0]['data'][:200])
923
        
924
        for f in DATE_FORMATS:
925
            past = t2.strftime(f)
926
            
927
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
928
            r = self.get_object(self.account,
929
                        self.containers[1],
930
                        self.objects[0]['name'],
931
                        **headers)
932
            
933
            #assert get success
934
            self.assertEqual(r.status_code, 200)
935
            
936
    def test_get_modified_since_invalid_date(self):
937
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
938
        r = self.get_object(self.account,
939
                    self.containers[1],
940
                    self.objects[0]['name'],
941
                    **headers)
942
        
943
        #assert get success
944
        self.assertEqual(r.status_code, 200)
945

    
946
    def test_get_not_modified_since(self):
947
        now = datetime.datetime.utcnow()
948
        since = now + datetime.timedelta(1)
949
        
950
        for f in DATE_FORMATS:
951
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
952
            r = self.get_object(self.account,
953
                                self.containers[1],
954
                                self.objects[0]['name'],
955
                                **headers)
956
            
957
            #assert not modified
958
            self.assertEqual(r.status_code, 304)
959

    
960
    def test_get_with_if_unmodified_since(self):
961
        return
962

    
963
class UploadObject(BaseTestCase):
964
    def setUp(self):
965
        BaseTestCase.setUp(self)
966
        self.account = 'test'
967
        self.container = 'c1'
968
        self.create_container(self.account, self.container)
969
        
970
        self.src = os.path.join('.', 'api', 'tests.py')
971
        self.dest = os.path.join('.', 'api', 'chunked_update_test_file')
972
        create_chunked_update_test_file(self.src, self.dest)
973

    
974
    def tearDown(self):
975
        for o in get_content_splitted(self.list_objects(self.account, self.container)):
976
            self.delete_object(self.account, self.container, o)
977
        self.delete_container(self.account, self.container)
978
        
979
        # delete test file
980
        os.remove(self.dest)
981
        
982
    def test_upload(self):
983
        filename = 'tests.py'
984
        fullpath = os.path.join('.', 'api', filename) 
985
        f = open(fullpath, 'r')
986
        data = f.read()
987
        hash = compute_hash(data)
988
        meta={'HTTP_ETAG':hash,
989
              'HTTP_X_OBJECT_MANIFEST':123,
990
              'HTTP_X_OBJECT_META_TEST':'test1'
991
              }
992
        meta['HTTP_CONTENT_TYPE'], meta['HTTP_CONTENT_ENCODING'] = mimetypes.guess_type(fullpath)
993
        r = self.upload_object(self.account,
994
                               self.container,
995
                               filename,
996
                               data,
997
                               content_type=meta['HTTP_CONTENT_TYPE'],
998
                               **meta)
999
        self.assertEqual(r.status_code, 201)
1000
        r = self.get_object_meta(self.account, self.container, filename)
1001
        self.assertTrue(r['X-Object-Meta-Test'])
1002
        self.assertEqual(r['X-Object-Meta-Test'], meta['HTTP_X_OBJECT_META_TEST'])
1003
        
1004
        #assert uploaded content
1005
        r = self.get_object(self.account, self.container, filename)
1006
        self.assertEqual(os.path.getsize(fullpath), int(r['Content-Length']))
1007
        self.assertEqual(data.strip(), r.content.strip())
1008

    
1009
    def test_upload_unprocessable_entity(self):
1010
        filename = 'tests.py'
1011
        fullpath = os.path.join('.', 'api', filename) 
1012
        f = open(fullpath, 'r')
1013
        data = f.read()
1014
        meta={'HTTP_ETAG':'123',
1015
              'HTTP_X_OBJECT_MANIFEST':123,
1016
              'HTTP_X_OBJECT_META_TEST':'test1'
1017
              }
1018
        meta['HTTP_CONTENT_TYPE'], meta['HTTP_CONTENT_ENCODING'] = mimetypes.guess_type(fullpath)
1019
        r = self.upload_object(self.account,
1020
                               self.container,
1021
                               filename,
1022
                               data,
1023
                               content_type = meta['HTTP_CONTENT_TYPE'],
1024
                               **meta)
1025
        self.assertEqual(r.status_code, 422)
1026
        
1027
    def test_chucked_update(self):
1028
        objname = os.path.split(self.src)[-1:][0]
1029
        f = open(self.dest, 'r')
1030
        data = f.read()
1031
        meta = {}
1032
        meta['HTTP_CONTENT_TYPE'], meta['HTTP_CONTENT_ENCODING'] = mimetypes.guess_type(objname)
1033
        meta.update({'HTTP_TRANSFER_ENCODING':'chunked'})
1034
        r = self.upload_object(self.account,
1035
                               self.container,
1036
                               objname,
1037
                               data,
1038
                               content_type = 'plain/text',
1039
                               **meta)
1040
        self.assertEqual(r.status_code, 201)
1041
        
1042
        r = self.get_object(self.account,
1043
                            self.container,
1044
                            objname)
1045
        uploaded_data = r.content
1046
        f = open(self.src, 'r')
1047
        actual_data = f.read()
1048
        self.assertEqual(actual_data, uploaded_data)
1049

    
1050
class CopyObject(BaseTestCase):
1051
    def setUp(self):
1052
        BaseTestCase.setUp(self)
1053
        self.account = 'test'
1054
        self.containers = ['c1', 'c2']
1055
        for c in self.containers:
1056
            self.create_container(self.account, c)
1057
        self.obj = self.upload_os_file(self.account,
1058
                            self.containers[0],
1059
                            './api/tests.py')
1060

    
1061
    def tearDown(self):
1062
        for c in self.containers:
1063
            for o in get_content_splitted(self.list_objects(self.account, c)):
1064
                self.delete_object(self.account, c, o)
1065
            self.delete_container(self.account, c)
1066

    
1067
    def test_copy(self):
1068
        with AssertInvariant(self.get_object_meta, self.account, self.containers[0], self.obj['name']):
1069
            #perform copy
1070
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1071
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1072
            r = self.copy_object(self.account,
1073
                             self.containers[0],
1074
                             'testcopy',
1075
                             src_path,
1076
                             **meta)
1077
            #assert copy success
1078
            self.assertEqual(r.status_code, 201)
1079
            
1080
            #assert access the new object
1081
            r = self.get_object_meta(self.account,
1082
                                     self.containers[0],
1083
                                     'testcopy')
1084
            self.assertTrue(r['X-Object-Meta-Test'])
1085
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1086
            
1087
            #assert etag is the same
1088
            self.assertEqual(r['ETag'], self.obj['hash'])
1089
            
1090
            #assert src object still exists
1091
            r = self.get_object_meta(self.account, self.containers[0], self.obj['name'])
1092
            self.assertEqual(r.status_code, 204)
1093

    
1094
    def test_copy_from_different_container(self):
1095
        with AssertInvariant(self.get_object_meta,
1096
                             self.account,
1097
                             self.containers[0],
1098
                             self.obj['name']):
1099
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1100
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1101
            r = self.copy_object(self.account,
1102
                             self.containers[1],
1103
                             'testcopy',
1104
                             src_path,
1105
                             **meta)
1106
            self.assertEqual(r.status_code, 201)
1107
            
1108
            # assert updated metadata
1109
            r = self.get_object_meta(self.account,
1110
                                     self.containers[1],
1111
                                     'testcopy')
1112
            self.assertTrue(r['X-Object-Meta-Test'])
1113
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1114
            
1115
            #assert src object still exists
1116
            r = self.get_object_meta(self.account, self.containers[0], self.obj['name'])
1117
            self.assertEqual(r.status_code, 204)
1118

    
1119
    def test_copy_invalid(self):
1120
        #copy from invalid object
1121
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1122
        r = self.copy_object(self.account,
1123
                         self.containers[1],
1124
                         'testcopy',
1125
                         os.path.join('/', self.containers[0], 'test.py'),
1126
                         **meta)
1127
        self.assertItemNotFound(r)
1128
        
1129
        
1130
        #copy from invalid container
1131
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1132
        src_path = os.path.join('/', self.containers[1], self.obj['name'])
1133
        r = self.copy_object(self.account,
1134
                         self.containers[1],
1135
                         'testcopy',
1136
                         src_path,
1137
                         **meta)
1138
        self.assertItemNotFound(r)
1139

    
1140
class MoveObject(CopyObject):
1141
    def test_move(self):
1142
        #perform move
1143
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1144
        src_path = os.path.join('/', self.containers[0], self.obj['name'])
1145
        r = self.move_object(self.account,
1146
                         self.containers[0],
1147
                         'testcopy',
1148
                         src_path,
1149
                         **meta)
1150
        #assert successful move
1151
        self.assertEqual(r.status_code, 201)
1152
        
1153
        #assert updated metadata
1154
        r = self.get_object_meta(self.account,
1155
                                 self.containers[0],
1156
                                 'testcopy')
1157
        self.assertTrue(r['X-Object-Meta-Test'])
1158
        self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1159
        
1160
        #assert src object no more exists
1161
        r = self.get_object_meta(self.account, self.containers[0], self.obj['name'])
1162
        self.assertItemNotFound(r)
1163

    
1164
class UpdateObjectMeta(BaseTestCase):
1165
    def setUp(self):
1166
        BaseTestCase.setUp(self)
1167
        self.account = 'test'
1168
        self.containers = ['c1', 'c2']
1169
        for c in self.containers:
1170
            self.create_container(self.account, c)
1171
        self.obj = self.upload_os_file(self.account,
1172
                                       self.containers[0],
1173
                                       './api/tests.py')
1174

    
1175
    def tearDown(self):
1176
        for c in self.containers:
1177
            for o in get_content_splitted(self.list_objects(self.account, c)):
1178
                self.delete_object(self.account, c, o)
1179
            self.delete_container(self.account, c)
1180

    
1181
    def test_update(self):
1182
        #perform update metadata
1183
        more = {'HTTP_X_OBJECT_META_FOO':'foo',
1184
                'HTTP_X_OBJECT_META_BAR':'bar'}
1185
        r = self.update_object_meta(self.account,
1186
                                self.containers[0],
1187
                                self.obj['name'],
1188
                                **more)
1189
        #assert request accepted
1190
        self.assertEqual(r.status_code, 202)
1191
        
1192
        #assert old metadata are still there
1193
        r = self.get_object_meta(self.account, self.containers[0], self.obj['name'])
1194
        self.assertTrue('X-Object-Meta-Test' not in r.items())
1195
        
1196
        #assert new metadata have been updated
1197
        for k,v in more.items():
1198
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
1199
            self.assertTrue(r[key])
1200
            self.assertTrue(r[key], v)
1201

    
1202
class DeleteObject(BaseTestCase):
1203
    def setUp(self):
1204
        BaseTestCase.setUp(self)
1205
        self.account = 'test'
1206
        self.containers = ['c1', 'c2']
1207
        for c in self.containers:
1208
            self.create_container(self.account, c)
1209
        self.obj = self.upload_os_file(self.account,
1210
                            self.containers[0],
1211
                            './api/tests.py')
1212

    
1213
    def tearDown(self):
1214
        for c in self.containers:
1215
            for o in get_content_splitted(self.list_objects(self.account, c)):
1216
                self.delete_object(self.account, c, o)
1217
            self.delete_container(self.account, c)
1218

    
1219
    def test_delete(self):
1220
        #perform delete object
1221
        r = self.delete_object(self.account, self.containers[0], self.obj['name'])
1222
        
1223
        #assert success
1224
        self.assertEqual(r.status_code, 204)
1225
        
1226
    def test_delete_invalid(self):
1227
        #perform delete object
1228
        r = self.delete_object(self.account, self.containers[1], self.obj['name'])
1229
        
1230
        #assert failure
1231
        self.assertItemNotFound(r)
1232

    
1233
class AssertInvariant(object):
1234
    def __init__(self, callable, *args, **kwargs):
1235
        self.callable = callable
1236
        self.args = args
1237
        self.kwargs = kwargs
1238

    
1239
    def __enter__(self):
1240
        self.items = self.callable(*self.args, **self.kwargs).items()
1241
        return self.items
1242

    
1243
    def __exit__(self, type, value, tb):
1244
        items = self.callable(*self.args, **self.kwargs).items()
1245
        assert self.items == items
1246

    
1247
def get_content_splitted(response):
1248
    if response:
1249
        return response.content.split('\n')
1250

    
1251
def compute_hash(data):
1252
    md5 = hashlib.md5()
1253
    offset = 0
1254
    md5.update(data)
1255
    return md5.hexdigest().lower()
1256

    
1257
def create_chunked_update_test_file(src, dest):
1258
    fr = open(src, 'r')
1259
    fw = open(dest, 'w')
1260
    data = fr.readline()
1261
    while data:
1262
        fw.write(hex(len(data)))
1263
        fw.write('\r\n')
1264
        fw.write(data)
1265
        data = fr.readline()
1266
    fw.write(hex(0))
1267
    fw.write('\r\n')
1268

    
1269
o_names = ['kate.jpg',
1270
           'kate_beckinsale.jpg',
1271
           'How To Win Friends And Influence People.pdf',
1272
           'moms_birthday.jpg',
1273
           'poodle_strut.mov',
1274
           'Disturbed - Down With The Sickness.mp3',
1275
           'army_of_darkness.avi',
1276
           'the_mad.avi',
1277
           'photos/animals/dogs/poodle.jpg',
1278
           'photos/animals/dogs/terrier.jpg',
1279
           'photos/animals/cats/persian.jpg',
1280
           'photos/animals/cats/siamese.jpg',
1281
           'photos/plants/fern.jpg',
1282
           'photos/plants/rose.jpg',
1283
           'photos/me.jpg']