GSO: TAP/VHOST use case 10/29210/72
authorpmikus <pmikus@cisco.com>
Fri, 2 Oct 2020 05:48:34 +0000 (05:48 +0000)
committerPeter Mikus <pmikus@cisco.com>
Mon, 4 Jan 2021 10:30:50 +0000 (10:30 +0000)
Signed-off-by: pmikus <pmikus@cisco.com>
Change-Id: Ibafaaca4438587284d1e255c764f0701002941e8

21 files changed:
resources/libraries/python/CpuUtils.py
resources/libraries/python/Iperf3.py [new file with mode: 0644]
resources/libraries/python/QemuManager.py
resources/libraries/python/QemuUtils.py
resources/libraries/python/Tap.py
resources/libraries/python/VhostUser.py
resources/libraries/python/autogen/Regenerator.py
resources/libraries/python/autogen/Testcase.py
resources/libraries/robot/performance/performance_actions.robot
resources/libraries/robot/performance/performance_utils.robot
resources/libraries/robot/shared/default.robot
resources/libraries/robot/shared/interfaces.robot
resources/libraries/robot/shared/test_teardown.robot
resources/libraries/robot/shared/vm.robot
resources/templates/vm/init_iperf3.sh [new file with mode: 0644]
resources/tools/iperf/iperf_client.py [new file with mode: 0644]
tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-gso-iperf3.robot [new file with mode: 0644]
tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-iperf3.robot [new file with mode: 0644]
tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-gso-iperf3.robot [new file with mode: 0644]
tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-iperf3.robot [new file with mode: 0644]
tests/vpp/perf/gso/regenerate_testcases.py [new file with mode: 0755]

index e4fff01..f261f94 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -392,3 +392,57 @@ class CpuUtils:
             smt_used=False)
 
         return master_thread_id[0], latency_thread_id[0], cpu_node, threads
             smt_used=False)
 
         return master_thread_id[0], latency_thread_id[0], cpu_node, threads
+
+    @staticmethod
+    def get_affinity_iperf(
+            node, pf_key, cpu_skip_cnt=0, cpu_cnt=1):
+        """Get affinity for iPerf3. Result will be used to pin iPerf3 threads.
+
+        :param node: Topology node.
+        :param pf_key: Topology interface.
+        :param cpu_skip_cnt: Amount of CPU cores to skip.
+        :param cpu_cnt: CPU threads count.
+        :type node: dict
+        :type pf_key: str
+        :type cpu_skip_cnt: int
+        :type cpu_cnt: int
+        :returns: List of CPUs allocated to iPerf3.
+        :rtype: str
+        """
+        if pf_key:
+            cpu_node = Topology.get_interface_numa_node(node, pf_key)
+        else:
+            cpu_node = 0
+
+        return CpuUtils.cpu_range_per_node_str(
+            node, cpu_node, skip_cnt=cpu_skip_cnt, cpu_cnt=cpu_cnt,
+            smt_used=False)
+
+    @staticmethod
+    def get_affinity_vhost(
+            node, pf_key, skip_cnt=0, cpu_cnt=1):
+        """Get affinity for vhost. Result will be used to pin vhost threads.
+
+        :param node: Topology node.
+        :param pf_key: Topology interface.
+        :param skip_cnt: Amount of CPU cores to skip.
+        :param cpu_cnt: CPU threads count.
+        :type node: dict
+        :type pf_key: str
+        :type skip_cnt: int
+        :type cpu_cnt: int
+        :returns: List of CPUs allocated to vhost process.
+        :rtype: str
+        """
+        if pf_key:
+            cpu_node = Topology.get_interface_numa_node(node, pf_key)
+        else:
+            cpu_node = 0
+
+        smt_used = CpuUtils.is_smt_enabled(node[u"cpuinfo"])
+        if smt_used:
+            cpu_cnt = cpu_cnt // CpuUtils.NR_OF_THREADS
+
+        return CpuUtils.cpu_slice_of_list_per_node(
+            node, cpu_node=cpu_node, skip_cnt=skip_cnt, cpu_cnt=cpu_cnt,
+            smt_used=smt_used)
diff --git a/resources/libraries/python/Iperf3.py b/resources/libraries/python/Iperf3.py
new file mode 100644 (file)
index 0000000..ed186f0
--- /dev/null
@@ -0,0 +1,347 @@
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+"""iPerf3 utilities library."""
+
+import json
+
+from resources.libraries.python.Constants import Constants
+from resources.libraries.python.CpuUtils import CpuUtils
+from resources.libraries.python.IPUtil import IPUtil
+from resources.libraries.python.Namespaces import Namespaces
+from resources.libraries.python.OptionString import OptionString
+from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
+
+
+class Iperf3:
+    """iPerf3 traffic generator utilities."""
+
+    def __init__(self):
+        """Initialize iPerf3 class."""
+        # Computed affinity for iPerf server.
+        self._s_affinity = None
+        # Computed affinity for iPerf client.
+        self._c_affinity = None
+
+    def initialize_iperf_server(
+            self, node, pf_key, interface, bind, bind_gw, bind_mask,
+            namespace=None, cpu_skip_cnt=0, cpu_cnt=1, instances=1):
+        """iPerf3 initialization.
+
+        :param node: Topology node running iPerf3 server.
+        :param pf_key: First TG's interface (To compute numa location).
+        :param interface: Name of TG bind interface.
+        :param bind: Bind to host, one of node's addresses.
+        :param bind_gw: Bind gateway (required for default route).
+        :param bind_mask: Bind address mask.
+        :param namespace: Name of TG namespace to execute.
+        :param cpu_skip_cnt: Amount of CPU cores to skip.
+        :param cpu_cnt: iPerf3 main thread count.
+        :param instances: Number of simultaneous iPerf3 instances.
+        :type node: dict
+        :type pf_key: str
+        :type interface: str
+        :type bind: str
+        :type bind_gw: str
+        :type bind_mask: str
+        :type namespace: str
+        :type cpu_skip_cnt: int
+        :type cpu_cnt: int
+        :type instances: int
+        """
+        if Iperf3.is_iperf_running(node):
+            Iperf3.teardown_iperf(node)
+
+        if namespace:
+            IPUtil.set_linux_interface_ip(
+                node, interface=interface, ip_addr=bind, prefix=bind_mask,
+                namespace=namespace)
+            IPUtil.set_linux_interface_up(
+                node, interface=interface, namespace=namespace)
+            Namespaces.add_default_route_to_namespace(
+                node, namespace=namespace, default_route=bind_gw)
+
+        # Compute affinity for iPerf server.
+        self._s_affinity = CpuUtils.get_affinity_iperf(
+            node, pf_key, cpu_skip_cnt=cpu_skip_cnt,
+            cpu_cnt=cpu_cnt * instances)
+        # Compute affinity for iPerf client.
+        self._c_affinity = CpuUtils.get_affinity_iperf(
+            node, pf_key, cpu_skip_cnt=cpu_skip_cnt + cpu_cnt * instances,
+            cpu_cnt=cpu_cnt * instances)
+
+        for i in range(0, instances):
+            Iperf3.start_iperf_server(
+                node, namespace=namespace, port=5201 + i,
+                affinity=self._s_affinity)
+
+    @staticmethod
+    def start_iperf_server(
+            node, namespace=None, port=5201, affinity=None):
+        """Start iPerf3 server instance as a deamon.
+
+        :param node: Topology node running iPerf3 server.
+        :param namespace: Name of TG namespace to execute.
+        :param port: The server port for the server to listen on.
+        :param affinity: iPerf3 server affinity.
+        :type node: dict
+        :type namespace: str
+        :type port: int
+        :type affinity: str
+        """
+        cmd = IPerf3Server.iperf3_cmdline(
+            namespace=namespace, port=port, affinity=affinity)
+        exec_cmd_no_error(
+            node, cmd, sudo=True, message=u"Failed to start iPerf3 server!")
+
+    @staticmethod
+    def is_iperf_running(node):
+        """Check if iPerf3 is running using pgrep.
+
+        :param node: Topology node running iPerf3.
+        :type node: dict
+        :returns: True if iPerf3 is running otherwise False.
+        :rtype: bool
+        """
+        ret, _, _ = exec_cmd(node, u"pgrep iperf3", sudo=True)
+        return bool(int(ret) == 0)
+
+    @staticmethod
+    def teardown_iperf(node):
+        """iPerf3 teardown.
+
+        :param node: Topology node running iPerf3.
+        :type node: dict
+        """
+        pidfile = u"/tmp/iperf3_server.pid"
+        logfile = u"/tmp/iperf3.log"
+
+        exec_cmd_no_error(
+            node,
+            f"sh -c 'if [ -f {pidfile} ]; then "
+            f"pkill iperf3; "
+            f"cat {logfile}; "
+            f"rm {logfile}; "
+            f"fi'",
+            sudo=True, message=u"iPerf3 kill failed!")
+
+    def iperf_client_start_remote_exec(
+            self, node, duration, rate, frame_size, async_call=False,
+            warmup_time=0, traffic_directions=1, namespace=None, udp=False,
+            host=None, bind=None, affinity=None):
+        """Execute iPerf3 client script on remote node over ssh to start running
+        traffic.
+
+        :param node: Topology node running iPerf3.
+        :param duration: Time expressed in seconds for how long to send traffic.
+        :param rate: Traffic rate.
+        :param frame_size: L2 frame size to send (without padding and IPG).
+        :param async_call: If enabled then don't wait for all incoming traffic.
+        :param warmup_time: Warmup time period.
+        :param traffic_directions: Traffic is bi- (2) or uni- (1) directional.
+            Default: 1
+        :param namespace: Namespace to execute iPerf3 client on.
+        :param udp: UDP traffic.
+        :param host: Client connecting to an iPerf server running on host.
+        :param bind: Client bind IP address.
+        :param affinity: iPerf3 client affinity.
+        :type node: dict
+        :type duration: float
+        :type rate: str
+        :type frame_size: str
+        :type async_call: bool
+        :type warmup_time: float
+        :type traffic_directions: int
+        :type namespace: str
+        :type udp: bool
+        :type host: str
+        :type bind: str
+        :type affinity: str
+        :returns: List of iPerf3 PIDs.
+        :rtype: list
+        """
+        if not isinstance(duration, (float, int)):
+            duration = float(duration)
+        if not isinstance(warmup_time, (float, int)):
+            warmup_time = float(warmup_time)
+        if not affinity:
+            affinity = self._c_affinity
+
+        kwargs = dict()
+        if namespace:
+            kwargs[u"namespace"] = namespace
+        kwargs[u"host"] = host
+        kwargs[u"bind"] = bind
+        kwargs[u"udp"] = udp
+        if affinity:
+            kwargs[u"affinity"] = affinity
+        kwargs[u"duration"] = duration
+        kwargs[u"rate"] = rate
+        kwargs[u"frame_size"] = frame_size
+        kwargs[u"warmup_time"] = warmup_time
+        kwargs[u"traffic_directions"] = traffic_directions
+        kwargs[u"async_call"] = async_call
+
+        cmd = IPerf3Client.iperf3_cmdline(**kwargs)
+
+        stdout, _ = exec_cmd_no_error(
+            node, cmd, timeout=int(duration) + 30,
+            message=u"iPerf3 runtime error!")
+
+        if async_call:
+            return stdout.split()
+        return json.loads(stdout)
+
+    @staticmethod
+    def iperf_client_stop_remote_exec(node, pids):
+        """Stop iPerf3 client execution.
+
+        :param pids: PID or List of PIDs of iPerf3 client.
+        :type pids: str or list
+        """
+        if not isinstance(pids, list):
+            pids = [pids]
+
+        for pid in pids:
+            exec_cmd_no_error(
+                node, f"kill {pid}", sudo=True, message=u"Kill iPerf3 failed!")
+
+
+class IPerf3Server:
+    """iPerf3 server utilities."""
+
+    @staticmethod
+    def iperf3_cmdline(**kwargs):
+        """Get iPerf3 server command line.
+
+        :param kwargs: List of iPerf3 server parameters.
+        :type kwargs: dict
+        :returns: iPerf3 server command line.
+        :rtype: OptionString
+        """
+        cmd = OptionString()
+        if kwargs['namespace']:
+            cmd.add(f"ip netns exec {kwargs['namespace']}")
+        cmd.add(f"iperf3")
+
+        cmd_options = OptionString(prefix=u"--")
+        # Run iPerf in server mode. (This will only allow one iperf connection
+        # at a time)
+        cmd_options.add(
+            u"server")
+
+        # Run the server in background as a daemon.
+        cmd_options.add_if_from_dict(
+            u"daemon", u"daemon", kwargs, True)
+
+        # Write a file with the process ID, most useful when running as a
+        # daemon.
+        cmd_options.add_with_value_from_dict(
+            u"pidfile", u"pidfile", kwargs, f"/tmp/iperf3_server.pid")
+
+        # Send output to a log file.
+        cmd_options.add_with_value_from_dict(
+            u"logfile", u"logfile", kwargs, f"/tmp/iperf3.log")
+
+        # The server port for the server to listen on and the client to
+        # connect to. This should be the same in both client and server.
+        # Default is 5201.
+        cmd_options.add_with_value_from_dict(
+            u"port", u"port", kwargs, 5201)
+
+        # Set the CPU affinity, if possible (Linux and FreeBSD only).
+        cmd_options.add_with_value_from_dict(
+            u"affinity", u"affinity", kwargs)
+
+        # Output in JSON format.
+        cmd_options.add_if_from_dict(
+            u"json", u"json", kwargs, True)
+
+        # Give more detailed output.
+        cmd_options.add_if_from_dict(
+            u"verbose", u"verbose", kwargs, True)
+
+        return cmd.extend(cmd_options)
+
+
+class IPerf3Client:
+    """iPerf3 client utilities."""
+
+    @staticmethod
+    def iperf3_cmdline(**kwargs):
+        """Get iperf_client driver command line.
+
+        :param kwargs: List of iperf_client driver parameters.
+        :type kwargs: dict
+        :returns: iperf_client driver command line.
+        :rtype: OptionString
+        """
+        cmd = OptionString()
+        cmd.add(u"python3")
+        dirname = f"{Constants.REMOTE_FW_DIR}/resources/tools/iperf"
+        cmd.add(f"'{dirname}/iperf_client.py'")
+
+        cmd_options = OptionString(prefix=u"--")
+        # Namespace to execute iPerf3 client on.
+        cmd_options.add_with_value_from_dict(
+            u"namespace", u"namespace", kwargs)
+
+        # Client connecting to an iPerf3 server running on host.
+        cmd_options.add_with_value_from_dict(
+            u"host", u"host", kwargs)
+
+        # Client bind IP address.
+        cmd_options.add_with_value_from_dict(
+            u"bind", u"bind", kwargs)
+
+        # Use UDP rather than TCP.
+        cmd_options.add_if_from_dict(
+            u"udp", u"udp", kwargs, False)
+
+        # Set the CPU affinity, if possible.
+        cmd_options.add_with_value_from_dict(
+            u"affinity", u"affinity", kwargs)
+
+        # Time expressed in seconds for how long to send traffic.
+        cmd_options.add_with_value_from_dict(
+            u"duration", u"duration", kwargs)
+
+        # Send bi- (2) or uni- (1) directional traffic.
+        cmd_options.add_with_value_from_dict(
+            u"traffic_directions", u"traffic_directions", kwargs, 1)
+
+        # Traffic warm-up time in seconds, (0=disable).
+        cmd_options.add_with_value_from_dict(
+            u"warmup_time", u"warmup_time", kwargs, 5.0)
+
+        # L2 frame size to send (without padding and IPG).
+        cmd_options.add_with_value_from_dict(
+            u"frame_size", u"frame_size", kwargs)
+
+        # Traffic rate expressed with units.
+        cmd_options.add_with_value_from_dict(
+            u"rate", u"rate", kwargs)
+
+        # If enabled then don't wait for all incoming traffic.
+        cmd_options.add_if_from_dict(
+            u"async_start", u"async_call", kwargs, False)
+
+        # Number of iPerf3 client parallel instances.
+        cmd_options.add_with_value_from_dict(
+            u"instances", u"instances", kwargs, 1)
+
+        # Number of iPerf3 client parallel flows.
+        cmd_options.add_with_value_from_dict(
+            u"parallel", u"parallel", kwargs, 8)
+
+        return cmd.extend(cmd_options)
index 6436f69..766372a 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -51,8 +51,9 @@ class QemuManager:
         nf_nodes = int(kwargs[u"nf_nodes"])
         queues = kwargs[u"rxq_count_int"] if kwargs[u"auto_scale"] else 1
         vs_dtc = kwargs[u"vs_dtc"]
         nf_nodes = int(kwargs[u"nf_nodes"])
         queues = kwargs[u"rxq_count_int"] if kwargs[u"auto_scale"] else 1
         vs_dtc = kwargs[u"vs_dtc"]
