Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / pithos_tests.py @ bfe0d7b2

History | View | Annotate | Download (44 kB)

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

    
34
"""
35
This is the burnin class that tests the Pithos functionality
36

37
"""
38

    
39
import random
40
from datetime import datetime
41
from tempfile import NamedTemporaryFile
42

    
43
from synnefo_tools.burnin.common import BurninTests, Proper
44
from kamaki.clients import ClientError
45

    
46

    
47
def sample_block(f, block):
48
    block_size = 4 * 1024 * 1024
49
    f.seek(block * block_size)
50
    ch = [f.read(1)]
51
    f.seek(block_size / 2, 1)
52
    ch.append(f.read(1))
53
    f.seek((block + 1) * block_size - 1)
54
    ch.append(f.read(1))
55
    return ch
56

    
57

    
58
# Too many public methods. pylint: disable-msg=R0904
59
class PithosTestSuite(BurninTests):
60
    """Test Pithos functionality"""
61
    containers = Proper(value=None)
62
    created_container = Proper(value=None)
63
    now_unformated = Proper(value=datetime.utcnow())
64
    obj_metakey = Proper(value=None)
65
    large_file = Proper(value=None)
66

    
67
    def test_005_account_head(self):
68
        """Test account HEAD"""
69
        self._set_pithos_account(self._get_uuid())
70
        pithos = self.clients.pithos
71
        r = pithos.account_head()
72
        self.assertEqual(r.status_code, 204)
73
        self.info('Returns 204')
74

    
75
        r = pithos.account_head(until='1000000000')
76
        self.assertEqual(r.status_code, 204)
77
        datestring = unicode(r.headers['x-account-until-timestamp'])
78
        self.assertEqual(u'Sun, 09 Sep 2001 01:46:40 GMT', datestring)
79
        self.assertTrue('x-account-policy-quota' in r.headers)
80
        self.info('Until and policy quota exist')
81

    
82
        for format in pithos.DATE_FORMATS:
83
            now_formated = self.now_unformated.strftime(format)
84
            r1 = pithos.account_head(
85
                if_modified_since=now_formated, success=(204, 304, 412))
86
            r2 = pithos.account_head(
87
                if_unmodified_since=now_formated, success=(204, 304, 412))
88
            self.assertNotEqual(r1.status_code, r2.status_code)
89
        self.info('If_(un)modified_since is OK')
90

    
91
    def test_010_account_get(self):
92
        """Test account GET"""
93
        self.info('Preparation')
94
        pithos = self.clients.pithos
95
        for i in range(1, 3):
96
            cont_name = "cont%s_%s%s" % (
97
                i, self.run_id or 0, random.randint(1000, 9999))
98
            self._create_pithos_container(cont_name)
99
        pithos.container, obj = cont_name, 'shared_file'
100
        pithos.create_object(obj)
101
        pithos.set_object_sharing(obj, read_permission='*')
102
        self.info('Created object /%s/%s' % (cont_name, obj))
103

    
104
        #  Try to re-create the same container
105
        pithos.create_container(cont_name)
106

    
107
        r = pithos.list_containers()
108
        fullLen = len(r)
109
        self.assertTrue(fullLen > 2)
110
        self.info('Normal use is OK')
111

    
112
        cnames = [c['name'] for c in r]
113
        self.assertEqual(sorted(list(set(cnames))), sorted(cnames))
114
        self.info('Containers have unique names')
115

    
116
        r = pithos.account_get(limit=1)
117
        self.assertEqual(len(r.json), 1)
118
        self.info('Limit works')
119

    
120
        r = pithos.account_get(marker='cont')
121
        cont1, cont3 = r.json[0], r.json[2]
122
        self.info('Marker works')
123

    
124
        r = pithos.account_get(limit=2, marker='cont')
125
        conames = [container['name'] for container in r.json if (
126
            container['name'].lower().startswith('cont'))]
127
        self.assertTrue(cont1['name'] in conames)
128
        self.assertFalse(cont3['name'] in conames)
129
        self.info('Marker-limit combination works')
130

    
131
        r = pithos.account_get(show_only_shared=True)
132
        self.assertTrue(cont_name in [c['name'] for c in r.json])
133
        self.info('Show-only-shared works')
134

    
135
        r = pithos.account_get(until=1342609206.0)
136
        self.assertTrue(len(r.json) <= fullLen)
137
        self.info('Until works')
138

    
139
        for format in pithos.DATE_FORMATS:
140
            now_formated = self.now_unformated.strftime(format)
141
            r1 = pithos.account_get(
142
                if_modified_since=now_formated, success=(200, 304, 412))
143
            r2 = pithos.account_get(
144
                if_unmodified_since=now_formated, success=(200, 304, 412))
145
            self.assertNotEqual(r1.status_code, r2.status_code)
146
        self.info('If_(un)modified_since is OK')
147

    
148
    def test_015_account_post(self):
149
        """Test account POST"""
150
        pithos = self.clients.pithos
151
        r = pithos.account_post()
152
        self.assertEqual(r.status_code, 202)
153
        self.info('Status code is OK')
154

    
155
        rand_num = '%s%s' % (self.run_id or 0, random.randint(1000, 9999))
156
        grpName = 'grp%s' % rand_num
157

    
158
        u1, u2 = pithos.account, 'invalid-user-uuid-%s' % rand_num
159
        self.assertRaises(
160
            ClientError, pithos.set_account_group, grpName, [u1, u2])
161
        self.info('Invalid uuid is handled correctly')
162

    
163
        pithos.set_account_group(grpName, [u1])
164
        r = pithos.get_account_group()
165
        self.assertEqual(r['x-account-group-' + grpName], '%s' % u1)
166
        self.info('Account group is OK')
167
        pithos.del_account_group(grpName)
168
        r = pithos.get_account_group()
169
        self.assertTrue('x-account-group-' + grpName not in r)
170
        self.info('Removed account group')
171

    
172
        mprefix = 'meta%s' % rand_num
173
        pithos.set_account_meta({
174
            mprefix + '1': 'v1', mprefix + '2': 'v2'})
175
        r = pithos.get_account_meta()
