Revision f93cc364
b/snf-astakos-client/astakosclient/__init__.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
import logging |
35 | 35 |
import urlparse |
36 |
import httplib |
|
37 | 36 |
import urllib |
38 | 37 |
import hashlib |
39 | 38 |
from copy import copy |
40 | 39 |
|
41 | 40 |
import simplejson |
42 |
import objpool.http |
|
43 |
|
|
44 |
|
|
45 |
# -------------------------------------------------------------------- |
|
46 |
# Astakos Client Exception |
|
47 |
class AstakosClientException(Exception): |
|
48 |
def __init__(self, message, status=0): |
|
49 |
self.message = message |
|
50 |
self.status = status |
|
51 |
|
|
52 |
def __str__(self): |
|
53 |
return repr(self.message) |
|
54 |
|
|
55 |
|
|
56 |
class AstakosClientEInvalid(AstakosClientException): |
|
57 |
def __init__(self, message): |
|
58 |
"""Invalid X-Auth-Token""" |
|
59 |
super(AstakosClientEInvalid, self).__init__(message, 401) |
|
60 |
|
|
61 |
|
|
62 |
class AstakosClientEMethod(AstakosClientException): |
|
63 |
def __init__(self, message): |
|
64 |
"""Method not allowed""" |
|
65 |
super(AstakosClientEMethod, self).__init__(message, 400) |
|
66 |
|
|
67 |
|
|
68 |
class AstakosClientENotFound(AstakosClientException): |
|
69 |
def __init__(self, message): |
|
70 |
"""404 Not Found""" |
|
71 |
super(AstakosClientENotFound, self).__init__(message, 404) |
|
41 |
from astakosclient.utils import retry, scheme_to_class |
|
42 |
from astakosclient.errors import \ |
|
43 |
AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden |
|
72 | 44 |
|
73 | 45 |
|
74 | 46 |
# -------------------------------------------------------------------- |
... | ... | |
120 | 92 |
|
121 | 93 |
# Check for supported scheme |
122 | 94 |
p = urlparse.urlparse(astakos_url) |
123 |
conn_class = _scheme_to_class(p.scheme, use_pool, pool_size)
|
|
95 |
conn_class = scheme_to_class(p.scheme, use_pool, pool_size) |
|
124 | 96 |
if conn_class is None: |
125 | 97 |
m = "Unsupported scheme: %s" % p.scheme |
126 | 98 |
logger.error(m) |
... | ... | |
134 | 106 |
self.conn_class = conn_class |
135 | 107 |
|
136 | 108 |
# ---------------------------------- |
137 |
def retry(func): |
|
138 |
def decorator(self, *args, **kwargs): |
|
139 |
attemps = 0 |
|
140 |
while True: |
|
141 |
try: |
|
142 |
return func(self, *args, **kwargs) |
|
143 |
except AstakosClientException as err: |
|
144 |
is_last_attempt = attemps == self.retry |
|
145 |
if is_last_attempt: |
|
146 |
raise err |
|
147 |
if err.status == 401 or err.status == 404: |
|
148 |
# In case of Unauthorized response |
|
149 |
# or Not Found return immediately |
|
150 |
raise err |
|
151 |
self.logger.info("AstakosClient request failed..retrying") |
|
152 |
attemps += 1 |
|
153 |
return decorator |
|
154 |
|
|
155 |
# ---------------------------------- |
|
156 | 109 |
@retry |
157 | 110 |
def _callAstakos(self, token, request_path, |
158 | 111 |
headers=None, body=None, method="GET"): |
... | ... | |
202 | 155 |
# Return |
203 | 156 |
self.logger.debug("Request returned with status %s" % status) |
204 | 157 |
if status == 400: |
205 |
raise AstakosClientEMethod(data)
|
|
158 |
raise BadRequest(data)
|
|
206 | 159 |
if status == 401: |
207 |
raise AstakosClientEInvalid(data) |
|
160 |
raise Unauthorized(data) |
|
161 |
if status == 403: |
|
162 |
raise Forbidden(data) |
|
208 | 163 |
if status == 404: |
209 |
raise AstakosClientENotFound(data)
|
|
164 |
raise NotFound(data) |
|
210 | 165 |
if status < 200 or status >= 300: |
211 | 166 |
raise AstakosClientException(data, status) |
212 | 167 |
return simplejson.loads(unicode(data)) |
... | ... | |
333 | 288 |
|
334 | 289 |
# -------------------------------------------------------------------- |
335 | 290 |
# Private functions |
336 |
def _scheme_to_class(scheme, use_pool, pool_size): |
|
337 |
"""Return the appropriate conn class for given scheme""" |
|
338 |
def _objpool(netloc): |
|
339 |
return objpool.http.get_http_connection( |
|
340 |
netloc=netloc, scheme=scheme, pool_size=pool_size) |
|
341 |
|
|
342 |
if scheme == "http": |
|
343 |
if use_pool: |
|
344 |
return _objpool |
|
345 |
else: |
|
346 |
return httplib.HTTPConnection |
|
347 |
elif scheme == "https": |
|
348 |
if use_pool: |
|
349 |
return _objpool |
|
350 |
else: |
|
351 |
return httplib.HTTPSConnection |
|
352 |
else: |
|
353 |
return None |
|
354 |
|
|
355 |
|
|
291 |
# We want _doRequest to be a distinct function |
|
292 |
# so that we can replace it during unit tests. |
|
356 | 293 |
def _doRequest(conn, method, url, **kwargs): |
357 | 294 |
"""The actual request. This function can easily be mocked""" |
358 | 295 |
conn.request(method, url, **kwargs) |
b/snf-astakos-client/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) |
b/snf-astakos-client/astakosclient/tests.py | ||
---|---|---|
45 | 45 |
import simplejson |
46 | 46 |
|
47 | 47 |
import astakosclient |
48 |
from astakosclient import AstakosClient, AstakosClientException, \ |
|
49 |
AstakosClientEInvalid, AstakosClientEMethod, AstakosClientENotFound |
|
48 |
from astakosclient import AstakosClient |
|
49 |
from astakosclient.errors import \ |
|
50 |
AstakosClientException, Unauthorized, BadRequest, NotFound |
|
50 | 51 |
|
51 | 52 |
# Use backported unittest functionality if Python < 2.7 |
52 | 53 |
try: |
... | ... | |
296 | 297 |
try: |
297 | 298 |
client = AstakosClient("https://example.com", use_pool=pool) |
298 | 299 |
client._callAstakos(token, "/im/authenticate") |
299 |
except AstakosClientEInvalid:
|
|
300 |
except Unauthorized:
|
|
300 | 301 |
pass |
301 | 302 |
except Exception: |
302 | 303 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
... | ... | |
319 | 320 |
try: |
320 | 321 |
client = AstakosClient("https://example.com", use_pool=pool) |
321 | 322 |
client._callAstakos(token_1, "/im/misspelled") |
322 |
except AstakosClientENotFound:
|
|
323 |
except NotFound: |
|
323 | 324 |
pass |
324 | 325 |
except Exception: |
325 | 326 |
self.fail("Should have returned 404 (Not Found)") |
... | ... | |
387 | 388 |
try: |
388 | 389 |
client = AstakosClient("https://example.com", use_pool=pool) |
389 | 390 |
client._callAstakos(token_1, "/im/authenticate", method="POST") |
390 |
except AstakosClientEMethod:
|
|
391 |
except BadRequest:
|
|
391 | 392 |
pass |
392 | 393 |
except Exception: |
393 | 394 |
self.fail("Should have returned 400 (Method not allowed)") |
... | ... | |
410 | 411 |
try: |
411 | 412 |
client = AstakosClient("https://example.com", use_pool=pool) |
412 | 413 |
client._callAstakos(token_1, "/user_catalogs") |
413 |
except AstakosClientEMethod:
|
|
414 |
except BadRequest:
|
|
414 | 415 |
pass |
415 | 416 |
except Exception: |
416 | 417 |
self.fail("Should have returned 400 (Method not allowed)") |
... | ... | |
451 | 452 |
try: |
452 | 453 |
client = AstakosClient("https://example.com", use_pool=pool) |
453 | 454 |
client.authenticate(token) |
454 |
except AstakosClientEInvalid:
|
|
455 |
except Unauthorized:
|
|
455 | 456 |
pass |
456 | 457 |
except Exception: |
457 | 458 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
... | ... | |
538 | 539 |
try: |
539 | 540 |
client = AstakosClient("https://example.com") |
540 | 541 |
client.getDisplayNames(token, [user_1['uuid']]) |
541 |
except AstakosClientEInvalid:
|
|
542 |
except Unauthorized:
|
|
542 | 543 |
pass |
543 | 544 |
except Exception: |
544 | 545 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
... | ... | |
588 | 589 |
try: |
589 | 590 |
client = AstakosClient("https://example.com") |
590 | 591 |
client.getUUIDs(token, [user_1['username']]) |
591 |
except AstakosClientEInvalid:
|
|
592 |
except Unauthorized:
|
|
592 | 593 |
pass |
593 | 594 |
except Exception: |
594 | 595 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
b/snf-astakos-client/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 |
import httplib |
|
35 |
|
|
36 |
import objpool.http |
|
37 |
from astakosclient.errors import AstakosClientException |
|
38 |
|
|
39 |
|
|
40 |
def retry(func): |
|
41 |
def decorator(self, *args, **kwargs): |
|
42 |
attemps = 0 |
|
43 |
while True: |
|
44 |
try: |
|
45 |
return func(self, *args, **kwargs) |
|
46 |
except AstakosClientException as err: |
|
47 |
is_last_attempt = attemps == self.retry |
|
48 |
if is_last_attempt: |
|
49 |
raise err |
|
50 |
if err.status == 401 or err.status == 404: |
|
51 |
# In case of Unauthorized response |
|
52 |
# or Not Found return immediately |
|
53 |
raise err |
|
54 |
self.logger.info("AstakosClient request failed..retrying") |
|
55 |
attemps += 1 |
|
56 |
return decorator |
|
57 |
|
|
58 |
|
|
59 |
def scheme_to_class(scheme, use_pool, pool_size): |
|
60 |
"""Return the appropriate conn class for given scheme""" |
|
61 |
def _objpool(netloc): |
|
62 |
return objpool.http.get_http_connection( |
|
63 |
netloc=netloc, scheme=scheme, pool_size=pool_size) |
|
64 |
|
|
65 |
if scheme == "http": |
|
66 |
if use_pool: |
|
67 |
return _objpool |
|
68 |
else: |
|
69 |
return httplib.HTTPConnection |
|
70 |
elif scheme == "https": |
|
71 |
if use_pool: |
|
72 |
return _objpool |
|
73 |
else: |
|
74 |
return httplib.HTTPSConnection |
|
75 |
else: |
|
76 |
return None |
Also available in: Unified diff