-        nf_dtc = kwargs[u"vs_dtc"] if kwargs[u"auto_scale"] \
-            else kwargs[u"nf_dtc"]
+        nf_dtc = kwargs[u"nf_dtc"]
+        if kwargs[u"auto_scale"] and not kwargs[u"fixed_auto_scale"]:
+            nf_dtc = kwargs[u"vs_dtc"]
         nf_dtcr = kwargs[u"nf_dtcr"] \
             if isinstance(kwargs[u"nf_dtcr"], int) else 2
 
         nf_dtcr = kwargs[u"nf_dtcr"] \
             if isinstance(kwargs[u"nf_dtcr"], int) else 2
 
@@ -419,3 +420,38 @@ class QemuManager:
             pci=Topology.get_interface_pci_addr(
                 self.nodes[kwargs[u"node"]], kwargs[u"if1"])
         )
             pci=Topology.get_interface_pci_addr(
                 self.nodes[kwargs[u"node"]], kwargs[u"if1"])
         )
+
+    def _c_iperf3(self, **kwargs):
+        """Instantiate one VM with iperf3 configuration.
+
+        :param kwargs: Named parameters.
+        :type kwargs: dict
+        """
+        qemu_id = kwargs[u"qemu_id"]
+        name = kwargs[u"name"]
+        virtio_feature_mask = kwargs[u"virtio_feature_mask"] \
+            if u"virtio_feature_mask" in kwargs else None
+
+        self.machines[name] = QemuUtils(
+            node=self.nodes[kwargs[u"node"]],
+            qemu_id=qemu_id,
+            smp=len(self.machines_affinity[name]),
+            mem=4096,
+            vnf=kwargs[u"vnf"],
+            img=Constants.QEMU_VM_KERNEL
+        )
+        self.machines[name].add_default_params()
+        self.machines[name].add_kernelvm_params()
+        self.machines[name].configure_kernelvm_vnf(
+            queues=kwargs[u"queues"],
+            jumbo_frames=kwargs[u"jumbo"]
+        )
+        self.machines[name].add_net_user()
+        self.machines[name].add_vhost_user_if(
+            f"/run/vpp/sock-{qemu_id}-1",
+            server=False,
+            jumbo_frames=kwargs[u"jumbo"],
+            queues=kwargs[u"queues"],
+            queue_size=kwargs[u"perf_qemu_qsz"],
+            virtio_feature_mask=virtio_feature_mask
+        )
index c215dfd..51fba61 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -80,8 +80,8 @@ class QemuUtils:
             u"type": NodeType.VM,
             u"port": 10021 + qemu_id,
             u"serial": 4555 + qemu_id,
             u"type": NodeType.VM,
             u"port": 10021 + qemu_id,
             u"serial": 4555 + qemu_id,
-            u"username": 'cisco',
-            u"password": 'cisco',
+            u"username": 'testuser',
+            u"password": 'Csit1234',
             u"interfaces": {},
         }
         if node[u"port"] != 22:
             u"interfaces": {},
         }
         if node[u"port"] != 22:
@@ -142,10 +142,10 @@ class QemuUtils:
         self._params.add_with_value(u"numa", u"node,memdev=mem")
         self._params.add_with_value(u"balloon", u"none")
 
         self._params.add_with_value(u"numa", u"node,memdev=mem")
         self._params.add_with_value(u"balloon", u"none")
 
-    def add_net_user(self):
+    def add_net_user(self, net="10.0.2.0/24"):
         """Set managment port forwarding."""
         self._params.add_with_value(
         """Set managment port forwarding."""
         self._params.add_with_value(
-            u"netdev", f"user,id=mgmt,net=172.16.255.0/24,"
+            u"netdev", f"user,id=mgmt,net={net},"
             f"hostfwd=tcp::{self._vm_info[u'port']}-:22"
         )
         self._params.add_with_value(
             f"hostfwd=tcp::{self._vm_info[u'port']}-:22"
         )
         self._params.add_with_value(
@@ -158,7 +158,9 @@ class QemuUtils:
             u"chardev", f"socket,path={self._temp.get(u'qga')},"
             f"server,nowait,id=qga0"
         )
             u"chardev", f"socket,path={self._temp.get(u'qga')},"
             f"server,nowait,id=qga0"
         )
-        self._params.add_with_value(u"device", u"isa-serial,chardev=qga0")
+        self._params.add_with_value(
+            u"device", u"isa-serial,chardev=qga0"
+        )
         self._params.add_with_value(
             u"qmp", f"unix:{self._temp.get(u'qmp')},server,nowait"
         )
         self._params.add_with_value(
             u"qmp", f"unix:{self._temp.get(u'qmp')},server,nowait"
         )
@@ -167,8 +169,11 @@ class QemuUtils:
         """Set serial to file redirect."""
         self._params.add_with_value(
             u"chardev", f"socket,host=127.0.0.1,"
         """Set serial to file redirect."""
         self._params.add_with_value(
             u"chardev", f"socket,host=127.0.0.1,"
-            f"port={self._vm_info[u'serial']},id=gnc0,server,nowait")
-        self._params.add_with_value(u"device", u"isa-serial,chardev=gnc0")
+            f"port={self._vm_info[u'serial']},id=gnc0,server,nowait"
+        )
+        self._params.add_with_value(
+            u"device", u"isa-serial,chardev=gnc0"
+        )
         self._params.add_with_value(
             u"serial", f"file:{self._temp.get(u'log')}"
         )
         self._params.add_with_value(
             u"serial", f"file:{self._temp.get(u'log')}"
         )
@@ -210,8 +215,12 @@ class QemuUtils:
         self._params.add_with_value(
             u"device", u"virtio-9p-pci,fsdev=root9p,mount_tag=virtioroot"
         )
         self._params.add_with_value(
             u"device", u"virtio-9p-pci,fsdev=root9p,mount_tag=virtioroot"
         )
