Setup and check honeycomb on all DUTs
[csit.git] / resources / libraries / python / HTTPRequest.py
diff --git a/resources/libraries/python/HTTPRequest.py b/resources/libraries/python/HTTPRequest.py
new file mode 100644 (file)
index 0000000..7b21f5a
--- /dev/null
@@ -0,0 +1,258 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Implements HTTP requests GET, PUT, POST, DELETE used in communication with
+honeycomb.
+"""
+
+from requests import request, RequestException, Timeout, TooManyRedirects, \
+    HTTPError, ConnectionError
+from requests.auth import HTTPBasicAuth
+
+from robot.api import logger
+from robot.api.deco import keyword
+
+
+HTTP_CODES = {"OK": 200,
+              "UNAUTHORIZED": 401,
+              "FORBIDDEN": 403,
+              "NOT_FOUND": 404,
+              "SERVICE_UNAVAILABLE": 503}
+
+
+class HTTPRequestError(Exception):
+    """Exception raised by HTTPRequest objects."""
+
+    def __init__(self, msg, enable_logging=True):
+        """Sets the exception message and enables / disables logging
+
+        It is not wanted to log errors when using these keywords together
+        with keywords like "Wait until keyword succeeds".
+
+        :param msg: Message to be displayed and logged
+        :param enable_logging: When True, logging is enabled, otherwise
+        logging is disabled.
+        :type msg: str
+        :type enable_logging: bool
+        """
+        super(HTTPRequestError, self).__init__()
+        self._msg = msg
+        self._repr_msg = self.__module__ + '.' + \
+            self.__class__.__name__ + ": " + self._msg
+
+        if enable_logging:
+            logger.error(self._msg)
+            logger.debug(self._repr_msg)
+
+    def __repr__(self):
+        return repr(self._repr_msg)
+
+    def __str__(self):
+        return str(self._repr_msg)
+
+
+class HTTPRequest(object):
+    """A class implementing HTTP requests."""
+
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def create_full_url(ip_addr, port, path):
+        """Creates full url including IP, port, and path to data.
+
+        :param ip_addr: Server IP
+        :param port: Communication port
+        :param path: Path to data
+        :type ip_addr: str
+        :type port: str or int
+        :type path: str
+        :return: full url
+        :rtype: str
+        """
+        return "http://{ip}:{port}{path}".format(ip=ip_addr, port=port,
+                                                 path=path)
+
+    @staticmethod
+    def _http_request(method, node, path, enable_logging=True, **kwargs):
+        """Sends specified HTTP request and returns status code and
+        response content
+
+        :param method: The method to be performed on the resource identified by
+        the given request URI
+        :param node: honeycomb node
+        :param path: URL path, e.g. /index.html
+        :param enable_logging: used to suppress errors when checking
+        honeycomb state during suite setup and teardown
+        :param kwargs: named parameters accepted by request.request:
+            params -- (optional) Dictionary or bytes to be sent in the query
+            string for the Request.
+            data -- (optional) Dictionary, bytes, or file-like object to
+            send in the body of the Request.
+            json -- (optional) json data to send in the body of the Request.
+            headers -- (optional) Dictionary of HTTP Headers to send with
+            the Request.
+            cookies -- (optional) Dict or CookieJar object to send with the
+            Request.
+            files -- (optional) Dictionary of 'name': file-like-objects
+            (or {'name': ('filename', fileobj)}) for multipart encoding upload.
+            timeout (float or tuple) -- (optional) How long to wait for the
+            server to send data before giving up, as a float, or a (connect
+            timeout, read timeout) tuple.
+            allow_redirects (bool) -- (optional) Boolean. Set to True if POST/
+            PUT/DELETE redirect following is allowed.
+            proxies -- (optional) Dictionary mapping protocol to the URL of
+            the proxy.
+            verify -- (optional) whether the SSL cert will be verified.
+            A CA_BUNDLE path can also be provided. Defaults to True.
+            stream -- (optional) if False, the response content will be
+            immediately downloaded.
+            cert -- (optional) if String, path to ssl client cert file (.pem).
+            If Tuple, ('cert', 'key') pair.
+        :type method: str
+        :type node: dict
+        :type path: str
+        :type enable_logging: bool
+        :type kwargs: dict
+        :return: Status code and content of response
+        :rtype: tuple
+        :raises HTTPRequestError: If
+        1. it is not possible to connect
+        2. invalid HTTP response comes from server
+        3. request exceeded the configured number of maximum re-directions
+        4. request timed out
+        5. there is any other unexpected HTTP request exception
+        """
+        timeout = kwargs["timeout"]
+        url = HTTPRequest.create_full_url(node['host'],
+                                          node['honeycomb']['port'],
+                                          path)
+        try:
+            auth = HTTPBasicAuth(node['honeycomb']['user'],
+                                 node['honeycomb']['passwd'])
+            rsp = request(method, url, auth=auth, **kwargs)
+            return rsp.status_code, rsp.content
+
+        except ConnectionError as err:
+            # Switching the logging on / off is needed only for
+            # "requests.ConnectionError"
+            if enable_logging:
+                raise HTTPRequestError("Not possible to connect to {0}\n".
+                                       format(url) + repr(err))
+            else:
+                raise HTTPRequestError("Not possible to connect to {0}\n".
+                                       format(url) + repr(err),
+                                       enable_logging=False)
+        except HTTPError as err:
+            raise HTTPRequestError("Invalid HTTP response from {0}\n".
+                                   format(url) + repr(err))
+        except TooManyRedirects as err:
+            raise HTTPRequestError("Request exceeded the configured number "
+                                   "of maximum re-directions\n" + repr(err))
+        except Timeout as err:
+            raise HTTPRequestError("Request timed out. Timeout is set to "
+                                   "{0}\n".format(timeout) + repr(err))
+        except RequestException as err:
+            raise HTTPRequestError("Unexpected HTTP request exception.\n" +
+                                   repr(err))
+
+    @staticmethod
+    @keyword(name="HTTP Get")
+    def get(node, path, headers=None, timeout=10, enable_logging=True):
+        """Sends a GET request and returns the response and status code.
+
+        :param node: honeycomb node
+        :param path: URL path, e.g. /index.html
+        :param headers: Dictionary of HTTP Headers to send with the Request.
+        :param timeout: How long to wait for the server to send data before
+        giving up, as a float, or a (connect timeout, read timeout) tuple.
+        :param enable_logging: Used to suppress errors when checking
+        honeycomb state during suite setup and teardown. When True, logging
+        is enabled, otherwise logging is disabled.
+        :type node: dict
+        :type path: str
+        :type headers: dict
+        :type timeout: float or tuple
+        :type enable_logging: bool
+        :return: Status code and content of response
+        :rtype: tuple
+        """
+        return HTTPRequest._http_request('GET', node, path,
+                                         enable_logging=enable_logging,
+                                         headers=headers, timeout=timeout)
+
+    @staticmethod
+    @keyword(name="HTTP Put")
+    def put(node, path, headers=None, payload=None, timeout=10):
+        """Sends a PUT request and returns the response and status code.
+
+        :param node: honeycomb node
+        :param path: URL path, e.g. /index.html
+        :param headers: Dictionary of HTTP Headers to send with the Request.
+        :param payload: Dictionary, bytes, or file-like object to send in
+        the body of the Request.
+        :param timeout: How long to wait for the server to send data before
+        giving up, as a float, or a (connect timeout, read timeout) tuple.
+        :type node: dict
+        :type path: str
+        :type headers: dict
+        :type payload: dict, bytes, or file-like object
+        :type timeout: float or tuple
+        :return: Status code and content of response
+        :rtype: tuple
+        """
+        return HTTPRequest._http_request('PUT', node, path, headers=headers,
+                                         data=payload, timeout=timeout)
+
+    @staticmethod
+    @keyword(name="HTTP Post")
+    def post(node, path, headers=None, payload=None, json=None, timeout=10):
+        """Sends a POST request and returns the response and status code.
+
+        :param node: honeycomb node
+        :param path: URL path, e.g. /index.html
+        :param headers: Dictionary of HTTP Headers to send with the Request.
+        :param payload: Dictionary, bytes, or file-like object to send in
+        the body of the Request.
+        :param json: json data to send in the body of the Request
+        :param timeout: How long to wait for the server to send data before
+        giving up, as a float, or a (connect timeout, read timeout) tuple.
+        :type node: dict
+        :type path: str
+        :type headers: dict
+        :type payload: dict, bytes, or file-like object
+        :type json: str
+        :type timeout: float or tuple
+        :return: Status code and content of response
+        :rtype: tuple
+        """
+        return HTTPRequest._http_request('POST', node, path, headers=headers,
+                                         data=payload, json=json,
+                                         timeout=timeout)
+
+    @staticmethod
+    @keyword(name="HTTP Delete")
+    def delete(node, path, timeout=10):
+        """Sends a DELETE request and returns the response and status code.
+
+        :param node: honeycomb node
+        :param path: URL path, e.g. /index.html
+        :param timeout: How long to wait for the server to send data before
+        giving up, as a float, or a (connect timeout, read timeout) tuple.
+        :type node: dict
+        :type path: str
+        :type timeout: float or tuple
+        :return: Status code and content of response
+        :rtype: tuple
+        """
+        return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)