Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / plankton / backend.py @ b4135a1b

History | View | Annotate | Download (19.8 kB)

1 cda71050 Christos Stavrakakis
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2 cda71050 Christos Stavrakakis
3 c34de90f Giorgos Verigakis
#
4 c34de90f Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
5 c34de90f Giorgos Verigakis
# without modification, are permitted provided that the following
6 c34de90f Giorgos Verigakis
# conditions are met:
7 c34de90f Giorgos Verigakis
#
8 c34de90f Giorgos Verigakis
#   1. Redistributions of source code must retain the above
9 c34de90f Giorgos Verigakis
#      copyright notice, this list of conditions and the following
10 c34de90f Giorgos Verigakis
#      disclaimer.
11 c34de90f Giorgos Verigakis
#
12 c34de90f Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
13 c34de90f Giorgos Verigakis
#      copyright notice, this list of conditions and the following
14 c34de90f Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
15 c34de90f Giorgos Verigakis
#      provided with the distribution.
16 c34de90f Giorgos Verigakis
#
17 c34de90f Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18 c34de90f Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 c34de90f Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 c34de90f Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21 c34de90f Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 c34de90f Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 c34de90f Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 c34de90f Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 c34de90f Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 c34de90f Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 c34de90f Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 c34de90f Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
29 c34de90f Giorgos Verigakis
#
30 c34de90f Giorgos Verigakis
# The views and conclusions contained in the software and
31 c34de90f Giorgos Verigakis
# documentation are those of the authors and should not be
32 c34de90f Giorgos Verigakis
# interpreted as representing official policies, either expressed
33 c34de90f Giorgos Verigakis
# or implied, of GRNET S.A.
34 c34de90f Giorgos Verigakis
35 f5afd99b Giorgos Verigakis
"""
36 bfd9f988 Giorgos Verigakis
The Plankton attributes are the following:
37 0a72907b Giorgos Verigakis
  - checksum: the 'hash' meta
38 7bd1d3b5 Giorgos Verigakis
  - container_format: stored as a user meta
39 0a72907b Giorgos Verigakis
  - created_at: the 'modified' meta of the first version
40 0a72907b Giorgos Verigakis
  - deleted_at: the timestamp of the last version
41 7bd1d3b5 Giorgos Verigakis
  - disk_format: stored as a user meta
42 0a72907b Giorgos Verigakis
  - id: the 'uuid' meta
43 f5afd99b Giorgos Verigakis
  - is_public: True if there is a * entry for the read permission
44 0a72907b Giorgos Verigakis
  - location: generated based on the file's path
45 7bd1d3b5 Giorgos Verigakis
  - name: stored as a user meta
46 0a72907b Giorgos Verigakis
  - owner: the file's account
47 7bd1d3b5 Giorgos Verigakis
  - properties: stored as user meta prefixed with PROPERTY_PREFIX
48 0a72907b Giorgos Verigakis
  - size: the 'bytes' meta
49 7bd1d3b5 Giorgos Verigakis
  - status: stored as a system meta
50 7bd1d3b5 Giorgos Verigakis
  - store: is always 'pithos'
51 0a72907b Giorgos Verigakis
  - updated_at: the 'modified' meta
52 f5afd99b Giorgos Verigakis
"""
53 f5afd99b Giorgos Verigakis
54 c34de90f Giorgos Verigakis
import json
55 c23d211a Giorgos Verigakis
import warnings
56 cda71050 Christos Stavrakakis
import logging
57 9393d4ee Kostas Papadimitriou
import os
58 9393d4ee Kostas Papadimitriou
59 3a528c36 Christos Stavrakakis
from time import time, gmtime, strftime
60 cda71050 Christos Stavrakakis
from functools import wraps
61 cda71050 Christos Stavrakakis
from operator import itemgetter
62 c34de90f Giorgos Verigakis
63 cda71050 Christos Stavrakakis
from django.conf import settings
64 9393d4ee Kostas Papadimitriou
from django.utils import importlib
65 cda71050 Christos Stavrakakis
from pithos.backends.base import NotAllowedError, VersionNotExists
66 f4366b6c Stratos Psomadakis
67 469d0997 Georgios D. Tsoukalas
logger = logging.getLogger(__name__)
68 c34de90f Giorgos Verigakis
69 c34de90f Giorgos Verigakis
70 0a72907b Giorgos Verigakis
PLANKTON_DOMAIN = 'plankton'
71 bfd9f988 Giorgos Verigakis
PLANKTON_PREFIX = 'plankton:'
72 7bd1d3b5 Giorgos Verigakis
PROPERTY_PREFIX = 'property:'
73 7bd1d3b5 Giorgos Verigakis
74 bfd9f988 Giorgos Verigakis
PLANKTON_META = ('container_format', 'disk_format', 'name', 'properties',
75 3a528c36 Christos Stavrakakis
                 'status', 'created_at')
