root / snf-cyclades-app / synnefo / plankton / backend.py @ f4d6dfa6
History | View | Annotate | Download (22.5 kB)
1 |
# Copyright 2011-2014 GRNET S.A. All rights reserved.
|
---|---|
2 |
|
3 |
#
|
4 |
# Redistribution and use in source and binary forms, with or
|
5 |
# without modification, are permitted provided that the following
|
6 |
# conditions are met:
|
7 |
#
|
8 |
# 1. Redistributions of source code must retain the above
|
9 |
# copyright notice, this list of conditions and the following
|
10 |
# disclaimer.
|
11 |
#
|
12 |
# 2. Redistributions in binary form must reproduce the above
|
13 |
# copyright notice, this list of conditions and the following
|
14 |
# disclaimer in the documentation and/or other materials
|
15 |
# provided with the distribution.
|
16 |
#
|
17 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
18 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
20 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
21 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
24 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
25 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
27 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28 |
# POSSIBILITY OF SUCH DAMAGE.
|
29 |
#
|
30 |
# The views and conclusions contained in the software and
|
31 |
# documentation are those of the authors and should not be
|
32 |
# interpreted as representing official policies, either expressed
|
33 |
# or implied, of GRNET S.A.
|
34 |
|
35 |
"""
|
36 |
The Plankton attributes are the following:
|
37 |
- checksum: the 'hash' meta
|
38 |
- container_format: stored as a user meta
|
39 |
- created_at: the 'modified' meta of the first version
|
40 |
- deleted_at: the timestamp of the last version
|
41 |
- disk_format: stored as a user meta
|
42 |
- id: the 'uuid' meta
|
43 |
- is_public: True if there is a * entry for the read permission
|
44 |
- location: generated based on the file's path
|
45 |
- name: stored as a user meta
|
46 |
- owner: the file's account
|
47 |
- properties: stored as user meta prefixed with PROPERTY_PREFIX
|
48 |
- size: the 'bytes' meta
|
49 |
- status: stored as a system meta
|
50 |
- store: is always 'pithos'
|
51 |
- updated_at: the 'modified' meta
|
52 |
"""
|
53 |
|
54 |
import json |
55 |
import logging |
56 |
import os |
57 |
|
58 |
from time import time, gmtime, strftime |
59 |
from functools import wraps |
60 |
from operator import itemgetter |
61 |
from collections import namedtuple |
62 |
|
63 |
from django.conf import settings |
64 |
from django.utils import importlib |
65 |
from pithos.backends.base import NotAllowedError, VersionNotExists, QuotaError |
66 |
from synnefo.util.text import uenc |
67 |
from copy import deepcopy |
68 |
from snf_django.lib.api import faults |
69 |
|
70 |
Location = namedtuple("ObjectLocation", ["account", "container", "path"]) |
71 |
|
72 |
logger = logging.getLogger(__name__) |
73 |
|
74 |
|
75 |
PLANKTON_DOMAIN = 'plankton'
|
76 |
PLANKTON_PREFIX = 'plankton:'
|
77 |
PROPERTY_PREFIX = 'property:'
|
78 |
|
79 |
PLANKTON_META = ('container_format', 'disk_format', 'name', |
80 |
'status', 'created_at') |
81 |
|
82 |
MAX_META_KEY_LENGTH = 128 - len(PLANKTON_DOMAIN) - len(PROPERTY_PREFIX) |
83 |
MAX_META_VALUE_LENGTH = 256
|
84 |
|
85 |
from pithos.backends.util import PithosBackendPool |
86 |
_pithos_backend_pool = \ |
87 |
PithosBackendPool( |
88 |
settings.PITHOS_BACKEND_POOL_SIZE, |
89 |
astakos_auth_url=settings.ASTAKOS_AUTH_URL, |
90 |
service_token=settings.CYCLADES_SERVICE_TOKEN, |
91 |
astakosclient_poolsize=settings.CYCLADES_ASTAKOSCLIENT_POOLSIZE, |
92 |
db_connection=settings.BACKEND_DB_CONNECTION, |
93 |
block_path=settings.BACKEND_BLOCK_PATH) |
94 |
|
95 |
|
96 |
def get_pithos_backend(): |
97 |
return _pithos_backend_pool.pool_get()
|
98 |
|
99 |
|
100 |
def format_timestamp(t): |
101 |
return strftime('%Y-%m-%d %H:%M:%S', gmtime(t)) |
102 |
|
103 |
|
104 |
def handle_pithos_backend(func): |
105 |
@wraps(func)
|
106 |
def wrapper(self, *args, **kwargs): |
107 |
backend = self.backend
|
108 |
backend.pre_exec() |
109 |
commit = False
|
110 |
try:
|
111 |
ret = func(self, *args, **kwargs)
|
112 |
except NotAllowedError:
|
113 |
raise faults.Forbidden
|
114 |
except (NameError, VersionNotExists): |
115 |
raise faults.ItemNotFound
|
116 |
except (AssertionError, ValueError): |
117 |
raise faults.BadRequest
|
118 |
except QuotaError:
|
119 |
raise faults.OverLimit
|
120 |
else:
|
121 |
commit = True
|
122 |
finally:
|
123 |
backend.post_exec(commit) |
124 |
return ret
|
125 |
return wrapper
|
126 |
|
127 |
|
128 |
class PlanktonBackend(object): |
129 |
"""A wrapper arround the pithos backend to simplify image handling."""
|
130 |
|
131 |
def __init__(self, user): |
132 |
self.user = user
|
133 |
self.backend = get_pithos_backend()
|
134 |
|
135 |
def close(self): |
136 |
"""Close PithosBackend(return to pool)"""
|
137 |
self.backend.close()
|
138 |
|
139 |
def __enter__(self): |
140 |
return self |
141 |
|
142 |
def __exit__(self, exc_type, exc_val, exc_tb): |
143 |
self.close()
|
144 |
self.backend = None |
145 |
return False |
146 |
|
147 |
@handle_pithos_backend
|
148 |
def get_image(self, uuid): |
149 |
return self._get_image(uuid) |
150 |
|
151 |
def _get_image(self, uuid): |
152 |
location, metadata = self._get_raw_metadata(uuid)
|
153 |
permissions = self._get_raw_permissions(uuid, location)
|
154 |
return image_to_dict(location, metadata, permissions)
|
155 |
|
156 |
@handle_pithos_backend
|
157 |
def add_property(self, uuid, key, value): |
158 |
location, _ = self._get_raw_metadata(uuid)
|
159 |
properties = self._prefix_properties({key: value})
|
160 |
self._update_metadata(uuid, location, properties, replace=False) |
161 |
|
162 |
@handle_pithos_backend
|
163 |
def remove_property(self, uuid, key): |
164 |
location, _ = self._get_raw_metadata(uuid)
|
165 |
# Use empty string to delete a property
|
166 |
properties = self._prefix_properties({key: ""}) |
167 |
self._update_metadata(uuid, location, properties, replace=False) |
168 |
|
169 |
@handle_pithos_backend
|
170 |
def update_properties(self, uuid, properties): |
171 |
location, _ = self._get_raw_metadata(uuid)
|
172 |
properties = self._prefix_properties(properties)
|
173 |
self._update_metadata(uuid, location, properties, replace=False) |
174 |
|
175 |
@staticmethod
|
176 |
def _prefix_properties(properties): |
177 |
"""Add property prefix to properties."""
|
178 |
return dict([(PROPERTY_PREFIX + k, v) for k, v in properties.items()]) |
179 |
|
180 |
@staticmethod
|
181 |
def _unprefix_properties(properties): |
182 |
"""Remove property prefix from properties."""
|
183 |
return dict([(k.replace(PROPERTY_PREFIX, "", 1), v) |
184 |
for k, v in properties.items()]) |
185 |
|
186 |
@staticmethod
|
187 |
def _prefix_metadata(metadata): |
188 |
"""Add plankton prefix to metadata."""
|
189 |
return dict([(PLANKTON_PREFIX + k, v) for k, v in metadata.items()]) |
190 |
|
191 |
@staticmethod
|
192 |
def _unprefix_metadata(metadata): |
193 |
"""Remove plankton prefix from metadata."""
|
194 |
return dict([(k.replace(PLANKTON_PREFIX, "", 1), v) |
195 |
for k, v in metadata.items()]) |
196 |
|
197 |
@handle_pithos_backend
|
198 |
def update_metadata(self, uuid, metadata): |
199 |
location, _ = self._get_raw_metadata(uuid)
|
200 |
|
201 |
is_public = metadata.pop("is_public", None) |
202 |
if is_public is not None: |
203 |
self._set_public(uuid, location, public=is_public)
|
204 |
|
205 |
# Each property is stored as a separate prefixed metadata
|
206 |
meta = deepcopy(metadata) |
207 |
properties = meta.pop("properties", {})
|
208 |
meta.update(self._prefix_properties(properties))
|
209 |
|
210 |
self._update_metadata(uuid, location, metadata=meta, replace=False) |
211 |
|
212 |
return self._get_image(uuid) |
213 |
|
214 |
def _update_metadata(self, uuid, location, metadata, replace=False): |
215 |
_prefixed_metadata = self._prefix_metadata(metadata)
|
216 |
prefixed = {} |
217 |
for k, v in _prefixed_metadata.items(): |
218 |
# Encode to UTF-8
|
219 |
k, v = uenc(k), uenc(v) |
220 |
# Check the length of key/value
|
221 |
if len(k) > 128: |
222 |
raise faults.BadRequest('Metadata keys should be less than %s' |
223 |
' characters' % MAX_META_KEY_LENGTH)
|
224 |
if len(v) > 256: |
225 |
raise faults.BadRequest('Metadata values should be less than' |
226 |
' %scharacters.'
|
227 |
% MAX_META_VALUE_LENGTH) |
228 |
prefixed[k] = v |
229 |
|
230 |
account, container, path = location |
231 |
self.backend.update_object_meta(self.user, account, container, path, |
232 |
PLANKTON_DOMAIN, prefixed, replace) |
233 |
logger.debug("User '%s' updated image '%s', metadata: '%s'", self.user, |
234 |
uuid, prefixed) |
235 |
|
236 |
def _get_raw_metadata(self, uuid, version=None, check_image=True): |
237 |
"""Get info and metadata in Plankton doamin for the Pithos object.
|
238 |
|
239 |
Return the location and the metadata of the Pithos object.
|
240 |
If 'check_image' is set, check that the Pithos object is a registered
|
241 |
Plankton Image.
|
242 |
|
243 |
"""
|
244 |
# Convert uuid to location
|
245 |
account, container, path = self.backend.get_uuid(self.user, uuid) |
246 |
try:
|
247 |
meta = self.backend.get_object_meta(self.user, account, container, |
248 |
path, PLANKTON_DOMAIN, version) |
249 |
meta["deleted"] = False |
250 |
except NameError: |
251 |
if version is not None: |
252 |
raise
|
253 |
versions = self.backend.list_versions(self.user, account, |
254 |
container, path) |
255 |
assert(versions), ("Object without versions: %s/%s/%s" % |
256 |
(account, container, path)) |
257 |
# Object was deleted, use the latest version
|
258 |
version, timestamp = versions[-1]
|
259 |
meta = self.backend.get_object_meta(self.user, account, container, |
260 |
path, PLANKTON_DOMAIN, version) |
261 |
meta["deleted"] = True |
262 |
|
263 |
if check_image and PLANKTON_PREFIX + "name" not in meta: |
264 |
# Check that object is an image by checking if it has an Image name
|
265 |
# in Plankton metadata
|
266 |
raise faults.ItemNotFound("Image '%s' does not exist." % uuid) |
267 |
|
268 |
return Location(account, container, path), meta
|
269 |
|
270 |
# Users and Permissions
|
271 |
@handle_pithos_backend
|
272 |
def add_user(self, uuid, user): |
273 |
assert(isinstance(user, basestring)) |
274 |
location, _ = self._get_raw_metadata(uuid)
|
275 |
permissions = self._get_raw_permissions(uuid, location)
|
276 |
read = set(permissions.get("read", [])) |
277 |
if not user in read: |
278 |
read.add(user) |
279 |
permissions["read"] = list(read) |
280 |
self._update_permissions(uuid, location, permissions)
|
281 |
|
282 |
@handle_pithos_backend
|
283 |
def remove_user(self, uuid, user): |
284 |
assert(isinstance(user, basestring)) |
285 |
location, _ = self._get_raw_metadata(uuid)
|
286 |
permissions = self._get_raw_permissions(uuid, location)
|
287 |
read = set(permissions.get("read", [])) |
288 |
if user in read: |
289 |
read.remove(user) |
290 |
permissions["read"] = list(read) |
291 |
self._update_permissions(uuid, location, permissions)
|
292 |
|
293 |
@handle_pithos_backend
|
294 |
def replace_users(self, uuid, users): |
295 |
assert(isinstance(users, list)) |
296 |
location, _ = self._get_raw_metadata(uuid)
|
297 |
permissions = self._get_raw_permissions(uuid, location)
|
298 |
read = set(permissions.get("read", [])) |
299 |
if "*" in read: # Retain public permissions |
300 |
users.append("*")
|
301 |
permissions["read"] = list(users) |
302 |
self._update_permissions(uuid, location, permissions)
|
303 |
|
304 |
@handle_pithos_backend
|
305 |
def list_users(self, uuid): |
306 |
location, _ = self._get_raw_metadata(uuid)
|
307 |
permissions = self._get_raw_permissions(uuid, location)
|
308 |
return [user for user in permissions.get('read', []) if user != '*'] |
309 |
|
310 |
def _set_public(self, uuid, location, public): |
311 |
permissions = self._get_raw_permissions(uuid, location)
|
312 |
assert(isinstance(public, bool)) |
313 |
read = set(permissions.get("read", [])) |
314 |
if public and "*" not in read: |
315 |
read.add("*")
|
316 |
elif not public and "*" in read: |
317 |
read.discard("*")
|
318 |
permissions["read"] = list(read) |
319 |
self._update_permissions(uuid, location, permissions)
|
320 |
return permissions
|
321 |
|
322 |
def _get_raw_permissions(self, uuid, location): |
323 |
account, container, path = location |
324 |
_a, path, permissions = \ |
325 |
self.backend.get_object_permissions(self.user, account, container, |
326 |
path) |
327 |
|
328 |
if path is None and permissions != {}: |
329 |
raise Exception("Database Inconsistency Error:" |
330 |
" Image '%s' got permissions from 'None' path." %
|
331 |
uuid) |
332 |
|
333 |
return permissions
|
334 |
|
335 |
def _update_permissions(self, uuid, location, permissions): |
336 |
account, container, path = location |
337 |
self.backend.update_object_permissions(self.user, account, container, |
338 |
path, permissions) |
339 |
logger.debug("User '%s' updated image '%s' permissions: '%s'",
|
340 |
self.user, uuid, permissions)
|
341 |
|
342 |
@handle_pithos_backend
|
343 |
def register(self, name, image_url, metadata): |
344 |
# Validate that metadata are allowed
|
345 |
if "id" in metadata: |
346 |
raise faults.BadRequest("Passing an ID is not supported") |
347 |
store = metadata.pop("store", "pithos") |
348 |
if store != "pithos": |
349 |
raise faults.BadRequest("Invalid store '%s'. Only 'pithos' store" |
350 |
" is supported" % store)
|
351 |
disk_format = metadata.setdefault("disk_format",
|
352 |
settings.DEFAULT_DISK_FORMAT) |
353 |
if disk_format not in settings.ALLOWED_DISK_FORMATS: |
354 |
raise faults.BadRequest("Invalid disk format '%s'" % disk_format) |
355 |
container_format =\ |
356 |
metadata.setdefault("container_format",
|
357 |
settings.DEFAULT_CONTAINER_FORMAT) |
358 |
if container_format not in settings.ALLOWED_CONTAINER_FORMATS: |
359 |
raise faults.BadRequest("Invalid container format '%s'" % |
360 |
container_format) |
361 |
|
362 |
account, container, path = split_url(image_url) |
363 |
location = Location(account, container, path) |
364 |
meta = self.backend.get_object_meta(self.user, account, container, |
365 |
path, PLANKTON_DOMAIN, None)
|
366 |
uuid = meta["uuid"]
|
367 |
|
368 |
# Validate that 'size' and 'checksum'
|
369 |
size = metadata.pop('size', int(meta['bytes'])) |
370 |
if not isinstance(size, int) or int(size) != int(meta["bytes"]): |
371 |
raise faults.BadRequest("Invalid 'size' field") |
372 |
|
373 |
checksum = metadata.pop('checksum', meta['hash']) |
374 |
if not isinstance(checksum, basestring) or checksum != meta['hash']: |
375 |
raise faults.BadRequest("Invalid checksum field") |
376 |
|
377 |
users = [self.user]
|
378 |
public = metadata.pop("is_public", False) |
379 |
if not isinstance(public, bool): |
380 |
raise faults.BadRequest("Invalid value for 'is_public' metadata") |
381 |
if public:
|
382 |
users.append("*")
|
383 |
permissions = {'read': users}
|
384 |
self._update_permissions(uuid, location, permissions)
|
385 |
|
386 |
# Each property is stored as a separate prefixed metadata
|
387 |
meta = deepcopy(metadata) |
388 |
properties = meta.pop("properties", {})
|
389 |
meta.update(self._prefix_properties(properties))
|
390 |
# Add extra metadata
|
391 |
meta["name"] = name
|
392 |
meta["status"] = "AVAILABLE" |
393 |
meta['created_at'] = str(time()) |
394 |
#meta["is_snapshot"] = False
|
395 |
self._update_metadata(uuid, location, metadata=meta, replace=False) |
396 |
|
397 |
logger.debug("User '%s' registered image '%s'('%s')", self.user, |
398 |
uuid, location) |
399 |
return self._get_image(uuid) |
400 |
|
401 |
@handle_pithos_backend
|
402 |
def unregister(self, uuid): |
403 |
"""Unregister an Image.
|
404 |
|
405 |
Unregister an Image by removing all the metadata in the Plankton
|
406 |
domain. The Pithos file is not deleted.
|
407 |
|
408 |
"""
|
409 |
location, _ = self._get_raw_metadata(uuid)
|
410 |
self._update_metadata(uuid, location, metadata={}, replace=True) |
411 |
logger.debug("User '%s' unregistered image '%s'", self.user, uuid) |
412 |
|
413 |
# List functions
|
414 |
def _list_images(self, user=None, filters=None, params=None): |
415 |
filters = filters or {}
|
416 |
|
417 |
# TODO: Use filters
|
418 |
# # Fix keys
|
419 |
# keys = [PLANKTON_PREFIX + 'name']
|
420 |
# size_range = (None, None)
|
421 |
# for key, val in filters.items():
|
422 |
# if key == 'size_min':
|
423 |
# size_range = (val, size_range[1])
|
424 |
# elif key == 'size_max':
|
425 |
# size_range = (size_range[0], val)
|
426 |
# else:
|
427 |
# keys.append('%s = %s' % (PLANKTON_PREFIX + key, val))
|
428 |
_images = self.backend.get_domain_objects(domain=PLANKTON_DOMAIN,
|
429 |
user=user) |
430 |
|
431 |
images = [] |
432 |
for (location, metadata, permissions) in _images: |
433 |
location = Location(*location.split("/", 2)) |
434 |
images.append(image_to_dict(location, metadata, permissions)) |
435 |
|
436 |
if params is None: |
437 |
params = {} |
438 |
|
439 |
key = itemgetter(params.get('sort_key', 'created_at')) |
440 |
reverse = params.get('sort_dir', 'desc') == 'desc' |
441 |
images.sort(key=key, reverse=reverse) |
442 |
return images
|
443 |
|
444 |
@handle_pithos_backend
|
445 |
def list_images(self, filters=None, params=None): |
446 |
return self._list_images(user=self.user, filters=filters, |
447 |
params=params) |
448 |
|
449 |
@handle_pithos_backend
|
450 |
def list_shared_images(self, member, filters=None, params=None): |
451 |
images = self._list_images(user=self.user, filters=filters, |
452 |
params=params) |
453 |
is_shared = lambda img: not img["is_public"] and img["owner"] == member |
454 |
return filter(is_shared, images) |
455 |
|
456 |
@handle_pithos_backend
|
457 |
def list_public_images(self, filters=None, params=None): |
458 |
images = self._list_images(user=None, filters=filters, params=params) |
459 |
return filter(lambda img: img["is_public"], images) |
460 |
|
461 |
# # Snapshots
|
462 |
# def list_snapshots(self, user=None):
|
463 |
# _snapshots = self.list_images()
|
464 |
# return [s for s in _snapshots if s["is_snapshot"]]
|
465 |
|
466 |
# @handle_pithos_backend
|
467 |
# def get_snapshot(self, user, snapshot_uuid):
|
468 |
# snap = self._get_image(snapshot_uuid)
|
469 |
# if snap.get("is_snapshot", False) is False:
|
470 |
# raise faults.ItemNotFound("Snapshots '%s' does not exist" %
|
471 |
# snapshot_uuid)
|
472 |
# return snap
|
473 |
|
474 |
# @handle_pithos_backend
|
475 |
# def delete_snapshot(self, snapshot_uuid):
|
476 |
# self.backend.delete_object_for_uuid(self.user, snapshot_uuid)
|
477 |
|
478 |
# @handle_pithos_backend
|
479 |
# def update_status(self, image_uuid, status):
|
480 |
# """Update status of snapshot"""
|
481 |
# location, _ = self._get_raw_metadata(image_uuid)
|
482 |
# properties = {"status": status.upper()}
|
483 |
# self._update_metadata(image_uuid, location, properties,
|
484 |
# replace=False)
|
485 |
# return self._get_image(image_uuid)
|
486 |
|
487 |
|
488 |
def create_url(account, container, name): |
489 |
"""Create a Pithos URL from the object info"""
|
490 |
assert "/" not in account, "Invalid account" |
491 |
assert "/" not in container, "Invalid container" |
492 |
return "pithos://%s/%s/%s" % (account, container, name) |
493 |
|
494 |
|
495 |
def split_url(url): |
496 |
"""Get object info from the Pithos URL"""
|
497 |
assert(isinstance(url, basestring)) |
498 |
t = url.split('/', 4) |
499 |
assert t[0] == "pithos:", "Invalid url" |
500 |
assert len(t) == 5, "Invalid url" |
501 |
return t[2:5] |
502 |
|
503 |
|
504 |
def image_to_dict(location, metadata, permissions): |
505 |
"""Render an image to a dictionary"""
|
506 |
account, container, name = location |
507 |
|
508 |
image = {} |
509 |
image["id"] = metadata["uuid"] |
510 |
image["mapfile"] = metadata["hash"] |
511 |
image["checksum"] = metadata["hash"] |
512 |
image["location"] = create_url(account, container, name)
|
513 |
image["size"] = metadata["bytes"] |
514 |
image['owner'] = account
|
515 |
image["store"] = u"pithos" |
516 |
#image["is_snapshot"] = metadata.pop(PLANKTON_PREFIX + "is_snapshot",
|
517 |
#False)
|
518 |
# Permissions
|
519 |
users = list(permissions.get("read", [])) |
520 |
image["is_public"] = "*" in users |
521 |
image["users"] = [u for u in users if u != "*"] |
522 |
# Timestamps
|
523 |
updated_at = metadata["version_timestamp"]
|
524 |
created_at = metadata.get("created_at", updated_at)
|
525 |
image["created_at"] = format_timestamp(created_at)
|
526 |
image["updated_at"] = format_timestamp(updated_at)
|
527 |
if metadata.get("deleted", False): |
528 |
image["deleted_at"] = image["updated_at"] |
529 |
else:
|
530 |
image["deleted_at"] = "" |
531 |
|
532 |
properties = {} |
533 |
for key, val in metadata.items(): |
534 |
# Get plankton properties
|
535 |
if key.startswith(PLANKTON_PREFIX):
|
536 |
# Remove plankton prefix
|
537 |
key = key.replace(PLANKTON_PREFIX, "")
|
538 |
# Keep only those in plankton metadata
|
539 |
if key in PLANKTON_META: |
540 |
if key != "created_at": |
541 |
# created timestamp is return in 'created_at' field
|
542 |
image[key] = val |
543 |
elif key.startswith(PROPERTY_PREFIX):
|
544 |
key = key.replace(PROPERTY_PREFIX, "")
|
545 |
properties[key] = val |
546 |
image["properties"] = properties
|
547 |
|
548 |
return image
|
549 |
|
550 |
|
551 |
class JSONFileBackend(PlanktonBackend): |
552 |
"""
|
553 |
A dummy image backend that loads available images from a file with json
|
554 |
formatted content.
|
555 |
|
556 |
usage:
|
557 |
PLANKTON_BACKEND_MODULE = 'synnefo.plankton.backend.JSONFileBackend'
|
558 |
PLANKTON_IMAGES_JSON_BACKEND_FILE = '/tmp/images.json'
|
559 |
|
560 |
# loading images from an existing plankton service
|
561 |
$ curl -H "X-Auth-Token: <MYTOKEN>" \
|
562 |
https://cyclades.synnefo.org/plankton/images/detail | \
|
563 |
python -m json.tool > /tmp/images.json
|
564 |
"""
|
565 |
def __init__(self, userid): |
566 |
self.images_file = getattr(settings, |
567 |
'PLANKTON_IMAGES_JSON_BACKEND_FILE', '') |
568 |
if not os.path.exists(self.images_file): |
569 |
raise Exception("Invalid plankgon images json backend file: %s", |
570 |
self.images_file)
|
571 |
fp = file(self.images_file) |
572 |
self.images = json.load(fp)
|
573 |
fp.close() |
574 |
|
575 |
def iter(self, *args, **kwargs): |
576 |
return self.images.__iter__() |
577 |
|
578 |
def list_images(self, *args, **kwargs): |
579 |
return self.images |
580 |
|
581 |
def get_image(self, image_uuid): |
582 |
try:
|
583 |
return filter(lambda i: i['id'] == image_uuid, self.images)[0] |
584 |
except IndexError: |
585 |
raise Exception("Unknown image uuid: %s" % image_uuid) |
586 |
|
587 |
def close(self): |
588 |
pass
|
589 |
|
590 |
|
591 |
def get_backend(): |
592 |
backend_module = getattr(settings, 'PLANKTON_BACKEND_MODULE', None) |
593 |
|
594 |
if not backend_module: |
595 |
# no setting set
|
596 |
return PlanktonBackend
|
597 |
|
598 |
parts = backend_module.split(".")
|
599 |
module = ".".join(parts[:-1]) |
600 |
cls = parts[-1]
|
601 |
try:
|
602 |
return getattr(importlib.import_module(module), cls) |
603 |
except (ImportError, AttributeError), e: |
604 |
raise ImportError("Cannot import plankton module: %s (%s)" % |
605 |
(backend_module, e.message)) |