commit 238085929eaccd0b0d6666e4ca1a50cda5fe1ec3
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Sat Feb 15 18:37:23 2014 +0200

    cyclades: Major refactor to plankton backend
    
    * Rename ImageBackend to PlanktonBackend
    * Rewrite PlanktonBackend methods in order that they refer to Pithos
      objects by their UUID
    * Convert PlanktonBackend to context manager that is responsible for
      returning the PithosBackend connection back to the pool, and remove
      stale 'image_backend' context manager
    * Make PlanktonBackend raise cloud (API) faults

diff --git a/snf-cyclades-app/synnefo/admin/stats.py b/snf-cyclades-app/synnefo/admin/stats.py
index 6744835..f730532 100644
--- a/snf-cyclades-app/synnefo/admin/stats.py
+++ b/snf-cyclades-app/synnefo/admin/stats.py
@@ -43,7 +43,7 @@ from django.db.models import Count, Sum
 
 from snf_django.lib.astakos import UserCache
 from synnefo.db.models import VirtualMachine, Network, Backend
-from synnefo.plankton.utils import image_backend
+from synnefo.plankton.backend import PlanktonBackend
 from synnefo.logic import backend as backend_mod
 
 
@@ -204,7 +204,7 @@ class ImageCache(object):
     def get_image(self, imageid, userid):
         if not imageid in self.images:
             try:
-                with image_backend(userid) as ib:
+                with PlanktonBackend(userid) as ib:
                     image = ib.get_image(imageid)
                 properties = image.get("properties")
                 os = properties.get("os",
diff --git a/snf-cyclades-app/synnefo/api/images.py b/snf-cyclades-app/synnefo/api/images.py
index a1e4986..c34b9f0 100644
--- a/snf-cyclades-app/synnefo/api/images.py
+++ b/snf-cyclades-app/synnefo/api/images.py
@@ -44,7 +44,7 @@ from django.utils import simplejson as json
 from snf_django.lib import api
 from snf_django.lib.api import faults, utils
 from synnefo.api import util
-from synnefo.plankton.utils import image_backend
+from synnefo.plankton import backend
 
 
 log = getLogger(__name__)
@@ -131,8 +131,8 @@ def list_images(request, detail=False):
 
     log.debug('list_images detail=%s', detail)
     since = utils.isoparse(request.GET.get('changes-since'))
-    with image_backend(request.user_uniq) as backend:
-        images = backend.list_images()
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        images = b.list_images()
         if since:
             updated_since = lambda img: date_parse(img["updated_at"]) >= since
             images = ifilter(updated_since, images)
@@ -180,8 +180,8 @@ def get_image_details(request, image_id):
     #                       overLimit (413)
 
     log.debug('get_image_details %s', image_id)
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
     reply = image_to_dict(image)
 
     if request.serialization == 'xml':
@@ -202,8 +202,8 @@ def delete_image(request, image_id):
     #                       overLimit (413)
 
     log.info('delete_image %s', image_id)
-    with image_backend(request.user_uniq) as backend:
-        backend.unregister(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        b.unregister(image_id)
     log.info('User %s deleted image %s', request.user_uniq, image_id)
     return HttpResponse(status=204)
 
@@ -218,8 +218,8 @@ def list_metadata(request, image_id):
     #                       overLimit (413)
 
     log.debug('list_image_metadata %s', image_id)
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
     metadata = image['properties']
     return util.render_metadata(request, metadata, use_values=False,
                                 status=200)
@@ -238,8 +238,8 @@ def update_metadata(request, image_id):
 
     req = utils.get_request_dict(request)
     log.info('update_image_metadata %s %s', image_id, req)
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
         try:
             metadata = req['metadata']
             assert isinstance(metadata, dict)
@@ -249,7 +249,7 @@ def update_metadata(request, image_id):
         properties = image['properties']
         properties.update(metadata)
 
-        backend.update_metadata(image_id, dict(properties=properties))
+        b.update_metadata(image_id, dict(properties=properties))
 
     return util.render_metadata(request, properties, status=201)
 
@@ -265,8 +265,8 @@ def get_metadata_item(request, image_id, key):
     #                       overLimit (413)
 
     log.debug('get_image_metadata_item %s %s', image_id, key)
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
     val = image['properties'].get(key)
     if val is None:
         raise faults.ItemNotFound('Metadata key not found.')
@@ -296,12 +296,12 @@ def create_metadata_item(request, image_id, key):
         raise faults.BadRequest('Malformed request.')
 
     val = metadict[key]
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
         properties = image['properties']
         properties[key] = val
 
-        backend.update_metadata(image_id, dict(properties=properties))
+        b.update_metadata(image_id, dict(properties=properties))
 
     return util.render_meta(request, {key: val}, status=201)
 
@@ -319,11 +319,11 @@ def delete_metadata_item(request, image_id, key):
     #                       overLimit (413),
 
     log.info('delete_image_metadata_item %s %s', image_id, key)
-    with image_backend(request.user_uniq) as backend:
-        image = backend.get_image(image_id)
+    with backend.PlanktonBackend(request.user_uniq) as b:
+        image = b.get_image(image_id)
         properties = image['properties']
         properties.pop(key, None)
 
-        backend.update_metadata(image_id, dict(properties=properties))
+        b.update_metadata(image_id, dict(properties=properties))
 
     return HttpResponse(status=204)
diff --git a/snf-cyclades-app/synnefo/api/tests/images.py b/snf-cyclades-app/synnefo/api/tests/images.py
index ff844d7..6067180 100644
--- a/snf-cyclades-app/synnefo/api/tests/images.py
+++ b/snf-cyclades-app/synnefo/api/tests/images.py
@@ -42,9 +42,13 @@ from synnefo.lib import join_urls
 from mock import patch
 from functools import wraps
 
+compute_path = get_service_path(cyclades_services, 'compute',
+                                version='v2.0')
+IMAGES_URL = join_urls(compute_path, "images/")
+
 
 def assert_backend_closed(func):
-    """Decorator for ensuring that ImageBackend is returned to pool."""
+    """Decorator for ensuring that PlanktonBackend is returned to pool."""
     @wraps(func)
     def wrapper(self, backend):
         result = func(self, backend)
@@ -54,34 +58,12 @@ def assert_backend_closed(func):
     return wrapper
 
 
-class ComputeAPITest(BaseAPITest):
-    def setUp(self, *args, **kwargs):
-        super(ComputeAPITest, self).setUp(*args, **kwargs)
-        self.compute_path = get_service_path(cyclades_services, 'compute',
-                                             version='v2.0')
-    def myget(self, path, *args, **kwargs):
-        path = join_urls(self.compute_path, path)
-        return self.get(path, *args, **kwargs)
-
-    def myput(self, path, *args, **kwargs):
-        path = join_urls(self.compute_path, path)
-        return self.put(path, *args, **kwargs)
-
-    def mypost(self, path, *args, **kwargs):
-        path = join_urls(self.compute_path, path)
-        return self.post(path, *args, **kwargs)
-
-    def mydelete(self, path, *args, **kwargs):
-        path = join_urls(self.compute_path, path)
-        return self.delete(path, *args, **kwargs)
-
-
-@patch('synnefo.plankton.backend.ImageBackend')
-class ImageAPITest(ComputeAPITest):
+@patch('synnefo.plankton.backend.PlanktonBackend')
+class ImageAPITest(BaseAPITest):
     @assert_backend_closed
     def test_create_image(self, mimage):
         """Test that create image is not implemented"""
-        response = self.mypost('images/', 'user', json.dumps(''), 'json')
+        response = self.post(IMAGES_URL, 'user', json.dumps(''), 'json')
         self.assertEqual(response.status_code, 501)
 
     @assert_backend_closed
@@ -90,8 +72,8 @@ class ImageAPITest(ComputeAPITest):
         images = [{'id': 1, 'name': 'image-1'},
                   {'id': 2, 'name': 'image-2'},
                   {'id': 3, 'name': 'image-3'}]
-        mimage().list_images.return_value = images
-        response = self.myget('images', 'user')
+        mimage().__enter__().list_images.return_value = images
+        response = self.get(IMAGES_URL, 'user')
         self.assertSuccess(response)
         api_images = json.loads(response.content)['images']
         self.assertEqual(images, api_images)
@@ -150,8 +132,8 @@ class ImageAPITest(ComputeAPITest):
                    'created': '2012-11-26T11:52:54+00:00',
                    'updated': '2012-12-26T11:52:54+00:00',
                    'metadata': {}}]
-        mimage().list_images.return_value = images
-        response = self.myget('images/detail', 'user')
+        mimage().__enter__().list_images.return_value = images
+        response = self.get(join_urls(IMAGES_URL, "detail"), 'user')
         self.assertSuccess(response)
         api_images = json.loads(response.content)['images']
         self.assertEqual(len(result_images), len(api_images))
@@ -184,9 +166,10 @@ class ImageAPITest(ComputeAPITest):
                    'updated_at': new_time.isoformat(),
                    'deleted_at': new_time.isoformat(),
                    'properties': ''}]
-        mimage().list_images.return_value = images
+        mimage().__enter__().list_images.return_value = images
         response =\
-            self.myget('images/detail?changes-since=%sUTC' % new_time)
+            self.get(join_urls(IMAGES_URL, 'detail?changes-since=%sUTC' %
+                               new_time))
         self.assertSuccess(response)
         api_images = json.loads(response.content)['images']
         self.assertEqual(1, len(api_images))
@@ -211,29 +194,28 @@ class ImageAPITest(ComputeAPITest):
                    'user_id': 'user1',
                    'tenant_id': 'user1',
                    'metadata': {'foo': 'bar'}}
-        mimage.return_value.get_image.return_value = image
-        response = self.myget('images/42', 'user')
+        mimage().__enter__().get_image.return_value = image
+        response = self.get(join_urls(IMAGES_URL, "42"), 'user')
         self.assertSuccess(response)
         api_image = json.loads(response.content)['image']
         api_image.pop("links")
         self.assertEqual(api_image, result_image)
 
-    @assert_backend_closed
     def test_invalid_image(self, mimage):
-        mimage.return_value.get_image.side_effect = faults.ItemNotFound('Image not found')
-        response = self.myget('images/42', 'user')
+        mimage().__enter__().get_image.side_effect = \
+            faults.ItemNotFound('Image not found')
+        response = self.get(join_urls(IMAGES_URL, "42"), 'user')
         self.assertItemNotFound(response)
 
     @assert_backend_closed
     def test_delete_image(self, mimage):
-        response = self.mydelete("images/42", "user")
+        response = self.delete(join_urls(IMAGES_URL, "42"), 'user')
         self.assertEqual(response.status_code, 204)
-        mimage.return_value.unregister.assert_called_once_with('42')
-        mimage.return_value._delete.assert_not_called('42')
+        mimage().__enter__().unregister.assert_called_once_with('42')
 
     @assert_backend_closed
     def test_catch_wrong_api_paths(self, *args):
-        response = self.myget('nonexistent')
+        response = self.get(join_urls(IMAGES_URL, 'nonexistent/lala/foo'))
         self.assertEqual(response.status_code, 400)
         try:
             error = json.loads(response.content)
@@ -243,36 +225,31 @@ class ImageAPITest(ComputeAPITest):
     @assert_backend_closed
     def test_method_not_allowed(self, *args):
         # /images/ allows only POST, GET
-        response = self.myput('images', '', '')
+        response = self.put(IMAGES_URL, '', '')
         self.assertMethodNotAllowed(response)
-        response = self.mydelete('images')
+        response = self.delete(IMAGES_URL, '')
         self.assertMethodNotAllowed(response)
 
         # /images/<imgid>/ allows only GET, DELETE
-        response = self.mypost("images/42")
-        self.assertMethodNotAllowed(response)
-        response = self.myput('images/42', '', '')
-        self.assertMethodNotAllowed(response)
-
-        # /images/<imgid>/metadata/ allows only POST, GET
-        response = self.myput('images/42/metadata', '', '')
+        response = self.post(join_urls(IMAGES_URL, "42"), 'user')
         self.assertMethodNotAllowed(response)
-        response = self.mydelete('images/42/metadata')
+        response = self.put(join_urls(IMAGES_URL, "42"), 'user')
         self.assertMethodNotAllowed(response)
 
         # /images/<imgid>/metadata/ allows only POST, GET
-        response = self.myput('images/42/metadata', '', '')
+        response = self.put(join_urls(IMAGES_URL, "42", "metadata"), 'user')
         self.assertMethodNotAllowed(response)
-        response = self.mydelete('images/42/metadata')
+        response = self.delete(join_urls(IMAGES_URL, "42", "metadata"), 'user')
         self.assertMethodNotAllowed(response)
 
         # /images/<imgid>/metadata/<key> allows only PUT, GET, DELETE
-        response = self.mypost('images/42/metadata/foo')
+        response = self.post(join_urls(IMAGES_URL, "42", "metadata", "foo"),
+                             'user')
         self.assertMethodNotAllowed(response)
 
 
