Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / astakos / __init__.py @ 058ee9a8

History | View | Annotate | Download (9.7 kB)

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
from logging import getLogger
35
from astakosclient import AstakosClient as SynnefoAstakosClientOrig
36
from astakosclient import AstakosClientException as SynnefoAstakosClientError
37

    
38
from kamaki.clients import Client, ClientError, RequestManager, recvlog
39

    
40

    
41
def _astakos_error(foo):
42
    def wrap(self, *args, **kwargs):
43
        try:
44
            return foo(self, *args, **kwargs)
45
        except SynnefoAstakosClientError as sace:
46
            self._raise_for_status(sace)
47
    return wrap
48

    
49

    
50
class SynnefoAstakosClient(SynnefoAstakosClientOrig):
51
    """A synnefo astakosclient.AstakosClient wrapper, that logs"""
52

    
53
    def _dump_response(self, request, status, message, data):
54
        recvlog.info('\n%d %s' % (status, message))
55
        recvlog.info('data size: %s' % len(data))
56
        token = request.headers.get('X-Auth-Token', '')
57
        data = data.replace(token, '...') if token else data
58
        recvlog.info(data)
59
        recvlog.info('-             -        -     -   -  - -')
60

    
61
    def _call_astakos(self, *args, **kwargs):
62
        r = super(SynnefoAstakosClient, self)._call_astakos(*args, **kwargs)
63
        try:
64
            log_request = getattr(self, 'log_request', None)
65
            if log_request:
66
                req = RequestManager(
67
                    method=log_request['method'],
68
                    url='%s://%s' % (self.scheme, self.astakos_base_url),
69
                    path=log_request['path'],
70
                    data=log_request.get('body', None),
71
                    headers=log_request.get('headers', dict()))
72
                req.dump_log()
73
                log_response = getattr(self, 'log_response', None)
74
                if log_response:
75
                    self._dump_response(
76
                        req,
77
                        status=log_response['status'],
78
                        message=log_response['message'],
79
                        data=log_response.get('data', ''))
80
        except Exception:
81
            pass
82
        finally:
83
            return r
84

    
85

    
86
class AstakosClient(Client):
87
    """Synnefo Astakos cached client wraper"""
88

    
89
    @_astakos_error
90
    def __init__(self, base_url, token=None):
91
        super(AstakosClient, self).__init__(base_url, token)
92
        self._astakos = dict()
93
        self._uuids = dict()
94
        self._cache = dict()
95
        self._uuids2usernames = dict()
96
        self._usernames2uuids = dict()
97

    
98
    def _resolve_token(self, token):
99
        """
100
        :returns: (str) a single token
101

102
        :raises AssertionError: if no token exists (either param or member)
103
        """
104
        token = token or self.token
105
        assert token, 'No token provided'
106
        return token[0] if (
107
            isinstance(token, list) or isinstance(token, tuple)) else token
108

    
109
    def get_client(self, token=None):
110
        """Get the Synnefo AstakosClient instance used by client"""
111
        token = self._resolve_token(token)
112
        self._validate_token(token)
113
        return self._astakos[self._uuids[token]]
114

    
115
    @_astakos_error
116
    def authenticate(self, token=None):
117
        """Get authentication information and store it in this client
118
        As long as the AstakosClient instance is alive, the latest
119
        authentication information for this token will be available
120

121
        :param token: (str) custom token to authenticate
122
        """
123
        token = self._resolve_token(token)
124
        astakos = SynnefoAstakosClient(
125
            token, self.base_url, logger=getLogger('astakosclient'))
126
        r = astakos.authenticate()
127
        uuid = r['access']['user']['id']
128
        self._uuids[token] = uuid
129
        self._cache[uuid] = r
130
        self._astakos[uuid] = astakos
131
        self._uuids2usernames[uuid] = dict()
132
        self._usernames2uuids[uuid] = dict()
133
        return self._cache[uuid]
134

    
135
    def remove_user(self, uuid):
136
        self._uuids.pop(self.get_token(uuid))
137
        self._cache.pop(uuid)
138
        self._astakos.pop(uuid)
139
        self._uuids2usernames.pop(uuid)
140
        self._usernames2uuids.pop(uuid)
141

    
142
    def get_token(self, uuid):
143
        return self._cache[uuid]['access']['token']['id']
144

    
145
    def _validate_token(self, token):
