Integrate vsperf in Tgen mode 79/35379/10
authorJing Zhang <jing.c.zhang@nokia.com>
Thu, 25 May 2017 22:41:00 +0000 (18:41 -0400)
committerRoss Brattain <ross.b.brattain@intel.com>
Tue, 15 Aug 2017 00:59:22 +0000 (00:59 +0000)
Problem:
Running Vsperf in Tgen mode is supported but the integration is not complete at the code level
i.e. not ready-to-use, and dpdk loopback is not supported inside the VM.

Solution:
(1) Completely automates VM image generation and supports 1G huge pages.
(2) Adds a new test scenario VsperfDPDK for testpmd based loopback inside the VM.

Update 1-2: Fixed "line too long" issues not reported by local run_tests.sh (why?)
Update 3: Per comment change to use SSH.from_node() and add unit test cases
Update 4: Add more unit test cases for coverage and ready the code for merge

JIRA: YARDSTICK-661

Change-Id: Iea3014d4c83e1b0c079019a4ed27771d40a7eed8
Signed-off-by: Jing Zhang <jing.c.zhang@nokia.com>
tests/unit/benchmark/scenarios/networking/test_vsperf_dpdk.py [new file with mode: 0644]
tests/vsperf/pvp_rfc2544_throughput_dpdk.yaml [new file with mode: 0644]
tools/vsperf-img-finalize.sh [new file with mode: 0755]
tools/vsperf-img-modify.sh [new file with mode: 0755]
tools/vsperf_install.yml [new file with mode: 0644]
yardstick/benchmark/scenarios/networking/testpmd_vsperf.bash [new file with mode: 0644]
yardstick/benchmark/scenarios/networking/vsperf_dpdk.py [new file with mode: 0644]

