Revision 1ecb12b5

b/.gitignore
34 34
*.iml
35 35
*.graffle
36 36
snf-stats-app/synnefo_stats/version.py
37
snf-astakos-client/astakosclient/version.py
37
astakosclient/astakosclient/version.py
38
snf-django-lib/snf_django/version.py
b/astakosclient/COPYRIGHT
1
Copyright (C) 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.
b/astakosclient/MANIFEST.in
1
include distribute_setup.py
2
include README Changelog
b/astakosclient/README
1
astakos-client
2
==============
3

  
4
Python client for the Astakos, the Identity Management Service of
5
synnefo(http://www.synnefo.org)
6

  
7
Feedback
8
========
9

  
10
For any comments or suggestions for improvement, please contact
11
the synnefo development team <synnefo-devel@googlegroups.com>
b/astakosclient/astakosclient/__init__.py
1
# Copyright (C) 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
import logging
35
import urlparse
36
import urllib
37
import hashlib
38
from copy import copy
39

  
40
import simplejson
41
from astakosclient.utils import retry, scheme_to_class
42
from astakosclient.errors import \
43
    AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
44
    NoUserName, NoUUID
45

  
46

  
47
# --------------------------------------------------------------------
48
# Astakos Client Class
49

  
50
def get_token_from_cookie(request, cookie_name):
51
    """Extract token from the cookie name provided
52

  
53
    Cookie should be in the same form as astakos
54
    service sets its cookie contents:
55
        <user_uniq>|<user_token>
56

  
57
    """
58
    try:
59
        cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None))
60
        return cookie_content.split("|")[1]
61
    except:
62
        return None
63

  
64

  
65
class AstakosClient():
66
    """AstakosClient Class Implementation"""
67

  
68
    # ----------------------------------
69
    def __init__(self, astakos_url, retry=0,
70
                 use_pool=False, pool_size=8, logger=None):
71
        """Intialize AstakosClient Class
72

  
73
        Keyword arguments:
74
        astakos_url -- i.e https://accounts.example.com (string)
75
        use_pool    -- use objpool for http requests (boolean)
76
        retry       -- how many time to retry (integer)
77
        logger      -- pass a different logger
78

  
79
        """
80
        if logger is None:
81
            logging.basicConfig(
82
                format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
83
                datefmt='%Y-%m-%d %H:%M:%S',
84
                level=logging.INFO)
85
            logger = logging.getLogger("astakosclient")
86
        logger.debug("Intialize AstakosClient: astakos_url = %s, "
87
                     "use_pool = %s" % (astakos_url, use_pool))
88

  
89
        if not astakos_url:
90
            m = "Astakos url not given"
91
            logger.error(m)
92
            raise ValueError(m)
93

  
94
        # Check for supported scheme
95
        p = urlparse.urlparse(astakos_url)
96
        conn_class = scheme_to_class(p.scheme, use_pool, pool_size)
97
        if conn_class is None:
98
            m = "Unsupported scheme: %s" % p.scheme
99
            logger.error(m)
100
            raise ValueError(m)
101

  
102
        # Save astakos_url etc. in our class
103
        self.retry = retry
104
        self.logger = logger
105
        self.netloc = p.netloc
106
        self.scheme = p.scheme
107
        self.conn_class = conn_class
108

  
109
    # ----------------------------------
110
    @retry
111
    def _call_astakos(self, token, request_path,
112
                      headers=None, body=None, method="GET"):
113
        """Make the actual call to Astakos Service"""
114
        hashed_token = hashlib.sha1()
115
        hashed_token.update(token)
116
        self.logger.debug(
117
            "Make a %s request to %s using token %s "
118
            "with headers %s and body %s"
119
            % (method, request_path, hashed_token.hexdigest(), headers, body))
120

  
121
        # Check Input
122
        if not token:
123
            m = "Token not given"
124
            self.logger.error(m)
125
            raise ValueError(m)
126
        if headers is None:
127
            headers = {}
128
        if body is None:
129
            body = {}
130
        if request_path[0] != '/':
131
            request_path = "/" + request_path
132

  
133
        # Build request's header and body
134
        kwargs = {}
135
        kwargs['headers'] = copy(headers)
136
        kwargs['headers']['X-Auth-Token'] = token
137
        if body:
138
            kwargs['body'] = copy(body)
139
            kwargs['headers'].setdefault(
140
                'content-type', 'application/octet-stream')
141
        kwargs['headers'].setdefault('content-length',
142
                                     len(body) if body else 0)
143

  
144
        try:
145
            # Get the connection object
146
            with self.conn_class(self.netloc) as conn:
147
                # Send request
148
                (data, status) = \
149
                    _do_request(conn, method, request_path, **kwargs)
150
        except Exception as err:
151
            self.logger.error("Failed to send request: %s" % repr(err))
152
            raise AstakosClientException(str(err))
153

  
154
        # Return
155
        self.logger.debug("Request returned with status %s" % status)
156
        if status == 400:
157
            raise BadRequest(data)
158
        elif status == 401:
159
            raise Unauthorized(data)
160
        elif status == 403:
161
            raise Forbidden(data)
162
        elif status == 404:
163
            raise NotFound(data)
164
        elif status < 200 or status >= 300:
165
            raise AstakosClientException(data, status)
166
        return simplejson.loads(unicode(data))
167

  
168
    # ------------------------
169
    def get_user_info(self, token, usage=False):
