From 8755c892f6cfbfb8ca4f3405675dfe770c769605 Mon Sep 17 00:00:00 2001 From: fmenguy Date: Tue, 21 Apr 2020 18:26:41 +0200 Subject: NFVBENCH-163: Add gratuitous ARP in case of L3 router mode Change-Id: Iec2b186176285f723eb2685319c55e6cd6d33a8a Signed-off-by: fmenguy --- docs/testing/user/userguide/pvpl3.rst | 3 ++ nfvbench/cfg.default.yaml | 8 ++++ nfvbench/chain_runner.py | 5 ++- nfvbench/nfvbench.py | 6 +++ nfvbench/summarizer.py | 5 +++ nfvbench/traffic_client.py | 19 +++++++- nfvbench/traffic_gen/dummy.py | 2 +- nfvbench/traffic_gen/trex_gen.py | 81 ++++++++++++++++++++++++++++++++--- test/mock_trex.py | 2 + test/test_nfvbench.py | 5 ++- 10 files changed, 125 insertions(+), 11 deletions(-) diff --git a/docs/testing/user/userguide/pvpl3.rst b/docs/testing/user/userguide/pvpl3.rst index f2b3d51..003e4c1 100644 --- a/docs/testing/user/userguide/pvpl3.rst +++ b/docs/testing/user/userguide/pvpl3.rst @@ -64,3 +64,6 @@ Upon start, NFVbench will: Please note: ``l3_router`` option is also compatible with external routers. In this case NFVBench will use ``EXT`` chain. + +.. note:: Using a long NFVbench run test, end-to-end connectivity can be lost depending on ARP stale time SUT configuration. +To avoid this issue, activate Gratuitous ARP stream using ``--gratuitous-arp`` or ``-garp`` option. \ No newline at end of file diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index 4491097..28f84f4 100644 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -738,6 +738,14 @@ no_traffic: false # Can be overriden by --l3-router l3_router: false +# If l3_router is true and depending on ARP stale time SUT configuration +# Gratuitous ARP (GARP) from TG port to the router is needed to keep traffic up +# Default value: 1 packet per second +# This value needs to be defined inferior to SUT ARP stale time to avoid GARP packets drop +# in case of high load traffic +periodic_gratuitous_arp: false +gratuitous_arp_pps: 1 + # Test configuration # The rate pps for traffic going in reverse direction in case of unidirectional flow. Default to 1. diff --git a/nfvbench/chain_runner.py b/nfvbench/chain_runner.py index 7bb3bbc..f045528 100644 --- a/nfvbench/chain_runner.py +++ b/nfvbench/chain_runner.py @@ -155,7 +155,10 @@ class ChainRunner(object): if self.config.single_run: result['run_config'] = self.traffic_client.get_run_config(result) required = result['run_config']['direction-total']['orig']['rate_pps'] - actual = result['stats']['total_tx_rate'] + if self.config.periodic_gratuitous_arp: + actual = result['stats']['total_tx_rate'] + self.config.gratuitous_arp_pps + else: + actual = result['stats']['total_tx_rate'] warning = self.traffic_client.compare_tx_rates(required, actual) if warning is not None: result['run_config']['warning'] = warning diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py index 6315289..7acb783 100644 --- a/nfvbench/nfvbench.py +++ b/nfvbench/nfvbench.py @@ -405,6 +405,12 @@ def _parse_opts_from_cli(): action='store_true', help='Use L3 neutron routers to handle traffic') + parser.add_argument('-garp', '--gratuitous-arp', dest='periodic_gratuitous_arp', + default=None, + action='store_true', + help='Use gratuitous ARP to maintain session between TG ' + 'and L3 routers to handle traffic') + parser.add_argument('-0', '--no-traffic', dest='no_traffic', default=None, action='store_true', diff --git a/nfvbench/summarizer.py b/nfvbench/summarizer.py index b30ef23..a4b02a6 100644 --- a/nfvbench/summarizer.py +++ b/nfvbench/summarizer.py @@ -266,6 +266,11 @@ class NFVBenchSummarizer(Summarizer): # 'append' expects a single parameter => double parentheses self.ndr_pdr_header.append((str(percentile) + ' %ile lat.', Formatter.standard)) self.single_run_header.append((str(percentile) + ' %ile lat.', Formatter.standard)) + + if self.config.periodic_gratuitous_arp: + self.direction_keys.insert(2, 'garp-direction-total') + self.direction_names.insert(2, 'Gratuitous ARP') + # if sender is available initialize record if self.sender: self.__record_init() diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index ae8af8d..c349289 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -1069,6 +1069,9 @@ class TrafficClient(object): 'theoretical_tx_rate_bps': stats['theoretical_tx_rate_bps'], 'theoretical_tx_rate_pps': stats['theoretical_tx_rate_pps']} + if self.config.periodic_gratuitous_arp: + retDict['garp_total_tx_rate'] = stats['garp_total_tx_rate'] + tx_keys = ['total_pkts', 'total_pkt_bytes', 'pkt_rate', 'pkt_bit_rate'] rx_keys = tx_keys + ['dropped_pkts'] @@ -1367,12 +1370,25 @@ class TrafficClient(object): for idx, key in enumerate(["direction-forward", "direction-reverse"]): tx_rate = results["stats"][str(idx)]["tx"]["total_pkts"] / self.config.duration_sec rx_rate = results["stats"][str(1 - idx)]["rx"]["total_pkts"] / self.config.duration_sec + + orig_rate = self.run_config['rates'][idx] + if self.config.periodic_gratuitous_arp: + orig_rate['rate_pps'] = float( + orig_rate['rate_pps']) - self.config.gratuitous_arp_pps + r[key] = { - "orig": self.__convert_rates(self.run_config['rates'][idx]), + "orig": self.__convert_rates(orig_rate), "tx": self.__convert_rates({'rate_pps': tx_rate}), "rx": self.__convert_rates({'rate_pps': rx_rate}) } + if self.config.periodic_gratuitous_arp: + r['garp-direction-total'] = { + "orig": self.__convert_rates({'rate_pps': self.config.gratuitous_arp_pps * 2}), + "tx": self.__convert_rates({'rate_pps': results["stats"]["garp_total_tx_rate"]}), + "rx": self.__convert_rates({'rate_pps': 0}) + } + total = {} for direction in ['orig', 'tx', 'rx']: total[direction] = {} @@ -1380,6 +1396,7 @@ class TrafficClient(object): total[direction][unit] = sum([float(x[direction][unit]) for x in list(r.values())]) r['direction-total'] = total + return r def insert_interface_stats(self, pps_list): diff --git a/nfvbench/traffic_gen/dummy.py b/nfvbench/traffic_gen/dummy.py index 8a6d11a..95147ab 100644 --- a/nfvbench/traffic_gen/dummy.py +++ b/nfvbench/traffic_gen/dummy.py @@ -102,7 +102,7 @@ class DummyTG(AbstractTrafficGenerator): def clear_streamblock(self): pass - def get_stats(self, ifstats): + def get_stats(self, ifstats=None): """Get stats from current run. The binary search mainly looks at 2 results to make the decision: diff --git a/nfvbench/traffic_gen/trex_gen.py b/nfvbench/traffic_gen/trex_gen.py index d5625eb..41768b1 100644 --- a/nfvbench/traffic_gen/trex_gen.py +++ b/nfvbench/traffic_gen/trex_gen.py @@ -26,6 +26,7 @@ from itertools import count from scapy.contrib.mpls import MPLS # flake8: noqa # pylint: enable=import-error from nfvbench.log import LOG +from nfvbench.specs import ChainType from nfvbench.traffic_server import TRexTrafficServer from nfvbench.utils import cast_integer from nfvbench.utils import timeout @@ -35,6 +36,7 @@ from hdrh.histogram import HdrHistogram # pylint: disable=import-error from trex.common.services.trex_service_arp import ServiceARP +from trex.stl.api import ARP from trex.stl.api import bind_layers from trex.stl.api import CTRexVmInsFixHwCs from trex.stl.api import Dot1Q @@ -50,6 +52,7 @@ from trex.stl.api import STLPktBuilder from trex.stl.api import STLScVmRaw from trex.stl.api import STLStream from trex.stl.api import STLTXCont +from trex.stl.api import STLTXMultiBurst from trex.stl.api import STLVmFixChecksumHw from trex.stl.api import STLVmFixIpv4 from trex.stl.api import STLVmFlowVar @@ -156,6 +159,32 @@ class TRex(AbstractTrafficGenerator): self.__combine_latencies(in_stats, result[ph]['rx'], ph) total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts'] + + # in case of GARP packets we need to base total_tx_pkts value using flow_stats + # as no GARP packets have no flow stats and will not be received on the other port + if self.config.periodic_gratuitous_arp: + if not self.config.no_flow_stats and not self.config.no_latency_stats: + global_total_tx_pkts = total_tx_pkts + total_tx_pkts = 0 + if ifstats: + for chain_id, _ in enumerate(ifstats): + for ph in self.port_handle: + pg_id, lat_pg_id = self.get_pg_id(ph, chain_id) + flows_tx_pkts = in_stats['flow_stats'][pg_id]['tx_pkts']['total'] + \ + in_stats['flow_stats'][lat_pg_id]['tx_pkts']['total'] + result[ph]['tx']['total_pkts'] = flows_tx_pkts + total_tx_pkts += flows_tx_pkts + else: + for pg_id in in_stats['flow_stats']: + if pg_id != 'global': + total_tx_pkts += in_stats['flow_stats'][pg_id]['tx_pkts']['total'] + result["garp_total_tx_rate"] = cast_integer( + (global_total_tx_pkts - total_tx_pkts) / self.config.duration_sec) + else: + LOG.warning("Gratuitous ARP are not received by the other port so TRex and NFVbench" + " see these packets as dropped. Please do not activate no_flow_stats" + " and no_latency_stats properties to have a better drop rate.") + result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec) # actual offered tx rate in bps avg_packet_size = utils.get_average_packet_size(self.l2_frame_size) @@ -578,6 +607,22 @@ class TRex(AbstractTrafficGenerator): return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size))) + def _create_gratuitous_arp_pkt(self, stream_cfg): + """Create a GARP packet. + + """ + pkt_base = Ether(src=stream_cfg['mac_src'], dst="ff:ff:ff:ff:ff:ff") + + if self.config.vxlan or self.config.mpls: + pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan']) + elif stream_cfg['vlan_tag'] is not None: + pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag']) + + pkt_base /= ARP(psrc=stream_cfg['ip_src_tg_gw'], hwsrc=stream_cfg['mac_src'], + hwdst=stream_cfg['mac_src'], pdst=stream_cfg['ip_src_tg_gw']) + + return STLPktBuilder(pkt=pkt_base) + def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True, e2e=False): """Create a list of streams corresponding to a given chain and stream config. @@ -623,23 +668,31 @@ class TRex(AbstractTrafficGenerator): else: l2frame_size = int(l2frame) pkt = self._create_pkt(stream_cfg, l2frame_size) + if self.config.periodic_gratuitous_arp: + requested_pps = int(utils.parse_rate_str(self.rates[0])[ + 'rate_pps']) - self.config.gratuitous_arp_pps + if latency: + requested_pps -= self.LATENCY_PPS + stltx_cont = STLTXCont(pps=requested_pps) + else: + stltx_cont = STLTXCont() if e2e or stream_cfg['mpls']: streams.append(STLStream(packet=pkt, # Flow stats is disabled for MPLS now # flow_stats=STLFlowStats(pg_id=pg_id), - mode=STLTXCont())) + mode=stltx_cont)) else: if stream_cfg['vxlan'] is True: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id, vxlan=True) if not self.config.no_flow_stats else None, - mode=STLTXCont())) + mode=stltx_cont)) else: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id) if not self.config.no_flow_stats else None, - mode=STLTXCont())) + mode=stltx_cont)) # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging # without vlan, the min l2 frame size is 64 # with vlan it is 68 @@ -669,6 +722,18 @@ class TRex(AbstractTrafficGenerator): flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id) if not self.config.no_latency_stats else None, mode=STLTXCont(pps=self.LATENCY_PPS))) + + if self.config.periodic_gratuitous_arp and ( + self.config.l3_router or self.config.service_chain == ChainType.EXT): + # In case of L3 router feature or EXT chain with router + # and depending on ARP stale time SUT configuration + # Gratuitous ARP from TG port to the router is needed to keep traffic up + garp_pkt = self._create_gratuitous_arp_pkt(stream_cfg) + ibg = self.config.gratuitous_arp_pps * 1000000.0 + packets_count = int(self.config.duration_sec / self.config.gratuitous_arp_pps) + streams.append( + STLStream(packet=garp_pkt, + mode=STLTXMultiBurst(pkts_per_burst=1, count=packets_count, ibg=ibg))) return streams @timeout(5) @@ -986,7 +1051,11 @@ class TRex(AbstractTrafficGenerator): r = utils.convert_rates(l2frame_size, rates[0], intf_speed) total_rate = int(r['rate_pps']) # rate must be enough for latency stream and at least 1 pps for base stream per chain - required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult + if self.config.periodic_gratuitous_arp: + required_rate = (self.LATENCY_PPS + 1 + self.config.gratuitous_arp_pps) \ + * self.config.service_chain_count * mult + else: + required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult result = utils.convert_rates(l2frame_size, {'rate_pps': required_rate}, intf_speed * mult) @@ -1059,10 +1128,10 @@ class TRex(AbstractTrafficGenerator): self.client.reset(self.port_handle) LOG.info('Cleared all existing streams') - def get_stats(self, if_stats=None): + def get_stats(self, ifstats=None): """Get stats from Trex.""" stats = self.client.get_stats() - return self.extract_stats(stats, if_stats) + return self.extract_stats(stats, ifstats) def get_macs(self): """Return the Trex local port MAC addresses. diff --git a/test/mock_trex.py b/test/mock_trex.py index 4884c98..ac7daf1 100644 --- a/test/mock_trex.py +++ b/test/mock_trex.py @@ -43,6 +43,7 @@ except ImportError: api_mod.CTRexVmInsFixHwCs = STLDummy api_mod.Dot1Q = STLDummy api_mod.Ether = STLDummy + api_mod.ARP = STLDummy api_mod.IP = STLDummy api_mod.STLClient = STLDummy api_mod.STLFlowLatencyStats = STLDummy @@ -51,6 +52,7 @@ except ImportError: api_mod.STLScVmRaw = STLDummy api_mod.STLStream = STLDummy api_mod.STLTXCont = STLDummy + api_mod.STLTXMultiBurst = STLDummy api_mod.STLVmFixChecksumHw = STLDummy api_mod.STLVmFixIpv4 = STLDummy api_mod.STLVmFlowVar = STLDummy diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py index c772d7b..e53a586 100644 --- a/test/test_nfvbench.py +++ b/test/test_nfvbench.py @@ -1070,8 +1070,9 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1', 'no_flow_stats': False, 'no_latency_stats': False, 'no_latency_streams': False, - 'intf_speed': '10Gbps' - + 'intf_speed': '10Gbps', + 'periodic_gratuitous_arp': False, + 'gratuitous_arp_pps': 1 }) def _get_traffic_client(user_info=None): -- cgit 1.2.3-korg