-@patch('synnefo.plankton.backend.ImageBackend')
-class ImageMetadataAPITest(ComputeAPITest):
+@patch('synnefo.plankton.backend.PlanktonBackend')
+class ImageMetadataAPITest(BaseAPITest):
     def setUp(self):
         self.image = {'id': 42,
                  'name': 'image-1',
@@ -293,92 +270,94 @@ class ImageMetadataAPITest(ComputeAPITest):
 
     @assert_backend_closed
     def test_list_metadata(self, backend):
-        backend.return_value.get_image.return_value = self.image
-        response = self.myget('images/42/metadata', 'user')
+        backend().__enter__().get_image.return_value = self.image
+        response = self.get(join_urls(IMAGES_URL, '42/metadata'), 'user')
         self.assertSuccess(response)
         meta = json.loads(response.content)['metadata']
         self.assertEqual(meta, self.image['properties'])
 
     @assert_backend_closed
     def test_get_metadata(self, backend):
-        backend.return_value.get_image.return_value = self.image
-        response = self.myget('images/42/metadata/foo', 'user')
+        backend().__enter__().get_image.return_value = self.image
+        response = self.get(join_urls(IMAGES_URL, '42/metadata/foo'), 'user')
         self.assertSuccess(response)
         meta = json.loads(response.content)['meta']
         self.assertEqual(meta['foo'], 'bar')
 
     @assert_backend_closed
     def test_get_invalid_metadata(self, backend):
-        backend.return_value.get_image.return_value = self.image
-        response = self.myget('images/42/metadata/not_found', 'user')
+        backend().__enter__().get_image.return_value = self.image
+        response = self.get(join_urls(IMAGES_URL, '42/metadata/not_found'),
+                            'user')
         self.assertItemNotFound(response)
 
     def test_delete_metadata_item(self, backend):
-        backend.return_value.get_image.return_value = self.image
-        response = self.mydelete('images/42/metadata/foo', 'user')
+        backend().__enter__().get_image.return_value = self.image
+        response = self.delete(join_urls(IMAGES_URL, '42/metadata/foo'),
+                               'user')
         self.assertEqual(response.status_code, 204)
-        backend.return_value.update_metadata.assert_called_once_with('42', {'properties': {'foo2':
-                                                    'bar2'}})
+        backend().__enter__().update_metadata\
+               .assert_called_once_with('42', {'properties': {'foo2': 'bar2'}})
 
     @assert_backend_closed
     def test_create_metadata_item(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'meta': {'foo3': 'bar3'}}
-        response = self.myput('images/42/metadata/foo3', 'user',
-                              json.dumps(request), 'json')
+        response = self.put(join_urls(IMAGES_URL, '42/metadata/foo3'), 'user',
+                            json.dumps(request), 'json')
         self.assertEqual(response.status_code, 201)
-        backend.return_value.update_metadata.assert_called_once_with('42',
+        backend().__enter__().update_metadata.assert_called_once_with('42',
                 {'properties':
                     {'foo': 'bar', 'foo2': 'bar2', 'foo3': 'bar3'}})
 
     @assert_backend_closed
     def test_create_metadata_malformed_1(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'met': {'foo3': 'bar3'}}
-        response = self.myput('images/42/metadata/foo3', 'user',
-                              json.dumps(request), 'json')
+        response = self.put(join_urls(IMAGES_URL, '42/metadata/foo3'), 'user',
+                            json.dumps(request), 'json')
         self.assertBadRequest(response)
 
     @assert_backend_closed
     def test_create_metadata_malformed_2(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'metadata': [('foo3', 'bar3')]}
-        response = self.myput('images/42/metadata/foo3', 'user',
-                              json.dumps(request), 'json')
+        response = self.put(join_urls(IMAGES_URL, '42/metadata/foo3'), 'user',
+                            json.dumps(request), 'json')
         self.assertBadRequest(response)
 
     @assert_backend_closed
     def test_create_metadata_malformed_3(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'met': {'foo3': 'bar3', 'foo4': 'bar4'}}
-        response = self.myput('images/42/metadata/foo3', 'user',
-                                json.dumps(request), 'json')
+        response = self.put(join_urls(IMAGES_URL, '42/metadata/foo3'), 'user',
+                            json.dumps(request), 'json')
         self.assertBadRequest(response)
 
     @assert_backend_closed
     def test_create_metadata_malformed_4(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'met': {'foo3': 'bar3'}}
-        response = self.myput('images/42/metadata/foo4', 'user',
-                              json.dumps(request), 'json')
+        response = self.put(join_urls(IMAGES_URL, '42/metadata/foo4'), 'user',
+                            json.dumps(request), 'json')
         self.assertBadRequest(response)
 
     @assert_backend_closed
     def test_update_metadata_item(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'metadata': {'foo': 'bar_new', 'foo4': 'bar4'}}
-        response = self.mypost('images/42/metadata', 'user',
-                               json.dumps(request), 'json')
+        response = self.post(join_urls(IMAGES_URL, '42/metadata'), 'user',
+                             json.dumps(request), 'json')
         self.assertEqual(response.status_code, 201)
-        backend.return_value.update_metadata.assert_called_once_with('42',
+        backend().__enter__().update_metadata.assert_called_once_with('42',
                 {'properties':
                     {'foo': 'bar_new', 'foo2': 'bar2', 'foo4': 'bar4'}
                 })
 
     @assert_backend_closed
     def test_update_metadata_malformed(self, backend):
-        backend.return_value.get_image.return_value = self.image
+        backend().__enter__().get_image.return_value = self.image
         request = {'meta': {'foo': 'bar_new', 'foo4': 'bar4'}}
-        response = self.mypost('images/42/metadata', 'user',
-                               json.dumps(request), 'json')
+        response = self.post(join_urls(IMAGES_URL, '42/metadata'), 'user',
+                             json.dumps(request), 'json')
         self.assertBadRequest(response)
diff --git a/snf-cyclades-app/synnefo/api/util.py b/snf-cyclades-app/synnefo/api/util.py
index 59e67db..31c1d31 100644
--- a/snf-cyclades-app/synnefo/api/util.py
+++ b/snf-cyclades-app/synnefo/api/util.py
@@ -51,7 +51,7 @@ from synnefo.db.models import (Flavor, VirtualMachine, VirtualMachineMetadata,
                                Network, NetworkInterface, SecurityGroup,
                                BridgePoolTable, MacPrefixPoolTable, IPAddress,
                                IPPoolTable)
-from synnefo.plankton.utils import image_backend
+from synnefo.plankton.backend import PlanktonBackend
 
 from synnefo.cyclades_settings import cyclades_services, BASE_HOST
 from synnefo.lib.services import get_service_path
@@ -163,7 +163,7 @@ def get_vm_meta(vm, key):
 def get_image(image_id, user_id):
     """Return an Image instance or raise ItemNotFound."""
 
-    with image_backend(user_id) as backend:
+    with PlanktonBackend(user_id) as backend:
         return backend.get_image(image_id)
 
 
diff --git a/snf-cyclades-app/synnefo/plankton/backend.py b/snf-cyclades-app/synnefo/plankton/backend.py
index fba61e6..aaf2a3b 100644
--- a/snf-cyclades-app/synnefo/plankton/backend.py
+++ b/snf-cyclades-app/synnefo/plankton/backend.py
@@ -1,4 +1,4 @@
-# Copyright 2011-2013 GRNET S.A. All rights reserved.
+# Copyright 2011-2014 GRNET S.A. All rights reserved.
 
 #
 # Redistribution and use in source and binary forms, with or
@@ -52,19 +52,22 @@ The Plankton attributes are the following:
 """
 
 import json
-import warnings
 import logging
 import os
 
 from time import time, gmtime, strftime
 from functools import wraps
 from operator import itemgetter
+from collections import namedtuple
 
 from django.conf import settings
 from django.utils import importlib
-from pithos.backends.base import NotAllowedError, VersionNotExists
+from pithos.backends.base import NotAllowedError, VersionNotExists, QuotaError
 from synnefo.util.text import uenc
+from copy import deepcopy
+from snf_django.lib.api import faults
 
+Location = namedtuple("ObjectLocation", ["account", "container", "path"])
 
 logger = logging.getLogger(__name__)
 
@@ -94,339 +97,320 @@ def get_pithos_backend():
     return _pithos_backend_pool.pool_get()
 
 
-def create_url(account, container, name):
-    assert "/" not in account, "Invalid account"
-    assert "/" not in container, "Invalid container"
-    return "pithos://%s/%s/%s" % (account, container, name)
-
-
-def split_url(url):
-    """Returns (accout, container, object) from a url string"""
-    try:
-        assert(isinstance(url, basestring))
-        t = url.split('/', 4)
-        assert t[0] == "pithos:", "Invalid url"
-        assert len(t) == 5, "Invalid url"
-        return t[2:5]
-    except AssertionError:
-        raise InvalidLocation("Invalid location '%s" % url)
-
-
 def format_timestamp(t):
     return strftime('%Y-%m-%d %H:%M:%S', gmtime(t))
 
 
-def handle_backend_exceptions(func):
+def handle_pithos_backend(func):
     @wraps(func)
-    def wrapper(*args, **kwargs):
-        try:
-            return func(*args, **kwargs)
-        except NotAllowedError:
-            raise Forbidden
-        except NameError:
-            raise ImageNotFound
-        except VersionNotExists:
-            raise ImageNotFound
-    return wrapper
-
-
-def commit_on_success(func):
     def wrapper(self, *args, **kwargs):
         backend = self.backend
         backend.pre_exec()
+        commit = False
         try:
             ret = func(self, *args, **kwargs)
-        except:
-            backend.post_exec(False)
-            raise
+        except NotAllowedError:
+            raise faults.Forbidden
+        except (NameError, VersionNotExists):
+            raise faults.ItemNotFound
+        except (AssertionError, ValueError):
+            raise faults.BadRequest
+        except QuotaError:
+            raise faults.OverLimit
         else:
-            backend.post_exec(True)
+            commit = True
+        finally:
+            backend.post_exec(commit)
         return ret
     return wrapper
 
 
-class ImageBackend(object):
+class PlanktonBackend(object):
     """A wrapper arround the pithos backend to simplify image handling."""
 
     def __init__(self, user):
         self.user = user
-
-        original_filters = warnings.filters
-        warnings.simplefilter('ignore')         # Suppress SQLAlchemy warnings
         self.backend = get_pithos_backend()
-        warnings.filters = original_filters     # Restore warnings
 
     def close(self):
         """Close PithosBackend(return to pool)"""
         self.backend.close()
 
-    @handle_backend_exceptions
-    @commit_on_success
-    def get_image(self, image_uuid):
-        """Retrieve information about an image."""
-        image_url = self._get_image_url(image_uuid)
-        return self._get_image(image_url)
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.close()
+        self.backend = None
+        return False
+
+    @handle_pithos_backend
+    def get_image(self, uuid):
+        return self._get_image(uuid)
+
+    def _get_image(self, uuid):
+        location, metadata = self._get_raw_metadata(uuid)
+        permissions = self._get_raw_permissions(uuid, location)
+        return image_to_dict(location, metadata, permissions)
+
+    @handle_pithos_backend
+    def add_property(self, uuid, key, value):
+        location, _ = self._get_raw_metadata(uuid)
+        properties = self._prefix_properties({key: value})
+        self._update_metadata(uuid, location, properties, replace=False)
+
+    @handle_pithos_backend
+    def remove_property(self, uuid, key):
+        location, _ = self._get_raw_metadata(uuid)
+        # Use empty string to delete a property
+        properties = self._prefix_properties({key: ""})
+        self._update_metadata(uuid, location, properties, replace=False)
+
+    @handle_pithos_backend
+    def update_properties(self, uuid, properties):
+        location, _ = self._get_raw_metadata(uuid)
+        properties = self._prefix_properties(properties)
+        self._update_metadata(uuid, location, properties, replace=False)
+
+    @staticmethod
+    def _prefix_properties(properties):
+        """Add property prefix to properties."""
+        return dict([(PROPERTY_PREFIX + k, v) for k, v in properties.items()])
+
+    @staticmethod
+    def _unprefix_properties(properties):
+        """Remove property prefix from properties."""
+        return dict([(k.replace(PROPERTY_PREFIX, "", 1), v)
+                     for k, v in properties.items()])
+
+    @staticmethod
+    def _prefix_metadata(metadata):
+        """Add plankton prefix to metadata."""
+        return dict([(PLANKTON_PREFIX + k, v) for k, v in metadata.items()])
+
+    @staticmethod
+    def _unprefix_metadata(metadata):
+        """Remove plankton prefix from metadata."""
+        return dict([(k.replace(PLANKTON_PREFIX, "", 1), v)
+                     for k, v in metadata.items()])
+
+    @handle_pithos_backend
+    def update_metadata(self, uuid, metadata):
+        location, _ = self._get_raw_metadata(uuid)
 
-    def _get_image_url(self, image_uuid):
-        """Get the Pithos url that corresponds to an image UUID."""
-        account, container, name = self.backend.get_uuid(self.user, image_uuid)
-        return create_url(account, container, name)
+        is_public = metadata.pop("is_public", None)
+        if is_public is not None:
+            self._set_public(uuid, location, public=is_public)
 
-    def _get_image(self, image_url):
-        """Get information about an Image.
+        # Each property is stored as a separate prefixed metadata
+        meta = deepcopy(metadata)
+        properties = meta.pop("properties", {})
+        meta.update(self._prefix_properties(properties))
 
-        Get all available information about an Image.
-        """
-        account, container, name = split_url(image_url)
-        try:
-            meta = self._get_meta(image_url)
-            meta["deleted"] = ""
-        except NameError:
-            versions = self.backend.list_versions(self.user, account,
-                                                  container, name)
-            if not versions:
-                raise Exception("Image without versions %s" % image_url)
-            # Object was deleted, use the latest version
-            version, timestamp = versions[-1]
-            meta = self._get_meta(image_url, version)
-            meta["deleted"] = timestamp
-
-        # XXX: Check that an object is a plankton image! PithosBackend will
-        # return common metadata for an object, even if it has no metadata in
-        # plankton domain. All images must have a name, so we check if a file
-        # is an image by checking if they are having an image name.
-        if PLANKTON_PREFIX + 'name' not in meta:
-            raise ImageNotFound
-
-        permissions = self._get_permissions(image_url)
-        return image_to_dict(image_url, meta, permissions)
-
-    def _get_meta(self, image_url, version=None):
-        """Get object's metadata."""
-        account, container, name = split_url(image_url)
-        return self.backend.get_object_meta(self.user, account, container,
-                                            name, PLANKTON_DOMAIN, version)
-
-    def _update_meta(self, image_url, meta, replace=False):
-        """Update object's metadata."""
-        account, container, name = split_url(image_url)
-
-        prefixed = [(PLANKTON_PREFIX + uenc(k), uenc(v))
-                    for k, v in meta.items()
-                    if k in PLANKTON_META or k.startswith(PROPERTY_PREFIX)]
-        prefixed = dict(prefixed)
-
-        for k, v in prefixed.items():
+        self._update_metadata(uuid, location, metadata=meta, replace=False)
+
+        return self._get_image(uuid)
+
+    def _update_metadata(self, uuid, location, metadata, replace=False):
+        _prefixed_metadata = self._prefix_metadata(metadata)
+        prefixed = {}
+        for k, v in _prefixed_metadata.items():
+            # Encode to UTF-8
+            k, v = uenc(k), uenc(v)
+            # Check the length of key/value
             if len(k) > 128:
-                raise InvalidMetadata('Metadata keys should be less than %s '
-                                      'characters' % MAX_META_KEY_LENGTH)
+                raise faults.BadRequest('Metadata keys should be less than %s'
+                                        ' characters' % MAX_META_KEY_LENGTH)
             if len(v) > 256:
-                raise InvalidMetadata('Metadata values should be less than %s '
-                                      'characters.' % MAX_META_VALUE_LENGTH)
+                raise faults.BadRequest('Metadata values should be less than'
+                                        ' %scharacters.'
+                                        % MAX_META_VALUE_LENGTH)
+            prefixed[k] = v
 
-        self.backend.update_object_meta(self.user, account, container, name,
+        account, container, path = location
+        self.backend.update_object_meta(self.user, account, container, path,
                                         PLANKTON_DOMAIN, prefixed, replace)
-        logger.debug("User '%s' updated image '%s', meta: '%s'", self.user,
-                     image_url, prefixed)
-
-    def _get_permissions(self, image_url):
-        """Get object's permissions."""
-        account, container, name = split_url(image_url)
-        _a, path, permissions = \
-            self.backend.get_object_permissions(self.user, account, container,
-                                                name)
-
-        if path is None and permissions != {}:
-            logger.warning("Image '%s' got permissions '%s' from 'None' path.",
-                           image_url, permissions)
-            raise Exception("Database Inconsistency Error:"
-                            " Image '%s' got permissions from 'None' path." %
-                            image_url)
-
-        return permissions
-
-    def _update_permissions(self, image_url, permissions):
-        """Update object's permissions."""
-        account, container, name = split_url(image_url)
-        self.backend.update_object_permissions(self.user, account, container,
-                                               name, permissions)
-        logger.debug("User '%s' updated image '%s', permissions: '%s'",
-                     self.user, image_url, permissions)
+        logger.debug("User '%s' updated image '%s', metadata: '%s'", self.user,
+                     uuid, prefixed)
 
-    @handle_backend_exceptions
-    @commit_on_success
-    def unregister(self, image_uuid):
-        """Unregister an image.
+    def _get_raw_metadata(self, uuid, version=None, check_image=True):
+        """Get info and metadata in Plankton doamin for the Pithos object.
 
-        Unregister an image, by removing all metadata from the Pithos
-        file that exist in the PLANKTON_DOMAIN.
+        Return the location and the metadata of the Pithos object.
+        If 'check_image' is set, check that the Pithos object is a registered
+        Plankton Image.
 
         """
-        image_url = self._get_image_url(image_uuid)
-        self._get_image(image_url)  # Assert that it is an image
-        # Unregister the image by removing all metadata from domain
-        # 'PLANKTON_DOMAIN'
-        meta = {}
-        self._update_meta(image_url, meta, True)
-        logger.debug("User '%s' deleted image '%s'", self.user, image_url)
-
-    @handle_backend_exceptions
-    @commit_on_success
-    def add_user(self, image_uuid, add_user):
-        """Add a user as an image member.
-
-        Update read permissions of Pithos file, to include the specified user.
-
-        """
-        image_url = self._get_image_url(image_uuid)
-        self._get_image(image_url)  # Assert that it is an image
-        permissions = self._get_permissions(image_url)
+        # Convert uuid to location
+        account, container, path = self.backend.get_uuid(self.user, uuid)
+        try:
+            meta = self.backend.get_object_meta(self.user, account, container,
+                                                path, PLANKTON_DOMAIN, version)
+            meta["deleted"] = False
+        except NameError:
+            if version is not None:
+                raise
+            versions = self.backend.list_versions(self.user, account,
+                                                  container, path)
+            assert(versions), ("Object without versions: %s/%s/%s" %
+                               (account, container, path))
+            # Object was deleted, use the latest version
+            version, timestamp = versions[-1]
+            meta = self.backend.get_object_meta(self.user, account, container,
+                                                path, PLANKTON_DOMAIN, version)
+            meta["deleted"] = True
+
+        if check_image and PLANKTON_PREFIX + "name" not in meta:
+            # Check that object is an image by checking if it has an Image name
+            # in Plankton metadata
+            raise faults.ItemNotFound("Image '%s' does not exist." % uuid)
+
+        return Location(account, container, path), meta
+
+    # Users and Permissions
+    @handle_pithos_backend
+    def add_user(self, uuid, user):
+        assert(isinstance(user, basestring))
+        location, _ = self._get_raw_metadata(uuid)
+        permissions = self._get_raw_permissions(uuid, location)
         read = set(permissions.get("read", []))
-        assert(isinstance(add_user, (str, unicode)))
-        read.add(add_user)
-        permissions["read"] = list(read)
-        self._update_permissions(image_url, permissions)
+        if not user in read:
+            read.add(user)
+            permissions["read"] = list(read)
+            self._update_permissions(uuid, location, permissions)
 
-    @handle_backend_exceptions
-    @commit_on_success
-    def remove_user(self, image_uuid, remove_user):
-        """Remove the user from image members.
+    @handle_pithos_backend
+    def remove_user(self, uuid, user):
+        assert(isinstance(user, basestring))
+        location, _ = self._get_raw_metadata(uuid)
+        permissions = self._get_raw_permissions(uuid, location)
+        read = set(permissions.get("read", []))
+        if user in read:
+            read.remove(user)
+            permissions["read"] = list(read)
+            self._update_permissions(uuid, location, permissions)
 
-        Remove the specified user from the read permissions of the Pithos file.
+    @handle_pithos_backend
+    def replace_users(self, uuid, users):
+        assert(isinstance(users, list))
+        location, _ = self._get_raw_metadata(uuid)
+        permissions = self._get_raw_permissions(uuid, location)
+        read = set(permissions.get("read", []))
+        if "*" in read:  # Retain public permissions
+            users.append("*")
+        permissions["read"] = list(users)
+        self._update_permissions(uuid, location, permissions)
+
+    @handle_pithos_backend
+    def list_users(self, uuid):
+        location, _ = self._get_raw_metadata(uuid)
+        permissions = self._get_raw_permissions(uuid, location)
+        return [user for user in permissions.get('read', []) if user != '*']
 
-        """
-        image_url = self._get_image_url(image_uuid)
-        self._get_image(image_url)  # Assert that it is an image
-        permissions = self._get_permissions(image_url)
+    def _set_public(self, uuid, location, public):
+        permissions = self._get_raw_permissions(uuid, location)
+        assert(isinstance(public, bool))
         read = set(permissions.get("read", []))
-        assert(isinstance(remove_user, (str, unicode)))
-        try:
-            read.remove(remove_user)
-        except ValueError:
-            return  # TODO: User did not have access
+        if public and "*" not in read:
+            read.add("*")
+        elif not public and "*" in read:
+            read.discard("*")
         permissions["read"] = list(read)
-        self._update_permissions(image_url, permissions)
-
-    @handle_backend_exceptions
-    @commit_on_success
-    def replace_users(self, image_uuid, replace_users):
-        """Replace image members.
-
-        Replace the read permissions of the Pithos files with the specified
-        users. If image is specified as public, we must preserve * permission.
-
-        """
-        image_url = self._get_image_url(image_uuid)
-        image = self._get_image(image_url)
-        permissions = self._get_permissions(image_url)
-        assert(isinstance(replace_users, list))
-        permissions["read"] = replace_users
-        if image.get("is_public", False):
-            permissions["read"].append("*")
-        self._update_permissions(image_url, permissions)
-
-    @handle_backend_exceptions
-    @commit_on_success
-    def list_users(self, image_uuid):
-        """List the image members.
-
-        List the image members, by listing all users that have read permission
-        to the corresponding Pithos file.
-
-        """
-        image_url = self._get_image_url(image_uuid)
-        self._get_image(image_url)  # Assert that it is an image
-        permissions = self._get_permissions(image_url)
-        return [user for user in permissions.get('read', []) if user != '*']
+        self._update_permissions(uuid, location, permissions)
+        return permissions
 
-    @handle_backend_exceptions
-    @commit_on_success
-    def update_metadata(self, image_uuid, metadata):
-        """Update Image metadata."""
-        image_url = self._get_image_url(image_uuid)
-        self._get_image(image_url)  # Assert that it is an image
+    def _get_raw_permissions(self, uuid, location):
+        account, container, path = location
+        _a, path, permissions = \
+            self.backend.get_object_permissions(self.user, account, container,
+                                                path)
 
-        # 'is_public' metadata is translated in proper file permissions
-        is_public = metadata.pop("is_public", None)
-        if is_public is not None:
-            permissions = self._get_permissions(image_url)
-            read = set(permissions.get("read", []))
-            if is_public:
-                read.add("*")
-            else:
-                read.discard("*")
-            permissions["read"] = list(read)
-            self._update_permissions(image_url, permissions)
+        if path is None and permissions != {}:
+            raise Exception("Database Inconsistency Error:"
+                            " Image '%s' got permissions from 'None' path." %
+                            uuid)
 
-        # Extract the properties dictionary from metadata, and store each
-        # property as a separeted, prefixed metadata
-        properties = metadata.pop("properties", {})
-        meta = dict([(PROPERTY_PREFIX + k, v) for k, v in properties.items()])
-        # Also add the following metadata
-        meta.update(**metadata)
+        return permissions
 
-        self._update_meta(image_url, meta)
-        image_url = self._get_image_url(image_uuid)
-        return self._get_image(image_url)
+    def _update_permissions(self, uuid, location, permissions):
+        account, container, path = location
+        self.backend.update_object_permissions(self.user, account, container,
+                                               path, permissions)
+        logger.debug("User '%s' updated image '%s' permissions: '%s'",
+                     self.user, uuid, permissions)
 
-    @handle_backend_exceptions
-    @commit_on_success
+    @handle_pithos_backend
     def register(self, name, image_url, metadata):
         # Validate that metadata are allowed
         if "id" in metadata:
-            raise ValueError("Passing an ID is not supported")
+            raise faults.BadRequest("Passing an ID is not supported")
         store = metadata.pop("store", "pithos")
         if store != "pithos":
-            raise ValueError("Invalid store '%s'. Only 'pithos' store is"
-                             "supported" % store)
+            raise faults.BadRequest("Invalid store '%s'. Only 'pithos' store"
+                                    " is supported" % store)
         disk_format = metadata.setdefault("disk_format",
                                           settings.DEFAULT_DISK_FORMAT)
         if disk_format not in settings.ALLOWED_DISK_FORMATS:
-            raise ValueError("Invalid disk format '%s'" % disk_format)
+            raise faults.BadRequest("Invalid disk format '%s'" % disk_format)
         container_format =\
             metadata.setdefault("container_format",
                                 settings.DEFAULT_CONTAINER_FORMAT)
         if container_format not in settings.ALLOWED_CONTAINER_FORMATS:
-            raise ValueError("Invalid container format '%s'" %
-                             container_format)
-
-        # Validate that 'size' and 'checksum' are valid
-        account, container, object = split_url(image_url)
+            raise faults.BadRequest("Invalid container format '%s'" %
+                                    container_format)
 
-        meta = self._get_meta(image_url)
+        account, container, path = split_url(image_url)
+        location = Location(account, container, path)
+        meta = self.backend.get_object_meta(self.user, account, container,
+                                            path, PLANKTON_DOMAIN, None)
+        uuid = meta["uuid"]
 
-        size = int(metadata.pop('size', meta['bytes']))
-        if size != meta['bytes']:
-            raise ValueError("Invalid size")
+        # Validate that 'size' and 'checksum'
+        size = metadata.pop('size', int(meta['bytes']))
+        if not isinstance(size, int) or int(size) != int(meta["bytes"]):
+            raise faults.BadRequest("Invalid 'size' field")
 
         checksum = metadata.pop('checksum', meta['hash'])
-        if checksum != meta['hash']:
-            raise ValueError("Invalid checksum")
+        if not isinstance(checksum, basestring) or checksum != meta['hash']:
+            raise faults.BadRequest("Invalid checksum field")
+
+        users = [self.user]
+        public = metadata.pop("is_public", False)
+        if not isinstance(public, bool):
+            raise faults.BadRequest("Invalid value for 'is_public' metadata")
+        if public:
+            users.append("*")
+        permissions = {'read': users}
+        self._update_permissions(uuid, location, permissions)
+
+        # Each property is stored as a separate prefixed metadata
+        meta = deepcopy(metadata)
+        properties = meta.pop("properties", {})
+        meta.update(self._prefix_properties(properties))
+        # Add extra metadata
+        meta["name"] = name
+        meta["status"] = "AVAILABLE"
+        meta['created_at'] = str(time())
+        #meta["is_snapshot"] = False
+        self._update_metadata(uuid, location, metadata=meta, replace=False)
+
+        logger.debug("User '%s' registered image '%s'('%s')", self.user,
+                     uuid, location)
+        return self._get_image(uuid)
+
+    @handle_pithos_backend
+    def unregister(self, uuid):
+        """Unregister an Image.
+
+        Unregister an Image by removing all the metadata in the Plankton
+        domain. The Pithos file is not deleted.
 
-        # Fix permissions
-        is_public = metadata.pop('is_public', False)
-        if is_public:
-            permissions = {'read': ['*']}
-        else:
-            permissions = {'read': [self.user]}
-
-        # Extract the properties dictionary from metadata, and store each
-        # property as a separeted, prefixed metadata
-        properties = metadata.pop("properties", {})
-        meta = dict([(PROPERTY_PREFIX + k, v) for k, v in properties.items()])
-        # Add creation(register) timestamp as a metadata, to avoid extra
-        # queries when retrieving the list of images.
-        meta['created_at'] = time()
-        # Update rest metadata
-        meta.update(name=name, status='available', **metadata)
-
-        # Do the actualy update in the Pithos backend
-        self._update_meta(image_url, meta)
-        self._update_permissions(image_url, permissions)
-        logger.debug("User '%s' created image '%s'('%s')", self.user,
-                     image_url, name)
-        return self._get_image(image_url)
+        """
+        location, _ = self._get_raw_metadata(uuid)
+        self._update_metadata(uuid, location, metadata={}, replace=True)
+        logger.debug("User '%s' unregistered image '%s'", self.user, uuid)
 
+    # List functions
     def _list_images(self, user=None, filters=None, params=None):
         filters = filters or {}
 
@@ -445,88 +429,113 @@ class ImageBackend(object):
                                                   user=user)
 
         images = []
-        for (location, meta, permissions) in _images:
-            image_url = "pithos://" + location
-            meta["modified"] = meta["version_timestamp"]
-            images.append(image_to_dict(image_url, meta, permissions))
+        for (location, metadata, permissions) in _images:
+            location = Location(*location.split("/", 2))
+            images.append(image_to_dict(location, metadata, permissions))
 
         if params is None:
             params = {}
+
         key = itemgetter(params.get('sort_key', 'created_at'))
         reverse = params.get('sort_dir', 'desc') == 'desc'
         images.sort(key=key, reverse=reverse)
         return images
 
-    @commit_on_success
+    @handle_pithos_backend
     def list_images(self, filters=None, params=None):
         return self._list_images(user=self.user, filters=filters,
                                  params=params)
 
-    @commit_on_success
+    @handle_pithos_backend
     def list_shared_images(self, member, filters=None, params=None):
         images = self._list_images(user=self.user, filters=filters,
                                    params=params)
         is_shared = lambda img: not img["is_public"] and img["owner"] == member
         return filter(is_shared, images)
 
-    @commit_on_success
+    @handle_pithos_backend
     def list_public_images(self, filters=None, params=None):
         images = self._list_images(user=None, filters=filters, params=params)
         return filter(lambda img: img["is_public"], images)
 
+    # # Snapshots
+    # def list_snapshots(self, user=None):
+    #     _snapshots = self.list_images()
+    #     return [s for s in _snapshots if s["is_snapshot"]]
+
+    # @handle_pithos_backend
+    # def get_snapshot(self, user, snapshot_uuid):
+    #     snap = self._get_image(snapshot_uuid)
+    #     if snap.get("is_snapshot", False) is False:
+    #         raise faults.ItemNotFound("Snapshots '%s' does not exist" %
+    #                                   snapshot_uuid)
+    #     return snap
+
+    # @handle_pithos_backend
+    # def delete_snapshot(self, snapshot_uuid):
+    #     self.backend.delete_object_for_uuid(self.user, snapshot_uuid)
+
+    # @handle_pithos_backend
+    # def update_status(self, image_uuid, status):
+    #     """Update status of snapshot"""
+    #     location, _ = self._get_raw_metadata(image_uuid)
+    #     properties = {"status": status.upper()}
+    #     self._update_metadata(image_uuid, location, properties,
+    #     replace=False)
+    #     return self._get_image(image_uuid)
 
-class ImageBackendError(Exception):
-    pass
 
-
-class ImageNotFound(ImageBackendError):
-    pass
-
-
-class Forbidden(ImageBackendError):
-    pass
-
-
-class InvalidMetadata(ImageBackendError):
-    pass
+def create_url(account, container, name):
+    """Create a Pithos URL from the object info"""
+    assert "/" not in account, "Invalid account"
+    assert "/" not in container, "Invalid container"
+    return "pithos://%s/%s/%s" % (account, container, name)
 
 
-class InvalidLocation(ImageBackendError):
-    pass
+def split_url(url):
+    """Get object info from the Pithos URL"""
+    assert(isinstance(url, basestring))
+    t = url.split('/', 4)
+    assert t[0] == "pithos:", "Invalid url"
+    assert len(t) == 5, "Invalid url"
+    return t[2:5]
 
 
-def image_to_dict(image_url, meta, permissions):
+def image_to_dict(location, metadata, permissions):
     """Render an image to a dictionary"""
-    account, container, name = split_url(image_url)
+    account, container, name = location
 
     image = {}
-    if PLANKTON_PREFIX + 'name' not in meta:
-        logger.warning("Image without Plankton name!! url %s meta %s",
-                       image_url, meta)
-        image[PLANKTON_PREFIX + "name"] = ""
-
-    image["id"] = meta["uuid"]
-    image["location"] = image_url
-    image["checksum"] = meta["hash"]
-    created = meta.get("created_at", meta["modified"])
-    image["created_at"] = format_timestamp(created)
-    deleted = meta.get("deleted", None)
-    image["deleted_at"] = format_timestamp(deleted) if deleted else ""
-    image["updated_at"] = format_timestamp(meta["modified"])
-    image["size"] = meta["bytes"]
-    image["store"] = "pithos"
+    image["id"] = metadata["uuid"]
+    image["mapfile"] = metadata["hash"]
+    image["checksum"] = metadata["hash"]
+    image["location"] = create_url(account, container, name)
+    image["size"] = metadata["bytes"]
     image['owner'] = account
-
+    image["store"] = u"pithos"
+    #image["is_snapshot"] = metadata.pop(PLANKTON_PREFIX + "is_snapshot",
+    #False)
     # Permissions
-    image["is_public"] = "*" in permissions.get('read', [])
+    users = list(permissions.get("read", []))
+    image["is_public"] = "*" in users
+    image["users"] = [u for u in users if u != "*"]
+    # Timestamps
+    updated_at = metadata["version_timestamp"]
+    created_at = metadata.get("created_at", updated_at)
+    image["created_at"] = format_timestamp(created_at)
+    image["updated_at"] = format_timestamp(updated_at)
+    if metadata.get("deleted", False):
+        image["deleted_at"] = image["updated_at"]
+    else:
+        image["deleted_at"] = ""
 
     properties = {}
-    for key, val in meta.items():
+    for key, val in metadata.items():
         # Get plankton properties
         if key.startswith(PLANKTON_PREFIX):
             # Remove plankton prefix
             key = key.replace(PLANKTON_PREFIX, "")
-            # Keep only those in plankton meta
+            # Keep only those in plankton metadata
             if key in PLANKTON_META:
                 if key != "created_at":
                     # created timestamp is return in 'created_at' field
@@ -583,7 +592,7 @@ def get_backend():
     backend_module = getattr(settings, 'PLANKTON_BACKEND_MODULE', None)
     if not backend_module:
         # no setting set
-        return ImageBackend
+        return PlanktonBackend
 
     parts = backend_module.split(".")
     module = ".".join(parts[:-1])
diff --git a/snf-cyclades-app/synnefo/plankton/management/commands/image-list.py b/snf-cyclades-app/synnefo/plankton/management/commands/image-list.py
index d845d05..01ea0f8 100644
--- a/snf-cyclades-app/synnefo/plankton/management/commands/image-list.py
+++ b/snf-cyclades-app/synnefo/plankton/management/commands/image-list.py
@@ -32,7 +32,7 @@ from django.core.management.base import BaseCommand
 from optparse import make_option
 
 from snf_django.management.utils import pprint_table
-from synnefo.plankton.utils import image_backend
+from synnefo.plankton.backend import PlanktonBackend
 
 
 class Command(BaseCommand):
@@ -50,7 +50,7 @@ class Command(BaseCommand):
     def handle(self, **options):
         user = options['userid']
 
-        with image_backend(user) as backend:
+        with PlanktonBackend(user) as backend:
             images = backend._list_images(user)
             images.sort(key=lambda x: x['created_at'], reverse=True)
 
diff --git a/snf-cyclades-app/synnefo/plankton/management/commands/image-show.py b/snf-cyclades-app/synnefo/plankton/management/commands/image-show.py
index d14734f..513dc10 100644
--- a/snf-cyclades-app/synnefo/plankton/management/commands/image-show.py
+++ b/snf-cyclades-app/synnefo/plankton/management/commands/image-show.py
@@ -1,4 +1,4 @@
-# Copyright 2012 GRNET S.A. All rights reserved.
+# Copyright 2012-2014 GRNET S.A. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
@@ -30,7 +30,7 @@
 
 from django.core.management.base import BaseCommand, CommandError
 
-from synnefo.plankton.utils import image_backend
+from synnefo.plankton.backend import PlanktonBackend
 from snf_django.management import utils
 
 
@@ -44,11 +44,7 @@ class Command(BaseCommand):
             raise CommandError("Please provide an image ID")
         image_id = args[0]
 
-        with image_backend(None) as backend:
-            images = backend._list_images(None)
-            try:
-                image = filter(lambda x: x["id"] == image_id, images)[0]
-            except IndexError:
-                raise CommandError("Image not found. Use snf-manage image-list"
-                                   " to get the list of all images.")
-        utils.pprint_table(out=self.stdout, table=[image.values()], headers=image.keys(), vertical=True)
+        with PlanktonBackend(None) as backend:
+            image = backend.get_image(image_id)
+        utils.pprint_table(out=self.stdout, table=[image.values()],
+                           headers=image.keys(), vertical=True)
diff --git a/snf-cyclades-app/synnefo/plankton/tests.py b/snf-cyclades-app/synnefo/plankton/tests.py
index a90370a..b2fb6c3 100644
--- a/snf-cyclades-app/synnefo/plankton/tests.py
+++ b/snf-cyclades-app/synnefo/plankton/tests.py
@@ -1,4 +1,4 @@
-# Copyright 2012 GRNET S.A. All rights reserved.
+# Copyright 2012-2014 GRNET S.A. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
@@ -36,110 +36,15 @@ import json
 from mock import patch
 from functools import wraps
 from copy import deepcopy
+from decimal import Decimal
 from snf_django.utils.testing import BaseAPITest
 from synnefo.cyclades_settings import cyclades_services
 from synnefo.lib.services import get_service_path
 from synnefo.lib import join_urls
 
-
-class PlanktonAPITest(BaseAPITest):
-    def setUp(self, *args, **kwargs):
-        super(PlanktonAPITest, self).setUp(*args, **kwargs)
-        self.api_path = get_service_path(cyclades_services, 'image',
-                                             version='v1.0')
-    def myget(self, path, *args, **kwargs):
-        path = join_urls(self.api_path, path)
-        return self.get(path, *args, **kwargs)
-
-    def myput(self, path, *args, **kwargs):
-        path = join_urls(self.api_path, path)
-        return self.put(path, *args, **kwargs)
-
-    def mypost(self, path, *args, **kwargs):
-        path = join_urls(self.api_path, path)
-        return self.post(path, *args, **kwargs)
-
-    def mydelete(self, path, *args, **kwargs):
-        path = join_urls(self.api_path, path)
-        return self.delete(path, *args, **kwargs)
-
-
-FILTERS = ('name', 'container_format', 'disk_format', 'status', 'size_min',
-           'size_max')
-PARAMS = ('sort_key', 'sort_dir')
-SORT_KEY_OPTIONS = ('id', 'name', 'status', 'size', 'disk_format',
-                    'container_format', 'created_at', 'updated_at')
-SORT_DIR_OPTIONS = ('asc', 'desc')
-LIST_FIELDS = ('status', 'name', 'disk_format', 'container_format', 'size',
-               'id')
-DETAIL_FIELDS = ('name', 'disk_format', 'container_format', 'size', 'checksum',
-                 'location', 'created_at', 'updated_at', 'deleted_at',
-                 'status', 'is_public', 'owner', 'properties', 'id')
-ADD_FIELDS = ('name', 'id', 'store', 'disk_format', 'container_format', 'size',
-              'checksum', 'is_public', 'owner', 'properties', 'location')
-UPDATE_FIELDS = ('name', 'disk_format', 'container_format', 'is_public',
-                 'owner', 'properties', 'status')
-
-
-DummyImages = {
- '0786a349-9725-48ec-8b86-8598eefc4043':
- {'checksum': u'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
-  u'container_format': u'bare',
-  'created_at': '2012-12-04 09:50:20',
-  'deleted_at': '',
-  u'disk_format': u'diskdump',
-  'id': u'0786a349-9725-48ec-8b86-8598eefc4043',
-  'is_public': True,
-  'location': u'pithos://foo@example.com/container/foo3',
-  u'name': u'dummyname',
-  'owner': u'foo@example.com',
-  'properties': {},
-  'size': 500L,
-  u'status': u'available',
-  'store': 'pithos',
-  'updated_at': '2012-12-04 09:50:54'},
-
- 'd8aa85b8-410b-4550-953d-6797572534e6':
- {'checksum': u'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
-  u'container_format': u'bare',
-  'created_at': '2012-11-26 11:56:42',
-  'deleted_at': '',
-  u'disk_format': u'diskdump',
-  'id': u'd8aa85b8-410b-4550-953d-6797572534e6',
-  'is_public': False,
-  'location': u'pithos://foo@example.com/container/private',
-  u'name': u'dummyname2',
-  'owner': u'foo@example.com',
-  'properties': {},
-  'size': 10000L,
-  u'status': u'available',
-  'store': 'pithos',
-  'updated_at': '2012-11-26 11:57:09'},
-
- '264fb9ac-2458-421c-b460-6a765a92825c':
- {'checksum': u'0c6d0586744781218672fff2d7ed94cc32efb02a6a8eb589a0628f0e22bd5a7f',
-  u'container_format': u'bare',
-  'created_at': '2012-11-26 11:52:54',
-  'deleted_at': '',
-  u'disk_format': u'diskdump',
-  'id': u'264fb9ac-2458-421c-b460-6a765a92825c',
-  'is_public': True,
-  'location': u'pithos://foo@example.com/container/baz.diskdump',
-  u'name': u'"dummyname3"',
-  'owner': u'foo@example.com',
-  'properties': {u'description': u'Debian Squeeze Base System',
-                 u'gui': u'No GUI',
-                 u'kernel': u'2.6.32',
-                 u'os': u'debian',
-                 u'osfamily': u'linux',
-                 u'root_partition': u'1',
-                 u'size': u'451',
-                 u'sortorder': u'1',
-                 u'users': u'root'},
-  'size': 473772032L,
-  u'status': u'available',
-  'store': 'pithos',
-  'updated_at': '2012-11-26 11:55:40'}}
+PLANKTON_URL = get_service_path(cyclades_services, 'image',
+                                version='v1.0')
+IMAGES_URL = join_urls(PLANKTON_URL, "images/")
 
 
 def assert_backend_closed(func):
@@ -152,121 +57,255 @@ def assert_backend_closed(func):
     return wrapper
 
 
-@patch("synnefo.plankton.backend.ImageBackend")
-class PlanktonTest(PlanktonAPITest):
-    @assert_backend_closed
-    def test_list_images(self, backend):
-        backend.return_value.list_images.return_value =\
-                deepcopy(DummyImages).values()
-        response = self.myget("images/")
-        self.assertSuccess(response)
-        images = json.loads(response.content)
-        for api_image in images:
-            id = api_image['id']
-            pithos_image = dict([(key, val)\
-                                for key, val in DummyImages[id].items()\
-                                if key in LIST_FIELDS])
-            self.assertEqual(api_image, pithos_image)
-        backend.return_value\
-                .list_images.assert_called_once_with({}, {'sort_key': 'created_at',
-                                                   'sort_dir': 'desc'})
+@patch("synnefo.plankton.backend.get_pithos_backend")
+class PlanktonTest(BaseAPITest):
+    def test_register_image(self, backend):
+        required = {
+            "HTTP_X_IMAGE_META_NAME": u"TestImage\u2602",
+            "HTTP_X_IMAGE_META_LOCATION": "pithos://4321-4321/images/foo"}
+        # Check valid name
+        headers = deepcopy(required)
+        headers.pop("HTTP_X_IMAGE_META_NAME")
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("name" in response.content)
+        headers["HTTP_X_IMAGE_META_NAME"] = ""
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("name" in response.content)
+        # Check valid location
+        headers = deepcopy(required)
+        headers.pop("HTTP_X_IMAGE_META_LOCATION")
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("location" in response.content)
+        headers["HTTP_X_IMAGE_META_LOCATION"] = ""
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("location" in response.content)
+        headers["HTTP_X_IMAGE_META_LOCATION"] = "pitho://4321-4321/images/foo"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("location" in response.content)
+        headers["HTTP_X_IMAGE_META_LOCATION"] = "pithos://4321-4321/foo"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("location" in response.content)
+        # ID not supported
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_ID"] = "1234"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        headers = deepcopy(required)
+        # ID not supported
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_LOLO"] = "1234"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_STORE"] = "pitho"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("store " in response.content)
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_DISK_FORMAT"] = "diskdumpp"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("disk format" in response.content)
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_CONTAINER_FORMAT"] = "baree"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("container format" in response.content)
 
-    @assert_backend_closed
-    def test_list_images_detail(self, backend):
-        backend.return_value.list_images.return_value =\
-                deepcopy(DummyImages).values()
-        response = self.myget("images/detail")
-        self.assertSuccess(response)
-        images = json.loads(response.content)
-        for api_image in images:
-            id = api_image['id']
-            pithos_image = dict([(key, val)\
-                                for key, val in DummyImages[id].items()\
-                                if key in DETAIL_FIELDS])
-            self.assertEqual(api_image, pithos_image)
-        backend.return_value\
-                .list_images.assert_called_once_with({}, {'sort_key': 'created_at',
-                                                   'sort_dir': 'desc'})
+        backend().get_object_meta.return_value = {"uuid": "1234-1234-1234",
+                                                  "bytes": 42,
+                                                  "hash": "unique_hash"}
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_SIZE"] = "foo"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("size" in response.content)
+        headers["HTTP_X_IMAGE_META_SIZE"] = "43"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
+        self.assertTrue("size" in response.content)
+
+        headers["HTTP_X_IMAGE_META_SIZE"] = 42
+        headers["HTTP_X_IMAGE_META_CHECKSUM"] = "wrong_checksum"
+        response = self.post(IMAGES_URL, **headers)
+        self.assertBadRequest(response)
 
-    @assert_backend_closed
-    def test_list_images_filters(self, backend):
-        backend.return_value.list_images.return_value =\
-                deepcopy(DummyImages).values()
-        response = self.myget("images/?size_max=1000")
+        backend().get_uuid.return_value =\
+            ("4321-4321", "images", "foo")
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": []})
+        backend().get_object_meta.side_effect = \
+            [{"uuid": "1234-1234-1234",
+              "bytes": 42,
+              "hash": "unique_hash"},
+             {"uuid": "1234-1234-1234",
+              "bytes": 42,
+              "hash": "unique_hash",
+              'version_timestamp': Decimal('1392487853.863673'),
+              "plankton:name": u"TestImage\u2602",
+              "plankton:container_format": "bare",
+              "plankton:disk_format": "diskdump",
+              "plankton:status": u"AVAILABLE"}]
+        headers = deepcopy(required)
+        response = self.post(IMAGES_URL, **headers)
         self.assertSuccess(response)
-        backend.return_value\
-                .list_images.assert_called_once_with({'size_max': 1000},
-                                                     {'sort_key': 'created_at',
-                                                     'sort_dir': 'desc'})
+        self.assertEqual(response["x-image-meta-location"],
+                         "pithos://4321-4321/images/foo")
+        self.assertEqual(response["x-image-meta-id"], "1234-1234-1234")
+        self.assertEqual(response["x-image-meta-status"], "AVAILABLE")
+        self.assertEqual(response["x-image-meta-deleted-at"], "")
+        self.assertEqual(response["x-image-meta-is-public"], "False")
+        self.assertEqual(response["x-image-meta-owner"], "4321-4321")
+        self.assertEqual(response["x-image-meta-size"], "42")
+        self.assertEqual(response["x-image-meta-checksum"], "unique_hash")
+        self.assertEqual(response["x-image-meta-name"],
+                         u"TestImage\u2602".encode("utf-8"))
+        self.assertEqual(response["x-image-meta-container-format"], "bare")
+        self.assertEqual(response["x-image-meta-disk-format"], "diskdump")
+        self.assertEqual(response["x-image-meta-created-at"],
+                         "2014-02-15 18:10:53")
+        self.assertEqual(response["x-image-meta-updated-at"],
+                         "2014-02-15 18:10:53")
 
-    @assert_backend_closed
-    def test_list_images_filters_error_1(self, backend):
-        response = self.myget("images/?size_max=")
-        self.assertBadRequest(response)
+        # Extra headers,properties
+        backend().get_object_meta.side_effect = \
+            [{"uuid": "1234-1234-1234",
+              "bytes": 42,
+              "hash": "unique_hash"},
+             {"uuid": "1234-1234-1234",
+              "bytes": 42,
+              "hash": "unique_hash",
+              'version_timestamp': Decimal('1392487853.863673'),
+              "plankton:name": u"TestImage\u2602",
+              "plankton:container_format": "bare",
+              "plankton:disk_format": "diskdump",
+              "plankton:status": u"AVAILABLE"}]
+        headers = deepcopy(required)
+        headers["HTTP_X_IMAGE_META_IS_PUBLIC"] = True
+        headers["HTTP_X_IMAGE_META_PROPERTY_KEY1"] = "val1"
+        headers["HTTP_X_IMAGE_META_PROPERTY_KEY2"] = u"\u2601"
+        response = self.post(IMAGES_URL, **headers)
+        name, args, kwargs = backend().update_object_meta.mock_calls[-1]
+        metadata = args[5]
+        self.assertEqual(metadata["plankton:property:key1"], "val1")
+        self.assertEqual(metadata["plankton:property:key2"],
+                         u"\u2601".encode("utf-8"))
+        self.assertSuccess(response)
 
-    @assert_backend_closed
-    def test_list_images_filters_error_2(self, backend):
-        response = self.myget("images/?size_min=foo")
-        self.assertBadRequest(response)
+    def test_unregister_image(self, backend):
+        backend().get_uuid.return_value = ("img_owner", "images", "foo")
+        backend().get_object_meta.return_value = {"uuid": "img_uuid",
+                                                  "bytes": 42,
+                                                  "plankton:name": "test"}
+        response = self.delete(join_urls(IMAGES_URL, "img_uuid"))
+        self.assertEqual(response.status_code, 204)
+        backend().update_object_meta.assert_called_once_with(
+            "user", "img_owner", "images", "foo", "plankton", {}, True)
 
-    @assert_backend_closed
-    def test_update_image(self, backend):
-        db_image = DummyImages.values()[0]
-        response = self.myput("images/%s" % db_image['id'],
-                              json.dumps({}),
-                              'json', HTTP_X_IMAGE_META_OWNER='user2')
+    def test_users(self, backend):
+        """Test adding/removing and replacing image members"""
+        # Add user
+        backend.reset_mock()
+        backend().get_uuid.return_value = ("img_owner", "images", "foo")
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": []})
+        backend().get_object_meta.return_value = {"uuid": "img_uuid",
+                                                  "bytes": 42,
+                                                  "plankton:name": "test"}
+        response = self.put(join_urls(IMAGES_URL, "img_uuid/members/user1"),
+                            user="user1")
         self.assertSuccess(response)
-        backend.return_value.update_metadata.assert_called_once_with(db_image['id'],
-                                                                     {"owner": "user2"})
+        backend().update_object_permissions.assert_called_once_with(
+            "user1", "img_owner", "images", "foo", {"read": ["user1"]})
 
-    @assert_backend_closed
-    def test_add_image_member(self, backend):
-        image_id = DummyImages.values()[0]['id']
-        response = self.myput("images/%s/members/user3" % image_id,
-                              json.dumps({}), 'json')
+        # Remove user
+        backend().update_object_permissions.reset_mock()
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": ["user1"]})
+        response = self.delete(join_urls(IMAGES_URL, "img_uuid/members/user1"),
+                               user="user1")
         self.assertSuccess(response)
-        backend.return_value.add_user.assert_called_once_with(image_id,
-                                                             'user3')
+        backend().update_object_permissions.assert_called_once_with(
+            "user1", "img_owner", "images", "foo", {"read": []})
 
-    @assert_backend_closed
-    def test_remove_image_member(self, backend):
-        image_id = DummyImages.values()[0]['id']
-        response = self.mydelete("images/%s/members/user3" % image_id)
+        # Update users
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": ["user1", "user2", "user3"]})
+        backend().update_object_permissions.reset_mock()
+        response = self.put(join_urls(IMAGES_URL, "img_uuid/members"),
+                            params=json.dumps({"memberships":
+                                               [{"member_id": "foo1"},
+                                                {"member_id": "foo2"}]}),
+                            ctype="json",
+                            user="user1")
         self.assertSuccess(response)
-        backend.return_value.remove_user.assert_called_once_with(image_id,
-                                                                'user3')
+        backend().update_object_permissions.assert_called_once_with(
+            "user1", "img_owner", "images", "foo", {"read": ["foo1", "foo2"]})
 
-    @assert_backend_closed
-    def test_add_image(self, backend):
-        location = "pithos://uuid/container/name/"
-        response = self.mypost("images/",
-                               json.dumps({}),
-                               'json',
-                               HTTP_X_IMAGE_META_NAME='dummy_name',
-                               HTTP_X_IMAGE_META_OWNER='dummy_owner',
-                               HTTP_X_IMAGE_META_LOCATION=location)
+        # List users
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": ["user1", "user2", "user3"]})
+        response = self.get(join_urls(IMAGES_URL, "img_uuid/members"))
         self.assertSuccess(response)
-        backend.return_value.register.assert_called_once_with('dummy_name',
-                                                              location,
-                                                      {'owner': 'dummy_owner'})
+        res_members = [{"member_id": m, "can_share": False}
+                       for m in ["user1", "user2", "user3"]]
+        self.assertEqual(json.loads(response.content)["members"], res_members)
 
-    @assert_backend_closed
-    def test_get_image(self, backend):
-        response = self.myget("images/123")
-        self.assertEqual(response.status_code, 501)
+    def test_metadata(self, backend):
+        backend().get_uuid.return_value = ("img_owner", "images", "foo")
+        backend().get_object_meta.return_value = \
+            {"uuid": "img_uuid",
+             "bytes": 42,
+             "hash": "unique_hash",
+             'version_timestamp': Decimal('1392487853.863673'),
+             "plankton:name": u"TestImage\u2602",
+             "plankton:container_format": "bare",
+             "plankton:disk_format": "diskdump",
+             "plankton:status": u"AVAILABLE"}
+        backend().get_object_permissions.return_value = \
+            ("foo", "foo", {"read": ["*", "user1"]})
+        response = self.head(join_urls(IMAGES_URL, "img_uuid2"))
+        self.assertSuccess(response)
+        self.assertEqual(response["x-image-meta-location"],
+                         "pithos://img_owner/images/foo")
+        self.assertEqual(response["x-image-meta-id"], "img_uuid")
+        self.assertEqual(response["x-image-meta-status"], "AVAILABLE")
+        self.assertEqual(response["x-image-meta-deleted-at"], "")
+        self.assertEqual(response["x-image-meta-is-public"], "True")
+        self.assertEqual(response["x-image-meta-owner"], "img_owner")
+        self.assertEqual(response["x-image-meta-size"], "42")
+        self.assertEqual(response["x-image-meta-checksum"], "unique_hash")
+        self.assertEqual(response["x-image-meta-name"],
+                         u"TestImage\u2602".encode("utf-8"))
+        self.assertEqual(response["x-image-meta-container-format"], "bare")
+        self.assertEqual(response["x-image-meta-disk-format"], "diskdump")
+        self.assertEqual(response["x-image-meta-created-at"],
+                         "2014-02-15 18:10:53")
+        self.assertEqual(response["x-image-meta-updated-at"],
+                         "2014-02-15 18:10:53")
+        response = self.head(join_urls(IMAGES_URL, "img_uuid2"))
 
-    @assert_backend_closed
-    def test_delete_image(self, backend):
-        response = self.mydelete("images/123")
-        self.assertEqual(response.status_code, 204)
-        backend.return_value.unregister.assert_called_once_with('123')
-        backend.return_value._delete.assert_not_called()
+        headers = {"HTTP_X_IMAGE_META_IS_PUBLIC": False,
+                   "HTTP_X_IMAGE_META_PROPERTY_KEY1": "Val1"}
+        response = self.put(join_urls(IMAGES_URL, "img_uuid"), **headers)
+        self.assertSuccess(response)
+        backend().update_object_permissions.assert_called_once_with(
+            "user", "img_owner", "images", "foo", {"read": ["user1"]})
 
-    @assert_backend_closed
     def test_catch_wrong_api_paths(self, *args):
-        response = self.myget('nonexistent')
+        response = self.get(join_urls(PLANKTON_URL, 'nonexistent'))
         self.assertEqual(response.status_code, 400)
         try:
-            error = json.loads(response.content)
+            json.loads(response.content)
         except ValueError:
             self.assertTrue(False)
+
+    def test_list_images_filters_error_1(self, backend):
+        response = self.get(join_urls(IMAGES_URL, "?size_max="))
+        self.assertBadRequest(response)
diff --git a/snf-cyclades-app/synnefo/plankton/utils.py b/snf-cyclades-app/synnefo/plankton/utils.py
deleted file mode 100644
index 2e52f77..0000000
--- a/snf-cyclades-app/synnefo/plankton/utils.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2011-2013 GRNET S.A. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or
-# without modification, are permitted provided that the following
-# conditions are met:
-#
-#   1. Redistributions of source code must retain the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer.
-#
-#   2. Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials
-#      provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
-# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and
-# documentation are those of the authors and should not be
-# interpreted as representing official policies, either expressed
-# or implied, of GRNET S.A.
-
-from contextlib import contextmanager
-from synnefo.plankton import backend
-from snf_django.lib.api import faults
-
-
-@contextmanager
-def image_backend(user_id):
-    """Context manager for ImageBackend.
-
-    Context manager for using ImageBackend in API methods. Handles
-    opening and closing a connection to Pithos and converting backend
-    erros to cloud faults.
-
-    """
-    image_backend = backend.get_backend()(user_id)
-    try:
-        yield image_backend
-    except backend.Forbidden as e:
-        raise faults.Forbidden
-    except backend.ImageNotFound:
-        raise faults.ItemNotFound
-    except backend.InvalidMetadata as e:
-        raise faults.BadRequest(str(e))
-    finally:
-        image_backend.close()
diff --git a/snf-cyclades-app/synnefo/plankton/views.py b/snf-cyclades-app/synnefo/plankton/views.py
index 5d6a35a..bc65a6a 100644
--- a/snf-cyclades-app/synnefo/plankton/views.py
+++ b/snf-cyclades-app/synnefo/plankton/views.py
@@ -43,8 +43,8 @@ from django.http import HttpResponse
 from snf_django.lib import api
 from snf_django.lib.api import faults
 from synnefo.util.text import uenc
-from synnefo.plankton.utils import image_backend
-from synnefo.plankton.backend import split_url, InvalidLocation
+from synnefo.plankton.backend import PlanktonBackend
+from synnefo.plankton.backend import split_url
 
 
 FILTERS = ('name', 'container_format', 'disk_format', 'status', 'size_min',
@@ -109,10 +109,10 @@ def _get_image_headers(request):
     for key, val in request.META.items():
         if key.startswith(META_PROPERTY_PREFIX):
             name = normalize(key[META_PROPERTY_PREFIX_LEN:])
-            headers['properties'][unquote(name)] = unquote(val)
+            headers['properties'][unquote(name)] = unquote(uenc(val))
         elif key.startswith(META_PREFIX):
             name = normalize(key[META_PREFIX_LEN:])
-            headers[unquote(name)] = unquote(val)
+            headers[unquote(name)] = unquote(uenc(val))
 
     is_public = headers.get('is_public', None)
     if is_public is not None:
@@ -149,7 +149,7 @@ def add_image(request):
     if not set(params.keys()).issubset(set(ADD_FIELDS)):
         raise faults.BadRequest("Invalid parameters")
 
-    name = params.pop('name')
+    name = params.pop('name', None)
     if name is None:
         raise faults.BadRequest("Image 'name' parameter is required")
     elif len(uenc(name)) == 0:
@@ -160,13 +160,13 @@ def add_image(request):
 
     try:
         split_url(location)
-    except InvalidLocation:
+    except AssertionError:
         raise faults.BadRequest("Invalid location '%s'" % location)
 
     validate_fields(params)
 
     if location:
-        with image_backend(request.user_uniq) as backend:
+        with PlanktonBackend(request.user_uniq) as backend:
             image = backend.register(name, location, params)
     else:
         #f = StringIO(request.body)
@@ -193,7 +193,7 @@ def delete_image(request, image_id):
     """
     log.info("delete_image '%s'" % image_id)
     userid = request.user_uniq
-    with image_backend(userid) as backend:
+    with PlanktonBackend(userid) as backend:
         backend.unregister(image_id)
     log.info("User '%s' deleted image '%s'" % (userid, image_id))
     return HttpResponse(status=204)
@@ -211,7 +211,7 @@ def add_image_member(request, image_id, member):
     """
 
     log.debug('add_image_member %s %s', image_id, member)
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         backend.add_user(image_id, member)
     return HttpResponse(status=204)
 
@@ -250,7 +250,7 @@ def get_image_meta(request, image_id):
     3.4. Requesting Detailed Metadata on a Specific Image
     """
 
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         image = backend.get_image(image_id)
     return _create_image_response(image)
 
@@ -263,7 +263,7 @@ def list_image_members(request, image_id):
     3.7. Requesting Image Memberships
     """
 
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         users = backend.list_users(image_id)
 
     members = [{'member_id': u, 'can_share': False} for u in users]
@@ -313,7 +313,7 @@ def list_images(request, detail=False):
         except ValueError:
             raise faults.BadRequest("Malformed request.")
 
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         images = backend.list_images(filters, params)
 
     # Remove keys that should not be returned
@@ -342,10 +342,9 @@ def list_shared_images(request, member):
     log.debug('list_shared_images %s', member)
 
     images = []
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         for image in backend.list_shared_images(member=member):
-            image_id = image['id']
-            images.append({'image_id': image_id, 'can_share': False})
+            images.append({'image_id': image["id"], 'can_share': False})
 
     data = json.dumps({'shared_images': images}, indent=settings.DEBUG)
     return HttpResponse(data)
@@ -360,7 +359,7 @@ def remove_image_member(request, image_id, member):
     """
 
     log.debug('remove_image_member %s %s', image_id, member)
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         backend.remove_user(image_id, member)
     return HttpResponse(status=204)
 
@@ -386,7 +385,7 @@ def update_image(request, image_id):
 
     validate_fields(meta)
 
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         image = backend.update_metadata(image_id, meta)
     return _create_image_response(image)
 
@@ -411,7 +410,7 @@ def update_image_members(request, image_id):
     except (ValueError, KeyError, TypeError):
         return HttpResponse(status=400)
 
-    with image_backend(request.user_uniq) as backend:
+    with PlanktonBackend(request.user_uniq) as backend:
         backend.replace_users(image_id, members)
     return HttpResponse(status=204)
 
diff --git a/snf-django-lib/snf_django/utils/testing.py b/snf-django-lib/snf_django/utils/testing.py
index 7b0dc39..0709297 100644
--- a/snf-django-lib/snf_django/utils/testing.py
+++ b/snf-django-lib/snf_django/utils/testing.py
@@ -221,6 +221,12 @@ class BaseAPITest(TestCase):
                 response = self.client.get(url, *args, **kwargs)
         return response
 
+    def head(self, url, user='user', *args, **kwargs):
+        with astakos_user(user):
+            with mocked_quotaholder():
+                response = self.client.head(url, *args, **kwargs)
+        return response
+
     def delete(self, url, user='user'):
         with astakos_user(user):
             with mocked_quotaholder() as m:
