Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (22.5 kB)

1 23808592 Christos Stavrakakis
# Copyright 2011-2014 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 cda71050 Christos Stavrakakis
import logging
56 9393d4ee Kostas Papadimitriou
import os
57 9393d4ee Kostas Papadimitriou
58 3a528c36 Christos Stavrakakis
from time import time, gmtime, strftime
59 cda71050 Christos Stavrakakis
from functools import wraps
60 cda71050 Christos Stavrakakis
from operator import itemgetter
61 23808592 Christos Stavrakakis
from collections import namedtuple
62 c34de90f Giorgos Verigakis
63 cda71050 Christos Stavrakakis
from django.conf import settings
64 9393d4ee Kostas Papadimitriou
from django.utils import importlib
65 23808592 Christos Stavrakakis
from pithos.backends.base import NotAllowedError, VersionNotExists, QuotaError
66 78fa9134 Christos Stavrakakis
from synnefo.util.text import uenc
67 23808592 Christos Stavrakakis
from copy import deepcopy
68 23808592 Christos Stavrakakis
from snf_django.lib.api import faults
69 78fa9134 Christos Stavrakakis
70 23808592 Christos Stavrakakis
Location = namedtuple("ObjectLocation", ["account", "container", "path"])
71 f4366b6c Stratos Psomadakis
72 469d0997 Georgios D. Tsoukalas
logger = logging.getLogger(__name__)
73 c34de90f Giorgos Verigakis
74 c34de90f Giorgos Verigakis
75 0a72907b Giorgos Verigakis
PLANKTON_DOMAIN = 'plankton'
76 bfd9f988 Giorgos Verigakis
PLANKTON_PREFIX = 'plankton:'
77 7bd1d3b5 Giorgos Verigakis
PROPERTY_PREFIX = 'property:'
78 7bd1d3b5 Giorgos Verigakis
79 d58ea30a Christos Stavrakakis
PLANKTON_META = ('container_format', 'disk_format', 'name',
80 3a528c36 Christos Stavrakakis
                 'status', 'created_at')
81 c34de90f Giorgos Verigakis
82 0efb43cd Christos Stavrakakis
MAX_META_KEY_LENGTH = 128 - len(PLANKTON_DOMAIN) - len(PROPERTY_PREFIX)
83 0efb43cd Christos Stavrakakis
MAX_META_VALUE_LENGTH = 256
84 0efb43cd Christos Stavrakakis
85 7784ab88 Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
86 7784ab88 Christos Stavrakakis
_pithos_backend_pool = \
87 b336e6fa Georgios D. Tsoukalas
    PithosBackendPool(
88 e3f006b0 Christos Stavrakakis
        settings.PITHOS_BACKEND_POOL_SIZE,
89 e407f159 Ilias Tsitsimpis
        astakos_auth_url=settings.ASTAKOS_AUTH_URL,
90 18c4414d Giorgos Korfiatis
        service_token=settings.CYCLADES_SERVICE_TOKEN,
91 b0c95903 Giorgos Korfiatis
        astakosclient_poolsize=settings.CYCLADES_ASTAKOSCLIENT_POOLSIZE,
92 b336e6fa Georgios D. Tsoukalas
        db_connection=settings.BACKEND_DB_CONNECTION,
93 b336e6fa Georgios D. Tsoukalas
        block_path=settings.BACKEND_BLOCK_PATH)
94 7784ab88 Christos Stavrakakis
95 7784ab88 Christos Stavrakakis
96 7784ab88 Christos Stavrakakis
def get_pithos_backend():
97 7784ab88 Christos Stavrakakis
    return _pithos_backend_pool.pool_get()
98 7784ab88 Christos Stavrakakis
99 7784ab88 Christos Stavrakakis
100 cda71050 Christos Stavrakakis
def format_timestamp(t):
101 cda71050 Christos Stavrakakis
    return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
102 cda71050 Christos Stavrakakis
103 cda71050 Christos Stavrakakis
104 23808592 Christos Stavrakakis
def handle_pithos_backend(func):
105 2db7d9df Christos Stavrakakis
    @wraps(func)
106 3347a30c Georgios D. Tsoukalas
    def wrapper(self, *args, **kwargs):
107 3347a30c Georgios D. Tsoukalas
        backend = self.backend
108 3347a30c Georgios D. Tsoukalas
        backend.pre_exec()
109 23808592 Christos Stavrakakis
        commit = False
110 3347a30c Georgios D. Tsoukalas
        try:
111 3347a30c Georgios D. Tsoukalas
            ret = func(self, *args, **kwargs)
112 23808592 Christos Stavrakakis
        except NotAllowedError:
113 23808592 Christos Stavrakakis
            raise faults.Forbidden
114 23808592 Christos Stavrakakis
        except (NameError, VersionNotExists):
115 23808592 Christos Stavrakakis
            raise faults.ItemNotFound
116 23808592 Christos Stavrakakis
        except (AssertionError, ValueError):
117 23808592 Christos Stavrakakis
            raise faults.BadRequest
118 23808592 Christos Stavrakakis
        except QuotaError:
119 23808592 Christos Stavrakakis
            raise faults.OverLimit
120 3347a30c Georgios D. Tsoukalas
        else:
121 23808592 Christos Stavrakakis
            commit = True
122 23808592 Christos Stavrakakis
        finally:
123 23808592 Christos Stavrakakis
            backend.post_exec(commit)
124 3347a30c Georgios D. Tsoukalas
        return ret
125 3347a30c Georgios D. Tsoukalas
    return wrapper
126 3347a30c Georgios D. Tsoukalas
127 3347a30c Georgios D. Tsoukalas
128 23808592 Christos Stavrakakis
class PlanktonBackend(object):
129 f5afd99b Giorgos Verigakis
    """A wrapper arround the pithos backend to simplify image handling."""
130 1e28ba40 Christos Stavrakakis
131 c34de90f Giorgos Verigakis
    def __init__(self, user):
132 c34de90f Giorgos Verigakis
        self.user = user
133 7784ab88 Christos Stavrakakis
        self.backend = get_pithos_backend()
134 7784ab88 Christos Stavrakakis
135 cda71050 Christos Stavrakakis
    def close(self):
136 cda71050 Christos Stavrakakis
        """Close PithosBackend(return to pool)"""
137 cda71050 Christos Stavrakakis
        self.backend.close()
138 cda71050 Christos Stavrakakis
139 23808592 Christos Stavrakakis
    def __enter__(self):
140 23808592 Christos Stavrakakis
        return self
141 23808592 Christos Stavrakakis
142 23808592 Christos Stavrakakis
    def __exit__(self, exc_type, exc_val, exc_tb):
143 23808592 Christos Stavrakakis
        self.close()
144 23808592 Christos Stavrakakis
        self.backend = None
145 23808592 Christos Stavrakakis
        return False
146 23808592 Christos Stavrakakis
147 23808592 Christos Stavrakakis
    @handle_pithos_backend
148 23808592 Christos Stavrakakis
    def get_image(self, uuid):
149 23808592 Christos Stavrakakis
        return self._get_image(uuid)
150 23808592 Christos Stavrakakis
151 23808592 Christos Stavrakakis
    def _get_image(self, uuid):
152 23808592 Christos Stavrakakis
        location, metadata = self._get_raw_metadata(uuid)
153 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
154 23808592 Christos Stavrakakis
        return image_to_dict(location, metadata, permissions)
155 23808592 Christos Stavrakakis
156 23808592 Christos Stavrakakis
    @handle_pithos_backend
157 23808592 Christos Stavrakakis
    def add_property(self, uuid, key, value):
158 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
159 23808592 Christos Stavrakakis
        properties = self._prefix_properties({key: value})
160 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, properties, replace=False)
161 23808592 Christos Stavrakakis
162 23808592 Christos Stavrakakis
    @handle_pithos_backend
163 23808592 Christos Stavrakakis
    def remove_property(self, uuid, key):
164 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
165 23808592 Christos Stavrakakis
        # Use empty string to delete a property
166 23808592 Christos Stavrakakis
        properties = self._prefix_properties({key: ""})
167 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, properties, replace=False)
168 23808592 Christos Stavrakakis
169 23808592 Christos Stavrakakis
    @handle_pithos_backend
170 23808592 Christos Stavrakakis
    def update_properties(self, uuid, properties):
171 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
172 23808592 Christos Stavrakakis
        properties = self._prefix_properties(properties)
173 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, properties, replace=False)
174 23808592 Christos Stavrakakis
175 23808592 Christos Stavrakakis
    @staticmethod
176 23808592 Christos Stavrakakis
    def _prefix_properties(properties):
177 23808592 Christos Stavrakakis
        """Add property prefix to properties."""
178 23808592 Christos Stavrakakis
        return dict([(PROPERTY_PREFIX + k, v) for k, v in properties.items()])
179 23808592 Christos Stavrakakis
180 23808592 Christos Stavrakakis
    @staticmethod
181 23808592 Christos Stavrakakis
    def _unprefix_properties(properties):
182 23808592 Christos Stavrakakis
        """Remove property prefix from properties."""
183 23808592 Christos Stavrakakis
        return dict([(k.replace(PROPERTY_PREFIX, "", 1), v)
184 23808592 Christos Stavrakakis
                     for k, v in properties.items()])
185 23808592 Christos Stavrakakis
186 23808592 Christos Stavrakakis
    @staticmethod
187 23808592 Christos Stavrakakis
    def _prefix_metadata(metadata):
188 23808592 Christos Stavrakakis
        """Add plankton prefix to metadata."""
189 23808592 Christos Stavrakakis
        return dict([(PLANKTON_PREFIX + k, v) for k, v in metadata.items()])
190 23808592 Christos Stavrakakis
191 23808592 Christos Stavrakakis
    @staticmethod
192 23808592 Christos Stavrakakis
    def _unprefix_metadata(metadata):
193 23808592 Christos Stavrakakis
        """Remove plankton prefix from metadata."""
194 23808592 Christos Stavrakakis
        return dict([(k.replace(PLANKTON_PREFIX, "", 1), v)
195 23808592 Christos Stavrakakis
                     for k, v in metadata.items()])
196 23808592 Christos Stavrakakis
197 23808592 Christos Stavrakakis
    @handle_pithos_backend
