Code Coverage

Cobertura Coverage Report > RO-SDN-juniper_contrail.osm_rosdn_juniper_contrail >

rest_lib.py

Trend

File Coverage summary

NameClassesLinesConditionals
rest_lib.py
100%
1/1
29%
36/126
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
rest_lib.py
29%
36/126
N/A

Source

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 1 import copy
18 1 import json
19 1 from time import time
20
21 1 import requests
22 1 from requests.exceptions import ConnectionError
23
24
25 1 class HttpException(Exception):
26 1     pass
27
28
29 1 class NotFound(HttpException):
30 1     pass
31
32
33 1 class AuthError(HttpException):
34 1     pass
35
36
37 1 class DuplicateFound(HttpException):
38 1     pass
39
40
41 1 class ServiceUnavailableException(HttpException):
42 1     pass
43
44
45 1 class ContrailHttp(object):
46 1     def __init__(self, auth_info, logger, verify):
47 1         self._logger = logger
48         # Verify client cert
49 1         self.ssl_verify = verify
50         # auth info: must contain auth_url and auth_dict
51 1         self.auth_url = auth_info["auth_url"]
52 1         self.auth_dict = auth_info["auth_dict"]
53
54 1         self.max_retries = 3
55
56         # Default token timeout
57 1         self.token_timeout = 3500
58 1         self.token = None
59         # TODO - improve configuration timeouts
60
61 1     def get_cmd(self, url, headers):
62 0         self._logger.debug("")
63 0         resp = self._request("GET", url, headers)
64
65 0         return resp.json()
66
67 1     def post_headers_cmd(self, url, headers, post_fields_dict=None):
68 0         self._logger.debug("")
69
70         # obfuscate password before logging dict
71 0         if (
72             post_fields_dict.get("auth", {})
73             .get("identity", {})
74             .get("password", {})
75             .get("user", {})
76             .get("password")
77         ):
78 0             post_fields_dict_copy = copy.deepcopy(post_fields_dict)
79 0             post_fields_dict["auth"]["identity"]["password"]["user"][
80                 "password"
81             ] = "******"
82 0             json_data_log = post_fields_dict_copy
83         else:
84 0             json_data_log = post_fields_dict
85
86 0         self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
87 0         resp = self._request("POST_HEADERS", url, headers, data=post_fields_dict)
88
89 0         return resp.text
90
91 1     def post_cmd(self, url, headers, post_fields_dict=None):
92 0         self._logger.debug("")
93
94         # obfuscate password before logging dict
95 0         if (
96             post_fields_dict.get("auth", {})
97             .get("identity", {})
98             .get("password", {})
99             .get("user", {})
100             .get("password")
101         ):
102 0             post_fields_dict_copy = copy.deepcopy(post_fields_dict)
103 0             post_fields_dict["auth"]["identity"]["password"]["user"][
104                 "password"
105             ] = "******"
106 0             json_data_log = post_fields_dict_copy
107         else:
108 0             json_data_log = post_fields_dict
109
110 0         self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
111 0         resp = self._request("POST", url, headers, data=post_fields_dict)
112
113 0         return resp.text
114
115 1     def delete_cmd(self, url, headers):
116 0         self._logger.debug("")
117 0         resp = self._request("DELETE", url, headers)
118
119 0         return resp.text
120
121 1     def _get_token(self, headers):
122 0         if self.auth_url:
123 0             self._logger.debug("Current Token: {}".format(self.token))
124 0             auth_url = self.auth_url + "auth/tokens"
125
126 0             if self.token is None or self._token_expired():
127 0                 if not self.auth_url:
128 0                     self.token = ""
129
130 0                 resp = self._request_noauth(
131                     url=auth_url, op="POST", headers=headers, data=self.auth_dict
132                 )
133 0                 self.token = resp.headers.get("x-subject-token")
134 0                 self.last_token_time = time.time()
135 0                 self._logger.debug("Obtained token: {}".format(self.token))
136
137 0                 return self.token
138
139 1     def _token_expired(self):
140 0         current_time = time.time()
141
142 0         if self.last_token_time and (
143             current_time - self.last_token_time < self.token_timeout
144         ):
145 0             return False
146         else:
147 0             return True
148
149 1     def _request(self, op, url, http_headers, data=None, retry_auth_error=True):
150 0         headers = http_headers.copy()
151
152         # Get authorization (include authentication headers)
153         # TODO add again token
154         # token = self._get_token(headers)
155 0         token = None
156
157 0         if token:
158 0             headers["X-Auth-Token"] = token
159
160 0         try:
161 0             return self._request_noauth(op, url, headers, data)
162 0         except AuthError:
163             # If there is an auth error retry just once
164 0             if retry_auth_error:
165 0                 return self._request(
166                     self, op, url, headers, data, retry_auth_error=False
167                 )
168
169 1     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:
174         # - NotFound
175         # - Conflict
176
177 0         retry = 0
178 0         while retry < self.max_retries:
179 0             retry += 1
180
181             # Execute operation
182 0             try:
183 0                 self._logger.info("Request METHOD: {} URL: {}".format(op, url))
184 0                 if op == "GET":
185 0                     resp = self._http_get(url, headers, query_params=data)
186 0                 elif op == "POST":
187 0                     resp = self._http_post(url, headers, json_data=data)
188 0                 elif op == "POST_HEADERS":
189 0                     resp = self._http_post_headers(url, headers, json_data=data)
190 0                 elif op == "DELETE":
191 0                     resp = self._http_delete(url, headers, json_data=data)
192                 else:
193 0                     raise HttpException("Unsupported operation: {}".format(op))
194
195 0                 self._logger.info("Response HTTPCODE: {}".format(resp.status_code))
196
197                 # Check http return code
198 0                 if resp:
199 0                     return resp
200                 else:
201 0                     status_code = resp.status_code
202 0                     if status_code == 401:
203                         # Auth Error - set token to None to reload it and raise AuthError
204 0                         self.token = None
205
206 0                         raise AuthError("Auth error executing operation")
207 0                     elif status_code == 409:
208 0                         raise DuplicateFound(
209                             "Duplicate resource url: {}, response: {}".format(
210                                 url, resp.text
211                             )
212                         )
213 0                     elif status_code == 404:
214 0                         raise NotFound(
215                             "Not found resource url: {}, response: {}".format(
216                                 url, resp.text
217                             )
218                         )
219 0                     elif resp.status_code in [502, 503]:
220 0                         if not self.max_retries or retry >= self.max_retries:
221 0                             raise ServiceUnavailableException(
222                                 "Service unavailable error url: {}".format(url)
223                             )
224
225 0                         continue
226                     else:
227 0                         raise HttpException(
228                             "Error status_code: {}, error_text: {}".format(
229                                 resp.status_code, resp.text
230                             )
231                         )
232
233 0             except ConnectionError as e:
234 0                 self._logger.error(
235                     "Connection error executing request: {}".format(repr(e))
236                 )
237
238 0                 if not self.max_retries or retry >= self.max_retries:
239 0                     raise ConnectionError
240
241 0                 continue
242 0             except Exception as e:
243 0                 self._logger.error("Error executing request: {}".format(repr(e)))
244 0                 raise e
245
246 1     def _http_get(self, url, headers, query_params=None):
247 0         return requests.get(url, headers=headers, params=query_params)
248
249 1     def _http_post_headers(self, url, headers, json_data=None):
250 0         return requests.head(
251             url, json=json_data, headers=headers, verify=self.ssl_verify
252         )
253
254 1     def _http_post(self, url, headers, json_data=None):
255 0         return requests.post(
256             url, json=json_data, headers=headers, verify=self.ssl_verify
257         )
258
259 1     def _http_delete(self, url, headers, json_data=None):
260 0         return requests.delete(url, json=json_data, headers=headers)