76 c34de90f Giorgos Verigakis
77 7784ab88 Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
78 7784ab88 Christos Stavrakakis
_pithos_backend_pool = \
79 b336e6fa Georgios D. Tsoukalas
    PithosBackendPool(
80 e3f006b0 Christos Stavrakakis
        settings.PITHOS_BACKEND_POOL_SIZE,
81 e3ff6830 Georgios D. Tsoukalas
        astakos_url=settings.ASTAKOS_BASE_URL,
82 18c4414d Giorgos Korfiatis
        service_token=settings.CYCLADES_SERVICE_TOKEN,
83 b0c95903 Giorgos Korfiatis
        astakosclient_poolsize=settings.CYCLADES_ASTAKOSCLIENT_POOLSIZE,
84 b336e6fa Georgios D. Tsoukalas
        db_connection=settings.BACKEND_DB_CONNECTION,
85 b336e6fa Georgios D. Tsoukalas
        block_path=settings.BACKEND_BLOCK_PATH)
86 7784ab88 Christos Stavrakakis
87 7784ab88 Christos Stavrakakis
88 7784ab88 Christos Stavrakakis
def get_pithos_backend():
89 7784ab88 Christos Stavrakakis
    return _pithos_backend_pool.pool_get()
90 7784ab88 Christos Stavrakakis
91 7784ab88 Christos Stavrakakis
92 cda71050 Christos Stavrakakis
def create_url(account, container, name):
93 cda71050 Christos Stavrakakis
    assert "/" not in account, "Invalid account"
94 cda71050 Christos Stavrakakis
    assert "/" not in container, "Invalid container"
95 cda71050 Christos Stavrakakis
    return "pithos://%s/%s/%s" % (account, container, name)
96 cda71050 Christos Stavrakakis
97 cda71050 Christos Stavrakakis
98 cda71050 Christos Stavrakakis
def split_url(url):
99 cda71050 Christos Stavrakakis
    """Returns (accout, container, object) from a url string"""
100 cda71050 Christos Stavrakakis
    t = url.split('/', 4)
101 59573532 Christos Stavrakakis
    assert t[0] == "pithos:", "Invalid url"
102 cda71050 Christos Stavrakakis
    assert len(t) == 5, "Invalid url"
103 cda71050 Christos Stavrakakis
    return t[2:5]
104 cda71050 Christos Stavrakakis
105 cda71050 Christos Stavrakakis
106 cda71050 Christos Stavrakakis
def format_timestamp(t):
107 cda71050 Christos Stavrakakis
    return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
108 cda71050 Christos Stavrakakis
109 cda71050 Christos Stavrakakis
110 2db7d9df Christos Stavrakakis
def handle_backend_exceptions(func):
111 2db7d9df Christos Stavrakakis
    @wraps(func)
112 2db7d9df Christos Stavrakakis
    def wrapper(*args, **kwargs):
113 2db7d9df Christos Stavrakakis
        try:
114 2db7d9df Christos Stavrakakis
            return func(*args, **kwargs)
115 f6ff4b40 Christos Stavrakakis
        except NotAllowedError:
116 cda71050 Christos Stavrakakis
            raise Forbidden
117 cda71050 Christos Stavrakakis
        except NameError:
118 cda71050 Christos Stavrakakis
            raise ImageNotFound
119 cda71050 Christos Stavrakakis
        except VersionNotExists:
120 cda71050 Christos Stavrakakis
            raise ImageNotFound
121 2db7d9df Christos Stavrakakis
    return wrapper
122 2db7d9df Christos Stavrakakis
123 2db7d9df Christos Stavrakakis
124 3347a30c Georgios D. Tsoukalas
def commit_on_success(func):
125 3347a30c Georgios D. Tsoukalas
    def wrapper(self, *args, **kwargs):
126 3347a30c Georgios D. Tsoukalas
        backend = self.backend
127 3347a30c Georgios D. Tsoukalas
        backend.pre_exec()
128 3347a30c Georgios D. Tsoukalas
        try:
129 3347a30c Georgios D. Tsoukalas
            ret = func(self, *args, **kwargs)
130 3347a30c Georgios D. Tsoukalas
        except:
131 3347a30c Georgios D. Tsoukalas
            backend.post_exec(False)
132 3347a30c Georgios D. Tsoukalas
            raise
133 3347a30c Georgios D. Tsoukalas
        else:
134 3347a30c Georgios D. Tsoukalas
            backend.post_exec(True)
135 3347a30c Georgios D. Tsoukalas
        return ret
136 3347a30c Georgios D. Tsoukalas
    return wrapper
137 3347a30c Georgios D. Tsoukalas
138 3347a30c Georgios D. Tsoukalas
139 ffab341c Christos Stavrakakis
class ImageBackend(object):
140 f5afd99b Giorgos Verigakis
    """A wrapper arround the pithos backend to simplify image handling."""
141 1e28ba40 Christos Stavrakakis
142 c34de90f Giorgos Verigakis
    def __init__(self, user):
143 c34de90f Giorgos Verigakis
        self.user = user
144 7784ab88 Christos Stavrakakis
145 c23d211a Giorgos Verigakis
        original_filters = warnings.filters
146 c23d211a Giorgos Verigakis
        warnings.simplefilter('ignore')         # Suppress SQLAlchemy warnings
147 7784ab88 Christos Stavrakakis
        self.backend = get_pithos_backend()
148 c23d211a Giorgos Verigakis
        warnings.filters = original_filters     # Restore warnings
149 7784ab88 Christos Stavrakakis
150 cda71050 Christos Stavrakakis
    def close(self):
151 cda71050 Christos Stavrakakis
        """Close PithosBackend(return to pool)"""
152 cda71050 Christos Stavrakakis
        self.backend.close()
153 cda71050 Christos Stavrakakis
154 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
155 3347a30c Georgios D. Tsoukalas
    @commit_on_success
156 cda71050 Christos Stavrakakis
    def get_image(self, image_uuid):
157 cda71050 Christos Stavrakakis
        """Retrieve information about an image."""
158 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
159 cda71050 Christos Stavrakakis
        return self._get_image(image_url)
160 1e28ba40 Christos Stavrakakis
161 cda71050 Christos Stavrakakis
    def _get_image_url(self, image_uuid):
162 cda71050 Christos Stavrakakis
        """Get the Pithos url that corresponds to an image UUID."""
163 cda71050 Christos Stavrakakis
        account, container, name = self.backend.get_uuid(self.user, image_uuid)
164 cda71050 Christos Stavrakakis
        return create_url(account, container, name)
165 1e28ba40 Christos Stavrakakis
166 cda71050 Christos Stavrakakis
    def _get_image(self, image_url):
167 cda71050 Christos Stavrakakis
        """Get information about an Image.
168 cda71050 Christos Stavrakakis

169 cda71050 Christos Stavrakakis
        Get all available information about an Image.
170 cda71050 Christos Stavrakakis
        """
171 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
172 cda71050 Christos Stavrakakis
        try:
173 cda71050 Christos Stavrakakis
            meta = self._get_meta(image_url)
174 14c94c48 Christos Stavrakakis
            meta["deleted"] = ""
175 cda71050 Christos Stavrakakis
        except NameError:
176 3a528c36 Christos Stavrakakis
            versions = self.backend.list_versions(self.user, account,
177 3a528c36 Christos Stavrakakis
                                                  container, name)
178 3a528c36 Christos Stavrakakis
            if not versions:
179 3a528c36 Christos Stavrakakis
                raise Exception("Image without versions %s" % image_url)
180 7bd1d3b5 Giorgos Verigakis
            # Object was deleted, use the latest version
181 7bd1d3b5 Giorgos Verigakis
            version, timestamp = versions[-1]
182 cda71050 Christos Stavrakakis
            meta = self._get_meta(image_url, version)
183 14c94c48 Christos Stavrakakis
            meta["deleted"] = timestamp
184 14c94c48 Christos Stavrakakis
185 0a72907b Giorgos Verigakis
        if PLANKTON_PREFIX + 'name' not in meta:
186 125c682c Christos Stavrakakis
            logger.warning("Image without Plankton name! url %s meta %s",
187 125c682c Christos Stavrakakis
                           image_url, meta)
188 125c682c Christos Stavrakakis
            meta[PLANKTON_PREFIX + "name"] = ""
189 1e28ba40 Christos Stavrakakis
190 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
191 14c94c48 Christos Stavrakakis
        return image_to_dict(image_url, meta, permissions)
192 1e28ba40 Christos Stavrakakis
193 cda71050 Christos Stavrakakis
    def _get_meta(self, image_url, version=None):
194 cda71050 Christos Stavrakakis
        """Get object's metadata."""
195 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
196 cda71050 Christos Stavrakakis
        return self.backend.get_object_meta(self.user, account, container,
197 cda71050 Christos Stavrakakis
                                            name, PLANKTON_DOMAIN, version)
198 1e28ba40 Christos Stavrakakis
199 cda71050 Christos Stavrakakis
    def _update_meta(self, image_url, meta, replace=False):
200 cda71050 Christos Stavrakakis
        """Update object's metadata."""
201 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
202 1e28ba40 Christos Stavrakakis
203 cda71050 Christos Stavrakakis
        prefixed = {}
204 cda71050 Christos Stavrakakis
        for key, val in meta.items():
205 cda71050 Christos Stavrakakis
            if key in PLANKTON_META:
206 cda71050 Christos Stavrakakis
                if key == "properties":
207 cda71050 Christos Stavrakakis
                    val = json.dumps(val)
208 cda71050 Christos Stavrakakis
                prefixed[PLANKTON_PREFIX + key] = val
209 1e28ba40 Christos Stavrakakis
210 cda71050 Christos Stavrakakis
        self.backend.update_object_meta(self.user, account, container, name,
211 cda71050 Christos Stavrakakis
                                        PLANKTON_DOMAIN, prefixed, replace)
212 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', meta: '%s'", self.user,
213 62d3ea53 Christos Stavrakakis
                     image_url, prefixed)
