gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 1 | import json |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 2 | import urllib.request |
koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 3 | from functools import partial |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 4 | from typing import Dict, Any |
koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 5 | |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 6 | from keystoneclient import exceptions |
koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 7 | from keystoneclient.v2_0 import Client as keystoneclient |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 8 | |
koder aka kdanilov | 0f0546c | 2015-02-17 20:42:05 -0800 | [diff] [blame] | 9 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 10 | class Urllib2HTTP: |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 11 | """ |
| 12 | class for making HTTP requests |
| 13 | """ |
| 14 | |
| 15 | allowed_methods = ('get', 'put', 'post', 'delete', 'patch', 'head') |
| 16 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 17 | def __init__(self, root_url:str, headers:Dict[str, str]=None, echo: bool=False): |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 18 | """ |
| 19 | """ |
| 20 | if root_url.endswith('/'): |
| 21 | self.root_url = root_url[:-1] |
| 22 | else: |
| 23 | self.root_url = root_url |
| 24 | |
| 25 | self.headers = headers if headers is not None else {} |
| 26 | self.echo = echo |
| 27 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 28 | def do(self, method: str, path: str, params: Dict[str, str]=None) -> Any: |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 29 | if path.startswith('/'): |
| 30 | url = self.root_url + path |
| 31 | else: |
| 32 | url = self.root_url + '/' + path |
| 33 | |
| 34 | if method == 'get': |
| 35 | assert params == {} or params is None |
| 36 | data_json = None |
| 37 | else: |
| 38 | data_json = json.dumps(params) |
| 39 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 40 | request = urllib.request.Request(url, |
| 41 | data=data_json, |
| 42 | headers=self.headers) |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 43 | if data_json is not None: |
| 44 | request.add_header('Content-Type', 'application/json') |
| 45 | |
| 46 | request.get_method = lambda: method.upper() |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 47 | response = urllib.request.urlopen(request) |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 48 | |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 49 | if response.code < 200 or response.code > 209: |
| 50 | raise IndexError(url) |
| 51 | |
| 52 | content = response.read() |
| 53 | |
| 54 | if '' == content: |
| 55 | return None |
| 56 | |
| 57 | return json.loads(content) |
| 58 | |
| 59 | def __getattr__(self, name): |
| 60 | if name in self.allowed_methods: |
| 61 | return partial(self.do, name) |
| 62 | raise AttributeError(name) |
| 63 | |
| 64 | |
| 65 | class KeystoneAuth(Urllib2HTTP): |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 66 | def __init__(self, root_url: str, creds: Dict[str, str], headers: Dict[str, str]=None, echo: bool=False, |
| 67 | admin_node_ip: str=None): |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 68 | super(KeystoneAuth, self).__init__(root_url, headers, echo) |
| 69 | self.keystone_url = "http://{0}:5000/v2.0".format(admin_node_ip) |
| 70 | self.keystone = keystoneclient( |
| 71 | auth_url=self.keystone_url, **creds) |
| 72 | self.refresh_token() |
| 73 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 74 | def refresh_token(self) -> None: |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 75 | """Get new token from keystone and update headers""" |
| 76 | try: |
| 77 | self.keystone.authenticate() |
| 78 | self.headers['X-Auth-Token'] = self.keystone.auth_token |
| 79 | except exceptions.AuthorizationFailure: |
koder aka kdanilov | 2c47309 | 2015-03-29 17:12:13 +0300 | [diff] [blame] | 80 | raise |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 81 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 82 | def do(self, method: str, path: str, params: Dict[str, str]=None) -> Any: |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 83 | """Do request. If gets 401 refresh token""" |
| 84 | try: |
| 85 | return super(KeystoneAuth, self).do(method, path, params) |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame^] | 86 | except urllib.request.HTTPError as e: |
gstepanov | 94531b8 | 2015-02-11 14:20:34 +0200 | [diff] [blame] | 87 | if e.code == 401: |
| 88 | self.refresh_token() |
| 89 | return super(KeystoneAuth, self).do(method, path, params) |
| 90 | else: |
| 91 | raise |