commit 1ecb12b5f5b0fa03a9e8a4e8d6d95f003a5f16c1
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Mon Apr 8 14:14:13 2013 +0300

    Rename snf-astakos-client to astakosclient

diff --git a/.gitignore b/.gitignore
index 97f0ae3..16a2489 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,5 @@ snf-quotaholder-app/quotaholder_django/version.py
 *.iml
 *.graffle
 snf-stats-app/synnefo_stats/version.py
-snf-astakos-client/astakosclient/version.py
+astakosclient/astakosclient/version.py
+snf-django-lib/snf_django/version.py
diff --git a/astakosclient/COPYRIGHT b/astakosclient/COPYRIGHT
new file mode 100644
index 0000000..245ccc1
--- /dev/null
+++ b/astakosclient/COPYRIGHT
@@ -0,0 +1,32 @@
+Copyright (C) 2012, 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.
diff --git a/astakosclient/Changelog b/astakosclient/Changelog
new file mode 100644
index 0000000..e69de29
diff --git a/astakosclient/MANIFEST.in b/astakosclient/MANIFEST.in
new file mode 100644
index 0000000..9a149cd
--- /dev/null
+++ b/astakosclient/MANIFEST.in
@@ -0,0 +1,2 @@
+include distribute_setup.py
+include README Changelog
diff --git a/astakosclient/README b/astakosclient/README
new file mode 100644
index 0000000..a3dc38a
--- /dev/null
+++ b/astakosclient/README
@@ -0,0 +1,11 @@
+astakos-client
+==============
+
+Python client for the Astakos, the Identity Management Service of
+synnefo(http://www.synnefo.org)
+
+Feedback
+========
+
+For any comments or suggestions for improvement, please contact
+the synnefo development team <synnefo-devel@googlegroups.com>
diff --git a/astakosclient/astakosclient/__init__.py b/astakosclient/astakosclient/__init__.py
new file mode 100644
index 0000000..a465947
--- /dev/null
+++ b/astakosclient/astakosclient/__init__.py
@@ -0,0 +1,317 @@
+# Copyright (C) 2012, 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.
+
+import logging
+import urlparse
+import urllib
+import hashlib
+from copy import copy
+
+import simplejson
+from astakosclient.utils import retry, scheme_to_class
+from astakosclient.errors import \
+    AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
+    NoUserName, NoUUID
+
+
+# --------------------------------------------------------------------
+# Astakos Client Class
+
+def get_token_from_cookie(request, cookie_name):
+    """Extract token from the cookie name provided
+
+    Cookie should be in the same form as astakos
+    service sets its cookie contents:
+        <user_uniq>|<user_token>
+
+    """
+    try:
+        cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None))
+        return cookie_content.split("|")[1]
+    except:
+        return None
+
+
+class AstakosClient():
+    """AstakosClient Class Implementation"""
+
+    # ----------------------------------
+    def __init__(self, astakos_url, retry=0,
+                 use_pool=False, pool_size=8, logger=None):
+        """Intialize AstakosClient Class
+
+        Keyword arguments:
+        astakos_url -- i.e https://accounts.example.com (string)
+        use_pool    -- use objpool for http requests (boolean)
+        retry       -- how many time to retry (integer)
+        logger      -- pass a different logger
+
+        """
+        if logger is None:
+            logging.basicConfig(
+                format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
+                datefmt='%Y-%m-%d %H:%M:%S',
+                level=logging.INFO)
+            logger = logging.getLogger("astakosclient")
+        logger.debug("Intialize AstakosClient: astakos_url = %s, "
+                     "use_pool = %s" % (astakos_url, use_pool))
+
+        if not astakos_url:
+            m = "Astakos url not given"
+            logger.error(m)
+            raise ValueError(m)
+
+        # Check for supported scheme
+        p = urlparse.urlparse(astakos_url)
+        conn_class = scheme_to_class(p.scheme, use_pool, pool_size)
+        if conn_class is None:
+            m = "Unsupported scheme: %s" % p.scheme
+            logger.error(m)
+            raise ValueError(m)
+
+        # Save astakos_url etc. in our class
+        self.retry = retry
+        self.logger = logger
+        self.netloc = p.netloc
+        self.scheme = p.scheme
+        self.conn_class = conn_class
+
+    # ----------------------------------
+    @retry
+    def _call_astakos(self, token, request_path,
+                      headers=None, body=None, method="GET"):
+        """Make the actual call to Astakos Service"""
+        hashed_token = hashlib.sha1()
+        hashed_token.update(token)
+        self.logger.debug(
+            "Make a %s request to %s using token %s "
+            "with headers %s and body %s"
+            % (method, request_path, hashed_token.hexdigest(), headers, body))
+
+        # Check Input
+        if not token:
+            m = "Token not given"
+            self.logger.error(m)
+            raise ValueError(m)
+        if headers is None:
+            headers = {}
+        if body is None:
+            body = {}
+        if request_path[0] != '/':
+            request_path = "/" + request_path
+
+        # Build request's header and body
+        kwargs = {}
+        kwargs['headers'] = copy(headers)
+        kwargs['headers']['X-Auth-Token'] = token
+        if body:
+            kwargs['body'] = copy(body)
+            kwargs['headers'].setdefault(
+                'content-type', 'application/octet-stream')
+        kwargs['headers'].setdefault('content-length',
+                                     len(body) if body else 0)
+
+        try:
+            # Get the connection object
+            with self.conn_class(self.netloc) as conn:
+                # Send request
+                (data, status) = \
+                    _do_request(conn, method, request_path, **kwargs)
+        except Exception as err:
+            self.logger.error("Failed to send request: %s" % repr(err))
+            raise AstakosClientException(str(err))
+
+        # Return
+        self.logger.debug("Request returned with status %s" % status)
+        if status == 400:
+            raise BadRequest(data)
+        elif status == 401:
+            raise Unauthorized(data)
+        elif status == 403:
+            raise Forbidden(data)
+        elif status == 404:
+            raise NotFound(data)
+        elif status < 200 or status >= 300:
+            raise AstakosClientException(data, status)
+        return simplejson.loads(unicode(data))
+
+    # ------------------------
+    def get_user_info(self, token, usage=False):
+        """Authenticate user and get user's info as a dictionary
+
+        Keyword arguments:
+        token   -- user's token (string)
+        usage   -- return usage information for user (boolean)
+
+        In case of success return user information (json parsed format).
+        Otherwise raise an AstakosClientException.
+
+        """
+        # Send request
+        auth_path = "/im/authenticate"
+        if usage:
+            auth_path += "?usage=1"
+        return self._call_astakos(token, auth_path)
+
+    # ----------------------------------
+    def _uuid_catalog(self, token, uuids, req_path):
+        req_headers = {'content-type': 'application/json'}
+        req_body = simplejson.dumps({'uuids': uuids})
+        data = self._call_astakos(
+            token, req_path, req_headers, req_body, "POST")
+        if "uuid_catalog" in data:
+            return data.get("uuid_catalog")
+        else:
+            m = "_uuid_catalog request returned %s. No uuid_catalog found" \
+                % data
+            self.logger.error(m)
+            raise AstakosClientException(m)
+
+    def get_usernames(self, token, uuids):
+        """Return a uuid_catalog dictionary for the given uuids
+
+        Keyword arguments:
+        token   -- user's token (string)
+        uuids   -- list of user ids (list of strings)
+
+        The returned uuid_catalog is a dictionary with uuids as
+        keys and the corresponding user names as values
+
+        """
+        req_path = "/user_catalogs"
+        return self._uuid_catalog(token, uuids, req_path)
+
+    def get_username(self, token, uuid):
+        """Return the user name of a uuid (see get_usernames)"""
+        if not uuid:
+            m = "No uuid was given"
+            self.logger.error(m)
+            raise ValueError(m)
+        uuid_dict = self.get_usernames(token, [uuid])
+        if uuid in uuid_dict:
+            return uuid_dict.get(uuid)
+        else:
+            raise NoUserName(uuid)
+
+    def service_get_usernames(self, token, uuids):
+        """Return a uuid_catalog dict using a service's token"""
+        req_path = "/service/api/user_catalogs"
+        return self._uuid_catalog(token, uuids, req_path)
+
+    def service_get_username(self, token, uuid):
+        """Return the displayName of a uuid using a service's token"""
+        if not uuid:
+            m = "No uuid was given"
+            self.logger.error(m)
+            raise ValueError(m)
+        uuid_dict = self.service_get_usernames(token, [uuid])
+        if uuid in uuid_dict:
+            return uuid_dict.get(uuid)
+        else:
+            raise NoUserName(uuid)
+
+    # ----------------------------------
+    def _displayname_catalog(self, token, display_names, req_path):
+        req_headers = {'content-type': 'application/json'}
+        req_body = simplejson.dumps({'displaynames': display_names})
+        data = self._call_astakos(
+            token, req_path, req_headers, req_body, "POST")
+        if "displayname_catalog" in data:
+            return data.get("displayname_catalog")
+        else:
+            m = "_displayname_catalog request returned %s. " \
+                "No displayname_catalog found" % data
+            self.logger.error(m)
+            raise AstakosClientException(m)
+
+    def get_uuids(self, token, display_names):
+        """Return a displayname_catalog for the given names
+
+        Keyword arguments:
+        token           -- user's token (string)
+        display_names   -- list of user names (list of strings)
+
+        The returned displayname_catalog is a dictionary with
+        the names as keys and the corresponding uuids as values
+
+        """
+        req_path = "/user_catalogs"
+        return self._displayname_catalog(token, display_names, req_path)
+
+    def get_uuid(self, token, display_name):
+        """Return the uuid of a name (see getUUIDs)"""
+        if not display_name:
+            m = "No display_name was given"
+            self.logger.error(m)
+            raise ValueError(m)
+        name_dict = self.get_uuids(token, [display_name])
+        if display_name in name_dict:
+            return name_dict.get(display_name)
+        else:
+            raise NoUUID(display_name)
+
+    def service_get_uuids(self, token, display_names):
+        """Return a display_name catalog using a service's token"""
+        req_path = "/service/api/user_catalogs"
+        return self._displayname_catalog(token, display_names, req_path)
+
+    def service_get_uuid(self, token, display_name):
+        """Return the uuid of a name using a service's token"""
+        if not display_name:
+            m = "No display_name was given"
+            self.logger.error(m)
+            raise ValueError(m)
+        name_dict = self.service_get_uuids(token, [display_name])
+        if display_name in name_dict:
+            return name_dict.get(display_name)
+        else:
+            raise NoUUID(display_name)
+
+    # ----------------------------------
+    def get_services(self):
+        """Return a list of dicts with the registered services"""
+        return self._call_astakos("dummy token", "/im/get_services")
+
+
+# --------------------------------------------------------------------
+# Private functions
+# We want _doRequest to be a distinct function
+# so that we can replace it during unit tests.
+def _do_request(conn, method, url, **kwargs):
+    """The actual request. This function can easily be mocked"""
+    conn.request(method, url, **kwargs)
+    response = conn.getresponse()
+    length = response.getheader('content-length', None)
+    data = response.read(length)
+    status = int(response.status)
+    return (data, status)
diff --git a/astakosclient/astakosclient/errors.py b/astakosclient/astakosclient/errors.py
new file mode 100644
index 0000000..150d43e
--- /dev/null
+++ b/astakosclient/astakosclient/errors.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2012, 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.
+
+
+class AstakosClientException(Exception):
+    def __init__(self, message, status=0):
+        self.message = message
+        self.status = status
+
+    def __str__(self):
+        return repr(self.message)
+
+
+class BadRequest(AstakosClientException):
+    def __init__(self, message):
+        """400 Bad Request"""
+        super(BadRequest, self).__init__(message, 400)
+
+
+class Unauthorized(AstakosClientException):
+    def __init__(self, message):
+        """401 Invalid X-Auth-Token"""
+        super(Unauthorized, self).__init__(message, 401)
+
+
+class Forbidden(AstakosClientException):
+    def __init__(self, message):
+        """403 Forbidden"""
+        super(Forbidden, self).__init__(message, 403)
+
+
+class NotFound(AstakosClientException):
+    def __init__(self, message):
+        """404 Not Found"""
+        super(NotFound, self).__init__(message, 404)
+
+
+class NoUserName(AstakosClientException):
+    def __init__(self, uuid):
+        """No display name for the given uuid"""
+        message = "No display name for the given uuid: %s" % uuid
+        super(NoUserName, self).__init__(message)
+
+
+class NoUUID(AstakosClientException):
+    def __init__(self, display_name):
+        """No uuid for the given display name"""
+        message = "No uuid for the given display name: %s" % display_name
+        super(NoUUID, self).__init__(message)
diff --git a/astakosclient/astakosclient/tests.py b/astakosclient/astakosclient/tests.py
new file mode 100644
index 0000000..454d6a9
--- /dev/null
+++ b/astakosclient/astakosclient/tests.py
@@ -0,0 +1,662 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2012, 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.
+
+"""Unit Tests for the astakos-client module
+
+Provides unit tests for the code implementing
+the astakos client library
+
+"""
+
+import sys
+import socket
+import simplejson
+
+import astakosclient
+from astakosclient import AstakosClient
+from astakosclient.errors import \
+    AstakosClientException, Unauthorized, BadRequest, NotFound, \
+    NoUserName, NoUUID
+
+# Use backported unittest functionality if Python < 2.7
+try:
+    import unittest2 as unittest
+except ImportError:
+    if sys.version_info < (2, 7):
+        raise Exception("The unittest2 package is required for Python < 2.7")
+    import unittest
+
+
+# --------------------------------------------------------------------
+# Helper functions
+
+# ----------------------------
+# This functions will be used as mocked requests
+def _request_offline(conn, method, url, **kwargs):
+    """This request behaves as we were offline"""
+    raise socket.gaierror
+
+
+def _request_status_302(conn, method, url, **kwargs):
+    """This request returns 302"""
+    status = 302
+    data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
+        '<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
+        '<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
+    return (data, status)
+
+
+def _request_status_404(conn, method, url, **kwargs):
+    """This request returns 404"""
+    status = 404
+    data = '<html><head><title>404 Not Found</title></head>' \
+        '<body><h1>Not Found</h1><p>The requested URL /foo was ' \
+        'not found on this server.</p><hr><address>Apache Server ' \
+        'at example.com Port 80</address></body></html>'
+    return (data, status)
+
+
+def _request_status_401(conn, method, url, **kwargs):
+    """This request returns 401"""
+    status = 401
+    data = "Invalid X-Auth-Token\n"
+    return (data, status)
+
+
+def _request_status_400(conn, method, url, **kwargs):
+    """This request returns 400"""
+    status = 400
+    data = "Method not allowed.\n"
+    return (data, status)
+
+
+def _request_ok(conn, method, url, **kwargs):
+    """This request behaves like original Astakos does"""
+    if url[0:16] == "/im/authenticate":
+        return _req_authenticate(conn, method, url, **kwargs)
+    elif url[0:14] == "/user_catalogs":
+        return _req_catalogs(conn, method, url, **kwargs)
+    else:
+        return _request_status_404(conn, method, url, **kwargs)
+
+
+def _req_authenticate(conn, method, url, **kwargs):
+    """Check if user exists and return his profile"""
+    global user_1, user_2
+
+    # Check input
+    if conn.__class__.__name__ != "HTTPSConnection":
+        return _request_status_302(conn, method, url, **kwargs)
+
+    if method != "GET":
+        return _request_status_400(conn, method, url, **kwargs)
+
+    token = kwargs['headers']['X-Auth-Token']
+    if token == token_1:
+        user = dict(user_1)
+    elif token == token_2:
+        user = dict(user_2)
+    else:
+        # No user found
+        return _request_status_401(conn, method, url, **kwargs)
+
+    # Return
+    if "usage=1" not in url:
+        # Strip `usage' key from `user'
+        del user['usage']
+    return (simplejson.dumps(user), 200)
+
+
+def _req_catalogs(conn, method, url, **kwargs):
+    """Return user catalogs"""
+    global token_1, token_2, user_1, user_2
+
+    # Check input
+    if conn.__class__.__name__ != "HTTPSConnection":
+        return _request_status_302(conn, method, url, **kwargs)
+
+    if method != "POST":
+        return _request_status_400(conn, method, url, **kwargs)
+
+    token = kwargs['headers']['X-Auth-Token']
+    if token != token_1 and token != token_2:
+        return _request_status_401(conn, method, url, **kwargs)
+
+    # Return
+    body = simplejson.loads(kwargs['body'])
+    if 'uuids' in body:
+        # Return uuid_catalog
+        uuids = body['uuids']
+        catalogs = {}
+        if user_1['uuid'] in uuids:
+            catalogs[user_1['uuid']] = user_1['username']
+        if user_2['uuid'] in uuids:
+            catalogs[user_2['uuid']] = user_2['username']
+        return_catalog = {"displayname_catalog": {}, "uuid_catalog": catalogs}
+    elif 'displaynames' in body:
+        # Return displayname_catalog
+        names = body['displaynames']
+        catalogs = {}
+        if user_1['username'] in names:
+            catalogs[user_1['username']] = user_1['uuid']
+        if user_2['username'] in names:
+            catalogs[user_2['username']] = user_2['uuid']
+        return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}}
+    else:
+        return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}}
+    return (simplejson.dumps(return_catalog), 200)
+
+
+# ----------------------------
+# Mock the actual _doRequest
+def _mock_request(new_requests):
+    """Mock the actual request
+
+    Given a list of requests to use (in rotation),
+    replace the original _doRequest function with
+    a new one
+
+    """
+    def _mock(conn, method, url, **kwargs):
+        # Get first request
+        request = _mock.requests[0]
+        # Rotate requests
+        _mock.requests = _mock.requests[1:] + _mock.requests[:1]
+        # Use first request
+        return request(conn, method, url, **kwargs)
+
+    _mock.requests = new_requests
+    # Replace `_doRequest' with our `_mock'
+    astakosclient._do_request = _mock
+
+
+# ----------------------------
+# Local users
+token_1 = "skzleaFlBl+fasFdaf24sx=="
+user_1 = \
+    {"username": "user1@example.com",
+     "auth_token_created": 1359386939000,
+     "name": "Example User One",
+     "email": ["user1@example.com"],
+     "auth_token_expires": 1361978939000,
+     "id": 108,
+     "uuid": "73917abc-abcd-477e-a1f1-1763abcdefab",
+     "usage": [
+         {"currValue": 42949672960,
+          "display_name": "System Disk",
+          "name": "cyclades.disk"},
+         {"currValue": 4,
+          "display_name": "CPU",
+          "name": "cyclades.cpu"},
+         {"currValue": 4294967296,
+          "display_name": "RAM",
+          "name": "cyclades.ram"},
+         {"currValue": 3,
+          "display_name": "VM",
+          "name": "cyclades.vm"},
+         {"currValue": 0,
+          "display_name": "private network",
+          "name": "cyclades.network.private"},
+         {"currValue": 152,
+          "display_name": "Storage Space",
+          "name": "pithos+.diskspace"}]}
+
+token_2 = "fasdfDSFdf98923DF+sdfk=="
+user_2 = \
+    {"username": "user2@example.com",
+     "auth_token_created": 1358386938997,
+     "name": "Example User Two",
+     "email": ["user1@example.com"],
+     "auth_token_expires": 1461998939000,
+     "id": 109,
+     "uuid": "73917bca-1234-5678-a1f1-1763abcdefab",
+     "usage": [
+         {"currValue": 68719476736,
+          "display_name": "System Disk",
+          "name": "cyclades.disk"},
+         {"currValue": 1,
+          "display_name": "CPU",
+          "name": "cyclades.cpu"},
+         {"currValue": 1073741824,
+          "display_name": "RAM",
+          "name": "cyclades.ram"},
+         {"currValue": 2,
+          "display_name": "VM",
+          "name": "cyclades.vm"},
+         {"currValue": 1,
+          "display_name": "private network",
+          "name": "cyclades.network.private"},
+         {"currValue": 2341634510,
+          "display_name": "Storage Space",
+          "name": "pithos+.diskspace"}]}
+
+
+# --------------------------------------------------------------------
+# The actual tests
+
+class TestCallAstakos(unittest.TestCase):
+    """Test cases for function _callAstakos"""
+
+    # ----------------------------------
+    # Test the response we get if we don't have internet access
+    def _offline(self, pool):
+        global token_1
+        _mock_request([_request_offline])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/im/authenticate")
+        except AstakosClientException:
+            pass
+        else:
+            self.fail("Should have raised AstakosClientException")
+
+    def test_offline(self):
+        """Test _offline without pool"""
+        self._offline(False)
+
+    def test_offline_pool(self):
+        """Test _offline using pool"""
+        self._offline(True)
+
+    # ----------------------------------
+    # Test the response we get if we send invalid token
+    def _invalid_token(self, pool):
+        token = "skaksaFlBl+fasFdaf24sx=="
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client._call_astakos(token, "/im/authenticate")
+        except Unauthorized:
+            pass
+        except Exception:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+        else:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+
+    def test_invalid_token(self):
+        """Test _invalid_token without pool"""
+        self._invalid_token(False)
+
+    def test_invalid_token_pool(self):
+        """Test _invalid_token using pool"""
+        self._invalid_token(True)
+
+    # ----------------------------------
+    # Test the response we get if we send invalid url
+    def _invalid_url(self, pool):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/im/misspelled")
+        except NotFound:
+            pass
+        except Exception:
+            self.fail("Should have returned 404 (Not Found)")
+        else:
+            self.fail("Should have returned 404 (Not Found)")
+
+    def test_invalid_url(self):
+        """Test _invalid_url without pool"""
+        self._invalid_url(False)
+
+    def test_invalid_url_pool(self):
+        """Test _invalid_url using pool"""
+        self._invalid_url(True)
+
+    # ----------------------------------
+    # Test the response we get if we use an unsupported scheme
+    def _unsupported_scheme(self, pool):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("ftp://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/im/authenticate")
+        except ValueError:
+            pass
+        except Exception:
+            self.fail("Should have raise ValueError Exception")
+        else:
+            self.fail("Should have raise ValueError Exception")
+
+    def test_unsupported_scheme(self):
+        """Test _unsupported_scheme without pool"""
+        self._unsupported_scheme(False)
+
+    def test_unsupported_scheme_pool(self):
+        """Test _unsupported_scheme using pool"""
+        self._unsupported_scheme(True)
+
+    # ----------------------------------
+    # Test the response we get if we use http instead of https
+    def _http_scheme(self, pool):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("http://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/im/authenticate")
+        except AstakosClientException as err:
+            if err.status != 302:
+                self.fail("Should have returned 302 (Found)")
+        else:
+            self.fail("Should have returned 302 (Found)")
+
+    def test_http_scheme(self):
+        """Test _http_scheme without pool"""
+        self._http_scheme(False)
+
+    def test_http_scheme_pool(self):
+        """Test _http_scheme using pool"""
+        self._http_scheme(True)
+
+    # ----------------------------------
+    # Test the response we get if we use authenticate with POST
+    def _post_authenticate(self, pool):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/im/authenticate", method="POST")
+        except BadRequest:
+            pass
+        except Exception:
+            self.fail("Should have returned 400 (Method not allowed)")
+        else:
+            self.fail("Should have returned 400 (Method not allowed)")
+
+    def test_post_authenticate(self):
+        """Test _post_authenticate without pool"""
+        self._post_authenticate(False)
+
+    def test_post_authenticate_pool(self):
+        """Test _post_authenticate using pool"""
+        self._post_authenticate(True)
+
+    # ----------------------------------
+    # Test the response if we request user_catalogs with GET
+    def _get_user_catalogs(self, pool):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client._call_astakos(token_1, "/user_catalogs")
+        except BadRequest:
+            pass
+        except Exception:
+            self.fail("Should have returned 400 (Method not allowed)")
+        else:
+            self.fail("Should have returned 400 (Method not allowed)")
+
+    def test_get_user_catalogs(self):
+        """Test _get_user_catalogs without pool"""
+        self._get_user_catalogs(False)
+
+    def test_get_user_catalogs_pool(self):
+        """Test _get_user_catalogs using pool"""
+        self._get_user_catalogs(True)
+
+
+class TestAuthenticate(unittest.TestCase):
+    """Test cases for function getUserInfo"""
+
+    # ----------------------------------
+    # Test the response we get if we don't have internet access
+    def test_offline(self):
+        """Test offline after 3 retries"""
+        global token_1
+        _mock_request([_request_offline])
+        try:
+            client = AstakosClient("https://example.com", retry=3)
+            client.get_user_info(token_1)
+        except AstakosClientException:
+            pass
+        else:
+            self.fail("Should have raised AstakosClientException exception")
+
+    # ----------------------------------
+    # Test the response we get for invalid token
+    def _invalid_token(self, pool):
+        token = "skaksaFlBl+fasFdaf24sx=="
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            client.get_user_info(token)
+        except Unauthorized:
+            pass
+        except Exception:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+        else:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+
+    def test_invalid_token(self):
+        """Test _invalid_token without pool"""
+        self._invalid_token(False)
+
+    def test_invalid_token_pool(self):
+        """Test _invalid_token using pool"""
+        self._invalid_token(True)
+
+    #- ---------------------------------
+    # Test response for user 1
+    def _auth_user(self, token, user_info, usage, pool):
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com", use_pool=pool)
+            auth_info = client.get_user_info(token, usage=usage)
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(user_info, auth_info)
+
+    def test_auth_user_one(self):
+        """Test _auth_user for User 1 without pool, without usage"""
+        global token_1, user_1
+        user_info = dict(user_1)
+        del user_info['usage']
+        self._auth_user(token_1, user_info, False, False)
+
+    def test_auth_user_one_usage(self):
+        """Test _auth_user for User 1 without pool, with usage"""
+        global token_1, user_1
+        self._auth_user(token_1, user_1, True, False)
+
+    def test_auth_user_one_usage_pool(self):
+        """Test _auth_user for User 1 using pool, with usage"""
+        global token_1, user_1
+        self._auth_user(token_1, user_1, True, True)
+
+    def test_auth_user_two(self):
+        """Test _auth_user for User 2 without pool, without usage"""
+        global token_2, user_2
+        user_info = dict(user_2)
+        del user_info['usage']
+        self._auth_user(token_2, user_info, False, False)
+
+    def test_auth_user_two_usage(self):
+        """Test _auth_user for User 2 without pool, with usage"""
+        global token_2, user_2
+        self._auth_user(token_2, user_2, True, False)
+
+    def test_auth_user_two_usage_pool(self):
+        """Test _auth_user for User 2 using pool, with usage"""
+        global token_2, user_2
+        self._auth_user(token_2, user_2, True, True)
+
+    # ----------------------------------
+    # Test retry functionality
+    def test_offline_retry(self):
+        """Test retry functionality for getUserInfo"""
+        global token_1, user_1
+        _mock_request([_request_offline, _request_offline, _request_ok])
+        try:
+            client = AstakosClient("https://example.com", retry=2)
+            auth_info = client.get_user_info(token_1, usage=True)
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(user_1, auth_info)
+
+
+class TestDisplayNames(unittest.TestCase):
+    """Test cases for functions getDisplayNames/getDisplayName"""
+
+    # ----------------------------------
+    # Test the response we get for invalid token
+    def test_invalid_token(self):
+        """Test the response we get for invalid token (without pool)"""
+        global user_1
+        token = "skaksaFlBl+fasFdaf24sx=="
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            client.get_usernames(token, [user_1['uuid']])
+        except Unauthorized:
+            pass
+        except Exception:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+        else:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+
+    # ----------------------------------
+    # Get Info for both users
+    def test_usernames(self):
+        """Test get_usernames with both users"""
+        global token_1, user_1, user_2
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            catalog = client.get_usernames(
+                token_1, [user_1['uuid'], user_2['uuid']])
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(catalog[user_1['uuid']], user_1['username'])
+        self.assertEqual(catalog[user_2['uuid']], user_2['username'])
+
+    # ----------------------------------
+    # Get info for user 1
+    def test_username_user_one(self):
+        """Test get_username for User One"""
+        global token_2, user_1
+        _mock_request([_request_offline, _request_ok])
+        try:
+            client = AstakosClient(
+                "https://example.com", use_pool=True, retry=2)
+            info = client.get_username(token_2, user_1['uuid'])
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(info, user_1['username'])
+
+    # ----------------------------------
+    # Get info with wrong uuid
+    def test_no_username(self):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            client.get_username(token_1, "1234")
+        except NoUserName:
+            pass
+        except:
+            self.fail("Should have raised NoDisplayName exception")
+        else:
+            self.fail("Should have raised NoDisplayName exception")
+
+
+class TestGetUUIDs(unittest.TestCase):
+    """Test cases for functions getUUIDs/getUUID"""
+
+    # ----------------------------------
+    # Test the response we get for invalid token
+    def test_invalid_token(self):
+        """Test the response we get for invalid token (using pool)"""
+        global user_1
+        token = "skaksaFlBl+fasFdaf24sx=="
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            client.get_uuids(token, [user_1['username']])
+        except Unauthorized:
+            pass
+        except Exception:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+        else:
+            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
+
+    # ----------------------------------
+    # Get info for both users
+    def test_uuids(self):
+        """Test get_uuids with both users"""
+        global token_1, user_1, user_2
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            catalog = client.get_uuids(
+                token_1, [user_1['username'], user_2['username']])
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(catalog[user_1['username']], user_1['uuid'])
+        self.assertEqual(catalog[user_2['username']], user_2['uuid'])
+
+    # ----------------------------------
+    # Get uuid for user 2
+    def test_get_uuid_user_two(self):
+        """Test get_uuid for User Two"""
+        global token_1, user_2
+        _mock_request([_request_offline, _request_ok])
+        try:
+            client = AstakosClient("https://example.com", retry=1)
+            info = client.get_uuid(token_2, user_1['username'])
+        except:
+            self.fail("Shouldn't raise an Exception")
+        self.assertEqual(info, user_1['uuid'])
+
+    # ----------------------------------
+    # Get uuid with wrong username
+    def test_no_uuid(self):
+        global token_1
+        _mock_request([_request_ok])
+        try:
+            client = AstakosClient("https://example.com")
+            client.get_uuid(token_1, "1234")
+        except NoUUID:
+            pass
+        except:
+            self.fail("Should have raised NoUUID exception")
+        else:
+            self.fail("Should have raised NoUUID exception")
+
+
+# ----------------------------
+# Run tests
+if __name__ == "__main__":
+    unittest.main()
diff --git a/astakosclient/astakosclient/utils.py b/astakosclient/astakosclient/utils.py
new file mode 100644
index 0000000..534c01c4
--- /dev/null
+++ b/astakosclient/astakosclient/utils.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2012, 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 httplib import HTTPConnection, HTTPSConnection
+from contextlib import closing
+
+from objpool.http import PooledHTTPConnection
+from astakosclient.errors import AstakosClientException
+
+
+def retry(func):
+    def decorator(self, *args, **kwargs):
+        attemps = 0
+        while True:
+            try:
+                return func(self, *args, **kwargs)
+            except AstakosClientException as err:
+                is_last_attempt = attemps == self.retry
+                if is_last_attempt:
+                    raise err
+                if err.status == 401 or err.status == 404:
+                    # In case of Unauthorized response
+                    # or Not Found return immediately
+                    raise err
+                self.logger.info("AstakosClient request failed..retrying")
+                attemps += 1
+    return decorator
+
+
+def scheme_to_class(scheme, use_pool, pool_size):
+    """Return the appropriate conn class for given scheme"""
+    def _objpool(netloc):
+        return PooledHTTPConnection(
+            netloc=netloc, scheme=scheme, pool_size=pool_size)
+
+    def _http_connection(netloc):
+        return closing(HTTPConnection(netloc))
+
+    def _https_connection(netloc):
+        return closing(HTTPSConnection(netloc))
+
+    if scheme == "http":
+        if use_pool:
+            return _objpool
+        else:
+            return _http_connection
+    elif scheme == "https":
+        if use_pool:
+            return _objpool
+        else:
+            return _https_connection
+    else:
+        return None
diff --git a/astakosclient/distribute_setup.py b/astakosclient/distribute_setup.py
new file mode 100644
index 0000000..10d6684
--- /dev/null
+++ b/astakosclient/distribute_setup.py
@@ -0,0 +1,485 @@
+#!python
+"""Bootstrap distribute installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from distribute_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import os
+import sys
+import time
+import fnmatch
+import tempfile
+import tarfile
+from distutils import log
+
+try:
+    from site import USER_SITE
+except ImportError:
+    USER_SITE = None
+
+try:
+    import subprocess
+
+    def _python_cmd(*args):
+        args = (sys.executable,) + args
+        return subprocess.call(args) == 0
+
+except ImportError:
+    # will be used for python 2.3
+    def _python_cmd(*args):
+        args = (sys.executable,) + args
+        # quoting arguments if windows
+        if sys.platform == 'win32':
+            def quote(arg):
+                if ' ' in arg:
+                    return '"%s"' % arg
+                return arg
+            args = [quote(arg) for arg in args]
+        return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
+
+DEFAULT_VERSION = "0.6.10"
+DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
+SETUPTOOLS_FAKED_VERSION = "0.6c11"
+
+SETUPTOOLS_PKG_INFO = """\
+Metadata-Version: 1.0
+Name: setuptools
+Version: %s
+Summary: xxxx
+Home-page: xxx
+Author: xxx
+Author-email: xxx
+License: xxx
+Description: xxx
+""" % SETUPTOOLS_FAKED_VERSION
+
+
+def _install(tarball):
+    # extracting the tarball
+    tmpdir = tempfile.mkdtemp()
+    log.warn('Extracting in %s', tmpdir)
+    old_wd = os.getcwd()
+    try:
+        os.chdir(tmpdir)
+        tar = tarfile.open(tarball)
+        _extractall(tar)
+        tar.close()
+
+        # going in the directory
+        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+        os.chdir(subdir)
+        log.warn('Now working in %s', subdir)
+
+        # installing
+        log.warn('Installing Distribute')
+        if not _python_cmd('setup.py', 'install'):
+            log.warn('Something went wrong during the installation.')
+            log.warn('See the error message above.')
+    finally:
+        os.chdir(old_wd)
+
+
+def _build_egg(egg, tarball, to_dir):
+    # extracting the tarball
+    tmpdir = tempfile.mkdtemp()
+    log.warn('Extracting in %s', tmpdir)
+    old_wd = os.getcwd()
+    try:
+        os.chdir(tmpdir)
+        tar = tarfile.open(tarball)
+        _extractall(tar)
+        tar.close()
+
+        # going in the directory
+        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+        os.chdir(subdir)
+        log.warn('Now working in %s', subdir)
+
+        # building an egg
+        log.warn('Building a Distribute egg in %s', to_dir)
+        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
+
+    finally:
+        os.chdir(old_wd)
+    # returning the result
+    log.warn(egg)
+    if not os.path.exists(egg):
+        raise IOError('Could not build the egg.')
+
+
+def _do_download(version, download_base, to_dir, download_delay):
+    egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
+                       % (version, sys.version_info[0], sys.version_info[1]))
+    if not os.path.exists(egg):
+        tarball = download_setuptools(version, download_base,
+                                      to_dir, download_delay)
+        _build_egg(egg, tarball, to_dir)
+    sys.path.insert(0, egg)
+    import setuptools
+    setuptools.bootstrap_install_from = egg
+
+
+def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+                   to_dir=os.curdir, download_delay=15, no_fake=True):
+    # making sure we use the absolute path
+    to_dir = os.path.abspath(to_dir)
+    was_imported = 'pkg_resources' in sys.modules or \
+        'setuptools' in sys.modules
+    try:
+        try:
+            import pkg_resources
+            if not hasattr(pkg_resources, '_distribute'):
+                if not no_fake:
+                    _fake_setuptools()
+                raise ImportError
+        except ImportError:
+            return _do_download(version, download_base, to_dir, download_delay)
+        try:
+            pkg_resources.require("distribute>="+version)
+            return
+        except pkg_resources.VersionConflict:
+            e = sys.exc_info()[1]
+            if was_imported:
+                sys.stderr.write(
+                "The required version of distribute (>=%s) is not available,\n"
+                "and can't be installed while this script is running. Please\n"
+                "install a more recent version first, using\n"
+                "'easy_install -U distribute'."
+                "\n\n(Currently using %r)\n" % (version, e.args[0]))
+                sys.exit(2)
+            else:
+                del pkg_resources, sys.modules['pkg_resources']    # reload ok
+                return _do_download(version, download_base, to_dir,
+                                    download_delay)
+        except pkg_resources.DistributionNotFound:
+            return _do_download(version, download_base, to_dir,
+                                download_delay)
+    finally:
+        if not no_fake:
+            _create_fake_setuptools_pkg_info(to_dir)
+
+def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+                        to_dir=os.curdir, delay=15):
+    """Download distribute from a specified location and return its filename
+
+    `version` should be a valid distribute version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download
+    attempt.
+    """
+    # making sure we use the absolute path
+    to_dir = os.path.abspath(to_dir)
+    try:
+        from urllib.request import urlopen
+    except ImportError:
+        from urllib2 import urlopen
+    tgz_name = "distribute-%s.tar.gz" % version
+    url = download_base + tgz_name
+    saveto = os.path.join(to_dir, tgz_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            log.warn("Downloading %s", url)
+            src = urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = src.read()
+            dst = open(saveto, "wb")
+            dst.write(data)
+        finally:
+            if src:
+                src.close()
+            if dst:
+                dst.close()
+    return os.path.realpath(saveto)
+
+def _no_sandbox(function):
+    def __no_sandbox(*args, **kw):
+        try:
+            from setuptools.sandbox import DirectorySandbox
+            if not hasattr(DirectorySandbox, '_old'):
+                def violation(*args):
+                    pass
+                DirectorySandbox._old = DirectorySandbox._violation
+                DirectorySandbox._violation = violation
+                patched = True
+            else:
+                patched = False
+        except ImportError:
+            patched = False
+
+        try:
+            return function(*args, **kw)
+        finally:
+            if patched:
+                DirectorySandbox._violation = DirectorySandbox._old
+                del DirectorySandbox._old
+
+    return __no_sandbox
+
+def _patch_file(path, content):
+    """Will backup the file then patch it"""
+    existing_content = open(path).read()
+    if existing_content == content:
+        # already patched
+        log.warn('Already patched.')
+        return False
+    log.warn('Patching...')
+    _rename_path(path)
+    f = open(path, 'w')
+    try:
+        f.write(content)
+    finally:
+        f.close()
+    return True
+
+_patch_file = _no_sandbox(_patch_file)
+
+def _same_content(path, content):
+    return open(path).read() == content
+
+def _rename_path(path):
+    new_name = path + '.OLD.%s' % time.time()
+    log.warn('Renaming %s into %s', path, new_name)
+    os.rename(path, new_name)
+    return new_name
+
+def _remove_flat_installation(placeholder):
+    if not os.path.isdir(placeholder):
+        log.warn('Unkown installation at %s', placeholder)
+        return False
+    found = False
+    for file in os.listdir(placeholder):
+        if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
+            found = True
+            break
+    if not found:
+        log.warn('Could not locate setuptools*.egg-info')
+        return
+
+    log.warn('Removing elements out of the way...')
+    pkg_info = os.path.join(placeholder, file)
+    if os.path.isdir(pkg_info):
+        patched = _patch_egg_dir(pkg_info)
+    else:
+        patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
+
+    if not patched:
+        log.warn('%s already patched.', pkg_info)
+        return False
+    # now let's move the files out of the way
+    for element in ('setuptools', 'pkg_resources.py', 'site.py'):
+        element = os.path.join(placeholder, element)
+        if os.path.exists(element):
+            _rename_path(element)
+        else:
+            log.warn('Could not find the %s element of the '
+                     'Setuptools distribution', element)
+    return True
+
+_remove_flat_installation = _no_sandbox(_remove_flat_installation)
+
+def _after_install(dist):
+    log.warn('After install bootstrap.')
+    placeholder = dist.get_command_obj('install').install_purelib
+    _create_fake_setuptools_pkg_info(placeholder)
+
+def _create_fake_setuptools_pkg_info(placeholder):
+    if not placeholder or not os.path.exists(placeholder):
+        log.warn('Could not find the install location')
+        return
+    pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
+    setuptools_file = 'setuptools-%s-py%s.egg-info' % \
+            (SETUPTOOLS_FAKED_VERSION, pyver)
+    pkg_info = os.path.join(placeholder, setuptools_file)
+    if os.path.exists(pkg_info):
+        log.warn('%s already exists', pkg_info)
+        return
+
+    log.warn('Creating %s', pkg_info)
+    f = open(pkg_info, 'w')
+    try:
+        f.write(SETUPTOOLS_PKG_INFO)
+    finally:
+        f.close()
+
+    pth_file = os.path.join(placeholder, 'setuptools.pth')
+    log.warn('Creating %s', pth_file)
+    f = open(pth_file, 'w')
+    try:
+        f.write(os.path.join(os.curdir, setuptools_file))
+    finally:
+        f.close()
+
+_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
+
+def _patch_egg_dir(path):
+    # let's check if it's already patched
+    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+    if os.path.exists(pkg_info):
+        if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
+            log.warn('%s already patched.', pkg_info)
+            return False
+    _rename_path(path)
+    os.mkdir(path)
+    os.mkdir(os.path.join(path, 'EGG-INFO'))
+    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+    f = open(pkg_info, 'w')
+    try:
+        f.write(SETUPTOOLS_PKG_INFO)
+    finally:
+        f.close()
+    return True
+
+_patch_egg_dir = _no_sandbox(_patch_egg_dir)
+
+def _before_install():
+    log.warn('Before install bootstrap.')
+    _fake_setuptools()
+
+
+def _under_prefix(location):
+    if 'install' not in sys.argv:
+        return True
+    args = sys.argv[sys.argv.index('install')+1:]
+    for index, arg in enumerate(args):
+        for option in ('--root', '--prefix'):
+            if arg.startswith('%s=' % option):
+                top_dir = arg.split('root=')[-1]
+                return location.startswith(top_dir)
+            elif arg == option:
+                if len(args) > index:
+                    top_dir = args[index+1]
+                    return location.startswith(top_dir)
+        if arg == '--user' and USER_SITE is not None:
+            return location.startswith(USER_SITE)
+    return True
+
+
+def _fake_setuptools():
+    log.warn('Scanning installed packages')
+    try:
+        import pkg_resources
+    except ImportError:
+        # we're cool
+        log.warn('Setuptools or Distribute does not seem to be installed.')
+        return
+    ws = pkg_resources.working_set
+    try:
+        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
+                                  replacement=False))
+    except TypeError:
+        # old distribute API
+        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+
+    if setuptools_dist is None:
+        log.warn('No setuptools distribution found')
+        return
+    # detecting if it was already faked
+    setuptools_location = setuptools_dist.location
+    log.warn('Setuptools installation detected at %s', setuptools_location)
+
+    # if --root or --preix was provided, and if
+    # setuptools is not located in them, we don't patch it
+    if not _under_prefix(setuptools_location):
+        log.warn('Not patching, --root or --prefix is installing Distribute'
+                 ' in another location')
+        return
+
+    # let's see if its an egg
+    if not setuptools_location.endswith('.egg'):
+        log.warn('Non-egg installation')
+        res = _remove_flat_installation(setuptools_location)
+        if not res:
+            return
+    else:
+        log.warn('Egg installation')
+        pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
+        if (os.path.exists(pkg_info) and
+            _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+            log.warn('Already patched.')
+            return
+        log.warn('Patching...')
+        # let's create a fake egg replacing setuptools one
+        res = _patch_egg_dir(setuptools_location)
+        if not res:
+            return
+    log.warn('Patched done.')
+    _relaunch()
+
+
+def _relaunch():
+    log.warn('Relaunching...')
+    # we have to relaunch the process
+    # pip marker to avoid a relaunch bug
+    if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
+        sys.argv[0] = 'setup.py'
+    args = [sys.executable] + sys.argv
+    sys.exit(subprocess.call(args))
+
+
+def _extractall(self, path=".", members=None):
+    """Extract all members from the archive to the current working
+       directory and set owner, modification time and permissions on
+       directories afterwards. `path' specifies a different directory
+       to extract to. `members' is optional and must be a subset of the
+       list returned by getmembers().
+    """
+    import copy
+    import operator
+    from tarfile import ExtractError
+    directories = []
+
+    if members is None:
+        members = self
+
+    for tarinfo in members:
+        if tarinfo.isdir():
+            # Extract directories with a safe mode.
+            directories.append(tarinfo)
+            tarinfo = copy.copy(tarinfo)
+            tarinfo.mode = 448 # decimal for oct 0700
+        self.extract(tarinfo, path)
+
+    # Reverse sort directories.
+    if sys.version_info < (2, 4):
+        def sorter(dir1, dir2):
+            return cmp(dir1.name, dir2.name)
+        directories.sort(sorter)
+        directories.reverse()
+    else:
+        directories.sort(key=operator.attrgetter('name'), reverse=True)
+
+    # Set correct owner, mtime and filemode on directories.
+    for tarinfo in directories:
+        dirpath = os.path.join(path, tarinfo.name)
+        try:
+            self.chown(tarinfo, dirpath)
+            self.utime(tarinfo, dirpath)
+            self.chmod(tarinfo, dirpath)
+        except ExtractError:
+            e = sys.exc_info()[1]
+            if self.errorlevel > 1:
+                raise
+            else:
+                self._dbg(1, "tarfile: %s" % e)
+
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+    tarball = download_setuptools()
+    _install(tarball)
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/astakosclient/docs/Makefile b/astakosclient/docs/Makefile
new file mode 100644
index 0000000..38fd314
--- /dev/null
+++ b/astakosclient/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/synnefo.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/synnefo.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/synnefo"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/synnefo"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/astakosclient/docs/conf.py b/astakosclient/docs/conf.py
new file mode 100644
index 0000000..4ebdb75
--- /dev/null
+++ b/astakosclient/docs/conf.py
@@ -0,0 +1,68 @@
+import sys
+import os
+
+sys.path.insert(0, os.path.abspath('..'))
+from astakosclient.version import __version__
+
+project = u'nnefo'
+copyright = u'2012-2013, GRNET'
+version = __version__
+release = __version__
+html_title = 'synnefo ' + version
+
+templates_path = ['_templates']
+source_suffix = '.rst'
+master_doc = 'index'
+exclude_patterns = ['_build']
+pygments_style = 'sphinx'
+html_theme = 'default'
+html_theme_options = {
+    'collapsiblesidebar': 'true',
+    'footerbgcolor':    '#55b577',
+    'footertextcolor':  '#000000',
+    'sidebarbgcolor':   '#ffffff',
+    'sidebarbtncolor':  '#f2f2f2',
+    'sidebartextcolor': '#000000',
+    'sidebarlinkcolor': '#328e4a',
+    'relbarbgcolor':    '#55b577',
+    'relbartextcolor':  '#ffffff',
+    'relbarlinkcolor':  '#ffffff',
+    'bgcolor':          '#ffffff',
+    'textcolor':        '#000000',
+    'headbgcolor':      '#ffffff',
+    'headtextcolor':    '#000000',
+    'headlinkcolor':    '#c60f0f',
+    'linkcolor':        '#328e4a',
+    'visitedlinkcolor': '#63409b',
+    'codebgcolor':      '#eeffcc',
+    'codetextcolor':    '#333333'
+}
+
+html_static_path = ['_static']
+htmlhelp_basename = 'synnefodoc'
+
+intersphinx_mapping = {
+    'pithon': ('http://docs.python.org/', None),
+    'django': ('https://docs.djangoproject.com/en/dev/',
+               'https://docs.djangoproject.com/en/dev/_objects/')
+}
+
+SYNNEFO_DOCS_BASE_URL = 'http://docs.dev.grnet.gr/'
+SYNNEFO_PROJECTS = {
+    'synnefo': 'dev',
+    'pithos': 'dev',
+    'snf-webproject': 'dev',
+    'snf-common': 'dev',
+    'snf-image': 'dev',
+    'snf-cyclades-app': 'dev'
+}
+
+for name, ver in SYNNEFO_PROJECTS.iteritems():
+    intersphinx_mapping[name.replace("-", "")] = (SYNNEFO_DOCS_BASE_URL +
+                                                  '%s/%s/' % (name, ver),
+                                                  None)
+
+extensions = ['sphinx.ext.autodoc',
+              'sphinx.ext.intersphinx',
+              'sphinx.ext.todo',
+              'sphinx.ext.viewcode']
diff --git a/astakosclient/docs/index.rst b/astakosclient/docs/index.rst
new file mode 100644
index 0000000..dee4334
--- /dev/null
+++ b/astakosclient/docs/index.rst
@@ -0,0 +1,153 @@
+.. _snf-astakos-client:
+
+Component snf-astakos-client
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The Synnefo component :ref:`snf-astakos-client <snf-astakos-client>` defines a
+default client for the :ref:`Astakos <astakos>` service. It is designed to be
+simple and minimal, hence easy to debug and test.
+
+It uses the user's authentication token to query Astakos for:
+
+    * User's info
+    * Usernames for given UUIDs
+    * UUIDs for given usernames
+
+It can also query Astakos with another service's (Cyclades or Pithos)
+authentication token for:
+
+    * Usernames for given UUIDs
+    * UUIDs for given usernames
+
+Additionally, there are options for using the `objpool
+<https://github.com/grnet/objpool>`_ library to pool the http connections.
+
+
+Basic example
+=============
+
+The ``astakosclient`` module provides the ``AstakosClient`` class. This section
+demonstrates how to get user's info using ``astakosclient``.
+
+.. code-block:: python
+
+    from astakosclient import AstakosClient
+
+    client = AstakosClient("https://accounts.example.com")
+    user_info = client.get_user_info("UQpYas7ElzWGD5yCcEXtjw==")
+    print user_info['username']
+
+Another example where we ask for the username of a user with UUID:
+``b3de8eb0-3958-477e-als9-789af8dd352c``
+
+.. code-block:: python
+
+    from astakosclient import AstakosClient
+
+    client = AstakosClient("https://accounts.example.com")
+    username = client.get_username("UQpYas7ElzWGD5yCcEXtjw==",
+                                   "b3de8eb0-3958-477e-als9-789af8dd352c")
+    print username
+
+
+Classes and functions
+=====================
+
+This section describes in depth the API of ``astakosclient``.
+
+Astakos Client
+--------------
+
+*class* astakosclient.\ **AstakosClient(**\ astakos_url,
+retry=0, use_pool=False, pool_size=8, logger=None\ **)**
+
+    Initialize an instance of **AstakosClient** given the *astakos_url*.
+    Optionally one can specify if we are going to use a pool, the pool_size
+    and the number of retries if the connection fails.
+
+    This class provides the following methods:
+
+    **get_user_info(**\ token, usage=False\ **)**
+        Given a valid authentication token it returns a dict with the
+        correspoinding user's info. If usage is set to True more
+        information about user's resources will be returned.
+        In case of error raise an AstakosClientException exception.
+
+    **get_usernames(**\ token, uuids\ **)**
+        Given a valid authentication token and a list of UUIDs
+        return a uuid_catalog, that is a dictionary with the given
+        UUIDs as keys and the corresponding user names as values.
+        Invalid UUIDs will not be in the dictionary.
+        In case of error raise an AstakosClientException exception.
+
+    **get_username(**\ token, uuid\ **)**
+        Given a valid authentication token and a UUID (as string)
+        return the corresponding user name (as string).
+        In case of invalid UUID raise NoUserName exception.
+        In case of error raise an AstakosClientException exception.
+
+    **service_get_usernames(**\ token, uuids\ **)**
+        Same as get_usernames but called with a service's token.
+
+    **service_get_username(**\ token, uuid\ **)**
+        Same as get_username but called with a service's token.
+
+    **get_uuids(**\ token, display_names\ **)**
+        Given a valid authentication token and a list of usernames
+        return a displayname_catalog, that is a dictionary with the given
+        usernames as keys and the corresponding UUIDs as values.
+        Invalid usernames will not be in the dictionary.
+        In case of error raise an AstakosClientException exception.
+
+    **get_uuid(**\ token, display_name\ **)**
+        Given a valid authentication token and a username (as string)
+        return the corresponding UUID (as string).
+        In case of invalid user name raise NoUUID exception.
+        In case of error raise an AstakosClientException exception.
+
+    **service_get_uuids(**\ token, uuids\ **)**
+        Same as get_uuids but called with a service's token.
+
+    **service_get_uuid(**\ token, uuid\ **)**
+        Same as get_uuid but called with a service's token.
+
+    **get_services()**
+        Return a list of dicts with the registered services.
+
+
+Public Functions
+----------------
+
+**get_token_from_cookie(**\ request, cookie_name\ **)**
+    Given a Django request object and an Astakos cookie name
+    extract the user's token from it.
+
+
+Exceptions and Errors
+=====================
+
+*exception* **AstakosClientException**
+    Raised in case of an error. It contains an error message and the
+    corresponding http status code. Other exceptions raise by astakosclient
+    module are derived from this one.
+
+*exception* **BadRequest**
+    Raised in case of a Bad Request, with status 400.
+
+*exception* **Unauthorized**
+    Raised in case of Invalid token (unauthorized access), with status 401.
+
+*exception* **Forbidden**
+    The server understood the request, but is refusing to fulfill it.
+    Status 401.
+
+*exception* **NotFound**
+    The server has not found anything matching the Request-URI. Status 404.
+
+*exception* **NoUserName**
+    Raised by getDisplayName and getServiceDisplayName when an invalid
+    UUID was given.
+
+*exception* **NoUUID**
+    Raised by *getUUID* and *getServiceUUID* when an invalid
+    username was given.
diff --git a/astakosclient/setup.py b/astakosclient/setup.py
new file mode 100644
index 0000000..630f7ce
--- /dev/null
+++ b/astakosclient/setup.py
@@ -0,0 +1,182 @@
+# Copyright 2012, 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.
+#
+import distribute_setup
+distribute_setup.use_setuptools()
+
+import os
+import sys
+
+from distutils.util import convert_path
+from fnmatch import fnmatchcase
+from setuptools import setup, find_packages
+
+HERE = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
+
+from astakosclient.version import __version__
+
+# Package info
+VERSION = __version__
+README = open(os.path.join(HERE, 'README')).read()
+CHANGES = open(os.path.join(HERE, 'Changelog')).read()
+SHORT_DESCRIPTION = 'A set of tools to ease versioning and use of git flow.'
+
+PACKAGES_ROOT = '.'
+PACKAGES = find_packages(PACKAGES_ROOT)
+
+# Package meta
+CLASSIFIERS = []
+
+# Package requirements
+INSTALL_REQUIRES = [
+    "objpool >= 0.2",
+    "simplejson"
+]
+
+# Provided as an attribute, so you can append to these instead
+# of replicating them:
+standard_exclude = ["*.py", "*.pyc", "*$py.class", "*~", ".*", "*.bak"]
+standard_exclude_directories = [".*", "CVS", "_darcs", "./build", "./dist",
+                                "EGG-INFO", "*.egg-info"]
+
+
+# (c) 2005 Ian Bicking and contributors;
+#     written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license:
+#     http://www.opensource.org/licenses/mit-license.php
+# Note: you may want to copy this into your setup.py file verbatim, as
+# you can't import this from another package, when you don't know if
+# that package is installed yet.
+def find_package_data(
+        where=".",
+        package="",
+        exclude=standard_exclude,
+        exclude_directories=standard_exclude_directories,
+        only_in_packages=True,
+        show_ignored=False):
+    """
+    Return a dictionary suitable for use in ``package_data``
+    in a distutils ``setup.py`` file.
+
+    The dictionary looks like::
+
+        {"package": [files]}
+
+    Where ``files`` is a list of all the files in that package that
+    don"t match anything in ``exclude``.
+
+    If ``only_in_packages`` is true, then top-level directories that
+    are not packages won"t be included (but directories under packages
+    will).
+
+    Directories matching any pattern in ``exclude_directories`` will
+    be ignored; by default directories with leading ``.``, ``CVS``,
+    and ``_darcs`` will be ignored.
+
+    If ``show_ignored`` is true, then all the files that aren"t
+    included in package data are shown on stderr (for debugging
+    purposes).
+
+    Note patterns use wildcards, or can be exact paths (including
+    leading ``./``), and all searching is case-insensitive.
+    """
+    out = {}
+    stack = [(convert_path(where), "", package, only_in_packages)]
+    while stack:
+        where, prefix, package, only_in_packages = stack.pop(0)
+        for name in os.listdir(where):
+            fn = os.path.join(where, name)
+            if os.path.isdir(fn):
+                bad_name = False
+                for pattern in exclude_directories:
+                    if (fnmatchcase(name, pattern) or
+                            fn.lower() == pattern.lower()):
+                        bad_name = True
+                        if show_ignored:
+                            print >> sys.stderr, (
+                                "Directory %s ignored by pattern %s"
+                                % (fn, pattern))
+                        break
+                if bad_name:
+                    continue
+                if (os.path.isfile(os.path.join(fn, "__init__.py"))
+                        and not prefix):
+                    if not package:
+                        new_package = name
+                    else:
+                        new_package = package + "." + name
+                    stack.append((fn, "", new_package, False))
+                else:
+                    stack.append((fn, prefix + name + "/", package,
+                                  only_in_packages))
+            elif package or not only_in_packages:
+                # is a file
+                bad_name = False
+                for pattern in exclude:
+                    if (fnmatchcase(name, pattern) or
+                            fn.lower() == pattern.lower()):
+                        bad_name = True
+                        if show_ignored:
+                            print >> sys.stderr, (
+                                "File %s ignored by pattern %s"
+                                % (fn, pattern))
+                        break
+                if bad_name:
+                    continue
+                out.setdefault(package, []).append(prefix + name)
+    return out
+
+setup(
+    name='astakosclient',
+    version=VERSION,
+    license='BSD',
+    url='http://www.synnefo.org/',
+    description=SHORT_DESCRIPTION,
+    long_description=README + '\n\n' + CHANGES,
+    classifiers=CLASSIFIERS,
+
+    author='Synnefo development team',
+    author_email='synnefo-devel@googlegroups.com',
+    maintainer='Synnefo development team',
+    maintainer_email='synnefo-devel@googlegroups.com',
+
+    packages=PACKAGES,
+    package_dir={'': PACKAGES_ROOT},
+    include_package_data=True,
+    package_data=find_package_data('.'),
+    zip_safe=False,
+
+    install_requires=INSTALL_REQUIRES,
+
+    entry_points={},
+)
diff --git a/ci/config b/ci/config
index 6c9ef63..e9320ac 100644
--- a/ci/config
+++ b/ci/config
@@ -2,7 +2,7 @@ PROJECTS="\
  snf-common\
  snf-webproject\
  snf-astakos-app\
- snf-astakos-client\
+ astakosclient\
  snf-pithos-backend\
  snf-cyclades-gtools\
  snf-cyclades-app\
diff --git a/devflow.conf b/devflow.conf
index af514e1..8780f3c 100644
--- a/devflow.conf
+++ b/devflow.conf
@@ -19,7 +19,7 @@
       version_file = "snf-quotaholder-app/quotaholder_django/version.py"
   [[snf-stats-app]]
       version_file = "snf-stats-app/synnefo_stats/version.py"
-  [[snf-astakos-client]]
-      version_file = "snf-astakos-client/astakosclient/version.py"
+  [[python-astakosclient]]
+      version_file = "astakosclient/astakosclient/version.py"
   [[snf-django-lib]]
       version_file = "snf-django-lib/snf_django/version.py"
diff --git a/snf-astakos-client/COPYRIGHT b/snf-astakos-client/COPYRIGHT
deleted file mode 100644
index 245ccc1..0000000
--- a/snf-astakos-client/COPYRIGHT
+++ /dev/null
@@ -1,32 +0,0 @@
-Copyright (C) 2012, 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.
diff --git a/snf-astakos-client/Changelog b/snf-astakos-client/Changelog
deleted file mode 100644
index e69de29..0000000
diff --git a/snf-astakos-client/MANIFEST.in b/snf-astakos-client/MANIFEST.in
deleted file mode 100644
index 9a149cd..0000000
--- a/snf-astakos-client/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include distribute_setup.py
-include README Changelog
diff --git a/snf-astakos-client/README b/snf-astakos-client/README
deleted file mode 100644
index a3dc38a..0000000
--- a/snf-astakos-client/README
+++ /dev/null
@@ -1,11 +0,0 @@
-astakos-client
-==============
-
-Python client for the Astakos, the Identity Management Service of
-synnefo(http://www.synnefo.org)
-
-Feedback
-========
-
-For any comments or suggestions for improvement, please contact
-the synnefo development team <synnefo-devel@googlegroups.com>
diff --git a/snf-astakos-client/astakosclient/__init__.py b/snf-astakos-client/astakosclient/__init__.py
deleted file mode 100644
index a465947..0000000
--- a/snf-astakos-client/astakosclient/__init__.py
+++ /dev/null
@@ -1,317 +0,0 @@
-# Copyright (C) 2012, 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.
-
-import logging
-import urlparse
-import urllib
-import hashlib
-from copy import copy
-
-import simplejson
-from astakosclient.utils import retry, scheme_to_class
-from astakosclient.errors import \
-    AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
-    NoUserName, NoUUID
-
-
-# --------------------------------------------------------------------
-# Astakos Client Class
-
-def get_token_from_cookie(request, cookie_name):
-    """Extract token from the cookie name provided
-
-    Cookie should be in the same form as astakos
-    service sets its cookie contents:
-        <user_uniq>|<user_token>
-
-    """
-    try:
-        cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None))
-        return cookie_content.split("|")[1]
-    except:
-        return None
-
-
-class AstakosClient():
-    """AstakosClient Class Implementation"""
-
-    # ----------------------------------
-    def __init__(self, astakos_url, retry=0,
-                 use_pool=False, pool_size=8, logger=None):
-        """Intialize AstakosClient Class
-
-        Keyword arguments:
-        astakos_url -- i.e https://accounts.example.com (string)
-        use_pool    -- use objpool for http requests (boolean)
-        retry       -- how many time to retry (integer)
-        logger      -- pass a different logger
-
-        """
-        if logger is None:
-            logging.basicConfig(
-                format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
-                datefmt='%Y-%m-%d %H:%M:%S',
-                level=logging.INFO)
-            logger = logging.getLogger("astakosclient")
-        logger.debug("Intialize AstakosClient: astakos_url = %s, "
-                     "use_pool = %s" % (astakos_url, use_pool))
-
-        if not astakos_url:
-            m = "Astakos url not given"
-            logger.error(m)
-            raise ValueError(m)
-
-        # Check for supported scheme
-        p = urlparse.urlparse(astakos_url)
-        conn_class = scheme_to_class(p.scheme, use_pool, pool_size)
-        if conn_class is None:
-            m = "Unsupported scheme: %s" % p.scheme
-            logger.error(m)
-            raise ValueError(m)
-
-        # Save astakos_url etc. in our class
-        self.retry = retry
-        self.logger = logger
-        self.netloc = p.netloc
-        self.scheme = p.scheme
-        self.conn_class = conn_class
-
-    # ----------------------------------
-    @retry
-    def _call_astakos(self, token, request_path,
-                      headers=None, body=None, method="GET"):
-        """Make the actual call to Astakos Service"""
-        hashed_token = hashlib.sha1()
-        hashed_token.update(token)
-        self.logger.debug(
-            "Make a %s request to %s using token %s "
-            "with headers %s and body %s"
-            % (method, request_path, hashed_token.hexdigest(), headers, body))
-
-        # Check Input
-        if not token:
-            m = "Token not given"
-            self.logger.error(m)
-            raise ValueError(m)
-        if headers is None:
-            headers = {}
-        if body is None:
-            body = {}
-        if request_path[0] != '/':
-            request_path = "/" + request_path
-
-        # Build request's header and body
-        kwargs = {}
-        kwargs['headers'] = copy(headers)
-        kwargs['headers']['X-Auth-Token'] = token
-        if body:
-            kwargs['body'] = copy(body)
-            kwargs['headers'].setdefault(
-                'content-type', 'application/octet-stream')
-        kwargs['headers'].setdefault('content-length',
-                                     len(body) if body else 0)
-
-        try:
-            # Get the connection object
-            with self.conn_class(self.netloc) as conn:
-                # Send request
-                (data, status) = \
-                    _do_request(conn, method, request_path, **kwargs)
-        except Exception as err:
-            self.logger.error("Failed to send request: %s" % repr(err))
-            raise AstakosClientException(str(err))
-
-        # Return
-        self.logger.debug("Request returned with status %s" % status)
-        if status == 400:
-            raise BadRequest(data)
-        elif status == 401:
-            raise Unauthorized(data)
-        elif status == 403:
-            raise Forbidden(data)
-        elif status == 404:
-            raise NotFound(data)
-        elif status < 200 or status >= 300:
-            raise AstakosClientException(data, status)
-        return simplejson.loads(unicode(data))
-
-    # ------------------------
-    def get_user_info(self, token, usage=False):
-        """Authenticate user and get user's info as a dictionary
-
-        Keyword arguments:
-        token   -- user's token (string)
-        usage   -- return usage information for user (boolean)
-
-        In case of success return user information (json parsed format).
-        Otherwise raise an AstakosClientException.
-
-        """
-        # Send request
-        auth_path = "/im/authenticate"
-        if usage:
-            auth_path += "?usage=1"
-        return self._call_astakos(token, auth_path)
-
-    # ----------------------------------
-    def _uuid_catalog(self, token, uuids, req_path):
-        req_headers = {'content-type': 'application/json'}
-        req_body = simplejson.dumps({'uuids': uuids})
-        data = self._call_astakos(
-            token, req_path, req_headers, req_body, "POST")
-        if "uuid_catalog" in data:
-            return data.get("uuid_catalog")
-        else:
-            m = "_uuid_catalog request returned %s. No uuid_catalog found" \
-                % data
-            self.logger.error(m)
-            raise AstakosClientException(m)
-
-    def get_usernames(self, token, uuids):
-        """Return a uuid_catalog dictionary for the given uuids
-
-        Keyword arguments:
-        token   -- user's token (string)
-        uuids   -- list of user ids (list of strings)
-
-        The returned uuid_catalog is a dictionary with uuids as
-        keys and the corresponding user names as values
-
-        """
-        req_path = "/user_catalogs"
-        return self._uuid_catalog(token, uuids, req_path)
-
-    def get_username(self, token, uuid):
-        """Return the user name of a uuid (see get_usernames)"""
-        if not uuid:
-            m = "No uuid was given"
-            self.logger.error(m)
-            raise ValueError(m)
-        uuid_dict = self.get_usernames(token, [uuid])
-        if uuid in uuid_dict:
-            return uuid_dict.get(uuid)
-        else:
-            raise NoUserName(uuid)
-
-    def service_get_usernames(self, token, uuids):
-        """Return a uuid_catalog dict using a service's token"""
-        req_path = "/service/api/user_catalogs"
-        return self._uuid_catalog(token, uuids, req_path)
-
-    def service_get_username(self, token, uuid):
-        """Return the displayName of a uuid using a service's token"""
-        if not uuid:
-            m = "No uuid was given"
-            self.logger.error(m)
-            raise ValueError(m)
-        uuid_dict = self.service_get_usernames(token, [uuid])
-        if uuid in uuid_dict:
-            return uuid_dict.get(uuid)
-        else:
-            raise NoUserName(uuid)
-
-    # ----------------------------------
-    def _displayname_catalog(self, token, display_names, req_path):
-        req_headers = {'content-type': 'application/json'}
-        req_body = simplejson.dumps({'displaynames': display_names})
-        data = self._call_astakos(
-            token, req_path, req_headers, req_body, "POST")
-        if "displayname_catalog" in data:
-            return data.get("displayname_catalog")
-        else:
-            m = "_displayname_catalog request returned %s. " \
-                "No displayname_catalog found" % data
-            self.logger.error(m)
-            raise AstakosClientException(m)
-
-    def get_uuids(self, token, display_names):
-        """Return a displayname_catalog for the given names
-
-        Keyword arguments:
-        token           -- user's token (string)
-        display_names   -- list of user names (list of strings)
-
-        The returned displayname_catalog is a dictionary with
-        the names as keys and the corresponding uuids as values
-
-        """
-        req_path = "/user_catalogs"
-        return self._displayname_catalog(token, display_names, req_path)
-
-    def get_uuid(self, token, display_name):
-        """Return the uuid of a name (see getUUIDs)"""
-        if not display_name:
-            m = "No display_name was given"
-            self.logger.error(m)
-            raise ValueError(m)
-        name_dict = self.get_uuids(token, [display_name])
-        if display_name in name_dict:
-            return name_dict.get(display_name)
-        else:
-            raise NoUUID(display_name)
-
-    def service_get_uuids(self, token, display_names):
-        """Return a display_name catalog using a service's token"""
-        req_path = "/service/api/user_catalogs"
-        return self._displayname_catalog(token, display_names, req_path)
-
-    def service_get_uuid(self, token, display_name):
-        """Return the uuid of a name using a service's token"""
-        if not display_name:
-            m = "No display_name was given"
-            self.logger.error(m)
-            raise ValueError(m)
-        name_dict = self.service_get_uuids(token, [display_name])
-        if display_name in name_dict:
-            return name_dict.get(display_name)
-        else:
-            raise NoUUID(display_name)
-
-    # ----------------------------------
-    def get_services(self):
-        """Return a list of dicts with the registered services"""
-        return self._call_astakos("dummy token", "/im/get_services")
-
-
-# --------------------------------------------------------------------
-# Private functions
-# We want _doRequest to be a distinct function
-# so that we can replace it during unit tests.
-def _do_request(conn, method, url, **kwargs):
-    """The actual request. This function can easily be mocked"""
-    conn.request(method, url, **kwargs)
-    response = conn.getresponse()
-    length = response.getheader('content-length', None)
-    data = response.read(length)
-    status = int(response.status)
-    return (data, status)
diff --git a/snf-astakos-client/astakosclient/errors.py b/snf-astakos-client/astakosclient/errors.py
deleted file mode 100644
index 150d43e..0000000
--- a/snf-astakos-client/astakosclient/errors.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) 2012, 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.
-
-
-class AstakosClientException(Exception):
-    def __init__(self, message, status=0):
-        self.message = message
-        self.status = status
-
-    def __str__(self):
-        return repr(self.message)
-
-
-class BadRequest(AstakosClientException):
-    def __init__(self, message):
-        """400 Bad Request"""
-        super(BadRequest, self).__init__(message, 400)
-
-
-class Unauthorized(AstakosClientException):
-    def __init__(self, message):
-        """401 Invalid X-Auth-Token"""
-        super(Unauthorized, self).__init__(message, 401)
-
-
-class Forbidden(AstakosClientException):
-    def __init__(self, message):
-        """403 Forbidden"""
-        super(Forbidden, self).__init__(message, 403)
-
-
-class NotFound(AstakosClientException):
-    def __init__(self, message):
-        """404 Not Found"""
-        super(NotFound, self).__init__(message, 404)
-
-
-class NoUserName(AstakosClientException):
-    def __init__(self, uuid):
-        """No display name for the given uuid"""
-        message = "No display name for the given uuid: %s" % uuid
-        super(NoUserName, self).__init__(message)
-
-
-class NoUUID(AstakosClientException):
-    def __init__(self, display_name):
-        """No uuid for the given display name"""
-        message = "No uuid for the given display name: %s" % display_name
-        super(NoUUID, self).__init__(message)
diff --git a/snf-astakos-client/astakosclient/tests.py b/snf-astakos-client/astakosclient/tests.py
deleted file mode 100644
index 454d6a9..0000000
--- a/snf-astakos-client/astakosclient/tests.py
+++ /dev/null
@@ -1,662 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2012, 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.
-
-"""Unit Tests for the astakos-client module
-
-Provides unit tests for the code implementing
-the astakos client library
-
-"""
-
-import sys
-import socket
-import simplejson
-
-import astakosclient
-from astakosclient import AstakosClient
-from astakosclient.errors import \
-    AstakosClientException, Unauthorized, BadRequest, NotFound, \
-    NoUserName, NoUUID
-
-# Use backported unittest functionality if Python < 2.7
-try:
-    import unittest2 as unittest
-except ImportError:
-    if sys.version_info < (2, 7):
-        raise Exception("The unittest2 package is required for Python < 2.7")
-    import unittest
-
-
-# --------------------------------------------------------------------
-# Helper functions
-
-# ----------------------------
-# This functions will be used as mocked requests
-def _request_offline(conn, method, url, **kwargs):
-    """This request behaves as we were offline"""
-    raise socket.gaierror
-
-
-def _request_status_302(conn, method, url, **kwargs):
-    """This request returns 302"""
-    status = 302
-    data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
-        '<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
-        '<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
-    return (data, status)
-
-
-def _request_status_404(conn, method, url, **kwargs):
-    """This request returns 404"""
-    status = 404
-    data = '<html><head><title>404 Not Found</title></head>' \
-        '<body><h1>Not Found</h1><p>The requested URL /foo was ' \
-        'not found on this server.</p><hr><address>Apache Server ' \
-        'at example.com Port 80</address></body></html>'
-    return (data, status)
-
-
-def _request_status_401(conn, method, url, **kwargs):
-    """This request returns 401"""
-    status = 401
-    data = "Invalid X-Auth-Token\n"
-    return (data, status)
-
-
-def _request_status_400(conn, method, url, **kwargs):
-    """This request returns 400"""
-    status = 400
-    data = "Method not allowed.\n"
-    return (data, status)
-
-
-def _request_ok(conn, method, url, **kwargs):
-    """This request behaves like original Astakos does"""
-    if url[0:16] == "/im/authenticate":
-        return _req_authenticate(conn, method, url, **kwargs)
-    elif url[0:14] == "/user_catalogs":
-        return _req_catalogs(conn, method, url, **kwargs)
-    else:
-        return _request_status_404(conn, method, url, **kwargs)
-
-
-def _req_authenticate(conn, method, url, **kwargs):
-    """Check if user exists and return his profile"""
-    global user_1, user_2
-
-    # Check input
-    if conn.__class__.__name__ != "HTTPSConnection":
-        return _request_status_302(conn, method, url, **kwargs)
-
-    if method != "GET":
-        return _request_status_400(conn, method, url, **kwargs)
-
-    token = kwargs['headers']['X-Auth-Token']
-    if token == token_1:
-        user = dict(user_1)
-    elif token == token_2:
-        user = dict(user_2)
-    else:
-        # No user found
-        return _request_status_401(conn, method, url, **kwargs)
-
-    # Return
-    if "usage=1" not in url:
-        # Strip `usage' key from `user'
-        del user['usage']
-    return (simplejson.dumps(user), 200)
-
-
-def _req_catalogs(conn, method, url, **kwargs):
-    """Return user catalogs"""
-    global token_1, token_2, user_1, user_2
-
-    # Check input
-    if conn.__class__.__name__ != "HTTPSConnection":
-        return _request_status_302(conn, method, url, **kwargs)
-
-    if method != "POST":
-        return _request_status_400(conn, method, url, **kwargs)
-
-    token = kwargs['headers']['X-Auth-Token']
-    if token != token_1 and token != token_2:
-        return _request_status_401(conn, method, url, **kwargs)
-
-    # Return
-    body = simplejson.loads(kwargs['body'])
-    if 'uuids' in body:
-        # Return uuid_catalog
-        uuids = body['uuids']
-        catalogs = {}
-        if user_1['uuid'] in uuids:
-            catalogs[user_1['uuid']] = user_1['username']
-        if user_2['uuid'] in uuids:
-            catalogs[user_2['uuid']] = user_2['username']
-        return_catalog = {"displayname_catalog": {}, "uuid_catalog": catalogs}
-    elif 'displaynames' in body:
-        # Return displayname_catalog
-        names = body['displaynames']
-        catalogs = {}
-        if user_1['username'] in names:
-            catalogs[user_1['username']] = user_1['uuid']
-        if user_2['username'] in names:
-            catalogs[user_2['username']] = user_2['uuid']
-        return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}}
-    else:
-        return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}}
-    return (simplejson.dumps(return_catalog), 200)
-
-
-# ----------------------------
-# Mock the actual _doRequest
-def _mock_request(new_requests):
-    """Mock the actual request
-
-    Given a list of requests to use (in rotation),
-    replace the original _doRequest function with
-    a new one
-
-    """
-    def _mock(conn, method, url, **kwargs):
-        # Get first request
-        request = _mock.requests[0]
-        # Rotate requests
-        _mock.requests = _mock.requests[1:] + _mock.requests[:1]
-        # Use first request
-        return request(conn, method, url, **kwargs)
-
-    _mock.requests = new_requests
-    # Replace `_doRequest' with our `_mock'
-    astakosclient._do_request = _mock
-
-
-# ----------------------------
-# Local users
-token_1 = "skzleaFlBl+fasFdaf24sx=="
-user_1 = \
-    {"username": "user1@example.com",
-     "auth_token_created": 1359386939000,
-     "name": "Example User One",
-     "email": ["user1@example.com"],
-     "auth_token_expires": 1361978939000,
-     "id": 108,
-     "uuid": "73917abc-abcd-477e-a1f1-1763abcdefab",
-     "usage": [
-         {"currValue": 42949672960,
-          "display_name": "System Disk",
-          "name": "cyclades.disk"},
-         {"currValue": 4,
-          "display_name": "CPU",
-          "name": "cyclades.cpu"},
-         {"currValue": 4294967296,
-          "display_name": "RAM",
-          "name": "cyclades.ram"},
-         {"currValue": 3,
-          "display_name": "VM",
-          "name": "cyclades.vm"},
-         {"currValue": 0,
-          "display_name": "private network",
-          "name": "cyclades.network.private"},
-         {"currValue": 152,
-          "display_name": "Storage Space",
-          "name": "pithos+.diskspace"}]}
-
-token_2 = "fasdfDSFdf98923DF+sdfk=="
-user_2 = \
-    {"username": "user2@example.com",
-     "auth_token_created": 1358386938997,
-     "name": "Example User Two",
-     "email": ["user1@example.com"],
-     "auth_token_expires": 1461998939000,
-     "id": 109,
-     "uuid": "73917bca-1234-5678-a1f1-1763abcdefab",
-     "usage": [
-         {"currValue": 68719476736,
-          "display_name": "System Disk",
-          "name": "cyclades.disk"},
-         {"currValue": 1,
-          "display_name": "CPU",
-          "name": "cyclades.cpu"},
-         {"currValue": 1073741824,
-          "display_name": "RAM",
-          "name": "cyclades.ram"},
-         {"currValue": 2,
-          "display_name": "VM",
-          "name": "cyclades.vm"},
-         {"currValue": 1,
-          "display_name": "private network",
-          "name": "cyclades.network.private"},
-         {"currValue": 2341634510,
-          "display_name": "Storage Space",
-          "name": "pithos+.diskspace"}]}
-
-
-# --------------------------------------------------------------------
-# The actual tests
-
-class TestCallAstakos(unittest.TestCase):
-    """Test cases for function _callAstakos"""
-
-    # ----------------------------------
-    # Test the response we get if we don't have internet access
-    def _offline(self, pool):
-        global token_1
-        _mock_request([_request_offline])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/im/authenticate")
-        except AstakosClientException:
-            pass
-        else:
-            self.fail("Should have raised AstakosClientException")
-
-    def test_offline(self):
-        """Test _offline without pool"""
-        self._offline(False)
-
-    def test_offline_pool(self):
-        """Test _offline using pool"""
-        self._offline(True)
-
-    # ----------------------------------
-    # Test the response we get if we send invalid token
-    def _invalid_token(self, pool):
-        token = "skaksaFlBl+fasFdaf24sx=="
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client._call_astakos(token, "/im/authenticate")
-        except Unauthorized:
-            pass
-        except Exception:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-        else:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-
-    def test_invalid_token(self):
-        """Test _invalid_token without pool"""
-        self._invalid_token(False)
-
-    def test_invalid_token_pool(self):
-        """Test _invalid_token using pool"""
-        self._invalid_token(True)
-
-    # ----------------------------------
-    # Test the response we get if we send invalid url
-    def _invalid_url(self, pool):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/im/misspelled")
-        except NotFound:
-            pass
-        except Exception:
-            self.fail("Should have returned 404 (Not Found)")
-        else:
-            self.fail("Should have returned 404 (Not Found)")
-
-    def test_invalid_url(self):
-        """Test _invalid_url without pool"""
-        self._invalid_url(False)
-
-    def test_invalid_url_pool(self):
-        """Test _invalid_url using pool"""
-        self._invalid_url(True)
-
-    # ----------------------------------
-    # Test the response we get if we use an unsupported scheme
-    def _unsupported_scheme(self, pool):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("ftp://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/im/authenticate")
-        except ValueError:
-            pass
-        except Exception:
-            self.fail("Should have raise ValueError Exception")
-        else:
-            self.fail("Should have raise ValueError Exception")
-
-    def test_unsupported_scheme(self):
-        """Test _unsupported_scheme without pool"""
-        self._unsupported_scheme(False)
-
-    def test_unsupported_scheme_pool(self):
-        """Test _unsupported_scheme using pool"""
-        self._unsupported_scheme(True)
-
-    # ----------------------------------
-    # Test the response we get if we use http instead of https
-    def _http_scheme(self, pool):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("http://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/im/authenticate")
-        except AstakosClientException as err:
-            if err.status != 302:
-                self.fail("Should have returned 302 (Found)")
-        else:
-            self.fail("Should have returned 302 (Found)")
-
-    def test_http_scheme(self):
-        """Test _http_scheme without pool"""
-        self._http_scheme(False)
-
-    def test_http_scheme_pool(self):
-        """Test _http_scheme using pool"""
-        self._http_scheme(True)
-
-    # ----------------------------------
-    # Test the response we get if we use authenticate with POST
-    def _post_authenticate(self, pool):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/im/authenticate", method="POST")
-        except BadRequest:
-            pass
-        except Exception:
-            self.fail("Should have returned 400 (Method not allowed)")
-        else:
-            self.fail("Should have returned 400 (Method not allowed)")
-
-    def test_post_authenticate(self):
-        """Test _post_authenticate without pool"""
-        self._post_authenticate(False)
-
-    def test_post_authenticate_pool(self):
-        """Test _post_authenticate using pool"""
-        self._post_authenticate(True)
-
-    # ----------------------------------
-    # Test the response if we request user_catalogs with GET
-    def _get_user_catalogs(self, pool):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client._call_astakos(token_1, "/user_catalogs")
-        except BadRequest:
-            pass
-        except Exception:
-            self.fail("Should have returned 400 (Method not allowed)")
-        else:
-            self.fail("Should have returned 400 (Method not allowed)")
-
-    def test_get_user_catalogs(self):
-        """Test _get_user_catalogs without pool"""
-        self._get_user_catalogs(False)
-
-    def test_get_user_catalogs_pool(self):
-        """Test _get_user_catalogs using pool"""
-        self._get_user_catalogs(True)
-
-
-class TestAuthenticate(unittest.TestCase):
-    """Test cases for function getUserInfo"""
-
-    # ----------------------------------
-    # Test the response we get if we don't have internet access
-    def test_offline(self):
-        """Test offline after 3 retries"""
-        global token_1
-        _mock_request([_request_offline])
-        try:
-            client = AstakosClient("https://example.com", retry=3)
-            client.get_user_info(token_1)
-        except AstakosClientException:
-            pass
-        else:
-            self.fail("Should have raised AstakosClientException exception")
-
-    # ----------------------------------
-    # Test the response we get for invalid token
-    def _invalid_token(self, pool):
-        token = "skaksaFlBl+fasFdaf24sx=="
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            client.get_user_info(token)
-        except Unauthorized:
-            pass
-        except Exception:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-        else:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-
-    def test_invalid_token(self):
-        """Test _invalid_token without pool"""
-        self._invalid_token(False)
-
-    def test_invalid_token_pool(self):
-        """Test _invalid_token using pool"""
-        self._invalid_token(True)
-
-    #- ---------------------------------
-    # Test response for user 1
-    def _auth_user(self, token, user_info, usage, pool):
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com", use_pool=pool)
-            auth_info = client.get_user_info(token, usage=usage)
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(user_info, auth_info)
-
-    def test_auth_user_one(self):
-        """Test _auth_user for User 1 without pool, without usage"""
-        global token_1, user_1
-        user_info = dict(user_1)
-        del user_info['usage']
-        self._auth_user(token_1, user_info, False, False)
-
-    def test_auth_user_one_usage(self):
-        """Test _auth_user for User 1 without pool, with usage"""
-        global token_1, user_1
-        self._auth_user(token_1, user_1, True, False)
-
-    def test_auth_user_one_usage_pool(self):
-        """Test _auth_user for User 1 using pool, with usage"""
-        global token_1, user_1
-        self._auth_user(token_1, user_1, True, True)
-
-    def test_auth_user_two(self):
-        """Test _auth_user for User 2 without pool, without usage"""
-        global token_2, user_2
-        user_info = dict(user_2)
-        del user_info['usage']
-        self._auth_user(token_2, user_info, False, False)
-
-    def test_auth_user_two_usage(self):
-        """Test _auth_user for User 2 without pool, with usage"""
-        global token_2, user_2
-        self._auth_user(token_2, user_2, True, False)
-
-    def test_auth_user_two_usage_pool(self):
-        """Test _auth_user for User 2 using pool, with usage"""
-        global token_2, user_2
-        self._auth_user(token_2, user_2, True, True)
-
-    # ----------------------------------
-    # Test retry functionality
-    def test_offline_retry(self):
-        """Test retry functionality for getUserInfo"""
-        global token_1, user_1
-        _mock_request([_request_offline, _request_offline, _request_ok])
-        try:
-            client = AstakosClient("https://example.com", retry=2)
-            auth_info = client.get_user_info(token_1, usage=True)
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(user_1, auth_info)
-
-
-class TestDisplayNames(unittest.TestCase):
-    """Test cases for functions getDisplayNames/getDisplayName"""
-
-    # ----------------------------------
-    # Test the response we get for invalid token
-    def test_invalid_token(self):
-        """Test the response we get for invalid token (without pool)"""
-        global user_1
-        token = "skaksaFlBl+fasFdaf24sx=="
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            client.get_usernames(token, [user_1['uuid']])
-        except Unauthorized:
-            pass
-        except Exception:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-        else:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-
-    # ----------------------------------
-    # Get Info for both users
-    def test_usernames(self):
-        """Test get_usernames with both users"""
-        global token_1, user_1, user_2
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            catalog = client.get_usernames(
-                token_1, [user_1['uuid'], user_2['uuid']])
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(catalog[user_1['uuid']], user_1['username'])
-        self.assertEqual(catalog[user_2['uuid']], user_2['username'])
-
-    # ----------------------------------
-    # Get info for user 1
-    def test_username_user_one(self):
-        """Test get_username for User One"""
-        global token_2, user_1
-        _mock_request([_request_offline, _request_ok])
-        try:
-            client = AstakosClient(
-                "https://example.com", use_pool=True, retry=2)
-            info = client.get_username(token_2, user_1['uuid'])
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(info, user_1['username'])
-
-    # ----------------------------------
-    # Get info with wrong uuid
-    def test_no_username(self):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            client.get_username(token_1, "1234")
-        except NoUserName:
-            pass
-        except:
-            self.fail("Should have raised NoDisplayName exception")
-        else:
-            self.fail("Should have raised NoDisplayName exception")
-
-
-class TestGetUUIDs(unittest.TestCase):
-    """Test cases for functions getUUIDs/getUUID"""
-
-    # ----------------------------------
-    # Test the response we get for invalid token
-    def test_invalid_token(self):
-        """Test the response we get for invalid token (using pool)"""
-        global user_1
-        token = "skaksaFlBl+fasFdaf24sx=="
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            client.get_uuids(token, [user_1['username']])
-        except Unauthorized:
-            pass
-        except Exception:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-        else:
-            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
-
-    # ----------------------------------
-    # Get info for both users
-    def test_uuids(self):
-        """Test get_uuids with both users"""
-        global token_1, user_1, user_2
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            catalog = client.get_uuids(
-                token_1, [user_1['username'], user_2['username']])
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(catalog[user_1['username']], user_1['uuid'])
-        self.assertEqual(catalog[user_2['username']], user_2['uuid'])
-
-    # ----------------------------------
-    # Get uuid for user 2
-    def test_get_uuid_user_two(self):
-        """Test get_uuid for User Two"""
-        global token_1, user_2
-        _mock_request([_request_offline, _request_ok])
-        try:
-            client = AstakosClient("https://example.com", retry=1)
-            info = client.get_uuid(token_2, user_1['username'])
-        except:
-            self.fail("Shouldn't raise an Exception")
-        self.assertEqual(info, user_1['uuid'])
-
-    # ----------------------------------
-    # Get uuid with wrong username
-    def test_no_uuid(self):
-        global token_1
-        _mock_request([_request_ok])
-        try:
-            client = AstakosClient("https://example.com")
-            client.get_uuid(token_1, "1234")
-        except NoUUID:
-            pass
-        except:
-            self.fail("Should have raised NoUUID exception")
-        else:
-            self.fail("Should have raised NoUUID exception")
-
-
-# ----------------------------
-# Run tests
-if __name__ == "__main__":
-    unittest.main()
diff --git a/snf-astakos-client/astakosclient/utils.py b/snf-astakos-client/astakosclient/utils.py
deleted file mode 100644
index 534c01c4..0000000
--- a/snf-astakos-client/astakosclient/utils.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright (C) 2012, 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 httplib import HTTPConnection, HTTPSConnection
-from contextlib import closing
-
-from objpool.http import PooledHTTPConnection
-from astakosclient.errors import AstakosClientException
-
-
-def retry(func):
-    def decorator(self, *args, **kwargs):
-        attemps = 0
-        while True:
-            try:
-                return func(self, *args, **kwargs)
-            except AstakosClientException as err:
-                is_last_attempt = attemps == self.retry
-                if is_last_attempt:
-                    raise err
-                if err.status == 401 or err.status == 404:
-                    # In case of Unauthorized response
-                    # or Not Found return immediately
-                    raise err
-                self.logger.info("AstakosClient request failed..retrying")
-                attemps += 1
-    return decorator
-
-
-def scheme_to_class(scheme, use_pool, pool_size):
-    """Return the appropriate conn class for given scheme"""
-    def _objpool(netloc):
-        return PooledHTTPConnection(
-            netloc=netloc, scheme=scheme, pool_size=pool_size)
-
-    def _http_connection(netloc):
-        return closing(HTTPConnection(netloc))
-
-    def _https_connection(netloc):
-        return closing(HTTPSConnection(netloc))
-
-    if scheme == "http":
-        if use_pool:
-            return _objpool
-        else:
-            return _http_connection
-    elif scheme == "https":
-        if use_pool:
-            return _objpool
-        else:
-            return _https_connection
-    else:
-        return None
diff --git a/snf-astakos-client/distribute_setup.py b/snf-astakos-client/distribute_setup.py
deleted file mode 100644
index 10d6684..0000000
--- a/snf-astakos-client/distribute_setup.py
+++ /dev/null
@@ -1,485 +0,0 @@
-#!python
-"""Bootstrap distribute installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from distribute_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import os
-import sys
-import time
-import fnmatch
-import tempfile
-import tarfile
-from distutils import log
-
-try:
-    from site import USER_SITE
-except ImportError:
-    USER_SITE = None
-
-try:
-    import subprocess
-
-    def _python_cmd(*args):
-        args = (sys.executable,) + args
-        return subprocess.call(args) == 0
-
-except ImportError:
-    # will be used for python 2.3
-    def _python_cmd(*args):
-        args = (sys.executable,) + args
-        # quoting arguments if windows
-        if sys.platform == 'win32':
-            def quote(arg):
-                if ' ' in arg:
-                    return '"%s"' % arg
-                return arg
-            args = [quote(arg) for arg in args]
-        return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
-
-DEFAULT_VERSION = "0.6.10"
-DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
-SETUPTOOLS_FAKED_VERSION = "0.6c11"
-
-SETUPTOOLS_PKG_INFO = """\
-Metadata-Version: 1.0
-Name: setuptools
-Version: %s
-Summary: xxxx
-Home-page: xxx
-Author: xxx
-Author-email: xxx
-License: xxx
-Description: xxx
-""" % SETUPTOOLS_FAKED_VERSION
-
-
-def _install(tarball):
-    # extracting the tarball
-    tmpdir = tempfile.mkdtemp()
-    log.warn('Extracting in %s', tmpdir)
-    old_wd = os.getcwd()
-    try:
-        os.chdir(tmpdir)
-        tar = tarfile.open(tarball)
-        _extractall(tar)
-        tar.close()
-
-        # going in the directory
-        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
-        os.chdir(subdir)
-        log.warn('Now working in %s', subdir)
-
-        # installing
-        log.warn('Installing Distribute')
-        if not _python_cmd('setup.py', 'install'):
-            log.warn('Something went wrong during the installation.')
-            log.warn('See the error message above.')
-    finally:
-        os.chdir(old_wd)
-
-
-def _build_egg(egg, tarball, to_dir):
-    # extracting the tarball
-    tmpdir = tempfile.mkdtemp()
-    log.warn('Extracting in %s', tmpdir)
-    old_wd = os.getcwd()
-    try:
-        os.chdir(tmpdir)
-        tar = tarfile.open(tarball)
-        _extractall(tar)
-        tar.close()
-
-        # going in the directory
-        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
-        os.chdir(subdir)
-        log.warn('Now working in %s', subdir)
-
-        # building an egg
-        log.warn('Building a Distribute egg in %s', to_dir)
-        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
-
-    finally:
-        os.chdir(old_wd)
-    # returning the result
-    log.warn(egg)
-    if not os.path.exists(egg):
-        raise IOError('Could not build the egg.')
-
-
-def _do_download(version, download_base, to_dir, download_delay):
-    egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
-                       % (version, sys.version_info[0], sys.version_info[1]))
-    if not os.path.exists(egg):
-        tarball = download_setuptools(version, download_base,
-                                      to_dir, download_delay)
-        _build_egg(egg, tarball, to_dir)
-    sys.path.insert(0, egg)
-    import setuptools
-    setuptools.bootstrap_install_from = egg
-
-
-def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
-                   to_dir=os.curdir, download_delay=15, no_fake=True):
-    # making sure we use the absolute path
-    to_dir = os.path.abspath(to_dir)
-    was_imported = 'pkg_resources' in sys.modules or \
-        'setuptools' in sys.modules
-    try:
-        try:
-            import pkg_resources
-            if not hasattr(pkg_resources, '_distribute'):
-                if not no_fake:
-                    _fake_setuptools()
-                raise ImportError
-        except ImportError:
-            return _do_download(version, download_base, to_dir, download_delay)
-        try:
-            pkg_resources.require("distribute>="+version)
-            return
-        except pkg_resources.VersionConflict:
-            e = sys.exc_info()[1]
-            if was_imported:
-                sys.stderr.write(
-                "The required version of distribute (>=%s) is not available,\n"
-                "and can't be installed while this script is running. Please\n"
-                "install a more recent version first, using\n"
-                "'easy_install -U distribute'."
-                "\n\n(Currently using %r)\n" % (version, e.args[0]))
-                sys.exit(2)
-            else:
-                del pkg_resources, sys.modules['pkg_resources']    # reload ok
-                return _do_download(version, download_base, to_dir,
-                                    download_delay)
-        except pkg_resources.DistributionNotFound:
-            return _do_download(version, download_base, to_dir,
-                                download_delay)
-    finally:
-        if not no_fake:
-            _create_fake_setuptools_pkg_info(to_dir)
-
-def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
-                        to_dir=os.curdir, delay=15):
-    """Download distribute from a specified location and return its filename
-
-    `version` should be a valid distribute version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download
-    attempt.
-    """
-    # making sure we use the absolute path
-    to_dir = os.path.abspath(to_dir)
-    try:
-        from urllib.request import urlopen
-    except ImportError:
-        from urllib2 import urlopen
-    tgz_name = "distribute-%s.tar.gz" % version
-    url = download_base + tgz_name
-    saveto = os.path.join(to_dir, tgz_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            log.warn("Downloading %s", url)
-            src = urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = src.read()
-            dst = open(saveto, "wb")
-            dst.write(data)
-        finally:
-            if src:
-                src.close()
-            if dst:
-                dst.close()
-    return os.path.realpath(saveto)
-
-def _no_sandbox(function):
-    def __no_sandbox(*args, **kw):
-        try:
-            from setuptools.sandbox import DirectorySandbox
-            if not hasattr(DirectorySandbox, '_old'):
-                def violation(*args):
-                    pass
-                DirectorySandbox._old = DirectorySandbox._violation
-                DirectorySandbox._violation = violation
-                patched = True
-            else:
-                patched = False
-        except ImportError:
-            patched = False
-
-        try:
-            return function(*args, **kw)
-        finally:
-            if patched:
-                DirectorySandbox._violation = DirectorySandbox._old
-                del DirectorySandbox._old
-
-    return __no_sandbox
-
-def _patch_file(path, content):
-    """Will backup the file then patch it"""
-    existing_content = open(path).read()
-    if existing_content == content:
-        # already patched
-        log.warn('Already patched.')
-        return False
-    log.warn('Patching...')
-    _rename_path(path)
-    f = open(path, 'w')
-    try:
-        f.write(content)
-    finally:
-        f.close()
-    return True
-
-_patch_file = _no_sandbox(_patch_file)
-
-def _same_content(path, content):
-    return open(path).read() == content
-
-def _rename_path(path):
-    new_name = path + '.OLD.%s' % time.time()
-    log.warn('Renaming %s into %s', path, new_name)
-    os.rename(path, new_name)
-    return new_name
-
-def _remove_flat_installation(placeholder):
-    if not os.path.isdir(placeholder):
-        log.warn('Unkown installation at %s', placeholder)
-        return False
-    found = False
-    for file in os.listdir(placeholder):
-        if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
-            found = True
-            break
-    if not found:
-        log.warn('Could not locate setuptools*.egg-info')
-        return
-
-    log.warn('Removing elements out of the way...')
-    pkg_info = os.path.join(placeholder, file)
-    if os.path.isdir(pkg_info):
-        patched = _patch_egg_dir(pkg_info)
-    else:
-        patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
-
-    if not patched:
-        log.warn('%s already patched.', pkg_info)
-        return False
-    # now let's move the files out of the way
-    for element in ('setuptools', 'pkg_resources.py', 'site.py'):
-        element = os.path.join(placeholder, element)
-        if os.path.exists(element):
-            _rename_path(element)
-        else:
-            log.warn('Could not find the %s element of the '
-                     'Setuptools distribution', element)
-    return True
-
-_remove_flat_installation = _no_sandbox(_remove_flat_installation)
-
-def _after_install(dist):
-    log.warn('After install bootstrap.')
-    placeholder = dist.get_command_obj('install').install_purelib
-    _create_fake_setuptools_pkg_info(placeholder)
-
-def _create_fake_setuptools_pkg_info(placeholder):
-    if not placeholder or not os.path.exists(placeholder):
-        log.warn('Could not find the install location')
-        return
-    pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
-    setuptools_file = 'setuptools-%s-py%s.egg-info' % \
-            (SETUPTOOLS_FAKED_VERSION, pyver)
-    pkg_info = os.path.join(placeholder, setuptools_file)
-    if os.path.exists(pkg_info):
-        log.warn('%s already exists', pkg_info)
-        return
-
-    log.warn('Creating %s', pkg_info)
-    f = open(pkg_info, 'w')
-    try:
-        f.write(SETUPTOOLS_PKG_INFO)
-    finally:
-        f.close()
-
-    pth_file = os.path.join(placeholder, 'setuptools.pth')
-    log.warn('Creating %s', pth_file)
-    f = open(pth_file, 'w')
-    try:
-        f.write(os.path.join(os.curdir, setuptools_file))
-    finally:
-        f.close()
-
-_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
-
-def _patch_egg_dir(path):
-    # let's check if it's already patched
-    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
-    if os.path.exists(pkg_info):
-        if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
-            log.warn('%s already patched.', pkg_info)
-            return False
-    _rename_path(path)
-    os.mkdir(path)
-    os.mkdir(os.path.join(path, 'EGG-INFO'))
-    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
-    f = open(pkg_info, 'w')
-    try:
-        f.write(SETUPTOOLS_PKG_INFO)
-    finally:
-        f.close()
-    return True
-
-_patch_egg_dir = _no_sandbox(_patch_egg_dir)
-
-def _before_install():
-    log.warn('Before install bootstrap.')
-    _fake_setuptools()
-
-
-def _under_prefix(location):
-    if 'install' not in sys.argv:
-        return True
-    args = sys.argv[sys.argv.index('install')+1:]
-    for index, arg in enumerate(args):
-        for option in ('--root', '--prefix'):
-            if arg.startswith('%s=' % option):
-                top_dir = arg.split('root=')[-1]
-                return location.startswith(top_dir)
-            elif arg == option:
-                if len(args) > index:
-                    top_dir = args[index+1]
-                    return location.startswith(top_dir)
-        if arg == '--user' and USER_SITE is not None:
-            return location.startswith(USER_SITE)
-    return True
-
-
-def _fake_setuptools():
-    log.warn('Scanning installed packages')
-    try:
-        import pkg_resources
-    except ImportError:
-        # we're cool
-        log.warn('Setuptools or Distribute does not seem to be installed.')
-        return
-    ws = pkg_resources.working_set
-    try:
-        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
-                                  replacement=False))
-    except TypeError:
-        # old distribute API
-        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
-
-    if setuptools_dist is None:
-        log.warn('No setuptools distribution found')
-        return
-    # detecting if it was already faked
-    setuptools_location = setuptools_dist.location
-    log.warn('Setuptools installation detected at %s', setuptools_location)
-
-    # if --root or --preix was provided, and if
-    # setuptools is not located in them, we don't patch it
-    if not _under_prefix(setuptools_location):
-        log.warn('Not patching, --root or --prefix is installing Distribute'
-                 ' in another location')
-        return
-
-    # let's see if its an egg
-    if not setuptools_location.endswith('.egg'):
-        log.warn('Non-egg installation')
-        res = _remove_flat_installation(setuptools_location)
-        if not res:
-            return
-    else:
-        log.warn('Egg installation')
-        pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
-        if (os.path.exists(pkg_info) and
-            _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
-            log.warn('Already patched.')
-            return
-        log.warn('Patching...')
-        # let's create a fake egg replacing setuptools one
-        res = _patch_egg_dir(setuptools_location)
-        if not res:
-            return
-    log.warn('Patched done.')
-    _relaunch()
-
-
-def _relaunch():
-    log.warn('Relaunching...')
-    # we have to relaunch the process
-    # pip marker to avoid a relaunch bug
-    if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
-        sys.argv[0] = 'setup.py'
-    args = [sys.executable] + sys.argv
-    sys.exit(subprocess.call(args))
-
-
-def _extractall(self, path=".", members=None):
-    """Extract all members from the archive to the current working
-       directory and set owner, modification time and permissions on
-       directories afterwards. `path' specifies a different directory
-       to extract to. `members' is optional and must be a subset of the
-       list returned by getmembers().
-    """
-    import copy
-    import operator
-    from tarfile import ExtractError
-    directories = []
-
-    if members is None:
-        members = self
-
-    for tarinfo in members:
-        if tarinfo.isdir():
-            # Extract directories with a safe mode.
-            directories.append(tarinfo)
-            tarinfo = copy.copy(tarinfo)
-            tarinfo.mode = 448 # decimal for oct 0700
-        self.extract(tarinfo, path)
-
-    # Reverse sort directories.
-    if sys.version_info < (2, 4):
-        def sorter(dir1, dir2):
-            return cmp(dir1.name, dir2.name)
-        directories.sort(sorter)
-        directories.reverse()
-    else:
-        directories.sort(key=operator.attrgetter('name'), reverse=True)
-
-    # Set correct owner, mtime and filemode on directories.
-    for tarinfo in directories:
-        dirpath = os.path.join(path, tarinfo.name)
-        try:
-            self.chown(tarinfo, dirpath)
-            self.utime(tarinfo, dirpath)
-            self.chmod(tarinfo, dirpath)
-        except ExtractError:
-            e = sys.exc_info()[1]
-            if self.errorlevel > 1:
-                raise
-            else:
-                self._dbg(1, "tarfile: %s" % e)
-
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-    tarball = download_setuptools()
-    _install(tarball)
-
-
-if __name__ == '__main__':
-    main(sys.argv[1:])
diff --git a/snf-astakos-client/docs/Makefile b/snf-astakos-client/docs/Makefile
deleted file mode 100644
index 38fd314..0000000
--- a/snf-astakos-client/docs/Makefile
+++ /dev/null
@@ -1,153 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-BUILDDIR      = _build
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  dirhtml    to make HTML files named index.html in directories"
-	@echo "  singlehtml to make a single large HTML file"
-	@echo "  pickle     to make pickle files"
-	@echo "  json       to make JSON files"
-	@echo "  htmlhelp   to make HTML files and a HTML help project"
-	@echo "  qthelp     to make HTML files and a qthelp project"
-	@echo "  devhelp    to make HTML files and a Devhelp project"
-	@echo "  epub       to make an epub"
-	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  text       to make text files"
-	@echo "  man        to make manual pages"
-	@echo "  texinfo    to make Texinfo files"
-	@echo "  info       to make Texinfo files and run them through makeinfo"
-	@echo "  gettext    to make PO message catalogs"
-	@echo "  changes    to make an overview of all changed/added/deprecated items"
-	@echo "  linkcheck  to check all external links for integrity"
-	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
-
-clean:
-	-rm -rf $(BUILDDIR)/*
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/synnefo.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/synnefo.qhc"
-
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/synnefo"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/synnefo"
-	@echo "# devhelp"
-
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-info:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
-	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
-	@echo
-	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/snf-astakos-client/docs/conf.py b/snf-astakos-client/docs/conf.py
deleted file mode 100644
index 4ebdb75..0000000
--- a/snf-astakos-client/docs/conf.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import sys
-import os
-
-sys.path.insert(0, os.path.abspath('..'))
-from astakosclient.version import __version__
-
-project = u'nnefo'
-copyright = u'2012-2013, GRNET'
-version = __version__
-release = __version__
-html_title = 'synnefo ' + version
-
-templates_path = ['_templates']
-source_suffix = '.rst'
-master_doc = 'index'
-exclude_patterns = ['_build']
-pygments_style = 'sphinx'
-html_theme = 'default'
-html_theme_options = {
-    'collapsiblesidebar': 'true',
-    'footerbgcolor':    '#55b577',
-    'footertextcolor':  '#000000',
-    'sidebarbgcolor':   '#ffffff',
-    'sidebarbtncolor':  '#f2f2f2',
-    'sidebartextcolor': '#000000',
-    'sidebarlinkcolor': '#328e4a',
-    'relbarbgcolor':    '#55b577',
-    'relbartextcolor':  '#ffffff',
-    'relbarlinkcolor':  '#ffffff',
-    'bgcolor':          '#ffffff',
-    'textcolor':        '#000000',
-    'headbgcolor':      '#ffffff',
-    'headtextcolor':    '#000000',
-    'headlinkcolor':    '#c60f0f',
-    'linkcolor':        '#328e4a',
-    'visitedlinkcolor': '#63409b',
-    'codebgcolor':      '#eeffcc',
-    'codetextcolor':    '#333333'
-}
-
-html_static_path = ['_static']
-htmlhelp_basename = 'synnefodoc'
-
-intersphinx_mapping = {
-    'pithon': ('http://docs.python.org/', None),
-    'django': ('https://docs.djangoproject.com/en/dev/',
-               'https://docs.djangoproject.com/en/dev/_objects/')
-}
-
-SYNNEFO_DOCS_BASE_URL = 'http://docs.dev.grnet.gr/'
-SYNNEFO_PROJECTS = {
-    'synnefo': 'dev',
-    'pithos': 'dev',
-    'snf-webproject': 'dev',
-    'snf-common': 'dev',
-    'snf-image': 'dev',
-    'snf-cyclades-app': 'dev'
-}
-
-for name, ver in SYNNEFO_PROJECTS.iteritems():
-    intersphinx_mapping[name.replace("-", "")] = (SYNNEFO_DOCS_BASE_URL +
-                                                  '%s/%s/' % (name, ver),
-                                                  None)
-
-extensions = ['sphinx.ext.autodoc',
-              'sphinx.ext.intersphinx',
-              'sphinx.ext.todo',
-              'sphinx.ext.viewcode']
diff --git a/snf-astakos-client/docs/index.rst b/snf-astakos-client/docs/index.rst
deleted file mode 100644
index dee4334..0000000
--- a/snf-astakos-client/docs/index.rst
+++ /dev/null
@@ -1,153 +0,0 @@
-.. _snf-astakos-client:
-
-Component snf-astakos-client
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The Synnefo component :ref:`snf-astakos-client <snf-astakos-client>` defines a
-default client for the :ref:`Astakos <astakos>` service. It is designed to be
-simple and minimal, hence easy to debug and test.
-
-It uses the user's authentication token to query Astakos for:
-
-    * User's info
-    * Usernames for given UUIDs
-    * UUIDs for given usernames
-
-It can also query Astakos with another service's (Cyclades or Pithos)
-authentication token for:
-
-    * Usernames for given UUIDs
-    * UUIDs for given usernames
-
-Additionally, there are options for using the `objpool
-<https://github.com/grnet/objpool>`_ library to pool the http connections.
-
-
-Basic example
-=============
-
-The ``astakosclient`` module provides the ``AstakosClient`` class. This section
-demonstrates how to get user's info using ``astakosclient``.
-
-.. code-block:: python
-
-    from astakosclient import AstakosClient
-
-    client = AstakosClient("https://accounts.example.com")
-    user_info = client.get_user_info("UQpYas7ElzWGD5yCcEXtjw==")
-    print user_info['username']
-
-Another example where we ask for the username of a user with UUID:
-``b3de8eb0-3958-477e-als9-789af8dd352c``
-
-.. code-block:: python
-
-    from astakosclient import AstakosClient
-
-    client = AstakosClient("https://accounts.example.com")
-    username = client.get_username("UQpYas7ElzWGD5yCcEXtjw==",
-                                   "b3de8eb0-3958-477e-als9-789af8dd352c")
-    print username
-
-
-Classes and functions
-=====================
-
-This section describes in depth the API of ``astakosclient``.
-
-Astakos Client
---------------
-
-*class* astakosclient.\ **AstakosClient(**\ astakos_url,
-retry=0, use_pool=False, pool_size=8, logger=None\ **)**
-
-    Initialize an instance of **AstakosClient** given the *astakos_url*.
-    Optionally one can specify if we are going to use a pool, the pool_size
-    and the number of retries if the connection fails.
-
-    This class provides the following methods:
-
-    **get_user_info(**\ token, usage=False\ **)**
-        Given a valid authentication token it returns a dict with the
-        correspoinding user's info. If usage is set to True more
-        information about user's resources will be returned.
-        In case of error raise an AstakosClientException exception.
-
-    **get_usernames(**\ token, uuids\ **)**
-        Given a valid authentication token and a list of UUIDs
-        return a uuid_catalog, that is a dictionary with the given
-        UUIDs as keys and the corresponding user names as values.
-        Invalid UUIDs will not be in the dictionary.
-        In case of error raise an AstakosClientException exception.
-
-    **get_username(**\ token, uuid\ **)**
-        Given a valid authentication token and a UUID (as string)
-        return the corresponding user name (as string).
-        In case of invalid UUID raise NoUserName exception.
-        In case of error raise an AstakosClientException exception.
-
-    **service_get_usernames(**\ token, uuids\ **)**
-        Same as get_usernames but called with a service's token.
-
-    **service_get_username(**\ token, uuid\ **)**
-        Same as get_username but called with a service's token.
-
-    **get_uuids(**\ token, display_names\ **)**
-        Given a valid authentication token and a list of usernames
-        return a displayname_catalog, that is a dictionary with the given
-        usernames as keys and the corresponding UUIDs as values.
-        Invalid usernames will not be in the dictionary.
-        In case of error raise an AstakosClientException exception.
-
-    **get_uuid(**\ token, display_name\ **)**
-        Given a valid authentication token and a username (as string)
-        return the corresponding UUID (as string).
-        In case of invalid user name raise NoUUID exception.
-        In case of error raise an AstakosClientException exception.
-
-    **service_get_uuids(**\ token, uuids\ **)**
-        Same as get_uuids but called with a service's token.
-
-    **service_get_uuid(**\ token, uuid\ **)**
-        Same as get_uuid but called with a service's token.
-
-    **get_services()**
-        Return a list of dicts with the registered services.
-
-
-Public Functions
-----------------
-
-**get_token_from_cookie(**\ request, cookie_name\ **)**
-    Given a Django request object and an Astakos cookie name
-    extract the user's token from it.
-
-
-Exceptions and Errors
-=====================
-
-*exception* **AstakosClientException**
-    Raised in case of an error. It contains an error message and the
-    corresponding http status code. Other exceptions raise by astakosclient
-    module are derived from this one.
-
-*exception* **BadRequest**
-    Raised in case of a Bad Request, with status 400.
-
-*exception* **Unauthorized**
-    Raised in case of Invalid token (unauthorized access), with status 401.
-
-*exception* **Forbidden**
-    The server understood the request, but is refusing to fulfill it.
-    Status 401.
-
-*exception* **NotFound**
-    The server has not found anything matching the Request-URI. Status 404.
-
-*exception* **NoUserName**
-    Raised by getDisplayName and getServiceDisplayName when an invalid
-    UUID was given.
-
-*exception* **NoUUID**
-    Raised by *getUUID* and *getServiceUUID* when an invalid
-    username was given.
diff --git a/snf-astakos-client/setup.py b/snf-astakos-client/setup.py
deleted file mode 100644
index 630f7ce..0000000
--- a/snf-astakos-client/setup.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright 2012, 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.
-#
-import distribute_setup
-distribute_setup.use_setuptools()
-
-import os
-import sys
-
-from distutils.util import convert_path
-from fnmatch import fnmatchcase
-from setuptools import setup, find_packages
-
-HERE = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
-
-from astakosclient.version import __version__
-
-# Package info
-VERSION = __version__
-README = open(os.path.join(HERE, 'README')).read()
-CHANGES = open(os.path.join(HERE, 'Changelog')).read()
-SHORT_DESCRIPTION = 'A set of tools to ease versioning and use of git flow.'
-
-PACKAGES_ROOT = '.'
-PACKAGES = find_packages(PACKAGES_ROOT)
-
-# Package meta
-CLASSIFIERS = []
-
-# Package requirements
-INSTALL_REQUIRES = [
-    "objpool >= 0.2",
-    "simplejson"
-]
-
-# Provided as an attribute, so you can append to these instead
-# of replicating them:
-standard_exclude = ["*.py", "*.pyc", "*$py.class", "*~", ".*", "*.bak"]
-standard_exclude_directories = [".*", "CVS", "_darcs", "./build", "./dist",
-                                "EGG-INFO", "*.egg-info"]
-
-
-# (c) 2005 Ian Bicking and contributors;
-#     written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license:
-#     http://www.opensource.org/licenses/mit-license.php
-# Note: you may want to copy this into your setup.py file verbatim, as
-# you can't import this from another package, when you don't know if
-# that package is installed yet.
-def find_package_data(
-        where=".",
-        package="",
-        exclude=standard_exclude,
-        exclude_directories=standard_exclude_directories,
-        only_in_packages=True,
-        show_ignored=False):
-    """
-    Return a dictionary suitable for use in ``package_data``
-    in a distutils ``setup.py`` file.
-
-    The dictionary looks like::
-
-        {"package": [files]}
-
-    Where ``files`` is a list of all the files in that package that
-    don"t match anything in ``exclude``.
-
-    If ``only_in_packages`` is true, then top-level directories that
-    are not packages won"t be included (but directories under packages
-    will).
-
-    Directories matching any pattern in ``exclude_directories`` will
-    be ignored; by default directories with leading ``.``, ``CVS``,
-    and ``_darcs`` will be ignored.
-
-    If ``show_ignored`` is true, then all the files that aren"t
-    included in package data are shown on stderr (for debugging
-    purposes).
-
-    Note patterns use wildcards, or can be exact paths (including
-    leading ``./``), and all searching is case-insensitive.
-    """
-    out = {}
-    stack = [(convert_path(where), "", package, only_in_packages)]
-    while stack:
-        where, prefix, package, only_in_packages = stack.pop(0)
-        for name in os.listdir(where):
-            fn = os.path.join(where, name)
-            if os.path.isdir(fn):
-                bad_name = False
-                for pattern in exclude_directories:
-                    if (fnmatchcase(name, pattern) or
-                            fn.lower() == pattern.lower()):
-                        bad_name = True
-                        if show_ignored:
-                            print >> sys.stderr, (
-                                "Directory %s ignored by pattern %s"
-                                % (fn, pattern))
-                        break
-                if bad_name:
-                    continue
-                if (os.path.isfile(os.path.join(fn, "__init__.py"))
-                        and not prefix):
-                    if not package:
-                        new_package = name
-                    else:
-                        new_package = package + "." + name
-                    stack.append((fn, "", new_package, False))
-                else:
-                    stack.append((fn, prefix + name + "/", package,
-                                  only_in_packages))
-            elif package or not only_in_packages:
-                # is a file
-                bad_name = False
-                for pattern in exclude:
-                    if (fnmatchcase(name, pattern) or
-                            fn.lower() == pattern.lower()):
-                        bad_name = True
-                        if show_ignored:
-                            print >> sys.stderr, (
-                                "File %s ignored by pattern %s"
-                                % (fn, pattern))
-                        break
-                if bad_name:
-                    continue
-                out.setdefault(package, []).append(prefix + name)
-    return out
-
-setup(
-    name='astakosclient',
-    version=VERSION,
-    license='BSD',
-    url='http://www.synnefo.org/',
-    description=SHORT_DESCRIPTION,
-    long_description=README + '\n\n' + CHANGES,
-    classifiers=CLASSIFIERS,
-
-    author='Synnefo development team',
-    author_email='synnefo-devel@googlegroups.com',
-    maintainer='Synnefo development team',
-    maintainer_email='synnefo-devel@googlegroups.com',
-
-    packages=PACKAGES,
-    package_dir={'': PACKAGES_ROOT},
-    include_package_data=True,
-    package_data=find_package_data('.'),
-    zip_safe=False,
-
-    install_requires=INSTALL_REQUIRES,
-
-    entry_points={},
-)
diff --git a/snf-django-lib/setup.py b/snf-django-lib/setup.py
index 1c1e152..451e7e8 100644
--- a/snf-django-lib/setup.py
+++ b/snf-django-lib/setup.py
@@ -44,7 +44,7 @@ from setuptools import setup, find_packages
 
 HERE = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
 
-from synnefo.versions.app import __version__
+from snf_django.version import __version__
 
 # Package info
 VERSION = __version__
