From ff1f49d9ba97ddfee3229907e3a344503e072578 Mon Sep 17 00:00:00 2001 From: Jan Gelety Date: Thu, 6 Aug 2020 03:34:31 +0200 Subject: Perf: NAT44 endpoint-dependent mode - udp, part I - continuation of https://gerrit.fd.io/r/c/csit/+/26898 as there was reached limit of changes (1000) Jira: CSIT-1711 - udp synthetic profiles w/o data packets - udp cps perf tests, phase I (no special "search cps" KW) Part I means that we are using MRR tests to collect traffic data until there is ready new CPS test type with corresponding algorithm. Change-Id: I0d30feb9ecf1d0bff937152656f8eb422f831378 Signed-off-by: Jan Gelety --- resources/libraries/python/Constants.py | 6 +- resources/libraries/python/IPUtil.py | 18 +++ resources/libraries/python/NATUtil.py | 148 ++++++++++++++++++----- resources/libraries/python/TrafficGenerator.py | 77 +++++++++--- resources/libraries/python/VppConfigGenerator.py | 13 +- 5 files changed, 212 insertions(+), 50 deletions(-) (limited to 'resources/libraries/python') diff --git a/resources/libraries/python/Constants.py b/resources/libraries/python/Constants.py index 90d60ad4e2..73d8b63a0b 100644 --- a/resources/libraries/python/Constants.py +++ b/resources/libraries/python/Constants.py @@ -187,13 +187,17 @@ class Constants: # TRex install directory TREX_INSTALL_DIR = u"/opt/trex-core-2.73" + # TODO: Find the right way how to use it in trex profiles + # TRex pcap files directory + TREX_PCAP_DIR = f"{TREX_INSTALL_DIR}/scripts/avl" + # TRex limit memory. TREX_LIMIT_MEMORY = get_int_from_env(u"TREX_LIMIT_MEMORY", 8192) # TRex number of cores TREX_CORE_COUNT = get_int_from_env(u"TREX_CORE_COUNT", 7) - # Trex force start regardles ports state + # Trex force start regardless ports state TREX_SEND_FORCE = get_pessimistic_bool_from_env(u"TREX_SEND_FORCE") # TRex extra commandline arguments diff --git a/resources/libraries/python/IPUtil.py b/resources/libraries/python/IPUtil.py index 946f2f7b72..10a231f9df 100644 --- a/resources/libraries/python/IPUtil.py +++ b/resources/libraries/python/IPUtil.py @@ -139,6 +139,24 @@ class IPUtil: PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib") PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib summary") + @staticmethod + def vpp_get_ip_table_summary(node): + """Get IPv4 FIB table summary on a VPP node. + + :param node: VPP node. + :type node: dict + """ + PapiSocketExecutor.run_cli_cmd(node, u"show ip fib summary") + + @staticmethod + def vpp_get_ip_table(node): + """Get IPv4 FIB table on a VPP node. + + :param node: VPP node. + :type node: dict + """ + PapiSocketExecutor.run_cli_cmd(node, u"show ip fib") + @staticmethod def vpp_get_ip_tables_prefix(node, address): """Get dump of all IP FIB tables on a VPP node. diff --git a/resources/libraries/python/NATUtil.py b/resources/libraries/python/NATUtil.py index 2d5c1c7b76..b43058b23f 100644 --- a/resources/libraries/python/NATUtil.py +++ b/resources/libraries/python/NATUtil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Cisco and/or its affiliates. +# Copyright (c) 2020 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: @@ -14,16 +14,17 @@ """NAT utilities library.""" from pprint import pformat -from socket import AF_INET, inet_pton from enum import IntEnum +from ipaddress import IPv4Address from robot.api import logger +from resources.libraries.python.Constants import Constants from resources.libraries.python.InterfaceUtil import InterfaceUtil from resources.libraries.python.PapiExecutor import PapiSocketExecutor -class NATConfigFlags(IntEnum): +class NatConfigFlags(IntEnum): """Common NAT plugin APIs""" NAT_IS_NONE = 0x00 NAT_IS_TWICE_NAT = 0x01 @@ -36,6 +37,13 @@ class NATConfigFlags(IntEnum): NAT_IS_EXT_HOST_VALID = 0x80 +class NatAddrPortAllocAlg(IntEnum): + """NAT Address and port assignment algorithms.""" + NAT_ALLOC_ALG_DEFAULT = 0 + NAT_ALLOC_ALG_MAP_E = 1 + NAT_ALLOC_ALG_PORT_RANGE = 2 + + class NATUtil: """This class defines the methods to set NAT.""" @@ -43,41 +51,42 @@ class NATUtil: pass @staticmethod - def set_nat44_interfaces(node, int_in, int_out): + def set_nat44_interface(node, interface, flag): """Set inside and outside interfaces for NAT44. :param node: DUT node. - :param int_in: Inside interface. - :param int_out: Outside interface. + :param interface: Inside interface. + :param flag: Interface NAT configuration flag name. :type node: dict - :type int_in: str - :type int_out: str + :type interface: str + :type flag: str """ cmd = u"nat44_interface_add_del_feature" - int_in_idx = InterfaceUtil.get_sw_if_index(node, int_in) - err_msg = f"Failed to set inside interface {int_in} for NAT44 " \ + err_msg = f"Failed to set {flag} interface {interface} for NAT44 " \ f"on host {node[u'host']}" args_in = dict( - sw_if_index=int_in_idx, + sw_if_index=InterfaceUtil.get_sw_if_index(node, interface), is_add=1, - flags=getattr(NATConfigFlags, u"NAT_IS_INSIDE").value + flags=getattr(NatConfigFlags, flag).value ) with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args_in).get_reply(err_msg) - int_out_idx = InterfaceUtil.get_sw_if_index(node, int_out) - err_msg = f"Failed to set outside interface {int_out} for NAT44 " \ - f"on host {node[u'host']}" - args_in = dict( - sw_if_index=int_out_idx, - is_add=1, - flags=getattr(NATConfigFlags, u"NAT_IS_OUTSIDE").value - ) + @staticmethod + def set_nat44_interfaces(node, int_in, int_out): + """Set inside and outside interfaces for NAT44. - with PapiSocketExecutor(node) as papi_exec: - papi_exec.add(cmd, **args_in).get_reply(err_msg) + :param node: DUT node. + :param int_in: Inside interface. + :param int_out: Outside interface. + :type node: dict + :type int_in: str + :type int_out: str + """ + NATUtil.set_nat44_interface(node, int_in, u"NAT_IS_INSIDE") + NATUtil.set_nat44_interface(node, int_out, u"NAT_IS_OUTSIDE") @staticmethod def set_nat44_deterministic(node, ip_in, subnet_in, ip_out, subnet_out): @@ -99,9 +108,9 @@ class NATUtil: f"on host {node[u'host']}" args_in = dict( is_add=True, - in_addr=inet_pton(AF_INET, str(ip_in)), + in_addr=IPv4Address(str(ip_in)).packed, in_plen=int(subnet_in), - out_addr=inet_pton(AF_INET, str(ip_out)), + out_addr=IPv4Address(str(ip_out)).packed, out_plen=int(subnet_out) ) @@ -109,26 +118,76 @@ class NATUtil: papi_exec.add(cmd, **args_in).get_reply(err_msg) @staticmethod - def show_nat(node): - """Show the NAT configuration and data. + def set_nat44_address_range( + node, start_ip, end_ip, vrf_id=Constants.BITWISE_NON_ZERO, + flag=u"NAT_IS_NONE"): + """Set NAT44 address range. + + :param node: DUT node. + :param start_ip: IP range start. + :param end_ip: IP range end. + :param vrf_id: VRF index (Optional). + :param flag: NAT flag name. + :type node: dict + :type start_ip: str + :type end_ip: str + :type vrf_id: int + :type flag: str + """ + cmd = u"nat44_add_del_address_range" + err_msg = f"Failed to set NAT44 address range on host {node[u'host']}" + args_in = dict( + is_add=True, + first_ip_address=IPv4Address(str(start_ip)).packed, + last_ip_address=IPv4Address(str(end_ip)).packed, + vrf_id=vrf_id, + flags=getattr(NatConfigFlags, flag).value + ) + + with PapiSocketExecutor(node) as papi_exec: + papi_exec.add(cmd, **args_in).get_reply(err_msg) + + @staticmethod + def show_nat_config(node): + """Show the NAT configuration. + + :param node: DUT node. + :type node: dict + """ + cmd = u"nat_show_config" + err_msg = f"Failed to get NAT configuration on host {node[u'host']}" + + with PapiSocketExecutor(node) as papi_exec: + reply = papi_exec.add(cmd).get_reply(err_msg) + + logger.debug(f"NAT Configuration:\n{pformat(reply)}") + + @staticmethod + def show_nat44_summary(node): + """Show NAT44 summary on the specified topology node. + + :param node: Topology node. + :type node: dict + """ + PapiSocketExecutor.run_cli_cmd(node, u"show nat44 summary") + + @staticmethod + def show_nat_base_data(node): + """Show the NAT base data. Used data sources: - nat_show_config nat_worker_dump nat44_interface_addr_dump nat44_address_dump nat44_static_mapping_dump - nat44_user_dump nat44_interface_dump - nat44_user_session_dump - nat_det_map_dump :param node: DUT node. :type node: dict """ cmd = u"nat_show_config" - err_msg = f"Failed to get NAT configuration on host {node[u'host']}" + err_msg = f"Failed to get NAT base data on host {node[u'host']}" with PapiSocketExecutor(node) as papi_exec: reply = papi_exec.add(cmd).get_reply(err_msg) @@ -140,9 +199,32 @@ class NATUtil: u"nat44_interface_addr_dump", u"nat44_address_dump", u"nat44_static_mapping_dump", - u"nat44_user_dump", u"nat44_interface_dump", + ] + PapiSocketExecutor.dump_and_log(node, cmds) + + @staticmethod + def show_nat_user_data(node): + """Show the NAT user data. + + Used data sources: + + nat44_user_dump + nat44_user_session_dump + + :param node: DUT node. + :type node: dict + """ + cmd = u"nat_show_config" + err_msg = f"Failed to get NAT user data on host {node[u'host']}" + + with PapiSocketExecutor(node) as papi_exec: + reply = papi_exec.add(cmd).get_reply(err_msg) + + logger.debug(f"NAT Configuration:\n{pformat(reply)}") + + cmds = [ + u"nat44_user_dump", u"nat44_user_session_dump", - u"nat_det_map_dump" ] PapiSocketExecutor.dump_and_log(node, cmds) diff --git a/resources/libraries/python/TrafficGenerator.py b/resources/libraries/python/TrafficGenerator.py index 119702c15b..54d0fbd502 100644 --- a/resources/libraries/python/TrafficGenerator.py +++ b/resources/libraries/python/TrafficGenerator.py @@ -466,9 +466,9 @@ class TrafficGenerator(AbstractMeasurer): if subtype == NodeSubTypeTG.TREX: # Last line from console output line = stdout.splitlines()[-1] - results = line.split(",") - if results[-1] == u" ": - results.remove(u" ") + results = line.split(u",") + if results[-1] in (u" ", u""): + results.pop(-1) self._result = dict() for result in results: key, value = result.split(u"=", maxsplit=1) @@ -490,22 +490,48 @@ class TrafficGenerator(AbstractMeasurer): self._result.get(u"client_active_flows") self._l7_data[u"client"][u"established_flows"] = \ self._result.get(u"client_established_flows") + self._l7_data[u"client"][u"err_rx_throttled"] = \ + self._result.get(u"client_err_rx_throttled") + self._l7_data[u"client"][u"err_c_nf_throttled"] = \ + self._result.get(u"client_err_nf_throttled") + self._l7_data[u"client"][u"err_flow_overflow"] = \ + self._result.get(u"client_err_flow_overflow") self._l7_data[u"server"] = dict() self._l7_data[u"server"][u"active_flows"] = \ self._result.get(u"server_active_flows") self._l7_data[u"server"][u"established_flows"] = \ self._result.get(u"server_established_flows") + self._l7_data[u"server"][u"err_rx_throttled"] = \ + self._result.get(u"client_err_rx_throttled") if u"udp" in self.traffic_profile: self._l7_data[u"client"][u"udp"] = dict() self._l7_data[u"client"][u"udp"][u"established_flows"] = \ self._result.get(u"client_udp_connects") self._l7_data[u"client"][u"udp"][u"closed_flows"] = \ self._result.get(u"client_udp_closed") + self._l7_data[u"client"][u"udp"][u"tx_bytes"] = \ + self._result.get(u"client_udp_tx_bytes") + self._l7_data[u"client"][u"udp"][u"rx_bytes"] = \ + self._result.get(u"client_udp_rx_bytes") + self._l7_data[u"client"][u"udp"][u"tx_packets"] = \ + self._result.get(u"client_udp_tx_packets") + self._l7_data[u"client"][u"udp"][u"rx_packets"] = \ + self._result.get(u"client_udp_rx_packets") + self._l7_data[u"client"][u"udp"][u"keep_drops"] = \ + self._result.get(u"client_udp_keep_drops") self._l7_data[u"server"][u"udp"] = dict() self._l7_data[u"server"][u"udp"][u"accepted_flows"] = \ self._result.get(u"server_udp_accepts") self._l7_data[u"server"][u"udp"][u"closed_flows"] = \ self._result.get(u"server_udp_closed") + self._l7_data[u"server"][u"udp"][u"tx_bytes"] = \ + self._result.get(u"server_udp_tx_bytes") + self._l7_data[u"server"][u"udp"][u"rx_bytes"] = \ + self._result.get(u"server_udp_rx_bytes") + self._l7_data[u"server"][u"udp"][u"tx_packets"] = \ + self._result.get(u"server_udp_tx_packets") + self._l7_data[u"server"][u"udp"][u"rx_packets"] = \ + self._result.get(u"server_udp_rx_packets") elif u"tcp" in self.traffic_profile: self._l7_data[u"client"][u"tcp"] = dict() self._l7_data[u"client"][u"tcp"][u"initiated_flows"] = \ @@ -514,6 +540,10 @@ class TrafficGenerator(AbstractMeasurer): self._result.get(u"client_tcp_connects") self._l7_data[u"client"][u"tcp"][u"closed_flows"] = \ self._result.get(u"client_tcp_closed") + self._l7_data[u"client"][u"tcp"][u"tx_bytes"] = \ + self._result.get(u"client_tcp_tx_bytes") + self._l7_data[u"client"][u"tcp"][u"rx_bytes"] = \ + self._result.get(u"client_tcp_rx_bytes") self._l7_data[u"server"][u"tcp"] = dict() self._l7_data[u"server"][u"tcp"][u"accepted_flows"] = \ self._result.get(u"server_tcp_accepts") @@ -521,6 +551,10 @@ class TrafficGenerator(AbstractMeasurer): self._result.get(u"server_tcp_connects") self._l7_data[u"server"][u"tcp"][u"closed_flows"] = \ self._result.get(u"server_tcp_closed") + self._l7_data[u"server"][u"tcp"][u"tx_bytes"] = \ + self._result.get(u"server_tcp_tx_bytes") + self._l7_data[u"server"][u"tcp"][u"rx_bytes"] = \ + self._result.get(u"server_tcp_rx_bytes") def trex_astf_stop_remote_exec(self, node): """Execute T-Rex ASTF script on remote node over ssh to stop running @@ -668,6 +702,7 @@ class TrafficGenerator(AbstractMeasurer): self._loss = None self._latency = None xstats = [None, None] + self._l7_data = dict() self._l7_data[u"client"] = dict() self._l7_data[u"client"][u"active_flows"] = None self._l7_data[u"client"][u"established_flows"] = None @@ -849,12 +884,11 @@ class TrafficGenerator(AbstractMeasurer): """ subtype = check_subtype(self._node) if subtype == NodeSubTypeTG.TREX: - self.set_rate_provider_defaults( - frame_size, traffic_profile, - traffic_directions=traffic_directions) + if self.traffic_profile != str(traffic_profile): + self.traffic_profile = str(traffic_profile) if u"trex-astf" in self.traffic_profile: self.trex_astf_start_remote_exec( - duration, int(rate), frame_size, traffic_profile, + duration, int(rate), frame_size, self.traffic_profile, async_call, latency, warmup_time, traffic_directions, tx_port, rx_port ) @@ -862,7 +896,7 @@ class TrafficGenerator(AbstractMeasurer): elif u"trex-sl" in self.traffic_profile: unit_rate_str = str(rate) + u"pps" self.trex_stl_start_remote_exec( - duration, unit_rate_str, frame_size, traffic_profile, + duration, unit_rate_str, frame_size, self.traffic_profile, async_call, latency, warmup_time, traffic_directions, tx_port, rx_port ) @@ -999,8 +1033,12 @@ class TrafficGenerator(AbstractMeasurer): # TG needs target Tr per stream, but reports aggregate Tx and Dx. unit_rate_int = transmit_rate / float(self.traffic_directions) self.send_traffic_on_tg( - duration, unit_rate_int, self.frame_size, self.traffic_profile, - warmup_time=self.warmup_time, latency=self.use_latency, + duration, + unit_rate_int, + self.frame_size, + self.traffic_profile, + warmup_time=self.warmup_time, + latency=self.use_latency, traffic_directions=self.traffic_directions ) return self.get_measurement_result(duration, transmit_rate) @@ -1019,7 +1057,7 @@ class OptimizedSearch: maximum_transmit_rate, packet_loss_ratio=0.005, final_relative_width=0.005, final_trial_duration=30.0, initial_trial_duration=1.0, number_of_intermediate_phases=2, - timeout=720.0, doublings=1, traffic_directions=2): + timeout=720.0, doublings=1, traffic_directions=2, latency=False): """Setup initialized TG, perform optimized search, return intervals. :param frame_size: Frame size identifier or value [B]. @@ -1044,6 +1082,8 @@ class OptimizedSearch: less stable tests might get better overal duration with 2 or more. :param traffic_directions: Traffic is bi- (2) or uni- (1) directional. Default: 2 + :param latency: Whether to measure latency during the trial. + Default: False. :type frame_size: str or int :type traffic_profile: str :type minimum_transmit_rate: float @@ -1056,6 +1096,7 @@ class OptimizedSearch: :type timeout: float :type doublings: int :type traffic_directions: int + :type latency: bool :returns: Structure containing narrowed down NDR and PDR intervals and their measurements. :rtype: NdrPdrResult @@ -1069,7 +1110,11 @@ class OptimizedSearch: u"resources.libraries.python.TrafficGenerator" ) tg_instance.set_rate_provider_defaults( - frame_size, traffic_profile, traffic_directions=traffic_directions) + frame_size, + traffic_profile, + traffic_directions=traffic_directions, + latency=latency + ) algorithm = MultipleLossRatioSearch( measurer=tg_instance, final_trial_duration=final_trial_duration, final_relative_width=final_relative_width, @@ -1130,8 +1175,12 @@ class OptimizedSearch: u"resources.libraries.python.TrafficGenerator" ) tg_instance.set_rate_provider_defaults( - frame_size, traffic_profile, traffic_directions=traffic_directions, - negative_loss=False, latency=latency) + frame_size, + traffic_profile, + traffic_directions=traffic_directions, + negative_loss=False, + latency=latency + ) algorithm = PLRsearch( measurer=tg_instance, trial_duration_per_trial=tdpt, packet_loss_ratio_target=plr_target, diff --git a/resources/libraries/python/VppConfigGenerator.py b/resources/libraries/python/VppConfigGenerator.py index f19294965a..68a4e22e6b 100644 --- a/resources/libraries/python/VppConfigGenerator.py +++ b/resources/libraries/python/VppConfigGenerator.py @@ -501,12 +501,21 @@ class VppConfigGenerator: self.add_config_item(self._nodeconfig, u"", path) def add_nat(self, value=u"deterministic"): - """Add NAT configuration. + """Add NAT mode configuration. :param value: NAT mode. :type value: str """ - path = [u"nat"] + path = [u"nat", value] + self.add_config_item(self._nodeconfig, u"", path) + + def add_nat_max_translations_per_thread(self, value): + """Add NAT max. translations per thread number configuration. + + :param value: NAT mode. + :type value: str + """ + path = [u"nat", u"max translations per thread"] self.add_config_item(self._nodeconfig, value, path) def add_nsim_poll_main_thread(self): -- cgit 1.2.3-korg