Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (20 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 e407f159 Ilias Tsitsimpis
        astakos_auth_url=settings.ASTAKOS_AUTH_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 69cdf66c Nanakos Chrysostomos
        if path is None and permissions != {}:
223 69cdf66c Nanakos Chrysostomos
            logger.warning("Image '%s' got permissions '%s' from 'None' path.",
224 69cdf66c Nanakos Chrysostomos
                           image_url, permissions)
225 69cdf66c Nanakos Chrysostomos
            raise Exception("Database Inconsistency Error:"
226 69cdf66c Nanakos Chrysostomos
                            " Image '%s' got permissions from 'None' path." %
227 69cdf66c Nanakos Chrysostomos
                            image_url)
228 1e28ba40 Christos Stavrakakis
229 cda71050 Christos Stavrakakis
        return permissions
230 1e28ba40 Christos Stavrakakis
231 cda71050 Christos Stavrakakis
    def _update_permissions(self, image_url, permissions):
232 cda71050 Christos Stavrakakis
        """Update object's permissions."""
233 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
234 cda71050 Christos Stavrakakis
        self.backend.update_object_permissions(self.user, account, container,
235 cda71050 Christos Stavrakakis
                                               name, permissions)
236 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', permissions: '%s'",
237 62d3ea53 Christos Stavrakakis
                     self.user, image_url, permissions)
238 1e28ba40 Christos Stavrakakis
239 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
240 3347a30c Georgios D. Tsoukalas
    @commit_on_success
241 cda71050 Christos Stavrakakis
    def unregister(self, image_uuid):
242 cda71050 Christos Stavrakakis
        """Unregister an image.
243 1e28ba40 Christos Stavrakakis

244 cda71050 Christos Stavrakakis
        Unregister an image, by removing all metadata from the Pithos
245 cda71050 Christos Stavrakakis
        file that exist in the PLANKTON_DOMAIN.
246 1e28ba40 Christos Stavrakakis

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

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

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

278 cda71050 Christos Stavrakakis
        Remove the specified user from the read permissions of the Pithos file.
279 cda71050 Christos Stavrakakis

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

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

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

316 cda71050 Christos Stavrakakis
        List the image members, by listing all users that have read permission
317 cda71050 Christos Stavrakakis
        to the corresponding Pithos file.
318 cda71050 Christos Stavrakakis

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

514 9393d4ee Kostas Papadimitriou
    usage:
515 9393d4ee Kostas Papadimitriou
        PLANKTON_BACKEND_MODULE = 'synnefo.plankton.backend.JSONFileBackend'
516 9393d4ee Kostas Papadimitriou
        PLANKTON_IMAGES_JSON_BACKEND_FILE = '/tmp/images.json'
517 9393d4ee Kostas Papadimitriou

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