176
        self.assertEqual(r['x-account-meta-' + mprefix + '1'], 'v1')
177
        self.assertEqual(r['x-account-meta-' + mprefix + '2'], 'v2')
178
        self.info('Account meta is OK')
179

    
180
        pithos.del_account_meta(mprefix + '1')
181
        r = pithos.get_account_meta()
182
        self.assertTrue('x-account-meta-' + mprefix + '1' not in r)
183
        self.assertTrue('x-account-meta-' + mprefix + '2' in r)
184
        self.info('Selective removal of account meta is OK')
185

    
186
        pithos.del_account_meta(mprefix + '2')
187
        r = pithos.get_account_meta()
188
        self.assertTrue('x-account-meta-' + mprefix + '2' not in r)
189
        self.info('Temporary account meta are removed')
190

    
191
    def test_020_container_head(self):
192
        """Test container HEAD"""
193
        pithos = self.clients.pithos
194
        r = pithos.container_head()
195
        self.assertEqual(r.status_code, 204)
196
        self.info('Status code is OK')
197

    
198
        r = pithos.container_head(until=1000000, success=(204, 404))
199
        self.assertEqual(r.status_code, 404)
200
        self.info('Until works')
201

    
202
        for format in pithos.DATE_FORMATS:
203
            now_formated = self.now_unformated.strftime(format)
204
            r1 = pithos.container_head(
205
                if_modified_since=now_formated, success=(204, 304, 412))
206
            r2 = pithos.container_head(
207
                if_unmodified_since=now_formated, success=(204, 304, 412))
208
            self.assertNotEqual(r1.status_code, r2.status_code)
209

    
210
        k = 'metakey%s' % random.randint(1000, 9999)
211
        pithos.set_container_meta({k: 'our value'})
212
        r = pithos.get_container_meta()
213
        k = 'x-container-meta-%s' % k
214
        self.assertIn(k, r)
215
        self.assertEqual('our value', r[k])
216
        self.info('Container meta exists')
217

    
218
        self.obj_metakey = 'metakey%s' % random.randint(1000, 9999)
219
        obj = 'object_with_meta'
220
        pithos.create_object(obj)
221
        pithos.set_object_meta(obj, {self.obj_metakey: 'our value'})
222
        r = pithos.get_container_object_meta()
223
        self.assertIn('x-container-object-meta', r)
224
        self.assertIn(
225
            self.obj_metakey, r['x-container-object-meta'].lower())
226
        self.info('Container object meta exists')
227

    
228
    def test_025_container_get(self):
229
        """Test container GET"""
230
        pithos = self.clients.pithos
231

    
232
        r = pithos.container_get()
233
        self.assertEqual(r.status_code, 200)
234
        self.info('Status code is OK')
235

    
236
        fullLen = len(r.json)
237
        self.assertGreater(fullLen, 0)
238
        self.info('There are enough (%s) containers' % fullLen)
239

    
240
        obj1 = 'test%s' % random.randint(1000, 9999)
241
        pithos.create_object(obj1)
242
        obj2 = 'test%s' % random.randint(1000, 9999)
243
        pithos.create_object(obj2)
244
        obj3 = 'another%s.test' % random.randint(1000, 9999)
245
        pithos.create_object(obj3)
246

    
247
        r = pithos.container_get(prefix='test')
248
        self.assertTrue(len(r.json) > 1)
249
        test_objects = [o for o in r.json if o['name'].startswith('test')]
250
        self.assertEqual(len(r.json), len(test_objects))
251
        self.info('Prefix is OK')
252

    
253
        r = pithos.container_get(limit=1)
254
        self.assertEqual(len(r.json), 1)
255
        self.info('Limit is OK')
256

    
257
        r = pithos.container_get(marker=obj3[:-5])
258
        self.assertTrue(len(r.json) > 1)
259
        aoobjects = [obj for obj in r.json if obj['name'] > obj3[:-5]]
260
        self.assertEqual(len(r.json), len(aoobjects))
261
        self.info('Marker is OK')
262

    
263
        r = pithos.container_get(prefix=obj3, delimiter='.')
264
        self.assertTrue(fullLen > len(r.json))
265
        self.info('Delimiter is OK')
266

    
267
        r = pithos.container_get(path='/')
268
        fullLen += 3
269
        self.assertEqual(fullLen, len(r.json))
270
        self.info('Path is OK')
271

    
272
        r = pithos.container_get(format='xml')
273
        self.assertEqual(r.text.split()[4], 'name="' + pithos.container + '">')
274
        self.info('Format is OK')
275

    
276
        r = pithos.container_get(meta=[self.obj_metakey, ])
277
        self.assertTrue(len(r.json) > 0)
278
        self.info('Meta is OK')
279

    
280
        r = pithos.container_get(show_only_shared=True)
281
        self.assertTrue(len(r.json) < fullLen)
282
        self.info('Show-only-shared is OK')
283

    
284
        try:
285
            r = pithos.container_get(until=1000000000)
286
            datestring = unicode(r.headers['x-account-until-timestamp'])
287
            self.assertEqual(u'Sun, 09 Sep 2001 01:46:40 GMT', datestring)
288
            self.info('Until is OK')
289
        except ClientError:
290
            pass
291

    
292
    def test_030_container_put(self):
293
        """Test container PUT"""
294
        pithos = self.clients.pithos
295
        pithos.container = 'cont%s%s' % (
296
            self.run_id or 0, random.randint(1000, 9999))
297
        self.temp_containers.append(pithos.container)
298

    
299
        r = pithos.create_container()
300
        self.assertTrue(isinstance(r, dict))
301

    
302
        r = pithos.get_container_limit(pithos.container)
303
        cquota = r.values()[0]
304
        newquota = 2 * int(cquota)
305
        self.info('Limit is OK')
306
        pithos.del_container()
307

    
308
        r = pithos.create_container(sizelimit=newquota)
309
        self.assertTrue(isinstance(r, dict))
310

    
311
        r = pithos.get_container_limit(pithos.container)
312
        xquota = int(r.values()[0])
313
        self.assertEqual(newquota, xquota)
314
        self.info('Can set container limit')
315
        pithos.del_container()
