X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2Fhoneycomb%2FHoneycombUtil.py;fp=resources%2Flibraries%2Fpython%2Fhoneycomb%2FHoneycombUtil.py;h=0000000000000000000000000000000000000000;hp=59483f4672daefc861594bd356185d3a92db7922;hb=e7ad66f3147662973039caaac33015de7e0c6f8c;hpb=20cc67d5f23a7f4e05b08012bf3d3a63be4bcf63 diff --git a/resources/libraries/python/honeycomb/HoneycombUtil.py b/resources/libraries/python/honeycomb/HoneycombUtil.py deleted file mode 100644 index 59483f4672..0000000000 --- a/resources/libraries/python/honeycomb/HoneycombUtil.py +++ /dev/null @@ -1,512 +0,0 @@ -# Copyright (c) 2018 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. - -"""Implementation of low level functionality used in communication with -Honeycomb. - -Exception HoneycombError is used in all methods and in all modules with -Honeycomb keywords. - -Class HoneycombUtil implements methods used by Honeycomb keywords. They must not -be used directly in tests. Use keywords implemented in the module -HoneycombAPIKeywords instead. -""" - -from json import loads, dumps -from enum import Enum, unique - -from robot.api import logger - -from resources.libraries.python.ssh import SSH -from resources.libraries.python.HTTPRequest import HTTPRequest -from resources.libraries.python.Constants import Constants as Const - - -@unique -class DataRepresentation(Enum): - """Representation of data sent by PUT and POST requests.""" - NO_DATA = 0 - JSON = 1 - XML = 2 - TXT = 3 - - -# Headers used in requests. Key - content representation, value - header. -HEADERS = {DataRepresentation.NO_DATA: - {}, # It must be empty dictionary. - DataRepresentation.JSON: - {"Content-Type": "application/json", - "Accept": "text/plain"}, - DataRepresentation.XML: - {"Content-Type": "application/xml", - "Accept": "text/plain"}, - DataRepresentation.TXT: - {"Content-Type": "text/plain", - "Accept": "text/plain"} - } - - -class HoneycombError(Exception): - - """Exception(s) raised by methods working with Honeycomb. - - When raising this exception, put this information to the message in this - order: - - - short description of the encountered problem (parameter msg), - - relevant messages if there are any collected, e.g., from caught - exception (optional parameter details), - - relevant data if there are any collected (optional parameter details). - - 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". So you can disable - logging by setting enable_logging to False. - - :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(HoneycombError, self).__init__() - self._msg = "{0}: {1}".format(self.__class__.__name__, msg) - self._details = details - if enable_logging: - logger.debug(self._details) - - def __repr__(self): - return repr(self._msg) - - def __str__(self): - return str(self._msg) - - -class HoneycombUtil(object): - """Implements low level functionality used in communication with Honeycomb. - - There are implemented methods to get, put and delete data to/from Honeycomb. - They are based on functionality implemented in the module HTTPRequests which - uses HTTP requests GET, PUT, POST and DELETE to communicate with Honeycomb. - - It is possible to PUT the data represented as XML or JSON structures or as - plain text. - Data received in the response of GET are always represented as a JSON - structure. - - There are also two supportive methods implemented: - - - read_path_from_url_file which reads URL file and returns a path (see - docs/honeycomb_url_files.rst). - - parse_json_response which parses data from response in JSON - representation according to given path. - """ - - def __init__(self): - pass - - @staticmethod - def read_path_from_url_file(url_file): - """Read path from .url file. - - For more information about .url file see docs/honeycomb_url_files.rst - - :param url_file: URL file. The argument contains only the name of file - without extension, not the full path. - :type url_file: str - :returns: Requested path. - :rtype: str - """ - - url_file = "{0}/{1}.url".format(Const.RESOURCES_TPL_HC, url_file) - with open(url_file) as template: - path = template.readline() - return path - - @staticmethod - def find_item(data, path): - """Find a data item (single leaf or sub-tree) in data received from - Honeycomb REST API. - - Path format: - The path is a tuple with items navigating to requested data. The items - can be strings or tuples: - - - string item represents a dictionary key in data, - - tuple item represents list item in data. - - Example:: - - data = \ - { - "interfaces": { - "interface": [ - { - "name": "GigabitEthernet0/8/0", - "enabled": "true", - "type": "iana-if-type:ethernetCsmacd", - }, - { - "name": "local0", - "enabled": "false", - "type": "iana-if-type:ethernetCsmacd", - } - ] - } - } - - path = ("interfaces", ("interface", "name", "local0"), "enabled") - This path points to "false". - - The tuple ("interface", "name", "local0") consists of:: - - index 0 - dictionary key pointing to a list, - index 1 - key which identifies an item in the list, it is also marked - as the key in corresponding yang file. - index 2 - key value. - - :param data: Data received from Honeycomb REST API. - :param path: Path to data we want to find. - :type data: dict - :type path: tuple - :returns: Data represented by path. - :rtype: str, dict, or list - :raises HoneycombError: If the data has not been found. - """ - - for path_item in path: - try: - if isinstance(path_item, str): - data = data[path_item] - elif isinstance(path_item, tuple): - for data_item in data[path_item[0]]: - if data_item[path_item[1]] == path_item[2]: - data = data_item - except KeyError as err: - raise HoneycombError("Data not found: {0}".format(err)) - - return data - - @staticmethod - def remove_item(data, path): - """Remove a data item (single leaf or sub-tree) in data received from - Honeycomb REST API. - - :param data: Data received from Honeycomb REST API. - :param path: Path to data we want to remove. - :type data: dict - :type path: tuple - :returns: Original data without removed part. - :rtype: dict - """ - - origin_data = previous_data = data - try: - for path_item in path: - previous_data = data - if isinstance(path_item, str): - data = data[path_item] - elif isinstance(path_item, tuple): - for data_item in data[path_item[0]]: - if data_item[path_item[1]] == path_item[2]: - data = data_item - except KeyError as err: - logger.debug("Data not found: {0}".format(err)) - return origin_data - - if isinstance(path[-1], str): - previous_data.pop(path[-1]) - elif isinstance(path[-1], tuple): - previous_data[path[-1][0]].remove(data) - if not previous_data[path[-1][0]]: - previous_data.pop(path[-1][0]) - - return origin_data - - @staticmethod - def set_item_value(data, path, new_value): - """Set or change the value (single leaf or sub-tree) in data received - from Honeycomb REST API. - - If the item is not present in the data structure, it is created. - - :param data: Data received from Honeycomb REST API. - :param path: Path to data we want to change or create. - :param new_value: The value to be set. - :type data: dict - :type path: tuple - :type new_value: str, dict or list - :returns: Original data with the new value. - :rtype: dict - """ - - origin_data = data - for path_item in path[:-1]: - if isinstance(path_item, str): - try: - data = data[path_item] - except KeyError: - data[path_item] = {} - data = data[path_item] - elif isinstance(path_item, tuple): - try: - flag = False - index = 0 - for data_item in data[path_item[0]]: - if data_item[path_item[1]] == path_item[2]: - data = data[path_item[0]][index] - flag = True - break - index += 1 - if not flag: - data[path_item[0]].append({path_item[1]: path_item[2]}) - data = data[path_item[0]][-1] - except KeyError: - data[path_item] = [] - - if not path[-1] in data.keys(): - data[path[-1]] = {} - - if isinstance(new_value, list) and isinstance(data[path[-1]], list): - for value in new_value: - data[path[-1]].append(value) - else: - data[path[-1]] = new_value - - return origin_data - - @staticmethod - def get_honeycomb_data(node, url_file, path=""): - """Retrieve data from Honeycomb according to given URL. - - :param node: Honeycomb node. - :param url_file: URL file. The argument contains only the name of file - without extension, not the full path. - :param path: Path which is added to the base path to identify the data. - :type node: dict - :type url_file: str - :type path: str - :returns: Status code and content of response. - :rtype: tuple - """ - - base_path = HoneycombUtil.read_path_from_url_file(url_file) - path = base_path + path - status_code, resp = HTTPRequest.get(node, path) - - try: - data = loads(resp) - except ValueError: - logger.debug("Failed to deserialize JSON data.") - data = None - - return status_code, data - - @staticmethod - def put_honeycomb_data(node, url_file, data, path="", - data_representation=DataRepresentation.JSON): - """Send configuration data using PUT request and return the status code - and response content. - - :param node: Honeycomb node. - :param url_file: URL file. The argument contains only the name of file - without extension, not the full path. - :param data: Configuration data to be sent to Honeycomb. - :param path: Path which is added to the base path to identify the data. - :param data_representation: How the data is represented. - :type node: dict - :type url_file: str - :type data: dict, str - :type path: str - :type data_representation: DataRepresentation - :returns: Status code and content of response. - :rtype: tuple - :raises HoneycombError: If the given data representation is not defined - in HEADERS. - """ - - try: - header = HEADERS[data_representation] - except AttributeError as err: - raise HoneycombError("Wrong data representation: {0}.". - format(data_representation), repr(err)) - if data_representation == DataRepresentation.JSON: - data = dumps(data) - - logger.trace(data) - - base_path = HoneycombUtil.read_path_from_url_file(url_file) - path = base_path + path - logger.trace(path) - return HTTPRequest.put( - node=node, path=path, headers=header, payload=data) - - @staticmethod - def post_honeycomb_data(node, url_file, data=None, - data_representation=DataRepresentation.JSON, - timeout=10): - """Send a POST request and return the status code and response content. - - :param node: Honeycomb node. - :param url_file: URL file. The argument contains only the name of file - without extension, not the full path. - :param data: Configuration data to be sent to Honeycomb. - :param data_representation: How the data is represented. - :param timeout: How long to wait for the server to send data before - giving up. - :type node: dict - :type url_file: str - :type data: dict, str - :type data_representation: DataRepresentation - :type timeout: int - :returns: Status code and content of response. - :rtype: tuple - :raises HoneycombError: If the given data representation is not defined - in HEADERS. - """ - - try: - header = HEADERS[data_representation] - except AttributeError as err: - raise HoneycombError("Wrong data representation: {0}.". - format(data_representation), repr(err)) - if data_representation == DataRepresentation.JSON: - data = dumps(data) - - path = HoneycombUtil.read_path_from_url_file(url_file) - return HTTPRequest.post( - node=node, path=path, headers=header, payload=data, timeout=timeout) - - @staticmethod - def delete_honeycomb_data(node, url_file, path=""): - """Delete data from Honeycomb according to given URL. - - :param node: Honeycomb node. - :param url_file: URL file. The argument contains only the name of file - without extension, not the full path. - :param path: Path which is added to the base path to identify the data. - :type node: dict - :type url_file: str - :type path: str - :returns: Status code and content of response. - :rtype: tuple - """ - - base_path = HoneycombUtil.read_path_from_url_file(url_file) - path = base_path + path - return HTTPRequest.delete(node, path) - - @staticmethod - def append_honeycomb_log(node, suite_name): - """Append Honeycomb log for the current test suite to the full log. - - :param node: Honeycomb node. - :param suite_name: Name of the current test suite. ${SUITE_NAME} - variable in robotframework. - :type node: dict - :type suite_name: str - """ - - ssh = SSH() - ssh.connect(node) - - ssh.exec_command( - "echo '{separator}' >> /tmp/honeycomb.log".format(separator="="*80)) - ssh.exec_command( - "echo 'Log for suite: {suite}' >> /tmp/honeycomb.log".format( - suite=suite_name)) - ssh.exec_command( - "cat {hc_log} >> /tmp/honeycomb.log".format( - hc_log=Const.REMOTE_HC_LOG)) - - @staticmethod - def append_odl_log(node, odl_name, suite_name): - """Append ODL karaf log for the current test suite to the full log. - - :param node: Honeycomb node. - :param odl_name: Name of ODL client version to use. - :param suite_name: Name of the current test suite. ${SUITE_NAME} - variable in robotframework. - :type node: dict - :type odl_name: str - :type suite_name: str - """ - - ssh = SSH() - ssh.connect(node) - - ssh.exec_command( - "echo '{separator}' >> /tmp/karaf.log".format(separator="="*80)) - ssh.exec_command( - "echo 'Log for suite: {suite}' >> /tmp/karaf.log".format( - suite=suite_name)) - ssh.exec_command( - "cat /tmp/karaf_{odl_name}/data/log/karaf.log >> /tmp/karaf.log" - .format(odl_name=odl_name)) - - @staticmethod - def clear_honeycomb_log(node): - """Delete the Honeycomb log file for the current test suite. - - :param node: Honeycomb node. - :type node: dict - """ - - ssh = SSH() - ssh.connect(node) - - ssh.exec_command("sudo rm {hc_log}".format(hc_log=Const.REMOTE_HC_LOG)) - - @staticmethod - def archive_honeycomb_log(node, perf=False): - """Copy honeycomb log file from DUT node to VIRL for archiving. - - :param node: Honeycomb node. - :param perf: Alternate handling, for use with performance test topology. - :type node: dict - :type perf: bool - """ - - ssh = SSH() - ssh.connect(node) - - if not perf: - cmd = "cp /tmp/honeycomb.log /scratch/" - ssh.exec_command_sudo(cmd, timeout=60) - else: - ssh.scp( - ".", - "/tmp/honeycomb.log", - get=True, - timeout=60) - ssh.exec_command("rm /tmp/honeycomb.log") - - @staticmethod - def archive_odl_log(node): - """Copy ODL karaf log file from DUT node to VIRL for archiving. - - :param node: Honeycomb node. - :type node: dict - """ - - ssh = SSH() - ssh.connect(node) - - cmd = "cp /tmp/karaf.log /scratch/" - ssh.exec_command_sudo(cmd, timeout=60)