146
        if (token not in self._uuids) or (
147
                self.get_token(self._uuids[token]) != token):
148
            self._uuids.pop(token, None)
149
            self.authenticate(token)
150

    
151
    def get_services(self, token=None):
152
        """
153
        :returns: (list) [{name:..., type:..., endpoints:[...]}, ...]
154
        """
155
        token = self._resolve_token(token)
156
        self._validate_token(token)
157
        r = self._cache[self._uuids[token]]
158
        return r['access']['serviceCatalog']
159

    
160
    def get_service_details(self, service_type, token=None):
161
        """
162
        :param service_type: (str) compute, object-store, image, account, etc.
163

164
        :returns: (dict) {name:..., type:..., endpoints:[...]}
165

166
        :raises ClientError: (600) if service_type not in service catalog
167
        """
168
        services = self.get_services(token)
169
        for service in services:
170
            try:
171
                if service['type'].lower() == service_type.lower():
172
                    return service
173
            except KeyError:
174
                self.log.warning('Misformated service %s' % service)
175
        raise ClientError(
176
            'Service type "%s" not in service catalog' % service_type, 600)
177

    
178
    def get_service_endpoints(self, service_type, version=None, token=None):
179
        """
180
        :param service_type: (str) can be compute, object-store, etc.
181

182
        :param version: (str) the version id of the service
183

184
        :returns: (dict) {SNF:uiURL, adminURL, internalURL, publicURL, ...}
185

186
        :raises ClientError: (600) if service_type not in service catalog
187

188
        :raises ClientError: (601) if #matching endpoints != 1
189
        """
190
        service = self.get_service_details(service_type, token)
191
        matches = []
192
        for endpoint in service['endpoints']:
193
            if (not version) or (
194
                    endpoint['versionId'].lower() == version.lower()):
195
                matches.append(endpoint)
196
        if len(matches) != 1:
197
            raise ClientError(
198
                '%s endpoints match type %s %s' % (
199
                    len(matches), service_type,
200
                    ('and versionId %s' % version) if version else ''),
201
                601)
202
        return matches[0]
203

    
204
    def list_users(self):
205
        """list cached users information"""
206
        if not self._cache:
207
            self.authenticate()
208
        r = []
209
        for k, v in self._cache.items():
210
            r.append(dict(v['access']['user']))
211
            r[-1].update(dict(auth_token=self.get_token(k)))
212
        return r
213

    
214
    def user_info(self, token=None):
215
        """Get (cached) user information"""
216
        token = self._resolve_token(token)
217
        self._validate_token(token)
218
        r = self._cache[self._uuids[token]]
219
        return r['access']['user']
220

    
221
    def term(self, key, token=None):
222
        """Get (cached) term, from user credentials"""
223
        return self.user_term(key, token)
224

    
225
    def user_term(self, key, token=None):
226
        """Get (cached) term, from user credentials"""
227
        return self.user_info(token).get(key, None)
228

    
229
    def post_user_catalogs(self, uuids=None, displaynames=None, token=None):
230
        """POST base_url/user_catalogs
231

232
        :param uuids: (list or tuple) user uuids
233

234
        :param displaynames: (list or tuple) usernames (mut. excl. to uuids)
235

236
        :returns: (dict) {uuid1: name1, uuid2: name2, ...} or oposite
237
        """
238
        return self.uuids2usernames(uuids, token) if (
239
            uuids) else self.usernames2uuids(displaynames, token)
240

    
241
    @_astakos_error
242
    def uuids2usernames(self, uuids, token=None):
243
        token = self._resolve_token(token)
244
        self._validate_token(token)
245
        uuid = self._uuids[token]
246
        astakos = self._astakos[uuid]
247
        if set(uuids).difference(self._uuids2usernames[uuid]):
248
            self._uuids2usernames[uuid].update(astakos.get_usernames(uuids))
249
        return self._uuids2usernames[uuid]
250

    
251
    @_astakos_error
252
    def usernames2uuids(self, usernames, token=None):
253
        token = self._resolve_token(token)
254
        self._validate_token(token)
255
        uuid = self._uuids[token]
256
        astakos = self._astakos[uuid]
257
        if set(usernames).difference(self._usernames2uuids[uuid]):
258
            self._usernames2uuids[uuid].update(astakos.get_uuids(usernames))
259
        return self._usernames2uuids[uuid]