From: selias Date: Fri, 9 Dec 2016 17:11:53 +0000 (+0100) Subject: CSIT-424: HC Test: JSON comparison function rework X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=1813672eb9f6988046bc65167235ae37b088298c;hp=cf561a6e3d4c4fbd78ab6c9d0a9aa817bb3300fc CSIT-424: HC Test: JSON comparison function rework When comparing multi-level JSON trees, the exceptions raised carry more useful information. Keys and values not present in reference data are ignored. No longer puts chunks of honeycomb's log file into robot report. The entire log file will be archived after https://gerrit.fd.io/r/4171 Change-Id: Ib597080fa3d9b2c43463c76ee0d52f317ea072e7 Signed-off-by: selias --- diff --git a/resources/libraries/python/honeycomb/HcAPIKwInterfaces.py b/resources/libraries/python/honeycomb/HcAPIKwInterfaces.py index 5658eea146..1e0883db27 100644 --- a/resources/libraries/python/honeycomb/HcAPIKwInterfaces.py +++ b/resources/libraries/python/honeycomb/HcAPIKwInterfaces.py @@ -1313,43 +1313,72 @@ class InterfaceKeywords(object): node, super_interface, path, None) @staticmethod - def compare_data_structures(data, ref, ignore=(), list_order=True): - """Checks if data obtained from UUT is as expected. + def compare_data_structures(data, ref, _path=''): + """Checks if data obtained from UUT is as expected. If it is not, + proceeds down the list/dictionary tree and finds the point of mismatch. :param data: Data to be checked. :param ref: Referential data used for comparison. - :param ignore: Dictionary keys to be ignored. - :param list_order: Whether to consider the order of list items\ - in comparison. + :param _path: Used in recursive calls, stores the path taken down + the JSON tree. :type data: dict :type ref: dict - :type ignore: iterable - :type list_order: bool + :type _path: str - :raises HoneycombError: If a parameter from referential data is not - present in operational data or if it has different value. + :raises HoneycombError: If the data structures do not match in some way, + or if they are not in deserialized JSON format. """ - errors = "" - - for key, item in ref.items(): - if key in ignore: - continue - try: - if data[key] != item: - if not list_order and sorted(data[key]) == sorted(item): - pass + if data == ref: + return True + + elif isinstance(data, dict) and isinstance(ref, dict): + for key in ref: + if key not in data: + raise HoneycombError( + "Key {key} is not present in path {path}. Keys in path:" + "{data_keys}".format( + key=key, + path=_path, + data_keys=data.keys())) + + if data[key] != ref[key]: + if isinstance(data[key], list) \ + or isinstance(data[key], dict): + InterfaceKeywords.compare_data_structures( + data[key], ref[key], + _path + '[{0}]'.format(key)) else: - errors += ("\nThe value of parameter '{0}' is " - "incorrect. It should be " - "'{1}' but it is '{2}'". - format(key, item, data[key])) - except KeyError: - errors += ("\nThe parameter '{0}' is not present in " - "operational data".format(key)) + raise HoneycombError( + "Data mismatch, key {key} in path {path} has value" + " {data}, but should be {ref}".format( + key=key, + path=_path, + data=data[key], + ref=ref[key])) + + elif isinstance(data, list) and isinstance(ref, list): + for item in ref: + if item not in data: + if isinstance(item, dict): + InterfaceKeywords.compare_data_structures( + data[0], item, + _path + '[{0}]'.format(ref.index(item))) + else: + raise HoneycombError( + "Data mismatch, list item {index} in path {path}" + " has value {data}, but should be {ref}".format( + index=ref.index(item), + path=_path, + data=data[0], + ref=item)) - if errors: - raise HoneycombError(errors) + else: + raise HoneycombError( + "Unexpected data type {data_type}, reference type is" + " {ref_type}. Must be list or dictionary.".format( + data_type=type(data), + ref_type=type(ref))) @staticmethod def compare_interface_lists(list1, list2): diff --git a/resources/libraries/python/honeycomb/HoneycombUtil.py b/resources/libraries/python/honeycomb/HoneycombUtil.py index 747c9dae8e..c290af9a6e 100644 --- a/resources/libraries/python/honeycomb/HoneycombUtil.py +++ b/resources/libraries/python/honeycomb/HoneycombUtil.py @@ -27,8 +27,7 @@ from enum import Enum, unique from robot.api import logger -from resources.libraries.python.ssh import SSH -from resources.libraries.python.HTTPRequest import HTTPRequest, HTTPCodes +from resources.libraries.python.HTTPRequest import HTTPRequest from resources.libraries.python.constants import Constants as Const @@ -302,10 +301,7 @@ class HoneycombUtil(object): base_path = HoneycombUtil.read_path_from_url_file(url_file) path = base_path + path status_code, resp = HTTPRequest.get(node, path) - response = loads(resp) - if status_code != HTTPCodes.OK: - HoneycombUtil.read_log_tail(node) - return status_code, response + return status_code, loads(resp) @staticmethod def put_honeycomb_data(node, url_file, data, path="", @@ -343,13 +339,9 @@ class HoneycombUtil(object): base_path = HoneycombUtil.read_path_from_url_file(url_file) path = base_path + path logger.trace(path) - (status_code, response) = HTTPRequest.put( + return HTTPRequest.put( node=node, path=path, headers=header, payload=data) - if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED): - HoneycombUtil.read_log_tail(node) - return status_code, response - @staticmethod def post_honeycomb_data(node, url_file, data=None, data_representation=DataRepresentation.JSON, @@ -383,13 +375,9 @@ class HoneycombUtil(object): data = dumps(data) path = HoneycombUtil.read_path_from_url_file(url_file) - (status_code, response) = HTTPRequest.post( + return HTTPRequest.post( node=node, path=path, headers=header, payload=data, timeout=timeout) - if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED): - HoneycombUtil.read_log_tail(node) - return status_code, response - @staticmethod def delete_honeycomb_data(node, url_file, path=""): """Delete data from Honeycomb according to given URL. @@ -407,48 +395,4 @@ class HoneycombUtil(object): base_path = HoneycombUtil.read_path_from_url_file(url_file) path = base_path + path - (status_code, response) = HTTPRequest.delete(node, path) - - if status_code != HTTPCodes.OK: - HoneycombUtil.read_log_tail(node) - return status_code, response - - @staticmethod - def read_log_tail(node, lines=120): - """Read the last N lines of the Honeycomb log file and print them - to robot log. - - :param node: Honeycomb node. - :param lines: Number of lines to read. - :type node: dict - :type lines: int - :returns: Last N log lines. - :rtype: str - """ - - logger.trace( - "HTTP request failed, " - "obtaining last {0} lines of Honeycomb log...".format(lines)) - - ssh = SSH() - ssh.connect(node) - cmd = "tail -n {0} /var/log/honeycomb/honeycomb.log".format(lines) - # ssh also logs the reply on trace level - (_, stdout, _) = ssh.exec_command(cmd, timeout=30) - - return stdout - - @staticmethod - def archive_honeycomb_log(node): - """Copy honeycomb log file from DUT node to VIRL for archiving. - - :param node: Honeycomb node. - :type node: dict - """ - - ssh = SSH() - ssh.connect(node) - - cmd = "cp /var/log/honeycomb/honeycomb.log /scratch/" - - ssh.exec_command_sudo(cmd) + return HTTPRequest.delete(node, path) diff --git a/resources/libraries/robot/honeycomb/access_control_lists.robot b/resources/libraries/robot/honeycomb/access_control_lists.robot index a9ea7dffe6..d24da5bd96 100644 --- a/resources/libraries/robot/honeycomb/access_control_lists.robot +++ b/resources/libraries/robot/honeycomb/access_control_lists.robot @@ -12,7 +12,6 @@ # limitations under the License. *** Variables *** #TODO: update based on resolution of bug https://jira.fd.io/browse/HONEYCOMB-119 -| @{hc_table_ignore}= | memory_size *** Settings *** | Library | resources.libraries.python.Classify @@ -124,7 +123,7 @@ | | ... | \| ${settings} \| | | [Arguments] | ${node} | ${settings} | | ${data}= | Get classify table oper data | ${node} | ${settings['name']} -| | Compare data structures | ${data} | ${settings} | ignore=${hc_table_ignore} +| | Compare data structures | ${data} | ${settings} | ACL table from VAT should be | | [Documentation] | Retrieves ACL table information from VAT\ diff --git a/resources/libraries/robot/honeycomb/interfaces.robot b/resources/libraries/robot/honeycomb/interfaces.robot index 027b7d74a3..765aaef7c0 100644 --- a/resources/libraries/robot/honeycomb/interfaces.robot +++ b/resources/libraries/robot/honeycomb/interfaces.robot @@ -235,7 +235,7 @@ | | ${api_data}= | interfaceAPI.Get interface oper data | ${node} | ${interface} | | ${data_neighbors}= | Set Variable | ${api_data['ietf-ip:ipv4']['neighbor']} | | Compare data structures -| | ... | ${data_neighbors} | ${neighbors} | list_order=${False} +| | ... | ${data_neighbors} | ${neighbors} | | Should be equal | ${neighbor['fib_address']} | | ... | ${api_data['ietf-ip:ipv4']['neighbor'][0]['ip']} | | Should be equal | ${neighbor['fib_mac']} diff --git a/resources/libraries/robot/honeycomb/sub_interface.robot b/resources/libraries/robot/honeycomb/sub_interface.robot index e9b8aeb008..4a9f5c403b 100644 --- a/resources/libraries/robot/honeycomb/sub_interface.robot +++ b/resources/libraries/robot/honeycomb/sub_interface.robot @@ -68,7 +68,6 @@ | | ${api_data}= | interfaceAPI.Get sub interface oper data | | ... | ${node} | ${super_interface} | ${identifier} | | interfaceAPI.Compare Data Structures | ${api_data} | ${sub_if_settings} -| | ... | list_order=${False} | Sub-interface configuration from Honeycomb should be empty | | [Documentation] | Retrieves sub-interface configuration through Honeycomb \ @@ -391,7 +390,7 @@ | | ${api_data}= | interfaceAPI.Get tag rewrite oper data | | ... | ${node} | ${super_if} | ${identifier} | | interfaceAPI.Compare Data Structures -| | ... | ${api_data} | ${settings} | list_order=${False} +| | ... | ${api_data} | ${settings} | Rewrite tag from VAT should be | | [Documentation] | Retrieves sub-interface configuration through VAT and\ diff --git a/resources/test_data/honeycomb/acl.py b/resources/test_data/honeycomb/acl.py index 227330c2a9..ee7f9525b8 100644 --- a/resources/test_data/honeycomb/acl.py +++ b/resources/test_data/honeycomb/acl.py @@ -17,7 +17,6 @@ hc_acl_table = { "name": "acl_table_test", "nbuckets": 1, - "memory_size": 100000, "skip_n_vectors": 0, "miss_next": "permit", "mask": "00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:00:00:00:00" @@ -26,7 +25,6 @@ hc_acl_table = { hc_acl_table2 = { "name": "acl_table_test2", "nbuckets": 2, - "memory_size": 100000, "skip_n_vectors": 1, "next_table": "acl_table_test", "miss_next": "deny",