198 23808592 Christos Stavrakakis
    def update_metadata(self, uuid, metadata):
199 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
200 1e28ba40 Christos Stavrakakis
201 23808592 Christos Stavrakakis
        is_public = metadata.pop("is_public", None)
202 23808592 Christos Stavrakakis
        if is_public is not None:
203 23808592 Christos Stavrakakis
            self._set_public(uuid, location, public=is_public)
204 1e28ba40 Christos Stavrakakis
205 23808592 Christos Stavrakakis
        # Each property is stored as a separate prefixed metadata
206 23808592 Christos Stavrakakis
        meta = deepcopy(metadata)
207 23808592 Christos Stavrakakis
        properties = meta.pop("properties", {})
208 23808592 Christos Stavrakakis
        meta.update(self._prefix_properties(properties))
209 cda71050 Christos Stavrakakis
210 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, metadata=meta, replace=False)
211 23808592 Christos Stavrakakis
212 23808592 Christos Stavrakakis
        return self._get_image(uuid)
213 23808592 Christos Stavrakakis
214 23808592 Christos Stavrakakis
    def _update_metadata(self, uuid, location, metadata, replace=False):
215 23808592 Christos Stavrakakis
        _prefixed_metadata = self._prefix_metadata(metadata)
216 23808592 Christos Stavrakakis
        prefixed = {}
217 23808592 Christos Stavrakakis
        for k, v in _prefixed_metadata.items():
218 23808592 Christos Stavrakakis
            # Encode to UTF-8
219 23808592 Christos Stavrakakis
            k, v = uenc(k), uenc(v)
220 23808592 Christos Stavrakakis
            # Check the length of key/value
221 0efb43cd Christos Stavrakakis
            if len(k) > 128:
222 23808592 Christos Stavrakakis
                raise faults.BadRequest('Metadata keys should be less than %s'
223 23808592 Christos Stavrakakis
                                        ' characters' % MAX_META_KEY_LENGTH)
224 0efb43cd Christos Stavrakakis
            if len(v) > 256:
225 23808592 Christos Stavrakakis
                raise faults.BadRequest('Metadata values should be less than'
226 23808592 Christos Stavrakakis
                                        ' %scharacters.'
227 23808592 Christos Stavrakakis
                                        % MAX_META_VALUE_LENGTH)
228 23808592 Christos Stavrakakis
            prefixed[k] = v
229 0efb43cd Christos Stavrakakis
230 23808592 Christos Stavrakakis
        account, container, path = location
231 23808592 Christos Stavrakakis
        self.backend.update_object_meta(self.user, account, container, path,
232 cda71050 Christos Stavrakakis
                                        PLANKTON_DOMAIN, prefixed, replace)
233 23808592 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', metadata: '%s'", self.user,
234 23808592 Christos Stavrakakis
                     uuid, prefixed)
235 1e28ba40 Christos Stavrakakis
236 23808592 Christos Stavrakakis
    def _get_raw_metadata(self, uuid, version=None, check_image=True):
237 23808592 Christos Stavrakakis
        """Get info and metadata in Plankton doamin for the Pithos object.
238 1e28ba40 Christos Stavrakakis

239 23808592 Christos Stavrakakis
        Return the location and the metadata of the Pithos object.
240 23808592 Christos Stavrakakis
        If 'check_image' is set, check that the Pithos object is a registered
241 23808592 Christos Stavrakakis
        Plankton Image.
242 1e28ba40 Christos Stavrakakis

243 cda71050 Christos Stavrakakis
        """
244 23808592 Christos Stavrakakis
        # Convert uuid to location
245 23808592 Christos Stavrakakis
        account, container, path = self.backend.get_uuid(self.user, uuid)
246 23808592 Christos Stavrakakis
        try:
247 23808592 Christos Stavrakakis
            meta = self.backend.get_object_meta(self.user, account, container,
248 23808592 Christos Stavrakakis
                                                path, PLANKTON_DOMAIN, version)
249 23808592 Christos Stavrakakis
            meta["deleted"] = False
250 23808592 Christos Stavrakakis
        except NameError:
251 23808592 Christos Stavrakakis
            if version is not None:
252 23808592 Christos Stavrakakis
                raise
253 23808592 Christos Stavrakakis
            versions = self.backend.list_versions(self.user, account,
254 23808592 Christos Stavrakakis
                                                  container, path)
255 23808592 Christos Stavrakakis
            assert(versions), ("Object without versions: %s/%s/%s" %
256 23808592 Christos Stavrakakis
                               (account, container, path))
257 23808592 Christos Stavrakakis
            # Object was deleted, use the latest version
258 23808592 Christos Stavrakakis
            version, timestamp = versions[-1]
259 23808592 Christos Stavrakakis
            meta = self.backend.get_object_meta(self.user, account, container,
260 23808592 Christos Stavrakakis
                                                path, PLANKTON_DOMAIN, version)
261 23808592 Christos Stavrakakis
            meta["deleted"] = True
262 23808592 Christos Stavrakakis
263 23808592 Christos Stavrakakis
        if check_image and PLANKTON_PREFIX + "name" not in meta:
264 23808592 Christos Stavrakakis
            # Check that object is an image by checking if it has an Image name
265 23808592 Christos Stavrakakis
            # in Plankton metadata
266 23808592 Christos Stavrakakis
            raise faults.ItemNotFound("Image '%s' does not exist." % uuid)
267 23808592 Christos Stavrakakis
268 23808592 Christos Stavrakakis
        return Location(account, container, path), meta
269 23808592 Christos Stavrakakis
270 23808592 Christos Stavrakakis
    # Users and Permissions
271 23808592 Christos Stavrakakis
    @handle_pithos_backend
272 23808592 Christos Stavrakakis
    def add_user(self, uuid, user):
273 23808592 Christos Stavrakakis
        assert(isinstance(user, basestring))
274 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
275 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
276 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
277 23808592 Christos Stavrakakis
        if not user in read:
278 23808592 Christos Stavrakakis
            read.add(user)
279 23808592 Christos Stavrakakis
            permissions["read"] = list(read)
280 23808592 Christos Stavrakakis
            self._update_permissions(uuid, location, permissions)
281 1e28ba40 Christos Stavrakakis
282 23808592 Christos Stavrakakis
    @handle_pithos_backend
283 23808592 Christos Stavrakakis
    def remove_user(self, uuid, user):
284 23808592 Christos Stavrakakis
        assert(isinstance(user, basestring))
285 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
286 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
287 23808592 Christos Stavrakakis
        read = set(permissions.get("read", []))
288 23808592 Christos Stavrakakis
        if user in read:
289 23808592 Christos Stavrakakis
            read.remove(user)
290 23808592 Christos Stavrakakis
            permissions["read"] = list(read)
291 23808592 Christos Stavrakakis
            self._update_permissions(uuid, location, permissions)
292 1e28ba40 Christos Stavrakakis
293 23808592 Christos Stavrakakis
    @handle_pithos_backend
294 23808592 Christos Stavrakakis
    def replace_users(self, uuid, users):
295 23808592 Christos Stavrakakis
        assert(isinstance(users, list))
296 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
297 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
298 23808592 Christos Stavrakakis
        read = set(permissions.get("read", []))
299 23808592 Christos Stavrakakis
        if "*" in read:  # Retain public permissions
300 23808592 Christos Stavrakakis
            users.append("*")
301 23808592 Christos Stavrakakis
        permissions["read"] = list(users)
302 23808592 Christos Stavrakakis
        self._update_permissions(uuid, location, permissions)
303 23808592 Christos Stavrakakis
304 23808592 Christos Stavrakakis
    @handle_pithos_backend
305 23808592 Christos Stavrakakis
    def list_users(self, uuid):
306 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
307 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
308 23808592 Christos Stavrakakis
        return [user for user in permissions.get('read', []) if user != '*']
309 cda71050 Christos Stavrakakis
310 23808592 Christos Stavrakakis
    def _set_public(self, uuid, location, public):
311 23808592 Christos Stavrakakis
        permissions = self._get_raw_permissions(uuid, location)
312 23808592 Christos Stavrakakis
        assert(isinstance(public, bool))
313 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
314 23808592 Christos Stavrakakis
        if public and "*" not in read:
315 23808592 Christos Stavrakakis
            read.add("*")
316 23808592 Christos Stavrakakis
        elif not public and "*" in read:
317 23808592 Christos Stavrakakis
            read.discard("*")
318 cda71050 Christos Stavrakakis
        permissions["read"] = list(read)
319 23808592 Christos Stavrakakis
        self._update_permissions(uuid, location, permissions)
320 23808592 Christos Stavrakakis
        return permissions
321 1e28ba40 Christos Stavrakakis
322 23808592 Christos Stavrakakis
    def _get_raw_permissions(self, uuid, location):
323 23808592 Christos Stavrakakis
        account, container, path = location
324 23808592 Christos Stavrakakis
        _a, path, permissions = \
325 23808592 Christos Stavrakakis
            self.backend.get_object_permissions(self.user, account, container,
326 23808592 Christos Stavrakakis
                                                path)
327 1e28ba40 Christos Stavrakakis
328 23808592 Christos Stavrakakis
        if path is None and permissions != {}:
329 23808592 Christos Stavrakakis
            raise Exception("Database Inconsistency Error:"
330 23808592 Christos Stavrakakis
                            " Image '%s' got permissions from 'None' path." %
331 23808592 Christos Stavrakakis
                            uuid)
332 d58ea30a Christos Stavrakakis
333 23808592 Christos Stavrakakis
        return permissions
334 cda71050 Christos Stavrakakis
335 23808592 Christos Stavrakakis
    def _update_permissions(self, uuid, location, permissions):
336 23808592 Christos Stavrakakis
        account, container, path = location
337 23808592 Christos Stavrakakis
        self.backend.update_object_permissions(self.user, account, container,
338 23808592 Christos Stavrakakis
                                               path, permissions)
339 23808592 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s' permissions: '%s'",
340 23808592 Christos Stavrakakis
                     self.user, uuid, permissions)
341 1e28ba40 Christos Stavrakakis
342 23808592 Christos Stavrakakis
    @handle_pithos_backend
343 cda71050 Christos Stavrakakis
    def register(self, name, image_url, metadata):
344 cda71050 Christos Stavrakakis
        # Validate that metadata are allowed
345 cda71050 Christos Stavrakakis
        if "id" in metadata:
346 23808592 Christos Stavrakakis
            raise faults.BadRequest("Passing an ID is not supported")
347 cda71050 Christos Stavrakakis
        store = metadata.pop("store", "pithos")
348 cda71050 Christos Stavrakakis
        if store != "pithos":
349 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid store '%s'. Only 'pithos' store"
350 23808592 Christos Stavrakakis
                                    " is supported" % store)
351 cda71050 Christos Stavrakakis
        disk_format = metadata.setdefault("disk_format",
352 cda71050 Christos Stavrakakis
                                          settings.DEFAULT_DISK_FORMAT)
353 cda71050 Christos Stavrakakis
        if disk_format not in settings.ALLOWED_DISK_FORMATS:
354 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid disk format '%s'" % disk_format)
355 cda71050 Christos Stavrakakis
        container_format =\
356 cda71050 Christos Stavrakakis
            metadata.setdefault("container_format",
357 cda71050 Christos Stavrakakis
                                settings.DEFAULT_CONTAINER_FORMAT)
358 cda71050 Christos Stavrakakis
        if container_format not in settings.ALLOWED_CONTAINER_FORMATS:
359 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid container format '%s'" %
360 23808592 Christos Stavrakakis
                                    container_format)
361 cda71050 Christos Stavrakakis
362 23808592 Christos Stavrakakis
        account, container, path = split_url(image_url)
363 23808592 Christos Stavrakakis
        location = Location(account, container, path)
364 23808592 Christos Stavrakakis
        meta = self.backend.get_object_meta(self.user, account, container,
365 23808592 Christos Stavrakakis
                                            path, PLANKTON_DOMAIN, None)
366 23808592 Christos Stavrakakis
        uuid = meta["uuid"]
367 cda71050 Christos Stavrakakis
368 23808592 Christos Stavrakakis
        # Validate that 'size' and 'checksum'
369 23808592 Christos Stavrakakis
        size = metadata.pop('size', int(meta['bytes']))
370 23808592 Christos Stavrakakis
        if not isinstance(size, int) or int(size) != int(meta["bytes"]):
371 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid 'size' field")
372 cda71050 Christos Stavrakakis
373 cda71050 Christos Stavrakakis
        checksum = metadata.pop('checksum', meta['hash'])
374 23808592 Christos Stavrakakis
        if not isinstance(checksum, basestring) or checksum != meta['hash']:
375 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid checksum field")
376 23808592 Christos Stavrakakis
377 23808592 Christos Stavrakakis
        users = [self.user]
378 23808592 Christos Stavrakakis
        public = metadata.pop("is_public", False)
379 23808592 Christos Stavrakakis
        if not isinstance(public, bool):
380 23808592 Christos Stavrakakis
            raise faults.BadRequest("Invalid value for 'is_public' metadata")
381 23808592 Christos Stavrakakis
        if public:
382 23808592 Christos Stavrakakis
            users.append("*")
383 23808592 Christos Stavrakakis
        permissions = {'read': users}
384 23808592 Christos Stavrakakis
        self._update_permissions(uuid, location, permissions)
385 23808592 Christos Stavrakakis
386 23808592 Christos Stavrakakis
        # Each property is stored as a separate prefixed metadata
387 23808592 Christos Stavrakakis
        meta = deepcopy(metadata)
388 23808592 Christos Stavrakakis
        properties = meta.pop("properties", {})
389 23808592 Christos Stavrakakis
        meta.update(self._prefix_properties(properties))
390 23808592 Christos Stavrakakis
        # Add extra metadata
391 23808592 Christos Stavrakakis
        meta["name"] = name
392 23808592 Christos Stavrakakis
        meta["status"] = "AVAILABLE"
393 23808592 Christos Stavrakakis
        meta['created_at'] = str(time())
394 23808592 Christos Stavrakakis
        #meta["is_snapshot"] = False
395 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, metadata=meta, replace=False)
396 23808592 Christos Stavrakakis
397 23808592 Christos Stavrakakis
        logger.debug("User '%s' registered image '%s'('%s')", self.user,
398 23808592 Christos Stavrakakis
                     uuid, location)
399 23808592 Christos Stavrakakis
        return self._get_image(uuid)
400 23808592 Christos Stavrakakis
401 23808592 Christos Stavrakakis
    @handle_pithos_backend
402 23808592 Christos Stavrakakis
    def unregister(self, uuid):
403 23808592 Christos Stavrakakis
        """Unregister an Image.
404 23808592 Christos Stavrakakis

405 23808592 Christos Stavrakakis
        Unregister an Image by removing all the metadata in the Plankton
406 23808592 Christos Stavrakakis
        domain. The Pithos file is not deleted.
407 cda71050 Christos Stavrakakis

408 23808592 Christos Stavrakakis
        """
409 23808592 Christos Stavrakakis
        location, _ = self._get_raw_metadata(uuid)
410 23808592 Christos Stavrakakis
        self._update_metadata(uuid, location, metadata={}, replace=True)
411 23808592 Christos Stavrakakis
        logger.debug("User '%s' unregistered image '%s'", self.user, uuid)
412 cda71050 Christos Stavrakakis
413 23808592 Christos Stavrakakis
    # List functions
414 14c94c48 Christos Stavrakakis
    def _list_images(self, user=None, filters=None, params=None):
415 d19e8f77 Giorgos Verigakis
        filters = filters or {}
416 1e28ba40 Christos Stavrakakis
417 14c94c48 Christos Stavrakakis
        # TODO: Use filters
418 14c94c48 Christos Stavrakakis
        # # Fix keys
419 14c94c48 Christos Stavrakakis
        # keys = [PLANKTON_PREFIX + 'name']
420 14c94c48 Christos Stavrakakis
        # size_range = (None, None)
421 14c94c48 Christos Stavrakakis
        # for key, val in filters.items():
422 14c94c48 Christos Stavrakakis
        #     if key == 'size_min':
423 14c94c48 Christos Stavrakakis
        #         size_range = (val, size_range[1])
424 14c94c48 Christos Stavrakakis
        #     elif key == 'size_max':
425 14c94c48 Christos Stavrakakis
        #         size_range = (size_range[0], val)
426 14c94c48 Christos Stavrakakis
        #     else:
427 14c94c48 Christos Stavrakakis
        #         keys.append('%s = %s' % (PLANKTON_PREFIX + key, val))
428 14c94c48 Christos Stavrakakis
        _images = self.backend.get_domain_objects(domain=PLANKTON_DOMAIN,
429 14c94c48 Christos Stavrakakis
                                                  user=user)
430 14c94c48 Christos Stavrakakis
431 14c94c48 Christos Stavrakakis
        images = []
432 23808592 Christos Stavrakakis
        for (location, metadata, permissions) in _images:
433 23808592 Christos Stavrakakis
            location = Location(*location.split("/", 2))
434 23808592 Christos Stavrakakis
            images.append(image_to_dict(location, metadata, permissions))
435 14c94c48 Christos Stavrakakis
436 14c94c48 Christos Stavrakakis
        if params is None:
437 14c94c48 Christos Stavrakakis
            params = {}
438 23808592 Christos Stavrakakis
439 aed77afe Christos Stavrakakis
        key = itemgetter(params.get('sort_key', 'created_at'))
440 aed77afe Christos Stavrakakis
        reverse = params.get('sort_dir', 'desc') == 'desc'
441 aed77afe Christos Stavrakakis
        images.sort(key=key, reverse=reverse)
442 aed77afe Christos Stavrakakis
        return images
443 1e28ba40 Christos Stavrakakis
444 23808592 Christos Stavrakakis
    @handle_pithos_backend
445 14c94c48 Christos Stavrakakis
    def list_images(self, filters=None, params=None):
446 14c94c48 Christos Stavrakakis
        return self._list_images(user=self.user, filters=filters,
447 14c94c48 Christos Stavrakakis
                                 params=params)
448 14c94c48 Christos Stavrakakis
449 23808592 Christos Stavrakakis
    @handle_pithos_backend
450 14c94c48 Christos Stavrakakis
    def list_shared_images(self, member, filters=None, params=None):
451 14c94c48 Christos Stavrakakis
        images = self._list_images(user=self.user, filters=filters,
452 14c94c48 Christos Stavrakakis
                                   params=params)
453 14c94c48 Christos Stavrakakis
        is_shared = lambda img: not img["is_public"] and img["owner"] == member
454 14c94c48 Christos Stavrakakis
        return filter(is_shared, images)
455 14c94c48 Christos Stavrakakis
456 23808592 Christos Stavrakakis
    @handle_pithos_backend
457 14c94c48 Christos Stavrakakis
    def list_public_images(self, filters=None, params=None):
458 14c94c48 Christos Stavrakakis
        images = self._list_images(user=None, filters=filters, params=params)
459 14c94c48 Christos Stavrakakis
        return filter(lambda img: img["is_public"], images)
460 1e28ba40 Christos Stavrakakis
461 23808592 Christos Stavrakakis
    # # Snapshots
462 23808592 Christos Stavrakakis
    # def list_snapshots(self, user=None):
463 23808592 Christos Stavrakakis
    #     _snapshots = self.list_images()
464 23808592 Christos Stavrakakis
    #     return [s for s in _snapshots if s["is_snapshot"]]
465 23808592 Christos Stavrakakis
466 23808592 Christos Stavrakakis
    # @handle_pithos_backend
467 23808592 Christos Stavrakakis
    # def get_snapshot(self, user, snapshot_uuid):
468 23808592 Christos Stavrakakis
    #     snap = self._get_image(snapshot_uuid)
469 23808592 Christos Stavrakakis
    #     if snap.get("is_snapshot", False) is False:
470 23808592 Christos Stavrakakis
    #         raise faults.ItemNotFound("Snapshots '%s' does not exist" %
471 23808592 Christos Stavrakakis
    #                                   snapshot_uuid)
472 23808592 Christos Stavrakakis
    #     return snap
473 23808592 Christos Stavrakakis
474 23808592 Christos Stavrakakis
    # @handle_pithos_backend
475 23808592 Christos Stavrakakis
    # def delete_snapshot(self, snapshot_uuid):
476 23808592 Christos Stavrakakis
    #     self.backend.delete_object_for_uuid(self.user, snapshot_uuid)
477 23808592 Christos Stavrakakis
478 23808592 Christos Stavrakakis
    # @handle_pithos_backend
479 23808592 Christos Stavrakakis
    # def update_status(self, image_uuid, status):
480 23808592 Christos Stavrakakis
    #     """Update status of snapshot"""
481 23808592 Christos Stavrakakis
    #     location, _ = self._get_raw_metadata(image_uuid)
482 23808592 Christos Stavrakakis
    #     properties = {"status": status.upper()}
483 23808592 Christos Stavrakakis
    #     self._update_metadata(image_uuid, location, properties,
484 23808592 Christos Stavrakakis
    #     replace=False)
485 23808592 Christos Stavrakakis
    #     return self._get_image(image_uuid)
486 1e28ba40 Christos Stavrakakis
487 1e28ba40 Christos Stavrakakis
488 23808592 Christos Stavrakakis
def create_url(account, container, name):
489 23808592 Christos Stavrakakis
    """Create a Pithos URL from the object info"""
490 23808592 Christos Stavrakakis
    assert "/" not in account, "Invalid account"
491 23808592 Christos Stavrakakis
    assert "/" not in container, "Invalid container"
492 23808592 Christos Stavrakakis
    return "pithos://%s/%s/%s" % (account, container, name)
493 0efb43cd Christos Stavrakakis
494 0efb43cd Christos Stavrakakis
495 23808592 Christos Stavrakakis
def split_url(url):
496 23808592 Christos Stavrakakis
    """Get object info from the Pithos URL"""
497 23808592 Christos Stavrakakis
    assert(isinstance(url, basestring))
498 23808592 Christos Stavrakakis
    t = url.split('/', 4)
499 23808592 Christos Stavrakakis
    assert t[0] == "pithos:", "Invalid url"
500 23808592 Christos Stavrakakis
    assert len(t) == 5, "Invalid url"
501 23808592 Christos Stavrakakis
    return t[2:5]
502 78fa9134 Christos Stavrakakis
503 78fa9134 Christos Stavrakakis
504 23808592 Christos Stavrakakis
def image_to_dict(location, metadata, permissions):
505 14c94c48 Christos Stavrakakis
    """Render an image to a dictionary"""
506 23808592 Christos Stavrakakis
    account, container, name = location
507 14c94c48 Christos Stavrakakis
508 14c94c48 Christos Stavrakakis
    image = {}
509 23808592 Christos Stavrakakis
    image["id"] = metadata["uuid"]
510 23808592 Christos Stavrakakis
    image["mapfile"] = metadata["hash"]
511 23808592 Christos Stavrakakis
    image["checksum"] = metadata["hash"]
512 23808592 Christos Stavrakakis
    image["location"] = create_url(account, container, name)
513 23808592 Christos Stavrakakis
    image["size"] = metadata["bytes"]
514 14c94c48 Christos Stavrakakis
    image['owner'] = account
515 23808592 Christos Stavrakakis
    image["store"] = u"pithos"
516 23808592 Christos Stavrakakis
    #image["is_snapshot"] = metadata.pop(PLANKTON_PREFIX + "is_snapshot",
517 23808592 Christos Stavrakakis
    #False)
518 14c94c48 Christos Stavrakakis
    # Permissions
519 23808592 Christos Stavrakakis
    users = list(permissions.get("read", []))
520 23808592 Christos Stavrakakis
    image["is_public"] = "*" in users
521 23808592 Christos Stavrakakis
    image["users"] = [u for u in users if u != "*"]
522 23808592 Christos Stavrakakis
    # Timestamps
523 23808592 Christos Stavrakakis
    updated_at = metadata["version_timestamp"]
524 23808592 Christos Stavrakakis
    created_at = metadata.get("created_at", updated_at)
525 23808592 Christos Stavrakakis
    image["created_at"] = format_timestamp(created_at)
526 23808592 Christos Stavrakakis
    image["updated_at"] = format_timestamp(updated_at)
527 23808592 Christos Stavrakakis
    if metadata.get("deleted", False):
528 23808592 Christos Stavrakakis
        image["deleted_at"] = image["updated_at"]
529 23808592 Christos Stavrakakis
    else:
530 23808592 Christos Stavrakakis
        image["deleted_at"] = ""
531 14c94c48 Christos Stavrakakis
532 d58ea30a Christos Stavrakakis
    properties = {}
533 23808592 Christos Stavrakakis
    for key, val in metadata.items():
534 14c94c48 Christos Stavrakakis
        # Get plankton properties
535 14c94c48 Christos Stavrakakis
        if key.startswith(PLANKTON_PREFIX):
536 14c94c48 Christos Stavrakakis
            # Remove plankton prefix
537 14c94c48 Christos Stavrakakis
            key = key.replace(PLANKTON_PREFIX, "")
538 23808592 Christos Stavrakakis
            # Keep only those in plankton metadata
539 14c94c48 Christos Stavrakakis
            if key in PLANKTON_META:
540 d58ea30a Christos Stavrakakis
                if key != "created_at":
