Revision f5afd99b

b/snf-app/synnefo/plankton/backend.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
"""
35
Plankton attributes are stored as user metadata in Pithos, prefixed with
36
PLANKTON_PREFIX.
37
Exceptions are the following:
38
  - location: generated based on the object's path
39
  - updated_at: generated based on the modified attribute
40
  - created_at: generated based on the modified attribute of the first version
41
  - owner: identical to the object's account
42
  - is_public: True if there is a * entry for the read permission
43

  
44
All Plankton properties are JSON serialized and stored as one user meta.
45
"""
46

  
34 47
import json
35 48

  
36 49
from binascii import hexlify
......
49 62
PLANKTON_PREFIX = 'plankton:'
50 63

  
51 64

  
52
def iterblock(data, block_size):
53
    while data:
54
        yield data[:block_size]
55
        data = data[block_size:]
56

  
57

  
58
def format_timestamp(t):
59
    return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
60

  
61

  
62 65
def set_plankton_attr(d, **kwargs):
63 66
    for key, val in kwargs.items():
64 67
        d[PLANKTON_PREFIX + key] = str(val) if val is not None else ''
65 68

  
69
def get_image_id(location):
70
    return str(UUID(bytes=md5(location).digest()))
71

  
66 72

  
67 73
class BackendException(Exception): pass
68 74

  
69 75

  
70
class BackendWrapper(object):
71
    """A proxy object that always passes user as a first argument."""
76
class ImageBackend(object):
77
    """A wrapper arround the pithos backend to simplify image handling."""
72 78
    
73 79
    def __init__(self, user):
74 80
        self.user = user
......
79 85
        except NameError:
80 86
            pass    # Container already exists
81 87
    
82
    def close(self):
83
        self.backend.close()
88
    def _get(self, user, account, container, object):
89
        def format_timestamp(t):
90
            return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
84 91

  
85
    def _get_image(self, user, account, container, object):
86 92
        meta = self.backend.get_object_meta(user, account, container, object)
87 93
        image = {
88
            '_user': user,
89 94
            '_account': account,
90 95
            '_container': container,
91 96
            '_object': object}
......
95 100
            val = meta.get(PLANKTON_PREFIX + key, None)
96 101
            if val is not None:
97 102
                image[key] = val
98
        
103

  
99 104
        image['location'] = 'pithos://%s/%s/%s' % (account, container, object)
100 105
        image['updated_at'] = format_timestamp(meta['modified'])
101 106
        image['owner'] = account
102
        
107

  
103 108
        # Get the creation date from the date of the first version
104 109
        version, created = self.backend.list_versions(user, account, container,
105 110
                                                      name=object)[0]
106 111
        image['created_at'] = format_timestamp(created)
107 112
        
108 113
        # Mark as public if there is a * entry for read
109
        action, path, perm = self.backend.get_object_permissions(user, account,
110
                                                            container, object)
111
        image['is_public'] = '*' in perm.get('read', [])
114
        permissions = self._get_permissions(image)
115
        image['is_public'] = '*' in permissions.get('read', [])
112 116
        
113 117
        properties = meta.get(PLANKTON_PREFIX + 'properties', None)
114 118
        if properties:
115 119
            image['properties'] = json.loads(properties)
116
        
117
        return image
118
    
119
    def add_user(self, image_id, user):
120
        image = self.get_image(image_id)
121
        assert image, "Image not found"
122
        
123
        account = image['_account']
124
        container = image['_container']
125
        object = image['_object']
126
        
127
        action, path, permissions = self.backend.get_object_permissions(
128
                self.user, account, container, object)
129
        read_permissions = set(permissions.get('read', []))
130
        read_permissions.add(user)
131
        permissions['read'] = list(read_permissions)
132
        self.backend.update_object_permissions(self.user, account, container,
133
                object, permissions)
134
    
135
    def remove_user(self, image_id, user):
136
        image = self.get_image(image_id)