214 1e28ba40 Christos Stavrakakis
215 cda71050 Christos Stavrakakis
    def _get_permissions(self, image_url):
216 cda71050 Christos Stavrakakis
        """Get object's permissions."""
217 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
218 cda71050 Christos Stavrakakis
        _a, path, permissions = \
219 cda71050 Christos Stavrakakis
            self.backend.get_object_permissions(self.user, account, container,
220 cda71050 Christos Stavrakakis
                                                name)
221 1e28ba40 Christos Stavrakakis
222 cda71050 Christos Stavrakakis
        if path is None:
223 cda71050 Christos Stavrakakis
            logger.warning("Image '%s' got permissions from None path",
224 cda71050 Christos Stavrakakis
                           image_url)
225 1e28ba40 Christos Stavrakakis
226 cda71050 Christos Stavrakakis
        return permissions
227 1e28ba40 Christos Stavrakakis
228 cda71050 Christos Stavrakakis
    def _update_permissions(self, image_url, permissions):
229 cda71050 Christos Stavrakakis
        """Update object's permissions."""
230 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
231 cda71050 Christos Stavrakakis
        self.backend.update_object_permissions(self.user, account, container,
232 cda71050 Christos Stavrakakis
                                               name, permissions)
233 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', permissions: '%s'",
234 62d3ea53 Christos Stavrakakis
                     self.user, image_url, permissions)
235 1e28ba40 Christos Stavrakakis
236 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
237 3347a30c Georgios D. Tsoukalas
    @commit_on_success
238 cda71050 Christos Stavrakakis
    def unregister(self, image_uuid):
239 cda71050 Christos Stavrakakis
        """Unregister an image.
240 1e28ba40 Christos Stavrakakis

241 cda71050 Christos Stavrakakis
        Unregister an image, by removing all metadata from the Pithos
242 cda71050 Christos Stavrakakis
        file that exist in the PLANKTON_DOMAIN.
243 1e28ba40 Christos Stavrakakis

244 cda71050 Christos Stavrakakis
        """
245 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
246 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
247 cda71050 Christos Stavrakakis
        # Unregister the image by removing all metadata from domain
248 cda71050 Christos Stavrakakis
        # 'PLANKTON_DOMAIN'
249 22b5ac0b Christos Stavrakakis
        meta = {}
250 22b5ac0b Christos Stavrakakis
        self._update_meta(image_url, meta, True)
251 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' deleted image '%s'", self.user, image_url)
252 1e28ba40 Christos Stavrakakis
253 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
254 3347a30c Georgios D. Tsoukalas
    @commit_on_success
255 cda71050 Christos Stavrakakis
    def add_user(self, image_uuid, add_user):
256 cda71050 Christos Stavrakakis
        """Add a user as an image member.
257 cda71050 Christos Stavrakakis

258 cda71050 Christos Stavrakakis
        Update read permissions of Pithos file, to include the specified user.
259 cda71050 Christos Stavrakakis

260 cda71050 Christos Stavrakakis
        """
261 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
262 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
263 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
264 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
265 cda71050 Christos Stavrakakis
        assert(isinstance(add_user, (str, unicode)))
266 cda71050 Christos Stavrakakis
        read.add(add_user)
267 cda71050 Christos Stavrakakis
        permissions["read"] = list(read)
268 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
269 1e28ba40 Christos Stavrakakis
270 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
271 3347a30c Georgios D. Tsoukalas
    @commit_on_success
272 cda71050 Christos Stavrakakis
    def remove_user(self, image_uuid, remove_user):
273 cda71050 Christos Stavrakakis
        """Remove the user from image members.
274 1e28ba40 Christos Stavrakakis

275 cda71050 Christos Stavrakakis
        Remove the specified user from the read permissions of the Pithos file.
276 cda71050 Christos Stavrakakis

277 cda71050 Christos Stavrakakis
        """
278 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
279 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
280 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
281 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
282 cda71050 Christos Stavrakakis
        assert(isinstance(remove_user, (str, unicode)))
283 cda71050 Christos Stavrakakis
        try:
284 cda71050 Christos Stavrakakis
            read.remove(remove_user)
285 cda71050 Christos Stavrakakis
        except ValueError:
286 cda71050 Christos Stavrakakis
            return  # TODO: User did not have access
287 cda71050 Christos Stavrakakis
        permissions["read"] = list(read)
288 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
289 1e28ba40 Christos Stavrakakis
290 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
291 3347a30c Georgios D. Tsoukalas
    @commit_on_success
292 cda71050 Christos Stavrakakis
    def replace_users(self, image_uuid, replace_users):
293 cda71050 Christos Stavrakakis
        """Replace image members.
294 f13aab5d Christos Stavrakakis

295 cda71050 Christos Stavrakakis
        Replace the read permissions of the Pithos files with the specified
296 cda71050 Christos Stavrakakis
        users. If image is specified as public, we must preserve * permission.
297 f13aab5d Christos Stavrakakis

298 f13aab5d Christos Stavrakakis
        """
