Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (17 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 18c4414d Giorgos Korfiatis
        service_token=settings.CYCLADES_SERVICE_TOKEN,
81 b0c95903 Giorgos Korfiatis
        astakosclient_poolsize=settings.CYCLADES_ASTAKOSCLIENT_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 125c682c Christos Stavrakakis
            logger.warning("Image without Plankton name! url %s meta %s",
171 125c682c Christos Stavrakakis
                           image_url, meta)
172 125c682c Christos Stavrakakis
            meta[PLANKTON_PREFIX + "name"] = ""
173 1e28ba40 Christos Stavrakakis
174 cda71050 Christos Stavrakakis
        permissions = self._get_permissions(image_url)
175 14c94c48 Christos Stavrakakis
        return image_to_dict(image_url, meta, permissions)
176 1e28ba40 Christos Stavrakakis
177 cda71050 Christos Stavrakakis
    def _get_meta(self, image_url, version=None):
178 cda71050 Christos Stavrakakis
        """Get object's metadata."""
179 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
180 cda71050 Christos Stavrakakis
        return self.backend.get_object_meta(self.user, account, container,
181 cda71050 Christos Stavrakakis
                                            name, PLANKTON_DOMAIN, version)
182 1e28ba40 Christos Stavrakakis
183 cda71050 Christos Stavrakakis
    def _update_meta(self, image_url, meta, replace=False):
184 cda71050 Christos Stavrakakis
        """Update object's metadata."""
185 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
186 1e28ba40 Christos Stavrakakis
187 cda71050 Christos Stavrakakis
        prefixed = {}
188 cda71050 Christos Stavrakakis
        for key, val in meta.items():
189 cda71050 Christos Stavrakakis
            if key in PLANKTON_META:
190 cda71050 Christos Stavrakakis
                if key == "properties":
191 cda71050 Christos Stavrakakis
                    val = json.dumps(val)
192 cda71050 Christos Stavrakakis
                prefixed[PLANKTON_PREFIX + key] = val
193 1e28ba40 Christos Stavrakakis
194 cda71050 Christos Stavrakakis
        self.backend.update_object_meta(self.user, account, container, name,
195 cda71050 Christos Stavrakakis
                                        PLANKTON_DOMAIN, prefixed, replace)
196 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', meta: '%s'", self.user,
197 62d3ea53 Christos Stavrakakis
                     image_url, prefixed)
198 1e28ba40 Christos Stavrakakis
199 cda71050 Christos Stavrakakis
    def _get_permissions(self, image_url):
200 cda71050 Christos Stavrakakis
        """Get object's permissions."""
201 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
202 cda71050 Christos Stavrakakis
        _a, path, permissions = \
203 cda71050 Christos Stavrakakis
            self.backend.get_object_permissions(self.user, account, container,
204 cda71050 Christos Stavrakakis
                                                name)
205 1e28ba40 Christos Stavrakakis
206 cda71050 Christos Stavrakakis
        if path is None:
207 cda71050 Christos Stavrakakis
            logger.warning("Image '%s' got permissions from None path",
208 cda71050 Christos Stavrakakis
                           image_url)
209 1e28ba40 Christos Stavrakakis
210 cda71050 Christos Stavrakakis
        return permissions
211 1e28ba40 Christos Stavrakakis
212 cda71050 Christos Stavrakakis
    def _update_permissions(self, image_url, permissions):
213 cda71050 Christos Stavrakakis
        """Update object's permissions."""
214 cda71050 Christos Stavrakakis
        account, container, name = split_url(image_url)
215 cda71050 Christos Stavrakakis
        self.backend.update_object_permissions(self.user, account, container,
216 cda71050 Christos Stavrakakis
                                               name, permissions)
217 62d3ea53 Christos Stavrakakis
        logger.debug("User '%s' updated image '%s', permissions: '%s'",
218 62d3ea53 Christos Stavrakakis
                     self.user, image_url, permissions)
219 1e28ba40 Christos Stavrakakis
220 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
221 cda71050 Christos Stavrakakis
    def unregister(self, image_uuid):
222 cda71050 Christos Stavrakakis
        """Unregister an image.
223 1e28ba40 Christos Stavrakakis

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

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

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

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

256 cda71050 Christos Stavrakakis
        Remove the specified user from the read permissions of the Pithos file.
257 cda71050 Christos Stavrakakis

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

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

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

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

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