170
        """Authenticate user and get user's info as a dictionary
171

  
172
        Keyword arguments:
173
        token   -- user's token (string)
174
        usage   -- return usage information for user (boolean)
175

  
176
        In case of success return user information (json parsed format).
177
        Otherwise raise an AstakosClientException.
178

  
179
        """
180
        # Send request
181
        auth_path = "/im/authenticate"
182
        if usage:
183
            auth_path += "?usage=1"
184
        return self._call_astakos(token, auth_path)
185

  
186
    # ----------------------------------
187
    def _uuid_catalog(self, token, uuids, req_path):
188
        req_headers = {'content-type': 'application/json'}
189
        req_body = simplejson.dumps({'uuids': uuids})
190
        data = self._call_astakos(
191
            token, req_path, req_headers, req_body, "POST")
192
        if "uuid_catalog" in data:
193
            return data.get("uuid_catalog")
194
        else:
195
            m = "_uuid_catalog request returned %s. No uuid_catalog found" \
196
                % data
197
            self.logger.error(m)
198
            raise AstakosClientException(m)
199

  
200
    def get_usernames(self, token, uuids):
201
        """Return a uuid_catalog dictionary for the given uuids
202

  
203
        Keyword arguments:
204
        token   -- user's token (string)
205
        uuids   -- list of user ids (list of strings)
206

  
207
        The returned uuid_catalog is a dictionary with uuids as
208
        keys and the corresponding user names as values
209

  
210
        """
211
        req_path = "/user_catalogs"
212
        return self._uuid_catalog(token, uuids, req_path)
213

  
214
    def get_username(self, token, uuid):
215
        """Return the user name of a uuid (see get_usernames)"""
216
        if not uuid:
217
            m = "No uuid was given"
218
            self.logger.error(m)
219
            raise ValueError(m)
220
        uuid_dict = self.get_usernames(token, [uuid])
221
        if uuid in uuid_dict:
222
            return uuid_dict.get(uuid)
223
        else:
224
            raise NoUserName(uuid)
225

  
226
    def service_get_usernames(self, token, uuids):
227
        """Return a uuid_catalog dict using a service's token"""
228
        req_path = "/service/api/user_catalogs"
229
        return self._uuid_catalog(token, uuids, req_path)
230

  
231
    def service_get_username(self, token, uuid):
232
        """Return the displayName of a uuid using a service's token"""
233
        if not uuid:
234
            m = "No uuid was given"
235
            self.logger.error(m)
236
            raise ValueError(m)
237
        uuid_dict = self.service_get_usernames(token, [uuid])
238
        if uuid in uuid_dict:
239
            return uuid_dict.get(uuid)
240
        else:
241
            raise NoUserName(uuid)
242

  
243
    # ----------------------------------
244
    def _displayname_catalog(self, token, display_names, req_path):
245
        req_headers = {'content-type': 'application/json'}
246
        req_body = simplejson.dumps({'displaynames': display_names})
247
        data = self._call_astakos(
248
            token, req_path, req_headers, req_body, "POST")
249
        if "displayname_catalog" in data:
250
            return data.get("displayname_catalog")
251
        else:
252
            m = "_displayname_catalog request returned %s. " \
253
                "No displayname_catalog found" % data
254
            self.logger.error(m)
255
            raise AstakosClientException(m)
256

  
257
    def get_uuids(self, token, display_names):
258
        """Return a displayname_catalog for the given names
259

  
260
        Keyword arguments:
261
        token           -- user's token (string)
262
        display_names   -- list of user names (list of strings)
263

  
264
        The returned displayname_catalog is a dictionary with
265
        the names as keys and the corresponding uuids as values
266

  
267
        """
268
        req_path = "/user_catalogs"
269
        return self._displayname_catalog(token, display_names, req_path)
270

  
271
    def get_uuid(self, token, display_name):
272
        """Return the uuid of a name (see getUUIDs)"""
273
        if not display_name:
274
            m = "No display_name was given"
275
            self.logger.error(m)
276
            raise ValueError(m)
277
        name_dict = self.get_uuids(token, [display_name])
278
        if display_name in name_dict:
279
            return name_dict.get(display_name)
280
        else:
281
            raise NoUUID(display_name)
282

  
283
    def service_get_uuids(self, token, display_names):
284
        """Return a display_name catalog using a service's token"""
285
        req_path = "/service/api/user_catalogs"
286
        return self._displayname_catalog(token, display_names, req_path)
287

  
288
    def service_get_uuid(self, token, display_name):
289
        """Return the uuid of a name using a service's token"""
290
        if not display_name:
291
            m = "No display_name was given"
292
            self.logger.error(m)
293
            raise ValueError(m)
294
        name_dict = self.service_get_uuids(token, [display_name])
295
        if display_name in name_dict:
296
            return name_dict.get(display_name)
297
        else:
298
            raise NoUUID(display_name)
299

  
300
    # ----------------------------------
301
    def get_services(self):
302
        """Return a list of dicts with the registered services"""
303
        return self._call_astakos("dummy token", "/im/get_services")
304

  
305

  
306
# --------------------------------------------------------------------
307
# Private functions
308
# We want _doRequest to be a distinct function
309
# so that we can replace it during unit tests.
310
def _do_request(conn, method, url, **kwargs):
311
    """The actual request. This function can easily be mocked"""
312
    conn.request(method, url, **kwargs)
313
    response = conn.getresponse()
314
    length = response.getheader('content-length', None)
315
    data = response.read(length)
316
    status = int(response.status)
317
    return (data, status)
b/astakosclient/astakosclient/errors.py
1
# Copyright (C) 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
class AstakosClientException(Exception):
36
    def __init__(self, message, status=0):
37
        self.message = message
38
        self.status = status
39

  
40
    def __str__(self):
41
        return repr(self.message)
42

  
43

  
44
class BadRequest(AstakosClientException):
45
    def __init__(self, message):
46
        """400 Bad Request"""
47
        super(BadRequest, self).__init__(message, 400)
48

  
49

  
50
class Unauthorized(AstakosClientException):
51
    def __init__(self, message):
52
        """401 Invalid X-Auth-Token"""
53
        super(Unauthorized, self).__init__(message, 401)
54

  
55

  
56
class Forbidden(AstakosClientException):
57
    def __init__(self, message):
58
        """403 Forbidden"""
59
        super(Forbidden, self).__init__(message, 403)
60

  
61

  
62
class NotFound(AstakosClientException):
63
    def __init__(self, message):
64
        """404 Not Found"""
65
        super(NotFound, self).__init__(message, 404)
66

  
67

  
68
class NoUserName(AstakosClientException):
69
    def __init__(self, uuid):
70
        """No display name for the given uuid"""
71
        message = "No display name for the given uuid: %s" % uuid
72
        super(NoUserName, self).__init__(message)
73

  
74

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

  
36
"""Unit Tests for the astakos-client module
37

  
38
Provides unit tests for the code implementing
39
the astakos client library
40

  
41
"""
42

  
43
import sys
44
import socket
45
import simplejson
46

  
47
import astakosclient
48
from astakosclient import AstakosClient
49
from astakosclient.errors import \
50
    AstakosClientException, Unauthorized, BadRequest, NotFound, \
51
    NoUserName, NoUUID
52

  
53
# Use backported unittest functionality if Python < 2.7
54
try:
55
    import unittest2 as unittest
56
except ImportError:
57
    if sys.version_info < (2, 7):
58
        raise Exception("The unittest2 package is required for Python < 2.7")
59
    import unittest
60

  
61

  
62
# --------------------------------------------------------------------
63
# Helper functions
64

  
65
# ----------------------------
66
# This functions will be used as mocked requests
67
def _request_offline(conn, method, url, **kwargs):
68
    """This request behaves as we were offline"""
69
    raise socket.gaierror
70

  
71

  
72
def _request_status_302(conn, method, url, **kwargs):
73
    """This request returns 302"""
74
    status = 302
75
    data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
76
        '<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
77
        '<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
78
    return (data, status)
79

  
80

  
81
def _request_status_404(conn, method, url, **kwargs):
82
    """This request returns 404"""
83
    status = 404
84
    data = '<html><head><title>404 Not Found</title></head>' \
85
        '<body><h1>Not Found</h1><p>The requested URL /foo was ' \
86
        'not found on this server.</p><hr><address>Apache Server ' \
87
        'at example.com Port 80</address></body></html>'
88
    return (data, status)
89

  
90

  
91
def _request_status_401(conn, method, url, **kwargs):
92
    """This request returns 401"""
93
    status = 401
94
    data = "Invalid X-Auth-Token\n"
95
    return (data, status)
96

  
97

  
98
def _request_status_400(conn, method, url, **kwargs):
99
    """This request returns 400"""
100
    status = 400
101
    data = "Method not allowed.\n"
102
    return (data, status)
103

  
104

  
105
def _request_ok(conn, method, url, **kwargs):
106
    """This request behaves like original Astakos does"""
107
    if url[0:16] == "/im/authenticate":
108
        return _req_authenticate(conn, method, url, **kwargs)
109
    elif url[0:14] == "/user_catalogs":
110
        return _req_catalogs(conn, method, url, **kwargs)
111
    else:
112
        return _request_status_404(conn, method, url, **kwargs)
113

  
114

  
115
def _req_authenticate(conn, method, url, **kwargs):
116
    """Check if user exists and return his profile"""
117
    global user_1, user_2
118

  
119
    # Check input
120
    if conn.__class__.__name__ != "HTTPSConnection":
121
        return _request_status_302(conn, method, url, **kwargs)
122

  
123
    if method != "GET":
124
        return _request_status_400(conn, method, url, **kwargs)
125

  
126
    token = kwargs['headers']['X-Auth-Token']
127
    if token == token_1:
128
        user = dict(user_1)
129
    elif token == token_2:
130
        user = dict(user_2)
131
    else:
132
        # No user found
133
        return _request_status_401(conn, method, url, **kwargs)
134

  
135
    # Return
136
    if "usage=1" not in url:
137
        # Strip `usage' key from `user'
138
        del user['usage']
139
    return (simplejson.dumps(user), 200)
140

  
141

  
142
def _req_catalogs(conn, method, url, **kwargs):
143
    """Return user catalogs"""
144
    global token_1, token_2, user_1, user_2
145

  
146
    # Check input
147
    if conn.__class__.__name__ != "HTTPSConnection":
148
        return _request_status_302(conn, method, url, **kwargs)
149

  
150
    if method != "POST":
151
        return _request_status_400(conn, method, url, **kwargs)
152

  
153
    token = kwargs['headers']['X-Auth-Token']
154
    if token != token_1 and token != token_2:
155
        return _request_status_401(conn, method, url, **kwargs)
156

  
157
    # Return
158
    body = simplejson.loads(kwargs['body'])
159
    if 'uuids' in body:
160
        # Return uuid_catalog
161
        uuids = body['uuids']
162
        catalogs = {}
163
        if user_1['uuid'] in uuids:
164
            catalogs[user_1['uuid']] = user_1['username']
