Merge branch 'contrail' into master
[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 import logging
21
22 from time import time
23 from requests.exceptions import ConnectionError
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 - aƱadir token de nuevo
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)
204