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