Revision 028f4731

b/snf-cyclades-app/synnefo/plankton/backend.py
44 44
  - location: generated based on the file's path
45 45
  - name: stored as a user meta
46 46
  - owner: the file's account
47
  - properties: stored as user meta prefixed with PROPERTY_PREFIX
47
  - property_*: stored as user meta prefixed with PROPERTY_PREFIX
48 48
  - size: the 'bytes' meta
49 49
  - status: stored as a system meta
50 50
  - store: is always 'pithos'
......
69 69

  
70 70
PLANKTON_DOMAIN = 'plankton'
71 71
PLANKTON_PREFIX = 'plankton:'
72
PROPERTY_PREFIX = 'property:'
72
PROPERTY_PREFIX = 'property_'
73 73

  
74
PLANKTON_META = ('container_format', 'disk_format', 'name', 'properties',
74
PLANKTON_META = ('container_format', 'disk_format', 'name',
75 75
                 'status', 'created_at')
76 76

  
77 77
from pithos.backends.util import PithosBackendPool
......
202 202

  
203 203
        prefixed = {}
204 204
        for key, val in meta.items():
205
            if key in PLANKTON_META:
206
                if key == "properties":
207
                    val = json.dumps(val)
205
            if key in PLANKTON_META or key.startswith(PROPERTY_PREFIX):
208 206
                prefixed[PLANKTON_PREFIX + key] = val
209 207

  
210 208
        self.backend.update_object_meta(self.user, account, container, name,
......
340 338
            permissions["read"] = list(read)
341 339
            self._update_permissions(image_url, permissions)
342 340
        meta = {}
343
        meta["properties"] = metadata.pop("properties", {})
344 341
        meta.update(**metadata)
345 342

  
346 343
        self._update_meta(image_url, meta)
......
390 387

  
391 388
        # Update rest metadata
392 389
        meta = {}
393
        meta['properties'] = metadata.pop('properties', {})
394 390
        # Add creation(register) timestamp as a metadata, to avoid extra
395 391
        # queries when retrieving the list of images.
396 392
        meta['created_at'] = time()
......
494 490
            # Remove plankton prefix
495 491
            key = key.replace(PLANKTON_PREFIX, "")
496 492
            # Keep only those in plankton meta
497
            if key in PLANKTON_META:
498
                if key == "properties":
499
                    image[key] = json.loads(val)
500
                elif key == "created_at":
493
            if key in PLANKTON_META or key.startswith(PROPERTY_PREFIX):
494
                if key == "created_at":
501 495
                    # created timestamp is return in 'created_at' field
502 496
                    pass
503 497
                else:
b/snf-cyclades-app/synnefo/plankton/tests.py
93 93
  'location': u'pithos://foo@example.com/container/foo3',
94 94
  u'name': u'dummyname',
95 95
  'owner': u'foo@example.com',
96
  'properties': {},
97 96
  'size': 500L,
98 97
  u'status': u'available',
99 98
  'store': 'pithos',
......
110 109
  'location': u'pithos://foo@example.com/container/private',
111 110
  u'name': u'dummyname2',
112 111
  'owner': u'foo@example.com',
113
  'properties': {},
114 112
  'size': 10000L,
115 113
  u'status': u'available',
116 114
  'store': 'pithos',
......
125 123
  'id': u'264fb9ac-2458-421c-b460-6a765a92825c',
126 124
  'is_public': True,
127 125
  'location': u'pithos://foo@example.com/container/baz.diskdump',
128
  u'name': u'"dummyname3"',
126
  'name': u'"dummyname3"',
129 127
  'owner': u'foo@example.com',
130
  'properties': {u'description': u'Debian Squeeze Base System',
131
                 u'gui': u'No GUI',
132
                 u'kernel': u'2.6.32',
133
                 u'os': u'debian',
134
                 u'osfamily': u'linux',
135
                 u'root_partition': u'1',
136
                 u'size': u'451',
137
                 u'sortorder': u'1',
138
                 u'users': u'root'},
128
  'property_description': u'Debian Squeeze Base System',
129
  'property_gui': u'No GUI',
130
  'property_kernel': u'2.6.32',
131
  'property_os': u'debian',
132
  'property_osfamily': u'linux',
133
  'property_root_partition': u'1',
134
  'property_size': u'451',
135
  'property_sortorder': u'1',
136
  'property_users': u'root',
139 137
  'size': 473772032L,
140 138
  u'status': u'available',
141 139
  'store': 'pithos',
......
180 178
        images = json.loads(response.content)
181 179
        for api_image in images:
182 180
            id = api_image['id']
183
            pithos_image = dict([(key, val)\
184
                                for key, val in DummyImages[id].items()\
185
                                if key in DETAIL_FIELDS])
181
            pithos_image = {}
182
            properties = {}
183
            for key, val in DummyImages[id].items():
184
                if key.startswith('property_'):
185
                    properties[key.replace('property_', '', 1)] = val
186
                elif key in DETAIL_FIELDS:
187
                    pithos_image[key] = val
188
            if properties:
189
                pithos_image['properties'] = properties
186 190
            self.assertEqual(api_image, pithos_image)
191

  
187 192
        backend.return_value\
188 193
                .list_images.assert_called_once_with({}, {'sort_key': 'created_at',
189 194
                                                   'sort_dir': 'desc'})
b/snf-cyclades-app/synnefo/plankton/views.py
61 61

  
62 62
DETAIL_FIELDS = ('name', 'disk_format', 'container_format', 'size', 'checksum',
63 63
                 'location', 'created_at', 'updated_at', 'deleted_at',
64
                 'status', 'is_public', 'owner', 'properties', 'id')
64
                 'status', 'is_public', 'owner', 'id')
65 65

  
66 66
ADD_FIELDS = ('name', 'id', 'store', 'disk_format', 'container_format', 'size',
67
              'checksum', 'is_public', 'owner', 'properties', 'location')
67
              'checksum', 'is_public', 'owner', 'location')