137
        assert image, "Image not found"
138
        
139
        account = image['_account']
140
        container = image['_container']
141
        object = image['_object']
142
        
143
        action, path, permissions = self.backend.get_object_permissions(
144
                self.user, account, container, object)
145
        try:
146
            permissions.get('read', []).remove(user)
147
        except ValueError:
148
            return      # User did not have access anyway
149
        self.backend.update_object_permissions(self.user, account, container,
150
                object, permissions)
151
    
152
    def replace_users(self, image_id, users):
153
        image = self.get_image(image_id)
154
        assert image, "Image not found"
155 120

  
156
        account = image['_account']
157
        container = image['_container']
158
        object = image['_object']
159

  
160
        action, path, permissions = self.backend.get_object_permissions(
161
                self.user, account, container, object)
162
        permissions['read'] = users
163
        if image.get('is_public', False):
164
            permissions['read'].append('*')
165
        self.backend.update_object_permissions(self.user, account, container,
166
                object, permissions)
121
        return image
167 122
    
168
    def list_users(self, image_id):
169
        image = self.get_image(image_id)
170
        assert image, "Image not found"
171
        
172
        account = image['_account']
173
        container = image['_container']
174
        object = image['_object']
175
        
123
    def _get_permissions(self, image):
176 124
        action, path, permissions = self.backend.get_object_permissions(
177
                self.user, account, container, object)
178
        return [user for user in permissions.get('read', []) if user != '*']
125
                user=self.user,
126
                account=image['_account'],
127
                container=image['_container'],
128
                name=image['_object'])
129
        return permissions
179 130
    
180
    def get_image(self, image_id):
131
    def _iter(self, keys=[], public=False):
181 132
        backend = self.backend
182
        
183
        user = self.user
184 133
        container = self.container
185
        
186
        # Arguments to be passed to list_objects
187
        listargs = {
188
            'user': user,
189
            'container': container,
190
            'prefix': '',
191
            'delimiter': '/',
192
            'keys': [PLANKTON_PREFIX + 'id']}
134
        user = None if public else self.user
135
        keys = [PLANKTON_PREFIX + key for key in keys]
193 136
        
194 137
        for account in backend.list_accounts(user):
195
            listargs['account'] = account
196
            for path, version_id in backend.list_objects(**listargs):
138
            for path, version_id in backend.list_objects(user, account,
139
                    container, prefix='', delimiter='/', keys=keys):
197 140
                try:
198
                    image = self._get_image(user, account, container, path)
199
                    if image['id'] == image_id:
200
                        return image
141
                    image = self._get(user, account, container, path)
142
                    if 'id' in image:
143
                        yield image
201 144
                except NotAllowedError:
202 145
                    continue
146
    
147
    def _store(self, data):
148
        """Breaks data into blocks and stores them in the backend.
203 149
        
204
        return None
150
        Returns a tuple of the hashmap and the MD5 checksum
151
        """
152
        
153
        def iterblock(data, block_size):
154
            while data:
155
                yield data[:block_size]
156
                data = data[block_size:]
157
        
158
        m = md5()
159
        hashmap = []
160
        
161
        for block in iterblock(data, self.backend.block_size):
162
            hash = self.backend.put_block(block)
163
            hashmap.append(hash)
164
            m.update(block)
165
        
166
        return hashmap, m.digest()
167
    
168
    def _update_permissions(self, image, permissions):
169
        self.backend.update_object_permissions(self.user, image['_account'],
170
                image['_container'], image['_object'], permissions)
171
    
172
    def add_user(self, image_id, user):
173
        image = self.get_meta(image_id)
174
        assert image, "Image not found"
175
        
176
        permissions = self._get_permissions(image)
177
        read = set(permissions.get('read', []))
178
        read.add(user)
179
        permissions['read'] = list(read)
180
        self._update_permissions(image, permissions)
181
    
182
    def close(self):
183
        self.backend.close()