diff --git a/tests/unit/benchmark/scenarios/networking/test_vsperf_dpdk.py b/tests/unit/benchmark/scenarios/networking/test_vsperf_dpdk.py
new file mode 100644 (file)
index 0000000..3b9f99b
--- /dev/null
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+
+# Copyright 2017 Nokia
+#
+# 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.
+
+# Unittest for yardstick.benchmark.scenarios.networking.vsperf.VsperfDPDK
+
+from __future__ import absolute_import
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+import unittest
+
+from yardstick.benchmark.scenarios.networking import vsperf_dpdk
+
+
+@mock.patch('yardstick.benchmark.scenarios.networking.vsperf_dpdk.subprocess')
+@mock.patch('yardstick.benchmark.scenarios.networking.vsperf_dpdk.ssh')
+@mock.patch("yardstick.benchmark.scenarios.networking.vsperf_dpdk.open",
+            mock.mock_open())
+class VsperfDPDKTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.ctx = {
+            "host": {
+                "ip": "10.229.47.137",
+                "user": "ubuntu",
+                "password": "ubuntu",
+            },
+        }
+        self.args = {
+            'task_id': "1234-5678",
+            'options': {
+                'testname': 'pvp_tput',
+                'traffic_type': 'rfc2544_throughput',
+                'frame_size': '64',
+                'test_params': 'TRAFFICGEN_DURATION=30;',
+                'trafficgen_port1': 'ens4',
+                'trafficgen_port2': 'ens5',
+                'conf_file': 'vsperf-yardstick.conf',
+                'setup_script': 'setup_yardstick.sh',
+                'moongen_helper_file': '~/moongen.py',
+                'moongen_host_ip': '10.5.201.151',
+                'moongen_port1_mac': '8c:dc:d4:ae:7c:5c',
+                'moongen_port2_mac': '8c:dc:d4:ae:7c:5d',
+                'trafficgen_port1_nw': 'test2',
+                'trafficgen_port2_nw': 'test3',
+            },
+            'sla': {
+                'metrics': 'throughput_rx_fps',
+                'throughput_rx_fps': 500000,
+                'action': 'monitor',
+            }
+        }
+
+    def test_vsperf_dpdk_setup(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+    def test_vsperf_dpdk_teardown(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        p.teardown()
+        self.assertEqual(p.setup_done, False)
+
+    def test_vsperf_dpdk_is_dpdk_setup_no(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # is_dpdk_setup() specific mocks
+        mock_ssh.SSH.from_node().execute.return_value = (0, 'dummy', '')
+
+        result = p._is_dpdk_setup()
+        self.assertEqual(result, False)
+
+    def test_vsperf_dpdk_is_dpdk_setup_yes(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # is_dpdk_setup() specific mocks
+        mock_ssh.SSH.from_node().execute.return_value = (0, '', '')
+
+        result = p._is_dpdk_setup()
+        self.assertEqual(result, True)
+
+    def test_vsperf_dpdk_dpdk_setup_first(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # is_dpdk_setup() specific mocks
+        mock_ssh.SSH.from_node().execute.return_value = (0, 'dummy', '')
+
+        p.dpdk_setup()
+        self.assertEqual(p._is_dpdk_setup(), False)
+        self.assertEqual(p.dpdk_setup_done, True)
+
+    def test_vsperf_dpdk_dpdk_setup_next(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # dpdk_setup() specific mocks
+        mock_ssh.SSH.from_node().execute.return_value = (0, '', '')
+
+        p.dpdk_setup()
+        self.assertEqual(p._is_dpdk_setup(), True)
+        self.assertEqual(p.dpdk_setup_done, True)
+
+    def test_vsperf_dpdk_dpdk_setup_fail(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # dpdk_setup() specific mocks
+        mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+        self.assertRaises(RuntimeError, p.dpdk_setup)
+
+    def test_vsperf_dpdk_run_ok(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # run() specific mocks
+        mock_subprocess.call().execute.return_value = None
+        mock_subprocess.call().execute.return_value = None
+        mock_ssh.SSH.from_node().execute.return_value = (
+            0, 'throughput_rx_fps\r\n14797660.000\r\n', '')
+
+        result = {}
+        p.run(result)
+
+        self.assertEqual(result['throughput_rx_fps'], '14797660.000')
+
+    def test_vsperf_dpdk_run_falied_vsperf_execution(self, mock_ssh,
+                                                mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # run() specific mocks
+        mock_subprocess.call().execute.return_value = None
+        mock_subprocess.call().execute.return_value = None
+        mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+        result = {}
+        self.assertRaises(RuntimeError, p.run, result)
+
+    def test_vsperf_dpdk_run_falied_csv_report(self, mock_ssh, mock_subprocess):
+        p = vsperf_dpdk.VsperfDPDK(self.args, self.ctx)
+
+        # setup() specific mocks
+        mock_subprocess.call().execute.return_value = None
+
+        p.setup()
+        self.assertIsNotNone(p.client)
+        self.assertEqual(p.setup_done, True)
+
+        # run() specific mocks
+        mock_subprocess.call().execute.return_value = None
+        mock_subprocess.call().execute.return_value = None
+        mock_ssh.SSH.from_node().execute.return_value = (0, '', '')
+        mock_ssh.SSH.from_node().execute.return_value = (1, '', '')
+
+        result = {}
+        self.assertRaises(RuntimeError, p.run, result)
+
+def main():
+    unittest.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/vsperf/pvp_rfc2544_throughput_dpdk.yaml b/tests/vsperf/pvp_rfc2544_throughput_dpdk.yaml
new file mode 100644 (file)
index 0000000..0b3e5a8
--- /dev/null
@@ -0,0 +1,91 @@
+# Copyright 2017 Nokia
+#
+# 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.
+
+# VSPERF specific configuration file for execution of RFC2544 throughput
+# traffic. Traffic executed by traffic generator is forwarded directly
+# between interfaces connected to the traffic generator. So test will only
+# benchmark the performance of OVS external bridge at controller node.
+# Details about supported test options and test case execution can be
+# found in VSPERF documentation:
+#
+#   http://artifacts.opnfv.org/vswitchperf/docs/userguide/yardstick.html
+
+schema: "yardstick:task:0.1"
+
+scenarios:
+{% for multistream in [1, 1000] %}
+-
+  type: VsperfDPDK
+  options:
+    testname: 'pvp_tput'
+    traffic_type: 'rfc2544_throughput'
+    multistream: {{multistream}} 
+    frame_size: 64
+    test_params: 'TRAFFICGEN_DURATION=60;'
+    trafficgen_port1: 'ens4'
+    trafficgen_port2: 'ens5'
+    conf_file: '~/vsperf-yardstick.conf'
+    moongen_helper_file: '~/moongen.py'
+    moongen_host_ip: '10.5.201.151'
+    moongen_port1_mac: '8c:dc:d4:ae:7c:5c'
+    moongen_port2_mac: '8c:dc:d4:ae:7c:5d'
+    trafficgen_port1_nw: 'test2'
+    trafficgen_port2_nw: 'test3'
+
+  host: vsperf.demo
+
+  runner:
+    type: Sequence
+    scenario_option_name: frame_size
+    sequence:
+    - 64
+    - 128
+    - 256
+    - 512
+    - 1024
+    - 1280
+    - 1518
+
+  sla:
+    # The throughput SLA (or any other SLA) cannot be set to a meaningful
+    # value without knowledge of the server and networking environment,
+    # possibly including prior testing in that environment to establish
+    # a baseline SLA level under well-understood circumstances.
+    metrics: 'throughput_rx_fps'
+    throughput_rx_fps: 500000
+    action: monitor
+{% endfor %}
+
+context:
+  name: demo
+  image: yardstick-vsperf-server
+  flavor: vsperf-flavor
+  user: ubuntu
+
+  placement_groups:
+    pgrp1:
+      policy: "availability"
+
+  servers:
+    vsperf:
+      floating_ip: true
+      placement: "pgrp1"
+
+  networks:
+    test:
+      cidr: '10.0.1.0/24'
+    test2:
+      cidr: '10.0.2.0/24'
+    test3:
+      cidr: '10.0.3.0/24'
diff --git a/tools/vsperf-img-finalize.sh b/tools/vsperf-img-finalize.sh
new file mode 100755 (executable)
index 0000000..cf3677b
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2017 Nokia
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# PREREQUISITES
+# modified image (yardstick-vsperf) must be uploaded to OpenStack
+# must have a proper flavor (vsperf-flavor) for the image e.g.
+# nova flavor-create vsperf-flavor auto 8192 80 6
+# nova flavor-key vsperf-flavor set hw:numa_nodes=1
+# nova flavor-key vsperf-flavor set hw:mem_page_size=1GB
+# nova flavor-key vsperf-flavor set hw:cpu_policy=dedicated
+# nova flavor-key vsperf-flavor set hw:vif_multiqueue_enabled=true
+
+stackname="vsperf-install-stack"
+template=vsperf_install.yml
+new_image_name="yardstick-vsperf-server"
+
+openstack stack create $stackname -f yaml -t $template
+progress="WARMING_UP"
+
+while [ "$progress" != "CREATE_COMPLETE" ]
+do
+  sleep 10
+  echo "check stack status......."
+  show_output=$(openstack stack show $stackname)
+  progress=$(echo $show_output | sed 's/^.*stack_status . \([^ ]*\).*$/\1/')
+  echo "$progress"
+  if [ "$progress" == "CREATE_FAILED" ];then
+    echo "create $stackname failed"
+    exit 1
+  fi
+done
+
+# has to stop the instance before taking the snapshot
+nova stop $stackname
+sleep 10
+
+status=$(nova image-create --poll $stackname $new_image_name)
+if [[ "$status" =~ "Finished" ]];then
+  echo "$new_image_name finished"
+fi
+
+nova delete $stackname
+sleep 10
+openstack stack delete --yes $stackname
diff --git a/tools/vsperf-img-modify.sh b/tools/vsperf-img-modify.sh
new file mode 100755 (executable)
index 0000000..3ba697c
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2017 Nokia
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# installs required packages
+# must be run from inside the image (either chrooted or running)
+
+set -ex
+
+if [ $# -eq 1 ]; then
+    nameserver_ip=$1
+
+    # /etc/resolv.conf is a symbolic link to /run, restore at end
+    rm /etc/resolv.conf
+    echo "nameserver $nameserver_ip" > /etc/resolv.conf
+    echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+    echo "nameserver 8.8.4.4" >> /etc/resolv.conf
+fi
+
+# Force apt to use ipv4 due to build problems on LF POD.
+echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99force-ipv4
+
+echo 'GRUB_CMDLINE_LINUX="resume=/dev/sda1 default_hugepagesz=1G hugepagesz=1G hugepages=32 iommu=on iommu=pt intel_iommu=on"' >> /etc/default/grub
+
+# Add hostname to /etc/hosts.
+# Allow console access via pwd
+cat <<EOF >/etc/cloud/cloud.cfg.d/10_etc_hosts.cfg
+manage_etc_hosts: True
+password: ubuntu
+chpasswd: { expire: False }
+ssh_pwauth: True
+EOF
+
+linuxheadersversion=`echo ls boot/vmlinuz* | cut -d- -f2-`
+
+apt-get update
+apt-get install -y \
+    linux-headers-$linuxheadersversion \
+    screen \
+    locate \
+    sshpass \
+    git
+
+cd /root
+git clone -b stable/danube https://gerrit.opnfv.org/gerrit/vswitchperf
+
+# do not compile ovs and qemu
+sed -i.bak -e 's/^\(SUBBUILDS\ =\ src_vanilla\)/#\1/' \
+           -e 's/^\(SUBDIRS\ += ovs.*\)/#\1/' \
+           -e 's/^\(SUBDIRS\ += qemu.*\)/#\1/' \
+    vswitchperf/src/Makefile
+# If these paths do not exist, vsperf wont start
+mkdir -p /root/vswitchperf/src/ovs/ovs/ovsdb/
+touch /root/vswitchperf/src/ovs/ovs/ovsdb/ovsdb-tool
+touch /root/vswitchperf/src/ovs/ovs/ovsdb/ovsdb-server
+mkdir -p /root/vswitchperf/src/qemu/qemu/x86_64-softmmu/
+touch /root/vswitchperf/src/qemu/qemu/x86_64-softmmu/qemu-system-x86_64
+mkdir -p /root/vswitchperf/src/ovs/ovs/utilities/
+touch /root/vswitchperf/src/ovs/ovs/utilities/ovs-dpctl
+touch /root/vswitchperf/src/ovs/ovs/utilities/ovs-vsctl
+touch /root/vswitchperf/src/ovs/ovs/utilities/ovs-ofctl
+touch /root/vswitchperf/src/ovs/ovs/utilities/ovs-appctl
+mkdir -p /root/vswitchperf/src/ovs/ovs/vswitchd/
+touch /root/vswitchperf/src/ovs/ovs/vswitchd/vswitch.ovsschema
+touch /root/vswitchperf/src/ovs/ovs/vswitchd/ovs-vswitchd
+
+# restore symlink
+#ln -sf /run/resolvconf/resolv.conf /etc/resolv.conf
diff --git a/tools/vsperf_install.yml b/tools/vsperf_install.yml
new file mode 100644 (file)
index 0000000..3c78e0c
--- /dev/null
@@ -0,0 +1,125 @@
+##############################################################################
+# Copyright (c) 2017 Nokia
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+heat_template_version: 2015-04-30
+
+description: >
+  Used to run VMs with Vsperf
+
+parameters:
+  image:
+    type: string
+    description: Name of the image
+    default: yardstick-vsperf
+
+  flavor:
+    type: string
+    default: vsperf-flavor
+
+  timeout:
+    type: number
+    description: Timeout in seconds for WaitCondition, depends on your image and environment
+    default: 6000
+
+  external_net_name:
+    type: string
+    description: Name of the external network which management network will connect to
+    default: ext-net1
+
+resources:
+  network:
+    type: OS::Neutron::Net
+    properties:
+      name: vsperf_net
+
+  subnet:
+    type: OS::Neutron::Subnet
+    properties:
+      name: vsperf_subnet
+      ip_version: 4
+      cidr: 192.168.0.0/24
+      network: { get_resource: network }
+
+  management_router:
+    type: OS::Neutron::Router
+    properties:
+      name: management_router
+      external_gateway_info:
+        network: { get_param: external_net_name }
+
+  management_router_interface:
+    type: OS::Neutron::RouterInterface
+    properties:
+      router: { get_resource: management_router }
+      subnet: { get_resource: subnet }
+
+  floating_ip:
+    type: OS::Neutron::FloatingIP
+    properties:
+      floating_network: { get_param: external_net_name }
+
+  floating_ip_association:
+    type: OS::Nova::FloatingIPAssociation
+    properties:
+      floating_ip: { get_resource: floating_ip }
+      server_id: {get_resource: vsperf_vm}
+
+  keypair:
+    type: OS::Nova::KeyPair
+    properties:
+      name: yardstick-key
+      public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0RkXfW6pksd1cZmXuvXZF/Mlqqq3ahIGcGoULOC97XMpu0vdxMpcUwdjwGqMwEXTVyfHidu0l99bLqOCpSUKCmbWx3ONJ+1kqFx4HwsKEWLiyDYqsuMrDeZT1eFjC5avCoTcrIw2wq5NaBb00lDGagNZOeopaL5YIa4+PizEY23+cir24D67NU21Fg3JE92AIeGlNa4j66L3a+lL0hZq74Dilmp42wm4GsbplRO6KJfyaraowHb1X+TmhCjBgHk6M/OJ9yPAroZyJNcwjMAuuxhAYWRuT3SdbnoUR0RG2VhfDh0qNid7vOqLbhKPeaLLFmzkN+9w3WdCp6LbSYt87 yardstick@yardstick.opnfv.org
+
+  wait_handle:
+    type: OS::Heat::WaitConditionHandle
+
+  wait_condition:
+    type: OS::Heat::WaitCondition
+    properties:
+      handle: { get_resource: wait_handle }
+      count: 1
+      timeout: { get_param: timeout }
+
+  vsperf_vm:
+    type: OS::Nova::Server
+    depends_on: [subnet, keypair]
+    properties:
+      name: { get_param: "OS::stack_name" }
+      image: { get_param: image }
+      flavor: { get_param: flavor }
+      key_name: {get_resource: keypair}
+      networks:
+        - network: { get_resource: network }
+      config_drive: True
+      user_data_format : RAW
+      user_data:
+        str_replace:
+          template: |
+            #!/bin/bash
+            cat <<'CEOF' > /tmp/vsperf_post_build.sh
+            echo "Install vswitchperf"
+            mv /root/vswitchperf /home/ubuntu
+            chown -R ubuntu:ubuntu /home/ubuntu/vswitchperf
+            cd /home/ubuntu/vswitchperf/systems
+            sudo -H -u ubuntu ./build_base_machine.sh
+            echo "Set password less access to MoonGen server"
+            sudo -H -u ubuntu ssh-keygen -b 2048 -t rsa -f /home/ubuntu/.ssh/id_rsa -N ''
+            sudo -H -u ubuntu touch /home/ubuntu/.cloud-warnings.skip
+            echo "Enable 1GB huge pages"
+            update-grub
+            $NOTIFY --data-binary '{"status": "SUCCESS"}'
+            CEOF
+            chmod +x /tmp/vsperf_post_build.sh
+            nohup /tmp/vsperf_post_build.sh &
+          params:
+            $NOTIFY: { get_attr: ['wait_handle', 'curl_cli'] }
+
+outputs:
+  vm_uuid:
+    description: uuid of the VM
+    value: { get_attr: [ vsperf_vm, show,id ] }
diff --git a/yardstick/benchmark/scenarios/networking/testpmd_vsperf.bash b/yardstick/benchmark/scenarios/networking/testpmd_vsperf.bash
new file mode 100644 (file)
index 0000000..f4d55b2
--- /dev/null
@@ -0,0 +1,60 @@
+##############################################################################
+# Copyright (c) 2017 Nokia
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+#!/bin/bash
+
+set -e
+
+# Commandline arguments
+MOONGEN_PORT1_MAC=$1         # MAC address of the peer port
+MOONGEN_PORT2_MAC=$2         # MAC address of the peer port
+
+DPDK_ROOT='/home/ubuntu/vswitchperf/src/dpdk/dpdk'
+
+load_modules()
+{
+    if ! lsmod | grep "uio" &> /dev/null; then
+        modprobe uio
+    fi
+
+    if ! lsmod | grep "igb_uio" &> /dev/null; then
+        insmod ${DPDK_ROOT}/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko
+    fi
+
+    if ! lsmod | grep "rte_kni" &> /dev/null; then
+        insmod ${DPDK_ROOT}/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko
+    fi
+}
+
+change_permissions()
+{
+    chmod 777 /sys/bus/pci/drivers/virtio-pci/*
+    chmod 777 /sys/bus/pci/drivers/igb_uio/*
+}
+
+add_interface_to_dpdk(){
+    interfaces=$(lspci |grep Eth |tail -n +2 |awk '{print $1}')
+    ${DPDK_ROOT}/tools/dpdk-devbind.py --bind=igb_uio $interfaces &> /dev/null
+}
+
+run_testpmd()
+{
+    blacklist=$(lspci |grep Eth |awk '{print $1}'|head -1)
+    cd ${DPDK_ROOT}
+    sudo ./dpdk/bin/testpmd -c 0x3f -n 4 -b $blacklist -- -a --nb-cores=4 --coremask=0x3c --burst=64 --txd=4096 --rxd=4096 --rxq=2 --txq=2 --rss-udp --eth-peer=0,$MOONGEN_PORT1_MAC --eth-peer=1,$MOONGEN_PORT2_MAC --forward-mode=mac
+}
+
+main()
+{
+    load_modules
+    change_permissions
+    add_interface_to_dpdk
+    run_testpmd
+}
+
+main
diff --git a/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py b/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py
new file mode 100644 (file)
index 0000000..4545878
--- /dev/null
@@ -0,0 +1,347 @@
+# Copyright 2016 Intel Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" VsperfDPDK specific scenario definition """
+
+from __future__ import absolute_import
+import pkg_resources
+import logging
+import os
+import subprocess
+import csv
+import time
+
+import yardstick.ssh as ssh
+import yardstick.common.utils as utils
+from yardstick.benchmark.scenarios import base
+
+LOG = logging.getLogger(__name__)
+
+
+class VsperfDPDK(base.Scenario):
+    """Execute vsperf with defined parameters
+
+  Parameters:
+    traffic_type - to specify the type of traffic executed by traffic generator
+    the valid values are "rfc2544", "continuous", "back2back"
+        type:    string
+        default: "rfc2544"
+    frame_size - a frame size for which test should be executed;
+        Multiple frame sizes can be tested by modification of sequence runner
+        section inside TC YAML definition.
+        type:    string
+        default: "64"
+    bidirectional - speficies if traffic will be uni (False) or bi-directional
+        (True)
+        type:    string
+        default: False
+    iload - specifies frame rate
+        type:    string
+        default: 100
+    multistream - the number of simulated streams
+        type:    string
+        default: 0 (disabled)
+    stream_type - specifies network layer used for multistream simulation
+        the valid values are "L4", "L3" and "L2"
+        type:    string
+        default: "L4"
+    test_params - specifies a string with a list of vsperf configuration
+        parameters, which will be passed to the '--test-params' CLI argument;
+        Parameters should be stated in the form of 'param=value' and separated
+        by a semicolon. Please check VSPERF documentation for details about
+        available configuration parameters and their data types.
+        In case that both 'test_params' and 'conf_file' are specified,
+        then values from 'test_params' will override values defined
+        in the configuration file.
+        type:    string
+        default: NA
+    conf_file - path to the vsperf configuration file, which will be uploaded
+        to the VM;
+        In case that both 'test_params' and 'conf_file' are specified,
+        then values from 'test_params' will override values defined
+        in configuration file.
+        type:   string
+        default: NA
+    setup_script - path to the setup script, which will be executed during
+        setup and teardown phases
+        type:   string
+        default: NA
+    trafficgen_port1 - specifies device name of 1st interface connected to
+        the trafficgen
+        type:   string
+        default: NA
+    trafficgen_port2 - specifies device name of 2nd interface connected to
+        the trafficgen
+        type:   string
+        default: NA
+    external_bridge - specifies name of external bridge configured in OVS
+        type:   string
+        default: "br-ex"
+
+    """
+    __scenario_type__ = "VsperfDPDK"
+
+    TESTPMD_SCRIPT = 'testpmd_vsperf.bash'
+
+    def __init__(self, scenario_cfg, context_cfg):
+        self.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+        self.moongen_host_ip = \
+            scenario_cfg['options'].get('moongen_host_ip', "127.0.0.1")
+        self.moongen_port1_mac = \
+            scenario_cfg['options'].get('moongen_port1_mac', None)
+        self.moongen_port2_mac = \
+            scenario_cfg['options'].get('moongen_port2_mac', None)
+        self.dpdk_setup_done = False
+        self.setup_done = False
+        self.client = None
+        self.tg_port1 = \
+            self.scenario_cfg['options'].get('trafficgen_port1', None)
+        self.tg_port2 = \
+            self.scenario_cfg['options'].get('trafficgen_port2', None)
+        self.tgen_port1_mac = None
+        self.tgen_port2_mac = None
+        self.br_ex = self.scenario_cfg['options'].get('external_bridge',
+                                                      'br-ex')
+        self.vsperf_conf = self.scenario_cfg['options'].get('conf_file', None)
+        if self.vsperf_conf:
+            self.vsperf_conf = os.path.expanduser(self.vsperf_conf)
+
+        self.moongen_helper = \
+            self.scenario_cfg['options'].get('moongen_helper_file', None)
+        if self.moongen_helper:
+            self.moongen_helper = os.path.expanduser(self.moongen_helper)
+
+        self.setup_script = self.scenario_cfg['options'].get('setup_script',
+                                                             None)
+        if self.setup_script:
+            self.setup_script = os.path.expanduser(self.setup_script)
+
+        self.test_params = self.scenario_cfg['options'].get('test-params',
+                                                            None)
+
+    def setup(self):
+        """scenario setup"""
+        vsperf = self.context_cfg['host']
+
+        task_id = self.scenario_cfg['task_id']
+        context_number = task_id.split('-')[0]
+        self.tg_port1_nw = vsperf.get('name', 'demo') + \
+            "-" + context_number + "-" + \
+            self.scenario_cfg['options'].get('trafficgen_port1_nw', 'test2')
+        self.tg_port2_nw = vsperf.get('name', 'demo') + \
+            "-" + context_number + "-" + \
+            self.scenario_cfg['options'].get('trafficgen_port2_nw', 'test3')
+
+        # copy vsperf conf to VM
+        self.client = ssh.SSH.from_node(vsperf, defaults={
+            "user": "ubuntu", "password": "ubuntu"
+        })
+        # traffic generation could last long
+        self.client.wait(timeout=1800)
+
+        # copy script to host
+        self.client._put_file_shell(self.vsperf_conf, '~/vsperf.conf')
+
+        self.client._put_file_shell(
+            self.moongen_helper,
+            '~/vswitchperf/tools/pkt_gen/moongen/moongen.py')
+
+        # execute external setup script
+        if self.setup_script:
+            cmd = "%s setup" % (self.setup_script)
+            LOG.info("Execute setup script \"%s\"", cmd)
+            subprocess.call(cmd, shell=True)
+
+        self.setup_done = True
+
+    def dpdk_setup(self):
+        """dpdk setup"""
+
+        # setup dpdk loopback in VM
+        self.testpmd_script = pkg_resources.resource_filename(
+            'yardstick.benchmark.scenarios.networking',
+            VsperfDPDK.TESTPMD_SCRIPT)
+
+        self.client._put_file_shell(self.testpmd_script,
+                                    '~/testpmd_vsperf.sh')
+
+        # disable Address Space Layout Randomization (ASLR)
+        cmd = "echo 0 | sudo tee /proc/sys/kernel/randomize_va_space"
+        self.client.send_command(cmd)
+
+        if not self._is_dpdk_setup():
+            self.tgen_port1_ip = \
+                utils.get_port_ip(self.client, self.tg_port1)
+            self.tgen_port1_mac = \
+                utils.get_port_mac(self.client, self.tg_port1)
+            self.client.run("tee ~/.testpmd.ipaddr.port1 > /dev/null",
+                            stdin=self.tgen_port1_ip)
+            self.client.run("tee ~/.testpmd.macaddr.port1 > /dev/null",
+                            stdin=self.tgen_port1_mac)
+            self.tgen_port2_ip = \
+                utils.get_port_ip(self.client, self.tg_port2)
+            self.tgen_port2_mac = \
+                utils.get_port_mac(self.client, self.tg_port2)
+            self.client.run("tee ~/.testpmd.ipaddr.port2 > /dev/null",
+                            stdin=self.tgen_port2_ip)
+            self.client.run("tee ~/.testpmd.macaddr.port2 > /dev/null",
+                            stdin=self.tgen_port2_mac)
+            cmd = "ip link set %s down" % (self.tg_port1)
+            LOG.debug("Executing command: %s", cmd)
+            self.client.send_command(cmd)
+            cmd = "ip link set %s down" % (self.tg_port2)
+            LOG.debug("Executing command: %s", cmd)
+            self.client.send_command(cmd)
+        else:
+            cmd = "cat ~/.testpmd.macaddr.port1"
+            status, stdout, stderr = self.client.execute(cmd)
+            if status:
+                raise RuntimeError(stderr)
+            self.tgen_port1_mac = stdout
+            cmd = "cat ~/.testpmd.macaddr.port2"
+            status, stdout, stderr = self.client.execute(cmd)
+            if status:
+                raise RuntimeError(stderr)
+            self.tgen_port2_mac = stdout
+
+        cmd = "screen -d -m sudo -E bash ~/testpmd_vsperf.sh %s %s" % \
+            (self.moongen_port1_mac, self.moongen_port2_mac)
+        LOG.debug("Executing command: %s", cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+        if status:
+            raise RuntimeError(stderr)
+
+        time.sleep(1)
+
+        self.dpdk_setup_done = True
+
+    def _is_dpdk_setup(self):
+        """Is dpdk already setup in the host?"""
+        is_run = True
+        cmd = "ip a | grep %s 2>/dev/null" % (self.tg_port1)
+        LOG.debug("Executing command: %s", cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+        if stdout:
+            is_run = False
+        return is_run
+
+    def run(self, result):
+        """ execute the vsperf benchmark and return test results
+            within result dictionary
+        """
+
+        if not self.setup_done:
+            self.setup()
+
+        # remove results from previous tests
+        self.client.execute("rm -rf /tmp/results*")
+
+        # get vsperf options
+        options = self.scenario_cfg['options']
+        test_params = []
+        traffic_type = self.scenario_cfg['options'].\
+            get("traffic_type", "rfc2544_throughput")
+        multistream = self.scenario_cfg['options'].get("multistream", 1)
+
+        if not self.dpdk_setup_done:
+            self.dpdk_setup()
+
+        if 'frame_size' in options:
+            test_params.append("%s=(%s,)" % ('TRAFFICGEN_PKT_SIZES',
+                                             options['frame_size']))
+
+        cmd = "openstack network show %s | grep segmentation_id | " \
+              "cut -d '|' -f 3" % (self.tg_port1_nw)
+        LOG.debug("Executing command: %s", cmd)
+        tg_port1_vlan = subprocess.check_output(cmd, shell=True)
+
+        cmd = "openstack network show %s | grep segmentation_id | " \
+              "cut -d '|' -f 3" % (self.tg_port2_nw)
+        LOG.debug("Executing command: %s", cmd)
+        tg_port2_vlan = subprocess.check_output(cmd, shell=True)
+
+        additional_params = \
+            'TRAFFIC={"traffic_type":"%s", "multistream":%d, ' \
+            '"l2":{"srcmac":"{\'%s\',\'%s\'}", "dstmac":"{\'%s\',\'%s\'}"}, ' \
+            '"vlan":{"enabled":"True", "id":"{%d,%d}"}}' \
+            % (traffic_type, multistream,
+               self.moongen_port1_mac, self.moongen_port2_mac,
+               self.tgen_port1_mac, self.tgen_port2_mac,
+               int(tg_port1_vlan), int(tg_port2_vlan))
+
+        if 'test_params' in options:
+            test_params.append(options['test_params'] + additional_params)
+
+        # filter empty parameters and escape quotes and double quotes
+        test_params = [tp.replace('"', '\\"').replace("'", "\\'")
+                       for tp in test_params if tp]
+
+        # Set password less access to MoonGen
+        cmd = "sshpass -p yardstick ssh-copy-id -o StrictHostKeyChecking=no " \
+              "root@%s -p 22" % (self.moongen_host_ip)
+        LOG.debug("Executing command: %s", cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+        if status:
+            raise RuntimeError(stderr)
+
+        # execute vsperf
+        cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf ; "
+        cmd += "./vsperf --mode trafficgen "
+        if self.vsperf_conf:
+            cmd += "--conf-file ~/vsperf.conf "
+        cmd += "--test-params=\"%s\"" % (';'.join(test_params))
+        LOG.debug("Executing command: %s", cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+
+        if status:
+            raise RuntimeError(stderr)
+
+        # get test results
+        cmd = "cat /tmp/results*/result.csv"
+        LOG.debug("Executing command: %s", cmd)
+        status, stdout, stderr = self.client.execute(cmd)
+
+        if status:
+            raise RuntimeError(stderr)
+
+        # convert result.csv to JSON format
+        reader = csv.DictReader(stdout.split('\r\n'))
+        result.update(next(reader))
+        result['nrFlows'] = multistream
+
+        # sla check; go through all defined SLAs and check if values measured
+        # by VSPERF are higher then those defined by SLAs
+        if 'sla' in self.scenario_cfg and \
+           'metrics' in self.scenario_cfg['sla']:
+            for metric in self.scenario_cfg['sla']['metrics'].split(','):
+                assert metric in result, \
+                    '%s is not collected by VSPERF' % (metric)
+                assert metric in self.scenario_cfg['sla'], \
+                    '%s is not defined in SLA' % (metric)
+                vs_res = float(result[metric])
+                sla_res = float(self.scenario_cfg['sla'][metric])
+                assert vs_res >= sla_res, \
+                    'VSPERF_%s(%f) < SLA_%s(%f)' % \
+                    (metric, vs_res, metric, sla_res)
+
+    def teardown(self):
+        """cleanup after the test execution"""
+
+        # execute external setup script
+        if self.setup_script:
+            cmd = "%s teardown" % (self.setup_script)
+            LOG.info("Execute setup script \"%s\"", cmd)
+            subprocess.call(cmd, shell=True)
+
+        self.setup_done = False