68 68

  
69 69
UPDATE_FIELDS = ('name', 'disk_format', 'container_format', 'is_public',
70
                 'owner', 'properties', 'status')
70
                 'owner',  'status')
71

  
72
PROPERTY_FIELD_PREFIX = 'property_'
71 73

  
72 74

  
73 75
log = getLogger('synnefo.plankton')
......
77 79
    response = HttpResponse()
78 80

  
79 81
    for key in DETAIL_FIELDS:
80
        if key == 'properties':
81
            for k, v in image.get('properties', {}).items():
82
                name = 'x-image-meta-property-' + k.replace('_', '-')
83
                response[name] = v
84
        else:
85
            name = 'x-image-meta-' + key.replace('_', '-')
86
            response[name] = image.get(key, '')
82
        name = 'x-image-meta-' + key.replace('_', '-')
83
        response[name] = image.get(key, '')
84
    for key in [k for k in image.keys() if
85
                k.startswith(PROPERTY_FIELD_PREFIX)]:
86
        name = 'x-image-meta-' + key.replace('_', '-')
87
        response[name] = image.get(key, '')
87 88

  
88 89
    return response
89 90

  
......
94 95

  
95 96
    META_PREFIX = 'HTTP_X_IMAGE_META_'
96 97
    META_PREFIX_LEN = len(META_PREFIX)
97
    META_PROPERTY_PREFIX = 'HTTP_X_IMAGE_META_PROPERTY_'
98
    META_PROPERTY_PREFIX_LEN = len(META_PROPERTY_PREFIX)
99 98

  
100 99
    headers = {'properties': {}}
101 100

  
102 101
    for key, val in request.META.items():
103
        if key.startswith(META_PROPERTY_PREFIX):
104
            name = normalize(key[META_PROPERTY_PREFIX_LEN:])
105
            headers['properties'][unquote(name)] = unquote(val)
106
        elif key.startswith(META_PREFIX):
102
        if key.startswith(META_PREFIX):
107 103
            name = normalize(key[META_PREFIX_LEN:])
108 104
            headers[unquote(name)] = unquote(val)
109 105

  
......
117 113
    return headers
118 114

  
119 115

  
116
def _assert_allowed_keys(d, allowed):
117
    # Filter out property fields
118

  
119
    for k in d:
120
        if k.startswith(PROPERTY_FIELD_PREFIX):
121
            continue
122
        assert k in allowed
123

  
124

  
120 125
@api.api_method(http_method="POST", user_required=True, logger=log)
121 126
def add_image(request):
122 127
    """Add a new virtual machine image
......
140 145
    log.debug('add_image %s', params)
141 146

  
142 147
    assert 'name' in params
143
    assert set(params.keys()).issubset(set(ADD_FIELDS))
148
    _assert_allowed_keys(params, ADD_FIELDS)
144 149

  
145 150
    name = params.pop('name')
146 151
    location = params.pop('location', None)
......
301 306
    # Remove keys that should not be returned
302 307
    fields = DETAIL_FIELDS if detail else LIST_FIELDS
303 308
    for image in images:
309
        properties = {}
304 310
        for key in image.keys():
305
            if key not in fields:
311
            if key.startswith(PROPERTY_FIELD_PREFIX):
312
                properties[key.replace(PROPERTY_FIELD_PREFIX, '', 1)] =\
313
                    image.pop(key)
314
            elif key not in fields:
306 315
                del image[key]
316
        if detail and properties:
317
            image['properties'] = properties
307 318

  
308 319
    data = json.dumps(images, indent=settings.DEBUG)
309 320
    return HttpResponse(data)
......
363 374
    meta = _get_image_headers(request)
364 375
    log.debug('update_image %s', meta)
365 376

  
366
    assert set(meta.keys()).issubset(set(UPDATE_FIELDS))
377
    _assert_allowed_keys(meta, UPDATE_FIELDS)
367 378

  
368 379
    with image_backend(request.user_uniq) as backend:
369 380
        image = backend.update_metadata(image_id, meta)

Also available in: Unified diff