diff options
-rw-r--r-- | conf/03_traffic.conf | 33 | ||||
-rw-r--r-- | conf/10_custom.conf | 25 | ||||
-rw-r--r-- | docs/release/release-notes/release-notes.rst | 35 | ||||
-rw-r--r-- | docs/testing/developer/devguide/results/results.rst | 42 | ||||
-rw-r--r-- | docs/testing/user/configguide/installation.rst | 1 | ||||
-rw-r--r-- | docs/testing/user/configguide/trafficgen.rst | 96 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | src/Makefile | 1 | ||||
-rw-r--r-- | src/package-list.mk | 4 | ||||
-rw-r--r-- | src/trex/Makefile | 55 | ||||
-rwxr-xr-x | systems/opensuse/42.2/build_base_machine.sh | 4 | ||||
-rwxr-xr-x | systems/opensuse/42.3/build_base_machine.sh | 93 | ||||
-rwxr-xr-x | systems/opensuse/42.3/prepare_python_env.sh | 28 | ||||
-rw-r--r-- | tools/opnfvdashboard/opnfvdashboard.py | 131 | ||||
-rw-r--r-- | tools/pkt_gen/trex/__init__.py | 13 | ||||
-rw-r--r-- | tools/pkt_gen/trex/trex.py | 338 | ||||
-rw-r--r-- | tools/systeminfo.py | 4 | ||||
-rwxr-xr-x | vsperf | 15 |
18 files changed, 819 insertions, 100 deletions
diff --git a/conf/03_traffic.conf b/conf/03_traffic.conf index 764fd731..179ff3e2 100644 --- a/conf/03_traffic.conf +++ b/conf/03_traffic.conf @@ -178,6 +178,7 @@ TRAFFICGEN = 'Dummy' #TRAFFICGEN = 'Ixia' #TRAFFICGEN = 'Xena' #TRAFFICGEN = 'Moongen' +#TRAFFICGEN = 'Trex' # List of packet sizes to send. # Expand like this: (64, 128, 256, 512, 1024) @@ -415,4 +416,34 @@ TRAFFICGEN_MOONGEN_PORTS = '' TRAFFICGEN_MOONGEN_LINE_SPEED_GBPS = '' # MoonGen Configuration and Connection Info-- END -################################################### +################################################# + +################################################ +# Trex Configuration and Connection Info-- BEGIN + +# Example: TRAFFICGEN_TREX_HOST_IP_ADDR = "192.10.1.1" +# Example: TRAFFICGEN_TREX_USER = 'root' +# Example: TRAFFICGEN_TREX_BASE_DIR = '/traffic_gen/trex/' +# Example: TRAFFICGEN_TREX_PORT1 = '00:00:00:00:00:00' +TRAFFICGEN_TREX_HOST_IP_ADDR = '' +TRAFFICGEN_TREX_USER = '' +TRAFFICGEN_TREX_BASE_DIR = '' +TRAFFICGEN_TREX_PORT1 = '' +TRAFFICGEN_TREX_PORT2 = '' +# Latency statistics are collected by separate stream created for each interface. +# Parameter below defines frequency of packets used for latency measurement in PPS. +# Value 0 will disable latency specific streams. +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +# Example 10 Gbps: TRAFFICGEN_TREXINE_SPEED_GBPS = '10' +# Today only 10 Gbps is supported +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' +PATHS['trafficgen'] = { + 'trex': { + 'type' : 'src', + 'src': { + 'path': os.path.join(ROOT_DIR, 'src/trex/trex/scripts/automation/trex_control_plane/stl') + } + } +} +# TREX Configuration and Connection Info-- END +############################################## diff --git a/conf/10_custom.conf b/conf/10_custom.conf index 9622fd71..6011e6a8 100644 --- a/conf/10_custom.conf +++ b/conf/10_custom.conf @@ -21,6 +21,7 @@ TRAFFICGEN = 'Dummy' #TRAFFICGEN = 'Ixia' #TRAFFICGEN = 'Xena' #TRAFFICGEN = 'Moongen' +#TRAFFICGEN = 'Trex' ########################################### # Spirent TestCenter Configuration -- BEGIN @@ -116,6 +117,30 @@ TRAFFICGEN_MOONGEN_LINE_SPEED_GBPS = '10' # MoonGen Configuration and Connection Info-- END ################################################### +################################################### +# TREX Configuration and Connection Info-- BEGIN + +# Example: TRAFFICGEN_TREX_HOST_IP_ADDR = "192.10.1.1" +# Example: TRAFFICGEN_TREX_USER = 'root' +# Example: TRAFFICGEN_TREX_BASE_DIR = '/traffic_gen/trex/' +# Example: TRAFFICGEN_TREX_PORT1 = '00:00:00:00:00:00' +TRAFFICGEN_TREX_HOST_IP_ADDR = '' +TRAFFICGEN_TREX_USER = '' +TRAFFICGEN_TREX_BASE_DIR = '' +TRAFFICGEN_TREX_PORT1 = '' +TRAFFICGEN_TREX_PORT2 = '' +# Latency statistics are collected by separate stream created for each interface. +# Parameter below defines frequency of packets used for latency measurement in PPS. +# Value 0 will disable latency specific streams. +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +# Example 10 Gbps: TRAFFICGEN_TREXINE_SPEED_GBPS = '10' +# Today only 10 Gbps is supported +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' + +# TREX Configuration and Connection Info-- END +#################################################### + +#################################################### #TEST_PARAMS = {'TRAFFICGEN_PKT_SIZES':(64,)} OPNFV_INSTALLER = "Fuel" OPNFV_URL = "http://testresults.opnfv.org/test/api/v1" diff --git a/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst index c2dff390..860cca77 100644 --- a/docs/release/release-notes/release-notes.rst +++ b/docs/release/release-notes/release-notes.rst @@ -2,6 +2,41 @@ .. http://creativecommons.org/licenses/by/4.0 .. (c) OPNFV, Intel Corporation, AT&T and others. +OPNFV Euphrates Release +======================= + +* Improvement of stepdriven testcases +* Support for graph plotting from vsperf results +* Support for vHost User client mode in OVS and VPP +* Support for DPDK 17.02 +* Support for dpdk driver NIC binding by drivectl tool +* Support for openSUSE Leap 42.3 +* Several bugfixes and small improvements + +* vSwitches + + * Support for VPP virtual switch + * OVS: Support for jumbo frames + +* Traffic Generators: + + * Support for Trex traffic generator + * Support for huge number of streams + * Ixia: L3, L4 or vlan headers can be turned off/on, support of 1 NIC connection + between DUT and Ixia, bugfixing + * MoonGen: fix multistream support + * Xena: option for final verification, JSON refactoring, support for xena + pairs topology and port removal options, bugfixes + +* Guest specific: + + * Support for additional QEMU cpu features + * Support for pinning of vCPU threads + +* Integration tests: + + * New VPP related testcases + * New multistream testcases focused on L3 and L4 performance of OVS and VPP OPNFV Danube Release ==================== diff --git a/docs/testing/developer/devguide/results/results.rst b/docs/testing/developer/devguide/results/results.rst index df9c52cb..0a0ab75d 100644 --- a/docs/testing/developer/devguide/results/results.rst +++ b/docs/testing/developer/devguide/results/results.rst @@ -7,32 +7,22 @@ OPNFV Test Results VSPERF CI jobs are run daily and sample results can be found at https://wiki.opnfv.org/display/vsperf/Vsperf+Results -The following example maps the results in the test dashboard to the appropriate -test case in the VSPERF Framework and specifies the metric the vertical/Y axis -is plotting. **Please note**, the presence of dpdk within a test name signifies -that the vswitch under test was OVS with DPDK, while its absence indicates that -the vswitch under test was stock OVS. +Testcase names shown in the dashboard are combination of orignal testcase +name from VSPERF framework and indication of used vswitch. -===================== ===================== ================== =============== - Dashboard Test Framework Test Metric Guest Interface -===================== ===================== ================== =============== -tput_ovsdpdk phy2phy_tput Throughput (FPS) N/A -tput_ovs phy2phy_tput Throughput (FPS) N/A -b2b_ovsdpdk back2back Back-to-back value N/A -b2b_ovs back2back Back-to-back value N/A -tput_mod_vlan_ovs phy2phy_tput_mod_vlan Throughput (FPS) N/A -tput_mod_vlan_ovsdpdk phy2phy_tput_mod_vlan Throughput (FPS) N/A -scalability_ovs phy2phy_scalability Throughput (FPS) N/A -scalability_ovsdpdk phy2phy_scalability Throughput (FPS) N/A -pvp_tput_ovsdpdkuser pvp_tput Throughput (FPS) vhost-user -pvp_tput_ovsvirtio pvp_tput Throughput (FPS) virtio-net -pvp_b2b_ovsdpdkuser pvp_back2back Back-to-back value vhost-user -pvp_b2b_ovsvirtio pvp_back2back Back-to-back value virtio-net -pvvp_tput_ovsdpdkuser pvvp_tput Throughput (FPS) vhost-user -pvvp_tput_ovsvirtio pvvp_tput Throughput (FPS) virtio-net -pvvp_b2b_ovsdpdkuser pvvp_back2back Throughput (FPS) vhost-user -pvvp_b2b_ovsvirtio pvvp_back2back Throughput (FPS) virtio-net -===================== ===================== ================== =============== + Example: -The loopback application in the VNF was used for PVP and PVVP scenarios was DPDK + Testcase ``phy2phy_tput`` is executed for three vSwitch types: ``OvsDpdkVhost``, + ``OvsVanilla`` and ``VppDpdkVhost``. In this case, following testcase names + will be used in the dashboard: ``phy2phy_tput_ovsdpdkvhost``, + ``phy2phy_tput_ovsvanilla`` and ``phy2phy_tput_vppdpdkvhost``. + +In case of RFC2544 Throughput test, the recorded metric is FPS (frames per +second) without packet loss. For RFC2544 Back2Back test, the recorded metric +is back-to-back value (number of frames) without packet loss. + +The loopback application in the VNF used for PVP and PVVP scenarios was DPDK testpmd. + +Guest interface types are ``vhost-user`` for ``OvsDpdkVhost`` and ``VppDpdkVhost`` +and ``virtio-net`` for ``OvsVanilla``. diff --git a/docs/testing/user/configguide/installation.rst b/docs/testing/user/configguide/installation.rst index 207e50a4..837bee1c 100644 --- a/docs/testing/user/configguide/installation.rst +++ b/docs/testing/user/configguide/installation.rst @@ -48,6 +48,7 @@ Supported Operating Systems * Fedora 24 (kernel 4.8 requires DPDK 16.11 and newer) * Fedora 25 (kernel 4.9 requires DPDK 16.11 and newer) * openSUSE 42.2 +* openSUSE 42.3 * RedHat 7.2 Enterprise Linux * RedHat 7.3 Enterprise Linux * Ubuntu 14.04 diff --git a/docs/testing/user/configguide/trafficgen.rst b/docs/testing/user/configguide/trafficgen.rst index 1059ce12..3c827f38 100644 --- a/docs/testing/user/configguide/trafficgen.rst +++ b/docs/testing/user/configguide/trafficgen.rst @@ -18,6 +18,7 @@ VSPERF supports the following traffic generators: * `Spirent TestCenter`_ * `Xena Networks`_ * MoonGen_ + * Trex_ To see the list of traffic gens from the cli: @@ -714,3 +715,98 @@ set to allow for proper connections to the host with MoonGen. TRAFFICGEN_MOONGEN_BASE_DIR = "" TRAFFICGEN_MOONGEN_PORTS = "" TRAFFICGEN_MOONGEN_LINE_SPEED_GBPS = "" + +Trex +---- + +Installation +~~~~~~~~~~~~ + +Trex architecture overview and general installation instructions +can be found here: + +https://trex-tgn.cisco.com/trex/doc/trex_stateless.html + +You can directly download from GitHub: + +.. code-block:: console + + git clone https://github.com/cisco-system-traffic-generator/trex-core + +and use the master branch: + +.. code-block:: console + + git checkout master + +or Trex latest release you can download from here: + +.. code-block:: console + + wget --no-cache http://trex-tgn.cisco.com/trex/release/latest + +After download, Trex repo has to be built: + +.. code-block:: console + + cd trex-core/linux_dpdk + ./b configure (run only once) + ./b build + +Next step is to create a minimum configuration file. It can be created by script ``dpdk_setup_ports.py``. +The script with parameter ``-i`` will run in interactive mode and it will create file ``/etc/trex_cfg.yaml``. + +.. code-block:: console + + cd trex-core/scripts + sudo ./dpdk_setup_ports.py -i + +Or example of configuration file can be found at location below, but it must be updated manually: + +.. code-block:: console + + cp trex-core/scripts/cfg/simple_cfg /etc/trex_cfg.yaml + +For additional information about configuration file see official documentation (chapter 3.1.2): + +https://trex-tgn.cisco.com/trex/doc/trex_manual.html#_creating_minimum_configuration_file + +After compilation and configuration it is possible to run trex server in stateless mode. +It is neccesary for proper connection between Trex server and VSPERF. + +.. code-block:: console + + cd trex-core/scripts/ + ./t-rex-64 -i + +For additional information about Trex stateless mode see Trex stateless documentation: + +https://trex-tgn.cisco.com/trex/doc/trex_stateless.html + +**NOTE:** One will need to set up ssh login to not use passwords between the server +running Trex and the device under test (running the VSPERF test +infrastructure). This is because VSPERF on one server uses 'ssh' to +configure and run Trex 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> + +Configuration +~~~~~~~~~~~~~ + +Connection information for Trex must be supplied inside the custom configuration +file. The following parameters must be set to allow for proper connections to the host with Trex. +Example of this configuration is in conf/03_traffic.conf or conf/10_custom.conf. + +.. code-block:: console + + TRAFFICGEN_TREX_HOST_IP_ADDR = '' + TRAFFICGEN_TREX_USER = '' + TRAFFICGEN_TREX_BASE_DIR = '' + +TRAFFICGEN_TREX_USER has to have sudo permission and passwordless access. +TRAFFICGEN_TREX_BASE_DIR is the place, where is stored 't-rex-64' file. diff --git a/requirements.txt b/requirements.txt index 8b928d4d..33bee1bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,5 @@ xmlrunner==1.7.7 requests==2.8.1 netaddr==0.7.18 scapy-python3==0.18 +pyzmq==14.5.0 distro diff --git a/src/Makefile b/src/Makefile index 6cd21dd6..db6c5e3c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -41,6 +41,7 @@ SUBDIRS += dpdk SUBDIRS += ovs SUBDIRS += qemu SUBDIRS += vpp +SUBDIRS += trex ovs: dpdk WITH_LINUX = VHOST_USER = y diff --git a/src/package-list.mk b/src/package-list.mk index 5abb6018..cf2ff57f 100644 --- a/src/package-list.mk +++ b/src/package-list.mk @@ -26,3 +26,7 @@ VPP_TAG ?= v17.04 # QEMU section QEMU_URL ?= https://github.com/qemu/qemu.git QEMU_TAG ?= v2.5.0 + +# TREX section +TREX_URL ?= https://github.com/cisco-system-traffic-generator/trex-core.git +TREX_TAG ?= 8bf9c16556843e55c232b64d9a5061bf588fad42 diff --git a/src/trex/Makefile b/src/trex/Makefile new file mode 100644 index 00000000..9aaaa203 --- /dev/null +++ b/src/trex/Makefile @@ -0,0 +1,55 @@ +# makefile to manage trex package +# + +# Copyright 2017 Martin Goldammer, OPNFV +# +# 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. + +include ../mk/master.mk +include ../package-list.mk + +WORK_DIR = trex +TAG_DONE_FLAG = $(WORK_DIR)/.$(TREX_TAG).done + +.PHONY: force_pull + +all: force_pull + @echo "Finished pulling $(WORK_DIR) " + +force_pull: $(WORK_DIR) Makefile + $(AT)cd $(WORK_DIR) && git pull $(TREX_URL) $(TREX_TAG) + @echo "git pull done" + +$(WORK_DIR): + $(AT)git clone $(TREX_URL) $(WORK_DIR) + +$(TAG_DONE_FLAG): $(WORK_DIR) + $(AT)cd $(WORK_DIR); git checkout $(TREX_TAG) + +install: + @echo "Make install in $(WORK_DIR) (stub) " + +clobber: + $(AT)rm -rf $(WORK_DIR) + +distclean: + @echo "Make distclean in $(WORK_DIR) (stub) " + +clean: + @echo "Make clean in $(WORK_DIR) (stub) " + +test: + @echo "Make test in $(WORK_DIR) (stub) " + +sanity: + @echo "Make sanity in $(WORK_DIR) (stub) " diff --git a/systems/opensuse/42.2/build_base_machine.sh b/systems/opensuse/42.2/build_base_machine.sh index cf55484b..44d6f02b 100755 --- a/systems/opensuse/42.2/build_base_machine.sh +++ b/systems/opensuse/42.2/build_base_machine.sh @@ -19,8 +19,8 @@ # Contributors: # Marco Varlese, SUSE LINUX GmbH -zypper update -zypper install $(echo " +zypper -q -n update +zypper -q -n install -y $(echo " # compiler, tools and dependencies make automake diff --git a/systems/opensuse/42.3/build_base_machine.sh b/systems/opensuse/42.3/build_base_machine.sh new file mode 100755 index 00000000..277b8b1b --- /dev/null +++ b/systems/opensuse/42.3/build_base_machine.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Build a base machine for openSUSE Leap 42.2 systems +# +# Copyright (c) 2017 SUSE LLC. +# +# 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: +# Marco Varlese, SUSE LINUX GmbH + +zypper -q -n update +zypper -q -n in -y $(echo " +# compiler, tools and dependencies +make +automake +gcc +gcc-c++ +glibc +glibc-devel +fuse +fuse-devel +glib2-devel +zlib-devel +ncurses-devel +kernel-default +kernel-default-devel +pkg-config +findutils-locate +curl +automake +autoconf +vim +wget +git +pciutils +cifs-utils +socat +sysstat +java-1_8_0-openjdk +git-review + +# python +python3 +python-pip +python3-pip +python3-setuptools +python3-devel +python3-tk + +# libraries +libnuma1 +libnuma-devel +libpixman-1-0 +libpixman-1-0-devel +libtool +libpcap-devel +libnet9 +libncurses5 +libcurl4 +libcurl-devel +libxml2 +libfuse2 +libopenssl1_0_0 +libopenssl-devel +libpython3_4m1_0 + +" | grep -v ^#) + +updatedb + +# fix for the Ixia TclClient +ln -sf $(locate libc.so.6) /lib/libc.so.6 + +# virtual environment for python +pip3 install virtualenv + +# hugepages setup +mkdir -p /dev/hugepages + +# fix for non-utf8 characters in file +cp /etc/services /etc/services.bak +iconv -o /etc/services -f utf-8 -t utf-8 -c /etc/services.bak diff --git a/systems/opensuse/42.3/prepare_python_env.sh b/systems/opensuse/42.3/prepare_python_env.sh new file mode 100755 index 00000000..66f94cf7 --- /dev/null +++ b/systems/opensuse/42.3/prepare_python_env.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Prepare Python environment for vsperf execution on openSUSE Leap 42.2 systems +# +# Copyright (c) 2017 SUSE LLC. +# +# 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. + +if [ -d "$VSPERFENV_DIR" ] ; then + echo "Directory $VSPERFENV_DIR already exists. Skipping python virtualenv creation." + exit +fi + +virtualenv "$VSPERFENV_DIR" +source "$VSPERFENV_DIR"/bin/activate +pip install -r ../requirements.txt +pip install pylint + diff --git a/tools/opnfvdashboard/opnfvdashboard.py b/tools/opnfvdashboard/opnfvdashboard.py index c24b9f8c..a77a9c3a 100644 --- a/tools/opnfvdashboard/opnfvdashboard.py +++ b/tools/opnfvdashboard/opnfvdashboard.py @@ -17,10 +17,14 @@ vsperf2dashboard import os import csv +import copy import logging +from datetime import datetime as dt import requests -def results2opnfv_dashboard(results_path, int_data): +_DETAILS = {"64": '', "128": '', "512": '', "1024": '', "1518": ''} + +def results2opnfv_dashboard(tc_names, results_path, int_data): """ the method open the csv file with results and calls json encoder """ @@ -31,22 +35,33 @@ def results2opnfv_dashboard(results_path, int_data): resfile = results_path + '/' + test with open(resfile, 'r') as in_file: reader = csv.DictReader(in_file) - _push_results(reader, int_data) + tc_data = _prepare_results(reader, int_data) + _push_results(tc_data) + tc_names.remove(tc_data['id']) + + # report TCs without results as FAIL + if tc_names: + tc_fail = copy.deepcopy(int_data) + tc_fail['start_time'] = dt.now().strftime('%Y-%m-%d %H:%M:%S') + tc_fail['stop_time'] = tc_fail['start_time'] + tc_fail['criteria'] = 'FAIL' + tc_fail['version'] = 'N/A' + tc_fail['details'] = copy.deepcopy(_DETAILS) + for tc_name in tc_names: + tc_fail['dashboard_id'] = "{}_{}".format(tc_name, tc_fail['vswitch']) + _push_results(tc_fail) -def _push_results(reader, int_data): +def _prepare_results(reader, int_data): """ - the method encodes results and sends them into opnfv dashboard + the method prepares dashboard details for passed testcases """ - db_url = int_data['db_url'] - url = db_url + "/results" - casename = "" - version_ovs = "" + version_vswitch = "" version_dpdk = "" - version = "" allowed_pkt = ["64", "128", "512", "1024", "1518"] - details = {"64": '', "128": '', "512": '', "1024": '', "1518": ''} - test_start = None - test_stop = None + vswitch = None + details = copy.deepcopy(_DETAILS) + tc_data = copy.deepcopy(int_data) + tc_data['criteria'] = 'PASS' for row_reader in reader: if allowed_pkt.count(row_reader['packet_size']) == 0: @@ -55,77 +70,65 @@ def _push_results(reader, int_data): # test execution time includes all frame sizes, so start & stop time # is the same (repeated) for every framesize in CSV file - if test_start is None: - test_start = row_reader['start_time'] - test_stop = row_reader['stop_time'] + if not 'test_start' in tc_data: + tc_data['start_time'] = row_reader['start_time'] + tc_data['stop_time'] = row_reader['stop_time'] + tc_data['id'] = row_reader['id'] + # CI job executes/reports TCs per vswitch type + vswitch = row_reader['vswitch'] - casename = _generate_test_name(row_reader['id'], int_data) + tc_data['dashboard_id'] = "{}_{}".format(row_reader['id'], row_reader['vswitch'].lower()) if "back2back" in row_reader['id']: + # 0 B2B frames is quite common, so we can't mark such TC as FAIL details[row_reader['packet_size']] = row_reader['b2b_frames'] else: details[row_reader['packet_size']] = row_reader['throughput_rx_fps'] + # 0 PPS is definitelly a failure + if float(row_reader['throughput_rx_fps']) == 0: + tc_data['criteria'] = 'FAIL' # Create version field - with open(int_data['pkg_list'], 'r') as pkg_file: + with open(tc_data['pkg_list'], 'r') as pkg_file: for line in pkg_file: - if "OVS_TAG" in line: - version_ovs = line.replace(' ', '') - version_ovs = version_ovs.replace('OVS_TAG?=', '') + if "OVS_TAG" in line and vswitch.startswith('Ovs'): + version_vswitch = line.replace(' ', '') + version_vswitch = "OVS " + version_vswitch.replace('OVS_TAG?=', '') + if "VPP_TAG" in line and vswitch.startswith('Vpp'): + version_vswitch = line.replace(' ', '') + version_vswitch = "VPP " + version_vswitch.replace('VPP_TAG?=', '') if "DPDK_TAG" in line: - if int_data['vanilla'] is False: + # DPDK_TAG is not used by VPP, it downloads its onw DPDK version + if vswitch == "OvsDpdkVhost": version_dpdk = line.replace(' ', '') - version_dpdk = version_dpdk.replace('DPDK_TAG?=', '') - else: - version_dpdk = "not used" - version = "OVS " + version_ovs.replace('\n', '') + " DPDK " + version_dpdk.replace('\n', '') + version_dpdk = " DPDK {}".format( + version_dpdk.replace('DPDK_TAG?=', '')) + + tc_data['details'] = details + tc_data['version'] = version_vswitch.replace('\n', '') + version_dpdk.replace('\n', '') + + return tc_data + +def _push_results(int_data): + """ + the method sends testcase details into dashboard database + """ + url = int_data['db_url'] + "/results" # Build body body = {"project_name": "vsperf", "scenario": "vsperf", - "start_date": test_start, - "stop_date": test_stop, - "case_name": casename, + "start_date": int_data['start_time'], + "stop_date": int_data['stop_time'], + "case_name": int_data['dashboard_id'], "pod_name": int_data['pod'], "installer": int_data['installer'], - "version": version, + "version": int_data['version'], "build_tag": int_data['build_tag'], "criteria": int_data['criteria'], - "details": details} + "details": int_data['details']} my_data = requests.post(url, json=body) - logging.info("Results for %s sent to opnfv, http response: %s", casename, my_data) - logging.debug("opnfv url: %s", db_url) + logging.info("Results for %s sent to opnfv, http response: %s", int_data['dashboard_id'], my_data) + logging.debug("opnfv url: %s", int_data['db_url']) logging.debug("the body sent to opnfv") logging.debug(body) - -def _generate_test_name(testcase, int_data): - """ - the method generates testcase name for releng - """ - vanilla = int_data['vanilla'] - res_name = "" - - names = {'phy2phy_tput': ["tput_ovsdpdk", "tput_ovs"], - 'back2back': ["b2b_ovsdpdk", "b2b_ovs"], - 'phy2phy_tput_mod_vlan': ["tput_mod_vlan_ovsdpdk", "tput_mod_vlan_ovs"], - 'phy2phy_cont': ["cont_ovsdpdk", "cont_ovs"], - 'pvp_cont': ["pvp_cont_ovsdpdkuser", "pvp_cont_ovsvirtio"], - 'pvvp_cont': ["pvvp_cont_ovsdpdkuser", "pvvp_cont_ovsvirtio"], - 'phy2phy_scalability': ["scalability_ovsdpdk", "scalability_ovs"], - 'pvp_tput': ["pvp_tput_ovsdpdkuser", "pvp_tput_ovsvirtio"], - 'pvp_back2back': ["pvp_b2b_ovsdpdkuser", "pvp_b2b_ovsvirtio"], - 'pvvp_tput': ["pvvp_tput_ovsdpdkuser", "pvvp_tput_ovsvirtio"], - 'pvvp_back2back': ["pvvp_b2b_ovsdpdkuser", "pvvp_b2b_ovsvirtio"], - 'phy2phy_cpu_load': ["cpu_load_ovsdpdk", "cpu_load_ovs"], - 'phy2phy_mem_load': ["mem_load_ovsdpdk", "mem_load_ovs"]} - - for name, name_list in names.items(): - if name != testcase: - continue - if vanilla is True: - res_name = name_list[1] - else: - res_name = name_list[0] - break - - return res_name diff --git a/tools/pkt_gen/trex/__init__.py b/tools/pkt_gen/trex/__init__.py new file mode 100644 index 00000000..455a1ef0 --- /dev/null +++ b/tools/pkt_gen/trex/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Martin Goldammer OPNFV. +# +# 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/trex/trex.py b/tools/pkt_gen/trex/trex.py new file mode 100644 index 00000000..ae262306 --- /dev/null +++ b/tools/pkt_gen/trex/trex.py @@ -0,0 +1,338 @@ +# Copyright 2017 Martin Goldammer, OPNFV, 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. +# +""" +Trex Traffic Generator Model +""" +# pylint: disable=undefined-variable +import logging +import subprocess +import sys +from collections import OrderedDict +# pylint: disable=unused-import +import zmq +from conf import settings +from conf import merge_spec +from core.results.results_constants import ResultsConstants +from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator +# pylint: disable=wrong-import-position, import-error +sys.path.append(settings.getValue('PATHS')['trafficgen']['trex']['src']['path']) +from trex_stl_lib.api import * + +class Trex(ITrafficGenerator): + """Trex Traffic generator wrapper.""" + _logger = logging.getLogger(__name__) + + def __init__(self): + """Trex class constructor.""" + super().__init__() + self._logger.info("In trex __init__ method") + self._params = {} + self._trex_host_ip_addr = ( + settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR')) + self._trex_base_dir = ( + settings.getValue('TRAFFICGEN_TREX_BASE_DIR')) + self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER') + self._stlclient = None + + def connect(self): + '''Connect to Trex traffic generator + + Verify that Trex is on the system indicated by + the configuration file + ''' + self._stlclient = STLClient() + self._logger.info("TREX: In Trex connect method...") + if self._trex_host_ip_addr: + cmd_ping = "ping -c1 " + self._trex_host_ip_addr + else: + raise RuntimeError('TREX: Trex 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('TREX: Cannot ping Trex host at ' + \ + self._trex_host_ip_addr) + + connect_trex = "ssh " + self._trex_user + \ + "@" + self._trex_host_ip_addr + + cmd_find_trex = connect_trex + " ls " + \ + self._trex_base_dir + "t-rex-64" + + + find_trex = subprocess.Popen(cmd_find_trex, + shell=True, + stderr=subprocess.PIPE) + output, error = find_trex.communicate() + + if find_trex.returncode: + self._logger.error(error) + self._logger.error(output) + raise RuntimeError( + 'TREX: Cannot locate Trex program at %s within %s' \ + % (self._trex_host_ip_addr, self._trex_base_dir)) + + self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr, + verbose_level=0) + self._stlclient.connect() + self._logger.info("TREX: Trex 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("TREX: In trex disconnect method") + self._stlclient.disconnect(stop_traffic=True, release_ports=True) + + @staticmethod + def create_packets(traffic, ports_info): + """Create base packet according to traffic specification. + If traffic haven't specified srcmac and dstmac fields + packet will be create with mac address of trex server. + """ + mac_add = [li['hw_mac'] for li in ports_info] + + if traffic and traffic['l2']['framesize'] > 0: + if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ + traffic['l2']['srcmac'] == '00:00:00:00:00:00': + base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \ + IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'], + dst=traffic['l3']['dstip'])/ \ + UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) + base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \ + IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], + dst=traffic['l3']['srcip'])/ \ + UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) + else: + base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \ + IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], + dst=traffic['l3']['srcip'])/ \ + UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) + + base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \ + IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], + dst=traffic['l3']['srcip'])/ \ + UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) + + return (base_pkt_a, base_pkt_b) + + @staticmethod + def create_streams(base_pkt_a, base_pkt_b, traffic): + """Add the base packet to the streams. Erase FCS and add payload + according to traffic specification + """ + stream_1_lat = None + stream_2_lat = None + frame_size = int(traffic['l2']['framesize']) + fsize_no_fcs = frame_size - 4 + payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x' + payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x' + pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a) + pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b) + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXCont(percentage=traffic['frame_rate'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXCont(percentage=traffic['frame_rate'])) + lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') + if lat_pps > 0: + stream_1_lat = STLStream(packet=pkt_a, + flow_stats=STLFlowLatencyStats(pg_id=0), + name='stream_1_lat', + mode=STLTXCont(pps=lat_pps)) + stream_2_lat = STLStream(packet=pkt_b, + flow_stats=STLFlowLatencyStats(pg_id=1), + name='stream_2_lat', + mode=STLTXCont(pps=lat_pps)) + + return (stream_1, stream_2, stream_1_lat, stream_2_lat) + + def generate_traffic(self, traffic, duration): + """The method that generate a stream + """ + my_ports = [0, 1] + self._stlclient.reset(my_ports) + ports_info = self._stlclient.get_port_info(my_ports) + packet_1, packet_2 = Trex.create_packets(traffic, ports_info) + stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic) + self._stlclient.add_streams(stream_1, ports=[0]) + self._stlclient.add_streams(stream_2, ports=[1]) + + if stream_1_lat is not None: + self._stlclient.add_streams(stream_1_lat, ports=[0]) + self._stlclient.add_streams(stream_2_lat, ports=[1]) + + self._stlclient.clear_stats() + self._stlclient.start(ports=[0, 1], force=True, duration=duration) + self._stlclient.wait_on_traffic(ports=[0, 1]) + stats = self._stlclient.get_stats(sync_now=True) + return stats + + @staticmethod + def calculate_results(stats): + """Calculate results from Trex statistic + """ + result = OrderedDict() + result[ResultsConstants.TX_RATE_FPS] = ( + '{:.3f}'.format( + float(stats["total"]["tx_pps"]))) + + result[ResultsConstants.THROUGHPUT_RX_FPS] = ( + '{:.3f}'.format( + float(stats["total"]["rx_pps"]))) + + result[ResultsConstants.TX_RATE_MBPS] = ( + '{:.3f}'.format( + float(stats["total"]["tx_bps"] / 1000000))) + result[ResultsConstants.THROUGHPUT_RX_MBPS] = ( + '{:.3f}'.format( + float(stats["total"]["rx_bps"] / 1000000))) + + result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown' + + result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown' + + result[ResultsConstants.FRAME_LOSS_PERCENT] = ( + '{:.3f}'.format( + float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 / + stats["total"]["opackets"]))) + + if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0: + result[ResultsConstants.MIN_LATENCY_NS] = ( + '{:.3f}'.format( + (float(min(stats["latency"][0]["latency"]["total_min"], + stats["latency"][1]["latency"]["total_min"]))))) + + result[ResultsConstants.MAX_LATENCY_NS] = ( + '{:.3f}'.format( + (float(max(stats["latency"][0]["latency"]["total_max"], + stats["latency"][1]["latency"]["total_max"]))))) + + result[ResultsConstants.AVG_LATENCY_NS] = ( + '{:.3f}'.format( + float((stats["latency"][0]["latency"]["average"]+ + stats["latency"][1]["latency"]["average"])/2))) + + else: + result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' + result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown' + result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown' + return result + + def send_cont_traffic(self, traffic=None, duration=30): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex 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) + + stats = self.generate_traffic(traffic, duration) + + return self.calculate_results(stats) + + def start_cont_traffic(self, traffic=None, duration=30): + raise NotImplementedError( + 'Trex start cont traffic not implemented') + + def stop_cont_traffic(self): + """See ITrafficGenerator for description + """ + raise NotImplementedError( + 'Trex stop_cont_traffic method not implemented') + + def send_rfc2544_throughput(self, traffic=None, duration=60, + lossrate=0.0, tests=10): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_rfc2544_throughput method") + self._params.clear() + test_lossrate = 0 + left = 0 + num_test = 1 + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + new_params = copy.deepcopy(traffic) + stats = self.generate_traffic(traffic, duration) + right = traffic['frame_rate'] + center = traffic['frame_rate'] + + while num_test <= tests: + test_lossrate = ((stats["total"]["opackets"] - stats["total"] + ["ipackets"]) * 100) / stats["total"]["opackets"] + self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s", + num_test, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], + "{:.3f}".format(test_lossrate)) + if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']: + break + elif test_lossrate > lossrate: + right = center + center = (left+right) / 2 + new_params = copy.deepcopy(traffic) + new_params['frame_rate'] = center + stats = self.generate_traffic(new_params, duration) + else: + left = center + center = (left+right) / 2 + new_params = copy.deepcopy(traffic) + new_params['frame_rate'] = center + stats = self.generate_traffic(new_params, duration) + num_test += 1 + return self.calculate_results(stats) + + def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60, + lossrate=0.0): + raise NotImplementedError( + 'Trex start rfc2544 throughput not implemented') + + def wait_rfc2544_throughput(self): + raise NotImplementedError( + 'Trex wait rfc2544 throughput not implemented') + + def send_burst_traffic(self, traffic=None, numpkts=100, duration=5): + raise NotImplementedError( + 'Trex send burst traffic not implemented') + + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30, + lossrate=0.0): + raise NotImplementedError( + 'Trex send rfc2544 back2back not implemented') + + def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30, + lossrate=0.0): + raise NotImplementedError( + 'Trex start rfc2544 back2back not implemented') + + def wait_rfc2544_back2back(self): + raise NotImplementedError( + 'Trex wait rfc2544 back2back not implemented') + +if __name__ == "__main__": + pass diff --git a/tools/systeminfo.py b/tools/systeminfo.py index 75b7aa0d..f34bcce6 100644 --- a/tools/systeminfo.py +++ b/tools/systeminfo.py @@ -230,6 +230,7 @@ def get_version(app_name): 'loopback_l2fwd' : os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/l2fwd.c'), 'ixnet' : os.path.join(S.getValue('TRAFFICGEN_IXNET_LIB_PATH'), 'pkgIndex.tcl'), 'ixia' : os.path.join(S.getValue('TRAFFICGEN_IXIA_ROOT_DIR'), 'lib/ixTcl1.0/ixTclHal.tcl'), + 'trex' : os.path.join(S.getValue('ROOT_DIR'), 'src/trex/trex'), } @@ -312,6 +313,9 @@ def get_version(app_name): app_version = match_line(app_version_file['ixia'], 'package provide IxTclHal') if app_version: app_version = app_version.split(' ')[3] + elif app_name.lower() == 'trex': + app_version = match_line(os.path.join(app_version_file['trex'], 'VERSION'), 'v') + app_git_tag = get_git_tag(app_version_file['trex']) elif app_name.lower() == 'xena': try: app_version = S.getValue('XENA_VERSION') @@ -707,16 +707,17 @@ def main(): opnfv_url = settings.getValue('OPNFV_URL') pkg_list = settings.getValue('PACKAGE_LIST') - int_data = {'vanilla': False, - 'pod': pod_name, - 'criteria': "PASS", + int_data = {'pod': pod_name, 'build_tag': get_build_tag(), 'installer': installer_name, 'pkg_list': pkg_list, - 'db_url': opnfv_url} - if str(settings.getValue('VSWITCH')).endswith('Vanilla'): - int_data['vanilla'] = True - opnfvdashboard.results2opnfv_dashboard(results_path, int_data) + 'db_url': opnfv_url, + # pass vswitch name from configuration to be used for failed + # TCs; In case of successful TCs it is safer to use vswitch + # name from CSV as TC can override global configuration + 'vswitch': str(settings.getValue('VSWITCH')).lower()} + tc_names = [tc['Name'] for tc in selected_tests] + opnfvdashboard.results2opnfv_dashboard(tc_names, results_path, int_data) # cleanup before exit vsperf_finalize() |