CSIT-424: HC Test: JSON comparison function rework 99/4199/4
authorselias <samelias@cisco.com>
Fri, 9 Dec 2016 17:11:53 +0000 (18:11 +0100)
committerselias <samelias@cisco.com>
Fri, 16 Dec 2016 11:13:53 +0000 (12:13 +0100)
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 <samelias@cisco.com>
resources/libraries/python/honeycomb/HcAPIKwInterfaces.py
resources/libraries/python/honeycomb/HoneycombUtil.py
resources/libraries/robot/honeycomb/access_control_lists.robot
resources/libraries/robot/honeycomb/interfaces.robot
resources/libraries/robot/honeycomb/sub_interface.robot
resources/test_data/honeycomb/acl.py

index 5658eea..1e0883d 100644 (file)
@@ -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):
index 747c9da..c290af9 100644 (file)
@@ -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)
index a9ea7df..d24da5b 100644 (file)
@@ -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
 | | ... | \| ${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\
index 027b7d7..765aaef 100644 (file)
 | | ${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']}
index e9b8aeb..4a9f5c4 100644 (file)
@@ -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 \
 | | ${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\
index 227330c..ee7f952 100644 (file)
@@ -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",