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