Commit 0e52c1b3 authored by Ilias Tsitsimpis's avatar Ilias Tsitsimpis Committed by Giorgos Korfiatis

astakosclient: Complete redesign astakosclient

Accept auth_url as the default entry point and
query astakos endpoints to find out the
account and ui urls.
parent 7e36eeef
This diff is collapsed.
......@@ -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)
# 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()
# 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},
],
},
}
......@@ -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("/")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment