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
9 # http://www.apache.org/licenses/LICENSE-2.0
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
22 from requests
.exceptions
import ConnectionError
25 class HttpException(Exception):
29 class NotFound(HttpException
):
33 class AuthError(HttpException
):
37 class DuplicateFound(HttpException
):
41 class ServiceUnavailableException(HttpException
):
45 class ContrailHttp(object):
46 def __init__(self
, auth_info
, logger
):
48 # default don't verify client cert
49 self
._ssl
_verify
= False
50 # auth info: must contain auth_url and auth_dict
51 self
.auth_url
= auth_info
["auth_url"]
52 self
.auth_dict
= auth_info
["auth_dict"]
56 # Default token timeout
57 self
.token_timeout
= 3500
59 # TODO - improve configuration timeouts
61 def get_cmd(self
, url
, headers
):
62 self
._logger
.debug("")
63 resp
= self
._request
("GET", url
, headers
)
67 def post_headers_cmd(self
, url
, headers
, post_fields_dict
=None):
68 self
._logger
.debug("")
70 # obfuscate password before logging dict
72 post_fields_dict
.get("auth", {})
78 post_fields_dict_copy
= copy
.deepcopy(post_fields_dict
)
79 post_fields_dict
["auth"]["identity"]["password"]["user"][
82 json_data_log
= post_fields_dict_copy
84 json_data_log
= post_fields_dict
86 self
._logger
.debug("Request POSTFIELDS: {}".format(json
.dumps(json_data_log
)))
87 resp
= self
._request
("POST_HEADERS", url
, headers
, data
=post_fields_dict
)
91 def post_cmd(self
, url
, headers
, post_fields_dict
=None):
92 self
._logger
.debug("")
94 # obfuscate password before logging dict
96 post_fields_dict
.get("auth", {})
102 post_fields_dict_copy
= copy
.deepcopy(post_fields_dict
)
103 post_fields_dict
["auth"]["identity"]["password"]["user"][
106 json_data_log
= post_fields_dict_copy
108 json_data_log
= post_fields_dict
110 self
._logger
.debug("Request POSTFIELDS: {}".format(json
.dumps(json_data_log
)))
111 resp
= self
._request
("POST", url
, headers
, data
=post_fields_dict
)
115 def delete_cmd(self
, url
, headers
):
116 self
._logger
.debug("")
117 resp
= self
._request
("DELETE", url
, headers
)
121 def _get_token(self
, headers
):
123 self
._logger
.debug("Current Token: {}".format(self
.token
))
124 auth_url
= self
.auth_url
+ "auth/tokens"
126 if self
.token
is None or self
._token
_expired
():
127 if not self
.auth_url
:
130 resp
= self
._request
_noauth
(
131 url
=auth_url
, op
="POST", headers
=headers
, data
=self
.auth_dict
133 self
.token
= resp
.headers
.get("x-subject-token")
134 self
.last_token_time
= time
.time()
135 self
._logger
.debug("Obtained token: {}".format(self
.token
))
139 def _token_expired(self
):
140 current_time
= time
.time()
142 if self
.last_token_time
and (
143 current_time
- self
.last_token_time
< self
.token_timeout
149 def _request(self
, op
, url
, http_headers
, data
=None, retry_auth_error
=True):
150 headers
= http_headers
.copy()
152 # Get authorization (include authentication headers)
153 # TODO add again token
154 # token = self._get_token(headers)
158 headers
["X-Auth-Token"] = token
161 return self
._request
_noauth
(op
, url
, headers
, data
)
163 # If there is an auth error retry just once
165 return self
._request
(
166 self
, op
, url
, headers
, data
, retry_auth_error
=False
169 def _request_noauth(self
, op
, url
, headers
, data
=None):
170 # Method to execute http requests with error control
171 # Authentication error, always make just one retry
172 # ConnectionError or ServiceUnavailable make configured retries with sleep between them
173 # Other errors to raise:
178 while retry
< self
.max_retries
:
183 self
._logger
.info("Request METHOD: {} URL: {}".format(op
, url
))
185 resp
= self
._http
_get
(url
, headers
, query_params
=data
)
187 resp
= self
._http
_post
(url
, headers
, json_data
=data
)
188 elif op
== "POST_HEADERS":
189 resp
= self
._http
_post
_headers
(url
, headers
, json_data
=data
)
191 resp
= self
._http
_delete
(url
, headers
, json_data
=data
)
193 raise HttpException("Unsupported operation: {}".format(op
))
195 self
._logger
.info("Response HTTPCODE: {}".format(resp
.status_code
))
197 # Check http return code
201 status_code
= resp
.status_code
202 if status_code
== 401:
203 # Auth Error - set token to None to reload it and raise AuthError
206 raise AuthError("Auth error executing operation")
207 elif status_code
== 409:
208 raise DuplicateFound(
209 "Duplicate resource url: {}, response: {}".format(
213 elif status_code
== 404:
215 "Not found resource url: {}, response: {}".format(
219 elif resp
.status_code
in [502, 503]:
220 if not self
.max_retries
or retry
>= self
.max_retries
:
221 raise ServiceUnavailableException(
222 "Service unavailable error url: {}".format(url
)
228 "Error status_code: {}, error_text: {}".format(
229 resp
.status_code
, resp
.text
233 except ConnectionError
as e
:
235 "Connection error executing request: {}".format(repr(e
))
238 if not self
.max_retries
or retry
>= self
.max_retries
:
239 raise ConnectionError
242 except Exception as e
:
243 self
._logger
.error("Error executing request: {}".format(repr(e
)))
246 def _http_get(self
, url
, headers
, query_params
=None):
247 return requests
.get(url
, headers
=headers
, params
=query_params
)
249 def _http_post_headers(self
, url
, headers
, json_data
=None):
250 return requests
.head(url
, json
=json_data
, headers
=headers
, verify
=False)
252 def _http_post(self
, url
, headers
, json_data
=None):
253 return requests
.post(url
, json
=json_data
, headers
=headers
, verify
=False)
255 def _http_delete(self
, url
, headers
, json_data
=None):
256 return requests
.delete(url
, json
=json_data
, headers
=headers
)