__init__.py 10 KB
Newer Older
1
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
# 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.

34
from logging import getLogger
35
36
from astakosclient import *
#  astakosclient contains: AstakosCLient, AstakosClientException
37

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


41

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


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

    LOG_TOKEN = False
    LOG_DATA = False
56
57

    def _dump_response(self, request, status, message, data):
58
59
        recvlog.info('\n%d %s' % (status, message))
        recvlog.info('data size: %s' % len(data))
60
61
62
63
64
65
        if not self.LOG_TOKEN:
            token = request.headers.get('X-Auth-Token', '')
            if self.LOG_DATA:
                data = data.replace(token, '...') if token else data
        if self.LOG_DATA:
            recvlog.info(data)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
66
        recvlog.info('-             -        -     -   -  - -')
67
68
69
70
71
72
73
74
75
76
77
78

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


94
class CachedAstakosClient(Client):
95
    """Synnefo Astakos cached client wraper"""
Giorgos Verigakis's avatar
Giorgos Verigakis committed
96

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

    def _resolve_token(self, token):
        """
        :returns: (str) a single token

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

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

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
128
        authentication information for this token will be available

129
130
        :param token: (str) custom token to authenticate
        """
131
132
        token = self._resolve_token(token)
        astakos = SynnefoAstakosClient(
133
            token, self.base_url, logger=getLogger('astakosclient'))
134
135
        astakos.LOG_TOKEN = getattr(self, 'LOG_TOKEN', False)
        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
142
        self._uuids2usernames[uuid] = dict()
        self._usernames2uuids[uuid] = dict()
143
        return self._cache[uuid]
144

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

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

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

161
162
163
164
    def get_services(self, token=None):
        """
        :returns: (list) [{name:..., type:..., endpoints:[...]}, ...]
        """
165
166
167
        token = self._resolve_token(token)
        self._validate_token(token)
        r = self._cache[self._uuids[token]]
168
        return r['access']['serviceCatalog']
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

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

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

        :raises ClientError: (600) if service_type not in service catalog
        """
        services = self.get_services(token)
        for service in services:
            try:
                if service['type'].lower() == service_type.lower():
                    return service
            except KeyError:
                self.log.warning('Misformated service %s' % service)
        raise ClientError(
            'Service type "%s" not in service catalog' % service_type, 600)

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

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

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

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

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

    def list_users(self):
        """list cached users information"""
216
217
        if not self._cache:
            self.authenticate()
218
219
        r = []
        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
227
228
        token = self._resolve_token(token)
        self._validate_token(token)
        r = self._cache[self._uuids[token]]
229
        return r['access']['user']
230

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

    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
241
242
243
        """POST base_url/user_catalogs

        :param uuids: (list or tuple) user uuids

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

        :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
253
254
    def uuids2usernames(self, uuids, token=None):
        token = self._resolve_token(token)
        self._validate_token(token)
255
256
        uuid = self._uuids[token]
        astakos = self._astakos[uuid]
257
        if set(uuids or []).difference(self._uuids2usernames[uuid]):
258
259
            self._uuids2usernames[uuid].update(astakos.get_usernames(uuids))
        return self._uuids2usernames[uuid]
260

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