299 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
300 cda71050 Christos Stavrakakis
        image = self._get_image(image_url)
301 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
302 cda71050 Christos Stavrakakis
        assert(isinstance(replace_users, list))
303 cda71050 Christos Stavrakakis
        permissions["read"] = replace_users
304 cda71050 Christos Stavrakakis
        if image.get("is_public", False):
305 cda71050 Christos Stavrakakis
            permissions["read"].append("*")
306 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
307 1e28ba40 Christos Stavrakakis
308 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
309 3347a30c Georgios D. Tsoukalas
    @commit_on_success
310 cda71050 Christos Stavrakakis
    def list_users(self, image_uuid):
311 cda71050 Christos Stavrakakis
        """List the image members.
312 cda71050 Christos Stavrakakis

313 cda71050 Christos Stavrakakis
        List the image members, by listing all users that have read permission
314 cda71050 Christos Stavrakakis
        to the corresponding Pithos file.
315 cda71050 Christos Stavrakakis

316 cda71050 Christos Stavrakakis
        """
317 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
318 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
319 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
320 cda71050 Christos Stavrakakis
        return [user for user in permissions.get('read', []) if user != '*']
321 1e28ba40 Christos Stavrakakis
322 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
323 3347a30c Georgios D. Tsoukalas
    @commit_on_success
324 cda71050 Christos Stavrakakis
    def update_metadata(self, image_uuid, metadata):
325 cda71050 Christos Stavrakakis
        """Update Image metadata."""
326 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
327 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
328 1e28ba40 Christos Stavrakakis
329 cda71050 Christos Stavrakakis
        is_public = metadata.pop("is_public", None)
330 cda71050 Christos Stavrakakis
        if is_public is not None:
331 cda71050 Christos Stavrakakis
            permissions = self._get_permissions(image_url)
332 cda71050 Christos Stavrakakis
            read = set(permissions.get("read", []))
333 cda71050 Christos Stavrakakis
            if is_public:
334 cda71050 Christos Stavrakakis
                read.add("*")
335 cda71050 Christos Stavrakakis
            else:
336 cda71050 Christos Stavrakakis
                read.discard("*")
337 cda71050 Christos Stavrakakis
            permissions["read"] = list(read)
338 cda71050 Christos Stavrakakis
            self._update_permissions(image_url, permissions)
339 cda71050 Christos Stavrakakis
        meta = {}
340 cda71050 Christos Stavrakakis
        meta["properties"] = metadata.pop("properties", {})
341 cda71050 Christos Stavrakakis
        meta.update(**metadata)
342 cda71050 Christos Stavrakakis
343 cda71050 Christos Stavrakakis
        self._update_meta(image_url, meta)
344 3347a30c Georgios D. Tsoukalas
        image_url = self._get_image_url(image_uuid)
345 3347a30c Georgios D. Tsoukalas
        return self._get_image(image_url)
346 1e28ba40 Christos Stavrakakis
347 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
348 3347a30c Georgios D. Tsoukalas
    @commit_on_success
349 cda71050 Christos Stavrakakis
    def register(self, name, image_url, metadata):
350 cda71050 Christos Stavrakakis
        # Validate that metadata are allowed
351 cda71050 Christos Stavrakakis
        if "id" in metadata:
352 cda71050 Christos Stavrakakis
            raise ValueError("Passing an ID is not supported")
353 cda71050 Christos Stavrakakis
        store = metadata.pop("store", "pithos")
354 cda71050 Christos Stavrakakis
        if store != "pithos":
355 cda71050 Christos Stavrakakis
            raise ValueError("Invalid store '%s'. Only 'pithos' store is"
356 cda71050 Christos Stavrakakis
                             "supported" % store)
357 cda71050 Christos Stavrakakis
        disk_format = metadata.setdefault("disk_format",
358 cda71050 Christos Stavrakakis
                                          settings.DEFAULT_DISK_FORMAT)
359 cda71050 Christos Stavrakakis
        if disk_format not in settings.ALLOWED_DISK_FORMATS:
360 cda71050 Christos Stavrakakis
            raise ValueError("Invalid disk format '%s'" % disk_format)
361 cda71050 Christos Stavrakakis
        container_format =\
362 cda71050 Christos Stavrakakis
            metadata.setdefault("container_format",
363 cda71050 Christos Stavrakakis
                                settings.DEFAULT_CONTAINER_FORMAT)
364 cda71050 Christos Stavrakakis
        if container_format not in settings.ALLOWED_CONTAINER_FORMATS:
365 cda71050 Christos Stavrakakis
            raise ValueError("Invalid container format '%s'" %
366 cda71050 Christos Stavrakakis
                             container_format)
367 cda71050 Christos Stavrakakis
368 cda71050 Christos Stavrakakis
        # Validate that 'size' and 'checksum' are valid
369 cda71050 Christos Stavrakakis
        account, container, object = split_url(image_url)
