From: "Juraj Linkeš" <juraj.linkes@pantheon.tech>
To: thomas@monjalon.net, david.marchand@redhat.com,
Honnappa.Nagarahalli@arm.com, ohilyard@iol.unh.edu,
lijuan.tu@intel.com
Cc: dev@dpdk.org, "Juraj Linkeš" <juraj.linkes@pantheon.tech>
Subject: [RFC PATCH v1 06/18] dts: merge DTS framework/pktgen_ixia.py to DPDK
Date: Wed, 6 Apr 2022 15:04:28 +0000 [thread overview]
Message-ID: <20220406150440.2914464-7-juraj.linkes@pantheon.tech> (raw)
In-Reply-To: <20220406150440.2914464-1-juraj.linkes@pantheon.tech>
---
dts/framework/pktgen_ixia.py | 1869 ++++++++++++++++++++++++++++++++++
1 file changed, 1869 insertions(+)
create mode 100644 dts/framework/pktgen_ixia.py
diff --git a/dts/framework/pktgen_ixia.py b/dts/framework/pktgen_ixia.py
new file mode 100644
index 0000000000..9851e567a4
--- /dev/null
+++ b/dts/framework/pktgen_ixia.py
@@ -0,0 +1,1869 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2019 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import os
+import re
+import string
+import time
+from pprint import pformat
+
+from scapy.packet import Packet
+from scapy.utils import wrpcap
+
+from .pktgen_base import (
+ PKTGEN_IXIA,
+ TRANSMIT_CONT,
+ TRANSMIT_M_BURST,
+ TRANSMIT_S_BURST,
+ PacketGenerator,
+)
+from .settings import SCAPY2IXIA
+from .ssh_connection import SSHConnection
+from .utils import convert_int2ip, convert_ip2int, convert_mac2long, convert_mac2str
+
+
+class Ixia(SSHConnection):
+ """
+ IXIA performance measurement class.
+ """
+
+ def __init__(self, tester, ixiaPorts, logger):
+ self.tester = tester
+ self.NAME = PKTGEN_IXIA
+ super(Ixia, self).__init__(
+ self.get_ip_address(),
+ self.NAME,
+ self.tester.get_username(),
+ self.get_password(),
+ )
+ self.logger = logger
+ super(Ixia, self).init_log(self.logger)
+
+ self.tcl_cmds = []
+ self.chasId = None
+ self.conRelation = {}
+
+ ixiaRef = self.NAME
+ if ixiaRef is None or ixiaRef not in ixiaPorts:
+ return
+
+ self.ixiaVersion = ixiaPorts[ixiaRef]["Version"]
+ self.ports = ixiaPorts[ixiaRef]["Ports"]
+
+ if "force100g" in ixiaPorts[ixiaRef]:
+ self.enable100g = ixiaPorts[ixiaRef]["force100g"]
+ else:
+ self.enable100g = "disable"
+
+ self.logger.debug(self.ixiaVersion)
+ self.logger.debug(self.ports)
+
+ self.tclServerIP = ixiaPorts[ixiaRef]["IP"]
+
+ # prepare tcl shell and ixia library
+ self.send_expect("tclsh", "% ")
+ self.send_expect("source ./IxiaWish.tcl", "% ")
+ self.send_expect("set ::env(IXIA_VERSION) %s" % self.ixiaVersion, "% ")
+ out = self.send_expect("package req IxTclHal", "% ")
+ self.logger.debug("package req IxTclHal return:" + out)
+ if self.ixiaVersion in out:
+ if not self.tcl_server_login():
+ self.close()
+ self.session = None
+ for port in self.ports:
+ port["speed"] = self.get_line_rate(self.chasId, port)
+ # ixia port stream management table
+ self.stream_index = {}
+ self.stream_total = {}
+
+ def get_line_rate(self, chasid, port):
+ ixia_port = "%d %d %d" % (self.chasId, port["card"], port["port"])
+ return self.send_expect("stat getLineSpeed %s" % ixia_port, "%")
+
+ def get_ip_address(self):
+ return self.tester.get_ip_address()
+
+ def get_password(self):
+ return self.tester.get_password()
+
+ def add_tcl_cmd(self, cmd):
+ """
+ Add one tcl command into command list.
+ """
+ self.tcl_cmds.append(cmd)
+
+ def add_tcl_cmds(self, cmds):
+ """
+ Add one tcl command list into command list.
+ """
+ self.tcl_cmds += cmds
+
+ def clean(self):
+ """
+ Clean ownership of IXIA devices and logout tcl session.
+ """
+ self.send_expect("clearOwnershipAndLogout", "% ")
+ self.close()
+
+ def parse_pcap(self, fpcap):
+ # save Packet instance to pcap file
+ if isinstance(fpcap, Packet):
+ pcap_path = "/root/temp.pcap"
+ if os.path.exists(pcap_path):
+ os.remove(pcap_path)
+ wrpcap(pcap_path, fpcap)
+ else:
+ pcap_path = fpcap
+
+ dump_str1 = "cmds = []\n"
+ dump_str2 = "for i in rdpcap('%s', -1):\n" % pcap_path
+ dump_str3 = (
+ " if 'VXLAN' in i.command():\n"
+ + " vxlan_str = ''\n"
+ + " l = len(i[VXLAN])\n"
+ + " vxlan = str(i[VXLAN])\n"
+ + " first = True\n"
+ + " for j in range(l):\n"
+ + " if first:\n"
+ + ' vxlan_str += "VXLAN(hexval=\'%02X" %ord(vxlan[j])\n'
+ + " first = False\n"
+ + " else:\n"
+ + ' vxlan_str += " %02X" %ord(vxlan[j])\n'
+ + ' vxlan_str += "\')"\n'
+ + ' command = re.sub(r"VXLAN(.*)", vxlan_str, i.command())\n'
+ + " else:\n"
+ + " command = i.command()\n"
+ + " cmds.append(command)\n"
+ + "print(cmds)\n"
+ + "exit()"
+ )
+
+ f = open("dumppcap.py", "w")
+ f.write(dump_str1)
+ f.write(dump_str2)
+ f.write(dump_str3)
+ f.close()
+
+ self.session.copy_file_to("dumppcap.py")
+ out = self.send_expect("scapy -c dumppcap.py 2>/dev/null", "% ", 120)
+ flows = eval(out)
+ return flows
+
+ def macToTclFormat(self, macAddr):
+ """
+ Convert normal mac address format into IXIA's format.
+ """
+ macAddr = macAddr.upper()
+ return "%s %s %s %s %s %s" % (
+ macAddr[:2],
+ macAddr[3:5],
+ macAddr[6:8],
+ macAddr[9:11],
+ macAddr[12:14],
+ macAddr[15:17],
+ )
+
+ def set_ether_fields(self, fields, default_fields):
+ """
+ Configure Ether protocol field value.
+ """
+ addr_mode = {
+ # decrement the MAC address for as many numSA/numDA specified
+ "dec": "decrement",
+ # increment the MAC address for as many numSA/numDA specified
+ "inc": "increment",
+ # Generate random destination MAC address for each frame
+ "random": "ctrRandom",
+ # set RepeatCounter mode to be idle as default
+ "default": "idle",
+ }
+
+ cmds = []
+ for name, config in fields.items():
+ default_config = default_fields.get(name)
+ mac_start = config.get("start") or default_config.get("start")
+ mac_end = config.get("end")
+ step = config.get("step") or 1
+ action = config.get("action") or default_config.get("action")
+ prefix = "sa" if name == "src" else "da"
+ if action == "dec" and mac_end:
+ cmds.append('stream config -{0} "{1}"'.format(prefix, mac_end))
+ else:
+ cmds.append('stream config -{0} "{1}"'.format(prefix, mac_start))
+ if step:
+ cmds.append("stream config -{0}Step {1}".format(prefix, step))
+ # if not enable ContinueFromLastValue, the mac will always be start_mac
+ if prefix == "sa":
+ cmds.append("stream config -enableSaContinueFromLastValue true")
+ elif prefix == "da":
+ cmds.append("stream config -enableDaContinueFromLastValue true")
+ if action:
+ cmds.append(
+ "stream config -{0}RepeatCounter {1}".format(
+ prefix, addr_mode.get(action)
+ )
+ )
+ if mac_end:
+ mac_start_int = convert_mac2long(mac_start)
+ mac_end_int = convert_mac2long(mac_end)
+ flow_num = mac_end_int - mac_start_int + 1
+ if flow_num <= 0:
+ msg = "end mac should not be bigger than start mac"
+ raise Exception(msg)
+ else:
+ flow_num = None
+
+ if flow_num:
+ cmds.append(
+ "stream config -num{0} {1}".format(prefix.upper(), flow_num)
+ )
+ # clear default field after it has been set
+ default_fields.pop(name)
+ # if some filed not set, set it here
+ if default_fields:
+ for name, config in default_fields.items():
+ ip_start = config.get("start")
+ prefix = "sa" if name == "src" else "da"
+ cmds.append('stream config -{0} "{1}"'.format(prefix, ip_start))
+
+ return cmds
+
+ def ether(self, port, vm, src, dst, type):
+ """
+ Configure Ether protocol.
+ """
+ fields = vm.get("mac")
+ srcMac = self.macToTclFormat(src)
+ dstMac = self.macToTclFormat(dst)
+ # common command setting
+ self.add_tcl_cmd("protocol config -ethernetType ethernetII")
+ cmds = []
+ # if vm has been set, pick pcap fields' as default value
+ if fields:
+ default_fields = {
+ "src": {
+ "action": "default",
+ "start": src,
+ },
+ "dst": {
+ "action": "default",
+ "start": dst,
+ },
+ }
+ # set custom setting for field actions
+ cmds = self.set_ether_fields(fields, default_fields)
+ # set them in tcl commands group
+ self.add_tcl_cmds(cmds)
+ else:
+ self.add_tcl_cmd('stream config -sa "%s"' % srcMac)
+ self.add_tcl_cmd('stream config -da "%s"' % dstMac)
+
+ def set_ip_fields(self, fields, default_fields):
+ addr_mode = {
+ # increment the host portion of the IP address for as many
+ # IpAddrRepeatCount specified
+ "dec": "ipDecrHost",
+ # increment the host portion of the IP address for as many
+ # IpAddrRepeatCount specified
+ "inc": "ipIncrHost",
+ # Generate random IP addresses
+ "random": "ipRandom",
+ # no change to IP address regardless of IpAddrRepeatCount
+ "idle": "ipIdle",
+ # set default
+ "default": "ipIdle",
+ }
+ cmds = []
+ for name, config in fields.items():
+ default_config = default_fields.get(name)
+ fv_name = "IP.{0}".format(name)
+ ip_start = config.get("start") or default_config.get("start")
+ ip_end = config.get("end")
+ if ip_end:
+ ip_start_int = convert_ip2int(ip_start)
+ ip_end_int = convert_ip2int(ip_end)
+ flow_num = ip_end_int - ip_start_int + 1
+ if flow_num <= 0:
+ msg = "end ip address parameter is wrong"
+ raise Exception(msg)
+ else:
+ flow_num = None
+
+ mask = config.get("mask")
+ _step = config.get("step")
+ step = int(_step) if _step and isinstance(_step, str) else _step or 1
+ action = config.get("action")
+ # get ixia command prefix
+ prefix = "source" if name == "src" else "dest"
+ # set command
+ if action == "dec" and ip_end:
+ cmds.append('ip config -{0}IpAddr "{1}"'.format(prefix, ip_end))
+ else:
+ cmds.append('ip config -{0}IpAddr "{1}"'.format(prefix, ip_start))
+ if flow_num:
+ cmds.append(
+ "ip config -{0}IpAddrRepeatCount {1}".format(prefix, flow_num)
+ )
+
+ cmds.append(
+ "ip config -{0}IpAddrMode {1}".format(
+ prefix, addr_mode.get(action or "default")
+ )
+ )
+
+ if mask:
+ cmds.append("ip config -{0}IpMask '{1}'".format(prefix, mask))
+ # clear default field after it has been set
+ default_fields.pop(name)
+ # if all fields are set
+ if not default_fields:
+ return cmds
+ # if some filed not set, set it here
+ for name, config in default_fields.items():
+ ip_start = config.get("start")
+ prefix = "source" if name == "src" else "dest"
+ cmds.append('ip config -{0}IpAddr "{1}"'.format(prefix, ip_start))
+ cmds.append(
+ "ip config -{0}IpAddrMode {1}".format(prefix, addr_mode.get("default"))
+ )
+
+ return cmds
+
+ def ip(
+ self,
+ port,
+ vm,
+ frag,
+ src,
+ proto,
+ tos,
+ dst,
+ chksum,
+ len,
+ version,
+ flags,
+ ihl,
+ ttl,
+ id,
+ options=None,
+ ):
+ """
+ Configure IP protocol.
+ """
+ fields = vm.get("ip")
+ # common command setting
+ self.add_tcl_cmd("protocol config -name ip")
+ # if fields has been set
+ if fields:
+ # pick pcap fields' as default value
+ default_fields = {
+ "src": {
+ "action": "default",
+ "start": src,
+ },
+ "dst": {
+ "action": "default",
+ "start": dst,
+ },
+ }
+ # set custom setting for field actions
+ cmds = self.set_ip_fields(fields, default_fields)
+ # append custom setting
+ self.add_tcl_cmds(cmds)
+ else:
+ self.add_tcl_cmd('ip config -sourceIpAddr "%s"' % src)
+ self.add_tcl_cmd('ip config -destIpAddr "%s"' % dst)
+ # common command setting
+ self.add_tcl_cmd("ip config -ttl %d" % ttl)
+ self.add_tcl_cmd("ip config -totalLength %d" % len)
+ self.add_tcl_cmd("ip config -fragment %d" % frag)
+ self.add_tcl_cmd("ip config -ipProtocol {0}".format(proto))
+ self.add_tcl_cmd("ip config -identifier %d" % id)
+ self.add_tcl_cmd("stream config -framesize %d" % (len + 18))
+ # set stream setting in port
+ self.add_tcl_cmd("ip set %s" % port)
+
+ def ipv6(self, port, vm, version, tc, fl, plen, nh, hlim, src, dst):
+ """
+ Configure IPv6 protocol.
+ """
+ self.add_tcl_cmd("protocol config -name ipV6")
+ self.add_tcl_cmd("ipV6 setDefault")
+ self.add_tcl_cmd('ipV6 config -destAddr "%s"' % self.ipv6_to_tcl_format(dst))
+ self.add_tcl_cmd('ipV6 config -sourceAddr "%s"' % self.ipv6_to_tcl_format(src))
+ self.add_tcl_cmd("ipV6 config -flowLabel %d" % fl)
+ self.add_tcl_cmd("ipV6 config -nextHeader %d" % nh)
+ self.add_tcl_cmd("ipV6 config -hopLimit %d" % hlim)
+ self.add_tcl_cmd("ipV6 config -trafficClass %d" % tc)
+ self.add_tcl_cmd("ipV6 clearAllExtensionHeaders")
+ self.add_tcl_cmd("ipV6 addExtensionHeader %d" % nh)
+
+ self.add_tcl_cmd("stream config -framesize %d" % (plen + 40 + 18))
+ self.add_tcl_cmd("ipV6 set %s" % port)
+
+ def udp(self, port, vm, dport, sport, len, chksum):
+ """
+ Configure UDP protocol.
+ """
+ self.add_tcl_cmd("udp setDefault")
+ self.add_tcl_cmd("udp config -sourcePort %d" % sport)
+ self.add_tcl_cmd("udp config -destPort %d" % dport)
+ self.add_tcl_cmd("udp config -length %d" % len)
+ self.add_tcl_cmd("udp set %s" % port)
+
+ def vxlan(self, port, vm, hexval):
+ self.add_tcl_cmd("protocolPad setDefault")
+ self.add_tcl_cmd("protocol config -enableProtocolPad true")
+ self.add_tcl_cmd('protocolPad config -dataBytes "%s"' % hexval)
+ self.add_tcl_cmd("protocolPad set %s" % port)
+
+ def tcp(
+ self,
+ port,
+ vm,
+ sport,
+ dport,
+ seq,
+ ack,
+ dataofs,
+ reserved,
+ flags,
+ window,
+ chksum,
+ urgptr,
+ options=None,
+ ):
+ """
+ Configure TCP protocol.
+ """
+ self.add_tcl_cmd("tcp setDefault")
+ self.add_tcl_cmd("tcp config -sourcePort %d" % sport)
+ self.add_tcl_cmd("tcp config -destPort %d" % dport)
+ self.add_tcl_cmd("tcp set %s" % port)
+
+ def sctp(self, port, vm, sport, dport, tag, chksum):
+ """
+ Configure SCTP protocol.
+ """
+ self.add_tcl_cmd("tcp config -sourcePort %d" % sport)
+ self.add_tcl_cmd("tcp config -destPort %d" % dport)
+ self.add_tcl_cmd("tcp set %s" % port)
+
+ def set_dot1q_fields(self, fields):
+ """
+ Configure 8021Q protocol field name.
+ """
+ addr_mode = {
+ # The VlanID tag is decremented by step for repeat number of times
+ "dec": "vDecrement",
+ # The VlanID tag is incremented by step for repeat number of times
+ "inc": "vIncrement",
+ # Generate random VlanID tag for each frame
+ "random": "vCtrRandom",
+ # No change to VlanID tag regardless of repeat
+ "idle": "vIdle",
+ }
+ cmds = []
+ for name, config in fields.items():
+ fv_name = "8021Q.{0}".format(name)
+ vlan_start = config.get("start") or 0
+ vlan_end = config.get("end") or 256
+ if vlan_end:
+ flow_num = vlan_end - vlan_start + 1
+ if flow_num <= 0:
+ msg = "end vlan id parameter is wrong"
+ raise Exception(msg)
+ else:
+ flow_num = None
+ step = config.get("step") or 1
+ action = config.get("action")
+ # ------------------------------------------------
+ # set command
+ if step:
+ cmds.append("vlan config -step {0}".format(step))
+ if flow_num:
+ cmds.append("vlan config -repeat {0}".format(flow_num))
+ if action:
+ cmds.append("vlan config -mode {0}".format(addr_mode.get(action)))
+ return cmds
+
+ def dot1q(self, port, vm, prio, id, vlan, type):
+ """
+ Configure 8021Q protocol.
+ """
+ fields = vm.get("vlan")
+ # common command setting
+ self.add_tcl_cmd("protocol config -enable802dot1qTag true")
+ # if fields has been set
+ if fields:
+ # set custom setting for field actions
+ cmds = self.set_dot1q_fields(fields)
+ self.add_tcl_cmds(cmds)
+ self.add_tcl_cmd("vlan config -vlanID %d" % vlan)
+ self.add_tcl_cmd("vlan config -userPriority %d" % prio)
+ # set stream in port
+ self.add_tcl_cmd("vlan set %s" % port)
+
+ def config_stream(
+ self, fpcap, vm, port_index, rate_percent, stream_id=1, latency=False
+ ):
+ """
+ Configure IXIA stream and enable multiple flows.
+ """
+ ixia_port = self.get_ixia_port(port_index)
+ flows = self.parse_pcap(fpcap)
+ if not flows:
+ msg = "flow has no format, it should be one."
+ raise Exception(msg)
+ if len(flows) >= 2:
+ msg = "flow contain more than one format, it should be one."
+ raise Exception(msg)
+
+ # set commands at first stream
+ if stream_id == 1:
+ self.add_tcl_cmd("ixGlobalSetDefault")
+ # set burst stream if burst stream is required
+ stream_config = vm.get("stream_config")
+ transmit_mode = stream_config.get("transmit_mode") or TRANSMIT_CONT
+ if transmit_mode == TRANSMIT_S_BURST:
+ cmds = self.config_single_burst_stream(
+ stream_config.get("txmode"), rate_percent
+ )
+ self.add_tcl_cmds(cmds)
+ else:
+ self.config_ixia_stream(
+ rate_percent, self.stream_total.get(port_index), latency
+ )
+
+ pat = re.compile(r"(\w+)\((.*)\)")
+ for flow in flows:
+ for header in flow.split("/"):
+ match = pat.match(header)
+ params = eval("dict(%s)" % match.group(2))
+ method_name = match.group(1)
+ if method_name == "VXLAN":
+ method = getattr(self, method_name.lower())
+ method(ixia_port, vm.get("fields_config", {}), **params)
+ break
+ if method_name in SCAPY2IXIA:
+ method = getattr(self, method_name.lower())
+ method(ixia_port, vm.get("fields_config", {}), **params)
+ self.add_tcl_cmd("stream set %s %d" % (ixia_port, stream_id))
+ # only use one packet format in pktgen
+ break
+
+ # set commands at last stream
+ if stream_id >= self.stream_total[port_index]:
+ self.add_tcl_cmd("stream config -dma gotoFirst")
+ self.add_tcl_cmd("stream set %s %d" % (ixia_port, stream_id))
+
+ def config_single_burst_stream(self, txmode, rate_percent):
+ """configure burst stream."""
+ gapUnits = {
+ # (default) Sets units of time for gap to nanoseconds
+ "ns": "gapNanoSeconds",
+ # Sets units of time for gap to microseconds
+ "us": "gapMicroSeconds",
+ # Sets units of time for gap to milliseconds
+ "m": "gapMilliSeconds",
+ # Sets units of time for gap to seconds
+ "s": "gapSeconds",
+ }
+ pkt_count = 1
+ burst_count = txmode.get("total_pkts", 32)
+ frameType = txmode.get("frameType") or {}
+ time_unit = frameType.get("type", "ns")
+ gapUnit = (
+ gapUnits.get(time_unit)
+ if time_unit in list(gapUnits.keys())
+ else gapUnits.get("ns")
+ )
+ # The inter-stream gap is the delay in clock ticks between stream.
+ # This delay comes after the receive trigger is enabled. Setting this
+ # option to 0 means no delay. (default = 960.0)
+ isg = frameType.get("isg", 100)
+ # The inter-frame gap specified in clock ticks (default = 960.0).
+ ifg = frameType.get("ifg", 100)
+ # Inter-Burst Gap is the delay between bursts of frames in clock ticks
+ # (see ifg option for definition of clock ticks). If the IBG is set to
+ # 0 then the IBG is equal to the ISG and the IBG becomes disabled.
+ # (default = 960.0)
+ ibg = frameType.get("ibg", 100)
+ frame_cmds = [
+ "stream config -rateMode usePercentRate",
+ "stream config -percentPacketRate %s" % rate_percent,
+ "stream config -dma stopStream",
+ "stream config -rateMode useGap",
+ "stream config -gapUnit {0}".format(gapUnit),
+ "stream config -numFrames {0}".format(pkt_count),
+ "stream config -numBursts {0}".format(burst_count),
+ "stream config -ifg {0}".format(ifg),
+ "stream config -ifgType gapFixed",
+ # "stream config -enableIbg true", # reserve
+ # "stream config -ibg {0}".format(ibg), # reserve
+ # "stream config -enableIsg true", # reserve
+ # "stream config -isg {0}".format(isg), # reserve
+ "stream config -frameSizeType sizeFixed",
+ ]
+
+ return frame_cmds
+
+ def config_ixia_stream(self, rate_percent, total_flows, latency):
+ """
+ Configure IXIA stream with rate and latency.
+ Override this method if you want to add custom stream configuration.
+ """
+ self.add_tcl_cmd("stream config -rateMode usePercentRate")
+ self.add_tcl_cmd("stream config -percentPacketRate %s" % rate_percent)
+ self.add_tcl_cmd("stream config -numFrames 1")
+ if total_flows == 1:
+ self.add_tcl_cmd("stream config -dma contPacket")
+ else:
+ self.add_tcl_cmd("stream config -dma advance")
+ # request by packet Group
+ if latency is not False:
+ self.add_tcl_cmd("stream config -fir true")
+
+ def tcl_server_login(self):
+ """
+ Connect to tcl server and take ownership of all the ports needed.
+ """
+ out = self.send_expect("ixConnectToTclServer %s" % self.tclServerIP, "% ", 30)
+ self.logger.debug("ixConnectToTclServer return:" + out)
+ if out.strip()[-1] != "0":
+ return False
+
+ self.send_expect("ixLogin IxiaTclUser", "% ")
+
+ out = self.send_expect("ixConnectToChassis %s" % self.tclServerIP, "% ", 30)
+ if out.strip()[-1] != "0":
+ return False
+
+ out = self.send_expect(
+ "set chasId [ixGetChassisID %s]" % self.tclServerIP, "% "
+ )
+ self.chasId = int(out.strip())
+
+ out = self.send_expect(
+ "ixClearOwnership [list %s]"
+ % " ".join(
+ [
+ "[list %d %d %d]" % (self.chasId, item["card"], item["port"])
+ for item in self.ports
+ ]
+ ),
+ "% ",
+ 10,
+ )
+ if out.strip()[-1] != "0":
+ self.logger.info("Force to take ownership:")
+ out = self.send_expect(
+ "ixTakeOwnership [list %s] force"
+ % " ".join(
+ [
+ "[list %d %d %d]" % (self.chasId, item["card"], item["port"])
+ for item in self.ports
+ ]
+ ),
+ "% ",
+ 10,
+ )
+ if out.strip()[-1] != "0":
+ return False
+
+ return True
+
+ def tcl_server_logout(self):
+ """
+ Disconnect to tcl server and make sure has been logged out.
+ """
+ self.send_expect("ixDisconnectFromChassis %s" % self.tclServerIP, "%")
+ self.send_expect("ixLogout", "%")
+ self.send_expect("ixDisconnectTclServer %s" % self.tclServerIP, "%")
+
+ def config_port(self, pList):
+ """
+ Configure ports and make them ready for performance validation.
+ """
+ pl = list()
+ for item in pList:
+ ixia_port = "%d %d %d" % (self.chasId, item["card"], item["port"])
+ self.add_tcl_cmd("port setFactoryDefaults %s" % ixia_port)
+ # if the line rate is 100G and we need this port work in 100G mode,
+ # we need to add some configure to make it so.
+ if (
+ int(self.get_line_rate(self.chasId, item).strip()) == 100000
+ and self.enable100g == "enable"
+ ):
+ self.add_tcl_cmd("port config -ieeeL1Defaults 0")
+ self.add_tcl_cmd("port config -autonegotiate false")
+ self.add_tcl_cmd("port config -enableRsFec true")
+ self.add_tcl_cmd(
+ "port set %d %d %d" % (self.chasId, item["card"], item["port"])
+ )
+
+ pl.append("[list %d %d %d]" % (self.chasId, item["card"], item["port"]))
+
+ self.add_tcl_cmd("set portList [list %s]" % " ".join(pl))
+
+ self.add_tcl_cmd("ixClearTimeStamp portList")
+ self.add_tcl_cmd("ixWritePortsToHardware portList")
+ self.add_tcl_cmd("ixCheckLinkState portList")
+
+ def set_ixia_port_list(self, pList):
+ """
+ Implement ports/streams configuration on specified ports.
+ """
+ self.add_tcl_cmd(
+ "set portList [list %s]"
+ % " ".join(["[list %s]" % ixia_port for ixia_port in pList])
+ )
+
+ def send_ping6(self, pci, mac, ipv6):
+ """
+ Send ping6 packet from IXIA ports.
+ """
+ port = self.pci_to_port(pci)
+ ixia_port = "%d %d %d" % (self.chasId, port["card"], port["port"])
+ self.send_expect("source ./ixTcl1.0/ixiaPing6.tcl", "% ")
+ cmd = 'ping6 "%s" "%s" %s' % (
+ self.ipv6_to_tcl_format(ipv6),
+ self.macToTclFormat(mac),
+ ixia_port,
+ )
+ out = self.send_expect(cmd, "% ", 90)
+ return out
+
+ def ipv6_to_tcl_format(self, ipv6):
+ """
+ Convert normal IPv6 address to IXIA format.
+ """
+ ipv6 = ipv6.upper()
+ singleAddr = ipv6.split(":")
+ if "" == singleAddr[0]:
+ singleAddr = singleAddr[1:]
+ if "" in singleAddr:
+ tclFormatAddr = ""
+ addStr = "0:" * (8 - len(singleAddr)) + "0"
+ for i in range(len(singleAddr)):
+ if singleAddr[i] == "":
+ tclFormatAddr += addStr + ":"
+ else:
+ tclFormatAddr += singleAddr[i] + ":"
+ tclFormatAddr = tclFormatAddr[0 : len(tclFormatAddr) - 1]
+ return tclFormatAddr
+ else:
+ return ipv6
+
+ def get_ports(self):
+ """
+ API to get ixia ports for dts `ports_info`
+ """
+ plist = list()
+ if self.session is None:
+ return plist
+
+ for p in self.ports:
+ plist.append({"type": "ixia", "pci": "IXIA:%d.%d" % (p["card"], p["port"])})
+ return plist
+
+ def get_ixia_port_pci(self, port_id):
+ ports_info = self.get_ports()
+ pci = ports_info[port_id]["pci"]
+ return pci
+
+ def pci_to_port(self, pci):
+ """
+ Convert IXIA fake pci to IXIA port.
+ """
+ ixia_pci_regex = "IXIA:(\d*).(\d*)"
+ m = re.match(ixia_pci_regex, pci)
+ if m is None:
+ msg = "ixia port not found"
+ self.logger.warning(msg)
+ return {"card": -1, "port": -1}
+
+ return {"card": int(m.group(1)), "port": int(m.group(2))}
+
+ def get_ixia_port_info(self, port):
+ if port == None or port >= len(self.ports):
+ msg = "<{0}> exceed maximum ixia ports".format(port)
+ raise Exception(msg)
+ pci_addr = self.get_ixia_port_pci(port)
+ port_info = self.pci_to_port(pci_addr)
+ return port_info
+
+ def get_ixia_port(self, port):
+ port_info = self.get_ixia_port_info(port)
+ ixia_port = "%d %d %d" % (self.chasId, port_info["card"], port_info["port"])
+ return ixia_port
+
+ def loss(self, portList, ratePercent, delay=5):
+ """
+ Run loss performance test and return loss rate.
+ """
+ rxPortlist, txPortlist = self._configure_everything(portList, ratePercent)
+ return self.get_loss_packet_rate(rxPortlist, txPortlist, delay)
+
+ def get_loss_packet_rate(self, rxPortlist, txPortlist, delay=5):
+ """
+ Get RX/TX packet statistics and calculate loss rate.
+ """
+ time.sleep(delay)
+
+ self.send_expect("ixStopTransmit portList", "%", 10)
+ time.sleep(2)
+ sendNumber = 0
+ for port in txPortlist:
+ self.stat_get_stat_all_stats(port)
+ sendNumber += self.get_frames_sent()
+ time.sleep(0.5)
+
+ self.logger.debug("send :%f" % sendNumber)
+
+ assert sendNumber != 0
+
+ revNumber = 0
+ for port in rxPortlist:
+ self.stat_get_stat_all_stats(port)
+ revNumber += self.get_frames_received()
+ self.logger.debug("rev :%f" % revNumber)
+
+ return float(sendNumber - revNumber) / sendNumber, sendNumber, revNumber
+
+ def latency(self, portList, ratePercent, delay=5):
+ """
+ Run latency performance test and return latency statistics.
+ """
+ rxPortlist, txPortlist = self._configure_everything(portList, ratePercent, True)
+ return self.get_packet_latency(rxPortlist)
+
+ def get_packet_latency(self, rxPortlist):
+ """
+ Stop IXIA transmit and return latency statistics.
+ """
+ latencyList = []
+ time.sleep(10)
+ self.send_expect("ixStopTransmit portList", "%", 10)
+ for rx_port in rxPortlist:
+ self.pktGroup_get_stat_all_stats(rx_port)
+ latency = {
+ "port": rx_port,
+ "min": self.get_min_latency(),
+ "max": self.get_max_latency(),
+ "average": self.get_average_latency(),
+ }
+ latencyList.append(latency)
+ return latencyList
+
+ def throughput(self, port_list, rate_percent=100, delay=5):
+ """
+ Run throughput performance test and return throughput statistics.
+ """
+ rxPortlist, txPortlist = self._configure_everything(port_list, rate_percent)
+ return self.get_transmission_results(rxPortlist, txPortlist, delay)
+
+ def is_packet_ordered(self, port_list, delay):
+ """
+ This function could be used to check the packets' order whether same as
+ the receive sequence.
+
+ Please notice that this function only support single-stream mode.
+ """
+ port = self.ports[0]
+ ixia_port = "%d %d %d" % (self.chasId, port["card"], port["port"])
+ rxPortlist, txPortlist = self.prepare_port_list(port_list)
+ self.prepare_ixia_for_transmission(txPortlist, rxPortlist)
+ self.send_expect(
+ "port config -receiveMode [expr $::portCapture|$::portRxSequenceChecking|$::portRxModeWidePacketGroup]",
+ "%",
+ )
+ self.send_expect("port config -autonegotiate true", "%")
+ self.send_expect("ixWritePortsToHardware portList", "%")
+ self.send_expect("set streamId 1", "%")
+ self.send_expect("stream setDefault", "%")
+ self.send_expect("ixStartPortPacketGroups %s" % ixia_port, "%")
+ self.send_expect("ixStartTransmit portList", "%")
+ # wait `delay` seconds to make sure link is up
+ self.send_expect("after 1000 * %d" % delay, "%")
+ self.send_expect("ixStopTransmit portList", "%")
+ self.send_expect("ixStopPortPacketGroups %s" % ixia_port, "%")
+ self.send_expect("packetGroupStats get %s 1 1" % ixia_port, "%")
+ self.send_expect("packetGroupStats getGroup 1", "%")
+ self.send_expect(
+ "set reverseSequenceError [packetGroupStats cget -reverseSequenceError]]",
+ "%",
+ )
+ output = self.send_expect("puts $reverseSequenceError", "%")
+ return int(output[:-2])
+
+ def _configure_everything(self, port_list, rate_percent, latency=False):
+ """
+ Prepare and configure IXIA ports for performance test.
+ """
+ rxPortlist, txPortlist = self.prepare_port_list(
+ port_list, rate_percent, latency
+ )
+ self.prepare_ixia_for_transmission(txPortlist, rxPortlist)
+ self.configure_transmission()
+ self.start_transmission()
+ self.clear_tcl_commands()
+ return rxPortlist, txPortlist
+
+ def clear_tcl_commands(self):
+ """
+ Clear all commands in command list.
+ """
+ del self.tcl_cmds[:]
+
+ def start_transmission(self):
+ """
+ Run commands in command list.
+ """
+ fileContent = "\n".join(self.tcl_cmds) + "\n"
+ self.tester.create_file(fileContent, "ixiaConfig.tcl")
+ self.send_expect("source ixiaConfig.tcl", "% ", 75)
+
+ def configure_transmission(self, option=None):
+ """
+ Start IXIA ports transmission.
+ """
+ self.add_tcl_cmd("ixStartTransmit portList")
+
+ def prepare_port_list(self, portList, rate_percent=100, latency=False):
+ """
+ Configure stream and flow on every IXIA ports.
+ """
+ txPortlist = set()
+ rxPortlist = set()
+
+ for subPortList in portList:
+ txPort, rxPort = subPortList[:2]
+ txPortlist.add(txPort)
+ rxPortlist.add(rxPort)
+
+ # port init
+ self.config_port(
+ [self.get_ixia_port_info(port) for port in txPortlist.union(rxPortlist)]
+ )
+
+ # calculate total streams of ports
+ for (txPort, rxPort, pcapFile, option) in portList:
+ if txPort not in list(self.stream_total.keys()):
+ self.stream_total[txPort] = 1
+ else:
+ self.stream_total[txPort] += 1
+
+ # stream/flow setting
+ for (txPort, rxPort, pcapFile, option) in portList:
+ if txPort not in list(self.stream_index.keys()):
+ self.stream_index[txPort] = 1
+ frame_index = self.stream_index[txPort]
+ self.config_stream(
+ pcapFile, option, txPort, rate_percent, frame_index, latency
+ )
+ self.stream_index[txPort] += 1
+ # clear stream ids table
+ self.stream_index.clear()
+ self.stream_total.clear()
+
+ # config stream before packetGroup
+ if latency is not False:
+ for subPortList in portList:
+ txPort, rxPort = subPortList[:2]
+ flow_num = len(self.parse_pcap(pcapFile))
+ self.config_pktGroup_rx(self.get_ixia_port(rxPort))
+ self.config_pktGroup_tx(self.get_ixia_port(txPort))
+ return rxPortlist, txPortlist
+
+ def prepare_ixia_for_transmission(self, txPortlist, rxPortlist):
+ """
+ Clear all statistics and implement configuration to IXIA hardware.
+ """
+ self.add_tcl_cmd("ixClearStats portList")
+ self.set_ixia_port_list([self.get_ixia_port(port) for port in txPortlist])
+ self.add_tcl_cmd("ixWriteConfigToHardware portList")
+ # Wait for changes to take affect and make sure links are up
+ self.add_tcl_cmd("after 1000")
+ for port in txPortlist:
+ self.start_pktGroup(self.get_ixia_port(port))
+ for port in rxPortlist:
+ self.start_pktGroup(self.get_ixia_port(port))
+
+ def hook_transmission_func(self):
+ pass
+
+ def get_transmission_results(self, rx_port_list, tx_port_list, delay=5):
+ """
+ Override this method if you want to change the way of getting results
+ back from IXIA.
+ """
+ time.sleep(delay)
+ bpsRate = 0
+ rate = 0
+ oversize = 0
+ for port in rx_port_list:
+ self.stat_get_rate_stat_all_stats(port)
+ out = self.send_expect("stat cget -framesReceived", "%", 10)
+ rate += int(out.strip())
+ out = self.send_expect("stat cget -bitsReceived", "% ", 10)
+ self.logger.debug("port %d bits rate:" % (port) + out)
+ bpsRate += int(out.strip())
+ out = self.send_expect("stat cget -oversize", "%", 10)
+ oversize += int(out.strip())
+
+ self.logger.debug("Rate: %f Mpps" % (rate * 1.0 / 1000000))
+ self.logger.debug("Mbps rate: %f Mbps" % (bpsRate * 1.0 / 1000000))
+
+ self.hook_transmission_func()
+
+ self.send_expect("ixStopTransmit portList", "%", 30)
+
+ if rate == 0 and oversize > 0:
+ return (bpsRate, oversize)
+ else:
+ return (bpsRate, rate)
+
+ def config_ixia_dcb_init(self, rxPort, txPort):
+ """
+ Configure Ixia for DCB.
+ """
+ self.send_expect("source ./ixTcl1.0/ixiaDCB.tcl", "% ")
+ self.send_expect(
+ "configIxia %d %s"
+ % (
+ self.chasId,
+ " ".join(
+ [
+ "%s" % (repr(self.conRelation[port][n]))
+ for port in [rxPort, txPort]
+ for n in range(3)
+ ]
+ ),
+ ),
+ "% ",
+ 100,
+ )
+
+ def config_port_dcb(self, direction, tc):
+ """
+ Configure Port for DCB.
+ """
+ self.send_expect("configPort %s %s" % (direction, tc), "% ", 100)
+
+ def config_port_flow_control(self, ports, option):
+ """configure the type of flow control on a port"""
+ if not ports:
+ return
+ # mac address, default is "01 80 C2 00 00 01"
+ dst_mac = option.get("dst_mac") or '"01 80 C2 00 00 01"'
+ if not dst_mac:
+ return
+ pause_time = option.get("pause_time") or 255
+ flow_ctrl_cmds = [
+ "protocol setDefault",
+ "port config -flowControl true",
+ "port config -flowControlType ieee8023x",
+ ]
+ for port in ports:
+ ixia_port = self.get_ixia_port(port)
+ flow_ctrl_cmds = [
+ # configure a pause control packet.
+ "port set {0}".format(ixia_port),
+ "protocol config -name pauseControl",
+ "pauseControl setDefault",
+ "pauseControl config -pauseControlType ieee8023x",
+ 'pauseControl config -da "{0}"'.format(dst_mac),
+ "pauseControl config -pauseTime {0}".format(pause_time),
+ "pauseControl set {0}".format(ixia_port),
+ ]
+ self.add_tcl_cmds(flow_ctrl_cmds)
+
+ def cfgStreamDcb(self, stream, rate, prio, types):
+ """
+ Configure Stream for DCB.
+ """
+ self.send_expect(
+ "configStream %s %s %s %s" % (stream, rate, prio, types), "% ", 100
+ )
+
+ def get_connection_relation(self, dutPorts):
+ """
+ Get the connect relations between DUT and Ixia.
+ """
+ for port in dutPorts:
+ info = self.tester.get_pci(self.tester.get_local_port(port)).split(".")
+ self.conRelation[port] = [
+ int(info[0]),
+ int(info[1]),
+ repr(self.tester.dut.get_mac_address(port).replace(":", " ").upper()),
+ ]
+ return self.conRelation
+
+ def config_pktGroup_rx(self, ixia_port):
+ """
+ Sets the transmit Packet Group configuration of the stream
+ Default streamID is 1
+ """
+ self.add_tcl_cmd("port config -receiveMode $::portRxModeWidePacketGroup")
+ self.add_tcl_cmd("port set %s" % ixia_port)
+ self.add_tcl_cmd("packetGroup setDefault")
+ self.add_tcl_cmd("packetGroup config -latencyControl cutThrough")
+ self.add_tcl_cmd("packetGroup setRx %s" % ixia_port)
+ self.add_tcl_cmd("packetGroup setTx %s 1" % ixia_port)
+
+ def config_pktGroup_tx(self, ixia_port):
+ """
+ Configure tx port pktGroup for latency.
+ """
+ self.add_tcl_cmd("packetGroup setDefault")
+ self.add_tcl_cmd("packetGroup config -insertSignature true")
+ self.add_tcl_cmd("packetGroup setTx %s 1" % ixia_port)
+
+ def start_pktGroup(self, ixia_port):
+ """
+ Start tx port pktGroup for latency.
+ """
+ self.add_tcl_cmd("ixStartPortPacketGroups %s" % ixia_port)
+
+ def pktGroup_get_stat_all_stats(self, port_number):
+ """
+ Stop Packet Group operation on port and get current Packet Group
+ statistics on port.
+ """
+ ixia_port = self.get_ixia_port(port_number)
+ self.send_expect("ixStopPortPacketGroups %s" % ixia_port, "%", 100)
+ self.send_expect("packetGroupStats get %s 0 16384" % ixia_port, "%", 100)
+ self.send_expect("packetGroupStats getGroup 0", "%", 100)
+
+ def close(self):
+ """
+ We first close the tclsh session opened at the beginning,
+ then the SSH session.
+ """
+ if self.isalive():
+ self.send_expect("exit", "# ")
+ super(Ixia, self).close()
+
+ def stat_get_stat_all_stats(self, port_number):
+ """
+ Sends a IXIA TCL command to obtain all the stat values on a given port.
+ """
+ ixia_port = self.get_ixia_port(port_number)
+ command = "stat get statAllStats {0}".format(ixia_port)
+ self.send_expect(command, "% ", 10)
+
+ def prepare_ixia_internal_buffers(self, port_number):
+ """
+ Tells IXIA to prepare the internal buffers were the frames were captured.
+ """
+ ixia_port = self.get_ixia_port(port_number)
+ command = "capture get {0}".format(ixia_port)
+ self.send_expect(command, "% ", 30)
+
+ def stat_get_rate_stat_all_stats(self, port_number):
+ """
+ All statistics of specified IXIA port.
+ """
+ ixia_port = self.get_ixia_port(port_number)
+ command = "stat getRate statAllStats {0}".format(ixia_port)
+ out = self.send_expect(command, "% ", 30)
+ return out
+
+ def ixia_capture_buffer(self, port_number, first_frame, last_frame):
+ """
+ Tells IXIA to load the captured frames into the internal buffers.
+ """
+ ixia_port = self.get_ixia_port(port_number)
+ command = "captureBuffer get {0} {1} {2}".format(
+ ixia_port, first_frame, last_frame
+ )
+ self.send_expect(command, "%", 60)
+
+ def ixia_export_buffer_to_file(self, frames_filename):
+ """
+ Tells IXIA to dump the frames it has loaded in its internal buffer to a
+ text file.
+ """
+ command = "captureBuffer export %s" % frames_filename
+ self.send_expect(command, "%", 30)
+
+ def _stat_cget_value(self, requested_value):
+ """
+ Sends a IXIA TCL command to obtain a given stat value.
+ """
+ command = "stat cget -" + requested_value
+ result = self.send_expect(command, "%", 10)
+ return int(result.strip())
+
+ def _capture_cget_value(self, requested_value):
+ """
+ Sends a IXIA TCL command to capture certain number of packets.
+ """
+ command = "capture cget -" + requested_value
+ result = self.send_expect(command, "%", 10)
+ return int(result.strip())
+
+ def _packetgroup_cget_value(self, requested_value):
+ """
+ Sends a IXIA TCL command to get pktGroup stat value.
+ """
+ command = "packetGroupStats cget -" + requested_value
+ result = self.send_expect(command, "%", 10)
+ return int(result.strip())
+
+ def number_of_captured_packets(self):
+ """
+ Returns the number of packets captured by IXIA on a previously set
+ port. Call self.stat_get_stat_all_stats(port) before.
+ """
+ return self._capture_cget_value("nPackets")
+
+ def get_frames_received(self):
+ """
+ Returns the number of packets captured by IXIA on a previously set
+ port. Call self.stat_get_stat_all_stats(port) before.
+ """
+ if self._stat_cget_value("framesReceived") != 0:
+ return self._stat_cget_value("framesReceived")
+ else:
+ # if the packet size is large than 1518, this line will avoid return
+ # a wrong number
+ return self._stat_cget_value("oversize")
+
+ def get_flow_control_frames(self):
+ """
+ Returns the number of control frames captured by IXIA on a
+ previously set port. Call self.stat_get_stat_all_stats(port) before.
+ """
+ return self._stat_cget_value("flowControlFrames")
+
+ def get_frames_sent(self):
+ """
+ Returns the number of packets sent by IXIA on a previously set
+ port. Call self.stat_get_stat_all_stats(port) before.
+ """
+ return self._stat_cget_value("framesSent")
+
+ def get_transmit_duration(self):
+ """
+ Returns the duration in nanosecs of the last transmission on a
+ previously set port. Call self.stat_get_stat_all_stats(port) before.
+ """
+ return self._stat_cget_value("transmitDuration")
+
+ def get_min_latency(self):
+ """
+ Returns the minimum latency in nanoseconds of the frames in the
+ retrieved capture buffer. Call packetGroupStats get before.
+ """
+ return self._packetgroup_cget_value("minLatency")
+
+ def get_max_latency(self):
+ """
+ Returns the maximum latency in nanoseconds of the frames in the
+ retrieved capture buffer. Call packetGroupStats get before.
+ """
+ return self._packetgroup_cget_value("maxLatency")
+
+ def get_average_latency(self):
+ """
+ Returns the average latency in nanoseconds of the frames in the
+ retrieved capture buffer. Call packetGroupStats get before.
+ """
+ return self._packetgroup_cget_value("averageLatency")
+
+ def _transmission_pre_config(self, port_list, rate_percent, latency=False):
+ """
+ Prepare and configure IXIA ports for performance test. And remove the
+ transmission step in this config sequence.
+
+ This function is set only for function send_number_packets for
+ nic_single_core_perf test case use
+ """
+ rxPortlist, txPortlist = self.prepare_port_list(
+ port_list, rate_percent, latency
+ )
+ self.prepare_ixia_for_transmission(txPortlist, rxPortlist)
+ self.start_transmission()
+ self.clear_tcl_commands()
+ return rxPortlist, txPortlist
+
+ def send_number_packets(self, portList, ratePercent, packetNum):
+ """
+ Configure ixia to send fixed number of packets
+ Note that this function is only set for test_suite nic_single_core_perf,
+ Not for common use
+ """
+ rxPortlist, txPortlist = self._transmission_pre_config(portList, ratePercent)
+
+ self.send_expect("stream config -numFrames %s" % packetNum, "%", 5)
+ self.send_expect("stream config -dma stopStream", "%", 5)
+ for txPort in txPortlist:
+ ixia_port = self.get_ixia_port(txPort)
+ self.send_expect("stream set %s 1" % ixia_port, "%", 5)
+
+ self.send_expect("ixWritePortsToHardware portList", "%", 5)
+ self.send_expect("ixClearStats portList", "%", 5)
+ self.send_expect("ixStartTransmit portList", "%", 5)
+ time.sleep(10)
+
+ rxPackets = 0
+ for port in txPortlist:
+ self.stat_get_stat_all_stats(port)
+ txPackets = self.get_frames_sent()
+ while txPackets != packetNum:
+ time.sleep(10)
+ self.stat_get_stat_all_stats(port)
+ txPackets = self.get_frames_sent()
+ rxPackets += self.get_frames_received()
+ self.logger.debug("Received packets :%s" % rxPackets)
+
+ return rxPackets
+
+ # ---------------------------------------------------------
+ # extend methods for pktgen subclass `IxiaPacketGenerator
+ # ---------------------------------------------------------
+ def disconnect(self):
+ """quit from ixia server"""
+ pass
+
+ def start(self, **run_opt):
+ """start ixia ports"""
+ self.configure_transmission(run_opt)
+ self.start_transmission()
+
+ def remove_all_streams(self):
+ """delete all streams on all ixia ports"""
+ if not self.ports:
+ return
+ for item in self.ports:
+ cmd = "port reset {0} {1} {2}".format(
+ self.chasId, item["card"], item["port"]
+ )
+ self.send_expect(cmd, "%", 10)
+
+ def reset(self, ports=None):
+ """reset ixia configuration for ports"""
+ pass
+
+ def clear_tcl_buffer(self):
+ """clear tcl commands buffer"""
+ self.tcl_cmds = []
+
+ def clear_stats(self):
+ pass
+
+ def stop_transmit(self):
+ """
+ Stop IXIA transmit
+ """
+ time.sleep(2)
+ self.send_expect("ixStopTransmit portList", "%", 40)
+
+ def get_latency_stat(self, port_list):
+ """
+ get latency statistics.
+ """
+ stats = {}
+ for port in port_list:
+ self.pktGroup_get_stat_all_stats(port)
+ stats[port] = {
+ "average": self.get_average_latency(),
+ "total_max": self.get_max_latency(),
+ "total_min": self.get_min_latency(),
+ }
+ return stats
+
+ def get_loss_stat(self, port_list):
+ """
+ Get RX/TX packet statistics.
+ """
+ stats = {}
+ for port in port_list:
+ self.stat_get_stat_all_stats(port)
+ stats[port] = {
+ "ibytes": 0,
+ "ierrors": 0,
+ "ipackets": self.get_frames_received(),
+ "obytes": 0,
+ "oerrors": 0,
+ "opackets": self.get_frames_sent(),
+ "rx_bps": 0,
+ "rx_pps": 0,
+ "tx_bps": 0,
+ "tx_pps": 0,
+ }
+ time.sleep(0.5)
+ return stats
+
+ def get_throughput_stat(self, port_list):
+ """
+ Get RX transmit rate.
+ """
+ stats = {}
+ for port in port_list:
+ self.stat_get_rate_stat_all_stats(port)
+ out = self.send_expect("stat cget -framesReceived", "%", 10)
+ rate = int(out.strip())
+ out = self.send_expect("stat cget -bitsReceived", "% ", 10)
+ bpsRate = int(out.strip())
+ out = self.send_expect("stat cget -oversize", "%", 10)
+ oversize = int(out.strip())
+ rate = oversize if rate == 0 and oversize > 0 else rate
+
+ stats[port] = {
+ "ibytes": 0,
+ "ierrors": 0,
+ "ipackets": 0,
+ "obytes": 0,
+ "oerrors": 0,
+ "opackets": 0,
+ "rx_bps": bpsRate,
+ "rx_pps": rate,
+ "tx_bps": 0,
+ "tx_pps": 0,
+ }
+
+ return stats
+
+ def get_stats(self, ports, mode):
+ """
+ get statistics of custom mode
+ """
+ methods = {
+ "throughput": self.get_throughput_stat,
+ "loss": self.get_loss_stat,
+ "latency": self.get_latency_stat,
+ }
+ if mode not in list(methods.keys()):
+ msg = "not support mode <{0}>".format(mode)
+ raise Exception(msg)
+ # get custom mode stat
+ func = methods.get(mode)
+ stats = func(ports)
+
+ return stats
+
+
+class IxiaPacketGenerator(PacketGenerator):
+ """
+ Ixia packet generator
+ """
+
+ def __init__(self, tester):
+ super(IxiaPacketGenerator, self).__init__(tester)
+ # ixia management
+ self.pktgen_type = PKTGEN_IXIA
+ self._conn = None
+ # ixia configuration information of dts
+ conf_inst = self._get_generator_conf_instance()
+ self.conf = conf_inst.load_pktgen_config()
+ # ixia port configuration
+ self._traffic_opt = {}
+ self._traffic_ports = []
+ self._ports = []
+ self._rx_ports = []
+ # statistics management
+ self.runtime_stats = {}
+ # check configuration options
+ self.options_keys = ["txmode", "ip", "vlan", "transmit_mode", "rate"]
+ self.ip_keys = [
+ "start",
+ "end",
+ "action",
+ "step",
+ "mask",
+ ]
+ self.vlan_keys = [
+ "start",
+ "end",
+ "action",
+ "step",
+ "count",
+ ]
+
+ self.tester = tester
+
+ def get_ports(self):
+ """only used for ixia packet generator"""
+ return self._conn.get_ports()
+
+ def _prepare_generator(self):
+ """start ixia server"""
+ try:
+ self._connect(self.tester, self.conf)
+ except Exception as e:
+ msg = "failed to connect to ixia server"
+ raise Exception(msg)
+
+ def _connect(self, tester, conf):
+ # initialize ixia class
+ self._conn = Ixia(tester, conf, self.logger)
+ for p in self._conn.get_ports():
+ self._ports.append(p)
+
+ self.logger.debug(self._ports)
+
+ def _disconnect(self):
+ """
+ disconnect with ixia server
+ """
+ try:
+ self._remove_all_streams()
+ self._conn.disconnect()
+ except Exception as e:
+ msg = "Error disconnecting: %s" % e
+ self.logger.error(msg)
+ self._conn = None
+
+ def _get_port_pci(self, port_id):
+ """
+ get ixia port pci address
+ """
+ for pktgen_port_id, info in enumerate(self._ports):
+ if pktgen_port_id == port_id:
+ _pci = info.get("pci")
+ return _pci
+ else:
+ return None
+
+ def _get_gen_port(self, pci):
+ """
+ get port management id of the packet generator
+ """
+ for pktgen_port_id, info in enumerate(self._ports):
+ _pci = info.get("pci")
+ if _pci == pci:
+ return pktgen_port_id
+ else:
+ return -1
+
+ def _is_gen_port(self, pci):
+ """
+ check if a pci address is managed by the packet generator
+ """
+ for name, _port_obj in self._conn.ports.items():
+ _pci = _port_obj.info["pci_addr"]
+ self.logger.debug((_pci, pci))
+ if _pci == pci:
+ return True
+ else:
+ return False
+
+ def _get_ports(self):
+ """
+ Return self ports information
+ """
+ ports = []
+ for idx in range(len(self._ports)):
+ ports.append("IXIA:%d" % idx)
+ return ports
+
+ @property
+ def _vm_conf(self):
+ # close it and wait for more discussion about pktgen framework
+ return None
+ conf = {}
+ # get the subnet range of src and dst ip
+ if "ip_src" in self.conf:
+ conf["src"] = {}
+ ip_src = self.conf["ip_src"]
+ ip_src_range = ip_src.split("-")
+ conf["src"]["start"] = ip_src_range[0]
+ conf["src"]["end"] = ip_src_range[1]
+
+ if "ip_dst" in self.conf:
+ conf["dst"] = {}
+ ip_dst = self.conf["ip_dst"]
+ ip_dst_range = ip_dst.split("-")
+ conf["dst"]["start"] = ip_dst_range[0]
+ conf["dst"]["end"] = ip_dst_range[1]
+
+ return conf if conf else None
+
+ def _clear_streams(self):
+ """clear streams in `PacketGenerator`"""
+ # if streams has been attached, remove them from trex server.
+ self._remove_all_streams()
+
+ def _remove_all_streams(self):
+ """
+ remove all stream deployed on the packet generator
+ """
+ if not self.get_streams():
+ return
+ self._conn.remove_all_streams()
+
+ def _get_port_features(self, port_id):
+ """get ports features"""
+ ports = self._conn.ports
+ if port_id not in ports:
+ return None
+ features = self._conn.ports[port_id].get_formatted_info()
+
+ return features
+
+ def _is_support_flow_control(self, port_id):
+ """check if a port support flow control"""
+ features = self._get_port_features(port_id)
+ if not features or features.get("fc_supported") == "no":
+ return False
+ else:
+ return True
+
+ def _preset_ixia_port(self):
+ """set ports flow_ctrl attribute"""
+ rx_ports = self._rx_ports
+ flow_ctrl_opt = self._traffic_opt.get("flow_control")
+ if not flow_ctrl_opt:
+ return
+ # flow control of port running trex traffic
+ self._conn.config_port_flow_control(rx_ports, flow_ctrl_opt)
+
+ def _throughput_stats(self, stream, stats):
+ """convert ixia throughput statistics format to dts PacketGenerator format"""
+ # tx packet
+ tx_port_id = stream["tx_port"]
+ port_stats = stats.get(tx_port_id)
+ if not port_stats:
+ msg = "failed to get tx_port {0} statistics".format(tx_port_id)
+ raise Exception(msg)
+ tx_bps = port_stats.get("tx_bps")
+ tx_pps = port_stats.get("tx_pps")
+ msg = [
+ "Tx Port %d stats: " % (tx_port_id),
+ "tx_port: %d, tx_bps: %f, tx_pps: %f " % (tx_port_id, tx_bps, tx_pps),
+ ]
+ self.logger.debug(pformat(port_stats))
+ self.logger.debug(os.linesep.join(msg))
+ # rx bps/pps
+ rx_port_id = stream["rx_port"]
+ port_stats = stats.get(rx_port_id)
+ if not port_stats:
+ msg = "failed to get rx_port {0} statistics".format(rx_port_id)
+ raise Exception(msg)
+ rx_bps = port_stats.get("rx_bps")
+ rx_pps = port_stats.get("rx_pps")
+ msg = [
+ "Rx Port %d stats: " % (rx_port_id),
+ "rx_port: %d, rx_bps: %f, rx_pps: %f" % (rx_port_id, rx_bps, rx_pps),
+ ]
+
+ self.logger.debug(pformat(port_stats))
+ self.logger.debug(os.linesep.join(msg))
+
+ return rx_bps, rx_pps
+
+ def _loss_rate_stats(self, stream, stats):
+ """convert ixia loss rate statistics format to dts PacketGenerator format"""
+ # tx packet
+ port_id = stream.get("tx_port")
+ if port_id in list(stats.keys()):
+ port_stats = stats[port_id]
+ else:
+ msg = "port {0} statistics is not found".format(port_id)
+ self.logger.error(msg)
+ return None
+ msg = "Tx Port %d stats: " % (port_id)
+ self.logger.debug(msg)
+ opackets = port_stats["opackets"]
+ # rx packet
+ port_id = stream.get("rx_port")
+ port_stats = stats[port_id]
+ msg = "Rx Port %d stats: " % (port_id)
+ self.logger.debug(msg)
+ ipackets = port_stats["ipackets"]
+
+ return opackets, ipackets
+
+ def _latency_stats(self, stream, stats):
+ """convert ixia latency statistics format to dts PacketGenerator format"""
+ port_id = stream.get("tx_port")
+ if port_id in list(stats.keys()):
+ port_stats = stats[port_id]
+ else:
+ msg = "port {0} latency stats is not found".format(port_id)
+ self.logger.error(msg)
+ return None
+
+ latency_stats = {
+ "min": port_stats.get("total_min"),
+ "max": port_stats.get("total_max"),
+ "average": port_stats.get("average"),
+ }
+
+ return latency_stats
+
+ def send_ping6(self, pci, mac, ipv6):
+ """Send ping6 packet from IXIA ports."""
+ return self._conn.send_ping6(pci, mac, ipv6)
+
+ ##########################################################################
+ #
+ # class ``PacketGenerator`` abstract methods should be implemented here
+ #
+ ##########################################################################
+ def _prepare_transmission(self, stream_ids=[], latency=False):
+ """add one/multiple streams in one/multiple ports"""
+ port_config = {}
+
+ for stream_id in stream_ids:
+ stream = self._get_stream(stream_id)
+ tx_port = stream.get("tx_port")
+ rx_port = stream.get("rx_port")
+ pcap_file = stream.get("pcap_file")
+ # save port id list
+ if tx_port not in self._traffic_ports:
+ self._traffic_ports.append(tx_port)
+ if rx_port not in self._traffic_ports:
+ self._traffic_ports.append(rx_port)
+ if rx_port not in self._rx_ports:
+ self._rx_ports.append(rx_port)
+ # set all streams in one port to do batch configuration
+ options = stream["options"]
+ if tx_port not in list(port_config.keys()):
+ port_config[tx_port] = []
+ config = {}
+ config.update(options)
+ # In pktgen, all streams flow control option are the same by design.
+ self._traffic_opt["flow_control"] = options.get("flow_control") or {}
+ # if vm config by pktgen config file, set it here to take the place
+ # of setting on suite
+ if self._vm_conf: # TBD, remove this process later
+ config["fields_config"] = self._vm_conf
+ # get stream rate percent
+ stream_config = options.get("stream_config")
+ rate_percent = stream_config.get("rate")
+ # set port list input parameter of ixia class
+ ixia_option = [tx_port, rx_port, pcap_file, options]
+ port_config[tx_port].append(ixia_option)
+
+ if not port_config:
+ msg = "no stream options for ixia packet generator"
+ raise Exception(msg)
+ # -------------------------------------------------------------------
+ port_lists = []
+ for port_id, option in port_config.items():
+ port_lists += option
+ self._conn.clear_tcl_buffer()
+ rxPortlist, txPortlist = self._conn.prepare_port_list(
+ port_lists, rate_percent or 100, latency
+ )
+ self._conn.prepare_ixia_for_transmission(txPortlist, rxPortlist)
+ # preset port status before running traffic
+ self._preset_ixia_port()
+
+ def _start_transmission(self, stream_ids, options={}):
+ # get rate percentage
+ rate_percent = options.get("rate")
+ if rate_percent:
+ msg = (
+ "{0} only support set rate percent in streams, "
+ "current run traffic with stream rate percent"
+ ).format(self.pktgen_type)
+ self.logger.warning(msg)
+ # run ixia server
+ try:
+ ###########################################
+ # Start traffic on port(s)
+ self.logger.info("begin traffic ......")
+ run_opt = {
+ "ports": self._traffic_ports,
+ "mult": rate_percent,
+ "force": True,
+ }
+ self._conn.start(**run_opt)
+ except Exception as e:
+ self.logger.error(e)
+
+ def _stop_transmission(self, stream_id):
+ # using ixia server command
+ if self._traffic_ports:
+ self._conn.stop_transmit()
+ self.logger.info("traffic completed. ")
+
+ def _retrieve_port_statistic(self, stream_id, mode):
+ """ixia traffic statistics"""
+ stats = self._conn.get_stats(self._traffic_ports, mode)
+ stream = self._get_stream(stream_id)
+ self.logger.debug(pformat(stream))
+ self.logger.debug(pformat(stats))
+ if mode == "throughput":
+ return self._throughput_stats(stream, stats)
+ elif mode == "loss":
+ return self._loss_rate_stats(stream, stats)
+ elif mode == "latency":
+ return self._latency_stats(stream, stats)
+ else:
+ msg = "not support mode <{0}>".format(mode)
+ raise Exception(msg)
+
+ def _check_options(self, opts={}):
+ # remove it to upper level class and wait for more discussion about
+ # pktgen framework
+ return True
+ for key in opts:
+ if key in self.options_keys:
+ if key == "ip":
+ ip = opts["ip"]
+ for ip_key in ip:
+ if not ip_key in self.ip_keys:
+ msg = " %s is invalid ip option" % ip_key
+ self.logger.info(msg)
+ return False
+ if key == "action":
+ if not ip[key] == "inc" or not ip[key] == "dec":
+ msg = " %s is invalid ip action" % ip[key]
+ self.logger.info(msg)
+ return False
+ elif key == "vlan":
+ vlan = opts["vlan"]
+ for vlan_key in vlan:
+ if not vlan_key in self.vlan_keys:
+ msg = " %s is invalid vlan option" % vlan_key
+ self.logger.info(msg)
+ return False
+ if key == "action":
+ if not vlan[key] == "inc" or not ip[key] == "dec":
+ msg = " %s is invalid vlan action" % vlan[key]
+ self.logger.info(msg)
+ return False
+ else:
+ msg = " %s is invalid option" % key
+ self.logger.info(msg)
+ return False
+ return True
+
+ def quit_generator(self):
+ """close ixia session"""
+ if self._conn is not None:
+ self._disconnect()
+ return
--
2.20.1
next prev parent reply other threads:[~2022-04-06 15:05 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-04-06 15:04 [RFC PATCH v1 00/18] merge DTS component files " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 01/18] dts: merge DTS framework/crb.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 02/18] dts: merge DTS framework/dut.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 03/18] dts: merge DTS framework/ixia_buffer_parser.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 04/18] dts: merge DTS framework/pktgen.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 05/18] dts: merge DTS framework/pktgen_base.py " Juraj Linkeš
2022-04-06 15:04 ` Juraj Linkeš [this message]
2022-04-06 15:04 ` [RFC PATCH v1 07/18] dts: merge DTS framework/pktgen_ixia_network.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 08/18] dts: merge DTS framework/pktgen_trex.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 09/18] dts: merge DTS framework/ssh_connection.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 10/18] dts: merge DTS framework/ssh_pexpect.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 11/18] dts: merge DTS framework/tester.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 12/18] dts: merge DTS framework/ixia_network/__init__.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 13/18] dts: merge DTS framework/ixia_network/ixnet.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 14/18] dts: merge DTS framework/ixia_network/ixnet_config.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 15/18] dts: merge DTS framework/ixia_network/ixnet_stream.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 16/18] dts: merge DTS framework/ixia_network/packet_parser.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 17/18] dts: merge DTS nics/__init__.py " Juraj Linkeš
2022-04-06 15:04 ` [RFC PATCH v1 18/18] dts: merge DTS nics/net_device.py " Juraj Linkeš
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220406150440.2914464-7-juraj.linkes@pantheon.tech \
--to=juraj.linkes@pantheon.tech \
--cc=Honnappa.Nagarahalli@arm.com \
--cc=david.marchand@redhat.com \
--cc=dev@dpdk.org \
--cc=lijuan.tu@intel.com \
--cc=ohilyard@iol.unh.edu \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).