Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.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 2db7d9df Christos Stavrakakis
from time import gmtime, strftime
58 cda71050 Christos Stavrakakis
from functools import wraps
59 cda71050 Christos Stavrakakis
from operator import itemgetter
60 c34de90f Giorgos Verigakis
61 cda71050 Christos Stavrakakis
from django.conf import settings
62 cda71050 Christos Stavrakakis
from pithos.backends.base import NotAllowedError, VersionNotExists
63 f4366b6c Stratos Psomadakis
64 469d0997 Georgios D. Tsoukalas
logger = logging.getLogger(__name__)
65 c34de90f Giorgos Verigakis
66 c34de90f Giorgos Verigakis
67 0a72907b Giorgos Verigakis
PLANKTON_DOMAIN = 'plankton'
68 bfd9f988 Giorgos Verigakis
PLANKTON_PREFIX = 'plankton:'
69 7bd1d3b5 Giorgos Verigakis
PROPERTY_PREFIX = 'property:'
70 7bd1d3b5 Giorgos Verigakis
71 bfd9f988 Giorgos Verigakis
PLANKTON_META = ('container_format', 'disk_format', 'name', 'properties',
72 bfd9f988 Giorgos Verigakis
                 'status')
73 c34de90f Giorgos Verigakis
74 7784ab88 Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
75 7784ab88 Christos Stavrakakis
POOL_SIZE = 8
76 7784ab88 Christos Stavrakakis
_pithos_backend_pool = \
77 b336e6fa Georgios D. Tsoukalas
    PithosBackendPool(
78 b336e6fa Georgios D. Tsoukalas
        POOL_SIZE,
79 e3ff6830 Georgios D. Tsoukalas
        astakos_url=settings.ASTAKOS_BASE_URL,
80 16f2673e Sofia Papagiannaki
        service_token=settings.CYCLADES_ASTAKOS_SERVICE_TOKEN,
81 28c41829 Christos Stavrakakis
        astakosclient_poolsize=settings.ASTAKOS_POOLSIZE,
82 b336e6fa Georgios D. Tsoukalas
        db_connection=settings.BACKEND_DB_CONNECTION,
83 b336e6fa Georgios D. Tsoukalas
        block_path=settings.BACKEND_BLOCK_PATH)
84 7784ab88 Christos Stavrakakis
85 7784ab88 Christos Stavrakakis
86 7784ab88 Christos Stavrakakis
def get_pithos_backend():
87 7784ab88 Christos Stavrakakis
    return _pithos_backend_pool.pool_get()
88 7784ab88 Christos Stavrakakis
89 7784ab88 Christos Stavrakakis
90 cda71050 Christos Stavrakakis
def create_url(account, container, name):
91 cda71050 Christos Stavrakakis
    assert "/" not in account, "Invalid account"
92 cda71050 Christos Stavrakakis
    assert "/" not in container, "Invalid container"
93 cda71050 Christos Stavrakakis
    return "pithos://%s/%s/%s" % (account, container, name)
94 cda71050 Christos Stavrakakis
95 cda71050 Christos Stavrakakis
96 cda71050 Christos Stavrakakis
def split_url(url):
97 cda71050 Christos Stavrakakis
    """Returns (accout, container, object) from a url string"""
98 cda71050 Christos Stavrakakis
    t = url.split('/', 4)
99 59573532 Christos Stavrakakis
    assert t[0] == "pithos:", "Invalid url"
100 cda71050 Christos Stavrakakis
    assert len(t) == 5, "Invalid url"
101 cda71050 Christos Stavrakakis
    return t[2:5]
102 cda71050 Christos Stavrakakis
103 cda71050 Christos Stavrakakis
104 cda71050 Christos Stavrakakis
def format_timestamp(t):
105 cda71050 Christos Stavrakakis
    return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
106 cda71050 Christos Stavrakakis
107 cda71050 Christos Stavrakakis
108 2db7d9df Christos Stavrakakis
def handle_backend_exceptions(func):
109 2db7d9df Christos Stavrakakis
    @wraps(func)
110 2db7d9df Christos Stavrakakis
    def wrapper(*args, **kwargs):
