CSIT-577 HC Test: Scripts for test jobs using ODL client
[csit.git] / resources / libraries / python / HTTPRequest.py
1 # Copyright (c) 2017 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Implementation of HTTP requests GET, PUT, POST and DELETE used in
15 communication with Honeycomb.
16
17 The HTTP requests are implemented in the class HTTPRequest which uses
18 requests.request.
19 """
20
21 from ipaddress import IPv6Address, AddressValueError
22 from enum import IntEnum, unique
23
24 from robot.api.deco import keyword
25 from robot.api import logger
26 from robot.libraries.BuiltIn import BuiltIn
27
28 from requests import request, RequestException, Timeout, TooManyRedirects, \
29     HTTPError, ConnectionError
30 from requests.auth import HTTPBasicAuth
31
32
33 @unique
34 class HTTPCodes(IntEnum):
35     """HTTP status codes"""
36     OK = 200
37     ACCEPTED = 201
38     UNAUTHORIZED = 401
39     FORBIDDEN = 403
40     NOT_FOUND = 404
41     CONFLICT = 409
42     INTERNAL_SERVER_ERROR = 500
43     SERVICE_UNAVAILABLE = 503
44
45
46 class HTTPRequestError(Exception):
47     """Exception raised by HTTPRequest objects.
48
49     When raising this exception, put this information to the message in this
50     order:
51     - short description of the encountered problem,
52     - relevant messages if there are any collected, e.g., from caught
53       exception,
54     - relevant data if there are any collected.
55     The logging is performed on two levels: 1. error - short description of the
56     problem; 2. debug - detailed information.
57     """
58
59     def __init__(self, msg, details='', enable_logging=True):
60         """Sets the exception message and enables / disables logging.
61
62         It is not wanted to log errors when using these keywords together
63         with keywords like "Wait until keyword succeeds". So you can disable
64         logging by setting enable_logging to False.
65
66         :param msg: Message to be displayed and logged.
67         :param enable_logging: When True, logging is enabled, otherwise
68         logging is disabled.
69         :type msg: str
70         :type enable_logging: bool
71         """
72         super(HTTPRequestError, self).__init__()
73         self._msg = "{0}: {1}".format(self.__class__.__name__, msg)
74         self._details = details
75         if enable_logging:
76             logger.error(self._msg)
77             logger.debug(self._details)
78
79     def __repr__(self):
80         return repr(self._msg)
81
82     def __str__(self):
83         return str(self._msg)
84
85
86 class HTTPRequest(object):
87     """A class implementing HTTP requests GET, PUT, POST and DELETE used in
88     communication with Honeycomb.
89
90     The communication with Honeycomb and processing of all exceptions is done in
91     the method _http_request which uses requests.request to send requests and
92     receive responses. The received status code and content of response are
93     logged on the debug level.
94     All possible exceptions raised by requests.request are also processed there.
95
96     The other methods (get, put, post and delete) use _http_request to send
97     corresponding request.
98
99     These methods must not be used as keywords in tests. Use keywords
100     implemented in the module HoneycombAPIKeywords instead.
101     """
102
103     def __init__(self):
104         pass
105
106     @staticmethod
107     def create_full_url(ip_addr, port, path):
108         """Creates full url including host, port, and path to data.
109
110         :param ip_addr: Server IP.
111         :param port: Communication port.
112         :param path: Path to data.
113         :type ip_addr: str
114         :type port: str or int
115         :type path: str
116         :return: Full url.
117         :rtype: str
118         """
119
120         try:
121             IPv6Address(unicode(ip_addr))
122             # IPv6 address must be in brackets
123             ip_addr = "[{0}]".format(ip_addr)
124         except (AttributeError, AddressValueError):
125             pass
126
127         return "http://{ip}:{port}{path}".format(ip=ip_addr, port=port,
128                                                  path=path)
129
130     @staticmethod
131     def _http_request(method, node, path, enable_logging=True, **kwargs):
132         """Sends specified HTTP request and returns status code and response
133         content.
134
135         :param method: The method to be performed on the resource identified by
136         the given request URI.
137         :param node: Honeycomb node.
138         :param path: URL path, e.g. /index.html.
139         :param enable_logging: Used to suppress errors when checking Honeycomb
140         state during suite setup and teardown.
141         :param kwargs: Named parameters accepted by request.request:
142             params -- (optional) Dictionary or bytes to be sent in the query
143             string for the Request.
144             data -- (optional) Dictionary, bytes, or file-like object to
145             send in the body of the Request.
146             json -- (optional) json data to send in the body of the Request.
147             headers -- (optional) Dictionary of HTTP Headers to send with
148             the Request.
149             cookies -- (optional) Dict or CookieJar object to send with the
150             Request.
151             files -- (optional) Dictionary of 'name': file-like-objects
152             (or {'name': ('filename', fileobj)}) for multipart encoding upload.
153             timeout (float or tuple) -- (optional) How long to wait for the
154             server to send data before giving up, as a float, or a (connect
155             timeout, read timeout) tuple.
156             allow_redirects (bool) -- (optional) Boolean. Set to True if POST/
157             PUT/DELETE redirect following is allowed.
158             proxies -- (optional) Dictionary mapping protocol to the URL of
159             the proxy.
160             verify -- (optional) whether the SSL cert will be verified.
161             A CA_BUNDLE path can also be provided. Defaults to True.
162             stream -- (optional) if False, the response content will be
163             immediately downloaded.
164             cert -- (optional) if String, path to ssl client cert file (.pem).
165             If Tuple, ('cert', 'key') pair.
166         :type method: str
167         :type node: dict
168         :type path: str
169         :type enable_logging: bool
170         :type kwargs: dict
171         :return: Status code and content of response.
172         :rtype: tuple
173         :raises HTTPRequestError: If
174         1. it is not possible to connect,
175         2. invalid HTTP response comes from server,
176         3. request exceeded the configured number of maximum re-directions,
177         4. request timed out,
178         5. there is any other unexpected HTTP request exception.
179         """
180         timeout = kwargs["timeout"]
181
182         use_odl = BuiltIn().get_variable_value("${use_odl_client}")
183
184         if use_odl:
185             port = 8181
186             # Using default ODL Restconf port
187             # TODO: add node["honeycomb"]["odl_port"] to topology, use it here
188             odl_url_part = "/network-topology:network-topology/topology/" \
189                            "topology-netconf/node/vpp/yang-ext:mount"
190         else:
191             port = node["honeycomb"]["port"]
192             odl_url_part = ""
193
194         try:
195             path = path.format(odl_url_part=odl_url_part)
196         except KeyError:
197             pass
198
199         url = HTTPRequest.create_full_url(node['host'],
200                                           port,
201                                           path)
202         try:
203             auth = HTTPBasicAuth(node['honeycomb']['user'],
204                                  node['honeycomb']['passwd'])
205             rsp = request(method, url, auth=auth, verify=False, **kwargs)
206
207             logger.debug("Status code: {0}".format(rsp.status_code))
208             logger.debug("Response: {0}".format(rsp.content))
209
210             return rsp.status_code, rsp.content
211
212         except ConnectionError as err:
213             # Switching the logging on / off is needed only for
214             # "requests.ConnectionError"
215             raise HTTPRequestError("Not possible to connect to {0}:{1}.".
216                                    format(node['host'],
217                                           node['honeycomb']['port']),
218                                    repr(err), enable_logging=enable_logging)
219         except HTTPError as err:
220             raise HTTPRequestError("Invalid HTTP response from {0}.".
221                                    format(node['host']), repr(err))
222         except TooManyRedirects as err:
223             raise HTTPRequestError("Request exceeded the configured number "
224                                    "of maximum re-directions.", repr(err))
225         except Timeout as err:
226             raise HTTPRequestError("Request timed out. Timeout is set to {0}.".
227                                    format(timeout), repr(err))
228         except RequestException as err:
229             raise HTTPRequestError("Unexpected HTTP request exception.",
230                                    repr(err))
231
232     @staticmethod
233     @keyword(name="HTTP Get")
234     def get(node, path, headers=None, timeout=15, enable_logging=True):
235         """Sends a GET request and returns the response and status code.
236
237         :param node: Honeycomb node.
238         :param path: URL path, e.g. /index.html.
239         :param headers: Dictionary of HTTP Headers to send with the Request.
240         :param timeout: How long to wait for the server to send data before
241         giving up, as a float, or a (connect timeout, read timeout) tuple.
242         :param enable_logging: Used to suppress errors when checking Honeycomb
243         state during suite setup and teardown. When True, logging is enabled,
244         otherwise logging is disabled.
245         :type node: dict
246         :type path: str
247         :type headers: dict
248         :type timeout: float or tuple
249         :type enable_logging: bool
250         :return: Status code and content of response.
251         :rtype: tuple
252         """
253
254         return HTTPRequest._http_request('GET', node, path,
255                                          enable_logging=enable_logging,
256                                          headers=headers, timeout=timeout)
257
258     @staticmethod
259     @keyword(name="HTTP Put")
260     def put(node, path, headers=None, payload=None, json=None, timeout=15):
261         """Sends a PUT request and returns the response and status code.
262
263         :param node: Honeycomb node.
264         :param path: URL path, e.g. /index.html.
265         :param headers: Dictionary of HTTP Headers to send with the Request.
266         :param payload: Dictionary, bytes, or file-like object to send in
267         the body of the Request.
268         :param json: JSON formatted string to send in the body of the Request.
269         :param timeout: How long to wait for the server to send data before
270         giving up, as a float, or a (connect timeout, read timeout) tuple.
271         :type node: dict
272         :type path: str
273         :type headers: dict
274         :type payload: dict, bytes, or file-like object
275         :type json: str
276         :type timeout: float or tuple
277         :return: Status code and content of response.
278         :rtype: tuple
279         """
280         return HTTPRequest._http_request('PUT', node, path, headers=headers,
281                                          data=payload, json=json,
282                                          timeout=timeout)
283
284     @staticmethod
285     @keyword(name="HTTP Post")
286     def post(node, path, headers=None, payload=None, json=None, timeout=15,
287              enable_logging=True):
288         """Sends a POST request and returns the response and status code.
289
290         :param node: Honeycomb node.
291         :param path: URL path, e.g. /index.html.
292         :param headers: Dictionary of HTTP Headers to send with the Request.
293         :param payload: Dictionary, bytes, or file-like object to send in
294         the body of the Request.
295         :param json: JSON formatted string to send in the body of the Request.
296         :param timeout: How long to wait for the server to send data before
297         giving up, as a float, or a (connect timeout, read timeout) tuple.
298         :param enable_logging: Used to suppress errors when checking ODL
299         state during suite setup and teardown. When True, logging is enabled,
300         otherwise logging is disabled.
301         :type node: dict
302         :type path: str
303         :type headers: dict
304         :type payload: dict, bytes, or file-like object
305         :type json: str
306         :type timeout: float or tuple
307         :type enable_logging: bool
308         :return: Status code and content of response.
309         :rtype: tuple
310         """
311         return HTTPRequest._http_request('POST', node, path,
312                                          enable_logging=enable_logging,
313                                          headers=headers, data=payload,
314                                          json=json, timeout=timeout)
315
316     @staticmethod
317     @keyword(name="HTTP Delete")
318     def delete(node, path, timeout=15):
319         """Sends a DELETE request and returns the response and status code.
320
321         :param node: Honeycomb node.
322         :param path: URL path, e.g. /index.html.
323         :param timeout: How long to wait for the server to send data before
324         giving up, as a float, or a (connect timeout, read timeout) tuple.
325         :type node: dict
326         :type path: str
327         :type timeout: float or tuple
328         :return: Status code and content of response.
329         :rtype: tuple
330         """
331         return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)