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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Implementation of HTTP requests GET, PUT, POST and DELETE used in
15 communication with Honeycomb.
17 The HTTP requests are implemented in the class HTTPRequest which uses
21 from enum import IntEnum, unique
23 from robot.api.deco import keyword
24 from robot.api import logger
26 from requests import request, RequestException, Timeout, TooManyRedirects, \
27 HTTPError, ConnectionError
28 from requests.auth import HTTPBasicAuth
32 class HTTPCodes(IntEnum):
33 """HTTP status codes"""
38 SERVICE_UNAVAILABLE = 503
41 class HTTPRequestError(Exception):
42 """Exception raised by HTTPRequest objects.
44 When raising this exception, put this information to the message in this
46 - short description of the encountered problem,
47 - relevant messages if there are any collected, e.g., from caught
49 - relevant data if there are any collected.
50 The logging is performed on two levels: 1. error - short description of the
51 problem; 2. debug - detailed information.
54 def __init__(self, msg, details='', enable_logging=True):
55 """Sets the exception message and enables / disables logging.
57 It is not wanted to log errors when using these keywords together
58 with keywords like "Wait until keyword succeeds". So you can disable
59 logging by setting enable_logging to False.
61 :param msg: Message to be displayed and logged.
62 :param enable_logging: When True, logging is enabled, otherwise
65 :type enable_logging: bool
67 super(HTTPRequestError, self).__init__()
68 self._msg = "{0}: {1}".format(self.__class__.__name__, msg)
69 self._details = details
71 logger.error(self._msg)
72 logger.debug(self._details)
75 return repr(self._msg)
81 class HTTPRequest(object):
82 """A class implementing HTTP requests GET, PUT, POST and DELETE used in
83 communication with Honeycomb.
85 The communication with Honeycomb and processing of all exceptions is done in
86 the method _http_request which uses requests.request to send requests and
87 receive responses. The received status code and content of response are
88 logged on the debug level.
89 All possible exceptions raised by requests.request are also processed there.
91 The other methods (get, put, post and delete) use _http_request to send
92 corresponding request.
94 These methods must not be used as keywords in tests. Use keywords
95 implemented in the module HoneycombAPIKeywords instead.
102 def create_full_url(ip_addr, port, path):
103 """Creates full url including host, port, and path to data.
105 :param ip_addr: Server IP.
106 :param port: Communication port.
107 :param path: Path to data.
109 :type port: str or int
114 return "http://{ip}:{port}{path}".format(ip=ip_addr, port=port,
118 def _http_request(method, node, path, enable_logging=True, **kwargs):
119 """Sends specified HTTP request and returns status code and response
122 :param method: The method to be performed on the resource identified by
123 the given request URI.
124 :param node: Honeycomb node.
125 :param path: URL path, e.g. /index.html.
126 :param enable_logging: Used to suppress errors when checking Honeycomb
127 state during suite setup and teardown.
128 :param kwargs: Named parameters accepted by request.request:
129 params -- (optional) Dictionary or bytes to be sent in the query
130 string for the Request.
131 data -- (optional) Dictionary, bytes, or file-like object to
132 send in the body of the Request.
133 json -- (optional) json data to send in the body of the Request.
134 headers -- (optional) Dictionary of HTTP Headers to send with
136 cookies -- (optional) Dict or CookieJar object to send with the
138 files -- (optional) Dictionary of 'name': file-like-objects
139 (or {'name': ('filename', fileobj)}) for multipart encoding upload.
140 timeout (float or tuple) -- (optional) How long to wait for the
141 server to send data before giving up, as a float, or a (connect
142 timeout, read timeout) tuple.
143 allow_redirects (bool) -- (optional) Boolean. Set to True if POST/
144 PUT/DELETE redirect following is allowed.
145 proxies -- (optional) Dictionary mapping protocol to the URL of
147 verify -- (optional) whether the SSL cert will be verified.
148 A CA_BUNDLE path can also be provided. Defaults to True.
149 stream -- (optional) if False, the response content will be
150 immediately downloaded.
151 cert -- (optional) if String, path to ssl client cert file (.pem).
152 If Tuple, ('cert', 'key') pair.
156 :type enable_logging: bool
158 :return: Status code and content of response
160 :raises HTTPRequestError: If
161 1. it is not possible to connect,
162 2. invalid HTTP response comes from server,
163 3. request exceeded the configured number of maximum re-directions,
164 4. request timed out,
165 5. there is any other unexpected HTTP request exception.
167 timeout = kwargs["timeout"]
168 url = HTTPRequest.create_full_url(node['host'],
169 node['honeycomb']['port'],
172 auth = HTTPBasicAuth(node['honeycomb']['user'],
173 node['honeycomb']['passwd'])
174 rsp = request(method, url, auth=auth, **kwargs)
176 logger.debug("Status code: {0}".format(rsp.status_code))
177 logger.debug("Response: {0}".format(rsp.content))
179 return rsp.status_code, rsp.content
181 except ConnectionError as err:
182 # Switching the logging on / off is needed only for
183 # "requests.ConnectionError"
184 raise HTTPRequestError("Not possible to connect to {0}:{1}.".
186 node['honeycomb']['port']),
187 repr(err), enable_logging=enable_logging)
188 except HTTPError as err:
189 raise HTTPRequestError("Invalid HTTP response from {0}.".
190 format(node['host']), repr(err))
191 except TooManyRedirects as err:
192 raise HTTPRequestError("Request exceeded the configured number "
193 "of maximum re-directions.", repr(err))
194 except Timeout as err:
195 raise HTTPRequestError("Request timed out. Timeout is set to {0}.".
196 format(timeout), repr(err))
197 except RequestException as err:
198 raise HTTPRequestError("Unexpected HTTP request exception.",
202 @keyword(name="HTTP Get")
203 def get(node, path, headers=None, timeout=10, enable_logging=True):
204 """Sends a GET request and returns the response and status code.
206 :param node: Honeycomb node.
207 :param path: URL path, e.g. /index.html.
208 :param headers: Dictionary of HTTP Headers to send with the Request.
209 :param timeout: How long to wait for the server to send data before
210 giving up, as a float, or a (connect timeout, read timeout) tuple.
211 :param enable_logging: Used to suppress errors when checking Honeycomb
212 state during suite setup and teardown. When True, logging is enabled,
213 otherwise logging is disabled.
217 :type timeout: float or tuple
218 :type enable_logging: bool
219 :return: Status code and content of response.
223 return HTTPRequest._http_request('GET', node, path,
224 enable_logging=enable_logging,
225 headers=headers, timeout=timeout)
228 @keyword(name="HTTP Put")
229 def put(node, path, headers=None, payload=None, json=None, timeout=10):
230 """Sends a PUT request and returns the response and status code.
232 :param node: Honeycomb node.
233 :param path: URL path, e.g. /index.html.
234 :param headers: Dictionary of HTTP Headers to send with the Request.
235 :param payload: Dictionary, bytes, or file-like object to send in
236 the body of the Request.
237 :param json: JSON formatted string to send in the body of the Request.
238 :param timeout: How long to wait for the server to send data before
239 giving up, as a float, or a (connect timeout, read timeout) tuple.
243 :type payload: dict, bytes, or file-like object
245 :type timeout: float or tuple
246 :return: Status code and content of response.
249 return HTTPRequest._http_request('PUT', node, path, headers=headers,
250 data=payload, json=json,
254 @keyword(name="HTTP Post")
255 def post(node, path, headers=None, payload=None, json=None, timeout=10):
256 """Sends a POST request and returns the response and status code.
258 :param node: Honeycomb node.
259 :param path: URL path, e.g. /index.html.
260 :param headers: Dictionary of HTTP Headers to send with the Request.
261 :param payload: Dictionary, bytes, or file-like object to send in
262 the body of the Request.
263 :param json: JSON formatted string to send in the body of the Request.
264 :param timeout: How long to wait for the server to send data before
265 giving up, as a float, or a (connect timeout, read timeout) tuple.
269 :type payload: dict, bytes, or file-like object
271 :type timeout: float or tuple
272 :return: Status code and content of response.
275 return HTTPRequest._http_request('POST', node, path, headers=headers,
276 data=payload, json=json,
280 @keyword(name="HTTP Delete")
281 def delete(node, path, timeout=10):
282 """Sends a DELETE request and returns the response and status code.
284 :param node: Honeycomb node.
285 :param path: URL path, e.g. /index.html.
286 :param timeout: How long to wait for the server to send data before
287 giving up, as a float, or a (connect timeout, read timeout) tuple.
290 :type timeout: float or tuple
291 :return: Status code and content of response.
294 return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)