165
        if user_2['uuid'] in uuids:
166
            catalogs[user_2['uuid']] = user_2['username']
167
        return_catalog = {"displayname_catalog": {}, "uuid_catalog": catalogs}
168
    elif 'displaynames' in body:
169
        # Return displayname_catalog
170
        names = body['displaynames']
171
        catalogs = {}
172
        if user_1['username'] in names:
173
            catalogs[user_1['username']] = user_1['uuid']
174
        if user_2['username'] in names:
175
            catalogs[user_2['username']] = user_2['uuid']
176
        return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}}
177
    else:
178
        return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}}
179
    return (simplejson.dumps(return_catalog), 200)
180

  
181

  
182
# ----------------------------
183
# Mock the actual _doRequest
184
def _mock_request(new_requests):
185
    """Mock the actual request
186

  
187
    Given a list of requests to use (in rotation),
188
    replace the original _doRequest function with
189
    a new one
190

  
191
    """
192
    def _mock(conn, method, url, **kwargs):
193
        # Get first request
194
        request = _mock.requests[0]
195
        # Rotate requests
196
        _mock.requests = _mock.requests[1:] + _mock.requests[:1]
197
        # Use first request
198
        return request(conn, method, url, **kwargs)
199

  
200
    _mock.requests = new_requests
201
    # Replace `_doRequest' with our `_mock'
202
    astakosclient._do_request = _mock
203

  
204

  
205
# ----------------------------
206
# Local users
207
token_1 = "skzleaFlBl+fasFdaf24sx=="
208
user_1 = \
209
    {"username": "user1@example.com",
210
     "auth_token_created": 1359386939000,
211
     "name": "Example User One",
212
     "email": ["user1@example.com"],
213
     "auth_token_expires": 1361978939000,
214
     "id": 108,
215
     "uuid": "73917abc-abcd-477e-a1f1-1763abcdefab",
216
     "usage": [
217
         {"currValue": 42949672960,
218
          "display_name": "System Disk",
219
          "name": "cyclades.disk"},
220
         {"currValue": 4,
221
          "display_name": "CPU",
222
          "name": "cyclades.cpu"},
223
         {"currValue": 4294967296,
224
          "display_name": "RAM",
225
          "name": "cyclades.ram"},
226
         {"currValue": 3,
227
          "display_name": "VM",
228
          "name": "cyclades.vm"},
229
         {"currValue": 0,
230
          "display_name": "private network",
231
          "name": "cyclades.network.private"},
232
         {"currValue": 152,
233
          "display_name": "Storage Space",
234
          "name": "pithos+.diskspace"}]}
235

  
236
token_2 = "fasdfDSFdf98923DF+sdfk=="
237
user_2 = \
238
    {"username": "user2@example.com",
239
     "auth_token_created": 1358386938997,
240
     "name": "Example User Two",
241
     "email": ["user1@example.com"],
242
     "auth_token_expires": 1461998939000,
243
     "id": 109,
244
     "uuid": "73917bca-1234-5678-a1f1-1763abcdefab",
245
     "usage": [
246
         {"currValue": 68719476736,
247
          "display_name": "System Disk",
248
          "name": "cyclades.disk"},
249
         {"currValue": 1,
250
          "display_name": "CPU",
251
          "name": "cyclades.cpu"},
252
         {"currValue": 1073741824,
253
          "display_name": "RAM",
254
          "name": "cyclades.ram"},
255
         {"currValue": 2,
256
          "display_name": "VM",
257
          "name": "cyclades.vm"},
258
         {"currValue": 1,
259
          "display_name": "private network",
260
          "name": "cyclades.network.private"},
261
         {"currValue": 2341634510,
262
          "display_name": "Storage Space",
263
          "name": "pithos+.diskspace"}]}
264

  
265

  
266
# --------------------------------------------------------------------
267
# The actual tests
268

  
269
class TestCallAstakos(unittest.TestCase):
270
    """Test cases for function _callAstakos"""
271

  
272
    # ----------------------------------
273
    # Test the response we get if we don't have internet access
274
    def _offline(self, pool):
275
        global token_1
276
        _mock_request([_request_offline])
277
        try:
278
            client = AstakosClient("https://example.com", use_pool=pool)
279
            client._call_astakos(token_1, "/im/authenticate")
280
        except AstakosClientException:
281
            pass
282
        else:
283
            self.fail("Should have raised AstakosClientException")
284

  
285
    def test_offline(self):
286
        """Test _offline without pool"""
287
        self._offline(False)
288

  
289
    def test_offline_pool(self):
290
        """Test _offline using pool"""
291
        self._offline(True)
292

  
293
    # ----------------------------------
294
    # Test the response we get if we send invalid token
295
    def _invalid_token(self, pool):
296
        token = "skaksaFlBl+fasFdaf24sx=="
297
        _mock_request([_request_ok])
298
        try:
299
            client = AstakosClient("https://example.com", use_pool=pool)
300
            client._call_astakos(token, "/im/authenticate")
301
        except Unauthorized:
302
            pass
303
        except Exception:
304
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
305
        else:
306
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
307

  
308
    def test_invalid_token(self):
309
        """Test _invalid_token without pool"""
310
        self._invalid_token(False)
311

  
312
    def test_invalid_token_pool(self):
313
        """Test _invalid_token using pool"""
314
        self._invalid_token(True)
315

  
316
    # ----------------------------------
317
    # Test the response we get if we send invalid url
318
    def _invalid_url(self, pool):
319
        global token_1
320
        _mock_request([_request_ok])
321
        try:
322
            client = AstakosClient("https://example.com", use_pool=pool)
323
            client._call_astakos(token_1, "/im/misspelled")
324
        except NotFound:
325
            pass
326
        except Exception:
327
            self.fail("Should have returned 404 (Not Found)")
328
        else:
329
            self.fail("Should have returned 404 (Not Found)")
330

  
331
    def test_invalid_url(self):
332
        """Test _invalid_url without pool"""
333
        self._invalid_url(False)
334

  
335
    def test_invalid_url_pool(self):
336
        """Test _invalid_url using pool"""
337
        self._invalid_url(True)
338

  
339
    # ----------------------------------
340
    # Test the response we get if we use an unsupported scheme
341
    def _unsupported_scheme(self, pool):
342
        global token_1
343
        _mock_request([_request_ok])
344
        try:
345
            client = AstakosClient("ftp://example.com", use_pool=pool)
346
            client._call_astakos(token_1, "/im/authenticate")
347
        except ValueError:
348
            pass
349
        except Exception:
350
            self.fail("Should have raise ValueError Exception")
351
        else:
352
            self.fail("Should have raise ValueError Exception")
353

  
354
    def test_unsupported_scheme(self):
355
        """Test _unsupported_scheme without pool"""
356
        self._unsupported_scheme(False)
357

  
358
    def test_unsupported_scheme_pool(self):
359
        """Test _unsupported_scheme using pool"""
360
        self._unsupported_scheme(True)
361

  
362
    # ----------------------------------
363
    # Test the response we get if we use http instead of https
364
    def _http_scheme(self, pool):
365
        global token_1
366
        _mock_request([_request_ok])
367
        try:
368
            client = AstakosClient("http://example.com", use_pool=pool)
369
            client._call_astakos(token_1, "/im/authenticate")
370
        except AstakosClientException as err:
371
            if err.status != 302:
372
                self.fail("Should have returned 302 (Found)")
373
        else:
374
            self.fail("Should have returned 302 (Found)")
375

  
376
    def test_http_scheme(self):
377
        """Test _http_scheme without pool"""
378
        self._http_scheme(False)
379

  
380
    def test_http_scheme_pool(self):
381
        """Test _http_scheme using pool"""
382
        self._http_scheme(True)
383

  
384
    # ----------------------------------
385
    # Test the response we get if we use authenticate with POST
386
    def _post_authenticate(self, pool):
387
        global token_1
388
        _mock_request([_request_ok])
389
        try:
390
            client = AstakosClient("https://example.com", use_pool=pool)
391
            client._call_astakos(token_1, "/im/authenticate", method="POST")
392
        except BadRequest:
393
            pass
394
        except Exception:
395
            self.fail("Should have returned 400 (Method not allowed)")
396
        else:
397
            self.fail("Should have returned 400 (Method not allowed)")
398

  
399
    def test_post_authenticate(self):
400
        """Test _post_authenticate without pool"""
401
        self._post_authenticate(False)
402

  
403
    def test_post_authenticate_pool(self):
404
        """Test _post_authenticate using pool"""
405
        self._post_authenticate(True)
406

  
407
    # ----------------------------------
408
    # Test the response if we request user_catalogs with GET
409
    def _get_user_catalogs(self, pool):
410
        global token_1
411
        _mock_request([_request_ok])
412
        try:
413
            client = AstakosClient("https://example.com", use_pool=pool)
414
            client._call_astakos(token_1, "/user_catalogs")
415
        except BadRequest:
416
            pass
417
        except Exception:
418
            self.fail("Should have returned 400 (Method not allowed)")
419
        else:
420
            self.fail("Should have returned 400 (Method not allowed)")
421

  
422
    def test_get_user_catalogs(self):
423
        """Test _get_user_catalogs without pool"""
424
        self._get_user_catalogs(False)
425

  
426
    def test_get_user_catalogs_pool(self):
427
        """Test _get_user_catalogs using pool"""
428
        self._get_user_catalogs(True)
429

  
430

  
431
class TestAuthenticate(unittest.TestCase):
432
    """Test cases for function getUserInfo"""
433

  
434
    # ----------------------------------
435
    # Test the response we get if we don't have internet access
436
    def test_offline(self):
437
        """Test offline after 3 retries"""
438
        global token_1
439
        _mock_request([_request_offline])
440
        try:
441
            client = AstakosClient("https://example.com", retry=3)
442
            client.get_user_info(token_1)
443
        except AstakosClientException:
444
            pass
445
        else:
446
            self.fail("Should have raised AstakosClientException exception")
447

  
448
    # ----------------------------------
449
    # Test the response we get for invalid token
450
    def _invalid_token(self, pool):
451
        token = "skaksaFlBl+fasFdaf24sx=="
452
        _mock_request([_request_ok])
453
        try:
454
            client = AstakosClient("https://example.com", use_pool=pool)
455
            client.get_user_info(token)
456
        except Unauthorized:
457
            pass
458
        except Exception:
459
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
460
        else:
461
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
462

  
463
    def test_invalid_token(self):
464
        """Test _invalid_token without pool"""
465
        self._invalid_token(False)
466

  
467
    def test_invalid_token_pool(self):
468
        """Test _invalid_token using pool"""
469
        self._invalid_token(True)
470

  
471
    #- ---------------------------------
472
    # Test response for user 1
473
    def _auth_user(self, token, user_info, usage, pool):
474
        _mock_request([_request_ok])
475
        try:
476
            client = AstakosClient("https://example.com", use_pool=pool)
477
            auth_info = client.get_user_info(token, usage=usage)
478
        except:
479
            self.fail("Shouldn't raise an Exception")
480
        self.assertEqual(user_info, auth_info)
481

  
482
    def test_auth_user_one(self):
483
        """Test _auth_user for User 1 without pool, without usage"""
484
        global token_1, user_1
485
        user_info = dict(user_1)
486
        del user_info['usage']
487
        self._auth_user(token_1, user_info, False, False)
488

  
489
    def test_auth_user_one_usage(self):
490
        """Test _auth_user for User 1 without pool, with usage"""
491
        global token_1, user_1
492
        self._auth_user(token_1, user_1, True, False)
493

  
494
    def test_auth_user_one_usage_pool(self):
495
        """Test _auth_user for User 1 using pool, with usage"""
496
        global token_1, user_1
497
        self._auth_user(token_1, user_1, True, True)
498

  
499
    def test_auth_user_two(self):
500
        """Test _auth_user for User 2 without pool, without usage"""
501
        global token_2, user_2
502
        user_info = dict(user_2)
503
        del user_info['usage']
504
        self._auth_user(token_2, user_info, False, False)
505

  
506
    def test_auth_user_two_usage(self):
507
        """Test _auth_user for User 2 without pool, with usage"""
508
        global token_2, user_2
509
        self._auth_user(token_2, user_2, True, False)
510

  
511
    def test_auth_user_two_usage_pool(self):
512
        """Test _auth_user for User 2 using pool, with usage"""
513
        global token_2, user_2
514
        self._auth_user(token_2, user_2, True, True)
515

  
516
    # ----------------------------------
517
    # Test retry functionality
518
    def test_offline_retry(self):
519
        """Test retry functionality for getUserInfo"""
520
        global token_1, user_1
521
        _mock_request([_request_offline, _request_offline, _request_ok])
522
        try:
523
            client = AstakosClient("https://example.com", retry=2)
524
            auth_info = client.get_user_info(token_1, usage=True)
525
        except:
526
            self.fail("Shouldn't raise an Exception")
527
        self.assertEqual(user_1, auth_info)
528

  
529

  
530
class TestDisplayNames(unittest.TestCase):
531
    """Test cases for functions getDisplayNames/getDisplayName"""
532

  
533
    # ----------------------------------
534
    # Test the response we get for invalid token
535
    def test_invalid_token(self):
536
        """Test the response we get for invalid token (without pool)"""
537
        global user_1
538
        token = "skaksaFlBl+fasFdaf24sx=="
539
        _mock_request([_request_ok])
540
        try:
541
            client = AstakosClient("https://example.com")
542
            client.get_usernames(token, [user_1['uuid']])
543
        except Unauthorized:
544
            pass
545
        except Exception:
546
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
547
        else:
548
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
549

  
550
    # ----------------------------------
551
    # Get Info for both users
552
    def test_usernames(self):
553
        """Test get_usernames with both users"""
554
        global token_1, user_1, user_2
555
        _mock_request([_request_ok])
556
        try:
557
            client = AstakosClient("https://example.com")
558
            catalog = client.get_usernames(
559
                token_1, [user_1['uuid'], user_2['uuid']])
560
        except:
561
            self.fail("Shouldn't raise an Exception")
562
        self.assertEqual(catalog[user_1['uuid']], user_1['username'])
563
        self.assertEqual(catalog[user_2['uuid']], user_2['username'])
564

  
565
    # ----------------------------------
566
    # Get info for user 1
567
    def test_username_user_one(self):
568
        """Test get_username for User One"""
569
        global token_2, user_1
570
        _mock_request([_request_offline, _request_ok])
571
        try:
572
            client = AstakosClient(
573
                "https://example.com", use_pool=True, retry=2)
574
            info = client.get_username(token_2, user_1['uuid'])
575
        except:
576
            self.fail("Shouldn't raise an Exception")
577
        self.assertEqual(info, user_1['username'])
578

  
579
    # ----------------------------------
580
    # Get info with wrong uuid
581
    def test_no_username(self):
582
        global token_1
583
        _mock_request([_request_ok])
584
        try:
585
            client = AstakosClient("https://example.com")
586
            client.get_username(token_1, "1234")
587
        except NoUserName:
588
            pass
589
        except:
590
            self.fail("Should have raised NoDisplayName exception")
591
        else:
592
            self.fail("Should have raised NoDisplayName exception")
593

  
594

  
595
class TestGetUUIDs(unittest.TestCase):
596
    """Test cases for functions getUUIDs/getUUID"""
597

  
598
    # ----------------------------------
599
    # Test the response we get for invalid token
600
    def test_invalid_token(self):
601
        """Test the response we get for invalid token (using pool)"""
602
        global user_1
603
        token = "skaksaFlBl+fasFdaf24sx=="
604
        _mock_request([_request_ok])
605
        try:
606
            client = AstakosClient("https://example.com")
607
            client.get_uuids(token, [user_1['username']])
608
        except Unauthorized:
609
            pass
610
        except Exception:
611
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
612
        else:
613
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
614

  
615
    # ----------------------------------
616
    # Get info for both users
617
    def test_uuids(self):
618
        """Test get_uuids with both users"""
619
        global token_1, user_1, user_2
620
        _mock_request([_request_ok])
621
        try:
622
            client = AstakosClient("https://example.com")
623
            catalog = client.get_uuids(
624
                token_1, [user_1['username'], user_2['username']])
625
        except:
626
            self.fail("Shouldn't raise an Exception")