541 3a528c36 Christos Stavrakakis
                    # created timestamp is return in 'created_at' field
542 3a528c36 Christos Stavrakakis
                    image[key] = val
543 d58ea30a Christos Stavrakakis
            elif key.startswith(PROPERTY_PREFIX):
544 d58ea30a Christos Stavrakakis
                key = key.replace(PROPERTY_PREFIX, "")
545 d58ea30a Christos Stavrakakis
                properties[key] = val
546 d58ea30a Christos Stavrakakis
    image["properties"] = properties
547 14c94c48 Christos Stavrakakis
548 14c94c48 Christos Stavrakakis
    return image
549 9393d4ee Kostas Papadimitriou
550 9393d4ee Kostas Papadimitriou
551 9393d4ee Kostas Papadimitriou
class JSONFileBackend(object):
552 9393d4ee Kostas Papadimitriou
    """
553 9393d4ee Kostas Papadimitriou
    A dummy image backend that loads available images from a file with json
554 9393d4ee Kostas Papadimitriou
    formatted content.
555 9393d4ee Kostas Papadimitriou

556 9393d4ee Kostas Papadimitriou
    usage:
557 9393d4ee Kostas Papadimitriou
        PLANKTON_BACKEND_MODULE = 'synnefo.plankton.backend.JSONFileBackend'
558 9393d4ee Kostas Papadimitriou
        PLANKTON_IMAGES_JSON_BACKEND_FILE = '/tmp/images.json'
559 9393d4ee Kostas Papadimitriou

560 9393d4ee Kostas Papadimitriou
        # loading images from an existing plankton service
561 9393d4ee Kostas Papadimitriou
        $ curl -H "X-Auth-Token: <MYTOKEN>" \
562 9393d4ee Kostas Papadimitriou
                https://cyclades.synnefo.org/plankton/images/detail | \
563 9393d4ee Kostas Papadimitriou
                python -m json.tool > /tmp/images.json
564 9393d4ee Kostas Papadimitriou
    """
565 9393d4ee Kostas Papadimitriou
    def __init__(self, userid):
566 9393d4ee Kostas Papadimitriou
        self.images_file = getattr(settings,
567 9393d4ee Kostas Papadimitriou
                                   'PLANKTON_IMAGES_JSON_BACKEND_FILE', '')
568 9393d4ee Kostas Papadimitriou
        if not os.path.exists(self.images_file):
569 9393d4ee Kostas Papadimitriou
            raise Exception("Invalid plankgon images json backend file: %s",
570 9393d4ee Kostas Papadimitriou
                            self.images_file)
571 9393d4ee Kostas Papadimitriou
        fp = file(self.images_file)
572 9393d4ee Kostas Papadimitriou
        self.images = json.load(fp)
573 9393d4ee Kostas Papadimitriou
        fp.close()
574 9393d4ee Kostas Papadimitriou
575 9393d4ee Kostas Papadimitriou
    def iter(self, *args, **kwargs):
576 9393d4ee Kostas Papadimitriou
        return self.images.__iter__()
577 9393d4ee Kostas Papadimitriou
578 9393d4ee Kostas Papadimitriou
    def list_images(self, *args, **kwargs):
579 9393d4ee Kostas Papadimitriou
        return self.images
580 9393d4ee Kostas Papadimitriou
581 9393d4ee Kostas Papadimitriou
    def get_image(self, image_uuid):
582 9393d4ee Kostas Papadimitriou
        try:
583 9393d4ee Kostas Papadimitriou
            return filter(lambda i: i['id'] == image_uuid, self.images)[0]
584 9393d4ee Kostas Papadimitriou
        except IndexError:
585 9393d4ee Kostas Papadimitriou
            raise Exception("Unknown image uuid: %s" % image_uuid)
586 9393d4ee Kostas Papadimitriou
587 9393d4ee Kostas Papadimitriou
    def close(self):
588 9393d4ee Kostas Papadimitriou
        pass
589 9393d4ee Kostas Papadimitriou
590 9393d4ee Kostas Papadimitriou
591 9393d4ee Kostas Papadimitriou
def get_backend():
592 9393d4ee Kostas Papadimitriou
    backend_module = getattr(settings, 'PLANKTON_BACKEND_MODULE', None)
593 9393d4ee Kostas Papadimitriou
    if not backend_module:
594 9393d4ee Kostas Papadimitriou
        # no setting set
595 23808592 Christos Stavrakakis
        return PlanktonBackend
596 9393d4ee Kostas Papadimitriou
597 9393d4ee Kostas Papadimitriou
    parts = backend_module.split(".")
598 9393d4ee Kostas Papadimitriou
    module = ".".join(parts[:-1])
599 9393d4ee Kostas Papadimitriou
    cls = parts[-1]
600 9393d4ee Kostas Papadimitriou
    try:
601 9393d4ee Kostas Papadimitriou
        return getattr(importlib.import_module(module), cls)
602 9393d4ee Kostas Papadimitriou
    except (ImportError, AttributeError), e:
603 9393d4ee Kostas Papadimitriou
        raise ImportError("Cannot import plankton module: %s (%s)" %
604 9393d4ee Kostas Papadimitriou
                          (backend_module, e.message))