fix flake8 for SDN-juniper_contrail
[osm/RO.git] / RO-SDN-juniper_contrail / osm_rosdn_juniper_contrail / rest_lib.py
1 # Copyright 2020 ETSI
2 #
3 # All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16
17 import requests
18 import json
19 import copy
20
21 from time import time
22 from requests.exceptions import ConnectionError
23
24
25 class HttpException(Exception):
26 pass
27
28
29 class NotFound(HttpException):
30 pass
31
32
33 class AuthError(HttpException):
34 pass
35
36
37 class DuplicateFound(HttpException):
38 pass
39
40
41 class ServiceUnavailableException(HttpException):
42 pass
43
44
45 class ContrailHttp(object):
46
47 def __init__(self, auth_info, logger):
48 self._logger = logger
49 # default don't verify client cert
50 self._ssl_verify = False
51 # auth info: must contain auth_url and auth_dict
52 self.auth_url = auth_info["auth_url"]
53 self.auth_dict = auth_info["auth_dict"]
54
55 self.max_retries = 3
56
57 # Default token timeout
58 self.token_timeout = 3500
59 self.token = None
60 # TODO - improve configuration timeouts
61
62 def get_cmd(self, url, headers):
63 self._logger.debug("")
64 resp = self._request("GET", url, headers)
65 return resp.json()
66
67 def post_headers_cmd(self, url, headers, post_fields_dict=None):
68 self._logger.debug("")
69 # obfuscate password before logging dict
70 if post_fields_dict.get('auth', {}).get('identity', {}).get('password', {}).get('user', {}).get('password'):
71 post_fields_dict_copy = copy.deepcopy(post_fields_dict)
72 post_fields_dict['auth']['identity']['password']['user']['password'] = '******'
73 json_data_log = post_fields_dict_copy
74 else:
75 json_data_log = post_fields_dict
76 self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
77 resp = self._request("POST_HEADERS", url, headers, data=post_fields_dict)
78 return resp.text
79
80 def post_cmd(self, url, headers, post_fields_dict=None):
81 self._logger.debug("")
82 # obfuscate password before logging dict
83 if post_fields_dict.get('auth', {}).get('identity', {}).get('password', {}).get('user', {}).get('password'):
84 post_fields_dict_copy = copy.deepcopy(post_fields_dict)
85 post_fields_dict['auth']['identity']['password']['user']['password'] = '******'
86 json_data_log = post_fields_dict_copy
87 else:
88 json_data_log = post_fields_dict
89 self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
90 resp = self._request("POST", url, headers, data=post_fields_dict)
91 return resp.text
92
93 def delete_cmd(self, url, headers):
94 self._logger.debug("")
95 resp = self._request("DELETE", url, headers)
96 return resp.text
97
98 def _get_token(self, headers):
99 if self.auth_url:
100 self._logger.debug('Current Token: {}'.format(self.token))
101 auth_url = self.auth_url + 'auth/tokens'
102 if self.token is None or self._token_expired():
103 if not self.auth_url:
104 self.token = ""
105 resp = self._request_noauth(url=auth_url, op="POST", headers=headers,
106 data=self.auth_dict)
107 self.token = resp.headers.get('x-subject-token')
108 self.last_token_time = time.time()
109 self._logger.debug('Obtained token: {}'.format(self.token))
110
111 return self.token
112
113 def _token_expired(self):
114 current_time = time.time()
115 if self.last_token_time and (current_time - self.last_token_time < self.token_timeout):
116 return False
117 else:
118 return True
119
120 def _request(self, op, url, http_headers, data=None, retry_auth_error=True):
121 headers = http_headers.copy()
122
123 # Get authorization (include authentication headers)
124 # TODO add again token
125 # token = self._get_token(headers)
126 token = None
127 if token:
128 headers['X-Auth-Token'] = token
129 try:
130 return self._request_noauth(op, url, headers, data)
131 except AuthError:
132 # If there is an auth error retry just once
133 if retry_auth_error:
134 return self._request(self, op, url, headers, data, retry_auth_error=False)
135
136 def _request_noauth(self, op, url, headers, data=None):
137 # Method to execute http requests with error control
138 # Authentication error, always make just one retry
139 # ConnectionError or ServiceUnavailable make configured retries with sleep between them
140 # Other errors to raise:
141 # - NotFound
142 # - Conflict
143
144 retry = 0
145 while retry < self.max_retries:
146 retry += 1
147
148 # Execute operation
149 try:
150 self._logger.info("Request METHOD: {} URL: {}".format(op, url))
151 if op == "GET":
152 resp = self._http_get(url, headers, query_params=data)
153 elif op == "POST":
154 resp = self._http_post(url, headers, json_data=data)
155 elif op == "POST_HEADERS":
156 resp = self._http_post_headers(url, headers, json_data=data)
157 elif op == "DELETE":
158 resp = self._http_delete(url, headers, json_data=data)
159 else:
160 raise HttpException("Unsupported operation: {}".format(op))
161 self._logger.info("Response HTTPCODE: {}".format(resp.status_code))
162
163 # Check http return code
164 if resp:
165 return resp
166 else:
167 status_code = resp.status_code
168 if status_code == 401:
169 # Auth Error - set token to None to reload it and raise AuthError
170 self.token = None
171 raise AuthError("Auth error executing operation")
172 elif status_code == 409:
173 raise DuplicateFound("Duplicate resource url: {}, response: {}".format(url, resp.text))
174 elif status_code == 404:
175 raise NotFound("Not found resource url: {}, response: {}".format(url, resp.text))
176 elif resp.status_code in [502, 503]:
177 if not self.max_retries or retry >= self.max_retries:
178 raise ServiceUnavailableException("Service unavailable error url: {}".format(url))
179
180 continue
181 else:
182 raise HttpException("Error status_code: {}, error_text: {}".format(resp.status_code, resp.text))
183
184 except ConnectionError as e:
185 self._logger.error("Connection error executing request: {}".format(repr(e)))
186 if not self.max_retries or retry >= self.max_retries:
187 raise ConnectionError
188 continue
189 except Exception as e:
190 self._logger.error("Error executing request: {}".format(repr(e)))
191 raise e
192
193 def _http_get(self, url, headers, query_params=None):
194 return requests.get(url, headers=headers, params=query_params)
195
196 def _http_post_headers(self, url, headers, json_data=None):
197 return requests.head(url, json=json_data, headers=headers, verify=False)
198
199 def _http_post(self, url, headers, json_data=None):
200 return requests.post(url, json=json_data, headers=headers, verify=False)
201
202 def _http_delete(self, url, headers, json_data=None):
203 return requests.delete(url, json=json_data, headers=headers)