Revision 00d2a0ee

b/astakosclient/astakosclient/__init__.py
43 43
from astakosclient.errors import \
44 44
    AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
45 45
    NoUserName, NoUUID, BadValue, QuotaLimit, InvalidResponse
46
from .keypath import get_path
47
from .services import astakos_services
48

  
49

  
50
# Customize astakos_services here?
46 51

  
47 52

  
48 53
def join_urls(a, b):
......
51 56

  
52 57
# --------------------------------------------------------------------
53 58
# Astakos API urls
54
ACCOUNTS_PREFIX = 'accounts'
59
ACCOUNTS_PREFIX = get_path(astakos_services, 'astakos_account.prefix')
55 60
API_AUTHENTICATE = join_urls(ACCOUNTS_PREFIX, "authenticate")
56 61
API_USERCATALOGS = join_urls(ACCOUNTS_PREFIX, "user_catalogs")
57 62
API_SERVICE_USERCATALOGS = join_urls(ACCOUNTS_PREFIX, "service/user_catalogs")
......
65 70

  
66 71
# --------------------------------------------------------------------
67 72
# Astakos Keystone API urls
68
KEYSTONE_PREFIX = 'keystone'
73
KEYSTONE_PREFIX = get_path(astakos_services, 'astakos_keystone.prefix')
69 74
API_TOKENS = join_urls(KEYSTONE_PREFIX, "tokens")
70 75
TOKENS_ENDPOINTS = join_urls(API_TOKENS, "endpoints")
71 76

  
b/astakosclient/astakosclient/keypath.py
1
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34

  
35
def dict_merge(a, b):
36
    """
37
    http://www.xormedia.com/recursively-merge-dictionaries-in-python/
38
    """
39
    if not isinstance(b, dict):
40
        return b
41
    result = copy.deepcopy(a)
42
    for k, v in b.iteritems():
43
        if k in result and isinstance(result[k], dict):
44
                result[k] = dict_merge(result[k], v)
45
        else:
46
            result[k] = copy.deepcopy(v)
47
    return result
48

  
49

  
50
def lookup_path(container, path, sep='.', createpath=False):
51
    """
52
    return (['a','b'],
53
            [container['a'], container['a']['b']],
54
            'c')  where path=sep.join(['a','b','c'])
55

  
56
    """
57
    names = path.split(sep)
58
    dirnames = names[:-1]
59
    basename = names[-1]
60

  
61
    node = container
62
    name_path = []
63
    node_path = [node]
64
    for name in dirnames:
65
        name_path.append(name)
66
        if name not in node:
67
            if not createpath:
68
                m = "'{0}': path not found".format(sep.join(name_path))
69
                raise KeyError(m)
70
            node[name] = {}
71
        try:
72
            node = node[name]
73
        except TypeError as e:
74
            m = "'{0}': cannot traverse path beyond this node: {1}"
75
            m = m.format(sep.join(name_path), str(e))
76
            raise ValueError(m)
77
        node_path.append(node)
78

  
79
    return name_path, node_path, basename
80

  
81

  
82
def walk_paths(container):
83
    for name, node in container.iteritems():
84
        if not hasattr(node, 'items'):
85
            yield [name], [node]
86
        else:
87
            for names, nodes in walk_paths(node):
88
                yield [name] + names, [node] + nodes
89

  
90

  
91
def list_paths(container, sep='.'):
92
    """
93
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}}}))
94
    [('a.b.c', 'd')]
95
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': 3}}))
96
    [('a.b.c', 'd'), ('a.e', 3)]
97
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}}))
98
    [('a.b.c', 'd'), ('a.e.f', 3)]
99
    >>> list_paths({})
100
    []
101

  
102
    """
103
    return [(sep.join(name_path), node_path[-1])
104
            for name_path, node_path in walk_paths(container)]
105

  
106

  
107
def del_path(container, path, sep='.', collect=True):
108
    """
109
    del container['a']['b']['c'] where path=sep.join(['a','b','c'])
110

  
111
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c'); d
112
    {}
113
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c', collect=False); d
114
    {'a': {'b': {}}}
115
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c.d')
116
    Traceback (most recent call last):
117
    ValueError: 'a.b.c': cannot traverse path beyond this node:\
118
 'str' object does not support item deletion
119
    """