-        self._params.add_with_value(u"kernel", f"{self._opt.get(u'img')}")
-        self._params.add_with_value(u"initrd", f"{self._opt.get(u'initrd')}")
+        self._params.add_with_value(
+            u"kernel", f"{self._opt.get(u'img')}"
+        )
+        self._params.add_with_value(
+            u"initrd", f"{self._opt.get(u'initrd')}"
+        )
         self._params.add_with_value(
             u"append", f"'ro rootfstype=9p rootflags=trans=virtio "
             f"root=virtioroot console={self._opt.get(u'console')} "
         self._params.add_with_value(
             u"append", f"'ro rootfstype=9p rootflags=trans=virtio "
             f"root=virtioroot console={self._opt.get(u'console')} "
@@ -250,19 +259,19 @@ class QemuUtils:
             f"{self._nic_id:02x}"
         queue_size = f"rx_queue_size={queue_size},tx_queue_size={queue_size}" \
             if queue_size else u""
             f"{self._nic_id:02x}"
         queue_size = f"rx_queue_size={queue_size},tx_queue_size={queue_size}" \
             if queue_size else u""
-        if virtio_feature_mask is None:
-            gso = False
-            csum = False
-        else:
-            gso = VirtioFeatureMask.is_feature_enabled(
-                virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_GSO)
-            csum = VirtioFeatureMask.is_feature_enabled(
-                virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_CSUM)
+        gso = VirtioFeatureMask.is_feature_enabled(
+            virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_GSO)
+        csum = VirtioFeatureMask.is_feature_enabled(
+            virtio_feature_mask, VirtioFeaturesFlags.VIRTIO_NET_F_API_CSUM)
+
         self._params.add_with_value(
             u"device", f"virtio-net-pci,netdev=vhost{self._nic_id},mac={mac},"
             f"addr={self._nic_id+5}.0,mq=on,vectors={2 * queues + 2},"
         self._params.add_with_value(
             u"device", f"virtio-net-pci,netdev=vhost{self._nic_id},mac={mac},"
             f"addr={self._nic_id+5}.0,mq=on,vectors={2 * queues + 2},"
-            f"csum={u'on' if csum else u'off'},gso={u'on' if gso else u'off'},"
-            f"guest_tso4=off,guest_tso6=off,guest_ecn=off,"
+            f"csum={u'on' if csum else u'off'},"
+            f"gso={u'on' if gso else u'off'},"
+            f"guest_tso4={u'on' if gso else u'off'},"
+            f"guest_tso6={u'on' if gso else u'off'},"
+            f"guest_ecn={u'on' if gso else u'off'},"
             f"{queue_size}"
         )
 
             f"{queue_size}"
         )
 
@@ -402,17 +411,20 @@ class QemuUtils:
 
         self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}"
 
 
         self._opt[u"vnf_bin"] = f"{self._testpmd_path}/{testpmd_cmd}"
 
+    def create_kernelvm_config_iperf3(self):
+        """Create QEMU iperf3 command line."""
+        self._opt[u"vnf_bin"] = f"mkdir /run/sshd; /usr/sbin/sshd -D -d"
+
     def create_kernelvm_init(self, **kwargs):
         """Create QEMU init script.
 
         :param kwargs: Key-value pairs to replace content of init startup file.
         :type kwargs: dict
         """
     def create_kernelvm_init(self, **kwargs):
         """Create QEMU init script.
 
         :param kwargs: Key-value pairs to replace content of init startup file.
         :type kwargs: dict
         """
-        template = f"{Constants.RESOURCES_TPL}/vm/init.sh"
         init = self._temp.get(u"ini")
         exec_cmd_no_error(self._node, f"rm -f {init}", sudo=True)
 
         init = self._temp.get(u"ini")
         exec_cmd_no_error(self._node, f"rm -f {init}", sudo=True)
 
-        with open(template, u"rt") as src_file:
+        with open(kwargs[u"template"], u"rt") as src_file:
             src = Template(src_file.read())
             exec_cmd_no_error(
                 self._node, f"echo '{src.safe_substitute(**kwargs)}' | "
             src = Template(src_file.read())
             exec_cmd_no_error(
                 self._node, f"echo '{src.safe_substitute(**kwargs)}' | "
@@ -428,13 +440,32 @@ class QemuUtils:
         """
         if u"vpp" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_vpp(**kwargs)
         """
         if u"vpp" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_vpp(**kwargs)
-            self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin"))
+            self.create_kernelvm_init(
+                template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
+                vnf_bin=self._opt.get(u"vnf_bin")
+            )
         elif u"testpmd_io" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_testpmd_io(**kwargs)
         elif u"testpmd_io" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_testpmd_io(**kwargs)
-            self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin"))
+            self.create_kernelvm_init(
+                template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
+                vnf_bin=self._opt.get(u"vnf_bin")
+            )
         elif u"testpmd_mac" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_testpmd_mac(**kwargs)
         elif u"testpmd_mac" in self._opt.get(u"vnf"):
             self.create_kernelvm_config_testpmd_mac(**kwargs)
-            self.create_kernelvm_init(vnf_bin=self._opt.get(u"vnf_bin"))
+            self.create_kernelvm_init(
+                template=f"{Constants.RESOURCES_TPL}/vm/init.sh",
+                vnf_bin=self._opt.get(u"vnf_bin")
+            )
+        elif u"iperf3" in self._opt.get(u"vnf"):
+            qemu_id = self._opt.get(u'qemu_id') % 2
+            self.create_kernelvm_config_iperf3()
+            self.create_kernelvm_init(
+                template=f"{Constants.RESOURCES_TPL}/vm/init_iperf3.sh",
+                vnf_bin=self._opt.get(u"vnf_bin"),
+                ip_address_l=u"2.2.2.2/30" if qemu_id else u"1.1.1.1/30",
+                ip_address_r=u"2.2.2.1" if qemu_id else u"1.1.1.2",
+                ip_route_r=u"1.1.1.0/30" if qemu_id else u"2.2.2.0/30"
+            )
         else:
             raise RuntimeError(u"QEMU: Unsupported VNF!")
 
         else:
             raise RuntimeError(u"QEMU: Unsupported VNF!")
 
@@ -639,6 +670,20 @@ class QemuUtils:
                 f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
             )
 
                 f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
             )
 
+    def _wait_iperf3(self, retries=60):
+        """Wait until QEMU with iPerf3 is booted.
+
+        :param retries: Number of retries.
+        :type retries: int
+        """
+        grep = u"Server listening on 0.0.0.0 port 22."
+        cmd = f"fgrep '{grep}' {self._temp.get(u'log')}"
+        message = f"QEMU: Timeout, VM not booted on {self._node[u'host']}!"
+        exec_cmd_no_error(
+            self._node, cmd=cmd, sudo=True, message=message, retries=retries,
+            include_reason=True
+        )
+
     def _update_vm_interfaces(self):
         """Update interface names in VM node dict."""
         # Send guest-network-get-interfaces command via QGA, output example:
     def _update_vm_interfaces(self):
         """Update interface names in VM node dict."""
         # Send guest-network-get-interfaces command via QGA, output example:
index 041a774..c729d60 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -13,6 +13,8 @@
 
 """Tap utilities library."""
 
 
 """Tap utilities library."""
 
+from enum import IntEnum
+
 from robot.api import logger
 
 from resources.libraries.python.Constants import Constants
 from robot.api import logger
 
 from resources.libraries.python.Constants import Constants
@@ -22,21 +24,43 @@ from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 from resources.libraries.python.topology import Topology
 
 
 from resources.libraries.python.topology import Topology
 
 
+class TapFeaturesFlags(IntEnum):
+    """TAP Features Flags."""
+    TAP_API_FLAG_GSO = 1
+    TAP_API_FLAG_CSUM_OFFLOAD = 2
+    TAP_API_FLAG_PERSIST = 4
+    TAP_API_FLAG_ATTACH = 8
+    TAP_API_FLAG_TUN = 16
+    TAP_API_FLAG_GRO_COALESCE = 32
+    TAP_API_FLAG_PACKED = 64
+    TAP_API_FLAG_IN_ORDER = 128
+
+
 class Tap:
     """Tap utilities."""
 
     @staticmethod
 class Tap:
     """Tap utilities."""
 
     @staticmethod
-    def add_tap_interface(node, tap_name, mac=None, num_rx_queues=1):
+    def add_tap_interface(
+            node, tap_name, mac=None, host_namespace=None, num_rx_queues=1,
+            rxq_size=0, txq_size=0, tap_feature_mask=0):
         """Add tap interface with name and optionally with MAC.
 
         :param node: Node to add tap on.
         :param tap_name: Tap interface name for linux tap.
         :param mac: Optional MAC address for VPP tap.
         """Add tap interface with name and optionally with MAC.
 
         :param node: Node to add tap on.
         :param tap_name: Tap interface name for linux tap.
         :param mac: Optional MAC address for VPP tap.
+        :param host_namespace: Namespace.
         :param num_rx_queues: Number of RX queues.
         :param num_rx_queues: Number of RX queues.
+        :param rxq_size: Size of RXQ (0 = Default API; 256 = Default VPP).
+        :param txq_size: Size of TXQ (0 = Default API; 256 = Default VPP).
+        :param tap_feature_mask: Mask of tap features to be enabled.
         :type node: dict
         :type tap_name: str
         :type mac: str
         :type node: dict
         :type tap_name: str
         :type mac: str
+        :type host_namespace: str
         :type num_rx_queues: int
         :type num_rx_queues: int
+        :type rxq_size: int
+        :type txq_size: int
+        :type tap_feature_mask: int
         :returns: Returns a interface index.
         :rtype: int
         """
         :returns: Returns a interface index.
         :rtype: int
         """
@@ -46,16 +70,20 @@ class Tap:
             use_random_mac=bool(mac is None),
             mac_address=L2Util.mac_to_bin(mac) if mac else None,
             num_rx_queues=int(num_rx_queues),
             use_random_mac=bool(mac is None),
             mac_address=L2Util.mac_to_bin(mac) if mac else None,
             num_rx_queues=int(num_rx_queues),
+            tx_ring_sz=int(txq_size),
+            rx_ring_sz=int(rxq_size),
             host_mtu_set=False,
             host_mac_addr_set=False,
             host_ip4_prefix_set=False,
             host_ip6_prefix_set=False,
             host_ip4_gw_set=False,
             host_ip6_gw_set=False,
             host_mtu_set=False,
             host_mac_addr_set=False,
             host_ip4_prefix_set=False,
             host_ip6_prefix_set=False,
             host_ip4_gw_set=False,
             host_ip6_gw_set=False,
-            host_namespace_set=False,
+            host_namespace_set=bool(host_namespace),
+            host_namespace=host_namespace,
             host_if_name_set=True,
             host_if_name=tap_name,
             host_if_name_set=True,
             host_if_name=tap_name,
-            host_bridge_set=False
+            host_bridge_set=False,
+            tap_flags=tap_feature_mask
         )
         err_msg = f"Failed to create tap interface {tap_name} " \
             f"on host {node[u'host']}"
         )
         err_msg = f"Failed to create tap interface {tap_name} " \
             f"on host {node[u'host']}"
@@ -152,3 +180,41 @@ class Tap:
 
         logger.debug(f"TAP data:\n{data}")
         return data
 
         logger.debug(f"TAP data:\n{data}")
         return data
+
+
+class TapFeatureMask:
+    """Tap features utilities"""
+
+    @staticmethod
+    def create_tap_feature_mask(**kwargs):
+        """Create tap feature mask with feature bits set according to kwargs.
+        :param kwargs: Key-value pairs of feature names and it's state
+        :type kwargs: dict
+        """
+        tap_feature_mask = 0
+
+        if u"all" in kwargs and kwargs[u"all"] is True:
+            for tap_feature_flag in TapFeaturesFlags:
+                tap_feature_mask |= 1 << (tap_feature_flag.value - 1)
+        else:
+            for feature_name, enabled in kwargs.items():
+                tap_feature_name = u"TAP_API_FLAG_" + feature_name.upper()
+                if tap_feature_name not in TapFeaturesFlags.__members__:
+                    raise ValueError(u"Unsupported tap feature flag name")
+                if enabled:
+                    tap_feature_mask |= \
+                        1 << (TapFeaturesFlags[tap_feature_name].value - 1)
+
+        return tap_feature_mask
+
+    @staticmethod
+    def is_feature_enabled(tap_feature_mask, tap_feature_flag):
+        """Checks if concrete tap feature is enabled within
+         tap_feature_mask
+        :param tap_feature_mask: Mask of enabled tap features
+        :param tap_feature_flag: Checked tap feature
+        :type tap_feature_mask: int
+        :type tap_feature_flag: TapFeaturesFlags
+        """
+        feature_flag_bit = 1 << tap_feature_flag.value
+        return (tap_feature_mask & feature_flag_bit) > 0
index 22528b2..c6b9185 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -17,9 +17,11 @@ from enum import IntEnum
 
 from robot.api import logger
 
 
 from robot.api import logger
 
+from resources.libraries.python.CpuUtils import CpuUtils
 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 from resources.libraries.python.topology import NodeType, Topology
 from resources.libraries.python.InterfaceUtil import InterfaceUtil
 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 from resources.libraries.python.topology import NodeType, Topology
 from resources.libraries.python.InterfaceUtil import InterfaceUtil
+from resources.libraries.python.ssh import exec_cmd_no_error
 
 
 class VirtioFeaturesFlags(IntEnum):
 
 
 class VirtioFeaturesFlags(IntEnum):
@@ -151,6 +153,26 @@ class VhostUser:
         logger.debug(f"Vhost-user details:\n{details}")
         return details
 
         logger.debug(f"Vhost-user details:\n{details}")
         return details
 
+    @staticmethod
+    def vhost_user_affinity(node, pf_key, skip_cnt=0):
+        """Set vhost-user affinity for the given node.
+
+        :param node: Topology node.
+        :param pf_key: Interface key to compute numa location.
+        :param skip_cnt: Skip first "skip_cnt" CPUs.
+        :type node: dict
+        :type pf_key: str
+        :type skip_cnt: int
+        """
+        pids, _ = exec_cmd_no_error(
+            node, f"grep -h vhost /proc/*/comm | uniq | xargs pidof")
+
+        affinity = CpuUtils.get_affinity_vhost(
+            node, pf_key, skip_cnt=skip_cnt, cpu_cnt=len(pids.split(" ")))
+
+        for cpu, pid in zip(affinity, pids.split(" ")):
+            exec_cmd_no_error(node, f"taskset -pc {cpu} {pid}", sudo=True)
+
 
 class VirtioFeatureMask:
     """Virtio features utilities"""
 
 class VirtioFeatureMask:
     """Virtio features utilities"""
index 6d35d1d..dd2672b 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -177,6 +177,20 @@ def add_tcp_testcases(testcase, file_out, tc_kwargs_list):
         file_out.write(testcase.generate(**kwargs))
 
 
         file_out.write(testcase.generate(**kwargs))
 
 
+def add_iperf3_testcases(testcase, file_out, tc_kwargs_list):
+    """Add iperf3 testcases to file.
+
+    :param testcase: Testcase class.
+    :param file_out: File to write testcases to.
+    :param tc_kwargs_list: Key-value pairs used to construct testcases.
+    :type testcase: Testcase
+    :type file_out: file
+    :type tc_kwargs_list: dict
+    """
+    for kwargs in tc_kwargs_list:
+        file_out.write(testcase.generate(**kwargs))
+
+
 def write_default_files(in_filename, in_prolog, kwargs_list):
     """Using given filename and prolog, write all generated suites.
 
 def write_default_files(in_filename, in_prolog, kwargs_list):
     """Using given filename and prolog, write all generated suites.
 
@@ -434,6 +448,34 @@ def write_tcp_files(in_filename, in_prolog, kwargs_list):
             add_tcp_testcases(testcase, file_out, kwargs_list)
 
 
             add_tcp_testcases(testcase, file_out, kwargs_list)
 
 
+def write_iperf3_files(in_filename, in_prolog, kwargs_list):
+    """Using given filename and prolog, write all generated iperf3 suites.
+
+    :param in_filename: Template filename to derive real filenames from.
+    :param in_prolog: Template content to derive real content from.
+    :param kwargs_list: List of kwargs for add_default_testcase.
+    :type in_filename: str
+    :type in_prolog: str
+    :type kwargs_list: list of dict
+    """
+    _, suite_id, suite_tag = get_iface_and_suite_ids(in_filename)
+    testcase = Testcase.iperf3(suite_id)
+    out_filename = replace_defensively(
+        in_filename, u"10ge2p1x710",
+        Constants.NIC_NAME_TO_CODE[u"Intel-X710"], 1,
+        u"File name should contain NIC code once.", in_filename
+    )
+    out_prolog = replace_defensively(
+        in_prolog, u"Intel-X710", u"Intel-X710", 2,
+        u"NIC name should appear twice (tag and variable).",
+        in_filename
+    )
+    check_suite_tag(suite_tag, out_prolog)
+    with open(out_filename, u"wt") as file_out:
+        file_out.write(out_prolog)
+        add_iperf3_testcases(testcase, file_out, kwargs_list)
+
+
 class Regenerator:
     """Class containing file generating methods."""
 
 class Regenerator:
     """Class containing file generating methods."""
 
@@ -485,6 +527,11 @@ class Regenerator:
         hs_quic_kwargs_list = [
             {u"frame_size": 1280, u"phy_cores": 1},
         ]
         hs_quic_kwargs_list = [
             {u"frame_size": 1280, u"phy_cores": 1},
         ]
+        iperf3_kwargs_list = [
+            {u"frame_size": 128000, u"phy_cores": 1},
+            {u"frame_size": 128000, u"phy_cores": 2},
+            {u"frame_size": 128000, u"phy_cores": 4}
+        ]
 
         for in_filename in glob(pattern):
             if not self.quiet:
 
         for in_filename in glob(pattern):
             if not self.quiet:
@@ -514,6 +561,8 @@ class Regenerator:
                     hs_quic_kwargs_list if u"quic" in in_filename \
                     else hs_bps_kwargs_list
                 write_tcp_files(in_filename, in_prolog, hoststack_kwargs_list)
                     hs_quic_kwargs_list if u"quic" in in_filename \
                     else hs_bps_kwargs_list
                 write_tcp_files(in_filename, in_prolog, hoststack_kwargs_list)