627
        self.assertEqual(catalog[user_1['username']], user_1['uuid'])
628
        self.assertEqual(catalog[user_2['username']], user_2['uuid'])
629

  
630
    # ----------------------------------
631
    # Get uuid for user 2
632
    def test_get_uuid_user_two(self):
633
        """Test get_uuid for User Two"""
634
        global token_1, user_2
635
        _mock_request([_request_offline, _request_ok])
636
        try:
637
            client = AstakosClient("https://example.com", retry=1)
638
            info = client.get_uuid(token_2, user_1['username'])
639
        except:
640
            self.fail("Shouldn't raise an Exception")
641
        self.assertEqual(info, user_1['uuid'])
642

  
643
    # ----------------------------------
644
    # Get uuid with wrong username
645
    def test_no_uuid(self):
646
        global token_1
647
        _mock_request([_request_ok])
648
        try:
649
            client = AstakosClient("https://example.com")
650
            client.get_uuid(token_1, "1234")
651
        except NoUUID:
652
            pass
653
        except:
654
            self.fail("Should have raised NoUUID exception")
655
        else:
656
            self.fail("Should have raised NoUUID exception")
657

  
658

  
659
# ----------------------------
660
# Run tests
661
if __name__ == "__main__":
662
    unittest.main()
b/astakosclient/astakosclient/utils.py
1
# Copyright (C) 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 httplib import HTTPConnection, HTTPSConnection
35
from contextlib import closing
36

  
37
from objpool.http import PooledHTTPConnection
38
from astakosclient.errors import AstakosClientException
39

  
40

  
41
def retry(func):
42
    def decorator(self, *args, **kwargs):
43
        attemps = 0
44
        while True:
45
            try:
46
                return func(self, *args, **kwargs)
47
            except AstakosClientException as err:
48
                is_last_attempt = attemps == self.retry
49
                if is_last_attempt:
50
                    raise err
51
                if err.status == 401 or err.status == 404:
52
                    # In case of Unauthorized response
53
                    # or Not Found return immediately
54
                    raise err
55
                self.logger.info("AstakosClient request failed..retrying")
56
                attemps += 1
57
    return decorator
58

  
59

  
60
def scheme_to_class(scheme, use_pool, pool_size):
61
    """Return the appropriate conn class for given scheme"""
62
    def _objpool(netloc):
63
        return PooledHTTPConnection(
64
            netloc=netloc, scheme=scheme, pool_size=pool_size)
65

  
66
    def _http_connection(netloc):
67
        return closing(HTTPConnection(netloc))
68

  
69
    def _https_connection(netloc):
70
        return closing(HTTPSConnection(netloc))
71

  
72
    if scheme == "http":
73
        if use_pool:
74
            return _objpool
75
        else:
76
            return _http_connection
77
    elif scheme == "https":
78
        if use_pool:
79
            return _objpool
80
        else:
81
            return _https_connection
82
    else:
83
        return None
b/astakosclient/distribute_setup.py
1
#!python
2
"""Bootstrap distribute installation
3

  
4
If you want to use setuptools in your package's setup.py, just include this
5
file in the same directory with it, and add this to the top of your setup.py::
6

  
7
    from distribute_setup import use_setuptools
8
    use_setuptools()
9

  
10
If you want to require a specific version of setuptools, set a download
11
mirror, or use an alternate download directory, you can do so by supplying
12
the appropriate options to ``use_setuptools()``.
13

  
14
This file can also be run as a script to install or upgrade setuptools.
15
"""
16
import os
17
import sys
18
import time
19
import fnmatch
20
import tempfile
21
import tarfile
22
from distutils import log
23

  
24
try:
25
    from site import USER_SITE
26
except ImportError:
27
    USER_SITE = None
28

  
29
try:
30
    import subprocess
31

  
32
    def _python_cmd(*args):
33
        args = (sys.executable,) + args
34
        return subprocess.call(args) == 0
35

  
36
except ImportError:
37
    # will be used for python 2.3
38
    def _python_cmd(*args):
39
        args = (sys.executable,) + args
40
        # quoting arguments if windows
41
        if sys.platform == 'win32':
42
            def quote(arg):
43
                if ' ' in arg:
44
                    return '"%s"' % arg
45
                return arg
46
            args = [quote(arg) for arg in args]
47
        return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
48

  
49
DEFAULT_VERSION = "0.6.10"
50
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
51
SETUPTOOLS_FAKED_VERSION = "0.6c11"
52

  
53
SETUPTOOLS_PKG_INFO = """\
54
Metadata-Version: 1.0
55
Name: setuptools
56
Version: %s
57
Summary: xxxx
58
Home-page: xxx
59
Author: xxx
60
Author-email: xxx
61
License: xxx
62
Description: xxx
63
""" % SETUPTOOLS_FAKED_VERSION
64

  
65

  
66
def _install(tarball):
67
    # extracting the tarball
68
    tmpdir = tempfile.mkdtemp()
69
    log.warn('Extracting in %s', tmpdir)
70
    old_wd = os.getcwd()
71
    try:
72
        os.chdir(tmpdir)
73
        tar = tarfile.open(tarball)
74
        _extractall(tar)
75
        tar.close()
76

  
77
        # going in the directory
78
        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
79
        os.chdir(subdir)
80
        log.warn('Now working in %s', subdir)
81

  
82
        # installing
83
        log.warn('Installing Distribute')
84
        if not _python_cmd('setup.py', 'install'):
85
            log.warn('Something went wrong during the installation.')