120

  
121
    name_path, node_path, basename = \
122
            lookup_path(container, path, sep=sep, createpath=False)
123

  
124
    lastnode = node_path.pop()
125
    lastname = basename
126
    try:
127
        if basename in lastnode:
128
            del lastnode[basename]
129
    except (TypeError, KeyError) as e:
130
        m = "'{0}': cannot traverse path beyond this node: {1}"
131
        m = m.format(sep.join(name_path), str(e))
132
        raise ValueError(m)
133

  
134
    if collect:
135
        while node_path and not lastnode:
136
            basename = name_path.pop()
137
            lastnode = node_path.pop()
138
            del lastnode[basename]
139

  
140

  
141
def get_path(container, path, sep='.'):
142
    """
143
    return container['a']['b']['c'] where path=sep.join(['a','b','c'])
144

  
145
    >>> get_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d')
146
    Traceback (most recent call last):
147
    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
148
 string indices must be integers, not str
149
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c.d')
150
    Traceback (most recent call last):
151
    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
152
 'int' object is unsubscriptable
153
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c')
154
    1
155
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b')
156
    {'c': 1}
157

  
158
    """
159
    name_path, node_path, basename = \
160
            lookup_path(container, path, sep=sep, createpath=False)
161
    name_path.append(basename)
162
    node = node_path[-1]
163

  
164
    try:
165
        return node[basename]
166
    except TypeError as e:
167
        m = "'{0}': cannot traverse path beyond this node: {1}"
168
        m = m.format(sep.join(name_path), str(e))
169
        raise ValueError(m)
170
    except KeyError as e:
171
        m = "'{0}': path not found: {1}"
172
        m = m.format(sep.join(name_path), str(e))
173
        raise KeyError(m)
174

  
175

  
176
def set_path(container, path, value, sep='.',
177
             createpath=False, overwrite=True):
178
    """
179
    container['a']['b']['c'] = value where path=sep.join(['a','b','c'])
180

  
181
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1)
182
    Traceback (most recent call last):
183
    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
184
 'str' object does not support item assignment
185
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1)
186
    Traceback (most recent call last):
187
    KeyError: "'a.b.x': path not found"
188
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1, createpath=True)
189
    
190
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1)
191
     
192
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False)
193
    Traceback (most recent call last):
194
    ValueError: will not overwrite path 'a.b.c'
195

  
196
    """
197
    name_path, node_path, basename = \
198
            lookup_path(container, path, sep=sep, createpath=createpath)
199
    name_path.append(basename)
200
    node = node_path[-1]
201

  
202
    if basename in node and not overwrite:
203
        m = "will not overwrite path '{0}'".format(path)
204
        raise ValueError(m)
205

  
206
    try:
207
        node[basename] = value
208
    except TypeError as e:
209
        m = "'{0}': cannot traverse path beyond this node: {1}"
210
        m = m.format(sep.join(name_path), str(e))
211
        raise ValueError(m)
212

  
213

  
214
if __name__ == '__main__':
215
    import doctest
216
    doctest.testmod()
b/astakosclient/astakosclient/services.py
1
# Copyright (C) 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34

  
35
astakos_services = {
36
    'astakos_account': {
37
        'type': 'account',
38
        'component': 'astakos',
39
        'prefix': 'account',
40
        'public': True,
41
        'endpoints': [
42
            {'version': 'v1.0',
43
             'publicURL': None},
44
        ]},
45

  
46
    'astakos_keystone': {
47
        'type': 'identity',
48
        'component': 'astakos',
49
        'prefix': 'identity',
50
        'public': True,
51
        'endpoints': [
52
            {'version': 'v2.0',
53
             'publicURL': None},
54
        ]},
55

  
56
    'astakos_ui': {
57
        'type': 'astakos_ui',
58
        'component': 'astakos',
59
        'prefix': 'ui',
60
        'public': False,
61
        'endpoints': [
62
            {'version': 'v1.0',
63
             'publicURL': None},
64
        ]},
65
}
b/snf-cyclades-app/synnefo/cyclades_settings.py
35 35
from synnefo.lib import join_urls, parse_base_url
36 36
from synnefo.util.keypath import get_path
37 37
from synnefo.api.services import cyclades_services as vanilla_cyclades_services
38
from astakosclient import astakos_services as vanilla_astakos_services
38
from astakosclient import astakos_services
39 39

  
40 40
from copy import deepcopy
41 41

  
42

  
43
# Process Cyclades settings
44

  
42 45
BASE_URL = getattr(settings, 'CYCLADES_BASE_URL',
43 46
                   'https://compute.example.synnefo.org/compute/')
