From: pmikus Date: Fri, 13 Oct 2023 12:34:04 +0000 (+0000) Subject: feat(core): Multilink TRex Sync mode II X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=039a8a325bc24cd8d7c5770d88a20ce517197fe9 feat(core): Multilink TRex Sync mode II Signed-off-by: Peter Mikus Change-Id: Idf4df372520d4a72c0738a89290d16710485f140 --- diff --git a/GPL/tools/trex/trex_stl_profile.py b/GPL/tools/trex/trex_stl_profile.py index c318674899..ac53e90571 100644 --- a/GPL/tools/trex/trex_stl_profile.py +++ b/GPL/tools/trex/trex_stl_profile.py @@ -74,8 +74,7 @@ def simple_burst( duration, framesize, rate, - port_0, - port_1, + ports, latency, async_start=False, traffic_directions=2, @@ -103,8 +102,7 @@ def simple_burst( :param framesize: Frame size. :param duration: Duration of traffic run in seconds (-1=infinite). :param rate: Traffic rate [percentage, pps, bps]. - :param port_0: Port 0 on the traffic generator. - :param port_1: Port 1 on the traffic generator. + :param ports: Port list on the traffic generator. :param latency: With latency stats. :param async_start: Start the traffic and exit. :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic. @@ -114,8 +112,7 @@ def simple_burst( :type framesize: int or str :type duration: float :type rate: str - :type port_0: int - :type port_1: int + :type ports: list :type latency: bool :type async_start: bool :type traffic_directions: int @@ -153,38 +150,47 @@ def simple_burst( if "macsrc" in profile_file: client.set_port_attr(promiscuous=True) if isinstance(framesize, int): - last_stream_a = int((len(streams) - 2) / 2) - last_stream_b = (last_stream_a * 2) - client.add_streams(streams[0:last_stream_a], ports=[port_0]) + mark_a = len(streams) // 4 + mark_b = len(streams) // 2 + for i,j in zip(streams[:mark_a], ports[::2]): + client.add_streams(streams=[i], ports=[j]) if traffic_directions > 1: - client.add_streams( - streams[last_stream_a:last_stream_b], ports=[port_1]) + for i,j in zip(streams[mark_a:mark_b], ports[1::2]): + print(i, j) + client.add_streams(streams=[i], ports=[j]) elif isinstance(framesize, str): - client.add_streams(streams[0:3], ports=[port_0]) + mark = 0 + for i in ports[::2]: + client.add_streams(streams=streams[mark:mark+3], ports=[i]) + mark = mark + 3 if traffic_directions > 1: - client.add_streams(streams[3:6], ports=[port_1]) + mark = len(streams) // 2 + for i in ports[1::2]: + client.add_streams(streams=streams[mark:mark+3], ports=[i]) + mark = mark + 3 if latency: try: if isinstance(framesize, int): - client.add_streams(streams[last_stream_b], ports=[port_0]) + mark_c = len(streams) // 2 + mark_d = len(streams) // 2 + len(streams) // 4 + for i,j in zip(streams[mark_c:mark_d], ports[::2]): + client.add_streams(streams=[i], ports=[j]) if traffic_directions > 1: - client.add_streams( - streams[last_stream_b + 1], ports=[port_1]) + for i,j in zip(streams[mark_d:], ports[1::2]): + client.add_streams(streams=[i], ports=[j]) elif isinstance(framesize, str): latency = False except STLError: # Disable latency if NIC does not support requested stream type print("##### FAILED to add latency streams #####") latency = False - # Even for unidir, both ports are needed to see both rx and tx. - ports = [port_0, port_1] # Clear the stats before injecting: client.clear_stats() # Choose rate and start traffic: client.start( - ports=ports[:traffic_directions], + ports=ports[::] if traffic_directions == 2 else ports[::2], mult=rate, duration=duration, force=force, @@ -286,12 +292,8 @@ def main(): help="Traffic rate with included units (pps)." ) parser.add_argument( - "--port_0", required=True, type=int, - help="Port 0 on the traffic generator." - ) - parser.add_argument( - "--port_1", required=True, type=int, - help="Port 1 on the traffic generator." + "--ports", required=True, type=int, nargs="+", + help="Port list on the traffic generator." ) parser.add_argument( "--async_start", action="store_true", default=False, @@ -326,8 +328,7 @@ def main(): duration=args.duration, framesize=framesize, rate=args.rate, - port_0=args.port_0, - port_1=args.port_1, + ports=args.ports, latency=args.latency, async_start=args.async_start, traffic_directions=args.traffic_directions, diff --git a/GPL/traffic_profiles/trex/profile_trex_stateless_scale_class.py b/GPL/traffic_profiles/trex/profile_trex_stateless_scale_class.py new file mode 100644 index 0000000000..33cf090da2 --- /dev/null +++ b/GPL/traffic_profiles/trex/profile_trex_stateless_scale_class.py @@ -0,0 +1,196 @@ +# Copyright (c) 2023 Cisco and/or its affiliates. +# +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +# +# Licensed under the Apache License 2.0 or +# GNU General Public License v2.0 or later; you may not use this file +# except in compliance with one of these Licenses. You +# may obtain a copy of the Licenses at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html +# +# Note: If this file is linked with Scapy, which is GPLv2+, your use of it +# must be under GPLv2+. If at any point in the future it is no longer linked +# with Scapy (or other GPLv2+ licensed software), you are free to choose +# Apache 2. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Base class for stream profiles for T-rex traffic generator. +""" + +import socket +import struct + +from random import choice +from string import ascii_letters + +from trex.stl.api import * + + +class TrafficStreamsScaleClass: + """Base class for stream profiles for T-rex traffic generator.""" + + STREAM_TABLE = { + "IMIX_v4": [ + {"size": 60, "pps": 28, "isg": 0}, + {"size": 590, "pps": 20, "isg": 0.1}, + {"size": 1514, "pps": 4, "isg": 0.2} + ], + "IMIX_v4_1": [ + {"size": 64, "pps": 28, "isg": 0}, + {"size": 570, "pps": 16, "isg": 0.1}, + {"size": 1518, "pps": 4, "isg": 0.2} + ] + } + + def __init__(self): + # Default value of frame size, it will be overwritten by the value of + # "framesize" parameter of "get_streams" method. + self.framesize = 64 + + # If needed, add your own parameters. + + def _gen_payload(self, length): + """Generate payload. + + If needed, implement your own algorithm. + + :param length: Length of generated payload. + :type length: int + :returns: The generated payload. + :rtype: str + """ + payload = "" + for _ in range(length): + payload += choice(ascii_letters) + + return payload + + def _get_start_end_ipv6(self, start_ip, end_ip): + """Get start host and number of hosts from IPv6 as integer. + + :param start_ip: Start IPv6. + :param end_ip: End IPv6. + :type start_ip: string + :type end_ip: string + :return: Start host, number of hosts. + :rtype tuple of int + :raises: ValueError if start_ip is greater then end_ip. + :raises: socket.error if the IP addresses are not valid IPv6 addresses. + """ + try: + ip1 = socket.inet_pton(socket.AF_INET6, start_ip) + ip2 = socket.inet_pton(socket.AF_INET6, end_ip) + + hi1, lo1 = struct.unpack("!QQ", ip1) + hi2, lo2 = struct.unpack("!QQ", ip2) + + if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2): + raise ValueError("IPv6: start_ip is greater then end_ip") + + return lo1, abs(int(lo1) - int(lo2)) + + except socket.error as err: + print(err) + raise + + def define_packets(self): + """Define the packets to be sent from the traffic generator. + + This method MUST return: + + return base_pkt_a, base_pkt_b, vm1, vm2 + + vm1 and vm2 CAN be None. + + :returns: Packets to be sent from the traffic generator. + :rtype: tuple + """ + raise NotImplementedError + + def create_streams(self): + """Create traffic streams. + + Implement your own traffic streams. + + :returns: Traffic streams. + :rtype: list + """ + pkts, vms = self.define_packets() + + # Frame size is defined as an integer, e.g. 64, 1518: + if isinstance(self.framesize, int): + pkt_streams = list() + lat_streams = list() + for i in range(len(pkts)): + payload_len = max(0, self.framesize - len(pkts[i]) - 4) + + pkt = STLPktBuilder( + pkt=pkts[i] / self._gen_payload(payload_len), + vm=vms[i] + ) + pkt_lat = STLPktBuilder( + pkt=pkts[i] / self._gen_payload(payload_len), + vm=vms[i] + ) + pkt_streams.append( + STLStream( + packet=pkt, + isg=10.0 * (i // (len(pkts) // 2)), + mode=STLTXCont(pps=9000) + ) + ) + lat_streams.append( + STLStream( + packet=pkt_lat, + isg=10.0 * (i // (len(pkts) // 2)), + flow_stats=STLFlowLatencyStats(pg_id=i), + mode=STLTXCont(pps=9000) + ) + ) + + streams = list() + streams.extend(pkt_streams) + streams.extend(lat_streams) + return streams + + # Frame size is defined as a string, e.g.IMIX_v4_1: + elif isinstance(self.framesize, str): + pkt_streams = list() + for i in range(len(pkts)): + for stream in self.STREAM_TABLE[self.framesize]: + payload_len = max(0, stream["size"] - len(pkts[i]) - 4) + + pkt = STLPktBuilder( + pkt=pkts[i] / self._gen_payload(payload_len), + vm=vms[i] + ) + pkt_streams.append( + STLStream( + packet=pkt, + isg=stream["isg"], + mode=STLTXCont(pps=stream["pps"]) + ) + ) + return pkt_streams + + def get_streams(self, **kwargs): + """Get traffic streams created by "create_streams" method. + + If needed, add your own parameters. + + :param kwargs: Key-value pairs used by "create_streams" method while + creating streams. + :returns: Traffic streams. + :rtype: list + """ + self.framesize = kwargs["framesize"] + self.rate = kwargs["rate"] + + return self.create_streams() diff --git a/GPL/traffic_profiles/trex/trex-stl-ethip4-ip4dst10000p3.py b/GPL/traffic_profiles/trex/trex-stl-ethip4-ip4dst10000p3.py new file mode 100644 index 0000000000..01b6e3c55e --- /dev/null +++ b/GPL/traffic_profiles/trex/trex-stl-ethip4-ip4dst10000p3.py @@ -0,0 +1,135 @@ +# Copyright (c) 2023 Cisco and/or its affiliates. +# +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +# +# Licensed under the Apache License 2.0 or +# GNU General Public License v2.0 or later; you may not use this file +# except in compliance with one of these Licenses. You +# may obtain a copy of the Licenses at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html +# +# Note: If this file is linked with Scapy, which is GPLv2+, your use of it +# must be under GPLv2+. If at any point in the future it is no longer linked +# with Scapy (or other GPLv2+ licensed software), you are free to choose +# Apache 2. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Stream profile for T-rex traffic generator. + +Stream profile: + - Three parallel bi-directional streams sent as W --> E and E --> W + at the same time. + - Packet: ETH / IP / +""" + +from trex.stl.api import * +from profile_trex_stateless_scale_class import TrafficStreamsScaleClass + + +class TrafficStreams(TrafficStreamsScaleClass): + """Stream profile.""" + + def __init__(self): + """Initialization and setting of streams' parameters.""" + + super(TrafficStreamsScaleClass, self).__init__() + + self.pkt_data = [ + # Direction W --> E: + { + "src_start_ip": "10.0.0.1", + "dst_start_ip": "20.0.0.0", + "dst_end_ip": "20.0.39.15" + }, + # Direction W --> E: + { + "src_start_ip": "30.0.0.1", + "dst_start_ip": "40.0.0.0", + "dst_end_ip": "40.0.39.15" + }, + # Direction W --> E: + { + "src_start_ip": "50.0.0.1", + "dst_start_ip": "60.0.0.0", + "dst_end_ip": "60.0.39.15" + }, + # Direction E --> W: + { + "src_start_ip": "20.0.0.1", + "dst_start_ip": "10.0.0.0", + "dst_end_ip": "10.0.39.15" + }, + # Direction E --> W: + { + "src_start_ip": "40.0.0.1", + "dst_start_ip": "30.0.0.0", + "dst_end_ip": "30.0.39.15" + }, + # Direction E --> W: + { + "src_start_ip": "60.0.0.1", + "dst_start_ip": "50.0.0.0", + "dst_end_ip": "50.0.39.15" + } + + ] + self.pkt_base = [] + self.pkt_vm = [] + + def define_packets(self): + """Defines the packets to be sent from the traffic generator. + + Packet definition: | ETH | IP | + + :returns: Base packets to be sent and transformation function. + :rtype: tuple + """ + for i in range(len(self.pkt_data)): + self.pkt_base.append( + Ether() / + IP( + src=self.pkt_data[i]["src_start_ip"], + dst=self.pkt_data[i]["dst_start_ip"], + proto=61 + ) + ) + self.pkt_vm.append( + STLScVmRaw( + [ + STLVmFlowVar( + name="dst", + min_value=self.pkt_data[i]["dst_start_ip"], + max_value=self.pkt_data[i]["dst_end_ip"], + size=4, + op="inc" + ), + STLVmWrFlowVar( + fv_name="dst", + pkt_offset="IP.dst" + ), + STLVmFixIpv4( + offset="IP" + ) + ] + ) + ) + + return self.pkt_base, self.pkt_vm + + +def register(): + """Register this traffic profile to T-Rex. + + Do not change this function. + + :return: Traffic streams. + :rtype: Object + """ + return TrafficStreams() diff --git a/resources/libraries/python/TrafficGenerator.py b/resources/libraries/python/TrafficGenerator.py index f28c77856e..7c6c859448 100644 --- a/resources/libraries/python/TrafficGenerator.py +++ b/resources/libraries/python/TrafficGenerator.py @@ -143,6 +143,7 @@ class TrafficGenerator(AbstractMeasurer): self._mode = None # TG interface order mapping self._ifaces_reordered = False + self._ifaces = [] # Result holding fields, to be removed. self._result = None self._loss = None @@ -177,7 +178,7 @@ class TrafficGenerator(AbstractMeasurer): self.ramp_up_duration = None self.state_timeout = None # Transient data needed for async measurements. - self._xstats = () + self._xstats = [] @property def node(self): @@ -319,23 +320,38 @@ class TrafficGenerator(AbstractMeasurer): ) ) - trex_topology.append( - dict( - interface=topology[f"TG_pf{link}"][0], - dst_mac=tg_if1_adj_addr - ) - ) - trex_topology.append( - dict( - interface=topology[f"TG_pf{link+1}"][0], - dst_mac=tg_if2_adj_addr - ) - ) if1_pci = topology[f"TG_pf{link}_pci"][0] if2_pci = topology[f"TG_pf{link+1}_pci"][0] if min(if1_pci, if2_pci) != if1_pci: - self._ifaces_reordered = True - trex_topology.reverse() + self._ifaces.append(str(link)) + self._ifaces.append(str(link-1)) + trex_topology.append( + dict( + interface=topology[f"TG_pf{link+1}"][0], + dst_mac=tg_if2_adj_addr + ) + ) + trex_topology.append( + dict( + interface=topology[f"TG_pf{link}"][0], + dst_mac=tg_if1_adj_addr + ) + ) + else: + self._ifaces.append(str(link-1)) + self._ifaces.append(str(link)) + trex_topology.append( + dict( + interface=topology[f"TG_pf{link}"][0], + dst_mac=tg_if1_adj_addr + ) + ) + trex_topology.append( + dict( + interface=topology[f"TG_pf{link+1}"][0], + dst_mac=tg_if2_adj_addr + ) + ) TrexConfig.add_startup_configuration( self._node, trex_topology @@ -362,18 +378,18 @@ class TrafficGenerator(AbstractMeasurer): if subtype == NodeSubTypeTG.TREX: for _ in range(0, 3): # Kill TRex only if it is already running. - cmd = u"sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\"" + cmd = "sh -c \"pgrep t-rex && pkill t-rex && sleep 3 || true\"" exec_cmd_no_error( - tg_node, cmd, sudo=True, message=u"Kill TRex failed!" + tg_node, cmd, sudo=True, message="Kill TRex failed!" ) # Prepare interfaces for TRex. tg_port_drv = Constants.TREX_PORT_DRIVER - mlx_driver = u"" - for port in tg_node[u"interfaces"].values(): - if u"Mellanox" in port.get(u"model"): - mlx_driver = port.get(u"driver") - pci_addr = port.get(u'pci_address') + mlx_driver = "" + for port in tg_node["interfaces"].values(): + if "Mellanox" in port.get("model"): + mlx_driver = port.get("driver") + pci_addr = port.get("pci_address") cur_driver = DS.get_pci_dev_driver(tg_node, pci_addr) if cur_driver == mlx_driver: pass @@ -383,7 +399,7 @@ class TrafficGenerator(AbstractMeasurer): DS.pci_driver_unbind(tg_node, pci_addr) DS.pci_driver_bind(tg_node, pci_addr, mlx_driver) else: - pci_addr = port.get(u'pci_address') + pci_addr = port.get("pci_address") cur_driver = DS.get_pci_dev_driver(tg_node, pci_addr) if cur_driver: DS.pci_driver_unbind(tg_node, pci_addr) @@ -391,12 +407,12 @@ class TrafficGenerator(AbstractMeasurer): # Start TRex. cd_cmd = f"cd '{Constants.TREX_INSTALL_DIR}/scripts/'" - trex_cmd = OptionString([u"nohup", u"./t-rex-64"]) - trex_cmd.add(u"-i") - trex_cmd.add(u"--prefix $(hostname)") - trex_cmd.add(u"--hdrh") - trex_cmd.add(u"--no-scapy-server") - trex_cmd.add_if(u"--astf", osi_layer == u"L7") + trex_cmd = OptionString(["nohup", "./t-rex-64"]) + trex_cmd.add("-i") + trex_cmd.add("--prefix $(hostname)") + trex_cmd.add("--hdrh") + trex_cmd.add("--no-scapy-server") + trex_cmd.add_if("--astf", osi_layer == "L7") # OptionString does not create double space if extra is empty. trex_cmd.add(f"{Constants.TREX_EXTRA_CMDLINE}") inner_command = f"{cd_cmd} && {trex_cmd} > /tmp/trex.log 2>&1 &" @@ -404,33 +420,33 @@ class TrafficGenerator(AbstractMeasurer): try: exec_cmd_no_error(tg_node, cmd, sudo=True) except RuntimeError: - cmd = u"sh -c \"cat /tmp/trex.log\"" + cmd = "sh -c \"cat /tmp/trex.log\"" exec_cmd_no_error( tg_node, cmd, sudo=True, - message=u"Get TRex logs failed!" + message="Get TRex logs failed!" ) - raise RuntimeError(u"Start TRex failed!") + raise RuntimeError("Start TRex failed!") # Test T-Rex API responsiveness. cmd = f"python3 {Constants.REMOTE_FW_DIR}/GPL/tools/trex/" - if osi_layer in (u"L2", u"L3"): - cmd += u"trex_stl_assert.py" - elif osi_layer == u"L7": - cmd += u"trex_astf_assert.py" + if osi_layer in ("L2", "L3"): + cmd += "trex_stl_assert.py" + elif osi_layer == "L7": + cmd += "trex_astf_assert.py" else: - raise ValueError(u"Unknown OSI layer!") + raise ValueError("Unknown OSI layer!") try: exec_cmd_no_error( tg_node, cmd, sudo=True, - message=u"T-Rex API is not responding!", retries=20 + message="T-Rex API is not responding!", retries=20 ) except RuntimeError: continue return # After max retries TRex is still not responding to API critical # error occurred. - exec_cmd(tg_node, u"cat /tmp/trex.log", sudo=True) - raise RuntimeError(u"Start T-Rex failed after multiple retries!") + exec_cmd(tg_node, "cat /tmp/trex.log", sudo=True) + raise RuntimeError("Start T-Rex failed after multiple retries!") @staticmethod def is_trex_running(node): @@ -441,7 +457,7 @@ class TrafficGenerator(AbstractMeasurer): :returns: True if T-Rex is running otherwise False. :rtype: bool """ - ret, _, _ = exec_cmd(node, u"pgrep t-rex", sudo=True) + ret, _, _ = exec_cmd(node, "pgrep t-rex", sudo=True) return bool(int(ret) == 0) @staticmethod @@ -718,38 +734,36 @@ class TrafficGenerator(AbstractMeasurer): :raises RuntimeError: In case of T-Rex driver issue. """ self.check_mode(TrexMode.STL) - p_0, p_1 = (1, 0) if self._ifaces_reordered else (0, 1) if not isinstance(duration, (float, int)): duration = float(duration) duration, _ = self._compute_duration(duration=duration, multiplier=rate) - command_line = OptionString().add(u"python3") + command_line = OptionString().add("python3") dirname = f"{Constants.REMOTE_FW_DIR}/GPL/tools/trex" command_line.add(f"'{dirname}/trex_stl_profile.py'") - command_line.change_prefix(u"--") + command_line.change_prefix("--") dirname = f"{Constants.REMOTE_FW_DIR}/GPL/traffic_profiles/trex" command_line.add_with_value( - u"profile", f"'{dirname}/{self.traffic_profile}.py'" + "profile", f"'{dirname}/{self.traffic_profile}.py'" ) - command_line.add_with_value(u"duration", f"{duration!r}") - command_line.add_with_value(u"frame_size", self.frame_size) - command_line.add_with_value(u"rate", f"{rate!r}") - command_line.add_with_value(u"port_0", p_0) - command_line.add_with_value(u"port_1", p_1) + command_line.add_with_value("duration", f"{duration!r}") + command_line.add_with_value("frame_size", self.frame_size) + command_line.add_with_value("rate", f"{rate!r}") + command_line.add_with_value("ports", " ".join(self._ifaces)) command_line.add_with_value( - u"traffic_directions", self.traffic_directions + "traffic_directions", self.traffic_directions ) - command_line.add_if(u"async_start", async_call) - command_line.add_if(u"latency", self.use_latency) - command_line.add_if(u"force", Constants.TREX_SEND_FORCE) - command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY) + command_line.add_if("async_start", async_call) + command_line.add_if("latency", self.use_latency) + command_line.add_if("force", Constants.TREX_SEND_FORCE) + command_line.add_with_value("delay", Constants.PERF_TRIAL_STL_DELAY) self._start_time = time.monotonic() - self._rate = float(rate[:-3]) if u"pps" in rate else float(rate) + self._rate = float(rate[:-3]) if "pps" in rate else float(rate) stdout, _ = exec_cmd_no_error( self._node, command_line, timeout=int(duration) + 60, - message=u"T-Rex STL runtime error" + message="T-Rex STL runtime error" ) if async_call: