aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--INFO4
-rw-r--r--conf/03_traffic.conf12
-rw-r--r--conf/10_custom.conf14
-rw-r--r--core/traffic_controller_rfc2544.py5
-rw-r--r--docs/configguide/trafficgen.rst45
-rw-r--r--docs/requirements/vswitchperf_ltd.rst10
-rw-r--r--src/package-list.mk6
-rw-r--r--testcases/integration.py2
-rw-r--r--testcases/testcase.py8
-rw-r--r--tools/pkt_gen/moongen/__init__.py13
-rw-r--r--tools/pkt_gen/moongen/moongen.py753
-rw-r--r--tools/pkt_gen/xena/XenaDriver.py1129
-rw-r--r--tools/pkt_gen/xena/profiles/baseconfig.x25442
-rwxr-xr-xtools/pkt_gen/xena/xena.py385
-rw-r--r--tools/pkt_gen/xena/xena_json.py39
-rwxr-xr-xvsperf4
16 files changed, 2350 insertions, 81 deletions
diff --git a/INFO b/INFO
index 85150b88..9a8889be 100644
--- a/INFO
+++ b/INFO
@@ -13,6 +13,10 @@ Commiters:
maryam.tahhan@intel.com
Eugene.Snider@huawei.com
acmorton@att.com
+ctrautma@redhat.com
+martinx.klozik@intel.com
+sridhar.rao@spirent.com
+bmichalo@redhat.com
Link to TSC approval of the project: http://meetbot.opnfv.org/meetings/opnfv-meeting/
Link(s) to approval of additional submitters:
diff --git a/conf/03_traffic.conf b/conf/03_traffic.conf
index 9937294b..01e3e5cf 100644
--- a/conf/03_traffic.conf
+++ b/conf/03_traffic.conf
@@ -28,6 +28,7 @@ TRAFFICGEN = 'Dummy'
#TRAFFICGEN = 'IxNet'
#TRAFFICGEN = 'Ixia'
#TRAFFICGEN = 'Xena'
+#TRAFFICGEN = 'MoonGen'
# List of packet sizes to send.
# Expand like this: (64, 128, 256, 512, 1024)
@@ -177,3 +178,14 @@ TRAFFICGEN_XENA_PORT0_GATEWAY = '192.168.199.1'
TRAFFICGEN_XENA_PORT1_IP = '192.168.199.11'
TRAFFICGEN_XENA_PORT1_CIDR = 24
TRAFFICGEN_XENA_PORT1_GATEWAY = '192.168.199.1'
+
+###################################################
+# MoonGen Configuration and Connection Info-- BEGIN
+
+TRAFFICGEN_MOONGEN_HOST_IP_ADDR = ''
+TRAFFICGEN_MOONGEN_USER = ''
+TRAFFICGEN_MOONGEN_BASE_DIR = ''
+TRAFFICGEN_MOONGEN_PORTS = ''
+
+# MoonGen Configuration and Connection Info-- END
+###################################################
diff --git a/conf/10_custom.conf b/conf/10_custom.conf
index 4c9341a4..4ffe470e 100644
--- a/conf/10_custom.conf
+++ b/conf/10_custom.conf
@@ -20,6 +20,7 @@ TRAFFICGEN = 'Dummy'
#TRAFFICGEN = 'IxNet'
#TRAFFICGEN = 'Ixia'
#TRAFFICGEN = 'Xena'
+#TRAFFICGEN = 'MoonGen'
###########################################
# Spirent TestCenter Configuration -- BEGIN
@@ -78,8 +79,19 @@ TRAFFICGEN_XENA_PORT1_IP = '192.168.199.11'
TRAFFICGEN_XENA_PORT1_CIDR = 24
TRAFFICGEN_XENA_PORT1_GATEWAY = '192.168.199.1'
-TEST_PARAMS = {'pkt_sizes':'64'}
+###################################################
+# MoonGen Configuration and Connection Info-- BEGIN
+# Ex: TRAFFICGEN_MOONGEN_HOST_IP_ADDR = "192.10.1.1"
+TRAFFICGEN_MOONGEN_HOST_IP_ADDR = ""
+TRAFFICGEN_MOONGEN_USER = "root"
+TRAFFICGEN_MOONGEN_BASE_DIR = "/root/MoonGen"
+TRAFFICGEN_MOONGEN_PORTS = "{0,1}"
+
+# MoonGen Configuration and Connection Info-- END
+###################################################
+
+#TEST_PARAMS = {'pkt_sizes':'64'}
OPNFV_INSTALLER = "Fuel"
OPNFV_URL = "http://testresults.opnfv.org/testapi"
PACKAGE_LIST = "src/package-list.mk"
diff --git a/core/traffic_controller_rfc2544.py b/core/traffic_controller_rfc2544.py
index 2630101f..81e499cd 100644
--- a/core/traffic_controller_rfc2544.py
+++ b/core/traffic_controller_rfc2544.py
@@ -41,6 +41,7 @@ class TrafficControllerRFC2544(ITrafficController, IResults):
self._traffic_started_call_count = 0
self._trials = int(get_test_param('rfc2544_trials', 1))
self._duration = int(get_test_param('duration', 30))
+ self._lossrate = float(get_test_param('lossrate', 0.0))
self._results = []
# If set, comma separated packet_sizes value from --test_params
@@ -100,13 +101,13 @@ class TrafficControllerRFC2544(ITrafficController, IResults):
if traffic['traffic_type'] == 'back2back':
result = self._traffic_gen_class.send_rfc2544_back2back(
- traffic, trials=self._trials, duration=self._duration)
+ traffic, trials=self._trials, duration=self._duration, lossrate=self._lossrate)
elif traffic['traffic_type'] == 'continuous':
result = self._traffic_gen_class.send_cont_traffic(
traffic, duration=self._duration)
else:
result = self._traffic_gen_class.send_rfc2544_throughput(
- traffic, trials=self._trials, duration=self._duration)
+ traffic, trials=self._trials, duration=self._duration, lossrate=self._lossrate)
result = TrafficControllerRFC2544._append_results(result,
packet_size)
diff --git a/docs/configguide/trafficgen.rst b/docs/configguide/trafficgen.rst
index f612569f..63560b9c 100644
--- a/docs/configguide/trafficgen.rst
+++ b/docs/configguide/trafficgen.rst
@@ -15,6 +15,7 @@ VSPERF supports the following traffic generators:
* IXIA (IxNet and IxOS)
* Spirent TestCenter
* Xena Networks
+ * MoonGen
To see the list of traffic gens from the cli:
@@ -253,6 +254,8 @@ folder. Contact Xena Networks for the latest version of this file. The user
can also visit www.xenanetworks/downloads to obtain the file with a valid
support contract.
+**Note** VSPerf has been fully tested with version v2.43 of Xena2544.exe
+
To execute the Xena2544.exe file under Linux distributions the mono-complete
package must be installed. To install this package follow the instructions
below. Further information can be obtained from
@@ -287,3 +290,45 @@ set to allow for proper connections to the chassis.
TRAFFICGEN_XENA_PASSWORD = ''
TRAFFICGEN_XENA_MODULE1 = ''
TRAFFICGEN_XENA_MODULE2 = ''
+
+
+MoonGen
+-------
+
+Installation
+~~~~~~~~~~~~
+
+MoonGen architecture overview and general installation instructions
+can be found here:
+
+https://github.com/emmericp/MoonGen
+
+For VSPerf use, MoonGen should be cloned from here (as opposed to the afore
+mentioned GitHub):
+
+git clone https://github.com/atheurer/MoonGen
+
+and use the opnfv-stable branch:
+
+git checkout opnfv-stable
+
+VSPerf uses a particular example script under the examples directory within
+the MoonGen project:
+
+MoonGen/examples/opnfv-vsperf.lua
+
+Follow MoonGen set up instructions here:
+
+https://github.com/atheurer/MoonGen/blob/opnfv-stable/MoonGenSetUp.html
+
+Note one will need to set up ssh login to not use passwords between the server
+running MoonGen and the device under test (running the VSPERF test
+infrastructure). This is because VSPERF on one server uses 'ssh' to
+configure and run MoonGen upon the other server.
+
+One can set up this ssh access by doing the following on both servers:
+
+.. code-block:: console
+
+ ssh-keygen -b 2048 -t rsa
+ ssh-copy-id <other server>
diff --git a/docs/requirements/vswitchperf_ltd.rst b/docs/requirements/vswitchperf_ltd.rst
index 1ac56470..6b882290 100644
--- a/docs/requirements/vswitchperf_ltd.rst
+++ b/docs/requirements/vswitchperf_ltd.rst
@@ -1285,11 +1285,11 @@ Test ID: LTD.Throughput.RFC2544.Profile
to the DUT's RFC 2544 Throughput as determined by
LTD.Throughput.RFC2544.PacketLoss Ratio (0% Packet Loss case). A delta
of 0% is equivalent to an offered traffic rate equal to the RFC 2544
- Throughput; A delta of +50% indicates an offered rate half-way
- between the Throughput and line-rate, whereas a delta of
- -50% indicates an offered rate of half the maximum rate. Therefore the
- range of the delta figure is natuarlly bounded at -100% (zero offered
- traffic) and +100% (traffic offered at line rate).
+ Maximum Throughput; A delta of +50% indicates an offered rate half-way
+ between the Maximum RFC2544 Throughput and line-rate, whereas a delta of
+ -50% indicates an offered rate of half the RFC 2544 Maximum Throughput.
+ Therefore the range of the delta figure is natuarlly bounded at -100%
+ (zero offered traffic) and +100% (traffic offered at line rate).
The following deltas to the maximum forwarding rate should be applied:
diff --git a/src/package-list.mk b/src/package-list.mk
index 8b09c424..5aa40bd2 100644
--- a/src/package-list.mk
+++ b/src/package-list.mk
@@ -6,14 +6,14 @@
# dpdk section
# DPDK_URL ?= git://dpdk.org/dpdk
DPDK_URL ?= http://dpdk.org/git/dpdk
-DPDK_TAG ?= v2.2.0
+DPDK_TAG ?= v16.04
# OVS section
OVS_URL ?= https://github.com/openvswitch/ovs
#The Tag below is for OVS v2.5.0 with backwards compatibility support for Qemu
#versions < 2.5.
-OVS_TAG ?= f3ea2ad27fd076735fdb78286980749bb12fe1ce
+OVS_TAG ?= 31871ee3839c35e6878debfc7926afa471dbdec6
# QEMU section
QEMU_URL ?= https://github.com/qemu/qemu.git
-QEMU_TAG ?= v2.3.0
+QEMU_TAG ?= v2.5.0
diff --git a/testcases/integration.py b/testcases/integration.py
index 9733c263..fdd8b09c 100644
--- a/testcases/integration.py
+++ b/testcases/integration.py
@@ -191,7 +191,7 @@ class IntegrationTestCase(TestCase):
results = OrderedDict()
results['status'] = 'OK' if self._inttest['status'] else 'FAILED'
results['details'] = self._inttest['details']
- TestCase._write_result_to_file([results], self._output_file)
+ TestCase.write_result_to_file([results], self._output_file)
self.report_status("Test '{}'".format(self.name), self._inttest['status'])
# inform vsperf about testcase failure
if not self._inttest['status']:
diff --git a/testcases/testcase.py b/testcases/testcase.py
index d76858d5..e5f8a14c 100644
--- a/testcases/testcase.py
+++ b/testcases/testcase.py
@@ -152,8 +152,8 @@ class TestCase(object):
# OVS Vanilla requires guest VM MAC address and IPs to work
if 'linux_bridge' in self.guest_loopback:
- self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
- 'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
+ self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
+ 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
@@ -245,7 +245,7 @@ class TestCase(object):
self._traffic_ctl.print_results()
self._tc_results = self._append_results(self._traffic_ctl.get_results())
- TestCase._write_result_to_file(self._tc_results, self._output_file)
+ TestCase.write_result_to_file(self._tc_results, self._output_file)
def run(self):
"""Run the test
@@ -392,7 +392,7 @@ class TestCase(object):
self._hugepages_mounted = False
@staticmethod
- def _write_result_to_file(results, output):
+ def write_result_to_file(results, output):
"""Write list of dictionaries to a CSV file.
Each element on list will create separate row in output file.
diff --git a/tools/pkt_gen/moongen/__init__.py b/tools/pkt_gen/moongen/__init__.py
new file mode 100644
index 00000000..562eb088
--- /dev/null
+++ b/tools/pkt_gen/moongen/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2016 Intel Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
diff --git a/tools/pkt_gen/moongen/moongen.py b/tools/pkt_gen/moongen/moongen.py
new file mode 100644
index 00000000..d6c09e5d
--- /dev/null
+++ b/tools/pkt_gen/moongen/moongen.py
@@ -0,0 +1,753 @@
+# Copyright 2016 Red Hat Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+# Contributors:
+# Bill Michalowski, Red Hat Inc.
+# Andrew Theurer, Red Hat Inc.
+"""
+Moongen Traffic Generator Model
+"""
+
+# python imports
+import logging
+from collections import OrderedDict
+import subprocess
+import re
+
+# VSPerf imports
+from conf import settings
+from core.results.results_constants import ResultsConstants
+from tools.pkt_gen.trafficgen.trafficgenhelper import (
+ TRAFFIC_DEFAULTS,
+ merge_spec)
+from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
+
+class Moongen(ITrafficGenerator):
+ """Moongen Traffic generator wrapper."""
+ _traffic_defaults = TRAFFIC_DEFAULTS.copy()
+ _logger = logging.getLogger(__name__)
+
+ def __init__(self):
+ """Moongen class constructor."""
+ self._logger.info("In moongen __init__ method")
+ self._params = {}
+ self._moongen_host_ip_addr = (
+ settings.getValue('TRAFFICGEN_MOONGEN_HOST_IP_ADDR'))
+ self._moongen_base_dir = (
+ settings.getValue('TRAFFICGEN_MOONGEN_BASE_DIR'))
+ self._moongen_user = settings.getValue('TRAFFICGEN_MOONGEN_USER')
+ self._moongen_ports = settings.getValue('TRAFFICGEN_MOONGEN_PORTS')
+
+ @property
+ def traffic_defaults(self):
+ """Default traffic values.
+
+ These can be expected to be constant across traffic generators,
+ so no setter is provided. Changes to the structure or contents
+ will likely break traffic generator implementations or tests
+ respectively.
+ """
+ self._logger.info("In moongen traffic_defaults method")
+ return self._traffic_defaults
+
+ def create_moongen_cfg_file(self, traffic, duration=60,
+ acceptable_loss_pct=1, one_shot=0):
+ """Create the MoonGen configuration file from VSPERF's traffic profile
+ :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
+ :param duration: The length of time to generate packet throughput
+ :param acceptable_loss: Maximum packet loss acceptable
+ :param one_shot: No RFC 2544 binary search,
+ just packet flow at traffic specifics
+ """
+ logging.debug("traffic['frame_rate'] = " + \
+ str(traffic['frame_rate']))
+
+ logging.debug("traffic['multistream'] = " + \
+ str(traffic['multistream']))
+
+ logging.debug("traffic['stream_type'] = " + \
+ str(traffic['stream_type']))
+
+ logging.debug("traffic['l2']['srcmac'] = " + \
+ str(traffic['l2']['srcmac']))
+
+ logging.debug("traffic['l2']['dstmac'] = " + \
+ str(traffic['l2']['dstmac']))
+
+ logging.debug("traffic['l3']['proto'] = " + \
+ str(traffic['l3']['proto']))
+
+ logging.debug("traffic['l3']['srcip'] = " + \
+ str(traffic['l3']['srcip']))
+
+ logging.debug("traffic['l3']['dstip'] = " + \
+ str(traffic['l3']['dstip']))
+
+ logging.debug("traffic['l4']['srcport'] = " + \
+ str(traffic['l4']['srcport']))
+
+ logging.debug("traffic['l4']['dstport'] = " + \
+ str(traffic['l4']['dstport']))
+
+ logging.debug("traffic['vlan']['enabled'] = " + \
+ str(traffic['vlan']['enabled']))
+
+ logging.debug("traffic['vlan']['id'] = " + \
+ str(traffic['vlan']['id']))
+
+ logging.debug("traffic['vlan']['priority'] = " + \
+ str(traffic['vlan']['priority']))
+
+ logging.debug("traffic['vlan']['cfi'] = " + \
+ str(traffic['vlan']['cfi']))
+
+ logging.debug(traffic['l2']['framesize'])
+
+ out_file = open("opnfv-vsperf-cfg.lua", "wt")
+
+ out_file.write("VSPERF {\n")
+
+ out_file.write("testType = \"throughput\",\n")
+
+ out_file.write("runBidirec = " + \
+ traffic['bidir'].lower() + ",\n")
+
+ out_file.write("frameSize = " + \
+ str(traffic['l2']['framesize']) + ",\n")
+
+ out_file.write("srcMac = \"" + \
+ str(traffic['l2']['srcmac']) + "\",\n")
+
+ out_file.write("dstMac = \"" + \
+ str(traffic['l2']['dstmac']) + "\",\n")
+
+ out_file.write("srcIp = \"" + \
+ str(traffic['l3']['srcip']) + "\",\n")
+
+ out_file.write("dstIp = \"" + \
+ str(traffic['l3']['dstip']) + "\",\n")
+
+ out_file.write("vlanId = " + \
+ str(traffic['vlan']['id']) + ",\n")
+
+ out_file.write("searchRunTime = " + \
+ str(duration) + ",\n")
+
+ out_file.write("validationRunTime = " + \
+ str(duration) + ",\n")
+
+ out_file.write("acceptableLossPct = " + \
+ str(acceptable_loss_pct) + ",\n")
+
+ out_file.write("ports = " +\
+ str(self._moongen_ports) + ",\n")
+
+ if one_shot:
+ out_file.write("oneShot = true,\n")
+
+ # Assume 10G line rates at the moment. Need to convert VSPERF
+ # frame_rate (percentage of line rate) to Mpps for MoonGen
+
+ out_file.write("startRate = " + str((traffic['frame_rate'] / 100) * 14.88) + "\n")
+ out_file.write("}" + "\n")
+ out_file.close()
+
+ copy_moongen_cfg = "scp opnfv-vsperf-cfg.lua " + \
+ self._moongen_user + "@" + \
+ self._moongen_host_ip_addr + ":" + \
+ self._moongen_base_dir + \
+ "/. && rm opnfv-vsperf-cfg.lua"
+
+ find_moongen = subprocess.Popen(copy_moongen_cfg,
+ shell=True,
+ stderr=subprocess.PIPE)
+
+ output, error = find_moongen.communicate()
+
+ if error:
+ logging.error(output)
+ logging.error(error)
+ raise RuntimeError('MOONGEN: Error copying configuration file')
+
+ def connect(self):
+ """Connect to MoonGen traffic generator
+
+ Verify that MoonGen is on the system indicated by
+ the configuration file
+ """
+ self._logger.info("MOONGEN: In MoonGen connect method...")
+
+ if self._moongen_host_ip_addr:
+ cmd_ping = "ping -c1 " + self._moongen_host_ip_addr
+ else:
+ raise RuntimeError('MOONGEN: MoonGen host not defined')
+
+ ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
+ output, error = ping.communicate()
+
+ if ping.returncode:
+ self._logger.error(error)
+ self._logger.error(output)
+ raise RuntimeError('MOONGEN: Cannot ping MoonGen host at ' + \
+ self._moongen_host_ip_addr)
+
+ connect_moongen = "ssh " + self._moongen_user + \
+ "@" + self._moongen_host_ip_addr
+
+ cmd_find_moongen = connect_moongen + " ls " + \
+ self._moongen_base_dir + "/examples/opnfv-vsperf.lua"
+
+ find_moongen = subprocess.Popen(cmd_find_moongen,
+ shell=True,
+ stderr=subprocess.PIPE)
+
+ output, error = find_moongen.communicate()
+
+ if find_moongen.returncode:
+ self._logger.error(error)
+ self._logger.error(output)
+ raise RuntimeError(
+ 'MOONGEN: Cannot locate MoonGen program at %s within %s' \
+ % (self._moongen_host_ip_addr, self._moongen_base_dir))
+
+ self._logger.info("MOONGEN: MoonGen host successfully found...")
+
+ def disconnect(self):
+ """Disconnect from the traffic generator.
+
+ As with :func:`connect`, this function is optional.
+
+ Where implemented, this function should raise an exception on
+ failure.
+
+ :returns: None
+ """
+ self._logger.info("MOONGEN: In moongen disconnect method")
+
+ def send_burst_traffic(self, traffic=None, numpkts=100, duration=20):
+ """Send a burst of traffic.
+
+ Send a ``numpkts`` packets of traffic, using ``traffic``
+ configuration, with a timeout of ``time``.
+
+ :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
+ :param numpkts: Number of packets to send
+ :param duration: Time to wait to receive packets
+
+ :returns: dictionary of strings with following data:
+ - List of Tx Frames,
+ - List of Rx Frames,
+ - List of Tx Bytes,
+ - List of List of Rx Bytes,
+ - Payload Errors and Sequence Errors.
+ """
+ self._logger.info("In moongen send_burst_traffic method")
+ return NotImplementedError('Moongen Burst traffic not implemented')
+
+ def send_cont_traffic(self, traffic=None, duration=20):
+ """Send a continuous flow of traffic
+
+ Send packets at ``frame rate``, using ``traffic`` configuration,
+ until timeout ``time`` occurs.
+
+ :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
+ :param duration: Time to wait to receive packets (secs)
+ :returns: dictionary of strings with following data:
+ - Tx Throughput (fps),
+ - Rx Throughput (fps),
+ - Tx Throughput (mbps),
+ - Rx Throughput (mbps),
+ - Tx Throughput (% linerate),
+ - Rx Throughput (% linerate),
+ - Min Latency (ns),
+ - Max Latency (ns),
+ - Avg Latency (ns)
+ """
+ self._logger.info("In moongen send_cont_traffic method")
+
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
+
+ Moongen.create_moongen_cfg_file(self,
+ traffic,
+ duration=duration,
+ acceptable_loss_pct=100.0,
+ one_shot=1)
+
+ collected_results = Moongen.run_moongen_and_collect_results(self,
+ test_run=1)
+
+ total_throughput_rx_fps = (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS]))
+
+ total_throughput_rx_mbps = (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_MBPS]))
+
+ total_throughput_rx_pct = (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT]))
+
+ total_throughput_tx_fps = (
+ float(collected_results[ResultsConstants.TX_RATE_FPS]))
+
+ total_throughput_tx_mbps = (
+ float(collected_results[ResultsConstants.TX_RATE_MBPS]))
+
+ total_throughput_tx_pct = (
+ float(collected_results[ResultsConstants.TX_RATE_PERCENT]))
+
+ total_min_latency_ns = 0
+ total_max_latency_ns = 0
+ total_avg_latency_ns = 0
+
+ results = OrderedDict()
+ results[ResultsConstants.THROUGHPUT_RX_FPS] = (
+ '{:,.6f}'.format(total_throughput_rx_fps))
+
+ results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
+ '{:,.3f}'.format(total_throughput_rx_mbps))
+
+ results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
+ '{:,.3f}'.format(total_throughput_rx_pct))
+
+ results[ResultsConstants.TX_RATE_FPS] = (
+ '{:,.6f}'.format(total_throughput_tx_fps))
+
+ results[ResultsConstants.TX_RATE_MBPS] = (
+ '{:,.3f}'.format(total_throughput_tx_mbps))
+
+ results[ResultsConstants.TX_RATE_PERCENT] = (
+ '{:,.3f}'.format(total_throughput_tx_pct))
+
+ results[ResultsConstants.MIN_LATENCY_NS] = (
+ '{:,.3f}'.format(total_min_latency_ns))
+
+ results[ResultsConstants.MAX_LATENCY_NS] = (
+ '{:,.3f}'.format(total_max_latency_ns))
+
+ results[ResultsConstants.AVG_LATENCY_NS] = (
+ '{:,.3f}'.format(total_avg_latency_ns))
+
+ return results
+
+ def start_cont_traffic(self, traffic=None, duration=20):
+ """ Non-blocking version of 'send_cont_traffic'.
+
+ Start transmission and immediately return. Do not wait for
+ results.
+ :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
+ :param duration: Time to wait to receive packets (secs)
+ """
+ self._logger.info("In moongen start_cont_traffic method")
+ return NotImplementedError('Moongen continuous traffic not implemented')
+
+ def stop_cont_traffic(self):
+ # Stop continuous transmission and return results.
+ self._logger.info("In moongen stop_cont_traffic method")
+
+ def run_moongen_and_collect_results(self, test_run=1):
+ """Execute MoonGen and transform results into VSPERF format
+ :param test_run: The number of tests to run
+ """
+ # Start MoonGen and create logfile of the run
+ connect_moongen = "ssh " + self._moongen_user + "@" + \
+ self._moongen_host_ip_addr
+
+ cmd_moongen = " 'cd " + self._moongen_base_dir + \
+ "; ./build/MoonGen examples/opnfv-vsperf.lua | tee moongen_log.txt'"
+
+ cmd_start_moongen = connect_moongen + cmd_moongen
+
+ start_moongen = subprocess.Popen(cmd_start_moongen,
+ shell=True, stderr=subprocess.PIPE)
+
+ output, error = start_moongen.communicate()
+
+ if start_moongen.returncode:
+ logging.debug(error)
+ logging.debug(output)
+ raise RuntimeError(
+ 'MOONGEN: Error starting MoonGen program at %s within %s' \
+ % (self._moongen_host_ip_addr, self._moongen_base_dir))
+
+ cmd_moongen = "mkdir -p /tmp/moongen/" + str(test_run)
+
+ moongen_create_log_dir = subprocess.Popen(cmd_moongen,
+ shell=True,
+ stderr=subprocess.PIPE)
+
+ output, error = moongen_create_log_dir.communicate()
+
+ if moongen_create_log_dir.returncode:
+ logging.debug(error)
+ logging.debug(output)
+ raise RuntimeError(
+ 'MOONGEN: Error obtaining MoonGen log from %s within %s' \
+ % (self._moongen_host_ip_addr, self._moongen_base_dir))
+
+ cmd_moongen = " scp " + self._moongen_user + "@" + \
+ self._moongen_host_ip_addr + ":" + \
+ self._moongen_base_dir + "/moongen_log.txt /tmp/moongen/" + \
+ str(test_run) + "/moongen-run.log"
+
+ copy_moongen_log = subprocess.Popen(cmd_moongen,
+ shell=True,
+ stderr=subprocess.PIPE)
+
+ output, error = copy_moongen_log.communicate()
+
+ if copy_moongen_log.returncode:
+ logging.debug(error)
+ logging.debug(output)
+ raise RuntimeError(
+ 'MOONGEN: Error obtaining MoonGen log from %s within %s' \
+ % (self._moongen_host_ip_addr, self._moongen_base_dir))
+
+ log_file = "/tmp/moongen/" + str(test_run) + "/moongen-run.log"
+
+ with open(log_file, 'r') as logfile_handle:
+ mytext = logfile_handle.read()
+
+ # REPORT results line
+ # match.group(1) = Tx frames
+ # match.group(2) = Rx frames
+ # match.group(3) = Frame loss (count)
+ # match.group(4) = Frame loss (percentage)
+ # match.group(5) = Tx Mpps
+ # match.group(6) = Rx Mpps
+ search_pattern = re.compile(
+ r'\[REPORT\]\s+total\:\s+'
+ r'Tx\s+frames\:\s+(\d+)\s+'
+ r'Rx\s+Frames\:\s+(\d+)\s+'
+ r'frame\s+loss\:\s+(\d+)\,'
+ r'\s+(\d+\.\d+|\d+)%\s+'
+ r'Tx\s+Mpps\:\s+(\d+.\d+|\d+)\s+'
+ r'Rx\s+Mpps\:\s+(\d+\.\d+|\d+)',
+ re.IGNORECASE)
+
+ results_match = search_pattern.search(mytext)
+
+ if not results_match:
+ logging.error('There was a problem parsing ' +\
+ 'MoonGen REPORT section of MoonGen log file')
+
+ moongen_results = OrderedDict()
+ moongen_results[ResultsConstants.THROUGHPUT_RX_FPS] = 0
+ moongen_results[ResultsConstants.THROUGHPUT_RX_MBPS] = 0
+ moongen_results[ResultsConstants.THROUGHPUT_RX_PERCENT] = 0
+ moongen_results[ResultsConstants.TX_RATE_FPS] = 0
+ moongen_results[ResultsConstants.TX_RATE_MBPS] = 0
+ moongen_results[ResultsConstants.TX_RATE_PERCENT] = 0
+ moongen_results[ResultsConstants.B2B_TX_COUNT] = 0
+ moongen_results[ResultsConstants.B2B_FRAMES] = 0
+ moongen_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = 0
+ moongen_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = 0
+
+ # find PARAMETERS line
+ # parameters_match.group(1) = Frame size
+
+ search_pattern = re.compile(
+ r'\[PARAMETERS\]\s+.*frameSize\:\s+(\d+)',
+ flags=re.IGNORECASE)
+ parameters_match = search_pattern.search(mytext)
+
+ if parameters_match:
+ frame_size = int(parameters_match.group(1))
+ else:
+ logging.error('There was a problem parsing MoonGen ' +\
+ 'PARAMETERS section of MoonGen log file')
+ frame_size = 0
+
+ if results_match and parameters_match:
+ # Assume for now 10G link speed
+ max_theoretical_mfps = (
+ (10000000000 / 8) / (frame_size + 20))
+
+ moongen_results[ResultsConstants.THROUGHPUT_RX_FPS] = (
+ float(results_match.group(6)) * 1000000)
+
+ moongen_results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
+ (float(results_match.group(6)) * frame_size + 20) * 8)
+
+ moongen_results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
+ float(results_match.group(6)) * \
+ 1000000 / max_theoretical_mfps * 100)
+
+ moongen_results[ResultsConstants.TX_RATE_FPS] = (
+ float(results_match.group(5)) * 1000000)
+
+ moongen_results[ResultsConstants.TX_RATE_MBPS] = (
+ float(results_match.group(5)) * (frame_size + 20) * 8)
+
+ moongen_results[ResultsConstants.TX_RATE_PERCENT] = (
+ float(results_match.group(5)) *
+ 1000000 / max_theoretical_mfps * 100)
+
+ moongen_results[ResultsConstants.B2B_TX_COUNT] = (
+ float(results_match.group(1)))
+
+ moongen_results[ResultsConstants.B2B_FRAMES] = (
+ float(results_match.group(2)))
+
+ moongen_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = (
+ float(results_match.group(3)))
+
+ moongen_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = (
+ float(results_match.group(4)))
+
+ return moongen_results
+
+ def send_rfc2544_throughput(self, traffic=None, duration=20,
+ lossrate=0.0, trials=1):
+ #
+ # Send traffic per RFC2544 throughput test specifications.
+ #
+ # Send packets at a variable rate, using ``traffic``
+ # configuration, until minimum rate at which no packet loss is
+ # detected is found.
+ #
+ # :param traffic: Detailed "traffic" spec, see design docs for details
+ # :param trials: Number of trials to execute
+ # :param duration: Per iteration duration
+ # :param lossrate: Acceptable lossrate percentage
+ # :returns: dictionary of strings with following data:
+ # - Tx Throughput (fps),
+ # - Rx Throughput (fps),
+ # - Tx Throughput (mbps),
+ # - Rx Throughput (mbps),
+ # - Tx Throughput (% linerate),
+ # - Rx Throughput (% linerate),
+ # - Min Latency (ns),
+ # - Max Latency (ns),
+ # - Avg Latency (ns)
+ #
+ self._logger.info("In moongen send_rfc2544_throughput method")
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
+ Moongen.create_moongen_cfg_file(self,
+ traffic,
+ duration=duration,
+ acceptable_loss_pct=lossrate)
+
+ total_throughput_rx_fps = 0
+ total_throughput_rx_mbps = 0
+ total_throughput_rx_pct = 0
+ total_throughput_tx_fps = 0
+ total_throughput_tx_mbps = 0
+ total_throughput_tx_pct = 0
+ total_min_latency_ns = 0
+ total_max_latency_ns = 0
+ total_avg_latency_ns = 0
+
+ for test_run in range(1, trials+1):
+ collected_results = (
+ Moongen.run_moongen_and_collect_results(self, test_run=test_run))
+
+ total_throughput_rx_fps += (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS]))
+
+ total_throughput_rx_mbps += (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_MBPS]))
+
+ total_throughput_rx_pct += (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT]))
+
+ total_throughput_tx_fps += (
+ float(collected_results[ResultsConstants.TX_RATE_FPS]))
+
+ total_throughput_tx_mbps += (
+ float(collected_results[ResultsConstants.TX_RATE_MBPS]))
+
+ total_throughput_tx_pct += (
+ float(collected_results[ResultsConstants.TX_RATE_PERCENT]))
+
+ # Latency not supported now, leaving as placeholder
+ total_min_latency_ns = 0
+ total_max_latency_ns = 0
+ total_avg_latency_ns = 0
+
+ results = OrderedDict()
+ results[ResultsConstants.THROUGHPUT_RX_FPS] = (
+ '{:,.6f}'.format(total_throughput_rx_fps / trials))
+
+ results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
+ '{:,.3f}'.format(total_throughput_rx_mbps / trials))
+
+ results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
+ '{:,.3f}'.format(total_throughput_rx_pct / trials))
+
+ results[ResultsConstants.TX_RATE_FPS] = (
+ '{:,.6f}'.format(total_throughput_tx_fps / trials))
+
+ results[ResultsConstants.TX_RATE_MBPS] = (
+ '{:,.3f}'.format(total_throughput_tx_mbps / trials))
+
+ results[ResultsConstants.TX_RATE_PERCENT] = (
+ '{:,.3f}'.format(total_throughput_tx_pct / trials))
+
+ results[ResultsConstants.MIN_LATENCY_NS] = (
+ '{:,.3f}'.format(total_min_latency_ns / trials))
+
+ results[ResultsConstants.MAX_LATENCY_NS] = (
+ '{:,.3f}'.format(total_max_latency_ns / trials))
+
+ results[ResultsConstants.AVG_LATENCY_NS] = (
+ '{:,.3f}'.format(total_avg_latency_ns / trials))
+
+ return results
+
+ def start_rfc2544_throughput(self, traffic=None, trials=3, duration=20,
+ lossrate=0.0):
+ """Non-blocking version of 'send_rfc2544_throughput'.
+
+ Start transmission and immediately return. Do not wait for
+ results.
+ """
+ self._logger.info(
+ "MOONGEN: In moongen start_rfc2544_throughput method")
+
+ def wait_rfc2544_throughput(self):
+ """Wait for and return results of RFC2544 test.
+ """
+ self._logger.info('In moongen wait_rfc2544_throughput')
+
+ def send_rfc2544_back2back(self, traffic=None, duration=60,
+ lossrate=0.0, trials=1):
+ """Send traffic per RFC2544 back2back test specifications.
+
+ Send packets at a fixed rate, using ``traffic``
+ configuration, for duration seconds.
+
+ :param traffic: Detailed "traffic" spec, see design docs for details
+ :param trials: Number of trials to execute
+ :param duration: Per iteration duration
+ :param lossrate: Acceptable loss percentage
+
+ :returns: Named tuple of Rx Throughput (fps), Rx Throughput (mbps),
+ Tx Rate (% linerate), Rx Rate (% linerate), Tx Count (frames),
+ Back to Back Count (frames), Frame Loss (frames), Frame Loss (%)
+ :rtype: :class:`Back2BackResult`
+ """
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
+
+ Moongen.create_moongen_cfg_file(self,
+ traffic,
+ duration=duration,
+ acceptable_loss_pct=lossrate)
+
+ results = OrderedDict()
+ results[ResultsConstants.B2B_RX_FPS] = 0
+ results[ResultsConstants.B2B_TX_FPS] = 0
+ results[ResultsConstants.B2B_RX_PERCENT] = 0
+ results[ResultsConstants.B2B_TX_PERCENT] = 0
+ results[ResultsConstants.B2B_TX_COUNT] = 0
+ results[ResultsConstants.B2B_FRAMES] = 0
+ results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = 0
+ results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = 0
+ results[ResultsConstants.SCAL_STREAM_COUNT] = 0
+ results[ResultsConstants.SCAL_STREAM_TYPE] = 0
+ results[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = 0
+
+ for test_run in range(1, trials+1):
+ collected_results = (
+ Moongen.run_moongen_and_collect_results(self, test_run=test_run))
+
+ results[ResultsConstants.B2B_RX_FPS] += (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS]))
+
+ results[ResultsConstants.B2B_RX_PERCENT] += (
+ float(collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT]))
+
+ results[ResultsConstants.B2B_TX_FPS] += (
+ float(collected_results[ResultsConstants.TX_RATE_FPS]))
+
+ results[ResultsConstants.B2B_TX_PERCENT] += (
+ float(collected_results[ResultsConstants.TX_RATE_PERCENT]))
+
+ results[ResultsConstants.B2B_TX_COUNT] += (
+ int(collected_results[ResultsConstants.B2B_TX_COUNT]))
+
+ results[ResultsConstants.B2B_FRAMES] += (
+ int(collected_results[ResultsConstants.B2B_FRAMES]))
+
+ results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] += (
+ int(collected_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES]))
+
+ results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] += (
+ int(collected_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT]))
+
+ # Calculate average results
+ results[ResultsConstants.B2B_RX_FPS] = (
+ results[ResultsConstants.B2B_RX_FPS] / trials)
+
+ results[ResultsConstants.B2B_RX_PERCENT] = (
+ results[ResultsConstants.B2B_RX_PERCENT] / trials)
+
+ results[ResultsConstants.B2B_TX_FPS] = (
+ results[ResultsConstants.B2B_TX_FPS] / trials)
+
+ results[ResultsConstants.B2B_TX_PERCENT] = (
+ results[ResultsConstants.B2B_TX_PERCENT] / trials)
+
+ results[ResultsConstants.B2B_TX_COUNT] = (
+ results[ResultsConstants.B2B_TX_COUNT] / trials)
+
+ results[ResultsConstants.B2B_FRAMES] = (
+ results[ResultsConstants.B2B_FRAMES] / trials)
+
+ results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = (
+ results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] / trials)
+
+ results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = (
+ results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] / trials)
+
+ results[ResultsConstants.SCAL_STREAM_COUNT] = 0
+ results[ResultsConstants.SCAL_STREAM_TYPE] = 0
+ results[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = 0
+
+ return results
+
+ def start_rfc2544_back2back(self, traffic=None, trials=1, duration=20,
+ lossrate=0.0):
+ #
+ # Non-blocking version of 'send_rfc2544_back2back'.
+ #
+ # Start transmission and immediately return. Do not wait for results.
+ #
+ self._logger.info("In moongen start_rfc2544_back2back method")
+ return NotImplementedError(
+ 'Moongen start back2back traffic not implemented')
+
+ def wait_rfc2544_back2back(self):
+ self._logger.info("In moongen wait_rfc2544_back2back method")
+ #
+ # Wait and set results of RFC2544 test.
+ #
+ return NotImplementedError(
+ 'Moongen wait back2back traffic not implemented')
+
+if __name__ == "__main__":
+ pass
diff --git a/tools/pkt_gen/xena/XenaDriver.py b/tools/pkt_gen/xena/XenaDriver.py
new file mode 100644
index 00000000..aa8443c9
--- /dev/null
+++ b/tools/pkt_gen/xena/XenaDriver.py
@@ -0,0 +1,1129 @@
+# Copyright 2016 Red Hat Inc & Xena Networks.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+# This is a port of code by Xena and Flavios ported to python 3 compatibility.
+# Credit given to Xena and Flavio for providing most of the logic of this code.
+# The code has changes for PEP 8 and python 3 conversion. Added Stat classes
+# for better scaling of future requirements. Also added calculation functions
+# for line rate to align within VSPerf project.
+# Flavios xena libraries available at https://github.com/fleitner/XenaPythonLib
+
+# Contributors:
+# Flavio Leitner, Red Hat Inc.
+# Dan Amzulescu, Xena Networks
+# Christian Trautman, Red Hat Inc.
+
+"""
+Xena Socket API Driver module for communicating directly with Xena system
+through socket commands and returning different statistics.
+"""
+import locale
+import logging
+import socket
+import struct
+import sys
+import threading
+import time
+
+# Xena Socket Commands
+CMD_CLEAR_RX_STATS = 'pr_clear'
+CMD_CLEAR_TX_STATS = 'pt_clear'
+CMD_COMMENT = ';'
+CMD_CREATE_STREAM = 'ps_create'
+CMD_DELETE_STREAM = 'ps_delete'
+CMD_GET_PORT_SPEED = 'p_speed ?'
+CMD_GET_PORT_SPEED_REDUCTION = 'p_speedreduction ?'
+CMD_GET_RX_STATS_PER_TID = 'pr_tpldtraffic'
+CMD_GET_STREAM_DATA = 'pt_stream'
+CMD_GET_STREAMS_PER_PORT = 'ps_indices'
+CMD_GET_TID_PER_STREAM = 'ps_tpldid'
+CMD_GET_TX_STATS_PER_STREAM = 'pt_stream'
+CMD_GET_RX_STATS = 'pr_all ?'
+CMD_GET_TX_STATS = 'pt_all ?'
+CMD_INTERFRAME_GAP = 'p_interframegap'
+CMD_LOGIN = 'c_logon'
+CMD_LOGOFF = 'c_logoff'
+CMD_OWNER = 'c_owner'
+CMD_PORT = ';Port:'
+CMD_PORT_IP = 'p_ipaddress'
+CMD_RESERVE = 'p_reservation reserve'
+CMD_RELEASE = 'p_reservation release'
+CMD_RELINQUISH = 'p_reservation relinquish'
+CMD_RESET = 'p_reset'
+CMD_SET_PORT_TIME_LIMIT = 'p_txtimelimit'
+CMD_SET_STREAM_HEADER_PROTOCOL = 'ps_headerprotocol'
+CMD_SET_STREAM_ON_OFF = 'ps_enable'
+CMD_SET_STREAM_PACKET_HEADER = 'ps_packetheader'
+CMD_SET_STREAM_PACKET_LENGTH = 'ps_packetlength'
+CMD_SET_STREAM_PACKET_LIMIT = 'ps_packetlimit'
+CMD_SET_STREAM_PACKET_PAYLOAD = 'ps_payload'
+CMD_SET_STREAM_RATE_FRACTION = 'ps_ratefraction'
+CMD_SET_STREAM_TEST_PAYLOAD_ID = 'ps_tpldid'
+CMD_SET_TPLD_MODE = 'p_tpldmode'
+CMD_START_TRAFFIC = 'p_traffic on'
+CMD_STOP_TRAFFIC = 'p_traffic off'
+CMD_STREAM_MODIFIER = 'ps_modifier'
+CMD_STREAM_MODIFIER_COUNT = 'ps_modifiercount'
+CMD_STREAM_MODIFIER_RANGE = 'ps_modifierrange'
+CMD_VERSION = 'c_versionno ?'
+
+_LOCALE = locale.getlocale()[1]
+_LOGGER = logging.getLogger(__name__)
+
+
+class SimpleSocket(object):
+ """
+ Socket class
+ """
+ def __init__(self, hostname, port=5025, timeout=1):
+ """Constructor
+ :param hostname: hostname or ip as string
+ :param port: port number to use for socket as int
+ :param timeout: socket timeout as int
+ :return: SimpleSocket object
+ """
+ self.hostname = hostname
+ try:
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.settimeout(timeout)
+ self.sock.connect((hostname, port))
+ except socket.error as msg:
+ _LOGGER.error(
+ "Cannot connect to Xena Socket at %s", hostname)
+ _LOGGER.error("Exception : %s", msg)
+ sys.exit(1)
+
+ def __del__(self):
+ """Deconstructor
+ :return:
+ """
+ self.sock.close()
+
+ def ask(self, cmd):
+ """ Send the command over the socket
+ :param cmd: cmd as string
+ :return: byte utf encoded return value from socket
+ """
+ cmd += '\n'
+ try:
+ self.sock.send(cmd.encode('utf-8'))
+ return self.sock.recv(1024)
+ except OSError:
+ return ''
+
+ def read_reply(self):
+ """ Get the response from the socket
+ :return: Return the reply
+ """
+ reply = self.sock.recv(1024)
+ if reply.find("---^".encode('utf-8')) != -1:
+ # read again the syntax error msg
+ reply = self.sock.recv(1024)
+ return reply
+
+ def send_command(self, cmd):
+ """ Send the command specified over the socket
+ :param cmd: Command to send as string
+ :return: None
+ """
+ cmd += '\n'
+ self.sock.send(cmd.encode('utf-8'))
+
+ def set_keep_alive(self):
+ """ Set the keep alive for the socket
+ :return: None
+ """
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+
+
+class KeepAliveThread(threading.Thread):
+ """
+ Keep alive socket class
+ """
+ message = ''
+
+ def __init__(self, connection, interval=10):
+ """ Constructor
+ :param connection: Socket for keep alive
+ :param interval: interval in seconds to send keep alive
+ :return: KeepAliveThread object
+ """
+ threading.Thread.__init__(self)
+ self.connection = connection
+ self.interval = interval
+ self.finished = threading.Event()
+ self.setDaemon(True)
+ _LOGGER.debug(
+ 'Xena Socket keep alive thread initiated, interval ' +
+ '{} seconds'.format(self.interval))
+
+ def stop(self):
+ """ Thread stop. See python thread docs for more info
+ :return: None
+ """
+ self.finished.set()
+ self.join()
+
+ def run(self):
+ """ Thread start. See python thread docs for more info
+ :return: None
+ """
+ while not self.finished.isSet():
+ self.finished.wait(self.interval)
+ self.connection.ask(self.message)
+
+
+class XenaSocketDriver(SimpleSocket):
+ """
+ Xena socket class
+ """
+ reply_ok = '<OK>'
+
+ def __init__(self, hostname, port=22611):
+ """ Constructor
+ :param hostname: Hostname or ip as string
+ :param port: port to use as int
+ :return: XenaSocketDriver object
+ """
+ SimpleSocket.__init__(self, hostname=hostname, port=port)
+ SimpleSocket.set_keep_alive(self)
+ self.access_semaphor = threading.Semaphore(1)
+
+ def ask(self, cmd):
+ """ Send the command over the socket in a thread safe manner
+ :param cmd: Command to send
+ :return: reply from socket
+ """
+ self.access_semaphor.acquire()
+ reply = SimpleSocket.ask(self, cmd)
+ self.access_semaphor.release()
+ return reply
+
+ def ask_verify(self, cmd):
+ """ Send the command over the socket in a thread safe manner and
+ verify the response is good.
+ :param cmd: Command to send
+ :return: Boolean True if command response is good, False otherwise
+ """
+ resp = self.ask(cmd).decode(_LOCALE).strip('\n')
+ _LOGGER.info('[ask_verify] %s', resp)
+ if resp == self.reply_ok:
+ return True
+ return False
+
+ def disconnect(self):
+ """
+ Close the socket connection
+ :return: None
+ """
+ self.sock.close()
+
+ def send_command(self, cmd):
+ """ Send the command over the socket with no return
+ :param cmd: Command to send
+ :return: None
+ """
+ self.access_semaphor.acquire()
+ SimpleSocket.send_command(self, cmd)
+ self.access_semaphor.release()
+
+ def send_query_replies(self, cmd):
+ """ Send the command over the socket and wait for all replies and return
+ the lines as a list
+ :param cmd: Command to send
+ :return: Response from command as list
+ """
+ # send the command followed by cmd SYNC to find out
+ # when the last reply arrives.
+ self.send_command(cmd.strip('\n'))
+ self.send_command('SYNC')
+ replies = []
+ self.access_semaphor.acquire()
+ msg = SimpleSocket.read_reply(self).decode(_LOCALE)
+ msgleft = ''
+ while True:
+ if '\n' in msg:
+ (reply, msgleft) = msg.split('\n', 1)
+ # check for syntax problems
+ if reply.rfind('Syntax') != -1:
+ self.access_semaphor.release()
+ return []
+
+ if reply.rfind('<SYNC>') == 0:
+
+ self.access_semaphor.release()
+ return replies
+
+ replies.append(reply + '\n')
+ msg = msgleft
+ else:
+ # more bytes to come
+ msgnew = SimpleSocket.read_reply(self).decode(_LOCALE)
+ msg = msgleft + msgnew
+
+
+class XenaManager(object):
+ """
+ Manager class for port and socket functions
+ """
+ def __init__(self, socketDriver, user='', password='xena'):
+ """Constructor
+
+ Establish a connection to Xena using a ``driver`` with the ``password``
+ supplied.
+
+ Attributes:
+ :param socketDriver: XenaSocketDriver connection object
+ :param password: Password to the Xena traffic generator
+ :returns: XenaManager object
+ """
+ self.driver = socketDriver
+ self.ports = list()
+ self.keep_alive_thread = KeepAliveThread(self.driver)
+
+ if self.logon(password):
+ _LOGGER.info('Connected to Xena at %s', self.driver.hostname)
+ else:
+ _LOGGER.error('Failed to logon to Xena at %s', self.driver.hostname)
+ return
+
+ self.set_owner(user)
+
+ def disconnect(self):
+ """ Release ports and disconnect from chassis.
+ """
+ for module_port in self.ports:
+ module_port.release_port()
+ self.ports = []
+ self.logoff()
+ self.keep_alive_thread.stop()
+
+ def add_module_port(self, module, port):
+ """Factory for Xena Ports
+
+ :param module: String or int of module
+ :param port: String or int of port
+ :return: XenaPort object if success, None if port already added
+ """
+ xenaport = XenaPort(self, module, port)
+ if xenaport in self.ports:
+ return None
+ else:
+ self.ports.append(xenaport)
+ return xenaport
+
+ def get_module_port(self, module, port):
+ """Return the Xena Port object if available
+ :param module: module number as int or str
+ :param port: port number as int or str
+ :return: XenaPort object or None if not found
+ """
+ for por in self.ports:
+ if por.port == str(port) and por.module == str(module):
+ return por
+ return None
+
+ def get_version(self):
+ """
+ Get the version from the chassis
+ :return: versions of server and driver as string
+ """
+ res = self.driver.ask(make_manager_command(
+ CMD_VERSION, '')).decode(_LOCALE)
+ res = res.rstrip('\n').split()
+ return "Server: {} Driver: {}".format(res[1], res[2])
+
+ def logoff(self):
+ """
+ Logoff from the Xena chassis
+ :return: Boolean True if response OK, False if error.
+ """
+ return self.driver.ask_verify(make_manager_command(CMD_LOGOFF))
+
+ def logon(self, password):
+ """Login to the Xena traffic generator using the ``password`` supplied.
+
+ :param password: string of password
+ :return: Boolean True if response OK, False if error.
+ """
+ self.keep_alive_thread.start()
+ return self.driver.ask_verify(make_manager_command(CMD_LOGIN, password))
+
+ def set_owner(self, username):
+ """Set the ports owner.
+ :return: Boolean True if response OK, False if error.
+ """
+ return self.driver.ask_verify(make_manager_command(CMD_OWNER, username))
+
+
+class XenaPort(object):
+ """
+ Xena Port emulator class
+ """
+ def __init__(self, manager, module, port):
+ """Constructor
+
+ :param manager: XenaManager object
+ :param module: Module as string or int of module to use
+ :param port: Port as string or int of port to use
+ :return: XenaPort object
+ """
+ self._manager = manager
+ self._module = str(module)
+ self._port = str(port)
+ self._streams = list()
+
+ @property
+ def manager(self):
+ """Property for manager attribute
+ :return: manager object
+ """
+ return self._manager
+
+ @property
+ def module(self):
+ """Property for module attribute
+ :return: module value as string
+ """
+ return self._module
+
+ @property
+ def port(self):
+ """Property for port attribute
+ :return: port value as string
+ """
+ return self._port
+
+ def port_string(self):
+ """String builder with attributes
+ :return: String of module port for command sequence
+ """
+ stringify = "{}/{}".format(self._module, self._port)
+ return stringify
+
+ def add_stream(self):
+ """Add a stream to the port.
+ :return: XenaStream object, None if failure
+ """
+ identifier = len(self._streams)
+ stream = XenaStream(self, identifier)
+ if self._manager.driver.ask_verify(make_stream_command(
+ CMD_CREATE_STREAM, '', stream)):
+ self._streams.append(stream)
+ return stream
+ else:
+ _LOGGER.error("Error during stream creation")
+ return None
+
+ def clear_stats(self, rx_clear=True, tx_clear=True):
+ """Clear the port stats
+
+ :param rx_clear: Boolean if rx stats are to be cleared
+ :param tx_clear: Boolean if tx stats are to be cleared
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_CLEAR_RX_STATS, self)
+ res1 = self._manager.driver.ask_verify(command) if rx_clear else True
+ command = make_port_command(CMD_CLEAR_TX_STATS, self)
+ res2 = self._manager.driver.ask_verify(command) if tx_clear else True
+ return all([res1, res2])
+
+ def get_effective_speed(self):
+ """
+ Get the effective speed on the port
+ :return: effective speed as float
+ """
+ port_speed = self.get_port_speed()
+ reduction = self.get_port_speed_reduction()
+ effective_speed = port_speed * (1.0 - reduction / 1000000.0)
+ return effective_speed
+
+ def get_inter_frame_gap(self):
+ """
+ Get the interframe gap and return it as string
+ :return: integer of interframe gap
+ """
+ command = make_port_command(CMD_INTERFRAME_GAP + '?', self)
+ res = self._manager.driver.ask(command).decode(_LOCALE)
+ res = int(res.rstrip('\n').split(' ')[-1])
+ return res
+
+ def get_port_speed(self):
+ """
+ Get the port speed as bits from port and return it as a int.
+ :return: Int of port speed
+ """
+ command = make_port_command(CMD_GET_PORT_SPEED, self)
+ res = self._manager.driver.ask(command).decode(_LOCALE)
+ port_speed = res.split(' ')[-1].rstrip('\n')
+ return int(port_speed) * 1000000
+
+ def get_port_speed_reduction(self):
+ """
+ Get the port speed reduction value as int
+ :return: Integer of port speed reduction value
+ """
+ command = make_port_command(CMD_GET_PORT_SPEED_REDUCTION, self)
+ res = self._manager.driver.ask(command).decode(_LOCALE)
+ res = int(res.rstrip('\n').split(' ')[-1])
+ return res
+
+ def get_rx_stats(self):
+ """Get the rx stats and return the data as a dict.
+ :return: Receive stats as dictionary
+ """
+ command = make_port_command(CMD_GET_RX_STATS, self)
+ rx_data = self._manager.driver.send_query_replies(command)
+ data = XenaRXStats(rx_data, time.time())
+ return data
+
+ def get_tx_stats(self):
+ """Get the tx stats and return the data as a dict.
+ :return: Receive stats as dictionary
+ """
+ command = make_port_command(CMD_GET_TX_STATS, self)
+ tx_data = self._manager.driver.send_query_replies(command)
+ data = XenaTXStats(tx_data, time.time())
+ return data
+
+ def micro_tpld_disable(self):
+ """Disable micro TPLD and return to standard payload size
+ :return: Boolean if response OK, False if error
+ """
+ command = make_port_command(CMD_SET_TPLD_MODE + ' normal', self)
+ return self._manager.driver.ask_verify(command)
+
+ def micro_tpld_enable(self):
+ """Enable micro TPLD 6 byte payloads.
+ :Return Boolean if response OK, False if error
+ """
+ command = make_port_command(CMD_SET_TPLD_MODE + ' micro', self)
+ return self._manager.driver.ask_verify(command)
+
+ def release_port(self):
+ """Release the port
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_RELEASE, self)
+ return self._manager.driver.ask_verify(command)
+
+ def reserve_port(self):
+ """Reserve the port
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_RESERVE, self)
+ return self._manager.driver.ask_verify(command)
+
+ def reset_port(self):
+ """Reset the port
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_RESET, self)
+ return self._manager.driver.ask_verify(command)
+
+ def set_port_ip(self, ip_addr, cidr, gateway, wild='255'):
+ """
+ Set the port ip address of the specific port
+ :param ip_addr: IP address to set to port
+ :param cidr: cidr number for the subnet
+ :param gateway: Gateway ip for port
+ :param wild: wildcard used for ARP and PING replies
+ :return: Boolean True if response OK, False if error
+ """
+ # convert the cidr to a dot notation subnet address
+ subnet = socket.inet_ntoa(
+ struct.pack(">I", (0xffffffff << (32 - cidr)) & 0xffffffff))
+
+ command = make_port_command('{} {} {} {} 0.0.0.{}'.format(
+ CMD_PORT_IP, ip_addr, subnet, gateway, wild), self)
+ return self._manager.driver.ask_verify(command)
+
+ def set_port_time_limit(self, micro_seconds):
+ """Set the port time limit in ms
+ :param micro_seconds: ms for port time limit
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command('{} {}'.format(
+ CMD_SET_PORT_TIME_LIMIT, micro_seconds), self)
+ return self._manager.driver.ask_verify(command)
+
+ def traffic_off(self):
+ """Start traffic
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_STOP_TRAFFIC, self)
+ return self._manager.driver.ask_verify(command)
+
+ def traffic_on(self):
+ """Stop traffic
+ :return: Boolean True if response OK, False if error.
+ """
+ command = make_port_command(CMD_START_TRAFFIC, self)
+ return self._manager.driver.ask_verify(command)
+
+
+class XenaStream(object):
+ """
+ Xena stream emulator class
+ """
+ def __init__(self, xenaPort, streamID):
+ """Constructor
+
+ :param xenaPort: XenaPort object
+ :param streamID: Stream ID as int or string
+ :return: XenaStream object
+ """
+ self._xena_port = xenaPort
+ self._stream_id = str(streamID)
+ self._manager = self._xena_port.manager
+ self._header_protocol = None
+
+ @property
+ def xena_port(self):
+ """Property for port attribute
+ :return: XenaPort object
+ """
+ return self._xena_port
+
+ @property
+ def stream_id(self):
+ """Property for streamID attribute
+ :return: streamID value as string
+ """
+ return self._stream_id
+
+ def enable_multistream(self, flows, layer):
+ """
+ Basic implementation of multi stream. Enable multi stream by setting
+ modifiers on the stream
+ :param flows: Numbers of flows or end range
+ :param layer: layer to enable multi stream as str. Acceptable values
+ are L2, L3, or L4
+ :return: True if success False otherwise
+ """
+ if not self._header_protocol:
+ raise RuntimeError(
+ "Please set a protocol header before calling this method.")
+
+ # byte offsets for setting the modifier
+ offsets = {
+ 'L2': [0, 6],
+ 'L3': [32, 36] if 'VLAN' in self._header_protocol else [28, 32],
+ 'L4': [38, 40] if 'VLAN' in self._header_protocol else [34, 36]
+ }
+
+ responses = list()
+ if layer in offsets.keys() and flows > 0:
+ command = make_port_command(
+ CMD_STREAM_MODIFIER_COUNT + ' [{}]'.format(self._stream_id) +
+ ' 2', self._xena_port)
+ responses.append(self._manager.driver.ask_verify(command))
+ command = make_port_command(
+ CMD_STREAM_MODIFIER + ' [{},0] {} 0xFFFF0000 INC 1'.format(
+ self._stream_id, offsets[layer][0]), self._xena_port)
+ responses.append(self._manager.driver.ask_verify(command))
+ command = make_port_command(
+ CMD_STREAM_MODIFIER_RANGE + ' [{},0] 0 1 {}'.format(
+ self._stream_id, flows), self._xena_port)
+ responses.append(self._manager.driver.ask_verify(command))
+ command = make_port_command(
+ CMD_STREAM_MODIFIER + ' [{},1] {} 0xFFFF0000 INC 1'.format(
+ self._stream_id, offsets[layer][1]), self._xena_port)
+ responses.append(self._manager.driver.ask_verify(command))
+ command = make_port_command(
+ CMD_STREAM_MODIFIER_RANGE + ' [{},1] 0 1 {}'.format(
+ self._stream_id, flows), self._xena_port)
+ responses.append(self._manager.driver.ask_verify(command))
+ return all(responses) # return True if they all worked
+ elif flows < 1:
+ _LOGGER.warning(
+ 'No flows specified in enable multistream. Bypassing...')
+ return False
+ else:
+ raise NotImplementedError(
+ "Non-implemented stream layer in method enable multistream ",
+ "layer=", layer)
+
+ def get_stream_data(self):
+ """
+ Get the response for stream data
+ :return: String of response for stream data info
+ """
+ command = make_stream_command(CMD_GET_STREAM_DATA, '?', self)
+ res = self._manager.driver.ask(command).decode(_LOCALE)
+ return res
+
+ def set_header_protocol(self, protocol_header):
+ """Set the header info for the packet header hex.
+ If the packet header contains just Ethernet and IP info then call this
+ method with ETHERNET IP as the protocol header.
+
+ :param protocol_header: protocol header argument
+ :return: Boolean True if success, False if error
+ """
+ command = make_stream_command(
+ CMD_SET_STREAM_HEADER_PROTOCOL,
+ protocol_header, self)
+ if self._manager.driver.ask_verify(command):
+ self._header_protocol = protocol_header
+ return True
+ else:
+ return False
+
+ def set_off(self):
+ """Set the stream to off
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_ON_OFF, 'off', self))
+
+ def set_on(self):
+ """Set the stream to on
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_ON_OFF, 'on', self))
+
+ def set_packet_header(self, header):
+ """Set the stream packet header
+
+ :param header: packet header as hex bytes
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_PACKET_HEADER, header, self))
+
+ def set_packet_length(self, pattern_type, minimum, maximum):
+ """Set the pattern length with min and max values based on the pattern
+ type supplied
+
+ :param pattern_type: String of pattern type, valid entries [ fixed,
+ butterfly, random, mix, incrementing ]
+ :param minimum: integer of minimum byte value
+ :param maximum: integer of maximum byte value
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_PACKET_LENGTH, '{} {} {}'.format(
+ pattern_type, minimum, maximum), self))
+
+ def set_packet_limit(self, limit):
+ """Set the packet limit
+
+ :param limit: number of packets that will be sent, use -1 to disable
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_PACKET_LIMIT, limit, self))
+
+ def set_packet_payload(self, payload_type, hex_value):
+ """Set the payload to the hex value based on the payload type
+
+ :param payload_type: string of the payload type, valid entries [ pattern,
+ incrementing, prbs ]
+ :param hex_value: hex string of valid hex
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_PACKET_PAYLOAD, '{} {}'.format(
+ payload_type, hex_value), self))
+
+ def set_rate_fraction(self, fraction):
+ """Set the rate fraction
+
+ :param fraction: fraction for the stream
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_RATE_FRACTION, fraction, self))
+
+ def set_payload_id(self, identifier):
+ """ Set the test payload ID
+ :param identifier: ID as int or string
+ :return: Boolean True if success, False if error
+ """
+ return self._manager.driver.ask_verify(make_stream_command(
+ CMD_SET_STREAM_TEST_PAYLOAD_ID, identifier, self))
+
+
+class XenaRXStats(object):
+ """
+ Receive stat class
+ """
+ def __init__(self, stats, epoc):
+ """ Constructor
+ :param stats: Stats from pr all command as list
+ :param epoc: Current time in epoc
+ :return: XenaRXStats object
+ """
+ self._stats = stats
+ self._time = epoc
+ self.data = self.parse_stats()
+ self.preamble = 8
+
+ @staticmethod
+ def _pack_stats(param, start, fields=None):
+ """ Pack up the list of stats in a dictionary
+ :param param: The list of params to process
+ :param start: What element to start at
+ :param fields: The field names to pack as keys
+ :return: Dictionary of data where fields match up to the params
+ """
+ if not fields:
+ fields = ['bps', 'pps', 'bytes', 'packets']
+ data = {}
+ i = 0
+ for column in fields:
+ data[column] = int(param[start + i])
+ i += 1
+
+ return data
+
+ @staticmethod
+ def _pack_tplds_stats(param, start):
+ """ Pack up the tplds stats
+ :param param: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ data = {}
+ i = 0
+ for val in range(start, len(param) - start):
+ data[i] = int(param[val])
+ i += 1
+ return data
+
+ def _pack_rxextra_stats(self, param, start):
+ """ Pack up the extra stats
+ :param param: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ fields = ['fcserrors', 'pauseframes', 'arprequests', 'arpreplies',
+ 'pingrequests', 'pingreplies', 'gapcount', 'gapduration']
+ return self._pack_stats(param, start, fields)
+
+ def _pack_tplderrors_stats(self, param, start):
+ """ Pack up tlpd errors
+ :param param: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ fields = ['dummy', 'seq', 'mis', 'pld']
+ return self._pack_stats(param, start, fields)
+
+ def _pack_tpldlatency_stats(self, param, start):
+ """ Pack up the tpld latency stats
+ :param param: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ fields = ['min', 'avg', 'max', '1sec']
+ return self._pack_stats(param, start, fields)
+
+ def _pack_tpldjitter_stats(self, param, start):
+ """ Pack up the tpld jitter stats
+ :param param: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ fields = ['min', 'avg', 'max', '1sec']
+ return self._pack_stats(param, start, fields)
+
+ @property
+ def time(self):
+ """
+ :return: Time as String of epoc of when stats were collected
+ """
+ return self._time
+
+ def parse_stats(self):
+ """ Parse the stats from pr all command
+ :return: Dictionary of all stats
+ """
+ statdict = {}
+ for line in self._stats:
+ param = line.split()
+ if param[1] == 'PR_TOTAL':
+ statdict['pr_total'] = self._pack_stats(param, 2)
+ elif param[1] == 'PR_NOTPLD':
+ statdict['pr_notpld'] = self._pack_stats(param, 2,)
+ elif param[1] == 'PR_EXTRA':
+ statdict['pr_extra'] = self._pack_rxextra_stats(param, 2)
+ elif param[1] == 'PT_STREAM':
+ entry_id = "pt_stream_%s" % param[2].strip('[]')
+ statdict[entry_id] = self._pack_stats(param, 3)
+ elif param[1] == 'PR_TPLDS':
+ tid_list = self._pack_tplds_stats(param, 2)
+ if len(tid_list):
+ statdict['pr_tplds'] = tid_list
+ elif param[1] == 'PR_TPLDTRAFFIC':
+ if 'pr_tpldstraffic' in statdict:
+ data = statdict['pr_tpldstraffic']
+ else:
+ data = {}
+ entry_id = param[2].strip('[]')
+ data[entry_id] = self._pack_stats(param, 3)
+ statdict['pr_tpldstraffic'] = data
+ elif param[1] == 'PR_TPLDERRORS':
+ if 'pr_tplderrors' in statdict:
+ data = statdict['pr_tplderrors']
+ else:
+ data = {}
+ entry_id = param[2].strip('[]')
+ data[entry_id] = self._pack_tplderrors_stats(param, 3)
+ statdict['pr_tplderrors'] = data
+ elif param[1] == 'PR_TPLDLATENCY':
+ if 'pr_tpldlatency' in statdict:
+ data = statdict['pr_tpldlatency']
+ else:
+ data = {}
+ entry_id = param[2].strip('[]')
+ data[entry_id] = self._pack_tpldlatency_stats(param, 3)
+ statdict['pr_tpldlatency'] = data
+ elif param[1] == 'PR_TPLDJITTER':
+ if 'pr_tpldjitter' in statdict:
+ data = statdict['pr_tpldjitter']
+ else:
+ data = {}
+ entry_id = param[2].strip('[]')
+ data[entry_id] = self._pack_tpldjitter_stats(param, 3)
+ statdict['pr_pldjitter'] = data
+ elif param[1] == 'PR_FILTER':
+ if 'pr_filter' in statdict:
+ data = statdict['pr_filter']
+ else:
+ data = {}
+ entry_id = param[2].strip('[]')
+ data[entry_id] = self._pack_stats(param, 3)
+ statdict['pr_filter'] = data
+ elif param[1] == 'P_RECEIVESYNC':
+ if param[2] == 'IN_SYNC':
+ statdict['p_receivesync'] = {'IN SYNC': 'True'}
+ else:
+ statdict['p_receivesync'] = {'IN SYNC': 'False'}
+ else:
+ logging.warning("XenaPort: unknown stats: %s", param[1])
+
+ mydict = statdict
+ return mydict
+
+
+class XenaTXStats(object):
+ """
+ Xena transmit stat class
+ """
+ def __init__(self, stats, epoc):
+ """ Constructor
+ :param stats: Stats from pt all command as list
+ :param epoc: Current time in epoc
+ :return: XenaTXStats object
+ """
+ self._stats = stats
+ self._time = epoc
+ self._ptstreamkeys = list()
+ self.data = self.parse_stats()
+ self.preamble = 8
+
+ @staticmethod
+ def _pack_stats(params, start, fields=None):
+ """ Pack up the list of stats in a dictionary
+ :param params: The list of params to process
+ :param start: What element to start at
+ :param fields: The field names to pack as keys
+ :return: Dictionary of data where fields match up to the params
+ """
+ if not fields:
+ fields = ['bps', 'pps', 'bytes', 'packets']
+ data = {}
+ i = 0
+ for column in fields:
+ data[column] = int(params[start + i])
+ i += 1
+
+ return data
+
+ def _pack_txextra_stats(self, params, start):
+ """ Pack up the tx extra stats
+ :param params: List of params to pack
+ :param start: What element to start at
+ :return: Dictionary of stats
+ """
+ fields = ['arprequests', 'arpreplies', 'pingrequests', 'pingreplies',
+ 'injectedfcs', 'injectedseq', 'injectedmis', 'injectedint',
+ 'injectedtid', 'training']
+ return self._pack_stats(params, start, fields)
+
+ @property
+ def pt_stream_keys(self):
+ """
+ :return: Return a list of pt_stream_x stream key ids
+ """
+ return self._ptstreamkeys
+
+ @property
+ def time(self):
+ """
+ :return: Time as String of epoc of when stats were collected
+ """
+ return self._time
+
+ def parse_stats(self):
+ """ Parse the stats from pr all command
+ :return: Dictionary of all stats
+ """
+ statdict = {}
+ for line in self._stats:
+ param = line.split()
+ if param[1] == 'PT_TOTAL':
+ statdict['pt_total'] = self._pack_stats(param, 2)
+ elif param[1] == 'PT_NOTPLD':
+ statdict['pt_notpld'] = self._pack_stats(param, 2,)
+ elif param[1] == 'PT_EXTRA':
+ statdict['pt_extra'] = self._pack_txextra_stats(param, 2)
+ elif param[1] == 'PT_STREAM':
+ entry_id = "pt_stream_%s" % param[2].strip('[]')
+ self._ptstreamkeys.append(entry_id)
+ statdict[entry_id] = self._pack_stats(param, 3)
+ else:
+ logging.warning("XenaPort: unknown stats: %s", param[1])
+ mydict = statdict
+ return mydict
+
+
+def aggregate_stats(stat1, stat2):
+ """
+ Recursive function to aggregate two sets of statistics. This is used when
+ bi directional traffic is done and statistics need to be calculated based
+ on two sets of statistics.
+ :param stat1: One set of dictionary stats from RX or TX stats
+ :param stat2: Second set of dictionary stats from RX or TX stats
+ :return: stats for data entry in RX or TX Stats instance
+ """
+ newstat = dict()
+ for (keys1, keys2) in zip(stat1.keys(), stat2.keys()):
+ if isinstance(stat1[keys1], dict):
+ newstat[keys1] = aggregate_stats(stat1[keys1], stat2[keys2])
+ else:
+ if not isinstance(stat1[keys1], int) and not isinstance(
+ [keys1], float):
+ # its some value we don't need to aggregate
+ return stat1[keys1]
+ # for latency stats do the appropriate calculation
+ if keys1 == 'max':
+ newstat[keys1] = max(stat1[keys1], stat2[keys2])
+ elif keys1 == 'min':
+ newstat[keys1] = min(stat1[keys1], stat2[keys2])
+ elif keys1 == 'avg':
+ newstat[keys1] = (stat1[keys1] + stat2[keys2]) / 2
+ else:
+ newstat[keys1] = (stat1[keys1] + stat2[keys2])
+ return newstat
+
+
+def line_percentage(port, stats, time_active, packet_size):
+ """
+ Calculate the line percentage rate from the duration, port object and stat
+ object.
+ :param port: XenaPort object
+ :param stats: Xena RXStat or TXStat object
+ :param time_active: time the stream was active in secs as int
+ :param packet_size: packet size as int
+ :return: line percentage as float
+ """
+ # this is ugly, but its prettier than calling the get method 3 times...
+ try:
+ packets = stats.data['pr_total']['packets']
+ except KeyError:
+ try:
+ packets = stats.data['pt_total']['packets']
+ except KeyError:
+ _LOGGER.error(
+ 'Could not calculate line rate because packet stat not found.')
+ return 0
+ ifg = port.get_inter_frame_gap()
+ pps = packets_per_second(packets, time_active)
+ l2br = l2_bit_rate(packet_size, stats.preamble, pps)
+ l1br = l1_bit_rate(l2br, pps, ifg, stats.preamble)
+ return 100.0 * l1br / port.get_effective_speed()
+
+
+def l2_bit_rate(packet_size, preamble, pps):
+ """
+ Return the l2 bit rate
+ :param packet_size: packet size on the line in bytes
+ :param preamble: preamble size of the packet header in bytes
+ :param pps: packets per second
+ :return: l2 bit rate as float
+ """
+ return (packet_size * preamble) * pps
+
+
+def l1_bit_rate(l2br, pps, ifg, preamble):
+ """
+ Return the l1 bit rate
+ :param l2br: l2 bit rate int bits per second
+ :param pps: packets per second
+ :param ifg: the inter frame gap
+ :param preamble: preamble size of the packet header in bytes
+ :return: l1 bit rate as float
+ """
+ return l2br + (pps * ifg * preamble)
+
+
+def make_manager_command(cmd, argument=None):
+ """ String builder for Xena socket commands
+
+ :param cmd: Command to send
+ :param argument: Arguments for command to send
+ :return: String of command
+ """
+ command = '{} "{}"'.format(cmd, argument) if argument else cmd
+ _LOGGER.info("[Command Sent] : %s", command)
+ return command
+
+
+def make_port_command(cmd, xena_port):
+ """ String builder for Xena port commands
+
+ :param cmd: Command to send
+ :param xena_port: XenaPort object
+ :return: String of command
+ """
+ command = "{} {}".format(xena_port.port_string(), cmd)
+ _LOGGER.info("[Command Sent] : %s", command)
+ return command
+
+
+def make_stream_command(cmd, args, xena_stream):
+ """ String builder for Xena port commands
+
+ :param cmd: Command to send
+ :param xena_stream: XenaStream object
+ :return: String of command
+ """
+ command = "{} {} [{}] {}".format(xena_stream.xena_port.port_string(), cmd,
+ xena_stream.stream_id, args)
+ _LOGGER.info("[Command Sent] : %s", command)
+ return command
+
+
+def packets_per_second(packets, time_in_sec):
+ """
+ Return the pps as float
+ :param packets: total packets
+ :param time_in_sec: time in seconds
+ :return: float of pps
+ """
+ return packets / time_in_sec
diff --git a/tools/pkt_gen/xena/profiles/baseconfig.x2544 b/tools/pkt_gen/xena/profiles/baseconfig.x2544
index 3ebe79dc..0612b329 100644
--- a/tools/pkt_gen/xena/profiles/baseconfig.x2544
+++ b/tools/pkt_gen/xena/profiles/baseconfig.x2544
@@ -207,7 +207,7 @@
"PassThreshold": 0.0
},
"ReportPropertyOptions": [
- "b"
+ "LatencyCounters"
],
"TestType": "Throughput",
"Enabled": false,
diff --git a/tools/pkt_gen/xena/xena.py b/tools/pkt_gen/xena/xena.py
index d29fc362..7dd4b90b 100755
--- a/tools/pkt_gen/xena/xena.py
+++ b/tools/pkt_gen/xena/xena.py
@@ -23,12 +23,15 @@ Xena Traffic Generator Model
"""
# python imports
+import binascii
import logging
import subprocess
import sys
from time import sleep
import xml.etree.ElementTree as ET
from collections import OrderedDict
+# scapy imports
+import scapy.layers.inet as inet
# VSPerf imports
from conf import settings
@@ -40,7 +43,12 @@ from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
# Xena module imports
from tools.pkt_gen.xena.xena_json import XenaJSON
-
+from tools.pkt_gen.xena.XenaDriver import (
+ aggregate_stats,
+ line_percentage,
+ XenaSocketDriver,
+ XenaManager,
+ )
class Xena(ITrafficGenerator):
"""
@@ -51,8 +59,12 @@ class Xena(ITrafficGenerator):
def __init__(self):
self.mono_pipe = None
+ self.xmanager = None
self._params = {}
+ self._xsocket = None
self._duration = None
+ self.tx_stats = None
+ self.rx_stats = None
@property
def traffic_defaults(self):
@@ -80,10 +92,12 @@ class Xena(ITrafficGenerator):
if test_type == 'Throughput':
results = OrderedDict()
- results[ResultsConstants.THROUGHPUT_RX_FPS] = int(
- root[0][1][0][1].get('PortRxPps'))
- results[ResultsConstants.THROUGHPUT_RX_MBPS] = int(
- root[0][1][0][1].get('PortRxBpsL1')) / 1000000
+ results[ResultsConstants.THROUGHPUT_RX_FPS] = float(
+ root[0][1][0][0].get('PortRxPps')) + float(
+ root[0][1][0][1].get('PortRxPps'))
+ results[ResultsConstants.THROUGHPUT_RX_MBPS] = (float(
+ root[0][1][0][0].get('PortRxBpsL1')) + float(
+ root[0][1][0][1].get('PortRxBpsL1')))/ 1000000
results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
100 - int(root[0][1][0].get('TotalLossRatioPcnt'))) * float(
root[0][1][0].get('TotalTxRatePcnt'))/100
@@ -127,6 +141,125 @@ class Xena(ITrafficGenerator):
return results
+ def _build_packet_header(self, reverse=False):
+ """
+ Build a packet header based on traffic profile using scapy external
+ libraries.
+ :param reverse: Swap source and destination info when building header
+ :return: packet header in hex
+ """
+ srcmac = self._params['traffic']['l2'][
+ 'srcmac'] if not reverse else self._params['traffic']['l2'][
+ 'dstmac']
+ dstmac = self._params['traffic']['l2'][
+ 'dstmac'] if not reverse else self._params['traffic']['l2'][
+ 'srcmac']
+ srcip = self._params['traffic']['l3'][
+ 'srcip'] if not reverse else self._params['traffic']['l3']['dstip']
+ dstip = self._params['traffic']['l3'][
+ 'dstip'] if not reverse else self._params['traffic']['l3']['srcip']
+ layer2 = inet.Ether(src=srcmac, dst=dstmac)
+ layer3 = inet.IP(src=srcip, dst=dstip,
+ proto=self._params['traffic']['l3']['proto'])
+ layer4 = inet.UDP(sport=self._params['traffic']['l4']['srcport'],
+ dport=self._params['traffic']['l4']['dstport'])
+ if self._params['traffic']['vlan']['enabled']:
+ vlan = inet.Dot1Q(vlan=self._params['traffic']['vlan']['id'],
+ prio=self._params['traffic']['vlan']['priority'],
+ id=self._params['traffic']['vlan']['cfi'])
+ else:
+ vlan = None
+ packet = layer2/vlan/layer3/layer4 if vlan else layer2/layer3/layer4
+ packet_bytes = bytes(packet)
+ packet_hex = '0x' + binascii.hexlify(packet_bytes).decode('utf-8')
+ return packet_hex
+
+ def _create_api_result(self):
+ """
+ Create result dictionary per trafficgen specifications from socket API
+ stats. If stats are not available return values of 0.
+ :return: ResultsConstants as dictionary
+ """
+ # Handle each case of statistics based on if the data is available.
+ # This prevents uncaught exceptions when the stats aren't available.
+ result_dict = OrderedDict()
+ if self.tx_stats.data.get(self.tx_stats.pt_stream_keys[0]):
+ result_dict[ResultsConstants.TX_FRAMES] = self.tx_stats.data[
+ self.tx_stats.pt_stream_keys[0]]['packets']
+ result_dict[ResultsConstants.TX_RATE_FPS] = self.tx_stats.data[
+ self.tx_stats.pt_stream_keys[0]]['pps']
+ result_dict[ResultsConstants.TX_RATE_MBPS] = self.tx_stats.data[
+ self.tx_stats.pt_stream_keys[0]]['bps'] / 1000000
+ result_dict[ResultsConstants.TX_BYTES] = self.tx_stats.data[
+ self.tx_stats.pt_stream_keys[0]]['bytes']
+ # tx rate percent may need to be halved if bi directional
+ result_dict[ResultsConstants.TX_RATE_PERCENT] = line_percentage(
+ self.xmanager.ports[0], self.tx_stats, self._duration,
+ self._params['traffic']['l2']['framesize']) if \
+ self._params['traffic']['bidir'] == 'False' else\
+ line_percentage(
+ self.xmanager.ports[0], self.tx_stats, self._duration,
+ self._params['traffic']['l2']['framesize']) / 2
+ else:
+ self._logger.error('Transmit stats not available.')
+ result_dict[ResultsConstants.TX_FRAMES] = 0
+ result_dict[ResultsConstants.TX_RATE_FPS] = 0
+ result_dict[ResultsConstants.TX_RATE_MBPS] = 0
+ result_dict[ResultsConstants.TX_BYTES] = 0
+ result_dict[ResultsConstants.TX_RATE_PERCENT] = 0
+
+ if self.rx_stats.data.get('pr_tpldstraffic'):
+ result_dict[ResultsConstants.RX_FRAMES] = self.rx_stats.data[
+ 'pr_tpldstraffic']['0']['packets']
+ result_dict[
+ ResultsConstants.THROUGHPUT_RX_FPS] = self.rx_stats.data[
+ 'pr_tpldstraffic']['0']['pps']
+ result_dict[
+ ResultsConstants.THROUGHPUT_RX_MBPS] = self.rx_stats.data[
+ 'pr_tpldstraffic']['0']['bps'] / 1000000
+ result_dict[ResultsConstants.RX_BYTES] = self.rx_stats.data[
+ 'pr_tpldstraffic']['0']['bytes']
+ # throughput percent may need to be halved if bi directional
+ result_dict[
+ ResultsConstants.THROUGHPUT_RX_PERCENT] = line_percentage(
+ self.xmanager.ports[1], self.rx_stats, self._duration,
+ self._params['traffic']['l2']['framesize']) if \
+ self._params['traffic']['bidir'] == 'False' else \
+ line_percentage(
+ self.xmanager.ports[1], self.rx_stats, self._duration,
+ self._params['traffic']['l2']['framesize']) / 2
+
+ else:
+ self._logger.error('Receive stats not available.')
+ result_dict[ResultsConstants.RX_FRAMES] = 0
+ result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = 0
+ result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = 0
+ result_dict[ResultsConstants.RX_BYTES] = 0
+ result_dict[ResultsConstants.THROUGHPUT_RX_PERCENT] = 0
+
+ if self.rx_stats.data.get('pr_tplderrors'):
+ result_dict[ResultsConstants.PAYLOAD_ERR] = self.rx_stats.data[
+ 'pr_tplderrors']['0']['pld']
+ result_dict[ResultsConstants.SEQ_ERR] = self.rx_stats.data[
+ 'pr_tplderrors']['0']['seq']
+ else:
+ result_dict[ResultsConstants.PAYLOAD_ERR] = 0
+ result_dict[ResultsConstants.SEQ_ERR] = 0
+
+ if self.rx_stats.data.get('pr_tpldlatency'):
+ result_dict[ResultsConstants.MIN_LATENCY_NS] = self.rx_stats.data[
+ 'pr_tpldlatency']['0']['min']
+ result_dict[ResultsConstants.MAX_LATENCY_NS] = self.rx_stats.data[
+ 'pr_tpldlatency']['0']['max']
+ result_dict[ResultsConstants.AVG_LATENCY_NS] = self.rx_stats.data[
+ 'pr_tpldlatency']['0']['avg']
+ else:
+ result_dict[ResultsConstants.MIN_LATENCY_NS] = 0
+ result_dict[ResultsConstants.MAX_LATENCY_NS] = 0
+ result_dict[ResultsConstants.AVG_LATENCY_NS] = 0
+
+ return result_dict
+
def _setup_json_config(self, trials, loss_rate, testtype=None):
"""
Create a 2bUsed json file that will be used for xena2544.exe execution.
@@ -153,14 +286,23 @@ class Xena(ITrafficGenerator):
1, settings.getValue("TRAFFICGEN_XENA_PORT1_IP"),
settings.getValue("TRAFFICGEN_XENA_PORT1_CIDR"),
settings.getValue("TRAFFICGEN_XENA_PORT1_GATEWAY"))
- j_file.set_test_options(
- packet_sizes=self._params['traffic']['l2']['framesize'],
- iterations=trials, loss_rate=loss_rate,
- duration=self._duration, micro_tpld=True if self._params[
- 'traffic']['l2']['framesize'] == 64 else False)
+
if testtype == '2544_throughput':
+ j_file.set_test_options_tput(
+ packet_sizes=self._params['traffic']['l2']['framesize'],
+ iterations=trials, loss_rate=loss_rate,
+ duration=self._duration, micro_tpld=True if self._params[
+ 'traffic']['l2']['framesize'] == 64 else False)
j_file.enable_throughput_test()
+
elif testtype == '2544_b2b':
+ j_file.set_test_options_back2back(
+ packet_sizes=self._params['traffic']['l2']['framesize'],
+ iterations=trials, duration=self._duration,
+ startvalue=self._params['traffic']['frame_rate'],
+ endvalue=self._params['traffic']['frame_rate'],
+ micro_tpld=True if self._params[
+ 'traffic']['l2']['framesize'] == 64 else False)
j_file.enable_back2back_test()
j_file.set_header_layer2(
@@ -192,87 +334,212 @@ class Xena(ITrafficGenerator):
self._logger.exception("Error during Xena JSON setup: %s", exc)
raise
- def connect(self):
- """Connect to the traffic generator.
+ def _start_traffic_api(self, packet_limit):
+ """
+ Start the Xena traffic using the socket API driver
+ :param packet_limit: packet limit for stream, set to -1 for no limit
+ :return: None
+ """
+ if not self.xmanager:
+ self._xsocket = XenaSocketDriver(
+ settings.getValue('TRAFFICGEN_XENA_IP'))
+ self.xmanager = XenaManager(
+ self._xsocket, settings.getValue('TRAFFICGEN_XENA_USER'),
+ settings.getValue('TRAFFICGEN_XENA_PASSWORD'))
+
+ # for the report file version info ask the chassis directly for its
+ # software versions
+ settings.setValue('XENA_VERSION', 'XENA Socket API - {}'.format(
+ self.xmanager.get_version()))
+
+ if not len(self.xmanager.ports):
+ self.xmanager.ports[0] = self.xmanager.add_module_port(
+ settings.getValue('TRAFFICGEN_XENA_MODULE1'),
+ settings.getValue('TRAFFICGEN_XENA_PORT1'))
+ if not self.xmanager.ports[0].reserve_port():
+ self._logger.error(
+ 'Unable to reserve port 0. Please release Xena Port')
+
+ if len(self.xmanager.ports) < 2:
+ self.xmanager.ports[1] = self.xmanager.add_module_port(
+ settings.getValue('TRAFFICGEN_XENA_MODULE2'),
+ settings.getValue('TRAFFICGEN_XENA_PORT2'))
+ if not self.xmanager.ports[1].reserve_port():
+ self._logger.error(
+ 'Unable to reserve port 1. Please release Xena Port')
+
+ # Clear port configuration for a clean start
+ self.xmanager.ports[0].reset_port()
+ self.xmanager.ports[1].reset_port()
+ self.xmanager.ports[0].clear_stats()
+ self.xmanager.ports[1].clear_stats()
+
+ # set the port IP from the conf file
+ self.xmanager.ports[0].set_port_ip(
+ settings.getValue('TRAFFICGEN_XENA_PORT0_IP'),
+ settings.getValue('TRAFFICGEN_XENA_PORT0_CIDR'),
+ settings.getValue('TRAFFICGEN_XENA_PORT0_GATEWAY'))
+ self.xmanager.ports[1].set_port_ip(
+ settings.getValue('TRAFFICGEN_XENA_PORT1_IP'),
+ settings.getValue('TRAFFICGEN_XENA_PORT1_CIDR'),
+ settings.getValue('TRAFFICGEN_XENA_PORT1_GATEWAY'))
+
+ def setup_stream(stream, port, payload_id, flip_addr=False):
+ """
+ Helper function to configure streams.
+ :param stream: Stream object from XenaDriver module
+ :param port: Port object from XenaDriver module
+ :param payload_id: payload ID as int
+ :param flip_addr: Boolean if the source and destination addresses
+ should be flipped.
+ :return: None
+ """
+ stream.set_on()
+ stream.set_packet_limit(packet_limit)
+
+ stream.set_rate_fraction(
+ 10000 * self._params['traffic']['frame_rate'])
+ stream.set_packet_header(self._build_packet_header(
+ reverse=flip_addr))
+ stream.set_header_protocol(
+ 'ETHERNET VLAN IP UDP' if self._params['traffic']['vlan'][
+ 'enabled'] else 'ETHERNET IP UDP')
+ stream.set_packet_length(
+ 'fixed', self._params['traffic']['l2']['framesize'], 16383)
+ stream.set_packet_payload('incrementing', '0x00')
+ stream.set_payload_id(payload_id)
+ port.set_port_time_limit(self._duration * 1000000)
+
+ if self._params['traffic']['l2']['framesize'] == 64:
+ # set micro tpld
+ port.micro_tpld_enable()
+
+ if self._params['traffic']['multistream']:
+ stream.enable_multistream(
+ flows=self._params['traffic']['multistream'],
+ layer=self._params['traffic']['stream_type'])
+
+ s1_p0 = self.xmanager.ports[0].add_stream()
+ setup_stream(s1_p0, self.xmanager.ports[0], 0)
+
+ if self._params['traffic']['bidir'] == 'True':
+ s1_p1 = self.xmanager.ports[1].add_stream()
+ setup_stream(s1_p1, self.xmanager.ports[1], 1, flip_addr=True)
+
+ if not self.xmanager.ports[0].traffic_on():
+ self._logger.error(
+ "Failure to start port 0. Check settings and retry.")
+ if self._params['traffic']['bidir'] == 'True':
+ if not self.xmanager.ports[1].traffic_on():
+ self._logger.error(
+ "Failure to start port 1. Check settings and retry.")
+ sleep(self._duration)
+ # getting results
+ if self._params['traffic']['bidir'] == 'True':
+ # need to aggregate out both ports stats and assign that data
+ self.rx_stats = self.xmanager.ports[1].get_rx_stats()
+ self.tx_stats = self.xmanager.ports[0].get_tx_stats()
+ self.tx_stats.data = aggregate_stats(
+ self.tx_stats.data,
+ self.xmanager.ports[1].get_tx_stats().data)
+ self.rx_stats.data = aggregate_stats(
+ self.rx_stats.data,
+ self.xmanager.ports[0].get_rx_stats().data)
+ else:
+ # no need to aggregate, just grab the appropriate port stats
+ self.tx_stats = self.xmanager.ports[0].get_tx_stats()
+ self.rx_stats = self.xmanager.ports[1].get_rx_stats()
+ sleep(1)
- This is an optional function, designed for traffic generators
- which must be "connected to" (i.e. via SSH or an API) before
- they can be used. If not required, simply do nothing here.
+ def _stop_api_traffic(self):
+ """
+ Stop traffic through the socket API
+ :return: Return results from _create_api_result method
+ """
+ self.xmanager.ports[0].traffic_off()
+ if self._params['traffic']['bidir'] == 'True':
+ self.xmanager.ports[1].traffic_off()
+ sleep(5)
- Where implemented, this function should raise an exception on
- failure.
+ stat = self._create_api_result()
+ self.disconnect()
+ return stat
- :returns: None
- """
- pass
+ def connect(self):
+ self._logger.debug('Connect')
+ return self
def disconnect(self):
"""Disconnect from the traffic generator.
As with :func:`connect`, this function is optional.
+
Where implemented, this function should raise an exception on
failure.
:returns: None
"""
- pass
+ self._logger.debug('disconnect')
+ if self.xmanager:
+ self.xmanager.disconnect()
+ self.xmanager = None
+
+ if self._xsocket:
+ self._xsocket.disconnect()
+ self._xsocket = None
def send_burst_traffic(self, traffic=None, numpkts=100, duration=20):
"""Send a burst of traffic.
- Send a ``numpkts`` packets of traffic, using ``traffic``
- configuration, with a timeout of ``time``.
+ See ITrafficGenerator for description
+ """
+ self._duration = duration
- Attributes:
- :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
- :param numpkts: Number of packets to send
- :param duration: Time to wait to receive packets
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
- :returns: dictionary of strings with following data:
- - List of Tx Frames,
- - List of Rx Frames,
- - List of Tx Bytes,
- - List of List of Rx Bytes,
- - Payload Errors and Sequence Errors.
- """
- raise NotImplementedError('Xena burst traffic not implemented')
+ self._start_traffic_api(numpkts)
+ return self._stop_api_traffic()
def send_cont_traffic(self, traffic=None, duration=20):
- """Send a continuous flow of traffic.r
-
- Send packets at ``framerate``, using ``traffic`` configuration,
- until timeout ``time`` occurs.
-
- :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
- :param duration: Time to wait to receive packets (secs)
- :returns: dictionary of strings with following data:
- - Tx Throughput (fps),
- - Rx Throughput (fps),
- - Tx Throughput (mbps),
- - Rx Throughput (mbps),
- - Tx Throughput (% linerate),
- - Rx Throughput (% linerate),
- - Min Latency (ns),
- - Max Latency (ns),
- - Avg Latency (ns)
+ """Send a continuous flow of traffic.
+
+ See ITrafficGenerator for description
"""
- raise NotImplementedError('Xena continuous traffic not implemented')
+ self._duration = duration
+
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
+
+ self._start_traffic_api(-1)
+ return self._stop_api_traffic()
def start_cont_traffic(self, traffic=None, duration=20):
"""Non-blocking version of 'send_cont_traffic'.
- Start transmission and immediately return. Do not wait for
- results.
- :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
- :param duration: Time to wait to receive packets (secs)
+ See ITrafficGenerator for description
"""
- raise NotImplementedError('Xena continuous traffic not implemented')
+ self._duration = duration
+
+ self._params.clear()
+ self._params['traffic'] = self.traffic_defaults.copy()
+ if traffic:
+ self._params['traffic'] = merge_spec(self._params['traffic'],
+ traffic)
+
+ self._start_traffic_api(-1)
def stop_cont_traffic(self):
"""Stop continuous transmission and return results.
"""
- raise NotImplementedError('Xena continuous traffic not implemented')
+ return self._stop_api_traffic()
def send_rfc2544_throughput(self, traffic=None, trials=3, duration=20,
lossrate=0.0):
diff --git a/tools/pkt_gen/xena/xena_json.py b/tools/pkt_gen/xena/xena_json.py
index 23b56084..2a15a932 100644
--- a/tools/pkt_gen/xena/xena_json.py
+++ b/tools/pkt_gen/xena/xena_json.py
@@ -354,10 +354,10 @@ class XenaJSON(object):
self.json_data['PortHandler']['EntityList'][
port]["IpV6RoutingPrefix"] = int(netmask)
- def set_test_options(self, packet_sizes, duration, iterations, loss_rate,
- micro_tpld=False):
+ def set_test_options_tput(self, packet_sizes, duration, iterations,
+ loss_rate, micro_tpld=False):
"""
- Set the test options
+ Set the tput test options
:param packet_sizes: List of packet sizes to test, single int entry is
acceptable for one packet size testing
:param duration: time for each test in seconds as int
@@ -379,6 +379,35 @@ class XenaJSON(object):
self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][
'Iterations'] = iterations
+ def set_test_options_back2back(self, packet_sizes, duration,
+ iterations, startvalue, endvalue,
+ micro_tpld=False):
+ """
+ Set the back2back test options
+ :param packet_sizes: List of packet sizes to test, single int entry is
+ acceptable for one packet size testing
+ :param duration: time for each test in seconds as int
+ :param iterations: number of iterations of testing as int
+ :param micro_tpld: boolean if micro_tpld should be enabled or disabled
+ :param StartValue: start value
+ :param EndValue: end value
+ :return: None
+ """
+ if isinstance(packet_sizes, int):
+ packet_sizes = [packet_sizes]
+ self.json_data['TestOptions']['PacketSizes'][
+ 'CustomPacketSizes'] = packet_sizes
+ self.json_data['TestOptions']['TestTypeOptionMap']['Back2Back'][
+ 'Duration'] = duration
+ self.json_data['TestOptions']['FlowCreationOptions'][
+ 'UseMicroTpldOnDemand'] = 'true' if micro_tpld else 'false'
+ self.json_data['TestOptions']['TestTypeOptionMap']['Back2Back'][
+ 'Iterations'] = iterations
+ self.json_data['TestOptions']['TestTypeOptionMap']['Back2Back'][
+ 'RateSweepOptions']['StartValue'] = startvalue
+ self.json_data['TestOptions']['TestTypeOptionMap']['Back2Back'][
+ 'RateSweepOptions']['EndValue'] = endvalue
+
def set_topology_blocks(self):
"""
Set the test topology to a West to East config for half duplex flow with
@@ -586,8 +615,8 @@ if __name__ == "__main__":
JSON.set_header_layer3(src_ip='192.168.100.2', dst_ip='192.168.100.3',
protocol='udp')
JSON.set_header_layer4_udp(source_port=3000, destination_port=3001)
- JSON.set_test_options(packet_sizes=[64], duration=10, iterations=1,
- loss_rate=0.0, micro_tpld=True)
+ JSON.set_test_options_tput(packet_sizes=[64], duration=10, iterations=1,
+ loss_rate=0.0, micro_tpld=True)
JSON.add_header_segments(flows=4000, multistream_layer='L4')
JSON.set_topology_blocks()
write_json_file(JSON.json_data, './testthis.x2544')
diff --git a/vsperf b/vsperf
index 789e5df8..f2f443be 100755
--- a/vsperf
+++ b/vsperf
@@ -622,6 +622,10 @@ def main():
traffic_ctl.send_traffic(traffic)
_LOGGER.debug("Traffic Results:")
traffic_ctl.print_results()
+
+ # write results into CSV file
+ result_file = os.path.join(results_path, "result.csv")
+ PerformanceTestCase.write_result_to_file(traffic_ctl.get_results(), result_file)
else:
# configure tests
if args['integration']: