Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / test / __init__.py @ bc4f25c0

History | View | Annotate | Download (18.3 kB)

1
#!/usr/bin/env python
2
#coding=utf8
3

    
4
# Copyright 2011-2013 GRNET S.A. All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or
7
# without modification, are permitted provided that the following
8
# conditions are met:
9
#
10
#   1. Redistributions of source code must retain the above
11
#      copyright notice, this list of conditions and the following
12
#      disclaimer.
13
#
14
#   2. Redistributions in binary form must reproduce the above
15
#      copyright notice, this list of conditions and the following
16
#      disclaimer in the documentation and/or other materials
17
#      provided with the distribution.
18
#
19
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
# POSSIBILITY OF SUCH DAMAGE.
31
#
32
# The views and conclusions contained in the software and
33
# documentation are those of the authors and should not be
34
# interpreted as representing official policies, either expressed
35
# or implied, of GRNET S.A.
36

    
37
from urlparse import urlunsplit, urlsplit
38
from xml.dom import minidom
39

    
40
from snf_django.utils.testing import with_settings, astakos_user
41

    
42
from pithos.api import settings as pithos_settings
43
from pithos.api.test.util import is_date, get_random_data, get_random_name
44
from pithos.backends.migrate import initialize_db
45

    
46
from synnefo.lib.services import get_service_path
47
from synnefo.lib import join_urls
48

    
49
from django.test import TestCase
50
from django.test.simple import DjangoTestSuiteRunner
51
from django.conf import settings
52
from django.utils.http import urlencode
53
from django.db.backends.creation import TEST_DATABASE_PREFIX
54

    
55
import django.utils.simplejson as json
56

    
57
import random
58
import functools
59

    
60

    
61
pithos_test_settings = functools.partial(with_settings, pithos_settings)
62

    
63
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
64
                "%A, %d-%b-%y %H:%M:%S GMT",
65
                "%a, %d %b %Y %H:%M:%S GMT"]
66

    
67
o_names = ['kate.jpg',
68
           'kate_beckinsale.jpg',
69
           'How To Win Friends And Influence People.pdf',
70
           'moms_birthday.jpg',
71
           'poodle_strut.mov',
72
           'Disturbed - Down With The Sickness.mp3',
73
           'army_of_darkness.avi',
74
           'the_mad.avi',
75
           'photos/animals/dogs/poodle.jpg',
76
           'photos/animals/dogs/terrier.jpg',
77
           'photos/animals/cats/persian.jpg',
78
           'photos/animals/cats/siamese.jpg',
79
           'photos/plants/fern.jpg',
80
           'photos/plants/rose.jpg',
81
           'photos/me.jpg']
82

    
83
details = {'container': ('name', 'count', 'bytes', 'last_modified',
84
                         'x_container_policy'),
85
           'object': ('name', 'hash', 'bytes', 'content_type',
86
                      'content_encoding', 'last_modified',)}
87

    
88
TEST_BLOCK_SIZE = 1024
89
TEST_HASH_ALGORITHM = 'sha256'
90

    
91
print 'backend module:', pithos_settings.BACKEND_DB_MODULE
92
print 'backend database engine:', settings.DATABASES['default']['ENGINE']
93
print 'update md5:', pithos_settings.UPDATE_MD5
94

    
95

    
96
django_sqlalchemy_engines = {
97
    'django.db.backends.postgresql_psycopg2': 'postgresql+psycopg2',
98
    'django.db.backends.postgresql': 'postgresql',
99
    'django.db.backends.mysql': '',
100
    'django.db.backends.sqlite3': 'mssql',
101
    'django.db.backends.oracle': 'oracle'}
102

    
103

    
104
def prepate_db_connection():
105
    """Build pithos backend connection string from django default database"""
106

    
107
    db = settings.DATABASES['default']
108
    name = db.get('TEST_NAME', TEST_DATABASE_PREFIX + db['NAME'])
109

    
110
    if (pithos_settings.BACKEND_DB_MODULE == 'pithos.backends.lib.sqlalchemy'):
111
        if db['ENGINE'] == 'django.db.backends.sqlite3':
112
            db_connection = 'sqlite:///%s' % name
113
        else:
114
            d = dict(scheme=django_sqlalchemy_engines.get(db['ENGINE']),
115
                     user=db['USER'],
116
                     pwd=db['PASSWORD'],
117
                     host=db['HOST'].lower(),
118
                     port=int(db['PORT']) if db['PORT'] != '' else '',
119
                     name=name)
120
            db_connection = (
121
                '%(scheme)s://%(user)s:%(pwd)s@%(host)s:%(port)s/%(name)s' % d)
122

    
123
            # initialize pithos database
124
            initialize_db(db_connection)
125
    else:
126
        db_connection = name
127
    pithos_settings.BACKEND_DB_CONNECTION = db_connection
128

    
129

    
130
class PithosTestSuiteRunner(DjangoTestSuiteRunner):
131
    def setup_databases(self, **kwargs):
132
        old_names, mirrors = super(PithosTestSuiteRunner,
133
                                   self).setup_databases(**kwargs)
134
        prepate_db_connection()
135
        return old_names, mirrors
136

    
137

    
138
class PithosAPITest(TestCase):
139
    def setUp(self):
140
        # Override default block size to spead up tests
141
        pithos_settings.BACKEND_BLOCK_SIZE = TEST_BLOCK_SIZE
142
        pithos_settings.BACKEND_HASH_ALGORITHM = TEST_HASH_ALGORITHM
143

    
144
        self.user = 'user'
145
        self.pithos_path = join_urls(get_service_path(
146
            pithos_settings.pithos_services, 'object-store'))
147

    
148
    def tearDown(self):
149
        #delete additionally created metadata
150
        meta = self.get_account_meta()
151
        self.delete_account_meta(meta)
152

    
153
        #delete additionally created groups
154
        groups = self.get_account_groups()
155
        self.delete_account_groups(groups)
156

    
157
        self._clean_account()
158

    
159
    def _clean_account(self):
160
        for c in self.list_containers():
161
            self.delete_container_content(c['name'])
162
            self.delete_container(c['name'])
163

    
164
    def head(self, url, user='user', *args, **kwargs):
165
        with astakos_user(user):
166
            response = self.client.head(url, *args, **kwargs)
167
        return response
168

    
169
    def get(self, url, user='user', *args, **kwargs):
170
        with astakos_user(user):
171
            response = self.client.get(url, *args, **kwargs)
172
        return response
173

    
174
    def delete(self, url, user='user', *args, **kwargs):
175
        with astakos_user(user):
176
            response = self.client.delete(url, *args, **kwargs)
177
        return response
178

    
179
    def post(self, url, user='user', *args, **kwargs):
180
        with astakos_user(user):
181
            kwargs.setdefault('content_type', 'application/octet-stream')
182
            response = self.client.post(url, *args, **kwargs)
183
        return response
184

    
185
    def put(self, url, user='user', *args, **kwargs):
186
        with astakos_user(user):
187
            kwargs.setdefault('content_type', 'application/octet-stream')
188
            response = self.client.put(url, *args, **kwargs)
189
        return response
190

    
191
    def update_account_meta(self, meta):
192
        kwargs = dict(
193
            ('HTTP_X_ACCOUNT_META_%s' % k, str(v)) for k, v in meta.items())
194
        url = join_urls(self.pithos_path, self.user)
195
        r = self.post('%s?update=' % url, **kwargs)
196
        self.assertEqual(r.status_code, 202)
197
        account_meta = self.get_account_meta()
198
        (self.assertTrue('X-Account-Meta-%s' % k in account_meta) for
199
            k in meta.keys())
200
        (self.assertEqual(account_meta['X-Account-Meta-%s' % k], v) for
201
            k, v in meta.items())
202

    
203
    def reset_account_meta(self, meta):
204
        kwargs = dict(
205
            ('HTTP_X_ACCOUNT_META_%s' % k, str(v)) for k, v in meta.items())
206
        url = join_urls(self.pithos_path, self.user)
207
        r = self.post(url, **kwargs)
208
        self.assertEqual(r.status_code, 202)
209
        account_meta = self.get_account_meta()
210
        (self.assertTrue('X-Account-Meta-%s' % k in account_meta) for
211
            k in meta.keys())
212
        (self.assertEqual(account_meta['X-Account-Meta-%s' % k], v) for
213
            k, v in meta.items())
214

    
215
    def delete_account_meta(self, meta):
216
        transform = lambda k: 'HTTP_%s' % k.replace('-', '_').upper()
217
        kwargs = dict((transform(k), '') for k, v in meta.items())
218
        url = join_urls(self.pithos_path, self.user)
219
        r = self.post('%s?update=' % url, **kwargs)
220
        self.assertEqual(r.status_code, 202)
221
        account_meta = self.get_account_meta()
222
        (self.assertTrue('X-Account-Meta-%s' % k not in account_meta) for
223
            k in meta.keys())
224
        return r
225

    
226
    def delete_account_groups(self, groups):
227
        url = join_urls(self.pithos_path, self.user)
228
        r = self.post('%s?update=' % url, **groups)
229
        self.assertEqual(r.status_code, 202)
230
        return r
231

    
232
    def get_account_info(self, until=None):
233
        url = join_urls(self.pithos_path, self.user)
234
        if until is not None:
235
            parts = list(urlsplit(url))
236
            parts[3] = urlencode({
237
                'until': until
238
            })
239
            url = urlunsplit(parts)
240
        r = self.head(url)
241
        self.assertEqual(r.status_code, 204)
242
        return r
243

    
244
    def get_account_meta(self, until=None):
245
        r = self.get_account_info(until=until)
246
        headers = dict(r._headers.values())
247
        map(headers.pop,
248
            [k for k in headers.keys()
249
                if not k.startswith('X-Account-Meta-')])
250
        return headers
251

    
252
    def get_account_groups(self, until=None):
253
        r = self.get_account_info(until=until)
254
        headers = dict(r._headers.values())
255
        map(headers.pop,
256
            [k for k in headers.keys()
257
                if not k.startswith('X-Account-Group-')])
258
        return headers
259

    
260
    def get_container_info(self, container, until=None):
261
        url = join_urls(self.pithos_path, self.user, container)
262
        if until is not None:
263
            parts = list(urlsplit(url))
264
            parts[3] = urlencode({
265
                'until': until
266
            })
267
            url = urlunsplit(parts)
268
        r = self.head(url)
269
        self.assertEqual(r.status_code, 204)
270
        return r
271

    
272
    def get_container_meta(self, container, until=None):
273
        r = self.get_container_info(container, until=until)
274
        headers = dict(r._headers.values())
275
        map(headers.pop,
276
            [k for k in headers.keys()
277
                if not k.startswith('X-Container-Meta-')])
278
        return headers
279

    
280
    def update_container_meta(self, container, meta):
281
        kwargs = dict(
282
            ('HTTP_X_CONTAINER_META_%s' % k, str(v)) for k, v in meta.items())
283
        url = join_urls(self.pithos_path, self.user, container)
284
        r = self.post('%s?update=' % url, **kwargs)
285
        self.assertEqual(r.status_code, 202)
286
        container_meta = self.get_container_meta(container)
287
        (self.assertTrue('X-Container-Meta-%s' % k in container_meta) for
288
            k in meta.keys())
289
        (self.assertEqual(container_meta['X-Container-Meta-%s' % k], v) for
290
            k, v in meta.items())
291

    
292
    def list_containers(self, format='json', headers={}, **params):
293
        _url = join_urls(self.pithos_path, self.user)
294
        parts = list(urlsplit(_url))
295
        params['format'] = format
296
        parts[3] = urlencode(params)
297
        url = urlunsplit(parts)
298
        _headers = dict(('HTTP_%s' % k.upper(), str(v))
299
                        for k, v in headers.items())
300
        r = self.get(url, **_headers)
301

    
302
        if format is None:
303
            containers = r.content.split('\n')
304
            if '' in containers:
305
                containers.remove('')
306
            return containers
307
        elif format == 'json':
308
            try:
309
                containers = json.loads(r.content)
310
            except:
311
                self.fail('json format expected')
312
            return containers
313
        elif format == 'xml':
314
            return minidom.parseString(r.content)
315

    
316
    def delete_container_content(self, cname):
317
        url = join_urls(self.pithos_path, self.user, cname)
318
        r = self.delete('%s?delimiter=/' % url)
319
        self.assertEqual(r.status_code, 204)
320
        return r
321

    
322
    def delete_container(self, cname):
323
        url = join_urls(self.pithos_path, self.user, cname)
324
        r = self.delete(url)
325
        self.assertEqual(r.status_code, 204)
326
        return r
327

    
328
    def create_container(self, cname):
329
        url = join_urls(self.pithos_path, self.user, cname)
330
        r = self.put(url, data='')
331
        self.assertTrue(r.status_code in (202, 201))
332
        return r
333

    
334
    def upload_object(self, cname, oname=None, length=None, verify=True,
335
                      **meta):
336
        oname = oname or get_random_name()
337
        length = length or random.randint(TEST_BLOCK_SIZE, 2 * TEST_BLOCK_SIZE)
338
        data = get_random_data(length=length)
339
        headers = dict(('HTTP_X_OBJECT_META_%s' % k.upper(), v)
340
                       for k, v in meta.iteritems())
341
        url = join_urls(self.pithos_path, self.user, cname, oname)
342
        r = self.put(url, data=data, **headers)
343
        if verify:
344
            self.assertEqual(r.status_code, 201)
345
        return oname, data, r
346

    
347
    def update_object_data(self, cname, oname=None, length=None,
348
                           content_type=None, content_range=None,
349
                           verify=True, **meta):
350
        oname = oname or get_random_name()
351
        length = length or random.randint(TEST_BLOCK_SIZE, 2 * TEST_BLOCK_SIZE)
352
        content_type = content_type or 'application/octet-stream'
353
        data = get_random_data(length=length)
354
        headers = dict(('HTTP_X_OBJECT_META_%s' % k.upper(), v)
355
                       for k, v in meta.iteritems())
356
        if content_range:
357
            headers['HTTP_CONTENT_RANGE'] = content_range
358
        url = join_urls(self.pithos_path, self.user, cname, oname)
359
        r = self.post(url, data=data, content_type=content_type, **headers)
360
        if verify:
361
            self.assertEqual(r.status_code, 204)
362
        return oname, data, r
363

    
364
    def append_object_data(self, cname, oname=None, length=None,
365
                           content_type=None):
366
        return self.update_object_data(cname, oname=oname,
367
                                       length=length,
368
                                       content_type=content_type,
369
                                       content_range='bytes */*')
370

    
371
    def create_folder(self, cname, oname=None, **headers):
372
        oname = oname or get_random_name()
373
        url = join_urls(self.pithos_path, self.user, cname, oname)
374
        r = self.put(url, data='', content_type='application/directory',
375
                     **headers)
376
        self.assertEqual(r.status_code, 201)
377
        return oname, r
378

    
379
    def list_objects(self, cname, prefix=None):
380
        url = join_urls(self.pithos_path, self.user, cname)
381
        path = '%s?format=json' % url
382
        if prefix is not None:
383
            path = '%s&prefix=%s' % (path, prefix)
384
        r = self.get(path)
385
        self.assertTrue(r.status_code in (200, 204))
386
        try:
387
            objects = json.loads(r.content)
388
        except:
389
            self.fail('json format expected')
390
        return objects
391

    
392
    def get_object_info(self, container, object, version=None, until=None):
393
        url = join_urls(self.pithos_path, self.user, container, object)
394
        if until is not None:
395
            parts = list(urlsplit(url))
396
            parts[3] = urlencode({
397
                'until': until
398
            })
399
            url = urlunsplit(parts)
400
        if version:
401
            url = '%s?version=%s' % (url, version)
402
        r = self.head(url)
403
        self.assertEqual(r.status_code, 200)
404
        return r
405

    
406
    def get_object_meta(self, container, object, version=None, until=None):
407
        r = self.get_object_info(container, object, version, until=until)
408
        headers = dict(r._headers.values())
409
        map(headers.pop,
410
            [k for k in headers.keys()
411
                if not k.startswith('X-Object-Meta-')])
412
        return headers
413

    
414
    def update_object_meta(self, container, object, meta):
415
        kwargs = dict(
416
            ('HTTP_X_OBJECT_META_%s' % k, str(v)) for k, v in meta.items())
417
        url = join_urls(self.pithos_path, self.user, container, object)
418
        r = self.post('%s?update=' % url, content_type='', **kwargs)
419
        self.assertEqual(r.status_code, 202)
420
        object_meta = self.get_object_meta(container, object)
421
        (self.assertTrue('X-Objecr-Meta-%s' % k in object_meta) for
422
            k in meta.keys())
423
        (self.assertEqual(object_meta['X-Object-Meta-%s' % k], v) for
424
            k, v in meta.items())
425

    
426
    def assert_extended(self, data, format, type, size=10000):
427
        if format == 'xml':
428
            self._assert_xml(data, type, size)
429
        elif format == 'json':
430
            self._assert_json(data, type, size)
431

    
432
    def _assert_json(self, data, type, size):
433
        convert = lambda s: s.lower()
434
        info = [convert(elem) for elem in details[type]]
435
        self.assertTrue(len(data) <= size)
436
        for item in info:
437
            for i in data:
438
                if 'subdir' in i.keys():
439
                    continue
440
                self.assertTrue(item in i.keys())
441

    
442
    def _assert_xml(self, data, type, size):
443
        convert = lambda s: s.lower()
444
        info = [convert(elem) for elem in details[type]]
445
        try:
446
            info.remove('content_encoding')
447
        except ValueError:
448
            pass
449
        xml = data
450
        entities = xml.getElementsByTagName(type)
451
        self.assertTrue(len(entities) <= size)
452
        for e in entities:
453
            for item in info:
454
                self.assertTrue(e.getElementsByTagName(item))
455

    
456

    
457
class AssertMappingInvariant(object):
458
    def __init__(self, callable, *args, **kwargs):
459
        self.callable = callable
460
        self.args = args
461
        self.kwargs = kwargs
462

    
463
    def __enter__(self):
464
        self.map = self.callable(*self.args, **self.kwargs)
465
        return self.map
466

    
467
    def __exit__(self, type, value, tb):
468
        map = self.callable(*self.args, **self.kwargs)
469
        for k, v in self.map.items():
470
            if is_date(v):
471
                continue
472

    
473
            assert(k in map), '%s not in map' % k
474
            assert v == map[k]
475

    
476

    
477
class AssertUUidInvariant(object):
478
    def __init__(self, callable, *args, **kwargs):
479
        self.callable = callable
480
        self.args = args
481
        self.kwargs = kwargs
482

    
483
    def __enter__(self):
484
        self.map = self.callable(*self.args, **self.kwargs)
485
        assert('x-object-uuid' in self.map)
486
        self.uuid = self.map['x-object-uuid']
487
        return self.map
488

    
489
    def __exit__(self, type, value, tb):
490
        map = self.callable(*self.args, **self.kwargs)
491
        assert('x-object-uuid' in self.map)
492
        uuid = map['x-object-uuid']
493
        assert(uuid == self.uuid)