316

    
317
        r = pithos.create_container(versioning='auto')
318
        self.assertTrue(isinstance(r, dict))
319

    
320
        r = pithos.get_container_versioning(pithos.container)
321
        nvers = r.values()[0]
322
        self.assertEqual('auto', nvers)
323
        self.info('Versioning=auto is OK')
324
        pithos.del_container()
325

    
326
        r = pithos.container_put(versioning='none')
327
        self.assertEqual(r.status_code, 201)
328

    
329
        r = pithos.get_container_versioning(pithos.container)
330
        nvers = r.values()[0]
331
        self.assertEqual('none', nvers)
332
        self.info('Versioning=none is OK')
333
        pithos.del_container()
334

    
335
        r = pithos.create_container(metadata={'m1': 'v1', 'm2': 'v2'})
336
        self.assertTrue(isinstance(r, dict))
337

    
338
        r = pithos.get_container_meta(pithos.container)
339
        self.assertTrue('x-container-meta-m1' in r)
340
        self.assertEqual(r['x-container-meta-m1'], 'v1')
341
        self.assertTrue('x-container-meta-m2' in r)
342
        self.assertEqual(r['x-container-meta-m2'], 'v2')
343

    
344
        r = pithos.container_put(metadata={'m1': '', 'm2': 'v2a'})
345
        self.assertEqual(r.status_code, 202)
346

    
347
        r = pithos.get_container_meta(pithos.container)
348
        self.assertTrue('x-container-meta-m1' not in r)
349
        self.assertTrue('x-container-meta-m2' in r)
350
        self.assertEqual(r['x-container-meta-m2'], 'v2a')
351
        self.info('Container meta is OK')
352

    
353
        pithos.del_container_meta(pithos.container)
354

    
355
    def test_035_container_post(self):
356
        """Test container POST"""
357
        pithos = self.clients.pithos
358

    
359
        r = pithos.container_post()
360
        self.assertEqual(r.status_code, 202)
361
        self.info('Status is OK')
362

    
363
        pithos.set_container_meta({'m1': 'v1', 'm2': 'v2'})
364
        r = pithos.get_container_meta(pithos.container)
365
        self.assertTrue('x-container-meta-m1' in r)
366
        self.assertEqual(r['x-container-meta-m1'], 'v1')
367
        self.assertTrue('x-container-meta-m2' in r)
368
        self.assertEqual(r['x-container-meta-m2'], 'v2')
369
        self.info('Set metadata works')
370

    
371
        r = pithos.del_container_meta('m1')
372
        r = pithos.set_container_meta({'m2': 'v2a'})
373
        r = pithos.get_container_meta(pithos.container)
374
        self.assertTrue('x-container-meta-m1' not in r)
375
        self.assertTrue('x-container-meta-m2' in r)
376
        self.assertEqual(r['x-container-meta-m2'], 'v2a')
377
        self.info('Delete metadata works')
378

    
379
        r = pithos.get_container_limit(pithos.container)
380
        cquota = r.values()[0]
381
        newquota = 2 * int(cquota)
382
        r = pithos.set_container_limit(newquota)
383
        r = pithos.get_container_limit(pithos.container)
384
        xquota = int(r.values()[0])
385
        self.assertEqual(newquota, xquota)
386
        self.info('Set quota works')
387

    
388
        pithos.set_container_versioning('auto')
389
        r = pithos.get_container_versioning(pithos.container)
390
        nvers = r.values()[0]
391
        self.assertEqual('auto', nvers)
392
        pithos.set_container_versioning('none')
393
        r = pithos.get_container_versioning(pithos.container)
394
        nvers = r.values()[0]
395
        self.assertEqual('none', nvers)
396
        self.info('Set versioning works')
397

    
398
        f = self._create_large_file(1024 * 1024 * 100)
399
        self.large_file = f
400
        self.info('Created file %s of 100 MB' % f.name)
401

    
402
        pithos.create_directory('dir')
403
        self.info('Upload the file ...')
404
        r = pithos.upload_object('/dir/sample.file', f)
405
        for term in ('content-length', 'content-type', 'x-object-version'):
406
            self.assertTrue(term in r)
407
        r = pithos.get_object_info('/dir/sample.file')
408
        self.assertTrue(int(r['content-length']) > 100000000)
409
        self.info('Made remote directory /dir and object /dir/sample.file')
410

    
411
        """What is tranfer_encoding? What should I check about it? """
412
        #TODO
413

    
414
        obj = 'object_with_meta'
415
        pithos.container = self.temp_containers[-2]
416
        r = pithos.object_post(
417
            obj, update='False', metadata={'newmeta': 'newval'})
418

    
419
        r = pithos.get_object_info(obj)
420
        self.assertTrue('x-object-meta-newmeta' in r)
421
        self.assertFalse('x-object-meta-%s' % self.obj_metakey not in r)
422
        self.info('Metadata with update=False works')
423

    
424
    def test_040_container_delete(self):
425
        """Test container DELETE"""
426
        pithos = self.clients.pithos
427

    
428
        r = pithos.container_delete(success=409)
429
        self.assertEqual(r.status_code, 409)
430
        self.assertRaises(ClientError, pithos.container_delete)
431
        self.info('Successfully failed to delete non-empty container')
432

    
433
        r = pithos.container_delete(until='1000000000')
434
        self.assertEqual(r.status_code, 204)
435
        self.info('Successfully failed to delete old-timestamped container')
436

    
437
        obj_names = [o['name'] for o in pithos.container_get().json]
438
        pithos.del_container(delimiter='/')
439
        r = pithos.container_get()
440
        self.assertEqual(len(r.json), 0)
441
        self.info('Successfully emptied container')
442

    
443
        for obj in obj_names:
444
            r = pithos.get_object_versionlist(obj)
445
            self.assertTrue(len(r) > 0)
446
        self.info('Versions are still there')
447

    
448
        pithos.purge_container()
449
        for obj in obj_names:
450
            self.assertRaises(ClientError, pithos.get_object_versionlist, obj)
451
        self.info('Successfully purged container')
452

    
453
        self.temp_containers.remove(pithos.container)
454
        pithos.container = self.temp_containers[-1]
455

    
456
    def test_045_object_head(self):
457
        """Test object HEAD"""
458
        pithos = self.clients.pithos
459

    
460
        obj = 'dir/sample.file'
461
        r = pithos.object_head(obj)
462
        self.assertEqual(r.status_code, 200)
463
        self.info('Status code is OK')
464
        etag = r.headers['etag']
465
        real_version = r.headers['x-object-version']
466

    
467
        self.assertRaises(ClientError, pithos.object_head, obj, version=-10)
468
        r = pithos.object_head(obj, version=real_version)
469
        self.assertEqual(r.headers['x-object-version'], real_version)
470
        self.info('Version works')
471

    
472
        r = pithos.object_head(obj, if_etag_match=etag)
473
        self.assertEqual(r.status_code, 200)
474
        self.info('if-etag-match is OK')
475

    
476
        r = pithos.object_head(
477
            obj, if_etag_not_match=etag, success=(200, 412, 304))
478
        self.assertNotEqual(r.status_code, 200)
479
        self.info('if-etag-not-match works')
480

    
481
        r = pithos.object_head(
482
            obj, version=real_version, if_etag_match=etag, success=200)
483
        self.assertEqual(r.status_code, 200)
484
        self.info('Version with if-etag-match works')
485

    
486
        for format in pithos.DATE_FORMATS:
487
            now_formated = self.now_unformated.strftime(format)
488
            r1 = pithos.object_head(
489
                obj, if_modified_since=now_formated, success=(200, 304, 412))
490
            r2 = pithos.object_head(
491
                obj, if_unmodified_since=now_formated, success=(200, 304, 412))
492
            self.assertNotEqual(r1.status_code, r2.status_code)
493
        self.info('if-(un)modified-since works')
494

    
495
    def test_050_object_get(self):
496
        """Test object GET"""
497
        pithos = self.clients.pithos
498
        obj = 'dir/sample.file'
499

    
500
        r = pithos.object_get(obj)
501
        self.assertEqual(r.status_code, 200)
502
        self.info('Status code is OK')
503

    
504
        osize = int(r.headers['content-length'])
505
        etag = r.headers['etag']
506

    
507
        r = pithos.object_get(obj, hashmap=True)
508
        self.assertEqual(
509
            set(('hashes', 'block_size', 'block_hash', 'bytes')), set(r.json))
510
        self.info('Hashmap works')
511
        hash0 = r.json['hashes'][0]
512

    
513
        r = pithos.object_get(obj, format='xml', hashmap=True)
514
        self.assertTrue(r.text.split('hash>')[1].startswith(hash0))
515
        self.info('Hashmap with XML format works')
516

    
517
        rangestr = 'bytes=%s-%s' % (osize / 3, osize / 2)
518
        r = pithos.object_get(obj, data_range=rangestr, success=(200, 206))
519
        partsize = int(r.headers['content-length'])
520
        self.assertTrue(0 < partsize and partsize <= 1 + osize / 3)
521
        self.info('Range x-y works')
522
        orig = r.text
523

    
524
        rangestr = 'bytes=%s' % (osize / 3)
525
        r = pithos.object_get(
526
            obj, data_range=rangestr, if_range=True, success=(200, 206))
527
        partsize = int(r.headers['content-length'])
528
        self.assertTrue(partsize, 1 + (osize / 3))
529
        diff = set(r.text).symmetric_difference(set(orig[:partsize]))
530
        self.assertEqual(len(diff), 0)
531
        self.info('Range x works')
532

    
533
        rangestr = 'bytes=-%s' % (osize / 3)
534
        r = pithos.object_get(
535
            obj, data_range=rangestr, if_range=True, success=(200, 206))
536
        partsize = int(r.headers['content-length'])
537
        self.assertTrue(partsize, osize / 3)
538
        diff = set(r.text).symmetric_difference(set(orig[-partsize:]))
539
        self.assertEqual(len(diff), 0)
540
        self.info('Range -x works')
541

    
542
        r = pithos.object_get(obj, if_etag_match=etag)
543
        self.assertEqual(r.status_code, 200)
544
        self.info('if-etag-match works')
545

    
546
        r = pithos.object_get(obj, if_etag_not_match=etag + 'LALALA')
547
        self.assertEqual(r.status_code, 200)
548
        self.info('if-etag-not-match works')
549

    
550
        for format in pithos.DATE_FORMATS:
551
            now_formated = self.now_unformated.strftime(format)
552
            r1 = pithos.object_get(
553
                obj, if_modified_since=now_formated, success=(200, 304, 412))
554
            r2 = pithos.object_get(
555
                obj, if_unmodified_since=now_formated, success=(200, 304, 412))
556
            self.assertNotEqual(r1.status_code, r2.status_code)
557
        self.info('if(un)modified-since works')
558

    
559
        obj, dnl_f = 'dir/sample.file', NamedTemporaryFile()
560
        self.info('Download %s as %s ...' % (obj, dnl_f.name))
561
        pithos.download_object(obj, dnl_f)
562
        self.info('Download is completed')
563

    
564
        f_size = len(orig)
565
        for pos in (0, f_size / 2, f_size - 128):
566
            dnl_f.seek(pos)
567
            self.large_file.seek(pos)
568
            self.assertEqual(self.large_file.read(64), dnl_f.read(64))
569
        self.info('Sampling shows that files match')
570

    
571
        self.info('Create a boring file of 42 blocks...')
572
        bor_f = self._create_boring_file(42)
573
        trg_fname = 'dir/uploaded.file'
574
        self.info('Now, upload the boring file as %s...' % trg_fname)
575
        pithos.upload_object(trg_fname, bor_f)
576
        self.info('Boring file %s is uploaded as %s' % (bor_f.name, trg_fname))
577
        dnl_f = NamedTemporaryFile()
578
        self.info('Download boring file as %s' % dnl_f.name)
579
        pithos.download_object(trg_fname, dnl_f)
580
        self.info('File is downloaded')
581

    
582
        for i in range(42):
583
            self.assertEqual(sample_block(bor_f, i), sample_block(dnl_f, i))