205 184
    
206
    def get_image_data(self, image):
207
        size, hashmap = self.backend.get_object_hashmap(image['_user'],
185
    def get_data(self, image):
186
        size, hashmap = self.backend.get_object_hashmap(self.user,
208 187
                image['_account'], image['_container'], image['_object'])
188
        data = ''.join(self.backend.get_block(hash) for hash in hashmap)
189
        assert len(data) == size
190
        return data
191
    
192
    def get_meta(self, image_id):
193
        for image in self._iter(keys=['id']):
194
            if image['id'] == image_id:
195
                return image
196
        return None
197
    
198
    def iter_public(self, filters):
199
        keys = set()
200
        for key, val in filters.items():
201
            if key in ('size_min', 'size_max'):
202
                key = 'size'
203
            keys.add(key)
204
        
205
        for image in self._iter(keys=keys, public=True):
206
            for key, val in filters.items():
207
                if key == 'size_min':
208
                    if image['size'] < int(val):
209
                        break
210
                elif key == 'size_max':
211
                    if image['size'] > int(val):
212
                        break
213
                else:
214
                    if image[key] != val:
215
                        break
216
            else:
217
                yield image
218
    
219
    def iter_shared(self):
220
        for image in self._iter():
221
            yield image
222
    
223
    def list_public_images(self, filters, params):
224
        images = list(self.iter_public(filters))
225
        key = itemgetter(params['sort_key'])
226
        reverse = params['sort_dir'] == 'desc'
227
        images.sort(key=key, reverse=reverse)
228
        return images
229
    
230
    def list_users(self, image_id):
231
        image = self.get_meta(image_id)
232
        assert image, "Image not found"
209 233
        
210
        buf = []
211
        for hash in hashmap:
212
            buf.append(self.backend.get_block(hash))
213
        return ''.join(buf)
234
        permissions = self._get_permissions(image)
235
        return [user for user in permissions.get('read', []) if user != '*']
214 236
    
215
    def register_image(self, name, location, store=None, disk_format=None,
216
            container_format=None, size=None, checksum=None, is_public=False,
217
            owner=None, properties={}):
237
    def put(self, name, data, params):
238
        meta = {}
218 239
        
219
        backend = self.backend
240
        location = 'pithos://%s/%s/%s' % (self.user, self.container, name)
241
        image_id = get_image_id(location)
220 242
        
221
        assert location.startswith('pithos://')
222
        t = location.split('/', 4)
223
        assert len(t) == 5
224
        account, container, object = t[2:5]
225
        user = self.user
243
        params.setdefault('store', settings.DEFAULT_IMAGE_STORE)
244
        params.setdefault('disk_format', settings.DEFAULT_DISK_FORMAT)
245
        params.setdefault('container_format',
246
                settings.DEFAULT_CONTAINER_FORMAT)
226 247
        
227
        store = store or settings.DEFAULT_IMAGE_STORE
228
        disk_format = disk_format or settings.DEFAULT_DISK_FORMAT
229
        container_format = container_format or \
230
                                            settings.DEFAULT_CONTAINER_FORMAT
248
        assert params['store'] in settings.IMAGE_STORES
249
        assert params['disk_format'] in settings.IMAGE_DISK_FORMATS
250
        assert params['container_format'] in settings.IMAGE_CONTAINER_FORMATS
231 251
        
232
        assert store in settings.IMAGE_STORES
233
        assert disk_format in settings.IMAGE_DISK_FORMATS
234
        assert container_format in settings.IMAGE_CONTAINER_FORMATS
252
        is_public = params.pop('is_public', False)
253
        permissions = {'read': ['*']} if is_public else None
235 254
        
236
        sz, hashmap = self.backend.get_object_hashmap(user, account,
237
                container, object)
238
        if size is not None and size != sz:
255
        size = params.get('size', len(data))
256
        if size != len(data):
239 257
            raise BackendException("Invalid size")
240 258
        
241
        m = md5()
242
        for hash in hashmap:
243
            m.update(self.backend.get_block(hash))
244
        
245
        digest = m.digest()
259
        hashmap, digest = self._store(data)
246 260
        hexdigest = hexlify(digest)
247
        if checksum is not None and checksum != hexdigest:
248
            raise BackendException("Invalid checksum")
261
        meta['ETag'] = hexdigest
249 262
        
250
        permissions = {'read': ['*']} if is_public else None
263
        checksum = params.pop('checksum', hexdigest)
264
        if checksum != hexdigest:
265
            raise BackendException("Invalid checksum")
251 266
        
252
        image_meta = {
253
            'id': str(UUID(bytes=digest)),
254
            'name': name,
255
            'status': 'available',
256
            'store': store,
257
            'disk_format': disk_format,
258
            'container_format': container_format,
259
            'size': size or sz,
260
            'checksum': checksum or hexdigest,
261
            'owner': owner,
262
            'deleted_at': ''}
267
        set_plankton_attr(meta, id=image_id, name=name, status='available',
268
                size=size, checksum=checksum, deleted_at='')
263 269
        
270
        properties = params.pop('properties', None)
264 271
        if properties:
265
            image_meta['properties'] = json.dumps(properties)
272
            set_plankton_attr(meta, properties=json.dumps(properties))
266 273
        
267
        meta = {}
268
        for key, val in image_meta.items():
269
            meta[PLANKTON_PREFIX + key] = str(val) if val is not None else ''
274
        set_plankton_attr(meta, **params)
270 275
        
271
        backend.update_object_meta(user, account, container, object, meta)
272
        backend.update_object_permissions(user, account, container, object,
273
                                          permissions)
276
        self.backend.update_object_hashmap(
277
                user=self.user,
278
                account=self.user,
279
                container=self.container,
280
                name=name,
281
                size=size,
282
                hashmap=hashmap,
283
                meta=meta,
284
                replace_meta=True,
285
                permissions=permissions)
274 286
        
275
        return self.get_image(image_meta['id'])
287
        return self.get_meta(image_id)
276 288
    
277
    def put_image(self, name, data, store=None, disk_format=None,
278
            container_format=None, size=None, checksum=None, is_public=False,
279
            owner=None, properties={}):
289
    def register(self, name, location, params):
290
        assert location.startswith('pithos://')
291
        t = location.split('/', 4)
292
        assert len(t) == 5
293
        account, container, object = t[2:5]
294
        user = self.user
295
        image_id = get_image_id(location)
280 296
        
281
        backend = self.backend
297
        params.setdefault('store', settings.DEFAULT_IMAGE_STORE)
298
        params.setdefault('disk_format', settings.DEFAULT_DISK_FORMAT)
299
        params.setdefault('container_format',
300
                settings.DEFAULT_CONTAINER_FORMAT)
282 301
        
283
        store = store or settings.DEFAULT_IMAGE_STORE
284
        disk_format = disk_format or settings.DEFAULT_DISK_FORMAT
285
        container_format = container_format or \
286
                                            settings.DEFAULT_CONTAINER_FORMAT
302
        assert params['store'] in settings.IMAGE_STORES
303
        assert params['disk_format'] in settings.IMAGE_DISK_FORMATS
304
        assert params['container_format'] in settings.IMAGE_CONTAINER_FORMATS
287 305
        
288
        assert store in settings.IMAGE_STORES
289
        assert disk_format in settings.IMAGE_DISK_FORMATS
290
        assert container_format in settings.IMAGE_CONTAINER_FORMATS
306
        sz, hashmap = self.backend.get_object_hashmap(user, account,
307
                container, object)
291 308
        
292
        if size is not None and size != len(data):
309
        size = params.get('size', sz)
310
        if size != sz:
293 311
            raise BackendException("Invalid size")
294 312
        
295 313
        m = md5()
296
        hashmap = []
297
        for block in iterblock(data, backend.block_size):
298
            hash = backend.put_block(block)
299
            hashmap.append(hash)
300
            m.update(block)
314
        for hash in hashmap:
315
            m.update(self.backend.get_block(hash))
301 316

  
302 317
        digest = m.digest()
303 318
        hexdigest = hexlify(digest)
304
        image_id = str(UUID(bytes=digest))
305
        if checksum is not None and checksum != hexdigest:
319
        
320
        checksum = params.pop('checksum', hexdigest)
321
        if checksum != hexdigest:
306 322
            raise BackendException("Invalid checksum")
307 323
        
324
        is_public = params.pop('is_public', False)
308 325
        permissions = {'read': ['*']} if is_public else None
309 326
        
310
        meta = {'hash': hexdigest}
311
        
312
        set_plankton_attr(meta,
313
            id=image_id,
314
            name=name,
315
            status='available',
316
            store=store,
317
            disk_format=disk_format,
318
            container_format=container_format,
319
            size=size or len(data),
320
            checksum=checksum or hexdigest,
321
            owner=owner,
322
            deleted_at='')
327
        meta = {}
328
        set_plankton_attr(meta, id=image_id, name=name, status='available',
329
                size=size, checksum=checksum, deleted_at='')
323 330
        
331
        properties = params.pop('properties', None)
324 332
        if properties:
325 333
            set_plankton_attr(meta, properties=json.dumps(properties))
326 334
        
327
        backend.update_object_hashmap(
328
                user=self.user,
329
                account=self.user,
330
                container=self.container,
331
                name=name,
332
                size=len(data),
333
                hashmap=hashmap,
334
                meta=meta,
335
                replace_meta=True,
336
                permissions=permissions)
337
        
338
        return self.get_image(image_id)
339
    
340
    def iter_public_images(self, filters):
341
        user = None
342
        container = self.container
335
        set_plankton_attr(meta, **params)
343 336
        
344
        keys = set()
345
        for key, val in filters.items():
346
            if key in ('size_min', 'size_max'):
347
                key = 'size'
348
            keys.add(PLANKTON_PREFIX + key)
349
        keys = list(keys)
337
        self.backend.update_object_meta(user, account, container, object, meta)
338
        self.backend.update_object_permissions(user, account, container,
339
                object, permissions)
350 340
        
351
        for account in self.backend.list_accounts(None):
352
            for path, version_id in self.backend.list_objects(user, account,
353
                    container, prefix='', delimiter='/', keys=keys):
354
                try:
355
                    image = self._get_image(user, account, container, path)
356
                except NotAllowedError:
357
                    continue
358
                
359
                skip = False
360
                for key, val in filters.items():
361
                    if key == 'size_min':
362
                        if image['size'] < int(val):
363
                            skip = True
364
                            break
365
                    elif key == 'size_max':
366
                        if image['size'] > int(val):
367
                            skip = True
368
                            break
369
                    else:
370
                        if image[key] != val:
371
                            skip = True
372
                            break
373
                
374
                if not skip:
375
                    yield image
341
        return self.get_meta(image_id)
376 342
    
377
    def iter_shared_images(self):
378
        user = self.user
379
        container = self.container
380
        
381
        for account in self.backend.list_accounts(user):
382
            for path, version_id in self.backend.list_objects(user, account,
383
                    container, prefix='', delimiter='/'):
384
                try:
385
                    image = self._get_image(user, account, container, path)
386
                    if 'id' in image:
387
                        yield image
388
                except NotAllowedError:
389
                    continue
343
    def remove_user(self, image_id, user):
344
        image = self.get_meta(image_id)
345
        assert image, "Image not found"
346

  
347
        permissions = self._get_permissions(image)
348
        try:
349
            permissions.get('read', []).remove(user)
350
        except ValueError:
351
            return      # User did not have access anyway
352
        self._update_permissions(image, permissions)
390 353
    
391
    def list_public_images(self, filters, params):
392
        images = list(self.iter_public_images(filters))
393
        
394
        key = itemgetter(params['sort_key'])
395
        reverse = params['sort_dir'] == 'desc'
396
        images.sort(key=key, reverse=reverse)
354
    def replace_users(self, image_id, users):
355
        image = self.get_meta(image_id)
356
        assert image, "Image not found"
397 357
        
398
        return images
358
        permissions = self._get_permissions(image)
359
        permissions['read'] = users
360
        if image.get('is_public', False):
361
            permissions['read'].append('*')
362
        self._update_permissions(image, permissions)
399 363
    
400
    def update_image(self, image_id, meta):
401
        image = self.get_image(image_id)
364
    def update(self, image_id, meta):
365
        image = self.get_meta(image_id)
402 366
        assert image, "Image not found"
403 367
        
404
        user = self.user
405
        account = image['_account']
406
        container = image['_container']
407
        object = image['_object']
408
        
409 368
        is_public = meta.pop('is_public', None)
410 369
        if is_public is not None:
411
            action, path, permissions = self.backend.get_object_permissions(
412
                    user, account, container, object)
413
            read_permissions = set(permissions.get('read', []))
370
            permissions = self._get_permissions(image)
371
            read = set(permissions.get('read', []))
414 372
            if is_public:
415
                read_permissions.add('*')
373
                read.add('*')
416 374
            else:
417
                read_permissions.discard('*')
418
            permissions['read'] = list(read_permissions)
419
            self.backend.update_object_permissions(user, account, container,
420
                    object, permissions)
421
        
375
                read.discard('*')
376
            permissions['read'] = list(read)
377
            self.backend._update_permissions(image, permissions)
378

  
422 379
        m = {}
423
        set_plankton_attr(m, **meta)
424
        
425
        properties = meta.get('properties', None)
380

  
381
        properties = meta.pop('properties', None)
426 382
        if properties:
427 383
            set_plankton_attr(m, properties=json.dumps(properties))
428 384
        
429
        self.backend.update_object_meta(user, account, container, object, m)
430
        return self.get_image(image_id)
385
        set_plankton_attr(m, **meta)
386
        
387
        self.backend.update_object_meta(self.user, image['_account'],
388
                image['_container'], image['_object'], m)
389
        return self.get_meta(image_id)
b/snf-app/synnefo/plankton/util.py
41 41
        HttpResponseServerError)
42 42

  
43 43
from synnefo.db.models import SynnefoUser
44
from synnefo.plankton.backend import BackendWrapper, BackendException
44
from synnefo.plankton.backend import ImageBackend, BackendException
45 45

  
46 46

  
47 47
def get_user_from_token(token):
......
75 75
            if not user:
76 76
                return HttpResponse(status=401)
77 77
            request.user = user
78
            request.backend = BackendWrapper(user.uniq)
78
            request.backend = ImageBackend(user.uniq)
79 79
            try:
80 80
                return func(request, *args, **kwargs)
81 81
            except (AssertionError, BackendException) as e:
b/snf-app/synnefo/plankton/views.py
135 135
    params = _get_image_headers(request)
136 136
    log.debug('add_image %s', params)
137 137
    
138
    assert 'name' in params
138 139
    assert set(params.keys()).issubset(set(ADD_FIELDS))
139 140
    
140 141
    if 'id' in params:
141 142
        return HttpResponse(status=409)     # Custom IDs are not supported
142 143
    
143
    if 'location' in params:
144
        image = request.backend.register_image(**params)
144
    name = params['name']
145
    
146
    location = params.pop('location', None)
147
    if location:
148
        image = request.backend.register(name, location, params)
145 149
    else:
146
        params['data'] = request.raw_post_data
147
        image = request.backend.put_image(**params)
150
        #image = request.backend.put(name, request.raw_post_data, params)
151
        return HttpResponse(status=501)     # Not Implemented
148 152
    
149 153
    if not image:
150 154
        return HttpResponse(status=500)
......
180 184
        in memory.
181 185
    """
182 186
    
183
    image = request.backend.get_image(image_id)
184
    if not image:
185
        return HttpResponseNotFound()
186
    
187
    response = _create_image_response(image)
188
    data = request.backend.get_image_data(image)
189
    response.content = data
190
    response['Content-Length'] = len(data)
191
    response['Content-Type'] = 'application/octet-stream'
192
    response['ETag'] = image['checksum']
193
    return response
187
    #image = request.backend.get_meta(image_id)
188
    #if not image:
189
    #    return HttpResponseNotFound()
190
    #
191
    #response = _create_image_response(image)
192
    #data = request.backend.get_data(image)
193
    #response.content = data
194
    #response['Content-Length'] = len(data)
195
    #response['Content-Type'] = 'application/octet-stream'
196
    #response['ETag'] = image['checksum']
197
    #return response
198
    return HttpResponse(status=501)     # Not Implemented
194 199

  
195 200

  
196 201
@plankton_method('HEAD')
......
201 206
    3.4. Requesting Detailed Metadata on a Specific Image
202 207
    """