44 47
BASE_HOST, BASE_PATH = parse_base_url(BASE_URL)
45 48

  
46
ASTAKOS_BASE_URL = getattr(settings, 'ASTAKOS_BASE_URL',
47
                           'https://accounts.example.synnefo.org/astakos/')
48
ASTAKOS_BASE_HOST, ASTAKOS_BASE_PATH = parse_base_url(ASTAKOS_BASE_URL)
49

  
50 49
CUSTOMIZE_SERVICES = getattr(settings, 'CYCLADES_CUSTOMIZE_SERVICES', ())
51 50
cyclades_services = deepcopy(vanilla_cyclades_services)
52 51
for path, value in CUSTOMIZE_SERVICES:
53 52
    set_path(cyclades_services, path, value, createpath=True)
54 53

  
55
astakos_services = deepcopy(vanilla_astakos_services)
56
CUSTOMIZE_ASTAKOS_SERVICES = \
57
        getattr(settings, 'CYCLADES_CUSTOMIZE_ASTAKOS_SERVICES', ())
58
for path, value in CUSTOMIZE_ASTAKOS_SERVICES:
59
    set_path(astakos_services, path, value, createpath=True)
60

  
61 54
COMPUTE_PREFIX = get_path(cyclades_services, 'cyclades_compute.prefix')
62 55
VMAPI_PREFIX = get_path(cyclades_services, 'cyclades_vmapi.prefix')
63 56
PLANKTON_PREFIX = get_path(cyclades_services, 'cyclades_plankton.prefix')
......
65 58
UI_PREFIX = get_path(cyclades_services, 'cyclades_ui.prefix')
66 59
USERDATA_PREFIX = get_path(cyclades_services, 'cyclades_userdata.prefix')
67 60

  
61
COMPUTE_ROOT_URL = join_urls(BASE_URL, COMPUTE_PREFIX)
62

  
63

  
64
# Process Astakos settings
65

  
66
ASTAKOS_BASE_URL = getattr(settings, 'ASTAKOS_BASE_URL',
67
                           'https://accounts.example.synnefo.org/astakos/')
68
ASTAKOS_BASE_HOST, ASTAKOS_BASE_PATH = parse_base_url(ASTAKOS_BASE_URL)
69

  
70
# Patch astakosclient directly, otherwise it will not see any customization
71
#astakos_services = deepcopy(vanilla_astakos_services)
72
CUSTOMIZE_ASTAKOS_SERVICES = \
73
        getattr(settings, 'CYCLADES_CUSTOMIZE_ASTAKOS_SERVICES', ())
74
for path, value in CUSTOMIZE_ASTAKOS_SERVICES:
75
    set_path(astakos_services, path, value, createpath=True)
76

  
68 77
ASTAKOS_ACCOUNTS_PREFIX = get_path(astakos_services, 'astakos_account.prefix')
69 78
ASTAKOS_VIEWS_PREFIX = get_path(astakos_services, 'astakos_ui.prefix')
70 79
ASTAKOS_KEYSTONE_PREFIX = get_path(astakos_services, 'astakos_keystone.prefix')
71 80

  
72
# The API implementation needs to accept and return absolute references
73
# to its resources. Thus, it needs to know its public URL.
74
COMPUTE_ROOT_URL = join_urls(BASE_URL, COMPUTE_PREFIX)
81

  
82
# Proxy Astakos settings
75 83

  
76 84
BASE_ASTAKOS_PROXY_PATH = getattr(settings,
77 85
                                  'CYCLADES_BASE_ASTAKOS_PROXY_PATH',

Also available in: Unified diff