diff options
-rw-r--r-- | conf/02_vswitch.conf | 4 | ||||
-rw-r--r-- | conf/03_traffic.conf | 12 | ||||
-rw-r--r-- | conf/04_vnf.conf | 15 | ||||
-rw-r--r-- | conf/10_custom.conf | 14 | ||||
-rw-r--r-- | core/traffic_controller_rfc2544.py | 5 | ||||
-rw-r--r-- | docs/configguide/trafficgen.rst | 45 | ||||
-rwxr-xr-x | docs/userguide/testusage.rst | 58 | ||||
-rw-r--r-- | testcases/integration.py | 7 | ||||
-rw-r--r-- | testcases/testcase.py | 20 | ||||
-rw-r--r-- | tools/namespace.py | 178 | ||||
-rw-r--r-- | tools/pkt_gen/moongen/__init__.py | 13 | ||||
-rw-r--r-- | tools/pkt_gen/moongen/moongen.py | 753 | ||||
-rw-r--r-- | tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py | 11 | ||||
-rw-r--r-- | tools/pkt_gen/testcenter/testcenter.py | 120 | ||||
-rw-r--r-- | tools/pkt_gen/xena/profiles/baseconfig.x2544 | 2 | ||||
-rwxr-xr-x | tools/pkt_gen/xena/xena.py | 8 | ||||
-rw-r--r-- | tools/veth.py | 118 | ||||
-rw-r--r-- | vnfs/qemu/qemu.py | 19 | ||||
-rw-r--r-- | vnfs/qemu/qemu_dpdk_vhost_user.py | 19 | ||||
-rw-r--r-- | vswitches/ovs_dpdk_vhost.py | 10 |
20 files changed, 1411 insertions, 20 deletions
diff --git a/conf/02_vswitch.conf b/conf/02_vswitch.conf index 7f9daf1c..79f0afbd 100644 --- a/conf/02_vswitch.conf +++ b/conf/02_vswitch.conf @@ -84,6 +84,10 @@ VSWITCHD_DPDK_CONFIG = { # Note: VSPERF will automatically detect, which type of DPDK configuration should # be used. +# To enable multi queue modify the below param to the number of queues. +# 0 = disabled +VSWITCH_MULTI_QUEUES = 0 + # parameters passed to ovs-vswitchd in case that OvsVanilla is selected VSWITCHD_VANILLA_ARGS = [] 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/04_vnf.conf b/conf/04_vnf.conf index 926ea50a..0a80c1af 100644 --- a/conf/04_vnf.conf +++ b/conf/04_vnf.conf @@ -98,10 +98,25 @@ GUEST_SMP = ['2', '2'] # For 2 VNFs you may use [(4,5), (6, 7)] GUEST_CORE_BINDING = [(6, 7), (9, 10)] +# Queues per NIC inside guest for multi-queue configuration, requires switch +# multi-queue to be enabled. Set to 0 for disabled. +GUEST_NIC_QUEUES = 0 + GUEST_START_TIMEOUT = 120 GUEST_OVS_DPDK_DIR = '/root/ovs_dpdk' OVS_DPDK_SHARE = '/mnt/ovs_dpdk_share' +# Set the CPU mask for testpmd loopback. To bind to specific guest CPUs use -l +# GUEST_TESTPMD_CPU_MASK = '-l 0,1' +GUEST_TESTPMD_CPU_MASK = '-c 0x3' + +# Testpmd multi-core config. Leave at 0's for disabled. Will not enable unless +# GUEST_NIC_QUEUES are > 0. For bi directional traffic NB_CORES must be equal +# to (RXQ + TXQ). +GUEST_TESTPMD_NB_CORES = 0 +GUEST_TESTPMD_TXQ = 0 +GUEST_TESTPMD_RXQ = 0 + # IP addresses to use for Vanilla OVS PVP testing # Consider using RFC 2544/3330 recommended IP addresses for benchmark testing. # Network: 198.18.0.0/15 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/userguide/testusage.rst b/docs/userguide/testusage.rst index 104723e3..d807590d 100755 --- a/docs/userguide/testusage.rst +++ b/docs/userguide/testusage.rst @@ -437,6 +437,64 @@ Guest loopback application must be configured, otherwise traffic will not be forwarded by VM and testcases with PVP and PVVP deployments will fail. Guest loopback application is set to 'testpmd' by default. +Multi-Queue Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^ + +VSPerf currently supports multi-queue with the following limitations: + + 1. Execution of pvp/pvvp tests require testpmd as the loopback if multi-queue + is enabled at the guest. + + 2. Requires QemuDpdkVhostUser as the vnf. + + 3. Requires switch to be set to OvsDpdkVhost. + + 4. Requires QEMU 2.5 or greater and any OVS version higher than 2.5. The + default upstream package versions installed by VSPerf satisfy this + requirement. + +To enable multi-queue modify the ''02_vswitch.conf'' file to enable multi-queue +on the switch. + + .. code-block:: console + + VSWITCH_MULTI_QUEUES = 2 + +**NOTE:** you should consider using the switch affinity to set a pmd cpu mask +that can optimize your performance. Consider the numa of the NIC in use if this +applies by checking /sys/class/net/<eth_name>/device/numa_node and setting an +appropriate mask to create PMD threads on the same numa node. + +When multi-queue is enabled, each dpdk or dpdkvhostuser port that is created +on the switch will set the option for multiple queues. + +To enable multi-queue on the guest modify the ''04_vnf.conf'' file. + + .. code-block:: console + + GUEST_NIC_QUEUES = 2 + +Enabling multi-queue at the guest will add multiple queues to each NIC port when +qemu launches the guest. + +Testpmd should be configured to take advantage of multi-queue on the guest. This +can be done by modifying the ''04_vnf.conf'' file. + + .. code-block:: console + + GUEST_TESTPMD_CPU_MASK = '-l 0,1,2,3,4' + + GUEST_TESTPMD_NB_CORES = 4 + GUEST_TESTPMD_TXQ = 2 + GUEST_TESTPMD_RXQ = 2 + +**NOTE:** The guest SMP cores must be configured to allow for testpmd to use the +optimal number of cores to take advantage of the multiple guest queues. + +**NOTE:** For optimal performance guest SMPs should be on the same numa as the +NIC in use if possible/applicable. Testpmd should be assigned at least +(nb_cores +1) total cores with the cpu mask. + Executing Packet Forwarding tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testcases/integration.py b/testcases/integration.py index fdd8b09c..ffde5822 100644 --- a/testcases/integration.py +++ b/testcases/integration.py @@ -22,10 +22,13 @@ import copy from testcases import TestCase from conf import settings as S from collections import OrderedDict +from tools import namespace +from tools import veth from core.loader import Loader CHECK_PREFIX = 'validate_' + class IntegrationTestCase(TestCase): """IntegrationTestCase class """ @@ -115,6 +118,10 @@ class IntegrationTestCase(TestCase): step_ok = False if step[0] == 'vswitch': test_object = self._vswitch_ctl.get_vswitch() + elif step[0] == 'namespace': + test_object = namespace + elif step[0] == 'veth': + test_object = veth elif step[0] == 'trafficgen': test_object = self._traffic_ctl # in case of send_traffic method, ensure that specified diff --git a/testcases/testcase.py b/testcases/testcase.py index e5f8a14c..5f5c9358 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -234,6 +234,26 @@ class TestCase(object): # restore original settings S.load_from_dict(self._settings_original) + # cleanup any namespaces created + if os.path.isdir('/tmp/namespaces'): + import tools.namespace + namespace_list = os.listdir('/tmp/namespaces') + if len(namespace_list): + self._logger.info('Cleaning up namespaces') + for name in namespace_list: + tools.namespace.delete_namespace(name) + os.rmdir('/tmp/namespaces') + # cleanup any veth ports created + if os.path.isdir('/tmp/veth'): + import tools.veth + veth_list = os.listdir('/tmp/veth') + if len(veth_list): + self._logger.info('Cleaning up veth ports') + for eth in veth_list: + port1, port2 = eth.split('-') + tools.veth.del_veth_port(port1, port2) + os.rmdir('/tmp/veth') + def run_report(self): """ Report test results """ diff --git a/tools/namespace.py b/tools/namespace.py new file mode 100644 index 00000000..e6bcd819 --- /dev/null +++ b/tools/namespace.py @@ -0,0 +1,178 @@ +# Copyright 2016 Red Hat 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. + + +""" +Network namespace emulation +""" + +import logging +import os + +from tools import tasks + +_LOGGER = logging.getLogger(__name__) + + +def add_ip_to_namespace_eth(port, name, ip_addr, cidr): + """ + Assign port ip address in namespace + :param port: port to assign ip to + :param name: namespace where port resides + :param ip_addr: ip address in dot notation format + :param cidr: cidr as string + :return: + """ + ip_string = '{}/{}'.format(ip_addr, cidr) + tasks.run_task(['sudo', 'ip', 'netns', 'exec', name, + 'ip', 'addr', 'add', ip_string, 'dev', port], + _LOGGER, 'Assigning ip to port {}...'.format(port), False) + + +def assign_port_to_namespace(port, name, port_up=False): + """ + Assign NIC port to namespace + :param port: port name as string + :param name: namespace name as string + :param port_up: Boolean if the port should be brought up on assignment + :return: None + """ + tasks.run_task(['sudo', 'ip', 'link', 'set', + 'netns', name, 'dev', port], + _LOGGER, 'Assigning port {} to namespace {}...'.format( + port, name), False) + if port_up: + tasks.run_task(['sudo', 'ip', 'netns', 'exec', name, + 'ip', 'link', 'set', port, 'up'], + _LOGGER, 'Bringing up port {}...'.format(port), False) + + +def create_namespace(name): + """ + Create a linux namespace. Raises RuntimeError if namespace already exists + in the system. + :param name: name of the namespace to be created as string + :return: None + """ + if name in get_system_namespace_list(): + raise RuntimeError('Namespace already exists in system') + + # touch some files in a tmp area so we can track them separately from + # the OS's internal namespace tracking. This allows us to track VSPerf + # created namespaces so they can be cleaned up if needed. + if not os.path.isdir('/tmp/namespaces'): + try: + os.mkdir('/tmp/namespaces') + except os.error: + # OK don't crash, but cleanup may be an issue... + _LOGGER.error('Unable to create namespace temp folder.') + _LOGGER.error( + 'Namespaces will not be removed on test case completion') + if os.path.isdir('/tmp/namespaces'): + with open('/tmp/namespaces/{}'.format(name), 'a'): + os.utime('/tmp/namespaces/{}'.format(name), None) + + tasks.run_task(['sudo', 'ip', 'netns', 'add', name], _LOGGER, + 'Creating namespace {}...'.format(name), False) + tasks.run_task(['sudo', 'ip', 'netns', 'exec', name, + 'ip', 'link', 'set', 'lo', 'up'], _LOGGER, + 'Enabling loopback interface...', False) + + +def delete_namespace(name): + """ + Delete linux network namespace + :param name: namespace to delete + :return: None + """ + # delete the file if it exists in the temp area + if os.path.exists('/tmp/namespaces/{}'.format(name)): + os.remove('/tmp/namespaces/{}'.format(name)) + tasks.run_task(['sudo', 'ip', 'netns', 'delete', name], _LOGGER, + 'Deleting namespace {}...'.format(name), False) + + +def get_system_namespace_list(): + """ + Return tuple of strings for namespaces on the system + :return: tuple of namespaces as string + """ + return tuple(os.listdir('/var/run/netns')) + + +def get_vsperf_namespace_list(): + """ + Return a tuple of strings for namespaces created by vsperf testcase + :return: tuple of namespaces as string + """ + if os.path.isdir('/tmp/namespaces'): + return tuple(os.listdir('/tmp/namespaces')) + else: + return [] + + +def reset_port_to_root(port, name): + """ + Return the assigned port to the root namespace + :param port: port to return as string + :param name: namespace the port currently resides + :return: None + """ + tasks.run_task(['sudo', 'ip', 'netns', 'exec', name, + 'ip', 'link', 'set', port, 'netns', '1'], + _LOGGER, 'Assigning port {} to namespace {}...'.format( + port, name), False) + + +# pylint: disable=unused-argument +# pylint: disable=invalid-name +def validate_add_ip_to_namespace_eth(result, port, name, ip_addr, cidr): + """ + Validation function for integration testcases + """ + ip_string = '{}/{}'.format(ip_addr, cidr) + return ip_string in ''.join(tasks.run_task( + ['sudo', 'ip', 'netns', 'exec', name, 'ip', 'addr', 'show', port], + _LOGGER, 'Validating ip address in namespace...', False)) + + +def validate_assign_port_to_namespace(result, port, name, port_up=False): + """ + Validation function for integration testcases + """ + # this could be improved...its not 100% accurate + return port in ''.join(tasks.run_task( + ['sudo', 'ip', 'netns', 'exec', name, 'ip', 'addr'], + _LOGGER, 'Validating port in namespace...')) + + +def validate_create_namespace(result, name): + """ + Validation function for integration testcases + """ + return name in get_system_namespace_list() + + +def validate_delete_namespace(result, name): + """ + Validation function for integration testcases + """ + return name not in get_system_namespace_list() + + +def validate_reset_port_to_root(result, port, name): + """ + Validation function for integration testcases + """ + return not validate_assign_port_to_namespace(result, port, name) 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/testcenter/testcenter-rfc2544-rest.py b/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py index 428240a1..91f7e27f 100644 --- a/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py +++ b/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py @@ -169,6 +169,11 @@ def main(): default="PAIR", help="The traffic pattern between endpoints", dest="traffic_pattern") + optional_named.add_argument("--traffic_custom", + required=False, + default=None, + help="The traffic pattern between endpoints", + dest="traffic_custom") optional_named.add_argument("--search_mode", required=False, choices=["COMBO", "STEP", "BINARY"], @@ -318,6 +323,12 @@ def main(): logger.debug("Creating project ...") project = stc.get("System1", "children-Project") + # Configure any custom traffic parameters + if args.traffic_custom == "cont": + if args.verbose: + logger.debug("Configure Continuous Traffic") + stc.create("ContinuousTestConfig", under=project) + # Create ports if args.verbose: logger.debug("Creating ports ...") diff --git a/tools/pkt_gen/testcenter/testcenter.py b/tools/pkt_gen/testcenter/testcenter.py index 2adf6c90..f05ce337 100644 --- a/tools/pkt_gen/testcenter/testcenter.py +++ b/tools/pkt_gen/testcenter/testcenter.py @@ -55,9 +55,125 @@ class TestCenter(trafficgen.ITrafficGenerator): def send_cont_traffic(self, traffic=None, duration=30): """ - Do nothing. + Send Custom - Continuous Test traffic + Reuse RFC2544 throughput test specifications along with + 'custom' configuration """ - return None + verbose = False + custom = "cont" + framesize = settings.getValue("TRAFFICGEN_STC_FRAME_SIZE") + if traffic and 'l2' in traffic: + if 'framesize' in traffic['l2']: + framesize = traffic['l2']['framesize'] + args = [settings.getValue("TRAFFICGEN_STC_PYTHON2_PATH"), + os.path.join( + settings.getValue("TRAFFICGEN_STC_TESTCENTER_PATH"), + settings.getValue( + "TRAFFICGEN_STC_RFC2544_TPUT_TEST_FILE_NAME")), + "--lab_server_addr", + settings.getValue("TRAFFICGEN_STC_LAB_SERVER_ADDR"), + "--license_server_addr", + settings.getValue("TRAFFICGEN_STC_LICENSE_SERVER_ADDR"), + "--east_chassis_addr", + settings.getValue("TRAFFICGEN_STC_EAST_CHASSIS_ADDR"), + "--east_slot_num", + settings.getValue("TRAFFICGEN_STC_EAST_SLOT_NUM"), + "--east_port_num", + settings.getValue("TRAFFICGEN_STC_EAST_PORT_NUM"), + "--west_chassis_addr", + settings.getValue("TRAFFICGEN_STC_WEST_CHASSIS_ADDR"), + "--west_slot_num", + settings.getValue("TRAFFICGEN_STC_WEST_SLOT_NUM"), + "--west_port_num", + settings.getValue("TRAFFICGEN_STC_WEST_PORT_NUM"), + "--test_session_name", + settings.getValue("TRAFFICGEN_STC_TEST_SESSION_NAME"), + "--results_dir", + settings.getValue("TRAFFICGEN_STC_RESULTS_DIR"), + "--csv_results_file_prefix", + settings.getValue("TRAFFICGEN_STC_CSV_RESULTS_FILE_PREFIX"), + "--num_trials", + settings.getValue("TRAFFICGEN_STC_NUMBER_OF_TRIALS"), + "--trial_duration_sec", + settings.getValue("TRAFFICGEN_STC_TRIAL_DURATION_SEC"), + "--traffic_pattern", + settings.getValue("TRAFFICGEN_STC_TRAFFIC_PATTERN"), + "--traffic_custom", + str(custom), + "--search_mode", + settings.getValue("TRAFFICGEN_STC_SEARCH_MODE"), + "--learning_mode", + settings.getValue("TRAFFICGEN_STC_LEARNING_MODE"), + "--rate_lower_limit_pct", + settings.getValue("TRAFFICGEN_STC_RATE_LOWER_LIMIT_PCT"), + "--rate_upper_limit_pct", + settings.getValue("TRAFFICGEN_STC_RATE_UPPER_LIMIT_PCT"), + "--rate_initial_pct", + settings.getValue("TRAFFICGEN_STC_RATE_INITIAL_PCT"), + "--rate_step_pct", + settings.getValue("TRAFFICGEN_STC_RATE_STEP_PCT"), + "--resolution_pct", + settings.getValue("TRAFFICGEN_STC_RESOLUTION_PCT"), + "--frame_size_list", + str(framesize), + "--acceptable_frame_loss_pct", + settings.getValue("TRAFFICGEN_STC_ACCEPTABLE_FRAME_LOSS_PCT"), + "--east_intf_addr", + settings.getValue("TRAFFICGEN_STC_EAST_INTF_ADDR"), + "--east_intf_gateway_addr", + settings.getValue("TRAFFICGEN_STC_EAST_INTF_GATEWAY_ADDR"), + "--west_intf_addr", + settings.getValue("TRAFFICGEN_STC_WEST_INTF_ADDR"), + "--west_intf_gateway_addr", + settings.getValue("TRAFFICGEN_STC_WEST_INTF_GATEWAY_ADDR")] + + if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + args.append("--verbose") + verbose = True + self._logger.debug("Arguments used to call test: %s", args) + subprocess.check_call(args) + + filec = os.path.join(settings.getValue("TRAFFICGEN_STC_RESULTS_DIR"), + settings.getValue( + "TRAFFICGEN_STC_CSV_RESULTS_FILE_PREFIX") + + ".csv") + + if verbose: + self._logger.info("file: %s", filec) + + result = {} + + with open(filec, "r") as csvfile: + csvreader = csv.DictReader(csvfile) + for row in csvreader: + self._logger.info("Row: %s", row) + tx_fps = ((float(row["TxFrameCount"])) / + (float(row["Duration(sec)"]))) + rx_fps = ((float(row["RxFrameCount"])) / + (float(row["Duration(sec)"]))) + tx_mbps = ((float(row["TxFrameCount"]) * + float(row["ConfiguredFrameSize"])) / + (float(row["Duration(sec)"]) * 1000000.0)) + rx_mbps = ((float(row["RxFrameCount"]) * + float(row["ConfiguredFrameSize"])) / + (float(row["Duration(sec)"]) * 1000000.0)) + result[ResultsConstants.TX_RATE_FPS] = tx_fps + result[ResultsConstants.THROUGHPUT_RX_FPS] = rx_fps + result[ResultsConstants.TX_RATE_MBPS] = tx_mbps + result[ResultsConstants.THROUGHPUT_RX_MBPS] = rx_mbps + result[ResultsConstants.TX_RATE_PERCENT] = float( + row["OfferedLoad(%)"]) + result[ResultsConstants.THROUGHPUT_RX_PERCENT] = float( + row["Throughput(%)"]) + result[ResultsConstants.MIN_LATENCY_NS] = float( + row["MinimumLatency(us)"]) * 1000 + result[ResultsConstants.MAX_LATENCY_NS] = float( + row["MaximumLatency(us)"]) * 1000 + result[ResultsConstants.AVG_LATENCY_NS] = float( + row["AverageLatency(us)"]) * 1000 + result[ResultsConstants.FRAME_LOSS_PERCENT] = float( + row["PercentLoss"]) + return result def send_rfc2544_throughput(self, traffic=None, trials=3, duration=20, lossrate=0.0): 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 e76ebcbf..7dd4b90b 100755 --- a/tools/pkt_gen/xena/xena.py +++ b/tools/pkt_gen/xena/xena.py @@ -92,11 +92,11 @@ class Xena(ITrafficGenerator): if test_type == 'Throughput': results = OrderedDict() - results[ResultsConstants.THROUGHPUT_RX_FPS] = int( - root[0][1][0][0].get('PortRxPps')) + int( + 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] = (int( - root[0][1][0][0].get('PortRxBpsL1')) + int( + 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( diff --git a/tools/veth.py b/tools/veth.py new file mode 100644 index 00000000..6418d11a --- /dev/null +++ b/tools/veth.py @@ -0,0 +1,118 @@ +# Copyright 2016 Red Hat 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. + +""" +veth port emulation +""" + +import logging +import os + +from tools import tasks + +_LOGGER = logging.getLogger(__name__) + + +def add_veth_port(port, peer_port): + """ + Add a veth port + :param port:port name for the first port + :param peer_port: port name for the peer port + :return: None + """ + # touch some files in a tmp area so we can track them. This allows us to + # track VSPerf created veth ports so they can be cleaned up if needed. + if not os.path.isdir('/tmp/veth'): + try: + os.mkdir('/tmp/veth') + except os.error: + # OK don't crash but cleanup may be an issue + _LOGGER.error('Unable to create veth temp folder.') + _LOGGER.error( + 'Veth ports may not be removed on testcase completion') + if os.path.isdir('/tmp/veth'): + with open('/tmp/veth/{}-{}'.format(port, peer_port), 'a'): + os.utime('/tmp/veth/{}-{}'.format(port, peer_port), None) + tasks.run_task(['sudo', 'ip', 'link', 'add', + port, 'type', 'veth', 'peer', 'name', peer_port], + _LOGGER, 'Adding veth port {} with peer port {}...'.format( + port, peer_port), False) + + +def bring_up_eth_port(eth_port, namespace=None): + """ + Bring up an eth port + :param eth_port: string of eth port to bring up + :param namespace: Namespace eth port it located if needed + :return: None + """ + if namespace: + tasks.run_task(['sudo', 'ip', 'netns', 'exec', namespace, + 'ip', 'link', 'set', eth_port, 'up'], + _LOGGER, + 'Bringing up port {} in namespace {}...'.format( + eth_port, namespace), False) + else: + tasks.run_task(['sudo', 'ip', 'link', 'set', eth_port, 'up'], + _LOGGER, 'Bringing up port...', False) + + +def del_veth_port(port, peer_port): + """ + Delete the veth ports, the peer will automatically be deleted on deletion + of the first port param + :param port: port name to delete + :param port: peer port name + :return: None + """ + # delete the file if it exists in the temp area + if os.path.exists('/tmp/veth/{}-{}'.format(port, peer_port)): + os.remove('/tmp/veth/{}-{}'.format(port, peer_port)) + tasks.run_task(['sudo', 'ip', 'link', 'del', port], + _LOGGER, 'Deleting veth port {} with peer {}...'.format( + port, peer_port), False) + + +# pylint: disable=unused-argument +def validate_add_veth_port(result, port, peer_port): + """ + Validation function for integration testcases + """ + devs = os.listdir('/sys/class/net') + return all([port in devs, peer_port in devs]) + + +def validate_bring_up_eth_port(result, eth_port, namespace=None): + """ + Validation function for integration testcases + """ + command = list() + if namespace: + command += ['ip', 'netns', 'exec', namespace] + command += ['cat', '/sys/class/net/{}/operstate'.format(eth_port)] + out = tasks.run_task(command, _LOGGER, 'Validating port up...', False) + + # since different types of ports may report different status the best way + # we can do this for now is to just make sure it doesn't say down + if 'down' in out: + return False + return True + + +def validate_del_veth_port(result, port, peer_port): + """ + Validation function for integration testcases + """ + devs = os.listdir('/sys/class/net') + return not any([port in devs, peer_port in devs]) diff --git a/vnfs/qemu/qemu.py b/vnfs/qemu/qemu.py index 2de8df2a..9382edef 100644 --- a/vnfs/qemu/qemu.py +++ b/vnfs/qemu/qemu.py @@ -354,9 +354,22 @@ class IVnfQemu(IVnf): '/DPDK/app/test-pmd') self.execute_and_wait('make clean') self.execute_and_wait('make') - self.execute_and_wait('./testpmd -c 0x3 -n 4 --socket-mem 512 --' - ' --burst=64 -i --txqflags=0xf00 ' + - '--disable-hw-vlan', 60, "Done") + if int(S.getValue('GUEST_NIC_QUEUES')): + self.execute_and_wait( + './testpmd {} -n4 --socket-mem 512 --'.format( + S.getValue('GUEST_TESTPMD_CPU_MASK')) + + ' --burst=64 -i --txqflags=0xf00 ' + + '--nb-cores={} --rxq={} --txq={} '.format( + S.getValue('GUEST_TESTPMD_NB_CORES'), + S.getValue('GUEST_TESTPMD_TXQ'), + S.getValue('GUEST_TESTPMD_RXQ')) + + '--disable-hw-vlan', 60, "Done") + else: + self.execute_and_wait( + './testpmd {} -n 4 --socket-mem 512 --'.format( + S.getValue('GUEST_TESTPMD_CPU_MASK')) + + ' --burst=64 -i --txqflags=0xf00 ' + + '--disable-hw-vlan', 60, "Done") self.execute('set fwd ' + self._testpmd_fwd_mode, 1) self.execute_and_wait('start', 20, 'TX RS bit threshold=.+ - TXQ flags=0xf00') diff --git a/vnfs/qemu/qemu_dpdk_vhost_user.py b/vnfs/qemu/qemu_dpdk_vhost_user.py index f0f97d8a..49131423 100644 --- a/vnfs/qemu/qemu_dpdk_vhost_user.py +++ b/vnfs/qemu/qemu_dpdk_vhost_user.py @@ -38,6 +38,14 @@ class QemuDpdkVhostUser(IVnfQemu): net1 = 'net' + str(i + 1) net2 = 'net' + str(i + 2) + # multi-queue values + if int(S.getValue('GUEST_NIC_QUEUES')): + queue_str = ',queues={}'.format(S.getValue('GUEST_NIC_QUEUES')) + mq_vector_str = ',mq=on,vectors={}'.format( + int(S.getValue('GUEST_NIC_QUEUES')) * 2 + 2) + else: + queue_str, mq_vector_str = '', '' + self._cmd += ['-chardev', 'socket,id=char' + if1 + ',path=' + S.getValue('OVS_VAR_DIR') + @@ -48,19 +56,20 @@ class QemuDpdkVhostUser(IVnfQemu): 'dpdkvhostuser' + if2, '-netdev', 'type=vhost-user,id=' + net1 + - ',chardev=char' + if1 + ',vhostforce', + ',chardev=char' + if1 + ',vhostforce' + queue_str, '-device', 'virtio-net-pci,mac=' + S.getValue('GUEST_NET1_MAC')[self._number] + ',netdev=' + net1 + ',csum=off,gso=off,' + - 'guest_tso4=off,guest_tso6=off,guest_ecn=off', + 'guest_tso4=off,guest_tso6=off,guest_ecn=off' + + mq_vector_str, '-netdev', 'type=vhost-user,id=' + net2 + - ',chardev=char' + if2 + ',vhostforce', + ',chardev=char' + if2 + ',vhostforce' + queue_str, '-device', 'virtio-net-pci,mac=' + S.getValue('GUEST_NET2_MAC')[self._number] + ',netdev=' + net2 + ',csum=off,gso=off,' + - 'guest_tso4=off,guest_tso6=off,guest_ecn=off', + 'guest_tso4=off,guest_tso6=off,guest_ecn=off' + + mq_vector_str, ] - diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py index 82b952de..2d424bc5 100644 --- a/vswitches/ovs_dpdk_vhost.py +++ b/vswitches/ovs_dpdk_vhost.py @@ -109,8 +109,11 @@ class OvsDpdkVhost(IVSwitchOvs): dpdk_count = self._get_port_count('type=dpdk') port_name = 'dpdk' + str(dpdk_count) params = ['--', 'set', 'Interface', port_name, 'type=dpdk'] + # multi-queue enable + if int(settings.getValue('VSWITCH_MULTI_QUEUES')): + params += ['options:n_rxq={}'.format( + settings.getValue('VSWITCH_MULTI_QUEUES'))] of_port = bridge.add_port(port_name, params) - return (port_name, of_port) def add_vport(self, switch_name): @@ -130,7 +133,10 @@ class OvsDpdkVhost(IVSwitchOvs): vhost_count = self._get_port_count('type=dpdkvhostuser') port_name = 'dpdkvhostuser' + str(vhost_count) params = ['--', 'set', 'Interface', port_name, 'type=dpdkvhostuser'] - + # multi queue enable + if int(settings.getValue('VSWITCH_MULTI_QUEUES')): + params += ['options:n_rxq={}'.format( + settings.getValue('VSWITCH_MULTI_QUEUES'))] of_port = bridge.add_port(port_name, params) return (port_name, of_port) |