+            elif in_filename.endswith(u"-iperf3.robot"):
+                write_iperf3_files(in_filename, in_prolog, iperf3_kwargs_list)
             else:
                 raise RuntimeError(
                     f"Error in {in_filename}: non-primary suite type found."
             else:
                 raise RuntimeError(
                     f"Error in {in_filename}: non-primary suite type found."
index 3ffbc45..173c591 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -109,5 +109,23 @@ class Testcase:
             template_string = f'''
 | ${{frame_str}}-${{cores_str}}c-{suite_id[:-4]}-{suite_id[-3:]}
 | | [Tags] | ${{cores_str}}C\n| | phy_cores=${{cores_num}}
             template_string = f'''
 | ${{frame_str}}-${{cores_str}}c-{suite_id[:-4]}-{suite_id[-3:]}
 | | [Tags] | ${{cores_str}}C\n| | phy_cores=${{cores_num}}
+'''
+        return cls(template_string)
+
+    @classmethod
+    def iperf3(cls, suite_id):
+        """Factory method for creating "iperf3" testcase objects.
+
+        Testcase name will contain core count, but not frame size.
+
+        :param suite_id: Part of suite name to distinguish from other suites.
+        :type suite_id: str
+        :returns: Instance for generating testcase text of this type.
+        :rtype: Testcase
+        """
+        template_string = f'''
+| 128KB-${{cores_str}}c-{suite_id}
+| | [Tags] | 128KB | ${{cores_str}}C
+| | frame_size=${{frame_num}} | phy_cores=${{cores_num}}
 '''
         return cls(template_string)
 '''
         return cls(template_string)
index 2cf954a..c28b2eb 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
 | |
 | | Clear and show runtime counters with running traffic
 
 | |
 | | Clear and show runtime counters with running traffic
 
+| Additional Statistics Action For clear-show-runtime-with-iperf3
+| | [Documentation]
+| | ... | Additional Statistics Action for clear and show runtime counters with
+| | ... | iPerf3 running traffic.
+| |
+| | ... | See documentation of the called keyword for required test variables.
+| |
+| | Clear and show runtime counters with running iperf3
+
 | Additional Statistics Action For noop
 | | [Documentation]
 | | ... | Additional Statistics Action for no operation.
 | Additional Statistics Action For noop
 | | [Documentation]
 | | ... | Additional Statistics Action for no operation.
index 5cf6dba..d45adc6 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -17,6 +17,7 @@
 | Library | resources.libraries.python.NodePath
 | Library | resources.libraries.python.PerfUtil
 | Library | resources.libraries.python.InterfaceUtil
 | Library | resources.libraries.python.NodePath
 | Library | resources.libraries.python.PerfUtil
 | Library | resources.libraries.python.InterfaceUtil
+| Library | resources.libraries.python.Iperf3
 | Library | resources.libraries.python.TrafficGenerator
 | Library | resources.libraries.python.TrafficGenerator.OptimizedSearch
 | Library | resources.libraries.python.TrafficGenerator.TGDropRateSearchImpl
 | Library | resources.libraries.python.TrafficGenerator
 | Library | resources.libraries.python.TrafficGenerator.OptimizedSearch
 | Library | resources.libraries.python.TrafficGenerator.TGDropRateSearchImpl
 | | END
 | | Return From Keyword | ${results}
 
 | | END
 | | Return From Keyword | ${results}
 
+| Clear and show runtime counters with running iperf3
+| | [Documentation]
+| | ... | Start traffic at specified rate then clear runtime counters on all
+| | ... | DUTs. Wait for specified amount of time and capture runtime counters
+| | ... | on all DUTs. Finally stop traffic.
+| |
+| | ... | *Example:*
+| |
+| | ... | \| Clear and show runtime counters with running traffic \|
+| |
+| | ${runtime_duration} = | Get Runtime Duration
+| | ${pids}= | iPerf Client Start Remote Exec
+| | | ... | ${nodes['${iperf_client_node}']}
+| | | ... | duration=${-1}
+| | | ... | rate=${None}
+| | | ... | frame_size=${None}
+| | | ... | async_call=True
+| | | ... | warmup_time=0
+| | | ... | traffic_directions=${1}
+| | | ... | namespace=${iperf_client_namespace}
+| | | ... | udp=${iperf_client_udp}
+| | | ... | host=${iperf_server_bind}
+| | | ... | bind=${iperf_client_bind}
+| | | ... | affinity=${iperf_client_affinity}
+| | FOR | ${action} | IN | @{pre_run_stats}
+| | | Run Keyword | Additional Statistics Action For ${action}
+| | END
+| | Sleep | ${runtime_duration}
+| | FOR | ${action} | IN | @{post_run_stats}
+| | | Run Keyword | Additional Statistics Action For ${action}
+| | END
+| | iPerf Client Stop Remote Exec | ${nodes['${iperf_client_node}']} | ${pids}
+
+| Traffic should pass with maximum rate on iPerf3
+| | [Documentation]
+| | ... | Send traffic at maximum rate on iPerf3.
+| |
+| | ... | *Arguments:*
+| | ... | - trial_duration - Duration of single trial [s].
+| | ... | Type: float
+| | ... | - trial_multiplicity - How many trials in this measurement.
+| | ... | Type: integer
+| | ... | - traffic_directions - Bi- (2) or uni- (1) directional traffic;
+| | ... | Type: integer
+| |
+| | ... | *Example:*
+| |
+| | ... | \| Traffic should pass with maximum rate on iPerf3 \| \${1} \| \
+| | ... | \| \${10.0} \| \${2} \|
+| |
+| | [Arguments] | ${trial_duration}=${trial_duration}
+| | ... | ${trial_multiplicity}=${trial_multiplicity}
+| | ... | ${traffic_directions}=${1}
+| |
+| | ${results}= | Send iPerf3 traffic at specified rate
+| | ... | ${trial_duration} | ${None} | ${None}
+| | ... | ${trial_multiplicity} | ${traffic_directions}
+| | Set Test Message | ${\n}iPerf3 trial results
+| | Set Test Message | in Gbits per second: ${results}
+| | ... | append=yes
+
+| Send iPerf3 traffic at specified rate
+| | [Documentation]
+| | ... | Perform a warmup, show runtime counters during it.
+| | ... | Then send traffic at specified rate, possibly multiple trials.
+| | ... | Show various DUT stats, optionally also packet trace.
+| | ... | Return list of measured receive rates.
+| |
+| | ... | *Arguments:*
+| | ... | - trial_duration - Duration of single trial [s].
+| | ... | Type: float
+| | ... | - rate - Target aggregate transmit rate [bps] / Bits per second
+| | ... | Type: float
+| | ... | - frame_size - L2 Frame Size [B].
+| | ... | Type: integer or string
+| | ... | - trial_multiplicity - How many trials in this measurement.
+| | ... | Type: integer
+| | ... | - traffic_directions - Bi- (2) or uni- (1) directional traffic.
+| | ... | Type: integer
+| | ... | - extended_debug - True to enable extended debug.
+| | ... | Type: boolean
+| |
+| | ... | *Example:*
+| |
+| | ... | \| Send iPerf3 traffic at specified rate \| \${1.0} \| ${4000000.0} \
+| | ... | \| \${64} \| \${10} \| \${1} \| ${False} \|
+| |
+| | [Arguments] | ${trial_duration} | ${rate} | ${frame_size}
+| | ... | ${trial_multiplicity}=${trial_multiplicity}
+| | ... | ${traffic_directions}=${1} | ${extended_debug}=${extended_debug}
+| |
+| | Set Test Variable | ${extended_debug}
+| | Set Test Variable | ${rate}
+| | Set Test Variable | ${traffic_directions}
+| |
+| | ${smt_used}= | Is SMT enabled | ${nodes['${iperf_server_node}']['cpuinfo']}
+| | ${vm_status} | ${value}= | Run Keyword And Ignore Error
+| | ... | Get Library Instance | vnf_manager
+| | ${vth}= | Evaluate | (${thr_count_int} + 1)
+| | ${cpu_skip_cnt}= | Set Variable If | '${vm_status}' == 'PASS'
+| | ... | ${CPU_CNT_SYSTEM}
+| | ... | ${${CPU_CNT_SYSTEM} + ${CPU_CNT_MAIN} + ${cpu_count_int} + ${vth}}
+| |
+| | Initialize iPerf Server
+| | ... | ${nodes['${iperf_server_node}']}
+| | ... | pf_key=${iperf_server_pf_key}
+| | ... | interface=${iperf_server_interface}
+| | ... | bind=${iperf_server_bind}
+| | ... | bind_gw=${iperf_server_bind_gw}
+| | ... | bind_mask=${iperf_server_bind_mask}
+| | ... | namespace=${iperf_server_namespace}
+| | ... | cpu_skip_cnt=${cpu_skip_cnt}
+| | Run Keyword If | '${iperf_client_namespace}' is not '${None}'
+| | ... | Set Linux Interface IP
+| | ... | ${nodes['${iperf_client_node}']}
+| | ... | interface=${iperf_client_interface}
+| | ... | ip_addr=${iperf_client_bind}
+| | ... | prefix=${iperf_client_bind_mask}
+| | ... | namespace=${iperf_client_namespace}
+| | Run Keyword If | '${iperf_client_namespace}' is not '${None}'
+| | ... | Add Default Route To Namespace
+| | ... | ${nodes['${iperf_client_node}']}
+| | ... | namespace=${iperf_client_namespace}
+| | ... | default_route=${iperf_client_bind_gw}
+| | ${pre_stats}= | Create List
+| | ... | clear-show-runtime-with-iperf3
+| | ... | vpp-clear-stats | vpp-enable-packettrace | vpp-enable-elog
+| | FOR | ${action} | IN | @{pre_stats}
+| | | Run Keyword | Additional Statistics Action For ${action}
+| | END
+| | ${results} = | Create List
+| | FOR | ${i} | IN RANGE | ${trial_multiplicity}
+| | | ${rr} = | iPerf Client Start Remote Exec
+| | | ... | ${nodes['${iperf_client_node}']}
+| | | ... | duration=${trial_duration}
+| | | ... | rate=${rate}
+| | | ... | frame_size=${frame_size}
+| | | ... | async_call=False
+| | | ... | warmup_time=0
+| | | ... | traffic_directions=${traffic_directions}
+| | | ... | namespace=${iperf_client_namespace}
+| | | ... | udp=${iperf_client_udp}
+| | | ... | host=${iperf_server_bind}
+| | | ... | bind=${iperf_client_bind}
+| | | ... | affinity=${iperf_client_affinity}
+| | | ${conv} = | Convert To Number | ${rr['sum_received']['bits_per_second']}
+| | | ${conv} = | Evaluate | ${conv} / ${1000} / ${1000} / ${1000}
+| | | ${conv} = | Evaluate | "{:.3f}".format(${conv})
+| | | Append To List
+| | | ... | ${results} | ${conv}
+| | END
+| | FOR | ${action} | IN | @{post_stats}
+| | | Run Keyword | Additional Statistics Action For ${action}
+| | END
+| | Return From Keyword | ${results}
+
 | Start Traffic on Background
 | | [Documentation]
 | | ... | Start traffic at specified rate then return control to Robot.
 | Start Traffic on Background
 | | [Documentation]
 | | ... | Start traffic at specified rate then return control to Robot.
index e44141d..1ae21e1 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -34,6 +34,7 @@
 | Library | resources.libraries.python.PapiHistory
 | Library | resources.libraries.python.SchedUtils
 | Library | resources.libraries.python.Tap
 | Library | resources.libraries.python.PapiHistory
 | Library | resources.libraries.python.SchedUtils
 | Library | resources.libraries.python.Tap
+| Library | resources.libraries.python.Tap.TapFeatureMask
 | Library | resources.libraries.python.TestConfig
 | Library | resources.libraries.python.TGSetup
 | Library | resources.libraries.python.topology.Topology
 | Library | resources.libraries.python.TestConfig
 | Library | resources.libraries.python.TGSetup
 | Library | resources.libraries.python.topology.Topology
index 27908c3..a9b6440 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
 | | ... | *Set UP state on VPP interfaces in path on all DUT nodes and set
 | | ... | maximal MTU.*
 | |
 | | ... | *Set UP state on VPP interfaces in path on all DUT nodes and set
 | | ... | maximal MTU.*
 | |
+| | ... | *Arguments:*
+| | ... | - validate - Validate interfaces are up.
+| | ... | Type: boolean
+| |
+| | [Arguments] | ${validate}=${True}
+| |
 | | FOR | ${dut} | IN | @{duts}
 | | | Set interfaces in path up on node | ${dut}
 | | END
 | | FOR | ${dut} | IN | @{duts}
 | | | Set interfaces in path up on node | ${dut}
 | | END
-| | All VPP Interfaces Ready Wait | ${nodes} | retries=${60}
+| | Run Keyword If | ${validate}
+| | ... | All VPP Interfaces Ready Wait | ${nodes} | retries=${60}
 
 | Set interfaces in path up on node
 | | [Documentation]
 
 | Set interfaces in path up on node
 | | [Documentation]
 | |
 | | Run Keyword | Pre-initialize layer ${driver} on all DUTs
 
 | |
 | | Run Keyword | Pre-initialize layer ${driver} on all DUTs
 
+| Pre-initialize layer tap on all DUTs
+| | [Documentation]
+| | ... | Pre-initialize tap driver. Currently no operation.
+| |
+| | No operation
+
+| Pre-initialize layer vhost on all DUTs
+| | [Documentation]
+| | ... | Pre-initialize vhost driver. Currently no operation.
+| |
+| | No operation
+
 | Pre-initialize layer vfio-pci on all DUTs
 | | [Documentation]
 | | ... | Pre-initialize vfio-pci driver by adding related sections to startup
 | Pre-initialize layer vfio-pci on all DUTs
 | | [Documentation]
 | | ... | Pre-initialize vfio-pci driver by adding related sections to startup
 | | ... | *Arguments:*
 | | ... | - driver - NIC driver used in test [vfio-pci|avf|rdma-core].
 | | ... | Type: string
 | | ... | *Arguments:*
 | | ... | - driver - NIC driver used in test [vfio-pci|avf|rdma-core].
 | | ... | Type: string
+| | ... | - validate - Validate interfaces are up.
+| | ... | Type: boolean
 | |
 | | ... | *Example:*
 | |
 | | ... | \| Initialize layer driver \| vfio-pci \|
 | |
 | |
 | | ... | *Example:*
 | |
 | | ... | \| Initialize layer driver \| vfio-pci \|
 | |
-| | [Arguments] | ${driver}
+| | [Arguments] | ${driver} | ${validate}=${True}
 | |
 | | FOR | ${dut} | IN | @{duts}
 | | | Initialize layer driver on node | ${dut} | ${driver}
 | | END
 | | Set Test Variable | ${int} | vf
 | |
 | | FOR | ${dut} | IN | @{duts}
 | | | Initialize layer driver on node | ${dut} | ${driver}
 | | END
 | | Set Test Variable | ${int} | vf
-| | Set interfaces in path up
+| | Set interfaces in path up | validate=${validate}
 
 | Initialize layer driver on node
 | | [Documentation]
 
 | Initialize layer driver on node
 | | [Documentation]
 | | | Run Keyword | Initialize layer ${driver} on node | ${dut} | ${pf}
 | | END
 
 | | | Run Keyword | Initialize layer ${driver} on node | ${dut} | ${pf}
 | | END
 
+| Initialize layer tap on node
+| | [Documentation]
+| | ... | Initialize tap interfaces on DUT.
+| |
+| | ... | *Arguments:*
+| | ... | - dut - DUT node.
+| | ... | Type: string
+| | ... | - pf - TAP ID (logical port).
+| | ... | Type: integer
+| |
+| | ... | *Example:*
+| |
+| | ... | \| Initialize layer tap on node \| DUT1 \| 0 \|
+| |
+| | [Arguments] | ${dut} | ${pf}
+| |
+| | Create Namespace
+| | ... | ${nodes['${dut}']} | tap${${pf}-1}_namespace
+| | ${tap_feature_mask}= | Create Tap feature mask | gso=${enable_gso}
+| | ${_tap}=
+| | ... | And Add Tap Interface | ${nodes['${dut}']} | tap${${pf}-1}
+| | ... | host_namespace=tap${${pf}-1}_namespace
+| | ... | num_rx_queues=${rxq_count_int}
+| | ... | rxq_size=${nic_rxq_size} | txq_size=${nic_txq_size}
+| | ... | tap_feature_mask=${tap_feature_mask}
+| | ${_mac}=
+| | ... | Get Interface MAC | ${nodes['${dut}']} | tap${pf}
+| | ${_tap}= | Create List | ${_tap}
+| | ${_mac}= | Create List | ${_mac}
+| | Vhost User Affinity
+| | ... | ${nodes['${dut}']} | ${${dut}_pf${pf}}[0]
+| | ... | skip_cnt=${${CPU_CNT_MAIN}+${CPU_CNT_SYSTEM}+${cpu_count_int}}
+| | Set Test Variable
+| | ... | ${${dut}_vf${pf}} | ${_tap}
+| | Set Test Variable
+| | ... | ${${dut}_vf${pf}_mac} | ${_mac}
+
+| Initialize layer vhost on node
+| | [Documentation]
+| | ... | Initialize vhost interfaces on DUT.
+| |
+| | ... | *Arguments:*
+| | ... | - dut - DUT node.
+| | ... | Type: string
+| | ... | - pf - VHOST ID (logical port).
+| | ... | Type: integer
+| |
+| | ... | *Example:*
+| |
+| | ... | \| Initialize layer vhost on node \| DUT1 \| 0 \|
+| |
+| | [Arguments] | ${dut} | ${pf}
+| |
+| | ${virtio_feature_mask}= | Create Virtio feature mask | gso=${enable_gso}
+| | ${vhost}= | Vpp Create Vhost User Interface
+| | ... | ${nodes['${dut}']} | /var/run/vpp/sock-${pf}-1
+| | ... | is_server=${True} | virtio_feature_mask=${virtio_feature_mask}
+| | ${_mac}=
+| | ... | Get Interface MAC | ${nodes['${dut}']} | vhost${pf}
+| | ${_vhost}= | Create List | ${_vhost}
+| | ${_mac}= | Create List | ${_mac}
+| | Set Test Variable
+| | ... | ${${dut}_vf${pf}} | ${_vhost}
+| | Set Test Variable
+| | ... | ${${dut}_vf${pf}_mac} | ${_mac}
+
 | Initialize layer vfio-pci on node
 | | [Documentation]
 | | ... | Initialize vfio-pci interfaces on DUT on NIC PF.
 | Initialize layer vfio-pci on node
 | | [Documentation]
 | | ... | Initialize vfio-pci interfaces on DUT on NIC PF.
index a2f35ed..18be67c 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
 | | | ... | Show Geneve Tunnel Data | ${nodes['${dut}']}
 | | END
 
 | | | ... | Show Geneve Tunnel Data | ${nodes['${dut}']}
 | | END
 
+| Additional Test Tear Down Action For iPerf3
+| | [Documentation]
+| | ... | Additional teardown for test which uses iPerf3 server.
+| |
+| | Run Keyword And Ignore Error
+| | ... | Teardown iPerf | ${nodes['${iperf_server_node}']}
+
 | Additional Test Tear Down Action For ipsec_sa
 | | [Documentation]
 | | ... | Additional teardown for tests which uses IPSec security association.
 | Additional Test Tear Down Action For ipsec_sa
 | | [Documentation]
 | | ... | Additional teardown for tests which uses IPSec security association.
 | |
 | | ${vnf_status} | ${value}= | Run Keyword And Ignore Error
 | | ... | Keyword Should Exist | vnf_manager.Kill All VMs
 | |
 | | ${vnf_status} | ${value}= | Run Keyword And Ignore Error
 | | ... | Keyword Should Exist | vnf_manager.Kill All VMs
-| | Run Keyword If | '${vnf_status}' == 'PASS' | vnf_manager.Kill All VMs
+| | Run Keyword If | '${vnf_status}' == 'PASS' | vnf_manager.Kill All VMs
\ No newline at end of file
index c33ca5f..eb6acb3 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -12,9 +12,9 @@
 # limitations under the License.
 
 *** Settings ***
 # limitations under the License.
 
 *** Settings ***
-| Documentation | Keywords related to vm lifecycle management
-...
 | Library | resources.libraries.python.InterfaceUtil
 | Library | resources.libraries.python.InterfaceUtil
+|
+| Documentation | Keywords related to vm lifecycle management
 
 *** Keywords ***
 | Configure chains of NFs connected via vhost-user
 
 *** Keywords ***
 | Configure chains of NFs connected via vhost-user
@@ -32,6 +32,7 @@
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
+| | ... | - fixed_auto_scale - Enable fixed auto_scale (nf_dtc). Type: boolean
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
 | |
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
 | |
 | |
 | | [Arguments] | ${nf_chains}=${1} | ${nf_nodes}=${1} | ${jumbo}=${False}
 | | ... | ${perf_qemu_qsz}=${1024} | ${use_tuned_cfs}=${False}
 | |
 | | [Arguments] | ${nf_chains}=${1} | ${nf_nodes}=${1} | ${jumbo}=${False}
 | | ... | ${perf_qemu_qsz}=${1024} | ${use_tuned_cfs}=${False}
-| | ... | ${auto_scale}=${True} | ${vnf}=vpp | ${pinning}=${True}
+| | ... | ${auto_scale}=${True} | ${fixed_auto_scale}=${False} | ${vnf}=vpp
+| | ... | ${pinning}=${True}
 | |
 | |
+| | ${enable_gso} = | Get Variable Value | ${enable_gso} | ${False}
+| | ${enable_csum} = | Get Variable Value | ${enable_csum} | ${False}
+| | ${virtio_feature_mask}= | Create Virtio feature mask
+| | ... | gso=${enable_gso} | csum=${enable_csum}
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Construct VMs on all nodes
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Construct VMs on all nodes
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
-| | ... | auto_scale=${auto_scale} | vnf=${vnf}
-| | ... | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
+| | ... | auto_scale=${auto_scale} | fixed_auto_scale=${fixed_auto_scale}
+| | ... | vnf=${vnf} | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
-| | ... | rxq_count_int=${rxq_count_int} | enable_csum=${False}
-| | ... | enable_gso=${False}
+| | ... | rxq_count_int=${rxq_count_int}
+| | ... | virtio_feature_mask=${virtio_feature_mask}
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
 | | Set Test Variable | ${cpu_alloc_str}
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
 | | Set Test Variable | ${cpu_alloc_str}
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
+| | ... | - fixed_auto_scale - Enable override auto_scale. Type: boolean
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
+| | ... | - validate - Validate interfaces are up. Type: boolean
 | |
 | | ... | *Example:*
 | |
 | |
 | | ... | *Example:*
 | |
 | |
 | | [Arguments] | ${node} | ${nf_chains}=${1} | ${nf_nodes}=${1}
 | | ... | ${jumbo}=${False} | ${perf_qemu_qsz}=${1024}
 | |
 | | [Arguments] | ${node} | ${nf_chains}=${1} | ${nf_nodes}=${1}
 | | ... | ${jumbo}=${False} | ${perf_qemu_qsz}=${1024}
-| | ... | ${use_tuned_cfs}=${False} | ${auto_scale}=${True} | ${vnf}=vpp
-| | ... | ${pinning}=${True}
+| | ... | ${use_tuned_cfs}=${False} | ${auto_scale}=${True}
+| | ... | ${fixed_auto_scale}=${False} | ${vnf}=vpp | ${pinning}=${True}
+| | ... | ${validate}=${True}
 | |
 | |
+| | ${enable_gso}= | Get Variable Value | ${enable_gso} | ${False}
+| | ${enable_csum}= | Get Variable Value | ${enable_csum} | ${False}
+| | ${virtio_feature_mask}= | Create Virtio feature mask
+| | ... | gso=${enable_gso} | csum=${enable_csum}
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Initialize
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Initialize
 | | ... | node=${node}
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
 | | ... | node=${node}
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
-| | ... | auto_scale=${auto_scale} | vnf=${vnf}
-| | ... | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
+| | ... | auto_scale=${auto_scale} | fixed_auto_scale=${fixed_auto_scale}
+| | ... | vnf=${vnf} | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
-| | ... | rxq_count_int=${rxq_count_int} | enable_csum=${False}
-| | ... | enable_gso=${False}
+| | ... | rxq_count_int=${rxq_count_int}
+| | ... | virtio_feature_mask=${virtio_feature_mask}
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
 | | Set Test Variable | ${cpu_alloc_str}
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
 | | Set Test Variable | ${cpu_alloc_str}
-| | All VPP Interfaces Ready Wait | ${nodes} | retries=${300}
+| | Run Keyword If | ${validate}
+| | ... | All VPP Interfaces Ready Wait | ${nodes} | retries=${300}
 | | VPP round robin RX placement on all DUTs | ${nodes} | prefix=Virtual
 
 | Configure chains of NFs connected via passtrough
 | | VPP round robin RX placement on all DUTs | ${nodes} | prefix=Virtual
 
 | Configure chains of NFs connected via passtrough
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
 | | ... | Type: boolean
 | | ... | - auto_scale - Whether to use same amount of RXQs for memif interface
 | | ... | in containers as vswitch, otherwise use single RXQ. Type: boolean
+| | ... | - fixed_auto_scale - Enable override auto_scale. Type: boolean
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
 | |
 | | ... | - vnf - Network function as a payload. Type: string
 | | ... | - pinning - Whether to pin QEMU VMs to specific cores
 | |
 | |
 | | [Arguments] | ${nf_chains}=${1} | ${nf_nodes}=${1} | ${jumbo}=${False}
 | | ... | ${perf_qemu_qsz}=${1024} | ${use_tuned_cfs}=${False}
 | |
 | | [Arguments] | ${nf_chains}=${1} | ${nf_nodes}=${1} | ${jumbo}=${False}
 | | ... | ${perf_qemu_qsz}=${1024} | ${use_tuned_cfs}=${False}
-| | ... | ${auto_scale}=${True} | ${vnf}=vpp | ${pinning}=${True}
+| | ... | ${auto_scale}=${True} | ${fixed_auto_scale}=${False} | ${vnf}=vpp
+| | ... | ${pinning}=${True}
 | |
 | |
+| | ${enable_gso} = | Get Variable Value | ${enable_gso} | ${False}
+| | ${enable_csum} = | Get Variable Value | ${enable_csum} | ${False}
+| | ${virtio_feature_mask}= | Create Virtio feature mask
+| | ... | gso=${enable_gso} | csum=${enable_csum}
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Construct VMs on all nodes
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
 | | Import Library | resources.libraries.python.QemuManager | ${nodes}
 | | ... | WITH NAME | vnf_manager
 | | Run Keyword | vnf_manager.Construct VMs on all nodes
 | | ... | nf_chains=${nf_chains} | nf_nodes=${nf_nodes} | jumbo=${jumbo}
 | | ... | perf_qemu_qsz=${perf_qemu_qsz} | use_tuned_cfs=${use_tuned_cfs}
-| | ... | auto_scale=${auto_scale} | vnf=${vnf}
-| | ... | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
+| | ... | auto_scale=${auto_scale} | fixed_auto_scale=${fixed_auto_scale}
+| | ... | vnf=${vnf} | tg_pf1_mac=${TG_pf1_mac}[0] | tg_pf2_mac=${TG_pf2_mac}[0]
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
 | | ... | vs_dtc=${cpu_count_int} | nf_dtc=${nf_dtc} | nf_dtcr=${nf_dtcr}
-| | ... | rxq_count_int=${rxq_count_int} | enable_csum=${False}
-| | ... | enable_gso=${False}
+| | ... | rxq_count_int=${rxq_count_int}
+| | ... | virtio_feature_mask=${virtio_feature_mask}
 | | ... | if1=${DUT1_${int}1}[0] | if2=${DUT1_${int}2}[0]
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
 | | ... | if1=${DUT1_${int}1}[0] | if2=${DUT1_${int}2}[0]
 | | ${cpu_wt}= | Run Keyword | vnf_manager.Start All VMs | pinning=${pinning}
 | | ${cpu_alloc_str}= | Catenate | SEPARATOR=, | ${cpu_alloc_str} | ${cpu_wt}
diff --git a/resources/templates/vm/init_iperf3.sh b/resources/templates/vm/init_iperf3.sh
new file mode 100644 (file)
index 0000000..959406f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+mkdir /dev/pts
+mkdir /dev/hugepages
+mount -t devpts -o "rw,noexec,nosuid,gid=5,mode=0620" devpts /dev/pts || true
+mount -t tmpfs -o "rw,noexec,nosuid,size=10%,mode=0755" tmpfs /run
+cp  /tmp/openvpp-testing/resources/tools/iperf/iperf_client.py /run
+mount -t tmpfs -o "rw,noexec,nosuid,size=10%,mode=0755" tmpfs /tmp
+mkdir -p /tmp/openvpp-testing/resources/tools/iperf/
+mv /run/iperf_client.py /tmp/openvpp-testing/resources/tools/iperf/
+mount -t hugetlbfs -o "rw,relatime,pagesize=2M" hugetlbfs /dev/hugepages
+echo Y > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
+
+# Qemu virtio-net-pci mgmt
+ip address add dev ens3 10.0.2.15/24
+ip link set dev ens3 up
+ip route add default via 10.0.2.2
+
+# Qemu virtio-net-pci vhost1
+ip address add dev ens6 ${ip_address_l}
+ip link set dev ens6 up
+ip route add ${ip_route_r} via ${ip_address_r}
+
+# Payload
+${vnf_bin}
+
+# Safenet
+poweroff -f
\ No newline at end of file
diff --git a/resources/tools/iperf/iperf_client.py b/resources/tools/iperf/iperf_client.py
new file mode 100644 (file)
index 0000000..9d9ed9b
--- /dev/null
@@ -0,0 +1,182 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module gets a bandwith limit together with other parameters, reads
+the iPerf3 configuration and sends the traffic. At the end, it measures
+the packet loss and latency.
+"""
+
+import argparse
+import json
+import sys
+import time
+import subprocess
+
+def simple_burst(args):
+    """Send traffic and measure throughput.
+
+    :param args: Named arguments from command line.
+    :type args: dict
+    """
+    if1_process = []
+    if1_results = []
+    cmd = None
+
+    if args.rate and args.frame_size:
+        iperf_frame_size = args.frame_size - 18
+        iperf_rate = float(args.rate)
+        bandwidth = \
+            int(args.frame_size) * float(iperf_rate) / args.instances
+
+    if args.warmup_time > 0:
+        try:
+            for i in range(0, args.instances):
+                cmd = u"exec sudo "
+                cmd += f"ip netns exec {args.namespace} " if args.namespace else u""
+                cmd += f"iperf3 "
+                cmd += f"--client {args.host} "
+                cmd += f"--bind {args.bind} "
+                if args.rate and args.frame_size:
+                    cmd += f"--bandwidth {bandwidth} "
+                    cmd += f"--length {iperf_frame_size} "
+                cmd += f"--port {5201 + i} "
+                cmd += f"--parallel {args.parallel} "
+                cmd += f"--time {args.warmup_time} "
+                if args.affinity:
+                    cmd += f"--affinity {args.affinity} "
+                if args.udp:
+                    cmd += f"--udp "
+                cmd += f"--zerocopy "
+                cmd += f"--json"
+                if1_process.append(
+                    subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE)
+                )
+        finally:
+            for i in range(0, args.instances):
+                if1, _ = if1_process[i].communicate(
+                    timeout=args.warmup_time + 60)
+
+    if1_process = []
+    if1_results = []
+    cmd = None
+
+    try:
+        if args.async_start:
+            args.duration += 999
+        for i in range(0, args.instances):
+            cmd = u"exec sudo "
+            cmd += f"ip netns exec {args.namespace} " if args.namespace else u""
+            cmd += f"iperf3 "
+            cmd += f"--client {args.host} "
+            cmd += f"--bind {args.bind} "
+            if args.rate and args.frame_size:
+                cmd += f"--bandwidth {bandwidth} "
+                cmd += f"--length {iperf_frame_size} "
+            cmd += f"--port {5201 + i} "
+            cmd += f"--parallel {args.parallel} "
+            cmd += f"--time {args.duration} "
+            if args.affinity:
+                cmd += f"--affinity {args.affinity} "
+            if args.udp:
+                cmd += f"--udp "
+            cmd += f"--zerocopy "
+            cmd += f"--json"
+            if1_process.append(
+                subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE)
+            )
+    finally:
+        if args.async_start:
+            for i in range(0, args.instances):
+                print(if1_process[i].pid)
+        else:
+            for i in range(0, args.instances):
+                if1, _ = if1_process[i].communicate(timeout=args.duration + 60)
+                if1_results.append(json.loads(if1))
+                if1_results[i][u"end"][u"command"] = cmd
+                print(f"{json.dumps(if1_results[i]['end'], indent = 4)}")
+
+
+def main():
+    """Main function for the traffic generator using iPerf3.
+
+    It verifies the given command line arguments and runs "simple_burst"
+    function.
+    """
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        u"--namespace", required=False, type=str,
+        help=u"Port netns name to run iPerf client on."
+    )
+    parser.add_argument(
+        u"--host", required=True, type=str,
+        help=u"Run in client mode, connecting to an iPerf server host."
+    )
+    parser.add_argument(
+        u"--bind", required=True, type=str,
+        help=u"Client bind IP address."
+    )
+    parser.add_argument(
+        u"--udp", action=u"store_true", default=False,
+        help=u"UDP test."
+    )
+    parser.add_argument(
+        u"--affinity", required=False, type=str,
+        help=u"Set the CPU affinity, if possible."
+    )
+    parser.add_argument(
+        u"--duration", required=True, type=float,
+        help=u"Duration of traffic run in seconds (-1=infinite)."
+    )
+    parser.add_argument(
+        u"--frame_size", required=False,
+        help=u"Size of a Frame without padding and IPG."
+    )
+    parser.add_argument(
+        u"--rate", required=False,
+        help=u"Traffic rate with included units (pps)."
+    )
+    parser.add_argument(
+        u"--traffic_directions", default=1, type=int,
+        help=u"Send bi- (2) or uni- (1) directional traffic."
+    )
+    parser.add_argument(
+        u"--warmup_time", type=float, default=5.0,
+        help=u"Traffic warm-up time in seconds, (0=disable)."
+    )
+    parser.add_argument(
+        u"--async_start", action=u"store_true", default=False,
+        help=u"Non-blocking call of the script."
+    )
+    parser.add_argument(
+        u"--instances", default=1, type=int,
+        help=u"The number of simultaneous client instances."
+    )
+    parser.add_argument(
+        u"--parallel", default=8, type=int,
+        help=u"The number of simultaneous client streams."
+    )
+
+    args = parser.parse_args()
+
+    # Currently limiting to uni- directional traffic.
+    if args.traffic_directions != 1:
+        print(f"Currently only uni- directional traffic is supported!")
+        sys.exit(1)
+
+    simple_burst(args)
+
+
+if __name__ == u"__main__":
+    main()
diff --git a/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-gso-iperf3.robot b/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-gso-iperf3.robot
new file mode 100644 (file)
index 0000000..91141ce
--- /dev/null
@@ -0,0 +1,112 @@
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+*** Settings ***
+| Resource | resources/libraries/robot/shared/default.robot
+|
+| Force Tags | 2_NODE_SINGLE_LINK_TOPO | PERFTEST | HW_ENV | MRR
+| ... | NIC_Intel-X710 | IP4FWD | BASE | IP4BASE | DRV_TAP
+| ... | RXQ_SIZE_4096 | TXQ_SIZE_4096 | GSO_TRUE
+| ... | ethip4-ip4base-2tap-gso
+|
+| Suite Setup | Setup suite topology interfaces
+| Suite Teardown | Tear down suite
+| Test Setup | Setup test
+| Test Teardown | Tear down test | iPerf3 | namespace
+|
+| Test Template | Local Template
+|
+| Documentation | *RFC2544: Pkt throughput IPv4 routing test cases with TAPv2*
+|
+| ... | *[Top] Network Topologies:* DUT1 1-node topology without physical links.
+| ... | *[Enc] Packet Encapsulations:* Eth-IPv4-TCP.
+| ... | *[Cfg] DUT configuration:* DUT1 is configured with IPv4 routing.
+| ... | *[Ver] TG verification:* iPerf3 client/server is used for Packet\
+| ... | generation and verification.
+| ... | *[Ref] Applicable standard specifications:* RFC2544.
+
+*** Variables ***
+| @{plugins_to_enable}= | ping_plugin.so
+| ${crypto_type}= | ${None}
+| ${nic_name}= | Intel-X710
+| ${nic_driver}= | tap
+| ${nic_rxq_size}= | 4096
+| ${nic_txq_size}= | 4096
+| ${nic_pfs}= | 2
+| ${nic_vfs}= | 0
+| ${osi_layer}= | L7
+| ${overhead}= | ${0}
+| ${enable_gso}= | ${True}
+# iPerf3 client settings:
+| ${iperf_client_bind}= | 1.1.1.1
+| ${iperf_client_bind_gw}= | 1.1.1.2
+| ${iperf_client_bind_mask}= | 30
+| ${iperf_client_interface}= | tap0
+| ${iperf_client_namespace}= | tap0_namespace
+| ${iperf_client_udp}= | ${False}
+| ${iperf_client_node}= | DUT1
+| ${iperf_client_affinity} | ${None}
+# iPerf3 server settings:
+| ${iperf_server_bind}= | 2.2.2.2
+| ${iperf_server_bind_gw}= | 2.2.2.1
+| ${iperf_server_bind_mask}= | 30
+| ${iperf_server_interface}= | tap1
+| ${iperf_server_namespace}= | tap1_namespace
+| ${iperf_server_node}= | DUT1
+| ${iperf_server_pf_key}= | ${None}
+# Trial data overwrite:
+| ${trial_duration}= | ${30}
+| ${trial_multiplicity}= | ${4}
+
+*** Keywords ***
+| Local Template
+| | [Documentation]
+| | ... | [Cfg] DUT configuration: DUT1 is configured with IPv4 routing.
+| | ... | [Ver] Measure MaxReceivedRate for ${frame_size}B frames\
+| | ... | using burst trials throughput test.\
+| |
+| | ... | *Arguments:*
+| | ... | - frame_size - Framesize in Bytes in integer. Type: integer, string
+| | ... | - phy_cores - Number of physical cores. Type: integer
+| | ... | - rxq - Number of RX queues, default value: ${None}. Type: integer
+| |
+| | [Arguments] | ${frame_size} | ${phy_cores} | ${rxq}=${None}
+| |
+| | Set Test Variable | \${frame_size}
+| | Set Test Variable | \${rxq_ratio} | ${1}
+| |
+| | Given Set Jumbo
+| | And Add worker threads to all DUTs | ${phy_cores} | ${rxq}
+| | And Pre-initialize layer driver | ${nic_driver}
+| | And Apply startup configuration on all VPP DUTs
+| | When Initialize layer driver | ${nic_driver}
+| | And Initialize layer interface
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}1}[0] | 1.1.1.2 | 30
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}2}[0] | 2.2.2.1 | 30
+| | Set Test Variable | ${iperf_server_pf_key} | ${DUT1_pf1}[0]
+| | Then Traffic should pass with maximum rate on iPerf3
+
+*** Test Cases ***
+| 128KB-1c-ethip4-ip4base-2tap-gso-iperf3
+| | [Tags] | 128KB | 1C
+| | frame_size=${128000} | phy_cores=${1}
+
+| 128KB-2c-ethip4-ip4base-2tap-gso-iperf3
+| | [Tags] | 128KB | 2C
+| | frame_size=${128000} | phy_cores=${2}
+
+| 128KB-4c-ethip4-ip4base-2tap-gso-iperf3
+| | [Tags] | 128KB | 4C
+| | frame_size=${128000} | phy_cores=${4}
diff --git a/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-iperf3.robot b/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2tap-iperf3.robot
new file mode 100644 (file)
index 0000000..31ece18
--- /dev/null
@@ -0,0 +1,112 @@
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+*** Settings ***
+| Resource | resources/libraries/robot/shared/default.robot
+|
+| Force Tags | 2_NODE_SINGLE_LINK_TOPO | PERFTEST | HW_ENV | MRR
+| ... | NIC_Intel-X710 | IP4FWD | BASE | IP4BASE | DRV_TAP
+| ... | RXQ_SIZE_4096 | TXQ_SIZE_4096 | GSO_FALSE
+| ... | ethip4-ip4base-2tap
+|
+| Suite Setup | Setup suite topology interfaces
+| Suite Teardown | Tear down suite
+| Test Setup | Setup test
+| Test Teardown | Tear down test | iPerf3 | namespace
+|
+| Test Template | Local Template
+|
+| Documentation | *RFC2544: Pkt throughput IPv4 routing test cases with TAPv2*
+|
+| ... | *[Top] Network Topologies:* DUT1 1-node topology without physical links.
+| ... | *[Enc] Packet Encapsulations:* Eth-IPv4-TCP.
+| ... | *[Cfg] DUT configuration:* DUT1 is configured with IPv4 routing.
+| ... | *[Ver] TG verification:* iPerf3 client/server is used for Packet\
+| ... | generation and verification.
+| ... | *[Ref] Applicable standard specifications:* RFC2544.
+
+*** Variables ***
+| @{plugins_to_enable}= | ping_plugin.so
+| ${crypto_type}= | ${None}
+| ${nic_name}= | Intel-X710
+| ${nic_driver}= | tap
+| ${nic_rxq_size}= | 4096
+| ${nic_txq_size}= | 4096
+| ${nic_pfs}= | 2
+| ${nic_vfs}= | 0
+| ${osi_layer}= | L7
+| ${overhead}= | ${0}
+| ${enable_gso}= | ${False}
+# iPerf3 client settings:
+| ${iperf_client_bind}= | 1.1.1.1
+| ${iperf_client_bind_gw}= | 1.1.1.2
+| ${iperf_client_bind_mask}= | 30
+| ${iperf_client_interface}= | tap0
+| ${iperf_client_namespace}= | tap0_namespace
+| ${iperf_client_udp}= | ${False}
+| ${iperf_client_node}= | DUT1
+| ${iperf_client_affinity} | ${None}
+# iPerf3 server settings:
+| ${iperf_server_bind}= | 2.2.2.2
+| ${iperf_server_bind_gw}= | 2.2.2.1
+| ${iperf_server_bind_mask}= | 30
+| ${iperf_server_interface}= | tap1
+| ${iperf_server_namespace}= | tap1_namespace
+| ${iperf_server_node}= | DUT1
+| ${iperf_server_pf_key}= | ${None}
+# Trial data overwrite:
+| ${trial_duration}= | ${30}
+| ${trial_multiplicity}= | ${4}
+
+*** Keywords ***
+| Local Template
+| | [Documentation]
+| | ... | [Cfg] DUT configuration: DUT1 is configured with IPv4 routing.
+| | ... | [Ver] Measure MaxReceivedRate for ${frame_size}B frames\
+| | ... | using burst trials throughput test.\
+| |
+| | ... | *Arguments:*
+| | ... | - frame_size - Framesize in Bytes in integer. Type: integer, string
+| | ... | - phy_cores - Number of physical cores. Type: integer
+| | ... | - rxq - Number of RX queues, default value: ${None}. Type: integer
+| |
+| | [Arguments] | ${frame_size} | ${phy_cores} | ${rxq}=${None}
+| |
+| | Set Test Variable | \${frame_size}
+| | Set Test Variable | \${rxq_ratio} | ${1}
+| |
+| | Given Set Jumbo
+| | And Add worker threads to all DUTs | ${phy_cores} | ${rxq}
+| | And Pre-initialize layer driver | ${nic_driver}
+| | And Apply startup configuration on all VPP DUTs
+| | When Initialize layer driver | ${nic_driver}
+| | And Initialize layer interface
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}1}[0] | 1.1.1.2 | 30
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}2}[0] | 2.2.2.1 | 30
+| | Set Test Variable | ${iperf_server_pf_key} | ${DUT1_pf1}[0]
+| | Then Traffic should pass with maximum rate on iPerf3
+
+*** Test Cases ***
+| 128KB-1c-ethip4-ip4base-2tap-iperf3
+| | [Tags] | 128KB | 1C
+| | frame_size=${128000} | phy_cores=${1}
+
+| 128KB-2c-ethip4-ip4base-2tap-iperf3
+| | [Tags] | 128KB | 2C
+| | frame_size=${128000} | phy_cores=${2}
+
+| 128KB-4c-ethip4-ip4base-2tap-iperf3
+| | [Tags] | 128KB | 4C
+| | frame_size=${128000} | phy_cores=${4}
diff --git a/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-gso-iperf3.robot b/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-gso-iperf3.robot
new file mode 100644 (file)
index 0000000..8495040
--- /dev/null
@@ -0,0 +1,121 @@
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+*** Settings ***
+| Resource | resources/libraries/robot/shared/default.robot
+|
+| Force Tags | 2_NODE_SINGLE_LINK_TOPO | PERFTEST | HW_ENV | MRR
+| ... | NIC_Intel-X710 | IP4FWD | BASE | IP4BASE | DRV_VHOST
+| ... | RXQ_SIZE_4096 | TXQ_SIZE_4096 | GSO_TRUE
+| ... | ethip4-ip4base-2vhost-gso
+|
+| Suite Setup | Setup suite topology interfaces
+| Suite Teardown | Tear down suite
+| Test Setup | Setup test
+| Test Teardown | Tear down test | iPerf3 | vhost
+|
+| Test Template | Local Template
+|
+| Documentation | *RFC2544: Pkt throughput IPv4 routing test cases with VHOST*
+|
+| ... | *[Top] Network Topologies:* DUT1 1-node topology without physical links.
+| ... | *[Enc] Packet Encapsulations:* Eth-IPv4-TCP.
+| ... | *[Cfg] DUT configuration:* DUT1 is configured with IPv4 routing.
+| ... | *[Ver] TG verification:* iPerf3 client/server is used for Packet\
+| ... | generation and verification.
+| ... | *[Ref] Applicable standard specifications:* RFC2544.
+
+*** Variables ***
+| @{plugins_to_enable}= | ping_plugin.so
+| ${crypto_type}= | ${None}
+| ${nic_name}= | Intel-X710
+| ${nic_driver}= | vhost
+| ${nic_rxq_size}= | 4096
+| ${nic_txq_size}= | 4096
+| ${nic_pfs}= | 2
+| ${nic_vfs}= | 0
+| ${osi_layer}= | L7
+| ${overhead}= | ${0}
+# Qemu settings:
+| ${enable_gso}= | ${True}
+| ${enable_csum}= | ${True}
+| ${nf_dtcr}= | ${1}
+| ${nf_dtc}= | ${4}
+# iPerf3 client settings:
+| ${iperf_client_bind}= | 1.1.1.1
+| ${iperf_client_bind_gw}= | 1.1.1.2
+| ${iperf_client_bind_mask}= | 30
+| ${iperf_client_interface}= | ens6
+| ${iperf_client_namespace}= | ${None}
+| ${iperf_client_udp}= | ${False}
+| ${iperf_client_node}= | DUT1_2
+| ${iperf_client_affinity} | 1
+# iPerf3 server settings:
+| ${iperf_server_bind}= | 2.2.2.2
+| ${iperf_server_bind_gw}= | 2.2.2.1
+| ${iperf_server_bind_mask}= | 30
+| ${iperf_server_interface}= | ens6
+| ${iperf_server_namespace}= | ${None}
+| ${iperf_server_node}= | DUT1_1
+| ${iperf_server_pf_key}= | ${None}
+# Trial data overwrite:
+| ${trial_duration}= | ${30}
+| ${trial_multiplicity}= | ${4}
+
+*** Keywords ***
+| Local Template
+| | [Documentation]
+| | ... | [Cfg] DUT configuration: DUT1 is configured with IPv4 routing.
+| | ... | [Ver] Measure MaxReceivedRate for ${frame_size}B frames\
+| | ... | using burst trials throughput test.\
+| |
+| | ... | *Arguments:*
+| | ... | - frame_size - Framesize in Bytes in integer. Type: integer, string
+| | ... | - phy_cores - Number of physical cores. Type: integer
+| | ... | - rxq - Number of RX queues, default value: ${None}. Type: integer
+| |
+| | [Arguments] | ${frame_size} | ${phy_cores} | ${rxq}=${None}
+| |
+| | Set Test Variable | \${frame_size}
+| | Set Test Variable | \${rxq_ratio} | ${1}
+| |
+| | Given Set Jumbo
+| | And Add worker threads to all DUTs | ${phy_cores} | ${rxq}
+| | And Pre-initialize layer driver | ${nic_driver}
+| | And Apply startup configuration on all VPP DUTs
+| | When Initialize layer driver | ${nic_driver} | validate=${False}
+| | And Initialize layer interface
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}1}[0]
+| | ... | ${iperf_server_bind_gw} | ${iperf_server_bind_mask}
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}2}[0]
+| | ... | ${iperf_client_bind_gw} | ${iperf_client_bind_mask}
+| | And Configure chains of NFs connected via vhost-user on single node
+| | ... | node=DUT1 | nf_nodes=${2} | vnf=iperf3 | auto_scale=${True}
+| | ... | fixed_auto_scale=${True} | validate=${False}
+| | And Get CPU Info from All Nodes | ${nodes}
+| | Traffic should pass with maximum rate on iPerf3
+
+*** Test Cases ***
+| 128KB-1c-ethip4-ip4base-2vhost-gso-iperf3
+| | [Tags] | 128KB | 1C
+| | frame_size=${128000} | phy_cores=${1}
+
+| 128KB-2c-ethip4-ip4base-2vhost-gso-iperf3
+| | [Tags] | 128KB | 2C
+| | frame_size=${128000} | phy_cores=${2}
+
+| 128KB-4c-ethip4-ip4base-2vhost-gso-iperf3
+| | [Tags] | 128KB | 4C
+| | frame_size=${128000} | phy_cores=${4}
diff --git a/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-iperf3.robot b/tests/vpp/perf/gso/2n1l-10ge2p1x710-ethip4-ip4base-2vhost-iperf3.robot
new file mode 100644 (file)
index 0000000..c31097e
--- /dev/null
@@ -0,0 +1,121 @@
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+*** Settings ***
+| Resource | resources/libraries/robot/shared/default.robot
+|
+| Force Tags | 2_NODE_SINGLE_LINK_TOPO | PERFTEST | HW_ENV | MRR
+| ... | NIC_Intel-X710 | IP4FWD | BASE | IP4BASE | DRV_VHOST
+| ... | RXQ_SIZE_1024 | TXQ_SIZE_1024 | GSO_TRUE
+| ... | ethip4-ip4base-2vhost-gso
+|
+| Suite Setup | Setup suite topology interfaces
+| Suite Teardown | Tear down suite
+| Test Setup | Setup test
+| Test Teardown | Tear down test | iPerf3 | vhost
+|
+| Test Template | Local Template
+|
+| Documentation | *RFC2544: Pkt throughput IPv4 routing test cases with VHOST*
+|
+| ... | *[Top] Network Topologies:* DUT1 1-node topology without physical links.
+| ... | *[Enc] Packet Encapsulations:* Eth-IPv4-TCP.
+| ... | *[Cfg] DUT configuration:* DUT1 is configured with IPv4 routing.
+| ... | *[Ver] TG verification:* iPerf3 client/server is used for Packet\
+| ... | generation and verification.
+| ... | *[Ref] Applicable standard specifications:* RFC2544.
+
+*** Variables ***
+| @{plugins_to_enable}= | ping_plugin.so
+| ${crypto_type}= | ${None}
+| ${nic_name}= | Intel-X710
+| ${nic_driver}= | vhost
+| ${nic_rxq_size}= | 1024
+| ${nic_txq_size}= | 1024
+| ${nic_pfs}= | 2
+| ${nic_vfs}= | 0
+| ${osi_layer}= | L7
+| ${overhead}= | ${0}
+# Qemu settings:
+| ${enable_gso}= | ${False}
+| ${enable_csum}= | ${False}
+| ${nf_dtcr}= | ${1}
+| ${nf_dtc}= | ${4}
+# iPerf3 client settings:
+| ${iperf_client_bind}= | 1.1.1.1
+| ${iperf_client_bind_gw}= | 1.1.1.2
+| ${iperf_client_bind_mask}= | 30
+| ${iperf_client_interface}= | ens6
+| ${iperf_client_namespace}= | ${None}
+| ${iperf_client_udp}= | ${False}
+| ${iperf_client_node}= | DUT1_2
+| ${iperf_client_affinity} | 1
+# iPerf3 server settings:
+| ${iperf_server_bind}= | 2.2.2.2
+| ${iperf_server_bind_gw}= | 2.2.2.1
+| ${iperf_server_bind_mask}= | 30
+| ${iperf_server_interface}= | ens6
+| ${iperf_server_namespace}= | ${None}
+| ${iperf_server_node}= | DUT1_1
+| ${iperf_server_pf_key}= | ${None}
+# Trial data overwrite:
+| ${trial_duration}= | ${30}
+| ${trial_multiplicity}= | ${4}
+
+*** Keywords ***
+| Local Template
+| | [Documentation]
+| | ... | [Cfg] DUT configuration: DUT1 is configured with IPv4 routing.
+| | ... | [Ver] Measure MaxReceivedRate for ${frame_size}B frames\
+| | ... | using burst trials throughput test.\
+| |
+| | ... | *Arguments:*
+| | ... | - frame_size - Framesize in Bytes in integer. Type: integer, string
+| | ... | - phy_cores - Number of physical cores. Type: integer
+| | ... | - rxq - Number of RX queues, default value: ${None}. Type: integer
+| |
+| | [Arguments] | ${frame_size} | ${phy_cores} | ${rxq}=${None}
+| |
+| | Set Test Variable | \${frame_size}
+| | Set Test Variable | \${rxq_ratio} | ${1}
+| |
+| | Given Set Jumbo
+| | And Add worker threads to all DUTs | ${phy_cores} | ${rxq}
+| | And Pre-initialize layer driver | ${nic_driver}
+| | And Apply startup configuration on all VPP DUTs
+| | When Initialize layer driver | ${nic_driver} | validate=${False}
+| | And Initialize layer interface
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}1}[0]
+| | ... | ${iperf_server_bind_gw} | ${iperf_server_bind_mask}
+| | And VPP Interface Set IP Address
+| | ... | ${dut1} | ${DUT1_${int}2}[0]
+| | ... | ${iperf_client_bind_gw} | ${iperf_client_bind_mask}
+| | And Configure chains of NFs connected via vhost-user on single node
+| | ... | node=DUT1 | nf_nodes=${2} | vnf=iperf3 | auto_scale=${True}
+| | ... | fixed_auto_scale=${True} | validate=${False}
+| | And Get CPU Info from All Nodes | ${nodes}
+| | Traffic should pass with maximum rate on iPerf3
+
+*** Test Cases ***
+| 128KB-1c-ethip4-ip4base-2vhost-iperf3
+| | [Tags] | 128KB | 1C
+| | frame_size=${128000} | phy_cores=${1}
+
+| 128KB-2c-ethip4-ip4base-2vhost-iperf3
+| | [Tags] | 128KB | 2C
+| | frame_size=${128000} | phy_cores=${2}
+
+| 128KB-4c-ethip4-ip4base-2vhost-iperf3
+| | [Tags] | 128KB | 4C
+| | frame_size=${128000} | phy_cores=${4}
diff --git a/tests/vpp/perf/gso/regenerate_testcases.py b/tests/vpp/perf/gso/regenerate_testcases.py
new file mode 100755 (executable)
index 0000000..87521e6
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# 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.
+
+from resources.libraries.python.autogen.Regenerator import Regenerator
+
+Regenerator().regenerate_glob(u"*.robot")