Commit da2224e5 authored by Georgios D. Tsoukalas's avatar Georgios D. Tsoukalas
Browse files

Merge branch 'feature-reconfigure-urls' into develop

Conflicts:
	docs/pithos-api-guide.rst
parents ea9961c1 fcace70b
......@@ -67,7 +67,7 @@ Astakos
* API-related changes:
* Implemented API calls for quota, resources, and commissions.
* Moved all API calls under '/astakos/api'. Kept also previous locations
* Moved all API calls under '/account/v1.0'. Kept also previous locations
for backwards compatibility.
* Changes in views:
......
......@@ -43,22 +43,37 @@ from astakosclient.utils import \
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
API_AUTHENTICATE = "/astakos/api/authenticate"
API_USERCATALOGS = "/astakos/api/user_catalogs"
API_SERVICE_USERCATALOGS = "/astakos/api/service/user_catalogs"
API_GETSERVICES = "/astakos/api/get_services"
API_RESOURCES = "/astakos/api/resources"
API_QUOTAS = "/astakos/api/quotas"
API_SERVICE_QUOTAS = "/astakos/api/service_quotas"
API_COMMISSIONS = "/astakos/api/commissions"
API_COMMISSIONS_ACTION = API_COMMISSIONS + "/action"
API_FEEDBACK = "/astakos/api/feedback"
API_TOKENS = "/astakos/api/tokens"
TOKENS_ENDPOINTS = "endpoints"
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(ACCOUNTS_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")
# --------------------------------------------------------------------
# Astakos Keystone API urls
IDENTITY_PREFIX = get_path(astakos_services, 'astakos_identity.prefix')
API_TOKENS = join_urls(IDENTITY_PREFIX, "tokens")
TOKENS_ENDPOINTS = join_urls(API_TOKENS, "endpoints")
# --------------------------------------------------------------------
......
# 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.
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()
lastname = basename
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_ui': {
'type': 'astakos_ui',
'component': 'astakos',
'prefix': 'ui',
'public': False,
'endpoints': [
{'versionId': '',
'publicURL': None},
],
},
}
......@@ -56,7 +56,7 @@ Add in ``/etc/apache2/sites-available/synnefo-ssl``::
ShibConfig /etc/shibboleth/shibboleth2.xml
Alias /shibboleth-sp /usr/share/shibboleth
<Location /im/login/shibboleth>
<Location /ui/login/shibboleth>
AuthType shibboleth
ShibRequireSession On
ShibUseHeaders On
......@@ -1003,19 +1003,20 @@ fix-superusers Transform superusers created by syncdb into Astako
cleanup-full Cleanup sessions and session catalog
commission-list List pending commissions
commission-show Show details for a pending commission
component-add Register a component
component-list List components
component-modify Modify component attributes
project-control Manage projects and applications
project-list List projects
project-show Show project details
quota List and check the integrity of user quota
reconcile-resources-astakos Reconcile resource usage of Quotaholder with Astakos DB
resource-add Add resource
resource-export-astakos Export astakos resources in json format
resource-import Register service resources
resource-import Register resources
resource-list List resources
resource-modify Modify a resource's default base quota and boolean flags
service-add Register a service
service-import Register services
service-list List services
service-modify Modify service attributes
service-show Show service details
term-add Add approval terms
user-activation-send Send user activation
......
......@@ -20,6 +20,7 @@ Document Revisions
========================= ================================
Revision Description
========================= ================================
0.14 (June 03, 2013) Remove endpoint listing
0.14 (May 28, 2013) Extend token api with authenticate call
0.14 (May 23, 2013) Extend api to list endpoints
0.14 (May 14, 2013) Do not serve user quotas in :ref:`authenticate-api-label`
......@@ -37,7 +38,7 @@ Returns a json formatted list containing information about the supported cloud s
============================= ========= ==================
Uri Method Description
============================= ========= ==================
``/im/get_services`` GET Get cloud services
``/ui/get_services`` GET Get cloud services
============================= ========= ==================
Example reply:
......@@ -57,22 +58,22 @@ Returns a json formatted list containing the cloud bar links.
========================= ========= ==================
Uri Method Description
========================= ========= ==================
``/im/get_menu`` GET Get cloud bar menu
``/ui/get_menu`` GET Get cloud bar menu
========================= ========= ==================
Example reply if request user is not authenticated:
::
[{"url": "/im/", "name": "Sign in"}]
[{"url": "/ui/", "name": "Sign in"}]
Example reply if request user is authenticated:
::
[{"url": "/im/", "name": "user@example.com"},
{"url": "/im/landing", "name": "Dashboard"},
{"url": "/im/logout", "name": "Sign out"}]
[{"url": "/ui/", "name": "user@example.com"},
{"url": "/ui/landing", "name": "Dashboard"},
{"url": "/ui/logout", "name": "Sign out"}]
User API Operations
......@@ -89,11 +90,11 @@ Authenticate
Authenticate API requests require a token. An application that wishes to connect to Astakos, but does not have a token, should redirect the user to ``/login``. (see :ref:`authentication-label`)
============================= ========= ==================
Uri Method Description
============================= ========= ==================
``/astakos/api/authenticate`` GET Authenticate user using token
============================= ========= ==================
============================== ========= ==================
Uri Method Description
============================== ========= ==================
``/account/v1.0/authenticate`` GET Authenticate user using token
============================== ========= ==================
|
......@@ -140,7 +141,7 @@ Return Code Description
500 (Internal Server Error) The request cannot be completed because of an internal error
=========================== =====================
.. warning:: The service is also available under ``/im/authenticate``.
.. warning:: The service is also available under ``/ui/authenticate``.
It will be removed in the next version.
......@@ -149,11 +150,11 @@ Send feedback
Post user feedback.
========================= ========= ==================
Uri Method Description
========================= ========= ==================
``/astakos/api/feedback`` POST Send feedback
========================= ========= ==================
========================== ========= ==================
Uri Method Description
========================== ========= ==================
``/account/v1.0/feedback`` POST Send feedback
========================== ========= ==================
|
......@@ -192,11 +193,11 @@ Get User catalogs
Return a json formatted dictionary containing information about a specific user
================================ ========= ==================
Uri Method Description
================================ ========= ==================
``/astakos/api/user_catalogs`` POST Get 2 catalogs containing uuid to displayname mapping and the opposite
================================ ========= ==================
=============================== ========= ==================
Uri Method Description
=============================== ========= ==================
``/account/v1.0/user_catalogs`` POST Get 2 catalogs containing uuid to displayname mapping and the opposite
=============================== ========= ==================
|
......@@ -253,11 +254,11 @@ Get User catalogs
Return a json formatted dictionary containing information about a specific user
====================================== ========= ==================
Uri Method Description
====================================== ========= ==================
``/astakos/api/service/user_catalogs`` POST Get 2 catalogs containing uuid to displayname mapping and the opposite
====================================== ========= ==================
======================================= ========= ==================
Uri Method Description
======================================= ========= ==================
``/account/v1.0/service/user_catalogs`` POST Get 2 catalogs containing uuid to displayname mapping and the opposite
======================================= ========= ==================
|
......@@ -310,14 +311,14 @@ Tokens API Operations
Authenticate
^^^^^^^^^^^^
Fallback call which receives the user token or the user uuid/token and returns
back the token as well as information about the token holder and the services
he/she can access.
Fallback call which receives the user token or the user uuid/token pair and
returns back the token as well as information about the token holder and the
services he/she can access.
========================================= ========= ==================
Uri Method Description
========================================= ========= ==================
``/astakos/api/tokens/`` POST Checks whether the provided token is valid and conforms with the provided uuid (if present) and returns back information about the user
``/identity/v2.0/tokens/`` POST Checks whether the provided token is valid and conforms with the provided uuid (if present) and returns back information about the user
========================================= ========= ==================
The input should be json formatted.
......@@ -328,9 +329,8 @@ Example request:
{
"auth":{
"passwordCredentials":{
"username":"c18088be-16b1-4263-8180-043c54e22903",
"password":"CDEe2k0T/HdiJWBMMbHyOA=="
"token":{
"id":"CDEe2k0T/HdiJWBMMbHyOA=="
},
"tenantName":"c18088be-16b1-4263-8180-043c54e22903"
}
......@@ -342,8 +342,9 @@ or
{
"auth":{
"token":{
"id":"CDEe2k0T/HdiJWBMMbHyOA=="
"passwordCredentials":{
"username":"c18088be-16b1-4263-8180-043c54e22903",
"password":"CDEe2k0T/HdiJWBMMbHyOA=="
},
"tenantName":"c18088be-16b1-4263-8180-043c54e22903"
}
......@@ -359,36 +360,67 @@ Example json response:
::
{'serviceCatalog': [
{'endpoints': [{'SNF:uiURL': 'https://node1.example.com/ui/',
'adminURL': 'https://node1.example.com/v1',
'internalUrl': 'https://node1.example.com/v1',
'publicURL': 'https://node1.example.com/v1',
'region': 'cyclades'}],
'name': 'cyclades',
'type': 'compute'},
{'endpoints': [{'SNF:uiURL': 'https://node2.example.com/ui/',
'adminURL': 'https://node2.example.com/v1',
'internalUrl': 'https://node2.example.com/v1',
'publicURL': 'https://node2.example.com/v1',
'region': 'pithos'}],
'name': 'pithos',
'type': 'storage'}],
'token': {'expires': '2013-06-19T15:23:59.975572+00:00',
'id': 'CDEe2k0T/HdiJWBMMbHyOA==',
'tenant': {'id': 'c18088be-16b1-4263-8180-043c54e22903',
'name': 'Firstname Lastname'}},
'user': {'id': 'c18088be-16b1-4263-8180-043c54e22903',
'name': 'Firstname Lastname',
'roles': [{'id': 1, 'name': 'default'}],
'roles_links': []}}
{"access": {
"serviceCatalog": [
{"SNF:uiURL": "https://node2.example.com/ui/",
"endpoints": [{
"publicURL": "https://object-store.example.synnefo.org/pithos/public/v2.0",
"versionId": "v2.0"}],
"endpoints_links": [],
"name": "pithos_public",
"type": "public"},
{"SNF:uiURL": "https://node2.example.com/ui/",
"endpoints": [{
"publicURL": "https://object-store.example.synnefo.org/pithos/object-store/v1",
"versionId": "v1"}],
"endpoints_links": [],
"name": "pithos_object-store",
"type": "object-store"},
{"SNF:uiURL": "https://node2.example.com/ui/",
"endpoints": [{
"publicURL": "https://object-store.example.synnefo.org/pithos/ui",
"versionId": ""}],
"endpoints_links": [],
"name": "pithos_ui",
"type": "pithos_ui"},
{"SNF:uiURL": "http://localhost:8080",
"endpoints": [{
"publicURL": "https://accounts.example.synnefo.org/ui/v1.0",
"versionId": "v1.0"}],
"endpoints_links": [],
"name": "astakos_ui",
"type": "astakos_ui"},
{"SNF:uiURL": "http://localhost:8080",
"endpoints": [{
"publicURL": "https://accounts.example.synnefo.org/account/v1.0",
"versionId": "v1.0"}],
"endpoints_links": [],
"name": "astakos_account",
"type": "account"},
{"SNF:uiURL": "http://localhost:8080",
"endpoints": [{
"publicURL": "https://accounts.example.synnefo.org/identity/v2.0",
"versionId": "v2.0"}],
"endpoints_links": [],
"name": "astakos_identity",
"type": "identity"}],
"token": {
"expires": "2013-06-19T15:23:59.975572+00:00",
"id": "CDEe2k0T/HdiJWBMMbHyOA==",
"tenant": {"id": "c18088be-16b1-4263-8180-043c54e22903",
"name": "Firstname Lastname"}},
"user": {
"id": "c18088be-16b1-4263-8180-043c54e22903",
"name": "Firstname Lastname",
"roles": [{"id": 1, "name": "default"},