Statistics
| Branch: | Tag: | Revision:

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

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
_pithos_backend_pool = \
76 b336e6fa Georgios D. Tsoukalas
    PithosBackendPool(
77 e3f006b0 Christos Stavrakakis
        settings.PITHOS_BACKEND_POOL_SIZE,
78 e3ff6830 Georgios D. Tsoukalas
        astakos_url=settings.ASTAKOS_BASE_URL,
79 18c4414d Giorgos Korfiatis
        service_token=settings.CYCLADES_SERVICE_TOKEN,
80 b0c95903 Giorgos Korfiatis
        astakosclient_poolsize=settings.CYCLADES_ASTAKOSCLIENT_POOLSIZE,
81 b336e6fa Georgios D. Tsoukalas
        db_connection=settings.BACKEND_DB_CONNECTION,
82 b336e6fa Georgios D. Tsoukalas
        block_path=settings.BACKEND_BLOCK_PATH)
83 7784ab88 Christos Stavrakakis
84 7784ab88 Christos Stavrakakis
85 7784ab88 Christos Stavrakakis
def get_pithos_backend():
86 7784ab88 Christos Stavrakakis
    return _pithos_backend_pool.pool_get()
87 7784ab88 Christos Stavrakakis
88 7784ab88 Christos Stavrakakis
89 cda71050 Christos Stavrakakis
def create_url(account, container, name):
90 cda71050 Christos Stavrakakis
    assert "/" not in account, "Invalid account"
91 cda71050 Christos Stavrakakis
    assert "/" not in container, "Invalid container"
92 cda71050 Christos Stavrakakis
    return "pithos://%s/%s/%s" % (account, container, name)
93 cda71050 Christos Stavrakakis
94 cda71050 Christos Stavrakakis
95 cda71050 Christos Stavrakakis
def split_url(url):
96 cda71050 Christos Stavrakakis
    """Returns (accout, container, object) from a url string"""
97 cda71050 Christos Stavrakakis
    t = url.split('/', 4)
98 59573532 Christos Stavrakakis
    assert t[0] == "pithos:", "Invalid url"
99 cda71050 Christos Stavrakakis
    assert len(t) == 5, "Invalid url"
100 cda71050 Christos Stavrakakis
    return t[2:5]
101 cda71050 Christos Stavrakakis
102 cda71050 Christos Stavrakakis
103 cda71050 Christos Stavrakakis
def format_timestamp(t):
104 cda71050 Christos Stavrakakis
    return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
105 cda71050 Christos Stavrakakis
106 cda71050 Christos Stavrakakis
107 2db7d9df Christos Stavrakakis
def handle_backend_exceptions(func):
108 2db7d9df Christos Stavrakakis
    @wraps(func)
109 2db7d9df Christos Stavrakakis
    def wrapper(*args, **kwargs):
110 2db7d9df Christos Stavrakakis
        try:
111 2db7d9df Christos Stavrakakis
            return func(*args, **kwargs)
112 f6ff4b40 Christos Stavrakakis
        except NotAllowedError:
113 cda71050 Christos Stavrakakis
            raise Forbidden
114 cda71050 Christos Stavrakakis
        except NameError:
115 cda71050 Christos Stavrakakis
            raise ImageNotFound
116 cda71050 Christos Stavrakakis
        except VersionNotExists:
117 cda71050 Christos Stavrakakis
            raise ImageNotFound
118 2db7d9df Christos Stavrakakis
    return wrapper
119 2db7d9df Christos Stavrakakis
120 2db7d9df Christos Stavrakakis
121 ffab341c Christos Stavrakakis
class ImageBackend(object):
122 f5afd99b Giorgos Verigakis
    """A wrapper arround the pithos backend to simplify image handling."""
123 1e28ba40 Christos Stavrakakis
124 c34de90f Giorgos Verigakis
    def __init__(self, user):
125 c34de90f Giorgos Verigakis
        self.user = user
126 7784ab88 Christos Stavrakakis
127 c23d211a Giorgos Verigakis
        original_filters = warnings.filters
128 c23d211a Giorgos Verigakis
        warnings.simplefilter('ignore')         # Suppress SQLAlchemy warnings
129 7784ab88 Christos Stavrakakis
        self.backend = get_pithos_backend()
130 c23d211a Giorgos Verigakis
        warnings.filters = original_filters     # Restore warnings
131 7784ab88 Christos Stavrakakis
132 cda71050 Christos Stavrakakis
    def close(self):
133 cda71050 Christos Stavrakakis
        """Close PithosBackend(return to pool)"""
134 cda71050 Christos Stavrakakis
        self.backend.close()
135 cda71050 Christos Stavrakakis
136 2db7d9df Christos Stavrakakis
    @handle_backend_exceptions
137 cda71050 Christos Stavrakakis
    def get_image(self, image_uuid):
138 cda71050 Christos Stavrakakis
        """Retrieve information about an image."""
139 cda71050 Christos Stavrakakis
        image_url = self._get_image_url(image_uuid)
140 cda71050 Christos Stavrakakis
        return self._get_image(image_url)
141 1e28ba40 Christos Stavrakakis
142 cda71050 Christos Stavrakakis
    def _get_image_url(self, image_uuid):
143 cda71050 Christos Stavrakakis
        """Get the Pithos url that corresponds to an image UUID."""
144 cda71050 Christos Stavrakakis
        account, container, name = self.backend.get_uuid(self.user, image_uuid)
145 cda71050 Christos Stavrakakis
        return create_url(account, container, name)
146 1e28ba40 Christos Stavrakakis
147 cda71050 Christos Stavrakakis
    def _get_image(self, image_url):
148 cda71050 Christos Stavrakakis
        """Get information about an Image.
149 cda71050 Christos Stavrakakis

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

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

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

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

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

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

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

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

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

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

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