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