Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / astakos / __init__.py @ a22d311c

History | View | Annotate | Download (10 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 *
36
#  astakosclient contains: AstakosCLient, AstakosClientException
37

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

    
40

    
41

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

    
50

    
51
class SynnefoAstakosClient(AstakosClient):
52
    """An astakosclient.AstakosClient wrapper, that logs the way of kamaki"""
53

    
54
    LOG_TOKEN = False
55
    LOG_DATA = False
56

    
57
    def _dump_response(self, request, status, message, data):
58
        recvlog.info('\n%d %s' % (status, message))
59
        recvlog.info('data size: %s' % len(data))
60
        if not self.LOG_TOKEN:
61
            token = request.headers.get('X-Auth-Token', '')
62
            if self.LOG_DATA:
63
                data = data.replace(token, '...') if token else data
64
        if self.LOG_DATA:
65
            recvlog.info(data)
66
        recvlog.info('-             -        -     -   -  - -')
67

    
68
    def _call_astakos(self, *args, **kwargs):
69
        r = super(SynnefoAstakosClient, self)._call_astakos(*args, **kwargs)
70
        try:
71
            log_request = getattr(self, 'log_request', None)
72
            if log_request:
73
                req = RequestManager(
74
                    method=log_request['method'],
75
                    url='%s://%s' % (self.scheme, self.astakos_base_url),
76
                    path=log_request['path'],
77
                    data=log_request.get('body', None),
78
                    headers=log_request.get('headers', dict()))
79
                req.LOG_TOKEN, req.LOG_DATA = self.LOG_TOKEN, self.LOG_DATA
80
                req.dump_log()
81
                log_response = getattr(self, 'log_response', None)
82
                if log_response:
83
                    self._dump_response(
84
                        req,
85
                        status=log_response['status'],
86
                        message=log_response['message'],
87
                        data=log_response.get('data', ''))
88
        except Exception:
89
            pass
90
        finally:
91
            return r
92

    
93

    
94
class CachedAstakosClient(Client):
95
    """Synnefo Astakos cached client wraper"""
96

    
97
    @_astakos_error
98
    def __init__(self, base_url, token=None):
99
        super(CachedAstakosClient, self).__init__(base_url, token)
100
        self._astakos = dict()
101
        self._uuids = dict()
102
        self._cache = dict()
103
        self._uuids2usernames = dict()
104
        self._usernames2uuids = dict()
105

    
106
    def _resolve_token(self, token):
107
        """
108
        :returns: (str) a single token
109

110
        :raises AssertionError: if no token exists (either param or member)
111
        """
112
        token = token or self.token
113
        assert token, 'No token provided'
114
        return token[0] if (
115
            isinstance(token, list) or isinstance(token, tuple)) else token
116

    
117
    def get_client(self, token=None):
118
        """Get the Synnefo AstakosClient instance used by client"""
119
        token = self._resolve_token(token)
120
        self._validate_token(token)
121
        return self._astakos[self._uuids[token]]
122

    
123
    @_astakos_error
124
    def authenticate(self, token=None):
125
        """Get authentication information and store it in this client
126
        As long as the CachedAstakosClient instance is alive, the latest
127
        authentication information for this token will be available
128

129
        :param token: (str) custom token to authenticate
130
        """
131
        token = self._resolve_token(token)
132
        astakos = SynnefoAstakosClient(
133
            token, self.base_url, logger=getLogger('astakosclient'))
134
        astakos.LOG_TOKEN = getattr(self, 'LOG_TOKEN', False)
135
        astakos.LOG_DATA = getattr(self, 'LOG_DATA', False)
136
        r = astakos.authenticate()
137
        uuid = r['access']['user']['id']
138
        self._uuids[token] = uuid
139
        self._cache[uuid] = r
140
        self._astakos[uuid] = astakos
141
        self._uuids2usernames[uuid] = dict()
142
        self._usernames2uuids[uuid] = dict()
143
        return self._cache[uuid]
144

    
145
    def remove_user(self, uuid):
146
        self._uuids.pop(self.get_token(uuid))
147
        self._cache.pop(uuid)
148
        self._astakos.pop(uuid)
149
        self._uuids2usernames.pop(uuid)
150
        self._usernames2uuids.pop(uuid)
151

    
152
    def get_token(self, uuid):
153
        return self._cache[uuid]['access']['token']['id']
154

    
155
    def _validate_token(self, token):
156
        if (token not in self._uuids) or (
157
                self.get_token(self._uuids[token]) != token):
158
            self._uuids.pop(token, None)
159
            self.authenticate(token)
160

    
161
    def get_services(self, token=None):
162
        """
163
        :returns: (list) [{name:..., type:..., endpoints:[...]}, ...]
164
        """
165
        token = self._resolve_token(token)
166
        self._validate_token(token)
167
        r = self._cache[self._uuids[token]]
168
        return r['access']['serviceCatalog']
169

    
170
    def get_service_details(self, service_type, token=None):
171
        """
172
        :param service_type: (str) compute, object-store, image, account, etc.
173

174
        :returns: (dict) {name:..., type:..., endpoints:[...]}
175

176
        :raises ClientError: (600) if service_type not in service catalog
177
        """
178
        services = self.get_services(token)
179
        for service in services:
180
            try:
181
                if service['type'].lower() == service_type.lower():
182
                    return service
183
            except KeyError:
184
                self.log.warning('Misformated service %s' % service)
185
        raise ClientError(
186
            'Service type "%s" not in service catalog' % service_type, 600)
187

    
188
    def get_service_endpoints(self, service_type, version=None, token=None):
189
        """
190
        :param service_type: (str) can be compute, object-store, etc.
191

192
        :param version: (str) the version id of the service
193

194
        :returns: (dict) {SNF:uiURL, adminURL, internalURL, publicURL, ...}
195

196
        :raises ClientError: (600) if service_type not in service catalog
197

198
        :raises ClientError: (601) if #matching endpoints != 1
199
        """
200
        service = self.get_service_details(service_type, token)
201
        matches = []
202
        for endpoint in service['endpoints']:
203
            if (not version) or (
204
                    endpoint['versionId'].lower() == version.lower()):
205
                matches.append(endpoint)
206
        if len(matches) != 1:
207
            raise ClientError(
208
                '%s endpoints match type %s %s' % (
209
                    len(matches), service_type,
210
                    ('and versionId %s' % version) if version else ''),
211
                601)
212
        return matches[0]
213

    
214
    def list_users(self):
215
        """list cached users information"""
216
        if not self._cache:
217
            self.authenticate()
218
        r = []
219
        for k, v in self._cache.items():
220
            r.append(dict(v['access']['user']))
221
            r[-1].update(dict(auth_token=self.get_token(k)))
222
        return r
223

    
224
    def user_info(self, token=None):
225
        """Get (cached) user information"""
226
        token = self._resolve_token(token)
227
        self._validate_token(token)
228
        r = self._cache[self._uuids[token]]
229
        return r['access']['user']
230

    
231
    def term(self, key, token=None):
232
        """Get (cached) term, from user credentials"""
233
        return self.user_term(key, token)
234

    
235
    def user_term(self, key, token=None):
236
        """Get (cached) term, from user credentials"""
237
        return self.user_info(token).get(key, None)
238

    
239
    def post_user_catalogs(self, uuids=None, displaynames=None, token=None):
240
        """POST base_url/user_catalogs
241

242
        :param uuids: (list or tuple) user uuids
243

244
        :param displaynames: (list or tuple) usernames (mut. excl. to uuids)
245

246
        :returns: (dict) {uuid1: name1, uuid2: name2, ...} or oposite
247
        """
248
        return self.uuids2usernames(uuids, token) if (
249
            uuids) else self.usernames2uuids(displaynames, token)
250

    
251
    @_astakos_error
252
    def uuids2usernames(self, uuids, 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(uuids or []).difference(self._uuids2usernames[uuid]):
258
            self._uuids2usernames[uuid].update(astakos.get_usernames(uuids))
259
        return self._uuids2usernames[uuid]
260

    
261
    @_astakos_error
262
    def usernames2uuids(self, usernames, token=None):
263
        token = self._resolve_token(token)
264
        self._validate_token(token)
265
        uuid = self._uuids[token]
266
        astakos = self._astakos[uuid]
267
        if set(usernames or []).difference(self._usernames2uuids[uuid]):
268
            self._usernames2uuids[uuid].update(astakos.get_uuids(usernames))
269
        return self._usernames2uuids[uuid]