370 cda71050 Christos Stavrakakis
371 cda71050 Christos Stavrakakis
        meta = self._get_meta(image_url)
372 cda71050 Christos Stavrakakis
373 cda71050 Christos Stavrakakis
        size = int(metadata.pop('size', meta['bytes']))
374 cda71050 Christos Stavrakakis
        if size != meta['bytes']:
375 cda71050 Christos Stavrakakis
            raise ValueError("Invalid size")
376 cda71050 Christos Stavrakakis
377 cda71050 Christos Stavrakakis
        checksum = metadata.pop('checksum', meta['hash'])
378 cda71050 Christos Stavrakakis
        if checksum != meta['hash']:
379 cda71050 Christos Stavrakakis
            raise ValueError("Invalid checksum")
380 cda71050 Christos Stavrakakis
381 cda71050 Christos Stavrakakis
        # Fix permissions
382 cda71050 Christos Stavrakakis
        is_public = metadata.pop('is_public', False)
383 cda71050 Christos Stavrakakis
        if is_public:
384 cda71050 Christos Stavrakakis
            permissions = {'read': ['*']}
385 cda71050 Christos Stavrakakis
        else:
386 cda71050 Christos Stavrakakis
            permissions = {'read': [self.user]}
387 cda71050 Christos Stavrakakis
388 cda71050 Christos Stavrakakis
        # Update rest metadata
389 cda71050 Christos Stavrakakis
        meta = {}
390 cda71050 Christos Stavrakakis
        meta['properties'] = metadata.pop('properties', {})
391 3a528c36 Christos Stavrakakis
        # Add creation(register) timestamp as a metadata, to avoid extra
392 3a528c36 Christos Stavrakakis
        # queries when retrieving the list of images.
393 3a528c36 Christos Stavrakakis
        meta['created_at'] = time()
394 cda71050 Christos Stavrakakis
        meta.update(name=name, status='available', **metadata)
395 cda71050 Christos Stavrakakis
396 cda71050 Christos Stavrakakis
        # Do the actualy update in the Pithos backend
397 cda71050 Christos Stavrakakis
        self._update_meta(image_url, meta)
398 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
399 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' created image '%s'('%s')", self.user,
400 62d3ea53 Christos Stavrakakis
                     image_url, name)
401 cda71050 Christos Stavrakakis
        return self._get_image(image_url)
402 cda71050 Christos Stavrakakis
403 14c94c48 Christos Stavrakakis
    def _list_images(self, user=None, filters=None, params=None):
404 d19e8f77 Giorgos Verigakis
        filters = filters or {}
405 1e28ba40 Christos Stavrakakis
406 14c94c48 Christos Stavrakakis
        # TODO: Use filters
407 14c94c48 Christos Stavrakakis
        # # Fix keys
408 14c94c48 Christos Stavrakakis
        # keys = [PLANKTON_PREFIX + 'name']
409 14c94c48 Christos Stavrakakis
        # size_range = (None, None)
410 14c94c48 Christos Stavrakakis
        # for key, val in filters.items():
411 14c94c48 Christos Stavrakakis
        #     if key == 'size_min':
412 14c94c48 Christos Stavrakakis
        #         size_range = (val, size_range[1])
413 14c94c48 Christos Stavrakakis
        #     elif key == 'size_max':
414 14c94c48 Christos Stavrakakis
        #         size_range = (size_range[0], val)
415 14c94c48 Christos Stavrakakis
        #     else:
416 14c94c48 Christos Stavrakakis
        #         keys.append('%s = %s' % (PLANKTON_PREFIX + key, val))
417 14c94c48 Christos Stavrakakis
        _images = self.backend.get_domain_objects(domain=PLANKTON_DOMAIN,
418 14c94c48 Christos Stavrakakis
                                                  user=user)
419 14c94c48 Christos Stavrakakis
420 14c94c48 Christos Stavrakakis
        images = []
421 14c94c48 Christos Stavrakakis
        for (location, meta, permissions) in _images:
422 14c94c48 Christos Stavrakakis
            image_url = "pithos://" + location
423 14c94c48 Christos Stavrakakis
            meta["modified"] = meta["version_timestamp"]
424 14c94c48 Christos Stavrakakis
            images.append(image_to_dict(image_url, meta, permissions))
425 14c94c48 Christos Stavrakakis
426 14c94c48 Christos Stavrakakis
        if params is None:
427 14c94c48 Christos Stavrakakis
            params = {}
428 aed77afe Christos Stavrakakis
        key = itemgetter(params.get('sort_key', 'created_at'))
429 aed77afe Christos Stavrakakis
        reverse = params.get('sort_dir', 'desc') == 'desc'
430 aed77afe Christos Stavrakakis
        images.sort(key=key, reverse=reverse)
431 aed77afe Christos Stavrakakis
        return images
432 1e28ba40 Christos Stavrakakis
433 3347a30c Georgios D. Tsoukalas
    @commit_on_success
434 14c94c48 Christos Stavrakakis
    def list_images(self, filters=None, params=None):
