From 0e52c1b35e5e7ff6c64912fb100478d4ab265338 Mon Sep 17 00:00:00 2001
From: Ilias Tsitsimpis <iliastsi@grnet.gr>
Date: Wed, 2 Oct 2013 17:01:30 +0300
Subject: [PATCH] astakosclient: Complete redesign astakosclient
Accept auth_url as the default entry point and
query astakos endpoints to find out the
account and ui urls.
---
astakosclient/astakosclient/__init__.py | 532 ++++++++++++++----------
astakosclient/astakosclient/errors.py | 31 +-
astakosclient/astakosclient/keypath.py | 216 ----------
astakosclient/astakosclient/services.py | 88 ----
astakosclient/astakosclient/utils.py | 31 +-
5 files changed, 357 insertions(+), 541 deletions(-)
delete mode 100644 astakosclient/astakosclient/keypath.py
delete mode 100644 astakosclient/astakosclient/services.py
diff --git a/astakosclient/astakosclient/__init__.py b/astakosclient/astakosclient/__init__.py
index 8e5f32f72..6e34793d2 100644
--- a/astakosclient/astakosclient/__init__.py
+++ b/astakosclient/astakosclient/__init__.py
@@ -31,6 +31,10 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
+"""
+Simple and minimal client for the Astakos authentication service
+"""
+
import logging
import urlparse
import urllib
@@ -39,45 +43,10 @@ from copy import copy
import simplejson
from astakosclient.utils import \
- retry, scheme_to_class, parse_request, check_input
+ retry_dec, scheme_to_class, parse_request, check_input, join_urls
from astakosclient.errors import \
AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
- NoUserName, NoUUID, BadValue, QuotaLimit, InvalidResponse
-from .keypath import get_path
-from .services import astakos_services
-
-
-# Customize astakos_services here?
-
-
-def join_urls(a, b):
- """join_urls from synnefo.lib"""
- return a.rstrip("/") + "/" + b.lstrip("/")
-
-# --------------------------------------------------------------------
-# Astakos API urls
-UI_PREFIX = get_path(astakos_services, 'astakos_ui.prefix')
-ACCOUNTS_PREFIX = get_path(astakos_services, 'astakos_account.prefix')
-ACCOUNTS_PREFIX = join_urls(ACCOUNTS_PREFIX, 'v1.0')
-API_AUTHENTICATE = join_urls(ACCOUNTS_PREFIX, "authenticate")
-API_USERCATALOGS = join_urls(ACCOUNTS_PREFIX, "user_catalogs")
-API_SERVICE_USERCATALOGS = join_urls(ACCOUNTS_PREFIX, "service/user_catalogs")
-API_GETSERVICES = join_urls(UI_PREFIX, "get_services")
-API_RESOURCES = join_urls(ACCOUNTS_PREFIX, "resources")
-API_QUOTAS = join_urls(ACCOUNTS_PREFIX, "quotas")
-API_SERVICE_QUOTAS = join_urls(ACCOUNTS_PREFIX, "service_quotas")
-API_COMMISSIONS = join_urls(ACCOUNTS_PREFIX, "commissions")
-API_COMMISSIONS_ACTION = join_urls(API_COMMISSIONS, "action")
-API_FEEDBACK = join_urls(ACCOUNTS_PREFIX, "feedback")
-API_PROJECTS = join_urls(ACCOUNTS_PREFIX, "projects")
-API_APPLICATIONS = join_urls(API_PROJECTS, "apps")
-API_MEMBERSHIPS = join_urls(API_PROJECTS, "memberships")
-
-# --------------------------------------------------------------------
-# Astakos Keystone API urls
-IDENTITY_PREFIX = get_path(astakos_services, 'astakos_identity.prefix')
-IDENTITY_PREFIX = join_urls(IDENTITY_PREFIX, "v2.0")
-API_TOKENS = join_urls(IDENTITY_PREFIX, "tokens")
+ NoUserName, NoUUID, BadValue, QuotaLimit, InvalidResponse, NoEndpoints
# --------------------------------------------------------------------
@@ -94,80 +63,149 @@ def get_token_from_cookie(request, cookie_name):
try:
cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None))
return cookie_content.split("|")[1]
- except:
+ except BaseException:
return None
-class AstakosClient():
+# Too many instance attributes. pylint: disable-msg=R0902
+# Too many public methods. pylint: disable-msg=R0904
+class AstakosClient(object):
"""AstakosClient Class Implementation"""
# ----------------------------------
- def __init__(self, astakos_url, retry=0,
- use_pool=False, pool_size=8, logger=None):
+ # Initialize AstakosClient Class
+ # Too many arguments. pylint: disable-msg=R0913
+ # Too many local variables. pylint: disable-msg=R0914
+ # Too many statements. pylint: disable-msg=R0915
+ def __init__(self, token, auth_url,
+ retry=0, use_pool=False, pool_size=8, logger=None):
"""Initialize AstakosClient Class
Keyword arguments:
- astakos_url -- i.e https://accounts.example.com (string)
- use_pool -- use objpool for http requests (boolean)
+ token -- user's/service's token (string)
+ auth_url -- i.e https://accounts.example.com/identity/v2.0
retry -- how many time to retry (integer)
+ use_pool -- use objpool for http requests (boolean)
+ pool_size -- if using pool, define the pool size
logger -- pass a different logger
"""
+
+ # Get 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))
+ logger.debug("Intialize AstakosClient: auth_url = %s, "
+ "use_pool = %s, pool_size = %s",
+ auth_url, use_pool, pool_size)
- check_input("__init__", logger, astakos_url=astakos_url)
+ # Check that token and auth_url (mandatory options) are given
+ check_input("__init__", logger, token=token, auth_url=auth_url)
- # Check for supported scheme
- p = urlparse.urlparse(astakos_url)
- conn_class = scheme_to_class(p.scheme, use_pool, pool_size)
+ # Initialize connection class
+ parsed_auth_url = urlparse.urlparse(auth_url)
+ conn_class = \
+ scheme_to_class(parsed_auth_url.scheme, use_pool, pool_size)
if conn_class is None:
- m = "Unsupported scheme: %s" % p.scheme
- logger.error(m)
- raise BadValue(m)
+ msg = "Unsupported scheme: %s" % parsed_auth_url.scheme
+ logger.error(msg)
+ raise BadValue(msg)
- # Save astakos_url etc. in our class
+ # Save astakos base url, logger, connection class etc in our class
self.retry = retry
self.logger = logger
- self.netloc = p.netloc
- self.scheme = p.scheme
- self.path = p.path.rstrip('/')
+ self.token = token
+ self.astakos_base_url = parsed_auth_url.netloc
+ self.scheme = parsed_auth_url.scheme
self.conn_class = conn_class
+ # Initialize astakos api prefixes
+ # API urls under auth_url
+ self.auth_prefix = parsed_auth_url.path
+ self.api_tokens = join_urls(self.auth_prefix, "tokens")
+
+ # ------------------------------
+ # API urls under account_url
+ # Get account_url from get_endpoints
+ # get_endpoints needs self.api_tokens
+ endpoints = self.get_endpoints(non_authentication=True)
+ account_service_catalog = parse_endpoints(
+ endpoints, ep_name="astakos_account", ep_version_id="v1.0")
+ self.account_url = \
+ account_service_catalog[0]['endpoints'][0]['publicURL']
+ parsed_account_url = urlparse.urlparse(self.account_url)
+
+ self.account_prefix = parsed_account_url.path
+ self.logger.debug("Got account_prefix \"%s\"" % self.account_prefix)
+
+ self.api_authenticate = join_urls(
+ self.account_prefix, "authenticate")
+ self.api_usercatalogs = join_urls(
+ self.account_prefix, "user_catalogs")
+ self.api_service_usercatalogs = join_urls(
+ self.account_prefix, "service/user_catalogs")
+ self.api_resources = join_urls(
+ self.account_prefix, "resources")
+ self.api_quotas = join_urls(
+ self.account_prefix, "quotas")
+ self.api_service_quotas = join_urls(
+ self.account_prefix, "service_quotas")
+ self.api_commissions = join_urls(
+ self.account_prefix, "commissions")
+ self.api_commissions_action = join_urls(
+ self.api_commissions, "action")
+ self.api_feedback = join_urls(
+ self.account_prefix, "feedback")
+ self.api_projects = join_urls(
+ self.account_prefix, "projects")
+ self.api_applications = join_urls(
+ self.api_projects, "apps")
+ self.api_memberships = join_urls(
+ self.api_projects, "memberships")
+
+ # ------------------------------
+ # API urls under ui_url
+ # Get ui url from get_endpoints
+ # get_endpoints needs self.api_tokens
+ ui_service_catalog = parse_endpoints(
+ endpoints, ep_name="astakos_account", ep_version_id="v1.0")
+ parsed_ui_url = urlparse.urlparse(
+ ui_service_catalog[0]['endpoints'][0]['SNF:uiURL'])
+ self.ui_url = \
+ ui_service_catalog[0]['endpoints'][0]['SNF:uiURL']
+ parsed_ui_url = urlparse.urlparse(self.ui_url)
+
+ self.ui_prefix = parsed_ui_url.path
+ self.logger.debug("Got ui_prefix \"%s\"" % self.ui_prefix)
+
+ self.api_getservices = join_urls(self.ui_prefix, "get_services")
+
# ----------------------------------
- @retry
- def _call_astakos(self, token, request_path, headers=None,
+ @retry_dec
+ def _call_astakos(self, request_path, headers=None,
body=None, method="GET", log_body=True):
"""Make the actual call to Astakos Service"""
- if token is not None:
- hashed_token = hashlib.sha1()
- hashed_token.update(token)
- using_token = "using token %s" % (hashed_token.hexdigest())
- else:
- using_token = "without using token"
+ hashed_token = hashlib.sha1()
+ hashed_token.update(self.token)
self.logger.debug(
- "Make a %s request to %s %s with headers %s and body %s"
- % (method, request_path, using_token, headers,
- body if log_body else "(not logged)"))
+ "Make a %s request to %s, using token with hash %s, "
+ "with headers %s and body %s",
+ method, request_path, hashed_token.hexdigest(), headers,
+ body if log_body else "(not logged)")
# Check Input
if headers is None:
headers = {}
if body is None:
body = {}
- path = self.path + "/" + request_path.strip('/')
# Build request's header and body
kwargs = {}
kwargs['headers'] = copy(headers)
- if token is not None:
- kwargs['headers']['X-Auth-Token'] = token
+ kwargs['headers']['X-Auth-Token'] = self.token
if body:
kwargs['body'] = copy(body)
kwargs['headers'].setdefault(
@@ -177,10 +215,11 @@ class AstakosClient():
try:
# Get the connection object
- with self.conn_class(self.netloc) as conn:
+ with self.conn_class(self.astakos_base_url) as conn:
# Send request
+ # Used * or ** magic. pylint: disable-msg=W0142
(message, data, status) = \
- _do_request(conn, method, path, **kwargs)
+ _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))
@@ -210,11 +249,10 @@ class AstakosClient():
# ------------------------
# do a GET to ``API_AUTHENTICATE``
- def get_user_info(self, token, usage=False):
+ def get_user_info(self, 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).
@@ -222,59 +260,57 @@ class AstakosClient():
"""
# Send request
- auth_path = copy(API_AUTHENTICATE)
+ auth_path = self.api_authenticate
if usage:
auth_path += "?usage=1"
- return self._call_astakos(token, auth_path)
+ return self._call_astakos(auth_path)
# ----------------------------------
# do a POST to ``API_USERCATALOGS`` (or ``API_SERVICE_USERCATALOGS``)
# with {'uuids': uuids}
- def _uuid_catalog(self, token, uuids, req_path):
+ def _uuid_catalog(self, uuids, req_path):
+ """Helper function to retrieve uuid catalog"""
req_headers = {'content-type': 'application/json'}
req_body = parse_request({'uuids': uuids}, self.logger)
- data = self._call_astakos(
- token, req_path, req_headers, req_body, "POST")
+ data = self._call_astakos(req_path, headers=req_headers,
+ body=req_body, method="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)
+ msg = "_uuid_catalog request returned %s. No uuid_catalog found" \
+ % data
+ self.logger.error(msg)
+ raise AstakosClientException(msg)
- def get_usernames(self, token, uuids):
+ def get_usernames(self, 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 = copy(API_USERCATALOGS)
- return self._uuid_catalog(token, uuids, req_path)
+ return self._uuid_catalog(uuids, self.api_usercatalogs)
- def get_username(self, token, uuid):
+ def get_username(self, uuid):
"""Return the user name of a uuid (see get_usernames)"""
check_input("get_username", self.logger, uuid=uuid)
- uuid_dict = self.get_usernames(token, [uuid])
+ uuid_dict = self.get_usernames([uuid])
if uuid in uuid_dict:
return uuid_dict.get(uuid)
else:
raise NoUserName(uuid)
- def service_get_usernames(self, token, uuids):
+ def service_get_usernames(self, uuids):
"""Return a uuid_catalog dict using a service's token"""
- req_path = copy(API_SERVICE_USERCATALOGS)
- return self._uuid_catalog(token, uuids, req_path)
+ return self._uuid_catalog(uuids, self.api_service_usercatalogs)
- def service_get_username(self, token, uuid):
+ def service_get_username(self, uuid):
"""Return the displayName of a uuid using a service's token"""
check_input("service_get_username", self.logger, uuid=uuid)
- uuid_dict = self.service_get_usernames(token, [uuid])
+ uuid_dict = self.service_get_usernames([uuid])
if uuid in uuid_dict:
return uuid_dict.get(uuid)
else:
@@ -283,51 +319,51 @@ class AstakosClient():
# ----------------------------------
# do a POST to ``API_USERCATALOGS`` (or ``API_SERVICE_USERCATALOGS``)
# with {'displaynames': display_names}
- def _displayname_catalog(self, token, display_names, req_path):
+ def _displayname_catalog(self, display_names, req_path):
+ """Helper function to retrieve display names catalog"""
req_headers = {'content-type': 'application/json'}
req_body = parse_request({'displaynames': display_names}, self.logger)
- data = self._call_astakos(
- token, req_path, req_headers, req_body, "POST")
+ data = self._call_astakos(req_path, headers=req_headers,
+ body=req_body, method="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)
+ msg = "_displayname_catalog request returned %s. " \
+ "No displayname_catalog found" % data
+ self.logger.error(msg)
+ raise AstakosClientException(msg)
- def get_uuids(self, token, display_names):
+ def get_uuids(self, 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 = copy(API_USERCATALOGS)
- return self._displayname_catalog(token, display_names, req_path)
+ return self._displayname_catalog(
+ display_names, self.api_usercatalogs)
- def get_uuid(self, token, display_name):
+ def get_uuid(self, display_name):
"""Return the uuid of a name (see getUUIDs)"""
check_input("get_uuid", self.logger, display_name=display_name)
- name_dict = self.get_uuids(token, [display_name])
+ name_dict = self.get_uuids([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):
+ def service_get_uuids(self, display_names):
"""Return a display_name catalog using a service's token"""
- req_path = copy(API_SERVICE_USERCATALOGS)
- return self._displayname_catalog(token, display_names, req_path)
+ return self._displayname_catalog(
+ display_names, self.api_service_usercatalogs)
- def service_get_uuid(self, token, display_name):
+ def service_get_uuid(self, display_name):
"""Return the uuid of a name using a service's token"""
check_input("service_get_uuid", self.logger, display_name=display_name)
- name_dict = self.service_get_uuids(token, [display_name])
+ name_dict = self.service_get_uuids([display_name])
if display_name in name_dict:
return name_dict.get(display_name)
else:
@@ -337,21 +373,20 @@ class AstakosClient():
# do a GET to ``API_GETSERVICES``
def get_services(self):
"""Return a list of dicts with the registered services"""
- return self._call_astakos(None, copy(API_GETSERVICES))
+ return self._call_astakos(self.api_getservices)
# ----------------------------------
# do a GET to ``API_RESOURCES``
def get_resources(self):
"""Return a dict of dicts with the available resources"""
- return self._call_astakos(None, copy(API_RESOURCES))
+ return self._call_astakos(self.api_resources)
# ----------------------------------
# do a POST to ``API_FEEDBACK``
- def send_feedback(self, token, message, data):
+ def send_feedback(self, message, data):
"""Send feedback to astakos service
keyword arguments:
- token -- user's token (string)
message -- Feedback message
data -- Additional information about service client status
@@ -360,55 +395,63 @@ class AstakosClient():
"""
check_input("send_feedback", self.logger, message=message, data=data)
- path = copy(API_FEEDBACK)
req_body = urllib.urlencode(
{'feedback_msg': message, 'feedback_data': data})
- self._call_astakos(token, path, None, req_body, "POST")
+ self._call_astakos(self.api_feedback, headers=None,
+ body=req_body, method="POST")
# ----------------------------------
# do a POST to ``API_TOKENS``
- def get_endpoints(self, token, uuid=None):
- """ Fallback call for authenticate
+ def get_endpoints(self, tenant_name=None, non_authentication=False):
+ """ Authenticate and get services' endpoints
Keyword arguments:
- token -- user's token (string)
- uuid -- user's uniq id
+ tenant_name -- user's uniq id (optional)
+ non_authentication -- get only non authentication protected info
+
It returns back the token as well as information about the token
holder and the services he/she can acess (in json format).
+
+ The tenant_name is optional and if it is given it must match the
+ user's uuid.
+
+ In case on of the `name', `type', `region', `version_id' parameters
+ is given, return only the endpoints that match all of these criteria.
+ If no match is found then raise NoEndpoints exception.
+
In case of error raise an AstakosClientException.
"""
- req_path = copy(API_TOKENS)
req_headers = {'content-type': 'application/json'}
- body = {'auth': {'token': {'id': token}}}
- if uuid is not None:
- body['auth']['tenantName'] = uuid
- req_body = parse_request(body, self.logger)
- return self._call_astakos(token, req_path, req_headers,
- req_body, "POST", False)
+ if non_authentication:
+ req_body = None
+ else:
+ body = {'auth': {'token': {'id': self.token}}}
+ if tenant_name is not None:
+ body['auth']['tenantName'] = tenant_name
+ req_body = parse_request(body, self.logger)
+ return self._call_astakos(self.api_tokens, headers=req_headers,
+ body=req_body, method="POST",
+ log_body=False)
# ----------------------------------
# do a GET to ``API_QUOTAS``
- def get_quotas(self, token):
+ def get_quotas(self):
"""Get user's quotas
- Keyword arguments:
- token -- user's token (string)
-
In case of success return a dict of dicts with user's current quotas.
Otherwise raise an AstakosClientException
"""
- return self._call_astakos(token, copy(API_QUOTAS))
+ return self._call_astakos(self.api_quotas)
# ----------------------------------
# do a GET to ``API_SERVICE_QUOTAS``
- def service_get_quotas(self, token, user=None):
+ def service_get_quotas(self, user=None):
"""Get all quotas for resources associated with the service
Keyword arguments:
- token -- service's token (string)
user -- optionally, the uuid of a specific user
In case of success return a dict of dicts of dicts with current quotas
@@ -416,18 +459,17 @@ class AstakosClient():
Otherwise raise an AstakosClientException
"""
- query = copy(API_SERVICE_QUOTAS)
+ query = self.api_service_quotas
if user is not None:
query += "?user=" + user
- return self._call_astakos(token, query)
+ return self._call_astakos(query)
# ----------------------------------
# do a POST to ``API_COMMISSIONS``
- def issue_commission(self, token, request):
+ def issue_commission(self, request):
"""Issue a commission
Keyword arguments:
- token -- service's token (string)
request -- commision request (dict)
In case of success return commission's id (int).
@@ -437,8 +479,10 @@ class AstakosClient():
req_headers = {'content-type': 'application/json'}
req_body = parse_request(request, self.logger)
try:
- response = self._call_astakos(token, copy(API_COMMISSIONS),
- req_headers, req_body, "POST")
+ response = self._call_astakos(self.api_commissions,
+ headers=req_headers,
+ body=req_body,
+ method="POST")
except AstakosClientException as err:
if err.status == 413:
raise QuotaLimit(err.message, err.details)
@@ -448,17 +492,16 @@ class AstakosClient():
if "serial" in response:
return response['serial']
else:
- m = "issue_commission_core request returned %s. No serial found" \
- % response
- self.logger.error(m)
- raise AstakosClientException(m)
+ msg = "issue_commission_core request returned %s. " + \
+ "No serial found" % response
+ self.logger.error(msg)
+ raise AstakosClientException(msg)
- def issue_one_commission(self, token, holder, source, provisions,
+ def issue_one_commission(self, holder, source, provisions,
name="", force=False, auto_accept=False):
"""Issue one commission (with specific holder and source)
keyword arguments:
- token -- service's token (string)
holder -- user's id (string)
source -- commission's source (ex system) (string)
provisions -- resources with their quantity (dict from string to int)
@@ -482,36 +525,32 @@ class AstakosClient():
try:
request["provisions"] = []
for resource, quantity in provisions.iteritems():
- t = {"holder": holder, "source": source,
- "resource": resource, "quantity": quantity}
- request["provisions"].append(t)
+ prov = {"holder": holder, "source": source,
+ "resource": resource, "quantity": quantity}
+ request["provisions"].append(prov)
except Exception as err:
self.logger.error(str(err))
raise BadValue(str(err))
- return self.issue_commission(token, request)
+ return self.issue_commission(request)
# ----------------------------------
# do a GET to ``API_COMMISSIONS``
- def get_pending_commissions(self, token):
+ def get_pending_commissions(self):
"""Get Pending Commissions
- Keyword arguments:
- token -- service's token (string)
-
In case of success return a list of pending commissions' ids
(list of integers)
"""
- return self._call_astakos(token, copy(API_COMMISSIONS))
+ return self._call_astakos(self.api_commissions)
# ----------------------------------
# do a GET to ``API_COMMISSIONS``/<serial>
- def get_commission_info(self, token, serial):
+ def get_commission_info(self, serial):
"""Get Description of a Commission
Keyword arguments:
- token -- service's token (string)
serial -- commission's id (int)
In case of success return a dict of dicts containing
@@ -520,16 +559,15 @@ class AstakosClient():
"""
check_input("get_commission_info", self.logger, serial=serial)
- path = API_COMMISSIONS + "/" + str(serial)
- return self._call_astakos(token, path)
+ path = self.api_commissions.rstrip('/') + "/" + str(serial)
+ return self._call_astakos(path)
# ----------------------------------
# do a POST to ``API_COMMISSIONS``/<serial>/action"
- def commission_action(self, token, serial, action):
+ def commission_action(self, serial, action):
"""Perform a commission action
Keyword arguments:
- token -- service's token (string)
serial -- commission's id (int)
action -- action to perform, currently accept/reject (string)
@@ -539,26 +577,26 @@ class AstakosClient():
check_input("commission_action", self.logger,
serial=serial, action=action)
- path = API_COMMISSIONS + "/" + str(serial) + "/action"
+ path = self.api_commissions.rstrip('/') + "/" + str(serial) + "/action"
req_headers = {'content-type': 'application/json'}
req_body = parse_request({str(action): ""}, self.logger)
- self._call_astakos(token, path, req_headers, req_body, "POST")
+ self._call_astakos(path, headers=req_headers,
+ body=req_body, method="POST")
- def accept_commission(self, token, serial):
+ def accept_commission(self, serial):
"""Accept a commission (see commission_action)"""
- self.commission_action(token, serial, "accept")
+ self.commission_action(serial, "accept")
- def reject_commission(self, token, serial):
+ def reject_commission(self, serial):
"""Reject a commission (see commission_action)"""
- self.commission_action(token, serial, "reject")
+ self.commission_action(serial, "reject")
# ----------------------------------
# do a POST to ``API_COMMISSIONS_ACTION``
- def resolve_commissions(self, token, accept_serials, reject_serials):
+ def resolve_commissions(self, accept_serials, reject_serials):
"""Resolve multiple commissions at once
Keyword arguments:
- token -- service's token (string)
accept_serials -- commissions to accept (list of ints)
reject_serials -- commissions to reject (list of ints)
@@ -571,27 +609,26 @@ class AstakosClient():
accept_serials=accept_serials,
reject_serials=reject_serials)
- path = copy(API_COMMISSIONS_ACTION)
req_headers = {'content-type': 'application/json'}
req_body = parse_request({"accept": accept_serials,
"reject": reject_serials},
self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(self.api_commissions_action,
+ headers=req_headers, body=req_body,
+ method="POST")
# ----------------------------
# do a GET to ``API_PROJECTS``
- def get_projects(self, token, name=None, state=None, owner=None):
+ def get_projects(self, name=None, state=None, owner=None):
"""Retrieve all accessible projects
Arguments:
- token -- user's token (string)
name -- filter by name (optional)
state -- filter by state (optional)
owner -- filter by owner (optional)
In case of success, return a list of project descriptions.
"""
- path = API_PROJECTS
filters = {}
if name is not None:
filters["name"] = name
@@ -602,62 +639,61 @@ class AstakosClient():
req_headers = {'content-type': 'application/json'}
req_body = (parse_request({"filter": filters}, self.logger)
if filters else None)
- return self._call_astakos(token, path, req_headers, req_body)
+ return self._call_astakos(self.api_projects,
+ headers=req_headers, body=req_body)
# -----------------------------------------
# do a GET to ``API_PROJECTS``/<project_id>
- def get_project(self, token, project_id):
+ def get_project(self, project_id):
"""Retrieve project description, if accessible
Arguments:
- token -- user's token (string)
project_id -- project identifier
In case of success, return project description.
"""
- path = join_urls(API_PROJECTS, str(project_id))
- return self._call_astakos(token, path)
+ path = join_urls(self.api_projects, str(project_id))
+ return self._call_astakos(path)
# -----------------------------
# do a POST to ``API_PROJECTS``
- def create_project(self, token, specs):
+ def create_project(self, specs):
"""Submit application to create a new project
Arguments:
- token -- user's token (string)
specs -- dict describing a project
In case of success, return project and application identifiers.
"""
- path = API_PROJECTS
req_headers = {'content-type': 'application/json'}
req_body = parse_request(specs, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(self.api_projects,
+ headers=req_headers, body=req_body,
+ method="POST")
# ------------------------------------------
# do a POST to ``API_PROJECTS``/<project_id>
- def modify_project(self, token, project_id, specs):
+ def modify_project(self, project_id, specs):
"""Submit application to modify an existing project
Arguments:
- token -- user's token (string)
project_id -- project identifier
specs -- dict describing a project
In case of success, return project and application identifiers.
"""
- path = join_urls(API_PROJECTS, str(project_id))
+ path = join_urls(self.api_projects, str(project_id))
req_headers = {'content-type': 'application/json'}
req_body = parse_request(specs, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(path, headers=req_headers,
+ body=req_body, method="POST")
# -------------------------------------------------
# do a POST to ``API_PROJECTS``/<project_id>/action
- def project_action(self, token, project_id, action, reason=""):
+ def project_action(self, project_id, action, reason=""):
"""Perform action on a project
Arguments:
- token -- user's token (string)
project_id -- project identifier
action -- action to perform, one of "suspend", "unsuspend",
"terminate", "reinstate"
@@ -665,50 +701,48 @@ class AstakosClient():
In case of success, return nothing.
"""
- path = join_urls(API_PROJECTS, str(project_id))
+ path = join_urls(self.api_projects, str(project_id))
path = join_urls(path, "action")
req_headers = {'content-type': 'application/json'}
req_body = parse_request({action: reason}, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(path, headers=req_headers,
+ body=req_body, method="POST")
# --------------------------------
# do a GET to ``API_APPLICATIONS``
- def get_applications(self, token, project=None):
+ def get_applications(self, project=None):
"""Retrieve all accessible applications
Arguments:
- token -- user's token (string)
project -- filter by project (optional)
In case of success, return a list of application descriptions.
"""
- path = API_APPLICATIONS
req_headers = {'content-type': 'application/json'}
body = {"project": project} if project is not None else None
req_body = parse_request(body, self.logger) if body else None
- return self._call_astakos(token, path, req_headers, req_body)
+ return self._call_astakos(self.api_applications,
+ headers=req_headers, body=req_body)
# -----------------------------------------
# do a GET to ``API_APPLICATIONS``/<app_id>
- def get_application(self, token, app_id):
+ def get_application(self, app_id):
"""Retrieve application description, if accessible
Arguments:
- token -- user's token (string)
app_id -- application identifier
In case of success, return application description.
"""
- path = join_urls(API_APPLICATIONS, str(app_id))
- return self._call_astakos(token, path)
+ path = join_urls(self.api_applications, str(app_id))
+ return self._call_astakos(path)
# -------------------------------------------------
# do a POST to ``API_APPLICATIONS``/<app_id>/action
- def application_action(self, token, app_id, action, reason=""):
+ def application_action(self, app_id, action, reason=""):
"""Perform action on an application
Arguments:
- token -- user's token (string)
app_id -- application identifier
action -- action to perform, one of "approve", "deny",
"dismiss", "cancel"
@@ -716,50 +750,48 @@ class AstakosClient():
In case of success, return nothing.
"""
- path = join_urls(API_APPLICATIONS, str(app_id))
+ path = join_urls(self.api_applications, str(app_id))
path = join_urls(path, "action")
req_headers = {'content-type': 'application/json'}
req_body = parse_request({action: reason}, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(path, headers=req_headers,
+ body=req_body, method="POST")
# -------------------------------
# do a GET to ``API_MEMBERSHIPS``
- def get_memberships(self, token, project=None):
+ def get_memberships(self, project=None):
"""Retrieve all accessible memberships
Arguments:
- token -- user's token (string)
project -- filter by project (optional)
In case of success, return a list of membership descriptions.
"""
- path = API_MEMBERSHIPS
req_headers = {'content-type': 'application/json'}
body = {"project": project} if project is not None else None
req_body = parse_request(body, self.logger) if body else None
- return self._call_astakos(token, path, req_headers, req_body)
+ return self._call_astakos(self.api_memberships,
+ headers=req_headers, body=req_body)
# -----------------------------------------
# do a GET to ``API_MEMBERSHIPS``/<memb_id>
- def get_membership(self, token, memb_id):
+ def get_membership(self, memb_id):
"""Retrieve membership description, if accessible
Arguments:
- token -- user's token (string)
memb_id -- membership identifier
In case of success, return membership description.
"""
- path = join_urls(API_MEMBERSHIPS, str(memb_id))
- return self._call_astakos(token, path)
+ path = join_urls(self.api_memberships, str(memb_id))
+ return self._call_astakos(path)
# -------------------------------------------------
# do a POST to ``API_MEMBERSHIPS``/<memb_id>/action
- def membership_action(self, token, memb_id, action, reason=""):
+ def membership_action(self, memb_id, action, reason=""):
"""Perform action on a membership
Arguments:
- token -- user's token (string)
memb_id -- membership identifier
action -- action to perform, one of "leave", "cancel", "accept",
"reject", "remove"
@@ -767,46 +799,96 @@ class AstakosClient():
In case of success, return nothing.
"""
- path = join_urls(API_MEMBERSHIPS, str(memb_id))
+ path = join_urls(self.api_memberships, str(memb_id))
path = join_urls(path, "action")
req_headers = {'content-type': 'application/json'}
req_body = parse_request({action: reason}, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(path, headers=req_headers,
+ body=req_body, method="POST")
# --------------------------------
# do a POST to ``API_MEMBERSHIPS``
- def join_project(self, token, project_id):
+ def join_project(self, project_id):
"""Join a project
Arguments:
- token -- user's token (string)
project_id -- project identifier
In case of success, return membership identifier.
"""
- path = API_MEMBERSHIPS
req_headers = {'content-type': 'application/json'}
body = {"join": {"project": project_id}}
req_body = parse_request(body, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(self.api_memberships, headers=req_headers,
+ body=req_body, method="POST")
# --------------------------------
# do a POST to ``API_MEMBERSHIPS``
- def enroll_member(self, token, project_id, email):
+ def enroll_member(self, project_id, email):
"""Enroll a user in a project
Arguments:
- token -- user's token (string)
project_id -- project identifier
email -- user identified by email
In case of success, return membership identifier.
"""
- path = API_MEMBERSHIPS
req_headers = {'content-type': 'application/json'}
body = {"enroll": {"project": project_id, "user": email}}
req_body = parse_request(body, self.logger)
- return self._call_astakos(token, path, req_headers, req_body, "POST")
+ return self._call_astakos(self.api_memberships, headers=req_headers,
+ body=req_body, method="POST")
+
+
+# --------------------------------------------------------------------
+# parse endpoints
+def parse_endpoints(endpoints, ep_name=None, ep_type=None,
+ ep_region=None, ep_version_id=None):
+ """Parse endpoints server response and extract the ones needed
+
+ Keyword arguments:
+ endpoints -- the endpoints (json response from get_endpoints)
+ ep_name -- return only endpoints with this name (optional)
+ ep_type -- return only endpoints with this type (optional)
+ ep_region -- return only endpoints with this region (optional)
+ ep_version_id -- return only endpoints with this versionId (optional)
+
+ In case on of the `name', `type', `region', `version_id' parameters
+ is given, return only the endpoints that match all of these criteria.
+ If no match is found then raise NoEndpoints exception.
+
+ """
+ try:
+ catalog = endpoints['access']['serviceCatalog']
+ if ep_name is not None:
+ catalog = \
+ [c for c in catalog if c['name'] == ep_name]
+ if ep_type is not None:
+ catalog = \
+ [c for c in catalog if c['type'] == ep_type]
+ if ep_region is not None:
+ for c in catalog:
+ c['endpoints'] = [e for e in c['endpoints']
+ if e['region'] == ep_region]
+ # Remove catalog entries with no endpoints
+ catalog = \
+ [c for c in catalog if c['endpoints']]
+ if ep_version_id is not None:
+ for c in catalog:
+ c['endpoints'] = [e for e in c['endpoints']
+ if e['versionId'] == ep_version_id]
+ # Remove catalog entries with no endpoints
+ catalog = \
+ [c for c in catalog if c['endpoints']]
+
+ if not catalog:
+ raise NoEndpoints(ep_name, ep_type,
+ ep_region, ep_version_id)
+ else:
+ return catalog
+ except KeyError:
+ raise NoEndpoints()
+
# --------------------------------------------------------------------
# Private functions
diff --git a/astakosclient/astakosclient/errors.py b/astakosclient/astakosclient/errors.py
index a5f66beff..a85d38da0 100644
--- a/astakosclient/astakosclient/errors.py
+++ b/astakosclient/astakosclient/errors.py
@@ -31,8 +31,13 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
+"""
+Astakos Client Exceptions
+"""
+
class AstakosClientException(Exception):
+ """Base AstakosClientException Class"""
def __init__(self, message='', details='', status=500):
self.message = message
self.details = details
@@ -43,47 +48,65 @@ class AstakosClientException(Exception):
class BadValue(AstakosClientException):
+ """Re-define ValueError Exception under AstakosClientException"""
def __init__(self, details):
- """Re-define ValueError Exception under AstakosClientException"""
message = "ValueError"
super(BadValue, self).__init__(message, details)
class InvalidResponse(AstakosClientException):
+ """Return simplejson parse Exception as AstakosClient one"""
def __init__(self, message, details):
- """Return simplejson parse Exception as AstakosClient one"""
super(InvalidResponse, self).__init__(message, details)
class BadRequest(AstakosClientException):
+ """BadRequest Exception"""
status = 400
class Unauthorized(AstakosClientException):
+ """Unauthorized Exception"""
status = 401
class Forbidden(AstakosClientException):
+ """Forbidden Exception"""
status = 403
class NotFound(AstakosClientException):
+ """NotFound Exception"""
status = 404
class QuotaLimit(AstakosClientException):
+ """QuotaLimit Exception"""
status = 413
class NoUserName(AstakosClientException):
+ """No display name for the given uuid"""
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):
+ """No uuid for the given display name"""
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)
+
+
+class NoEndpoints(AstakosClientException):
+ """No endpoints found matching the criteria given"""
+ def __init__(self, ep_name, ep_type, ep_region, ep_version_id):
+ message = "No endpoints found matching" + \
+ (", name = %s" % ep_name) if ep_name is not None else "" + \
+ (", type = %s" % ep_type) if ep_type is not None else "" + \
+ (", region = %s" % ep_region) \
+ if ep_region is not None else "" + \
+ (", version_id = %s" % ep_version_id) \
+ if ep_version_id is not None else "."
+ super(NoEndpoints, self).__init__(message)
diff --git a/astakosclient/astakosclient/keypath.py b/astakosclient/astakosclient/keypath.py
deleted file mode 100644
index 883a40c8d..000000000
--- a/astakosclient/astakosclient/keypath.py
+++ /dev/null
@@ -1,216 +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 copy
-
-
-def dict_merge(a, b):
- """
- http://www.xormedia.com/recursively-merge-dictionaries-in-python/
- """
- if not isinstance(b, dict):
- return b
- result = copy.deepcopy(a)
- for k, v in b.iteritems():
- if k in result and isinstance(result[k], dict):
- result[k] = dict_merge(result[k], v)
- else:
- result[k] = copy.deepcopy(v)
- return result
-
-
-def lookup_path(container, path, sep='.', createpath=False):
- """
- return (['a','b'],
- [container['a'], container['a']['b']],
- 'c') where path=sep.join(['a','b','c'])
-
- """
- names = path.split(sep)
- dirnames = names[:-1]
- basename = names[-1]
-
- node = container
- name_path = []
- node_path = [node]
- for name in dirnames:
- name_path.append(name)
- if name not in node:
- if not createpath:
- m = "'{0}': path not found".format(sep.join(name_path))
- raise KeyError(m)
- node[name] = {}
- try:
- node = node[name]
- except TypeError as e:
- m = "'{0}': cannot traverse path beyond this node: {1}"
- m = m.format(sep.join(name_path), str(e))
- raise ValueError(m)
- node_path.append(node)
-
- return name_path, node_path, basename
-
-
-def walk_paths(container):
- for name, node in container.iteritems():
- if not hasattr(node, 'items'):
- yield [name], [node]
- else:
- for names, nodes in walk_paths(node):
- yield [name] + names, [node] + nodes
-
-
-def list_paths(container, sep='.'):
- """
- >>> sorted(list_paths({'a': {'b': {'c': 'd'}}}))
- [('a.b.c', 'd')]
- >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': 3}}))
- [('a.b.c', 'd'), ('a.e', 3)]
- >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}}))
- [('a.b.c', 'd'), ('a.e.f', 3)]
- >>> list_paths({})
- []
-
- """
- return [(sep.join(name_path), node_path[-1])
- for name_path, node_path in walk_paths(container)]
-
-
-def del_path(container, path, sep='.', collect=True):
- """
- del container['a']['b']['c'] where path=sep.join(['a','b','c'])
-
- >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c'); d
- {}
- >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c', collect=False); d
- {'a': {'b': {}}}
- >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c.d')
- Traceback (most recent call last):
- ValueError: 'a.b.c': cannot traverse path beyond this node:\
- 'str' object does not support item deletion
- """
-
- name_path, node_path, basename = \
- lookup_path(container, path, sep=sep, createpath=False)
-
- lastnode = node_path.pop()
- try:
- if basename in lastnode:
- del lastnode[basename]
- except (TypeError, KeyError) as e:
- m = "'{0}': cannot traverse path beyond this node: {1}"
- m = m.format(sep.join(name_path), str(e))
- raise ValueError(m)
-
- if collect:
- while node_path and not lastnode:
- basename = name_path.pop()
- lastnode = node_path.pop()
- del lastnode[basename]
-
-
-def get_path(container, path, sep='.'):
- """
- return container['a']['b']['c'] where path=sep.join(['a','b','c'])
-
- >>> get_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d')
- Traceback (most recent call last):
- ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
- string indices must be integers, not str
- >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c.d')
- Traceback (most recent call last):
- ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
- 'int' object is unsubscriptable
- >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c')
- 1
- >>> get_path({'a': {'b': {'c': 1}}}, 'a.b')
- {'c': 1}
-
- """
- name_path, node_path, basename = \
- lookup_path(container, path, sep=sep, createpath=False)
- name_path.append(basename)
- node = node_path[-1]
-
- try:
- return node[basename]
- except TypeError as e:
- m = "'{0}': cannot traverse path beyond this node: {1}"
- m = m.format(sep.join(name_path), str(e))
- raise ValueError(m)
- except KeyError as e:
- m = "'{0}': path not found: {1}"
- m = m.format(sep.join(name_path), str(e))
- raise KeyError(m)
-
-
-def set_path(container, path, value, sep='.',
- createpath=False, overwrite=True):
- """
- container['a']['b']['c'] = value where path=sep.join(['a','b','c'])
-
- >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1)
- Traceback (most recent call last):
- ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
- 'str' object does not support item assignment
- >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1)
- Traceback (most recent call last):
- KeyError: "'a.b.x': path not found"
- >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1, createpath=True)
-
- >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1)
-
- >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False)
- Traceback (most recent call last):
- ValueError: will not overwrite path 'a.b.c'
-
- """
- name_path, node_path, basename = \
- lookup_path(container, path, sep=sep, createpath=createpath)
- name_path.append(basename)
- node = node_path[-1]
-
- if basename in node and not overwrite:
- m = "will not overwrite path '{0}'".format(path)
- raise ValueError(m)
-
- try:
- node[basename] = value
- except TypeError as e:
- m = "'{0}': cannot traverse path beyond this node: {1}"
- m = m.format(sep.join(name_path), str(e))
- raise ValueError(m)
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/astakosclient/astakosclient/services.py b/astakosclient/astakosclient/services.py
deleted file mode 100644
index 98fa6b144..000000000
--- a/astakosclient/astakosclient/services.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright (C) 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.
-
-
-astakos_services = {
- 'astakos_account': {
- 'type': 'account',
- 'component': 'astakos',
- 'prefix': 'account',
- 'public': True,
- 'endpoints': [
- {'versionId': 'v1.0',
- 'publicURL': None},
- ],
- 'resources': {
- 'pending_app': {
- 'desc': "Number of pending project applications",
- 'name': "astakos.pending_app",
- 'service_type': "account",
- 'service_origin': "astakos_account",
- 'allow_in_projects': False},
- },
- },
-
- 'astakos_identity': {
- 'type': 'identity',
- 'component': 'astakos',
- 'prefix': 'identity',
- 'public': True,
- 'endpoints': [
- {'versionId': 'v2.0',
- 'publicURL': None},
- ],
- 'resources': {},
- },
-
- 'astakos_weblogin': {
- 'type': 'astakos_weblogin',
- 'component': 'astakos',
- 'prefix': 'weblogin',
- 'public': True,
- 'endpoints': [
- {'versionId': '',
- 'publicURL': None},
- ],
- },
-
- 'astakos_ui': {
- 'type': 'astakos_ui',
- 'component': 'astakos',
- 'prefix': 'ui',
- 'public': False,
- 'endpoints': [
- {'versionId': '',
- 'publicURL': None},
- ],
- },
-}
diff --git a/astakosclient/astakosclient/utils.py b/astakosclient/astakosclient/utils.py
index 22919cc4a..70cd87e82 100644
--- a/astakosclient/astakosclient/utils.py
+++ b/astakosclient/astakosclient/utils.py
@@ -31,6 +31,10 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
+"""
+Astakos Client utility module
+"""
+
from httplib import HTTPConnection, HTTPSConnection
from contextlib import closing
@@ -39,8 +43,10 @@ from objpool.http import PooledHTTPConnection
from astakosclient.errors import AstakosClientException, BadValue
-def retry(func):
+def retry_dec(func):
+ """Class Method Decorator"""
def decorator(self, *args, **kwargs):
+ """Retry `self.retry' times if connection fails"""
attemps = 0
while True:
try:
@@ -64,13 +70,16 @@ def retry(func):
def scheme_to_class(scheme, use_pool, pool_size):
"""Return the appropriate conn class for given scheme"""
def _objpool(netloc):
+ """Helper function to return a PooledHTTPConnection object"""
return PooledHTTPConnection(
netloc=netloc, scheme=scheme, size=pool_size)
def _http_connection(netloc):
+ """Helper function to return an HTTPConnection object"""
return closing(HTTPConnection(netloc))
def _https_connection(netloc):
+ """Helper function to return an HTTPSConnection object"""
return closing(HTTPSConnection(netloc))
if scheme == "http":
@@ -92,16 +101,22 @@ def parse_request(request, logger):
try:
return simplejson.dumps(request)
except Exception as err:
- m = "Cannot parse request \"%s\" with simplejson: %s" \
- % (request, str(err))
- logger.error(m)
- raise BadValue(m)
+ msg = "Cannot parse request \"%s\" with simplejson: %s" \
+ % (request, str(err))
+ logger.error(msg)
+ raise BadValue(msg)
def check_input(function_name, logger, **kwargs):
"""Check if given arguments are not None"""
for i in kwargs:
if kwargs[i] is None:
- m = "in " + function_name + ": " + str(i) + " parameter not given"
- logger.error(m)
- raise BadValue(m)
+ msg = "in " + function_name + ": " + \
+ str(i) + " parameter not given"
+ logger.error(msg)
+ raise BadValue(msg)
+
+
+def join_urls(url_a, url_b):
+ """Join_urls from synnefo.lib"""
+ return url_a.rstrip("/") + "/" + url_b.lstrip("/")
--
GitLab