203 208

  
204
    image = request.backend.get_image(image_id)
209
    image = request.backend.get_meta(image_id)
205 210
    if not image:
206 211
        return HttpResponseNotFound()
207 212
    return _create_image_response(image)
......
283 288
        return HttpResponse(status=403)
284 289
    
285 290
    images = []
286
    for image in request.backend.iter_shared_images():
291
    for image in request.backend.iter_shared():
287 292
        images.append({'image_id': image['id'], 'can_share': False})
288 293
    
289 294
    data = json.dumps({'shared_images': images}, indent=settings.DEBUG)
......
295 300
    """Remove a member from an image
296 301

  
297 302
    Described in:
298
    33.10. Removing a Member from an Image
303
    3.10. Removing a Member from an Image
299 304
    """
300 305

  
301 306
    log.debug('remove_image_member %s %s', image_id, member)
......
321 326
    
322 327
    assert set(meta.keys()).issubset(set(UPDATE_FIELDS))
323 328
    
324
    image = request.backend.update_image(image_id, meta)
329
    image = request.backend.update(image_id, meta)
325 330
    return _create_image_response(image)
326 331

  
327 332

  
b/snf-app/synnefo/settings/common/__init__.py
40 40
from synnefo.settings.common.database import *
41 41
from synnefo.settings.common.queues import *
42 42
from synnefo.settings.common.api import *
43
from synnefo.settings.common.plankton import *
43 44
from synnefo.settings.common.ui import *
44 45
from synnefo.settings.common.userdata import *
45 46
from synnefo.settings.common.aai import *
b/snf-app/synnefo/settings/common/plankton.py
3 3
# Plankton configuration
4 4
########################
5 5

  
6
PITHOS_ROOT = os.path.join(PROJECT_PATH, 'pithos')
6
from os.path import join
7

  
8
PITHOS_ROOT = '/usr/share/pithos'
7 9
BACKEND_DB_MODULE = 'pithos.backends.lib.sqlalchemy'
8
BACKEND_DB_CONNECTION = 'sqlite:///' + os.path.join(PITHOS_ROOT, 'backend.db')
10
BACKEND_DB_CONNECTION = 'sqlite:///' + join(PITHOS_ROOT, 'backend.db')
9 11
BACKEND_BLOCK_MODULE = 'pithos.backends.lib.hashfiler'
10
BACKEND_BLOCK_PATH = os.path.join(PITHOS_ROOT, 'data/')
12
BACKEND_BLOCK_PATH = join(PITHOS_ROOT, 'data/')
11 13

  
12 14
# Default setting for new accounts.
13 15
DEFAULT_QUOTA = 50 * 1024 * 1024 * 1024

Also available in: Unified diff