diff options
author | Viliam Luc <vluc@cisco.com> | 2021-08-02 14:25:26 +0200 |
---|---|---|
committer | Viliam Luc <vluc@cisco.com> | 2021-09-10 10:26:24 +0200 |
commit | ec467277744783015a2da6713298b35d13d92e6f (patch) | |
tree | 57b1bb084f5ae9c6346be5a92f8fe5fac38f1cd4 /resources/libraries/python | |
parent | 9cceefae3248e9a1e9e5586391c9263cf114a753 (diff) |
back-to-back tests: add TG tests
Change-Id: I9d028294deb1e31b6d185deb1c7523e0226a0ada
Signed-off-by: Viliam Luc <vluc@cisco.com>
Diffstat (limited to 'resources/libraries/python')
-rw-r--r-- | resources/libraries/python/NodePath.py | 43 | ||||
-rw-r--r-- | resources/libraries/python/autogen/Regenerator.py | 82 | ||||
-rw-r--r-- | resources/libraries/python/autogen/Testcase.py | 27 | ||||
-rw-r--r-- | resources/libraries/python/topology.py | 55 |
4 files changed, 178 insertions, 29 deletions
diff --git a/resources/libraries/python/NodePath.py b/resources/libraries/python/NodePath.py index cc91c4a2f5..d1f974aafe 100644 --- a/resources/libraries/python/NodePath.py +++ b/resources/libraries/python/NodePath.py @@ -92,7 +92,7 @@ class NodePath: self._path = [] self._path_iter = [] - def compute_path(self, always_same_link=True): + def compute_path(self, always_same_link=True, topo_has_dut=True): """Compute path for added nodes. .. note:: First add at least two nodes to the topology. @@ -100,19 +100,26 @@ class NodePath: :param always_same_link: If True use always same link between two nodes in path. If False use different link (if available) between two nodes if one link was used before. + :param topo_has_dut: If False we want to test back to back test on TG. :type always_same_link: bool + :type topo_has_dut: bool :raises RuntimeError: If not enough nodes for path. """ nodes = self._nodes - if len(nodes) < 2: + if len(nodes) < 2 and topo_has_dut: raise RuntimeError(u"Not enough nodes to compute path") for idx in range(0, len(nodes) - 1): topo = Topology() node1 = nodes[idx] - node2 = nodes[idx + 1] n1_list = self._nodes_filter[idx] - n2_list = self._nodes_filter[idx + 1] + if topo_has_dut: + node2 = nodes[idx + 1] + n2_list = self._nodes_filter[idx + 1] + else: + node2 = node1 + n2_list = n1_list + links = topo.get_active_connecting_links( node1, node2, filter_list_node1=n1_list, filter_list_node2=n2_list @@ -139,8 +146,11 @@ class NodePath: link = l_set[0] self._links.append(link) + + use_subsequent = not topo_has_dut interface1 = topo.get_interface_by_link_name(node1, link) - interface2 = topo.get_interface_by_link_name(node2, link) + interface2 = topo.get_interface_by_link_name(node2, link, + use_subsequent) self._path.append((interface1, node1)) self._path.append((interface2, node2)) @@ -208,7 +218,7 @@ class NodePath: return self._path[-2] def compute_circular_topology(self, nodes, filter_list=None, nic_pfs=1, - always_same_link=False, topo_has_tg=True): + always_same_link=False, topo_has_tg=True, topo_has_dut=True): """Return computed circular path. :param nodes: Nodes to append to the path. @@ -219,29 +229,34 @@ class NodePath: between two nodes if one link was used before. :param topo_has_tg: If True, the topology has a TG node. If False, the topology consists entirely of DUT nodes. + :param topo_has_dut: If True, the topology has a DUT node(s). If False, + the topology consists entirely of TG nodes. :type nodes: dict :type filter_list: list of strings :type nic_pfs: int :type always_same_link: bool :type topo_has_tg: bool + :type topo_has_dut: bool :returns: Topology information dictionary. :rtype: dict :raises RuntimeError: If unsupported combination of parameters. """ t_dict = dict() - duts = [key for key in nodes if u"DUT" in key] - t_dict[u"duts"] = duts - t_dict[u"duts_count"] = len(duts) - t_dict[u"int"] = u"pf" + if topo_has_dut: + duts = [key for key in nodes if u"DUT" in key] + t_dict[u"duts"] = duts + t_dict[u"duts_count"] = len(duts) + t_dict[u"int"] = u"pf" for _ in range(0, nic_pfs // 2): if topo_has_tg: self.append_node(nodes[u"TG"]) - for dut in duts: - self.append_node(nodes[dut], filter_list=filter_list) + if topo_has_dut: + for dut in duts: + self.append_node(nodes[dut], filter_list=filter_list) if topo_has_tg: self.append_node(nodes[u"TG"]) - self.compute_path(always_same_link) + self.compute_path(always_same_link, topo_has_dut) n_idx = 0 # node index t_idx = 1 # TG interface index @@ -257,7 +272,7 @@ class NodePath: i_pfx = f"if{t_idx}" # [backwards compatible] interface prefix n_idx = 0 t_idx = t_idx + 1 - elif topo_has_tg: + elif topo_has_tg and topo_has_dut: # Each node has 2 interfaces, starting with 1 # Calculate prefixes appropriately for current # path topology nomenclature: diff --git a/resources/libraries/python/autogen/Regenerator.py b/resources/libraries/python/autogen/Regenerator.py index 7003d3905d..14a9fd0ddb 100644 --- a/resources/libraries/python/autogen/Regenerator.py +++ b/resources/libraries/python/autogen/Regenerator.py @@ -195,6 +195,20 @@ def add_iperf3_testcases(testcase, file_out, tc_kwargs_list): file_out.write(testcase.generate(**kwargs)) +def add_trex_testcases(testcase, file_out, tc_kwargs_list): + """Add trex testcases to file. + + :param testcase: Testcase class. + :param file_out: File to write testcases to. + :param tc_kwargs_list: Key-value pairs used to construct testcases. + :type testcase: Testcase + :type file_out: file + :type tc_kwargs_list: dict + """ + for kwargs in tc_kwargs_list: + file_out.write(testcase.generate(**kwargs)) + + def write_default_files(in_filename, in_prolog, kwargs_list): """Using given filename and prolog, write all generated suites. @@ -480,6 +494,64 @@ def write_iperf3_files(in_filename, in_prolog, kwargs_list): add_iperf3_testcases(testcase, file_out, kwargs_list) +def write_trex_files(in_filename, in_prolog, kwargs_list): + """Using given filename and prolog, write all generated trex suites. + + :param in_filename: Template filename to derive real filenames from. + :param in_prolog: Template content to derive real content from. + :param kwargs_list: List of kwargs for add_trex_testcase. + :type in_filename: str + :type in_prolog: str + :type kwargs_list: list of dict + """ + for suite_type in Constants.PERF_TYPE_TO_KEYWORD: + tmp_filename = replace_defensively( + in_filename, u"ndrpdr", suite_type, 1, + u"File name should contain suite type once.", in_filename + ) + tmp_prolog = replace_defensively( + in_prolog, u"ndrpdr".upper(), suite_type.upper(), 1, + u"Suite type should appear once in uppercase (as tag).", + in_filename + ) + tmp_prolog = replace_defensively( + tmp_prolog, + u"Find NDR and PDR intervals using optimized search", + Constants.PERF_TYPE_TO_KEYWORD[suite_type], 1, + u"Main search keyword should appear once in suite.", + in_filename + ) + tmp_prolog = replace_defensively( + tmp_prolog, + Constants.PERF_TYPE_TO_SUITE_DOC_VER[u"ndrpdr"], + Constants.PERF_TYPE_TO_SUITE_DOC_VER[suite_type], + 1, u"Exact suite type doc not found.", in_filename + ) + tmp_prolog = replace_defensively( + tmp_prolog, + Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[u"ndrpdr"], + Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[suite_type], + 1, u"Exact template type doc not found.", in_filename + ) + _, suite_id, suite_tag = get_iface_and_suite_ids(tmp_filename) + testcase = Testcase.trex(suite_id) + for nic_name in Constants.NIC_NAME_TO_CODE: + out_filename = replace_defensively( + tmp_filename, u"10ge2p1x710", + Constants.NIC_NAME_TO_CODE[nic_name], 1, + u"File name should contain NIC code once.", in_filename + ) + out_prolog = replace_defensively( + tmp_prolog, u"Intel-X710", nic_name, 2, + u"NIC name should appear twice (tag and variable).", + in_filename + ) + check_suite_tag(suite_tag, out_prolog) + with open(out_filename, u"wt") as file_out: + file_out.write(out_prolog) + add_trex_testcases(testcase, file_out, kwargs_list) + + def write_device_files(in_filename, in_prolog, kwargs_list): """Using given filename and prolog, write all generated suites. @@ -637,6 +709,13 @@ class Regenerator: {u"frame_size": min_frame_size, u"phy_cores": 0} ] + trex_kwargs_list = [ + {u"frame_size": min_frame_size}, + {u"frame_size": 1518}, + {u"frame_size": 9000}, + {u"frame_size": u"IMIX_v4_1"} + ] + for in_filename in glob(pattern): if not self.quiet: print( @@ -656,6 +735,9 @@ class Regenerator: in_prolog = u"".join( file_in.read().partition(u"*** Test Cases ***")[:-1] ) + if "-tg-" in in_filename: + write_trex_files(in_filename, in_prolog, trex_kwargs_list) + continue if in_filename.endswith(u"-ndrpdr.robot"): if u"scheduler" in in_filename: write_default_files( diff --git a/resources/libraries/python/autogen/Testcase.py b/resources/libraries/python/autogen/Testcase.py index 643d32a3cb..32fc5014cc 100644 --- a/resources/libraries/python/autogen/Testcase.py +++ b/resources/libraries/python/autogen/Testcase.py @@ -33,16 +33,17 @@ class Testcase: """ self.template = Template(template_string) - def generate(self, frame_size, phy_cores): + def generate(self, frame_size, phy_cores=None): """Return string of test case code with placeholders filled. Fail if there are placeholders left unfilled. It is not required for all placeholders to be present in template. :param frame_size: Imix string or numeric frame size. Example: 74. - :param phy_cores: Number of physical cores to use. Example: 2. + :param phy_cores: Number of physical cores to use. Example: 2. It can + be None in n2n testcases. :type frame_size: str or int - :type phy_cores: int or str + :type phy_cores: int, str or None :returns: Filled template, usable as test case code. :rtype: str """ @@ -57,6 +58,8 @@ class Testcase: u"frame_num": str(frame_size), u"frame_str": u"IMIX" } + if phy_cores is None: + return self.template.substitute(subst_dict) cores_str = str(phy_cores) cores_num = int(cores_str) subst_dict.update( @@ -136,3 +139,21 @@ class Testcase: | | frame_size=${{frame_num}} | phy_cores=${{cores_num}} ''' return cls(template_string) + + @classmethod + def trex(cls, suite_id): + """Factory method for creating "trex" testcase objects. + + Testcase name will contain frame size, but not core count. + + :param suite_id: Part of suite name to distinguish from other suites. + :type suite_id: str + :returns: Instance for generating testcase text of this type. + :rtype: Testcase + """ + template_string = f''' +| ${{frame_str}}--{suite_id} +| | [Tags] | ${{frame_str}} +| | frame_size=${{frame_num}} +''' + return cls(template_string) diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index e05f4ef732..fba2dcb189 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -377,16 +377,19 @@ class Topology: return links @staticmethod - def _get_interface_by_key_value(node, key, value): + def _get_interface_by_key_value(node, key, value, subsequent=False): """Return node interface key from topology file according to key and value. :param node: The node dictionary. :param key: Key by which to select the interface. :param value: Value that should be found using the key. + :param subsequent: Use second interface of the link. Useful for + back-to-back links. Default: False :type node: dict :type key: string :type value: string + :type subsequent: bool :returns: Interface key from topology file :rtype: string """ @@ -396,8 +399,11 @@ class Topology: k_val = if_val.get(key) if k_val is not None: if k_val == value: - retval = if_key - break + if subsequent: + subsequent = False + else: + retval = if_key + break return retval @staticmethod @@ -417,7 +423,7 @@ class Topology: return Topology._get_interface_by_key_value(node, u"name", iface_name) @staticmethod - def get_interface_by_link_name(node, link_name): + def get_interface_by_link_name(node, link_name, subsequent=False): """Return interface key of link on node. This method returns the interface name associated with a given link @@ -425,12 +431,17 @@ class Topology: :param node: The node topology dictionary. :param link_name: Name of the link that a interface is connected to. + :param subsequent: Use second interface of the link. Useful for + back-to-back links. Default: False :type node: dict :type link_name: string + :type subsequent: bool :returns: Interface key of the interface connected to the given link. :rtype: str """ - return Topology._get_interface_by_key_value(node, u"link", link_name) + return Topology._get_interface_by_key_value( + node, u"link", link_name, subsequent=subsequent + ) def get_interfaces_by_link_names(self, node, link_names): """Return dictionary of dictionaries {"interfaceN", interface name}. @@ -836,13 +847,15 @@ class Topology: return None @staticmethod - def _get_node_active_link_names(node, filter_list=None): + def _get_node_active_link_names(node, filter_list=None, topo_has_dut=True): """Return list of link names that are other than mgmt links. :param node: Node topology dictionary. :param filter_list: Link filter criteria. + :param topo_has_dut: Whether we require back-to-back links. :type node: dict :type filter_list: list of strings + :type topo_has_dut: bool :returns: List of link names occupied by the node. :rtype: None or list of string """ @@ -862,6 +875,17 @@ class Topology: link_names.append(interface[u"link"]) if not link_names: link_names = None + if not topo_has_dut: + new_link_names = list() + for link_name in link_names: + count = 0 + for interface in interfaces.values(): + link = interface.get(u"link", None) + if link == link_name: + count += 1 + if count == 2: + new_link_names.append(link_name) + link_names = new_link_names return link_names def get_active_connecting_links( @@ -880,12 +904,19 @@ class Topology: :rtype: list """ - node1_links = self._get_node_active_link_names( - node1, filter_list=filter_list_node1 - ) - node2_links = self._get_node_active_link_names( - node2, filter_list=filter_list_node2 - ) + if node1 != node2: + node1_links = self._get_node_active_link_names( + node1, filter_list=filter_list_node1 + ) + node2_links = self._get_node_active_link_names( + node2, filter_list=filter_list_node2 + ) + else: + # Looking for back-to-back links. + node1_links = self._get_node_active_link_names( + node1, filter_list=filter_list_node1, topo_has_dut=False + ) + node2_links = node1_links connecting_links = None if node1_links is None: |