111 2db7d9df Christos Stavrakakis
        try:
112 2db7d9df Christos Stavrakakis
            return func(*args, **kwargs)
113 f6ff4b40 Christos Stavrakakis
        except NotAllowedError:
114 cda71050 Christos Stavrakakis
            raise Forbidden
115 cda71050 Christos Stavrakakis
        except NameError:
116 cda71050 Christos Stavrakakis
            raise ImageNotFound
117 cda71050 Christos Stavrakakis
        except VersionNotExists:
118 cda71050 Christos Stavrakakis
            raise ImageNotFound
119 2db7d9df Christos Stavrakakis
    return wrapper
120 2db7d9df Christos Stavrakakis
121 2db7d9df Christos Stavrakakis
122 ffab341c Christos Stavrakakis
class ImageBackend(object):
123 f5afd99b Giorgos Verigakis
    """A wrapper arround the pithos backend to simplify image handling."""
124 1e28ba40 Christos Stavrakakis
125 c34de90f Giorgos Verigakis
    def __init__(self, user):
126 c34de90f Giorgos Verigakis
        self.user = user
127 7784ab88 Christos Stavrakakis
128 c23d211a Giorgos Verigakis
        original_filters = warnings.filters
129 c23d211a Giorgos Verigakis
        warnings.simplefilter('ignore')         # Suppress SQLAlchemy warnings
130 7784ab88 Christos Stavrakakis
        self.backend = get_pithos_backend()
131 c23d211a Giorgos Verigakis
        warnings.filters = original_filters     # Restore warnings
132 7784ab88 Christos Stavrakakis
133 cda71050 Christos Stavrakakis
    def close(self):
134 cda71050 Christos Stavrakakis
        """Close PithosBackend(return to pool)"""
135 cda71050 Christos Stavrakakis
        self.backend.close()
136 cda71050 Christos Stavrakakis
137 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
138 cda71050 Christos Stavrakakis
    def get_image(self, image_uuid):
139 cda71050 Christos Stavrakakis
        """Retrieve information about an image."""
140 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
141 cda71050 Christos Stavrakakis
        return self._get_image(image_url)
142 1e28ba40 Christos Stavrakakis
143 cda71050 Christos Stavrakakis
    def _get_image_url(self, image_uuid):
144 cda71050 Christos Stavrakakis
        """Get the Pithos url that corresponds to an image UUID."""
145 cda71050 Christos Stavrakakis
        account, container, name = self.backend.get_uuid(self.user, image_uuid)
146 cda71050 Christos Stavrakakis
        return create_url(account, container, name)
147 1e28ba40 Christos Stavrakakis
148 cda71050 Christos Stavrakakis
    def _get_image(self, image_url):
149 cda71050 Christos Stavrakakis
        """Get information about an Image.
150 cda71050 Christos Stavrakakis

151 cda71050 Christos Stavrakakis
        Get all available information about an Image.
152 cda71050 Christos Stavrakakis
        """
153 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
154 cda71050 Christos Stavrakakis
        versions = self.backend.list_versions(self.user, account, container,
155 cda71050 Christos Stavrakakis
                                              name)
156 cda71050 Christos Stavrakakis
        if not versions:
157 cda71050 Christos Stavrakakis
            raise Exception("Image without versions %s" % image_url)
158 cda71050 Christos Stavrakakis
        try:
159 cda71050 Christos Stavrakakis
            meta = self._get_meta(image_url)
160 14c94c48 Christos Stavrakakis
            meta["deleted"] = ""
161 cda71050 Christos Stavrakakis
        except NameError:
162 7bd1d3b5 Giorgos Verigakis
            # Object was deleted, use the latest version
163 7bd1d3b5 Giorgos Verigakis
            version, timestamp = versions[-1]
164 cda71050 Christos Stavrakakis
            meta = self._get_meta(image_url, version)
165 14c94c48 Christos Stavrakakis
            meta["deleted"] = timestamp
166 14c94c48 Christos Stavrakakis
167 14c94c48 Christos Stavrakakis
        meta["created"] = versions[0][1]
168 1e28ba40 Christos Stavrakakis
169 0a72907b Giorgos Verigakis
        if PLANKTON_PREFIX + 'name' not in meta:
170 cda71050 Christos Stavrakakis
            raise ImageNotFound("'%s' is not a Plankton image" % image_url)
171 1e28ba40 Christos Stavrakakis
172 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
173 14c94c48 Christos Stavrakakis
        return image_to_dict(image_url, meta, permissions)
174 1e28ba40 Christos Stavrakakis
175 cda71050 Christos Stavrakakis
    def _get_meta(self, image_url, version=None):
176 cda71050 Christos Stavrakakis
        """Get object's metadata."""
177 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
178 cda71050 Christos Stavrakakis
        return self.backend.get_object_meta(self.user, account, container,
179 cda71050 Christos Stavrakakis
                                            name, PLANKTON_DOMAIN, version)
180 1e28ba40 Christos Stavrakakis
181 cda71050 Christos Stavrakakis
    def _update_meta(self, image_url, meta, replace=False):
182 cda71050 Christos Stavrakakis
        """Update object's metadata."""
183 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
184 1e28ba40 Christos Stavrakakis
185 cda71050 Christos Stavrakakis
        prefixed = {}
186 cda71050 Christos Stavrakakis
        for key, val in meta.items():
187 cda71050 Christos Stavrakakis
            if key in PLANKTON_META:
188 cda71050 Christos Stavrakakis
                if key == "properties":
189 cda71050 Christos Stavrakakis
                    val = json.dumps(val)
190 cda71050 Christos Stavrakakis
                prefixed[PLANKTON_PREFIX + key] = val
191 1e28ba40 Christos Stavrakakis
192 cda71050 Christos Stavrakakis
        self.backend.update_object_meta(self.user, account, container, name,
193 cda71050 Christos Stavrakakis
                                        PLANKTON_DOMAIN, prefixed, replace)
194 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', meta: '%s'", self.user,
195 62d3ea53 Christos Stavrakakis
                     image_url, prefixed)
196 1e28ba40 Christos Stavrakakis
197 cda71050 Christos Stavrakakis
    def _get_permissions(self, image_url):
198 cda71050 Christos Stavrakakis
        """Get object's permissions."""
199 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
200 cda71050 Christos Stavrakakis
        _a, path, permissions = \
201 cda71050 Christos Stavrakakis
            self.backend.get_object_permissions(self.user, account, container,
202 cda71050 Christos Stavrakakis
                                                name)
203 1e28ba40 Christos Stavrakakis
204 cda71050 Christos Stavrakakis
        if path is None:
205 cda71050 Christos Stavrakakis
            logger.warning("Image '%s' got permissions from None path",
206 cda71050 Christos Stavrakakis
                           image_url)
207 1e28ba40 Christos Stavrakakis
208 cda71050 Christos Stavrakakis
        return permissions
209 1e28ba40 Christos Stavrakakis
210 cda71050 Christos Stavrakakis
    def _update_permissions(self, image_url, permissions):
211 cda71050 Christos Stavrakakis
        """Update object's permissions."""
212 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
213 cda71050 Christos Stavrakakis
        self.backend.update_object_permissions(self.user, account, container,
214 cda71050 Christos Stavrakakis
                                               name, permissions)
215 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', permissions: '%s'",
216 62d3ea53 Christos Stavrakakis
                     self.user, image_url, permissions)
217 1e28ba40 Christos Stavrakakis
218 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
219 cda71050 Christos Stavrakakis
    def unregister(self, image_uuid):
220 cda71050 Christos Stavrakakis
        """Unregister an image.
221 1e28ba40 Christos Stavrakakis

222 cda71050 Christos Stavrakakis
        Unregister an image, by removing all metadata from the Pithos
223 cda71050 Christos Stavrakakis
        file that exist in the PLANKTON_DOMAIN.
224 1e28ba40 Christos Stavrakakis

225 cda71050 Christos Stavrakakis
        """
226 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
227 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
228 cda71050 Christos Stavrakakis
        # Unregister the image by removing all metadata from domain
229 cda71050 Christos Stavrakakis
        # 'PLANKTON_DOMAIN'
230 22b5ac0b Christos Stavrakakis
        meta = {}
231 22b5ac0b Christos Stavrakakis
        self._update_meta(image_url, meta, True)
232 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' deleted image '%s'", self.user, image_url)
233 1e28ba40 Christos Stavrakakis
234 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
235 cda71050 Christos Stavrakakis
    def add_user(self, image_uuid, add_user):
236 cda71050 Christos Stavrakakis
        """Add a user as an image member.
237 cda71050 Christos Stavrakakis

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

240 cda71050 Christos Stavrakakis
        """
241 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
242 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
243 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
244 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
245 cda71050 Christos Stavrakakis
        assert(isinstance(add_user, (str, unicode)))
246 cda71050 Christos Stavrakakis
        read.add(add_user)
247 cda71050 Christos Stavrakakis
        permissions["read"] = list(read)
248 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
249 1e28ba40 Christos Stavrakakis
250 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
251 cda71050 Christos Stavrakakis
    def remove_user(self, image_uuid, remove_user):
252 cda71050 Christos Stavrakakis
        """Remove the user from image members.
253 1e28ba40 Christos Stavrakakis

254 cda71050 Christos Stavrakakis
        Remove the specified user from the read permissions of the Pithos file.
255 cda71050 Christos Stavrakakis

256 cda71050 Christos Stavrakakis
        """
257 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
258 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
259 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
260 cda71050 Christos Stavrakakis
        read = set(permissions.get("read", []))
261 cda71050 Christos Stavrakakis
        assert(isinstance(remove_user, (str, unicode)))
262 cda71050 Christos Stavrakakis
        try:
263 cda71050 Christos Stavrakakis
            read.remove(remove_user)
264 cda71050 Christos Stavrakakis
        except ValueError:
265 cda71050 Christos Stavrakakis
            return  # TODO: User did not have access
266 cda71050 Christos Stavrakakis
        permissions["read"] = list(read)
267 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
268 1e28ba40 Christos Stavrakakis
269 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
270 cda71050 Christos Stavrakakis
    def replace_users(self, image_uuid, replace_users):
271 cda71050 Christos Stavrakakis
        """Replace image members.
272 f13aab5d Christos Stavrakakis

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

276 f13aab5d Christos Stavrakakis
        """
277 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
278 cda71050 Christos Stavrakakis
        image = self._get_image(image_url)
279 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
280 cda71050 Christos Stavrakakis
        assert(isinstance(replace_users, list))
281 cda71050 Christos Stavrakakis
        permissions["read"] = replace_users
282 cda71050 Christos Stavrakakis
        if image.get("is_public", False):
283 cda71050 Christos Stavrakakis
            permissions["read"].append("*")
284 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
285 1e28ba40 Christos Stavrakakis
286 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
287 cda71050 Christos Stavrakakis
    def list_users(self, image_uuid):
288 cda71050 Christos Stavrakakis
        """List the image members.
289 cda71050 Christos Stavrakakis

290 cda71050 Christos Stavrakakis
        List the image members, by listing all users that have read permission
291 cda71050 Christos Stavrakakis
        to the corresponding Pithos file.
292 cda71050 Christos Stavrakakis

293 cda71050 Christos Stavrakakis
        """
294 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
295 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
296 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
297 cda71050 Christos Stavrakakis
        return [user for user in permissions.get('read', []) if user != '*']
298 1e28ba40 Christos Stavrakakis
299 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
300 cda71050 Christos Stavrakakis
    def update_metadata(self, image_uuid, metadata):
301 cda71050 Christos Stavrakakis
        """Update Image metadata."""
302 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
303 cda71050 Christos Stavrakakis
        self._get_image(image_url)  # Assert that it is an image
304 1e28ba40 Christos Stavrakakis
305 cda71050 Christos Stavrakakis
        is_public = metadata.pop("is_public", None)
306 cda71050 Christos Stavrakakis
        if is_public is not None:
307 cda71050 Christos Stavrakakis
            permissions = self._get_permissions(image_url)
308 cda71050 Christos Stavrakakis
            read = set(permissions.get("read", []))
309 cda71050 Christos Stavrakakis
            if is_public:
310 cda71050 Christos Stavrakakis
                read.add("*")
311 cda71050 Christos Stavrakakis
            else:
312 cda71050 Christos Stavrakakis
                read.discard("*")
313 cda71050 Christos Stavrakakis
            permissions["read"] = list(read)
314 cda71050 Christos Stavrakakis
            self._update_permissions(image_url, permissions)
315 cda71050 Christos Stavrakakis
        meta = {}
316 cda71050 Christos Stavrakakis
        meta["properties"] = metadata.pop("properties", {})
317 cda71050 Christos Stavrakakis
        meta.update(**metadata)
318 cda71050 Christos Stavrakakis
319 cda71050 Christos Stavrakakis
        self._update_meta(image_url, meta)
320 cda71050 Christos Stavrakakis
        return self.get_image(image_uuid)
321 1e28ba40 Christos Stavrakakis
322 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
323 cda71050 Christos Stavrakakis
    def register(self, name, image_url, metadata):
324 cda71050 Christos Stavrakakis
        # Validate that metadata are allowed
325 cda71050 Christos Stavrakakis
        if "id" in metadata:
326 cda71050 Christos Stavrakakis
            raise ValueError("Passing an ID is not supported")
327 cda71050 Christos Stavrakakis
        store = metadata.pop("store", "pithos")
328 cda71050 Christos Stavrakakis
        if store != "pithos":
329 cda71050 Christos Stavrakakis
            raise ValueError("Invalid store '%s'. Only 'pithos' store is"
330 cda71050 Christos Stavrakakis
                             "supported" % store)
331 cda71050 Christos Stavrakakis
        disk_format = metadata.setdefault("disk_format",
332 cda71050 Christos Stavrakakis
                                          settings.DEFAULT_DISK_FORMAT)
333 cda71050 Christos Stavrakakis
        if disk_format not in settings.ALLOWED_DISK_FORMATS:
334 cda71050 Christos Stavrakakis
            raise ValueError("Invalid disk format '%s'" % disk_format)
335 cda71050 Christos Stavrakakis
        container_format =\
336 cda71050 Christos Stavrakakis
            metadata.setdefault("container_format",
337 cda71050 Christos Stavrakakis
                                settings.DEFAULT_CONTAINER_FORMAT)
338 cda71050 Christos Stavrakakis
        if container_format not in settings.ALLOWED_CONTAINER_FORMATS:
339 cda71050 Christos Stavrakakis
            raise ValueError("Invalid container format '%s'" %
340 cda71050 Christos Stavrakakis
                             container_format)
341 cda71050 Christos Stavrakakis
342 cda71050 Christos Stavrakakis
        # Validate that 'size' and 'checksum' are valid
343 cda71050 Christos Stavrakakis
        account, container, object = split_url(image_url)
344 cda71050 Christos Stavrakakis
345 cda71050 Christos Stavrakakis
        meta = self._get_meta(image_url)
346 cda71050 Christos Stavrakakis
347 cda71050 Christos Stavrakakis
        size = int(metadata.pop('size', meta['bytes']))
348 cda71050 Christos Stavrakakis
        if size != meta['bytes']:
349 cda71050 Christos Stavrakakis
            raise ValueError("Invalid size")
350 cda71050 Christos Stavrakakis
351 cda71050 Christos Stavrakakis
        checksum = metadata.pop('checksum', meta['hash'])
352 cda71050 Christos Stavrakakis
        if checksum != meta['hash']:
353 cda71050 Christos Stavrakakis
            raise ValueError("Invalid checksum")
354 cda71050 Christos Stavrakakis
355 cda71050 Christos Stavrakakis
        # Fix permissions
356 cda71050 Christos Stavrakakis
        is_public = metadata.pop('is_public', False)
357 cda71050 Christos Stavrakakis
        if is_public:
358 cda71050 Christos Stavrakakis
            permissions = {'read': ['*']}
359 cda71050 Christos Stavrakakis
        else:
360 cda71050 Christos Stavrakakis
            permissions = {'read': [self.user]}
361 cda71050 Christos Stavrakakis
362 cda71050 Christos Stavrakakis
        # Update rest metadata