86
            log.warn('See the error message above.')
87
    finally:
88
        os.chdir(old_wd)
89

  
90

  
91
def _build_egg(egg, tarball, to_dir):
92
    # extracting the tarball
93
    tmpdir = tempfile.mkdtemp()
94
    log.warn('Extracting in %s', tmpdir)
95
    old_wd = os.getcwd()
96
    try:
97
        os.chdir(tmpdir)
98
        tar = tarfile.open(tarball)
99
        _extractall(tar)
100
        tar.close()
101

  
102
        # going in the directory
103
        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
104
        os.chdir(subdir)
105
        log.warn('Now working in %s', subdir)
106

  
107
        # building an egg
108
        log.warn('Building a Distribute egg in %s', to_dir)
109
        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
110

  
111
    finally:
112
        os.chdir(old_wd)
113
    # returning the result
114
    log.warn(egg)
115
    if not os.path.exists(egg):
116
        raise IOError('Could not build the egg.')
117

  
118

  
119
def _do_download(version, download_base, to_dir, download_delay):
120
    egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
121
                       % (version, sys.version_info[0], sys.version_info[1]))
122
    if not os.path.exists(egg):
123
        tarball = download_setuptools(version, download_base,
124
                                      to_dir, download_delay)
125
        _build_egg(egg, tarball, to_dir)
126
    sys.path.insert(0, egg)
127
    import setuptools
128
    setuptools.bootstrap_install_from = egg
129

  
130

  
131
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
132
                   to_dir=os.curdir, download_delay=15, no_fake=True):
133
    # making sure we use the absolute path
134
    to_dir = os.path.abspath(to_dir)
135
    was_imported = 'pkg_resources' in sys.modules or \
136
        'setuptools' in sys.modules
137
    try:
138
        try:
139
            import pkg_resources
140
            if not hasattr(pkg_resources, '_distribute'):
141
                if not no_fake:
142
                    _fake_setuptools()
143
                raise ImportError
144
        except ImportError:
145
            return _do_download(version, download_base, to_dir, download_delay)
146
        try:
147
            pkg_resources.require("distribute>="+version)
148
            return
149
        except pkg_resources.VersionConflict:
150
            e = sys.exc_info()[1]
151
            if was_imported:
152
                sys.stderr.write(
153
                "The required version of distribute (>=%s) is not available,\n"
154
                "and can't be installed while this script is running. Please\n"
155
                "install a more recent version first, using\n"
156
                "'easy_install -U distribute'."
157
                "\n\n(Currently using %r)\n" % (version, e.args[0]))
158
                sys.exit(2)
159
            else:
160
                del pkg_resources, sys.modules['pkg_resources']    # reload ok
161
                return _do_download(version, download_base, to_dir,
162
                                    download_delay)
163
        except pkg_resources.DistributionNotFound:
164
            return _do_download(version, download_base, to_dir,
165
                                download_delay)
166
    finally:
167
        if not no_fake:
168
            _create_fake_setuptools_pkg_info(to_dir)
169

  
170
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
171
                        to_dir=os.curdir, delay=15):
172
    """Download distribute from a specified location and return its filename
173

  
174
    `version` should be a valid distribute version number that is available
175
    as an egg for download under the `download_base` URL (which should end
176
    with a '/'). `to_dir` is the directory where the egg will be downloaded.
177
    `delay` is the number of seconds to pause before an actual download
178
    attempt.
179
    """
180
    # making sure we use the absolute path
181
    to_dir = os.path.abspath(to_dir)
182
    try:
183
        from urllib.request import urlopen
184
    except ImportError:
185
        from urllib2 import urlopen
186
    tgz_name = "distribute-%s.tar.gz" % version
187
    url = download_base + tgz_name
188
    saveto = os.path.join(to_dir, tgz_name)
189
    src = dst = None
190
    if not os.path.exists(saveto):  # Avoid repeated downloads
191
        try:
192
            log.warn("Downloading %s", url)
193
            src = urlopen(url)
194
            # Read/write all in one block, so we don't create a corrupt file
195
            # if the download is interrupted.
196
            data = src.read()
197
            dst = open(saveto, "wb")
198
            dst.write(data)
199
        finally:
200
            if src:
201
                src.close()
202
            if dst:
203
                dst.close()
204
    return os.path.realpath(saveto)
205

  
206
def _no_sandbox(function):
207
    def __no_sandbox(*args, **kw):
208
        try:
209
            from setuptools.sandbox import DirectorySandbox
210
            if not hasattr(DirectorySandbox, '_old'):
211
                def violation(*args):
212
                    pass
213
                DirectorySandbox._old = DirectorySandbox._violation
214
                DirectorySandbox._violation = violation
215
                patched = True
216
            else:
217
                patched = False
218
        except ImportError:
219
            patched = False
220

  
221
        try:
222
            return function(*args, **kw)
223
        finally:
224
            if patched:
225
                DirectorySandbox._violation = DirectorySandbox._old
226
                del DirectorySandbox._old
227

  
228
    return __no_sandbox
229

  
230
def _patch_file(path, content):
231
    """Will backup the file then patch it"""
232
    existing_content = open(path).read()
233
    if existing_content == content:
234
        # already patched
235
        log.warn('Already patched.')
236
        return False
237
    log.warn('Patching...')
238
    _rename_path(path)
239
    f = open(path, 'w')
240
    try:
241
        f.write(content)
242
    finally:
243
        f.close()
244
    return True
245

  
246
_patch_file = _no_sandbox(_patch_file)
247

  
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff