summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfmenguy <francoisregis.menguy@orange.com>2020-04-21 18:26:41 +0200
committerfmenguy <francoisregis.menguy@orange.com>2021-03-30 09:12:15 +0200
commit8755c892f6cfbfb8ca4f3405675dfe770c769605 (patch)
treee54372ba440ea7f9c1815a31936b3d3d97fe25d4
parentc3e9d0fc0076f0a2930f13366255b0e8e65fb814 (diff)
NFVBENCH-163: Add gratuitous ARP in case of L3 router mode
Change-Id: Iec2b186176285f723eb2685319c55e6cd6d33a8a Signed-off-by: fmenguy <francoisregis.menguy@orange.com>
-rw-r--r--docs/testing/user/userguide/pvpl3.rst3
-rw-r--r--nfvbench/cfg.default.yaml8
-rw-r--r--nfvbench/chain_runner.py5
-rw-r--r--nfvbench/nfvbench.py6
-rw-r--r--nfvbench/summarizer.py5
-rwxr-xr-xnfvbench/traffic_client.py19
-rw-r--r--nfvbench/traffic_gen/dummy.py2
-rw-r--r--nfvbench/traffic_gen/trex_gen.py81
-rw-r--r--test/mock_trex.py2
-rw-r--r--test/test_nfvbench.py5
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):