363 cda71050 Christos Stavrakakis
        meta = {}
364 cda71050 Christos Stavrakakis
        meta['properties'] = metadata.pop('properties', {})
365 cda71050 Christos Stavrakakis
        meta.update(name=name, status='available', **metadata)
366 cda71050 Christos Stavrakakis
367 cda71050 Christos Stavrakakis
        # Do the actualy update in the Pithos backend
368 cda71050 Christos Stavrakakis
        self._update_meta(image_url, meta)
369 cda71050 Christos Stavrakakis
        self._update_permissions(image_url, permissions)
370 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' created image '%s'('%s')", self.user,
371 62d3ea53 Christos Stavrakakis
                     image_url, name)
372 cda71050 Christos Stavrakakis
        return self._get_image(image_url)
373 cda71050 Christos Stavrakakis
374 14c94c48 Christos Stavrakakis
    def _list_images(self, user=None, filters=None, params=None):
375 d19e8f77 Giorgos Verigakis
        filters = filters or {}
376 1e28ba40 Christos Stavrakakis
377 14c94c48 Christos Stavrakakis
        # TODO: Use filters
378 14c94c48 Christos Stavrakakis
        # # Fix keys
379 14c94c48 Christos Stavrakakis
        # keys = [PLANKTON_PREFIX + 'name']
380 14c94c48 Christos Stavrakakis
        # size_range = (None, None)
381 14c94c48 Christos Stavrakakis
        # for key, val in filters.items():
382 14c94c48 Christos Stavrakakis
        #     if key == 'size_min':
383 14c94c48 Christos Stavrakakis
        #         size_range = (val, size_range[1])
384 14c94c48 Christos Stavrakakis
        #     elif key == 'size_max':
385 14c94c48 Christos Stavrakakis
        #         size_range = (size_range[0], val)
386 14c94c48 Christos Stavrakakis
        #     else:
387 14c94c48 Christos Stavrakakis
        #         keys.append('%s = %s' % (PLANKTON_PREFIX + key, val))
388 14c94c48 Christos Stavrakakis
        _images = self.backend.get_domain_objects(domain=PLANKTON_DOMAIN,
389 14c94c48 Christos Stavrakakis
                                                  user=user)
390 14c94c48 Christos Stavrakakis
391 14c94c48 Christos Stavrakakis
        images = []
392 14c94c48 Christos Stavrakakis
        for (location, meta, permissions) in _images:
393 14c94c48 Christos Stavrakakis
            image_url = "pithos://" + location
394 14c94c48 Christos Stavrakakis
            meta["modified"] = meta["version_timestamp"]
395 14c94c48 Christos Stavrakakis
            # TODO: Create metadata when registering an Image
396 14c94c48 Christos Stavrakakis
            meta["created"] = meta["version_timestamp"]
397 14c94c48 Christos Stavrakakis
            images.append(image_to_dict(image_url, meta, permissions))
398 14c94c48 Christos Stavrakakis
399 14c94c48 Christos Stavrakakis
        if params is None:
400 14c94c48 Christos Stavrakakis
            params = {}
401 aed77afe Christos Stavrakakis
        key = itemgetter(params.get('sort_key', 'created_at'))
402 aed77afe Christos Stavrakakis
        reverse = params.get('sort_dir', 'desc') == 'desc'
403 aed77afe Christos Stavrakakis
        images.sort(key=key, reverse=reverse)
404 aed77afe Christos Stavrakakis
        return images
405 1e28ba40 Christos Stavrakakis
406 14c94c48 Christos Stavrakakis
    def list_images(self, filters=None, params=None):
407 14c94c48 Christos Stavrakakis
        return self._list_images(user=self.user, filters=filters,
408 14c94c48 Christos Stavrakakis
                                 params=params)
409 14c94c48 Christos Stavrakakis
410 14c94c48 Christos Stavrakakis
    def list_shared_images(self, member, filters=None, params=None):
411 14c94c48 Christos Stavrakakis
        images = self._list_images(user=self.user, filters=filters,
412 14c94c48 Christos Stavrakakis
                                   params=params)
