X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FHTTPRequest.py;h=869078bca307e56ca5df7e5c694ce7de9c9a95ad;hp=7b21f5a7612f370c3128fd30e1aad2b3757131fa;hb=df228e1794d4a5a1c3028e1e214731b5f0450b99;hpb=a1383e569b184808780fbe0405402dea584902a9 diff --git a/resources/libraries/python/HTTPRequest.py b/resources/libraries/python/HTTPRequest.py index 7b21f5a761..869078bca3 100644 --- a/resources/libraries/python/HTTPRequest.py +++ b/resources/libraries/python/HTTPRequest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Cisco and/or its affiliates. +# Copyright (c) 2017 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: @@ -11,90 +11,134 @@ # 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. +"""Implementation of HTTP requests GET, PUT, POST and DELETE used in +communication with Honeycomb. + +The HTTP requests are implemented in the class HTTPRequest which uses +requests.request. """ +from ipaddress import IPv6Address, AddressValueError +from enum import IntEnum, unique + +from robot.api.deco import keyword +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn + 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} +@unique +class HTTPCodes(IntEnum): + """HTTP status codes""" + OK = 200 + ACCEPTED = 201 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + CONFLICT = 409 + INTERNAL_SERVER_ERROR = 500 + SERVICE_UNAVAILABLE = 503 class HTTPRequestError(Exception): - """Exception raised by HTTPRequest objects.""" + """Exception raised by HTTPRequest objects. - def __init__(self, msg, enable_logging=True): - """Sets the exception message and enables / disables logging + When raising this exception, put this information to the message in this + order: + - short description of the encountered problem, + - relevant messages if there are any collected, e.g., from caught + exception, + - relevant data if there are any collected. + The logging is performed on two levels: 1. error - short description of the + problem; 2. debug - detailed information. + """ + + def __init__(self, msg, details='', 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". + with keywords like "Wait until keyword succeeds". So you can disable + logging by setting enable_logging to False. - :param msg: Message to be displayed and logged + :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 - + self._msg = "{0}: {1}".format(self.__class__.__name__, msg) + self._details = details if enable_logging: logger.error(self._msg) - logger.debug(self._repr_msg) + logger.debug(self._details) def __repr__(self): - return repr(self._repr_msg) + return repr(self._msg) def __str__(self): - return str(self._repr_msg) + return str(self._msg) class HTTPRequest(object): - """A class implementing HTTP requests.""" + """A class implementing HTTP requests GET, PUT, POST and DELETE used in + communication with Honeycomb. + + The communication with Honeycomb and processing of all exceptions is done in + the method _http_request which uses requests.request to send requests and + receive responses. The received status code and content of response are + logged on the debug level. + All possible exceptions raised by requests.request are also processed there. + + The other methods (get, put, post and delete) use _http_request to send + corresponding request. + + These methods must not be used as keywords in tests. Use keywords + implemented in the module HoneycombAPIKeywords instead. + """ def __init__(self): pass @staticmethod def create_full_url(ip_addr, port, path): - """Creates full url including IP, port, and path to data. + """Creates full url including host, port, and path to data. - :param ip_addr: Server IP - :param port: Communication port - :param path: 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 + :return: Full url. :rtype: str """ + + try: + IPv6Address(unicode(ip_addr)) + # IPv6 address must be in brackets + ip_addr = "[{0}]".format(ip_addr) + except (AttributeError, AddressValueError): + pass + 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 + """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: + 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 @@ -124,135 +168,164 @@ class HTTPRequest(object): :type path: str :type enable_logging: bool :type kwargs: dict - :return: Status code and content of response + :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 + 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"] + + use_odl = BuiltIn().get_variable_value("${use_odl_client}") + + if use_odl: + port = 8181 + # Using default ODL Restconf port + # TODO: add node["honeycomb"]["odl_port"] to topology, use it here + odl_url_part = "/network-topology:network-topology/topology/" \ + "topology-netconf/node/vpp/yang-ext:mount" + else: + port = node["honeycomb"]["port"] + odl_url_part = "" + + try: + path = path.format(odl_url_part=odl_url_part) + except KeyError: + pass + url = HTTPRequest.create_full_url(node['host'], - node['honeycomb']['port'], + port, path) try: auth = HTTPBasicAuth(node['honeycomb']['user'], node['honeycomb']['passwd']) - rsp = request(method, url, auth=auth, **kwargs) + rsp = request(method, url, auth=auth, verify=False, **kwargs) + + logger.debug("Status code: {0}".format(rsp.status_code)) + logger.debug("Response: {0}".format(rsp.content)) + 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) + raise HTTPRequestError("Not possible to connect to {0}:{1}.". + format(node['host'], + node['honeycomb']['port']), + repr(err), enable_logging=enable_logging) except HTTPError as err: - raise HTTPRequestError("Invalid HTTP response from {0}\n". - format(url) + repr(err)) + raise HTTPRequestError("Invalid HTTP response from {0}.". + format(node['host']), repr(err)) except TooManyRedirects as err: raise HTTPRequestError("Request exceeded the configured number " - "of maximum re-directions\n" + repr(err)) + "of maximum re-directions.", repr(err)) except Timeout as err: - raise HTTPRequestError("Request timed out. Timeout is set to " - "{0}\n".format(timeout) + repr(err)) + raise HTTPRequestError("Request timed out. Timeout is set to {0}.". + format(timeout), repr(err)) except RequestException as err: - raise HTTPRequestError("Unexpected HTTP request exception.\n" + + raise HTTPRequestError("Unexpected HTTP request exception.", repr(err)) @staticmethod @keyword(name="HTTP Get") - def get(node, path, headers=None, timeout=10, enable_logging=True): + def get(node, path, headers=None, timeout=15, 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 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. + :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 + :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): + def put(node, path, headers=None, payload=None, json=None, timeout=15): """Sends a PUT request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :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 formatted string 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 + :return: Status code and content of response. :rtype: tuple """ return HTTPRequest._http_request('PUT', node, path, headers=headers, - data=payload, timeout=timeout) + data=payload, json=json, + timeout=timeout) @staticmethod @keyword(name="HTTP Post") - def post(node, path, headers=None, payload=None, json=None, timeout=10): + def post(node, path, headers=None, payload=None, json=None, timeout=15, + enable_logging=True): """Sends a POST request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :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 json: JSON formatted string 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. + :param enable_logging: Used to suppress errors when checking ODL + 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 payload: dict, bytes, or file-like object :type json: str :type timeout: float or tuple - :return: Status code and content of response + :type enable_logging: bool + :return: Status code and content of response. :rtype: tuple """ - return HTTPRequest._http_request('POST', node, path, headers=headers, - data=payload, json=json, - timeout=timeout) + return HTTPRequest._http_request('POST', node, path, + enable_logging=enable_logging, + headers=headers, data=payload, + json=json, timeout=timeout) @staticmethod @keyword(name="HTTP Delete") - def delete(node, path, timeout=10): + def delete(node, path, timeout=15): """Sends a DELETE request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :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 + :return: Status code and content of response. :rtype: tuple """ return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)