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