413 14c94c48 Christos Stavrakakis
        is_shared = lambda img: not img["is_public"] and img["owner"] == member
414 14c94c48 Christos Stavrakakis
        return filter(is_shared, images)
415 14c94c48 Christos Stavrakakis
416 14c94c48 Christos Stavrakakis
    def list_public_images(self, filters=None, params=None):
417 14c94c48 Christos Stavrakakis
        images = self._list_images(user=None, filters=filters, params=params)
418 14c94c48 Christos Stavrakakis
        return filter(lambda img: img["is_public"], images)
419 1e28ba40 Christos Stavrakakis
420 1e28ba40 Christos Stavrakakis
421 cda71050 Christos Stavrakakis
class ImageBackendError(Exception):
422 cda71050 Christos Stavrakakis
    pass
423 1e28ba40 Christos Stavrakakis
424 1e28ba40 Christos Stavrakakis
425 cda71050 Christos Stavrakakis
class ImageNotFound(ImageBackendError):
426 cda71050 Christos Stavrakakis
    pass
427 f13aab5d Christos Stavrakakis
428 f13aab5d Christos Stavrakakis
429 cda71050 Christos Stavrakakis
class Forbidden(ImageBackendError):
430 cda71050 Christos Stavrakakis
    pass
431 14c94c48 Christos Stavrakakis
432 14c94c48 Christos Stavrakakis
433 14c94c48 Christos Stavrakakis
def image_to_dict(image_url, meta, permissions):
434 14c94c48 Christos Stavrakakis
    """Render an image to a dictionary"""
435 14c94c48 Christos Stavrakakis
    account, container, name = split_url(image_url)
436 14c94c48 Christos Stavrakakis
437 14c94c48 Christos Stavrakakis
    image = {}
438 14c94c48 Christos Stavrakakis
    if PLANKTON_PREFIX + 'name' not in meta:
439 14c94c48 Christos Stavrakakis
        raise ImageNotFound("'%s' is not a Plankton image" % image_url)
440 14c94c48 Christos Stavrakakis
441 14c94c48 Christos Stavrakakis
    image["id"] = meta["uuid"]
442 14c94c48 Christos Stavrakakis
    image["location"] = image_url
443 14c94c48 Christos Stavrakakis
    image["checksum"] = meta["hash"]
444 14c94c48 Christos Stavrakakis
    image["created_at"] = format_timestamp(meta["created"])
445 14c94c48 Christos Stavrakakis
    deleted = meta.get("deleted", None)
446 14c94c48 Christos Stavrakakis
    image["deleted_at"] = format_timestamp(deleted) if deleted else ""
447 14c94c48 Christos Stavrakakis
    image["updated_at"] = format_timestamp(meta["modified"])
448 14c94c48 Christos Stavrakakis
    image["size"] = meta["bytes"]
449 14c94c48 Christos Stavrakakis
    image["store"] = "pithos"
450 14c94c48 Christos Stavrakakis
    image['owner'] = account
451 14c94c48 Christos Stavrakakis
452 14c94c48 Christos Stavrakakis
    # Permissions
453 14c94c48 Christos Stavrakakis
    image["is_public"] = "*" in permissions.get('read', [])
454 14c94c48 Christos Stavrakakis
455 14c94c48 Christos Stavrakakis
    for key, val in meta.items():
456 14c94c48 Christos Stavrakakis
        # Get plankton properties
457 14c94c48 Christos Stavrakakis
        if key.startswith(PLANKTON_PREFIX):
458 14c94c48 Christos Stavrakakis
            # Remove plankton prefix
459 14c94c48 Christos Stavrakakis
            key = key.replace(PLANKTON_PREFIX, "")
460 14c94c48 Christos Stavrakakis
            # Keep only those in plankton meta
461 14c94c48 Christos Stavrakakis
            if key in PLANKTON_META:
462 14c94c48 Christos Stavrakakis
                if key == "properties":
463 14c94c48 Christos Stavrakakis
                    val = json.loads(val)
464 14c94c48 Christos Stavrakakis
                image[key] = val
465 14c94c48 Christos Stavrakakis
466 14c94c48 Christos Stavrakakis
    return image