Developing juniper contrail plugin, implenting CRUD operations and first version...
[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 self._logger.debug('Current Token:'.format(self.token))
100 auth_url = self.auth_url + 'auth/tokens'
101 if self.token is None or self._token_expired():
102 if not self.auth_url:
103 self.token = ""
104 resp = self._request_noauth(url=auth_url, op="POST", headers=headers,
105 data=self.auth_dict)
106 self.token = resp.headers.get('x-subject-token')
107 self.last_token_time = time.time()
108 self._logger.debug('Obtained token: '.format(self.token))
109
110 return self.token
111
112 def _token_expired(self):
113 current_time = time.time()
114 if self.last_token_time and (current_time - self.last_token_time < self.token_timeout):
115 return False
116 else:
117 return True
118
119 def _request(self, op, url, http_headers, data=None, retry_auth_error=True):
120 headers = http_headers.copy()
121
122 # Get authorization (include authentication headers)
123 # todo - aƱadir token de nuevo
124 #token = self._get_token(headers)
125 token = None
126 if token:
127 headers['X-Auth-Token'] = token
128 try:
129 return self._request_noauth(op, url, headers, data)
130 except AuthError:
131 # If there is an auth error retry just once
132 if retry_auth_error:
133 return self._request(self, op, url, headers, data, retry_auth_error=False)
134
135 def _request_noauth(self, op, url, headers, data=None):
136 # Method to execute http requests with error control
137 # Authentication error, always make just one retry
138 # ConnectionError or ServiceUnavailable make configured retries with sleep between them
139 # Other errors to raise:
140 # - NotFound
141 # - Conflict
142
143 retry = 0
144 while retry < self.max_retries:
145 retry += 1
146
147 # Execute operation
148 try:
149 self._logger.info("Request METHOD: {} URL: {}".format(op, url))
150 if (op == "GET"):
151 resp = self._http_get(url, headers, query_params=data)
152 elif (op == "POST"):
153 resp = self._http_post(url, headers, json_data=data)
154 elif (op == "POST_HEADERS"):
155 resp = self._http_post_headers(url, headers, json_data=data)
156 elif (op == "DELETE"):
157 resp = self._http_delete(url, headers, json_data=data)
158 else:
159 raise HttpException("Unsupported operation: {}".format(op))
160 self._logger.info("Response HTTPCODE: {}".format(resp.status_code))
161
162 # Check http return code
163 if resp:
164 return resp
165 else:
166 status_code = resp.status_code
167 if status_code == 401:
168 # Auth Error - set token to None to reload it and raise AuthError
169 self.token = None
170 raise AuthError("Auth error executing operation")
171 elif status_code == 409:
172 raise DuplicateFound("Duplicate resource url: {}, response: {}".format(url, resp.text))
173 elif status_code == 404:
174 raise NotFound("Not found resource url: {}, response: {}".format(url, resp.text))
175 elif resp.status_code in [502, 503]:
176 if not self.max_retries or retry >= self.max_retries:
177 raise ServiceUnavailableException("Service unavailable error url: {}".format(url))
178
179 continue
180 else:
181 raise HttpException("Error status_code: {}, error_text: {}".format(resp.status_code, resp.text))
182
183 except ConnectionError as e:
184 self._logger.error("Connection error executing request: {}".format(repr(e)))
185 if not self.max_retries or retry >= self.max_retries:
186 raise ConnectionError
187 continue
188 except Exception as e:
189 self._logger.error("Error executing request: {}".format(repr(e)))
190 raise e
191
192 def _http_get(self, url, headers, query_params=None):
193 return requests.get(url, headers=headers, params=query_params)
194
195 def _http_post_headers(self, url, headers, json_data=None):
196 return requests.head(url, json=json_data, headers=headers, verify=False)
197
198 def _http_post(self, url, headers, json_data=None):
199 return requests.post(url, json=json_data, headers=headers, verify=False)
200
201 def _http_delete(self, url, headers, json_data=None):
202 return requests.delete(url, json=json_data, headers=headers)
203