584

    
585
    def test_055_object_put(self):
586
        """Test object PUT"""
587
        pithos = self.clients.pithos
588
        obj = 'sample.file'
589

    
590
        pithos.create_object(obj + '.FAKE')
591
        r = pithos.get_object_info(obj + '.FAKE')
592
        self.assertEqual(
593
            set(r['content-type']), set('application/octer-stream'))
594
        self.info('Simple call creates a new object correctly')
595

    
596
        r = pithos.object_put(
597
            obj,
598
            data='a',
599
            content_type='application/octer-stream',
600
            permissions=dict(
601
                read=['accX:groupA', 'u1', 'u2'],
602
                write=['u2', 'u3']),
603
            metadata=dict(key1='val1', key2='val2'),
604
            content_encoding='UTF-8',
605
            content_disposition='attachment; filename="fname.ext"')
606
        self.assertEqual(r.status_code, 201)
607
        self.info('Status code is OK')
608
        etag = r.headers['etag']
609

    
610
        r = pithos.get_object_info(obj)
611
        self.assertTrue('content-disposition' in r)
612
        self.assertEqual(
613
            r['content-disposition'], 'attachment; filename="fname.ext"')
614
        self.info('Content-disposition is OK')
615

    
616
        sharing = r['x-object-sharing'].split('; ')
617
        self.assertTrue(sharing[0].startswith('read='))
618
        read = set(sharing[0][5:].split(','))
619
        self.assertEqual(set(('u1', 'accx:groupa')), read)
620
        self.assertTrue(sharing[1].startswith('write='))
621
        write = set(sharing[1][6:].split(','))
622
        self.assertEqual(set(('u2', 'u3')), write)
623
        self.info('Permissions are OK')
624

    
625
        r = pithos.get_object_meta(obj)
626
        self.assertEqual(r['x-object-meta-key1'], 'val1')
627
        self.assertEqual(r['x-object-meta-key2'], 'val2')
628
        self.info('Meta are OK')
629

    
630
        pithos.object_put(
631
            obj,
632
            if_etag_match=etag,
633
            data='b',
634
            content_type='application/octet-stream',
635
            public=True)
636
        self.info('If-etag-match is OK')
637

    
638
        r = pithos.object_get(obj)
639
        self.assertTrue('x-object-public' in r.headers)
640
        self.info('Publishing works')
641

    
642
        etag = r.headers['etag']
643
        self.assertEqual(r.text, 'b')
644
        self.info('Remote object content is correct')
645

    
646
        r = pithos.object_put(
647
            obj,
648
            if_etag_not_match=etag,
649
            data='c',
650
            content_type='application/octet-stream',
651
            success=(201, 412))
652
        self.assertEqual(r.status_code, 412)
653
        self.info('If-etag-not-match is OK')
654

    
655
        r = pithos.get_object_info('dir')
656
        self.assertEqual(r['content-type'], 'application/directory')
657
        self.info('Directory has been created correctly')
658

    
659
        r = pithos.object_put(
660
            '%s_v2' % obj,
661
            format=None,
662
            copy_from='/%s/%s' % (pithos.container, obj),
663
            content_encoding='application/octet-stream',
664
            source_account=pithos.account,
665
            content_length=0,
666
            success=201)
667
        self.assertEqual(r.status_code, 201)
668
        r1 = pithos.get_object_info(obj)
669
        r2 = pithos.get_object_info('%s_v2' % obj)
670
        self.assertEqual(r1['x-object-hash'], r2['x-object-hash'])
671
        self.info('Object has being copied in same container, OK')
672

    
673
        pithos.copy_object(
674
            src_container=pithos.container,
675
            src_object=obj,
676
            dst_container=self.temp_containers[-2],
677
            dst_object='%s_new' % obj)
678
        pithos.container = self.temp_containers[-2]
679
        r1 = pithos.get_object_info('%s_new' % obj)
680
        pithos.container = self.temp_containers[-1]
681
        r2 = pithos.get_object_info(obj)
682
        self.assertEqual(r1['x-object-hash'], r2['x-object-hash'])
683
        self.info('Object has being copied in another container, OK')
684

    
685
        fromstr = '/%s/%s_new' % (self.temp_containers[-2], obj)
686
        r = pithos.object_put(
687
            obj,
688
            format=None,
689
            copy_from=fromstr,
690
            content_encoding='application/octet-stream',
691
            source_account=pithos.account,
692
            content_length=0,
693
            success=201)
694
        self.assertEqual(r.status_code, 201)
695
        self.info('Cross container put accepts content_encoding')
696

    
697
        r = pithos.get_object_info(obj)
698
        self.assertEqual(r['etag'], etag)
699
        self.info('Etag is OK')
700

    
701
        r = pithos.object_put(
702
            '%s_v3' % obj,
703
            format=None,
704
            move_from=fromstr,
705
            content_encoding='application/octet-stream',
706
            source_account='nonExistendAddress@NeverLand.com',
707
            content_length=0,
708
            success=(403, ))
709
        self.info('Fake source account is handled correctly')
710

    
711
        r1 = pithos.get_object_info(obj)
712
        pithos.container = self.temp_containers[-2]
713
        pithos.move_object(
714
            src_container=self.temp_containers[-1],
715
            src_object=obj,
716
            dst_container=pithos.container,
717
            dst_object=obj + '_new')
718
        r0 = pithos.get_object_info(obj + '_new')
719
        self.assertEqual(r1['x-object-hash'], r0['x-object-hash'])
720
        self.info('Cross container move is OK')
721

    
722
        pithos.container = self.temp_containers[-1]
723
        pithos.create_container(versioning='auto')
724
        pithos.upload_from_string(obj, 'first version')
725
        source_hashmap = pithos.get_object_hashmap(obj)['hashes']
726
        pithos.upload_from_string(obj, 'second version')
727
        pithos.upload_from_string(obj, 'third version')
728
        versions = pithos.get_object_versionlist(obj)
729
        self.assertEqual(len(versions), 3)
730
        vers0 = versions[0][0]
731

    
732
        pithos.container = self.temp_containers[-2]
733
        pithos.object_put(
734
            obj,
735
            format=None,
736
            move_from='/%s/%s' % (self.temp_containers[-1], obj),
737
            source_version=vers0,
738
            content_encoding='application/octet-stream',
739
            content_length=0, success=201)
740
        target_hashmap = pithos.get_object_hashmap(obj)['hashes']
741
        self.info('Source-version is probably not OK (Check bug #4963)')
742
        source_hashmap, target_hashmap = source_hashmap, target_hashmap
743
        #  Comment out until it's fixed
744
        #  self.assertEqual(source_hashmap, target_hashmap)
745
        #  self.info('Source-version is OK')
746

    
747
        mobj = 'manifest.test'
748
        txt = ''
749
        for i in range(10):
750
            txt += '%s' % i
751
            pithos.object_put(
752
                '%s/%s' % (mobj, i),
753
                data='%s' % i,
754
                content_length=1,
755
                success=201,
756
                content_type='application/octet-stream',
757
                content_encoding='application/octet-stream')
758
        pithos.object_put(
759
            mobj,
760
            content_length=0,
761
            content_type='application/octet-stream',
762
            manifest='%s/%s' % (pithos.container, mobj))
763
        r = pithos.object_get(mobj)
764
        self.assertEqual(r.text, txt)
765
        self.info('Manifest file creation works')
766

    
767
        f = self._create_large_file(1024 * 10)
768
        pithos.upload_object('sample.file', f)
769
        r = pithos.get_object_info('sample.file')
770
        self.assertEqual(int(r['content-length']), 10240)
771
        self.info('Overwrite is OK')
772

    
773
        """MISSING: test transfer-encoding?"""
774

    
775
    def test_060_object_copy(self):
776
        pithos = self.clients.pithos
777
        obj, trg = 'source.file2copy', 'copied.file'
778
        data = '{"key1":"val1", "key2":"val2"}'
779

    
780
        r = pithos.object_put(
781
            obj,
782
            content_type='application/octet-stream',
783
            data=data,
784
            metadata=dict(mkey1='mval1', mkey2='mval2'),
785
            permissions=dict(
786
                read=['accX:groupA', 'u1', 'u2'],
787
                write=['u2', 'u3']),
788
            content_disposition='attachment; filename="fname.ext"')
789
        self.info('Prepared a file /%s/%s' % (pithos.container, obj))
790

    
791
        r = pithos.object_copy(
792
            obj,
793
            destination='/%s/%s' % (pithos.container, trg),
794
            ignore_content_type=False, content_type='application/json',
795
            metadata={'mkey2': 'mval2a', 'mkey3': 'mval3'},
796
            permissions={'write': ['u5', 'accX:groupB']})
797
        self.assertEqual(r.status_code, 201)
798
        self.info('Status code is OK')
799

    
800
        r = pithos.get_object_info(trg)
801
        self.assertTrue('content-disposition' in r)
802
        self.info('Content-disposition is OK')
803

    
804
        self.assertEqual(r['x-object-meta-mkey1'], 'mval1')
805
        self.assertEqual(r['x-object-meta-mkey2'], 'mval2a')
806
        self.assertEqual(r['x-object-meta-mkey3'], 'mval3')
807
        self.info('Metadata are OK')
808

    
809
        r = pithos.get_object_sharing(trg)
810
        self.assertFalse('read' in r or 'u2' in r['write'])
811
        self.assertTrue('accx:groupb' in r['write'])
812
        self.info('Sharing is OK')
813

    
814
        r = pithos.object_copy(
815
            obj,
816
            destination='/%s/%s' % (pithos.container, obj),
817
            content_encoding='utf8',
818
            content_type='application/json',
819
            destination_account='nonExistendAddress@NeverLand.com',
820
            success=(201, 404))
821
        self.assertEqual(r.status_code, 404)
822
        self.info('Non existing UUID correctly causes a 404')
823

    
824
        r = pithos.object_copy(
825
            obj,
826
            destination='/%s/%s' % (self.temp_containers[-1], obj),
827
            content_encoding='utf8',
828
            content_type='application/json')
829
        self.assertEqual(r.status_code, 201)
830
        self.assertEqual(
831
            r.headers['content-type'],
832
            'application/json; charset=UTF-8')
833

    
834
        pithos.container = self.temp_containers[-1]
835
        r = pithos.object_get(obj)
836
        etag = r.headers['etag']
837
        ctype = r.headers['content-type']
838
        self.assertEqual(ctype, 'application/json')
839
        self.info('Cross container copy w. content-type/encoding is OK')
840

    
841
        r = pithos.object_copy(
842
            obj,
843
            destination='/%s/%s0' % (pithos.container, obj),
844
            ignore_content_type=True,
845
            content_type='text/x-python')
846
        self.assertEqual(r.status_code, 201)
847
        self.assertNotEqual(r.headers['content-type'], 'application/json')
848
        r = pithos.object_get(obj + '0')
849
        self.assertNotEqual(r.headers['content-type'], 'text/x-python')
850

    
851
        r = pithos.object_copy(
852
            obj,
853
            destination='/%s/%s1' % (pithos.container, obj),
854
            if_etag_match=etag)
855
        self.assertEqual(r.status_code, 201)
856
        self.info('if-etag-match is OK')
857

    
858
        r = pithos.object_copy(
859
            obj,
860
            destination='/%s/%s2' % (pithos.container, obj),
861
            if_etag_not_match='lalala')
862
        self.assertEqual(r.status_code, 201)
863
        self.info('if-etag-not-match is OK')
864

    
865
        r = pithos.object_copy(
866
            '%s2' % obj,
867
            destination='/%s/%s3' % (pithos.container, obj),
868
            format='xml',
869
            public=True)
870
        self.assertEqual(r.status_code, 201)
871
        self.assertTrue('xml' in r.headers['content-type'])
872

    
873
        r = pithos.get_object_info(obj + '3')
874
        self.assertTrue('x-object-public' in r)
875
        self.info('Publish, format and source-version are OK')
876

    
877
    def test_065_object_move(self):
878
        """Test object MOVE"""
879
        pithos = self.clients.pithos
880
        obj = 'source.file2move'
881
        data = '{"key1": "val1", "key2": "val2"}'
882

    
883
        r = pithos.object_put(
884
            obj,
885
            content_type='application/octet-stream',
886
            data=data,
887
            metadata=dict(mkey1='mval1', mkey2='mval2'),
888
            permissions=dict(
889
                read=['accX:groupA', 'u1', 'u2'],
890
                write=['u2', 'u3']))
891
        self.info('Prepared a file /%s/%s' % (pithos.container, obj))
892

    
893
        r = pithos.object_move(
894
            obj,
895
            destination='/%s/%s0' % (pithos.container, obj),
896
            ignore_content_type=False, content_type='application/json',
897
            metadata={'mkey2': 'mval2a', 'mkey3': 'mval3'},
898
            permissions={'write': ['u5', 'accX:groupB']})
899
        self.assertEqual(r.status_code, 201)
900
        self.info('Status code is OK')
901

    
902
        r = pithos.get_object_meta(obj + '0')
903
        self.assertEqual(r['x-object-meta-mkey1'], 'mval1')
904
        self.assertEqual(r['x-object-meta-mkey2'], 'mval2a')
905
        self.assertEqual(r['x-object-meta-mkey3'], 'mval3')
906
        self.info('Metadata are OK')
907

    
908
        r = pithos.get_object_sharing(obj + '0')
909
        self.assertFalse('read' in r)
910
        self.assertTrue('u5' in r['write'])
911
        self.assertTrue('accx:groupb' in r['write'])
912
        self.info('Sharing is OK')
913

    
914
        self.assertRaises(ClientError, pithos.get_object_info, obj)
915
        self.info('Old object is not there, which is OK')
916

    
917
        r = pithos.object_move(
918
            obj + '0',
919
            destination='/%s/%s' % (pithos.container, obj),
920
            content_encoding='utf8',
921
            content_type='application/json',
922
            destination_account='nonExistendAddress@NeverLand.com',
923
            success=(201, 404))
924
        self.assertEqual(r.status_code, 404)
925
        self.info('Non existing UUID correctly causes a 404')
926

    
927
        r = pithos.object_move(
928
            obj + '0',
929
            destination='/%s/%s' % (self.temp_containers[-2], obj),
930
            content_encoding='utf8',
931
            content_type='application/json',
932
            content_disposition='attachment; filename="fname.ext"')
933
        self.assertEqual(r.status_code, 201)
934
        self.assertEqual(
935
            r.headers['content-type'],
936
            'application/json; charset=UTF-8')
937

    
938
        pithos.container = self.temp_containers[-2]
939
        r = pithos.object_get(obj)
940
        etag = r.headers['etag']
941
        ctype = r.headers['content-type']
942
        self.assertEqual(ctype, 'application/json')
943
        self.assertTrue('fname.ext' in r.headers['content-disposition'])
944
        self.info('Cross container copy w. content-type/encoding is OK')
945

    
946
        r = pithos.object_move(
947
            obj,
948
            destination='/%s/%s0' % (pithos.container, obj),
949
            ignore_content_type=True,
950
            content_type='text/x-python')
951
        self.assertEqual(r.status_code, 201)
952
        self.assertNotEqual(r.headers['content-type'], 'application/json')
953
        r = pithos.object_get(obj + '0')
954
        self.assertNotEqual(r.headers['content-type'], 'text/x-python')
955

    
956
        r = pithos.object_move(
957
            obj + '0',
958
            destination='/%s/%s' % (pithos.container, obj),
959
            if_etag_match=etag)
960
        self.assertEqual(r.status_code, 201)
961
        self.info('if-etag-match is OK')
962

    
963
        r = pithos.object_move(
964
            obj,
965
            destination='/%s/%s0' % (pithos.container, obj),
966
            if_etag_not_match='lalala')
967
        self.assertEqual(r.status_code, 201)
968
        self.info('if-etag-not-match is OK')
969

    
970
        r = pithos.object_move(
971
            obj + '0',
972
            destination='/%s/%s' % (pithos.container, obj),
973
            format='xml',
974
            public=True)
975
        self.assertEqual(r.status_code, 201)
976
        self.assertTrue('xml' in r.headers['content-type'])
977

    
978
        r = pithos.get_object_info(obj)
979
        self.assertTrue('x-object-public' in r)
980
        self.info('Publish, format and source-version are OK')
981

    
982
    def test_070_object_post(self):
983
        """Test object POST"""
984
        pithos = self.clients.pithos
985
        obj = 'sample2post.file'
986
        newf = NamedTemporaryFile()
987
        newf.writelines([
988
            'ello!\n',
989
            'This is a test line\n',
990
            'inside a test file\n'])
991
        newf.flush()
992

    
993
        r = pithos.object_put(
994
            obj,
995
            content_type='application/octet-stream',
996
            data='H',
997
            metadata=dict(mkey1='mval1', mkey2='mval2'),
998
            permissions=dict(
999
                read=['accX:groupA', 'u1', 'u2'],
1000
                write=['u2', 'u3']))
1001
        self.info(
1002
            'Prepared a local file %s & a remote object %s', newf.name, obj)
1003

    
1004
        newf.seek(0)
1005
        pithos.append_object(obj, newf)
1006
        r = pithos.object_get(obj)
1007
        self.assertEqual(r.text[:5], 'Hello')
1008
        self.info('Append is OK')
1009

    
1010
        newf.seek(0)
1011
        # r = pithos.overwrite_object(obj, 0, 10, newf, 'text/x-python')
1012
        # r = pithos.object_get(obj)
1013
        # print r.text, r.headers
1014
        # self.assertTrue(r.text.startswith('ello!'))
1015
        # self.assertEqual(r.headers['content-type'], 'text/x-python')
1016
        # self.info('Overwrite (involves content-legth/range/type) is OK')
1017
        self.info('ATTENTION: Overwrite is probably NOT OK')
1018
        #  This is just to mock the effects of the commented action
1019
        pithos.object_delete(obj)
1020
        pithos.upload_object(obj, newf, content_type='text/x-python')
1021
        r = pithos.object_post(
1022
            obj,
1023
            update=True,
1024
            content_type='text/x-python',
1025
            metadata=dict(mkey1='mval1', mkey2='mval2'),
1026
            permissions=dict(
1027
                read=['accX:groupA', 'u1', 'u2'],
1028
                write=['u2', 'u3']))
1029
        #  ---
1030

    
1031
        r = pithos.truncate_object(obj, 5)
1032
        r = pithos.object_get(obj)
1033
        self.assertEqual(r.text, 'ello!')
1034
        self.assertEqual(r.headers['content-type'], 'text/x-python')
1035
        self.info(
1036
            'Truncate (involves content-range, object-bytes and source-object)'
1037
            ' is OK')
1038

    
1039
        pithos.set_object_meta(obj, {'mkey2': 'mval2a', 'mkey3': 'mval3'})
1040
        r = pithos.get_object_meta(obj)
1041
        self.assertEqual(r['x-object-meta-mkey1'], 'mval1')
1042
        self.assertEqual(r['x-object-meta-mkey2'], 'mval2a')
1043
        self.assertEqual(r['x-object-meta-mkey3'], 'mval3')
1044
        pithos.del_object_meta(obj, 'mkey1')
1045
        r = pithos.get_object_meta(obj)
1046
        self.assertFalse('x-object-meta-mkey1' in r)
1047
        self.info('Metadata are OK')
1048

    
1049
        pithos.set_object_sharing(
1050
            obj, read_permission=['u4', 'u5'], write_permission=['u4'])
1051
        r = pithos.get_object_sharing(obj)
1052
        self.assertTrue('read' in r)
1053
        self.assertTrue('u5' in r['read'])
1054
        self.assertTrue('write' in r)
1055
        self.assertTrue('u4' in r['write'])
1056
        pithos.del_object_sharing(obj)
1057
        r = pithos.get_object_sharing(obj)
1058
        self.assertTrue(len(r) == 0)
1059
        self.info('Sharing is OK')
1060

    
1061
        pithos.publish_object(obj)
1062
        r = pithos.get_object_info(obj)
1063
        self.assertTrue('x-object-public' in r)
1064
        pithos.unpublish_object(obj)
1065
        r = pithos.get_object_info(obj)
1066
        self.assertFalse('x-object-public' in r)
1067
        self.info('Publishing is OK')
1068

    
1069
        etag = r['etag']
1070
        r = pithos.object_post(
1071
            obj,
1072
            update=True,
1073
            public=True,
1074
            if_etag_not_match=etag,
1075
            success=(412, 202, 204))
1076
        self.assertEqual(r.status_code, 412)
1077
        self.info('if-etag-not-match is OK')
1078

    
1079
        r = pithos.object_post(
1080
            obj,
1081
            update=True,
1082
            public=True,
1083
            if_etag_match=etag,
1084
            content_type='application/octet-srteam',
1085
            content_encoding='application/json')
1086

    
1087
        r = pithos.get_object_info(obj)
1088
        helloVersion = r['x-object-version']
1089
        self.assertTrue('x-object-public' in r)
1090
        #self.assertEqual(r['content-type'], 'application/octet-srteam')
1091
        #self.info('If-etag-match is OK')
1092
        self.info('If-etag-match is probably not OK')
1093

    
1094
        pithos.container = self.temp_containers[-1]
1095
        pithos.create_object(obj)
1096
        r = pithos.object_post(
1097
            obj,
1098
            update=True,
1099
            content_type='application/octet-srteam',
1100
            content_length=5,
1101
            content_range='bytes 1-5/*',
1102
            source_object='/%s/%s' % (self.temp_containers[-2], obj),
1103
            source_account='thisAccountWillNeverExist@adminland.com',
1104
            source_version=helloVersion,
1105
            data='12345',
1106
            success=(403, 202, 204))
1107
        self.assertEqual(r.status_code, 403)
1108
        self.info('Successfully failed with invalud user UUID')
1109

    
1110
        r = pithos.object_post(
1111
            obj,
1112
            update=True,
1113
            content_type='application/octet-srteam',
1114
            content_length=5,
1115
            content_range='bytes 1-5/*',
1116
            source_object='/%s/%s' % (self.temp_containers[-1], obj),
1117
            source_account=pithos.account,
1118
            source_version=helloVersion,
1119
            data='12345',
1120
            content_disposition='attachment; filename="fname.ext"')
1121

    
1122
        r = pithos.object_get(obj)
1123
        self.assertEqual(r.text, 'eello!')
1124
        self.info('Cross container POST with source-version/account are OK')
1125

    
1126
        self.assertTrue('content-disposition' in r.headers)
1127
        self.assertTrue('fname.ext' in r.headers['content-disposition'])
1128
        self.info('Content-disposition POST is OK')
1129

    
1130
        mobj = 'manifest.test'
1131
        txt = ''
1132
        for i in range(10):
1133
            txt += '%s' % i
1134
            r = pithos.object_put(
1135
                '%s/%s' % (mobj, i),
1136
                data='%s' % i,
1137
                content_length=1,
1138
                success=201,
1139
                content_encoding='application/octet-stream',
1140
                content_type='application/octet-stream')
1141

    
1142
        pithos.create_object_by_manifestation(
1143
            mobj, content_type='application/octet-stream')
1144

    
1145
        r = pithos.object_post(
1146
            mobj, manifest='%s/%s' % (pithos.container, mobj))
1147

    
1148
        r = pithos.object_get(mobj)
1149
        self.assertEqual(r.text, txt)
1150
        self.info('Manifestation is OK')
1151

    
1152
        """We need to check transfer_encoding """
1153

    
1154
    @classmethod
1155
    def tearDownClass(cls):  # noqa
1156
        """Clean up"""
1157
        from kamaki.cli.logger import deactivate
1158
        deactivate('kamaki.clients.send')
1159
        deactivate('kamaki.clients.recv')
1160
        pithos = cls.clients.pithos
1161
        for c in getattr(cls, 'temp_containers', []):
1162
            pithos.container = c
1163
            try:
1164
                pithos.del_container(delimiter='/')
1165
                pithos.purge_container(c)
1166
            except ClientError as ce:
1167
                print ('Failed to destroy container (%s)' % ce)