435 14c94c48 Christos Stavrakakis
        return self._list_images(user=self.user, filters=filters,
436 14c94c48 Christos Stavrakakis
                                 params=params)
437 14c94c48 Christos Stavrakakis
438 3347a30c Georgios D. Tsoukalas
    @commit_on_success
439 14c94c48 Christos Stavrakakis
    def list_shared_images(self, member, filters=None, params=None):
440 14c94c48 Christos Stavrakakis
        images = self._list_images(user=self.user, filters=filters,
441 14c94c48 Christos Stavrakakis
                                   params=params)
442 14c94c48 Christos Stavrakakis
        is_shared = lambda img: not img["is_public"] and img["owner"] == member
443 14c94c48 Christos Stavrakakis
        return filter(is_shared, images)
444 14c94c48 Christos Stavrakakis
445 3347a30c Georgios D. Tsoukalas
    @commit_on_success
446 14c94c48 Christos Stavrakakis
    def list_public_images(self, filters=None, params=None):
447 14c94c48 Christos Stavrakakis
        images = self._list_images(user=None, filters=filters, params=params)
448 14c94c48 Christos Stavrakakis
        return filter(lambda img: img["is_public"], images)
449 1e28ba40 Christos Stavrakakis
450 1e28ba40 Christos Stavrakakis
451 cda71050 Christos Stavrakakis
class ImageBackendError(Exception):
452 cda71050 Christos Stavrakakis
    pass
453 1e28ba40 Christos Stavrakakis
454 1e28ba40 Christos Stavrakakis
455 cda71050 Christos Stavrakakis
class ImageNotFound(ImageBackendError):
456 cda71050 Christos Stavrakakis
    pass
457 f13aab5d Christos Stavrakakis
458 f13aab5d Christos Stavrakakis
459 cda71050 Christos Stavrakakis
class Forbidden(ImageBackendError):
460 cda71050 Christos Stavrakakis
    pass
461 14c94c48 Christos Stavrakakis
462 14c94c48 Christos Stavrakakis
463 14c94c48 Christos Stavrakakis
def image_to_dict(image_url, meta, permissions):
464 14c94c48 Christos Stavrakakis
    """Render an image to a dictionary"""
465 14c94c48 Christos Stavrakakis
    account, container, name = split_url(image_url)
466 14c94c48 Christos Stavrakakis
467 14c94c48 Christos Stavrakakis
    image = {}
468 14c94c48 Christos Stavrakakis
    if PLANKTON_PREFIX + 'name' not in meta:
469 125c682c Christos Stavrakakis
        logger.warning("Image without Plankton name!! url %s meta %s",
470 125c682c Christos Stavrakakis
                       image_url, meta)
471 125c682c Christos Stavrakakis
        image[PLANKTON_PREFIX + "name"] = ""
472 14c94c48 Christos Stavrakakis
473 14c94c48 Christos Stavrakakis
    image["id"] = meta["uuid"]
474 14c94c48 Christos Stavrakakis
    image["location"] = image_url
475 14c94c48 Christos Stavrakakis
    image["checksum"] = meta["hash"]
476 3a528c36 Christos Stavrakakis
    created = meta.get("created_at", meta["modified"])
477 3a528c36 Christos Stavrakakis
    image["created_at"] = format_timestamp(created)
478 14c94c48 Christos Stavrakakis
    deleted = meta.get("deleted", None)
479 14c94c48 Christos Stavrakakis
    image["deleted_at"] = format_timestamp(deleted) if deleted else ""
480 14c94c48 Christos Stavrakakis
    image["updated_at"] = format_timestamp(meta["modified"])
481 14c94c48 Christos Stavrakakis
    image["size"] = meta["bytes"]
482 14c94c48 Christos Stavrakakis
    image["store"] = "pithos"
483 14c94c48 Christos Stavrakakis
    image['owner'] = account
484 14c94c48 Christos Stavrakakis
485 14c94c48 Christos Stavrakakis
    # Permissions
486 14c94c48 Christos Stavrakakis
    image["is_public"] = "*" in permissions.get('read', [])
487 14c94c48 Christos Stavrakakis
488 14c94c48 Christos Stavrakakis
    for key, val in meta.items():
489 14c94c48 Christos Stavrakakis
        # Get plankton properties
490 14c94c48 Christos Stavrakakis
        if key.startswith(PLANKTON_PREFIX):
491 14c94c48 Christos Stavrakakis
            # Remove plankton prefix
492 14c94c48 Christos Stavrakakis
            key = key.replace(PLANKTON_PREFIX, "")
493 14c94c48 Christos Stavrakakis
            # Keep only those in plankton meta
494 14c94c48 Christos Stavrakakis
            if key in PLANKTON_META:
495 14c94c48 Christos Stavrakakis
                if key == "properties":
496 6f7e8ffc Georgios D. Tsoukalas
                    image[key] = json.loads(val)
497 3a528c36 Christos Stavrakakis
                elif key == "created_at":
498 3a528c36 Christos Stavrakakis
                    # created timestamp is return in 'created_at' field
499 3a528c36 Christos Stavrakakis
                    pass
500 3a528c36 Christos Stavrakakis
                else:
501 3a528c36 Christos Stavrakakis
                    image[key] = val
502 14c94c48 Christos Stavrakakis
503 14c94c48 Christos Stavrakakis
    return image
504 9393d4ee Kostas Papadimitriou
505 9393d4ee Kostas Papadimitriou
506 9393d4ee Kostas Papadimitriou
class JSONFileBackend(object):
507 9393d4ee Kostas Papadimitriou
    """
508 9393d4ee Kostas Papadimitriou
    A dummy image backend that loads available images from a file with json
509 9393d4ee Kostas Papadimitriou
    formatted content.
510 9393d4ee Kostas Papadimitriou

511 9393d4ee Kostas Papadimitriou
    usage:
512 9393d4ee Kostas Papadimitriou
        PLANKTON_BACKEND_MODULE = 'synnefo.plankton.backend.JSONFileBackend'
513 9393d4ee Kostas Papadimitriou
        PLANKTON_IMAGES_JSON_BACKEND_FILE = '/tmp/images.json'
514 9393d4ee Kostas Papadimitriou

515 9393d4ee Kostas Papadimitriou
        # loading images from an existing plankton service
516 9393d4ee Kostas Papadimitriou
        $ curl -H "X-Auth-Token: <MYTOKEN>" \
517 9393d4ee Kostas Papadimitriou
                https://cyclades.synnefo.org/plankton/images/detail | \
518 9393d4ee Kostas Papadimitriou
                python -m json.tool > /tmp/images.json
519 9393d4ee Kostas Papadimitriou
    """
520 9393d4ee Kostas Papadimitriou
    def __init__(self, userid):
521 9393d4ee Kostas Papadimitriou
        self.images_file = getattr(settings,
522 9393d4ee Kostas Papadimitriou
                                   'PLANKTON_IMAGES_JSON_BACKEND_FILE', '')
523 9393d4ee Kostas Papadimitriou
        if not os.path.exists(self.images_file):
524 9393d4ee Kostas Papadimitriou
            raise Exception("Invalid plankgon images json backend file: %s",
525 9393d4ee Kostas Papadimitriou
                            self.images_file)
526 9393d4ee Kostas Papadimitriou
        fp = file(self.images_file)
527 9393d4ee Kostas Papadimitriou
        self.images = json.load(fp)
528 9393d4ee Kostas Papadimitriou
        fp.close()
529 9393d4ee Kostas Papadimitriou
530 9393d4ee Kostas Papadimitriou
    def iter(self, *args, **kwargs):
531 9393d4ee Kostas Papadimitriou
        return self.images.__iter__()
532 9393d4ee Kostas Papadimitriou
533 9393d4ee Kostas Papadimitriou
    def list_images(self, *args, **kwargs):
534 9393d4ee Kostas Papadimitriou
        return self.images
535 9393d4ee Kostas Papadimitriou
536 9393d4ee Kostas Papadimitriou
    def get_image(self, image_uuid):
537 9393d4ee Kostas Papadimitriou
        try:
538 9393d4ee Kostas Papadimitriou
            return filter(lambda i: i['id'] == image_uuid, self.images)[0]
539 9393d4ee Kostas Papadimitriou
        except IndexError:
540 9393d4ee Kostas Papadimitriou
            raise Exception("Unknown image uuid: %s" % image_uuid)
541 9393d4ee Kostas Papadimitriou
542 9393d4ee Kostas Papadimitriou
    def close(self):
543 9393d4ee Kostas Papadimitriou
        pass
544 9393d4ee Kostas Papadimitriou
545 9393d4ee Kostas Papadimitriou
546 9393d4ee Kostas Papadimitriou
def get_backend():
547 9393d4ee Kostas Papadimitriou
    backend_module = getattr(settings, 'PLANKTON_BACKEND_MODULE', None)
548 9393d4ee Kostas Papadimitriou
    if not backend_module:
549 9393d4ee Kostas Papadimitriou
        # no setting set
550 9393d4ee Kostas Papadimitriou
        return ImageBackend
551 9393d4ee Kostas Papadimitriou
552 9393d4ee Kostas Papadimitriou
    parts = backend_module.split(".")
553 9393d4ee Kostas Papadimitriou
    module = ".".join(parts[:-1])
554 9393d4ee Kostas Papadimitriou
    cls = parts[-1]
555 9393d4ee Kostas Papadimitriou
    try:
556 9393d4ee Kostas Papadimitriou
        return getattr(importlib.import_module(module), cls)
557 9393d4ee Kostas Papadimitriou
    except (ImportError, AttributeError), e:
558 9393d4ee Kostas Papadimitriou
        raise ImportError("Cannot import plankton module: %s (%s)" %
559 9393d4ee Kostas Papadimitriou
                          (backend_module, e.message))