aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Klozik <martinx.klozik@intel.com>2016-10-11 12:41:57 +0100
committerMartin Klozik <martinx.klozik@intel.com>2016-10-27 13:20:19 +0000
commit9c13028cf9b29da86e5b12c5d3b8c4d6bd858545 (patch)
treebfff243bcfa31ec2db5b92a0d507bcecbc7fcd2c
parentadfdd0db071cf8247434cb456cc676144323719f (diff)
teststeps: Generic support of step driven tests
In the past, step driven testcases were supported only by integration testcases. This patch adds generic support of TestSteps for both integration and performance testcases. Step driven test were improved to support modification of existing deployment. As part of the patch a refactoring of traffic controllers were performed. Traffic controllers were modified to support trafficgen-off and trafficgen-pause modes in all possible ways of trafficgen invocation. JIRA: VSPERF-362 Change-Id: Ic8b7a9b0e7165f0a15a52279ed0f0952da9fedb8 Signed-off-by: Martin Klozik <martinx.klozik@intel.com> Reviewed-by: Maryam Tahhan <maryam.tahhan@intel.com> Reviewed-by: Al Morton <acmorton@att.com> Reviewed-by: Christian Trautman <ctrautma@redhat.com> Reviewed-by: Bill Michalowski <bmichalo@redhat.com> Reviewed-by: Antonio Fischetti <antonio.fischetti@intel.com> Reviewed-by: Sridhar K. N. Rao <sridhar.rao@spirent.com>
-rw-r--r--core/__init__.py2
-rw-r--r--core/component_factory.py10
-rw-r--r--core/traffic_controller.py133
-rw-r--r--core/traffic_controller_rfc2544.py108
-rw-r--r--core/traffic_controller_rfc2889.py107
-rw-r--r--core/vnf_controller.py17
-rw-r--r--docs/userguide/index.rst1
-rwxr-xr-xdocs/userguide/integration.rst573
-rw-r--r--docs/userguide/teststeps.rst651
-rw-r--r--testcases/integration.py192
-rw-r--r--testcases/performance.py2
-rw-r--r--testcases/testcase.py224
-rw-r--r--vnfs/qemu/qemu.py43
13 files changed, 1055 insertions, 1008 deletions
diff --git a/core/__init__.py b/core/__init__.py
index e39ec88e..d16401ea 100644
--- a/core/__init__.py
+++ b/core/__init__.py
@@ -15,5 +15,5 @@
"""Core structural interfaces and their implementations
"""
import core.component_factory
-from core.traffic_controller import (ITrafficController)
+from core.traffic_controller import (TrafficController)
from core.vswitch_controller import (IVswitchController)
diff --git a/core/component_factory.py b/core/component_factory.py
index ef7ba86f..236a61ed 100644
--- a/core/component_factory.py
+++ b/core/component_factory.py
@@ -46,7 +46,7 @@ def create_traffic(traffic_type, trafficgen_class):
:param traffic_type: Name of traffic type
:param trafficgen_class: Reference to traffic generator class to be used.
- :return: A new ITrafficController
+ :return: A new TrafficController
"""
if traffic_type.lower().startswith('rfc2889'):
return TrafficControllerRFC2889(trafficgen_class)
@@ -87,7 +87,7 @@ def create_vswitch(deployment_scenario, vswitch_class, traffic,
raise RuntimeError("Unknown deployment scenario '{}'.".format(deployment_scenario))
-def create_vnf(deployment_scenario, vnf_class):
+def create_vnf(deployment_scenario, vnf_class, extra_vnfs):
"""Return a new VnfController for the deployment_scenario.
The returned controller is configured with the given VNF class.
@@ -96,9 +96,13 @@ def create_vnf(deployment_scenario, vnf_class):
:param deployment_scenario: The deployment scenario name
:param vswitch_class: Reference to vSwitch class to be used.
+ :param extra_vnfs: The number of VNFs not involved in given
+ deployment scenario. It will be used to correctly expand
+ configuration values and initialize shared dirs. This parameter
+ is used in case, that additional VNFs are executed by TestSteps.
:return: VnfController for the deployment_scenario
"""
- return VnfController(deployment_scenario, vnf_class)
+ return VnfController(deployment_scenario, vnf_class, extra_vnfs)
def create_collector(collector_class, result_dir, test_name):
"""Return a new Collector of the given class
diff --git a/core/traffic_controller.py b/core/traffic_controller.py
index 428e91f8..a6c1bd8d 100644
--- a/core/traffic_controller.py
+++ b/core/traffic_controller.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Intel Corporation.
+# Copyright 2015-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.
@@ -12,17 +12,105 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Interface to traffic controllers
+"""Base class for traffic controllers
"""
-class ITrafficController(object):
- """Abstract class which defines a traffic controller object
+import logging
+import os
+import time
+
+from core.results.results_constants import ResultsConstants
+from conf import settings
+from conf import get_test_param
+
+class TrafficController(object):
+ """Base class which defines a common functionality for all traffic
+ controller classes.
Used to setup and control a traffic generator for a particular deployment
scenario.
"""
+ def __init__(self, traffic_gen_class):
+ """Initialization common for all types of traffic controllers
+
+ :param traffic_gen_class: The traffic generator class to be used.
+ """
+ self._type = None
+ self._logger = logging.getLogger(__name__)
+ self._logger.debug("__init__")
+ self._traffic_gen_class = traffic_gen_class()
+ self._traffic_started = False
+ self._traffic_started_call_count = 0
+ self._duration = int(get_test_param('duration', 30))
+ self._lossrate = float(get_test_param('lossrate', 0.0))
+ self._mode = settings.getValue('mode').lower()
+ self._results = []
+
+ # If set, comma separated packet_sizes value from --test_params
+ # on cli takes precedence over value in settings file.
+ self._packet_sizes = None
+ packet_sizes_cli = get_test_param('pkt_sizes')
+ if packet_sizes_cli:
+ self._packet_sizes = [int(x.strip())
+ for x in packet_sizes_cli.split(',')]
+ else:
+ self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES')
+
+ def __enter__(self):
+ """Call initialisation function.
+ """
+ self._traffic_gen_class.connect()
+
+ def __exit__(self, type_, value, traceback):
+ """Stop traffic, clean up.
+ """
+ if self._traffic_started:
+ self.stop_traffic()
+
+ def _append_results(self, result_dict, packet_size):
+ """Adds common values to traffic generator results.
+
+ :param result_dict: Dictionary containing results from trafficgen
+ :param packet_size: Packet size value.
+
+ :returns: dictionary of results with additional entries.
+ """
+
+ ret_value = result_dict
- def send_traffic(self, traffic):
+ ret_value[ResultsConstants.TYPE] = self._type
+ ret_value[ResultsConstants.PACKET_SIZE] = str(packet_size)
+
+ return ret_value
+
+ def traffic_required(self):
+ """Checks selected '--mode' of traffic generator and performs
+ its specific handling.
+
+ :returns: True - in case that traffic generator should be executed
+ False - if traffic generation is not required
+ """
+ if self._mode == 'trafficgen-off':
+ time.sleep(2)
+ self._logger.debug("All is set. Please run traffic generator manually.")
+ input(os.linesep + "Press Enter to terminate vswitchperf..." +
+ os.linesep + os.linesep)
+ return False
+ elif self._mode == 'trafficgen-pause':
+ time.sleep(2)
+ while True:
+ choice = input(os.linesep + 'Transmission paused, should'
+ ' transmission be resumed? [y/n]' + os.linesep).lower()
+ if choice in ('yes', 'y', 'ye'):
+ return True
+ elif choice in ('no', 'n'):
+ self._logger.info("Traffic transmission will be skipped.")
+ return False
+ else:
+ print("Please respond with 'yes', 'y', 'no' or 'n' ", end='')
+ return True
+
+ def send_traffic(self, dummy_traffic):
"""Triggers traffic to be sent from the traffic generator.
This is a blocking function.
@@ -33,7 +121,7 @@ class ITrafficController(object):
"The TrafficController does not implement",
"the \"send_traffic\" function.")
- def send_traffic_async(self, traffic, function):
+ def send_traffic_async(self, dummy_traffic, dummy_function):
"""Triggers traffic to be sent asynchronously.
This is not a blocking function.
@@ -55,6 +143,33 @@ class ITrafficController(object):
def stop_traffic(self):
"""Kills traffic being sent from the traffic generator.
"""
- raise NotImplementedError(
- "The TrafficController does not implement",
- "the \"stop_traffic\" function.")
+ self._logger.debug("stop_traffic()")
+
+ def print_results(self):
+ """IResult interface implementation.
+ """
+ counter = 0
+ for item in self._results:
+ logging.info("Record: " + str(counter))
+ counter += 1
+ for(key, value) in list(item.items()):
+ logging.info(" Key: " + str(key) +
+ ", Value: " + str(value))
+
+ def get_results(self):
+ """IResult interface implementation.
+ """
+ return self._results
+
+ def validate_send_traffic(self, dummy_result, dummy_traffic):
+ """Verify that send traffic has succeeded
+ """
+ if len(self._results):
+ if 'b2b_frames' in self._results[-1]:
+ return float(self._results[-1]['b2b_frames']) > 0
+ elif 'throughput_rx_fps' in self._results[-1]:
+ return float(self._results[-1]['throughput_rx_fps']) > 0
+ else:
+ return True
+ else:
+ return False
diff --git a/core/traffic_controller_rfc2544.py b/core/traffic_controller_rfc2544.py
index af09deff..874c3ae7 100644
--- a/core/traffic_controller_rfc2544.py
+++ b/core/traffic_controller_rfc2544.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Intel Corporation.
+# Copyright 2015-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.
@@ -13,16 +13,12 @@
# limitations under the License.
"""RFC2544 Traffic Controller implementation.
"""
-import logging
-
-from core.traffic_controller import ITrafficController
-from core.results.results_constants import ResultsConstants
+from core.traffic_controller import TrafficController
from core.results.results import IResults
-from conf import settings
from conf import get_test_param
-class TrafficControllerRFC2544(ITrafficController, IResults):
+class TrafficControllerRFC2544(TrafficController, IResults):
"""Traffic controller for RFC2544 traffic
Used to setup and control a traffic generator for an RFC2544 deployment
@@ -34,60 +30,15 @@ class TrafficControllerRFC2544(ITrafficController, IResults):
:param traffic_gen_class: The traffic generator class to be used.
"""
- self._logger = logging.getLogger(__name__)
- self._logger.debug("__init__")
- self._traffic_gen_class = traffic_gen_class()
- self._traffic_started = False
- self._traffic_started_call_count = 0
+ super(TrafficControllerRFC2544, self).__init__(traffic_gen_class)
+ self._type = 'rfc2544'
self._tests = int(get_test_param('rfc2544_tests', 1))
- self._duration = int(get_test_param('duration', 30))
- self._lossrate = float(get_test_param('lossrate', 0.0))
- self._results = []
-
- # If set, comma separated packet_sizes value from --test_params
- # on cli takes precedence over value in settings file.
- self._packet_sizes = None
- packet_sizes_cli = get_test_param('pkt_sizes')
- if packet_sizes_cli:
- self._packet_sizes = [int(x.strip())
- for x in packet_sizes_cli.split(',')]
- else:
- self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES')
-
- def __enter__(self):
- """Call initialisation function.
- """
- self._traffic_gen_class.connect()
-
- def __exit__(self, type_, value, traceback):
- """Stop traffic, clean up.
- """
- if self._traffic_started:
- self.stop_traffic()
-
- @staticmethod
- def _append_results(result_dict, packet_size):
- """Adds common values to traffic generator results.
-
- :param result_dict: Dictionary containing results from trafficgen
- :param packet_size: Packet size value.
-
- :returns: dictionary of results with additional entries.
- """
-
- ret_value = result_dict
-
- # TODO Old TOIT controller had knowledge about scenario beeing
- # executed, should new controller also fill Configuration & ID,
- # or this should be passed to TestCase?
- ret_value[ResultsConstants.TYPE] = 'rfc2544'
- ret_value[ResultsConstants.PACKET_SIZE] = str(packet_size)
-
- return ret_value
def send_traffic(self, traffic):
- """See ITrafficController for description
+ """See TrafficController for description
"""
+ if not self.traffic_required():
+ return
self._logger.debug('send_traffic with ' +
str(self._traffic_gen_class))
@@ -109,13 +60,14 @@ class TrafficControllerRFC2544(ITrafficController, IResults):
result = self._traffic_gen_class.send_rfc2544_throughput(
traffic, tests=self._tests, duration=self._duration, lossrate=self._lossrate)
- result = TrafficControllerRFC2544._append_results(result,
- packet_size)
+ result = self._append_results(result, packet_size)
self._results.append(result)
def send_traffic_async(self, traffic, function):
- """See ITrafficController for description
+ """See TrafficController for description
"""
+ if not self.traffic_required():
+ return
self._logger.debug('send_traffic_async with ' +
str(self._traffic_gen_class))
@@ -131,40 +83,6 @@ class TrafficControllerRFC2544(ITrafficController, IResults):
else:
function['function']()
result = self._traffic_gen_class.wait_rfc2544_throughput()
- result = TrafficControllerRFC2544._append_results(result,
- packet_size)
+ result = self._append_results(result, packet_size)
self._results.append(result)
- def stop_traffic(self):
- """Kills traffic being sent from the traffic generator.
- """
- self._logger.debug("stop_traffic()")
-
- def print_results(self):
- """IResult interface implementation.
- """
- counter = 0
- for item in self._results:
- logging.info("Record: " + str(counter))
- counter += 1
- for(key, value) in list(item.items()):
- logging.info(" Key: " + str(key) +
- ", Value: " + str(value))
-
- def get_results(self):
- """IResult interface implementation.
- """
- return self._results
-
- def validate_send_traffic(self, dummy_result, dummy_traffic):
- """Verify that send traffic has succeeded
- """
- if len(self._results):
- if 'b2b_frames' in self._results[-1]:
- return float(self._results[-1]['b2b_frames']) > 0
- elif 'throughput_rx_fps' in self._results[-1]:
- return float(self._results[-1]['throughput_rx_fps']) > 0
- else:
- return True
- else:
- return False
diff --git a/core/traffic_controller_rfc2889.py b/core/traffic_controller_rfc2889.py
index a97a47d3..a2e12e6d 100644
--- a/core/traffic_controller_rfc2889.py
+++ b/core/traffic_controller_rfc2889.py
@@ -1,15 +1,24 @@
+# Copyright 2016 Spirent Communications, 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.
"""RFC2889 Traffic Controller implementation.
"""
-import logging
-
-from core.traffic_controller import ITrafficController
-from core.results.results_constants import ResultsConstants
+from core.traffic_controller import TrafficController
from core.results.results import IResults
-from conf import settings
from conf import get_test_param
-class TrafficControllerRFC2889(ITrafficController, IResults):
+class TrafficControllerRFC2889(TrafficController, IResults):
"""Traffic controller for RFC2889 traffic
Used to setup and control a traffic generator for an RFC2889 deployment
@@ -21,56 +30,15 @@ class TrafficControllerRFC2889(ITrafficController, IResults):
:param traffic_gen_class: The traffic generator class to be used.
"""
- self._logger = logging.getLogger(__name__)
- self._logger.debug("__init__")
- self._traffic_gen_class = traffic_gen_class()
- self._traffic_started = False
- self._traffic_started_call_count = 0
+ super(TrafficControllerRFC2889, self).__init__(traffic_gen_class)
+ self._type = 'rfc2889'
self._trials = int(get_test_param('rfc2889_trials', 1))
- self._duration = int(get_test_param('duration', 30))
- self._results = []
-
- # If set, comma separated packet_sizes value from --test_params
- # on cli takes precedence over value in settings file.
- self._packet_sizes = None
- packet_sizes_cli = get_test_param('pkt_sizes')
- if packet_sizes_cli:
- self._packet_sizes = [int(x.strip())
- for x in packet_sizes_cli.split(',')]
- else:
- self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES')
-
- def __enter__(self):
- """Call initialisation function.
- """
- self._traffic_gen_class.connect()
-
- def __exit__(self, type_, value, traceback):
- """Stop traffic, clean up.
- """
- if self._traffic_started:
- self.stop_traffic()
-
- @staticmethod
- def _append_results(result_dict, packet_size):
- """Adds common values to traffic generator results.
-
- :param result_dict: Dictionary containing results from trafficgen
- :param packet_size: Packet size value.
-
- :returns: dictionary of results with additional entries.
- """
-
- ret_value = result_dict
-
- ret_value[ResultsConstants.TYPE] = 'rfc2889'
- ret_value[ResultsConstants.PACKET_SIZE] = str(packet_size)
-
- return ret_value
def send_traffic(self, traffic):
- """See ITrafficController for description
+ """See TrafficController for description
"""
+ if not self.traffic_required():
+ return
self._logger.debug('send_traffic with ' +
str(self._traffic_gen_class))
@@ -92,13 +60,14 @@ class TrafficControllerRFC2889(ITrafficController, IResults):
result = self._traffic_gen_class.send_rfc2889_forwarding(
traffic, tests=self._trials, duration=self._duration)
- result = TrafficControllerRFC2889._append_results(result,
- packet_size)
+ result = self._append_results(result, packet_size)
self._results.append(result)
def send_traffic_async(self, traffic, function):
- """See ITrafficController for description
+ """See TrafficController for description
"""
+ if not self.traffic_required():
+ return
self._logger.debug('send_traffic_async with ' +
str(self._traffic_gen_class))
@@ -115,32 +84,6 @@ class TrafficControllerRFC2889(ITrafficController, IResults):
function['function']()
result = self._traffic_gen_class.wait_rfc2889_forwarding(
traffic, trials=self._trials, duration=self._duration)
- result = TrafficControllerRFC2889._append_results(result,
- packet_size)
+ result = self._append_results(result, packet_size)
self._results.append(result)
- def stop_traffic(self):
- """Kills traffic being sent from the traffic generator.
- """
- self._logger.debug("stop_traffic()")
-
- def print_results(self):
- """IResult interface implementation.
- """
- counter = 0
- for item in self._results:
- logging.info("Record: " + str(counter))
- counter += 1
- for(key, value) in list(item.items()):
- logging.info(" Key: " + str(key) +
- ", Value: " + str(value))
-
- def get_results(self):
- """IResult interface implementation.
- """
- return self._results
-
- def validate_send_traffic(self, result, traffic):
- """Verify that send traffic has succeeded
- """
- return True
diff --git a/core/vnf_controller.py b/core/vnf_controller.py
index 3e472f04..937cd5cc 100644
--- a/core/vnf_controller.py
+++ b/core/vnf_controller.py
@@ -31,10 +31,14 @@ class VnfController(object):
_vnfs: A list of vnfs controlled by the controller.
"""
- def __init__(self, deployment, vnf_class):
+ def __init__(self, deployment, vnf_class, extra_vnfs):
"""Sets up the VNF infrastructure based on deployment scenario
:param vnf_class: The VNF class to be used.
+ :param extra_vnfs: The number of VNFs not involved in given
+ deployment scenario. It will be used to correctly expand
+ configuration values and initialize shared dirs. This parameter
+ is used in case, that additional VNFs are executed by TestSteps.
"""
# reset VNF ID counter for each testcase
IVnf.reset_vnf_counter()
@@ -57,9 +61,9 @@ class VnfController(object):
# without VNFs like p2p
vm_number = 0
- if vm_number:
- self._logger.debug('Check configuration for %s guests.', vm_number)
- settings.check_vm_settings(vm_number)
+ if vm_number + extra_vnfs > 0:
+ self._logger.debug('Check configuration for %s guests.', vm_number + extra_vnfs)
+ settings.check_vm_settings(vm_number + extra_vnfs)
# enforce that GUEST_NIC_NR is 1 or even number of NICs
updated = False
nics_nr = settings.getValue('GUEST_NICS_NR')
@@ -73,10 +77,11 @@ class VnfController(object):
'was updated to GUEST_NICS_NR = %s',
settings.getValue('GUEST_NICS_NR'))
+ if vm_number:
self._vnfs = [vnf_class() for _ in range(vm_number)]
- self._logger.debug('__init__ ' + str(len(self._vnfs)) +
- ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
+ self._logger.debug('__init__ ' + str(len(self._vnfs)) +
+ ' VNF[s] with ' + ' '.join(map(str, self._vnfs)))
def get_vnfs(self):
"""Returns a list of vnfs controlled by this controller.
diff --git a/docs/userguide/index.rst b/docs/userguide/index.rst
index 1a796dbf..a1cce262 100644
--- a/docs/userguide/index.rst
+++ b/docs/userguide/index.rst
@@ -11,5 +11,6 @@ VSPERF User Guide
:maxdepth: 3
testusage.rst
+ teststeps.rst
integration.rst
yardstick.rst
diff --git a/docs/userguide/integration.rst b/docs/userguide/integration.rst
index 20d6b70c..5b26a716 100755
--- a/docs/userguide/integration.rst
+++ b/docs/userguide/integration.rst
@@ -33,579 +33,6 @@ view the current test list simply execute the following command:
The standard tests included are defined inside the
``conf/integration/01_testcases.conf`` file.
-Test Steps
-----------
-
-Execution of integration tests are done on a step by step work flow starting
-with step 0 as defined inside the test case. Each step of the test increments
-the step number by one which is indicated in the log.
-
-.. code-block:: console
-
- (testcases.integration) - Step 1 - 'vswitch add_switch ['int_br1']' ... OK
-
-Each step in the test case is validated. If a step does not pass validation the
-test will fail and terminate. The test will continue until a failure is detected
-or all steps pass. A csv report file is generated after a test completes with an
-OK or FAIL result.
-
-Test objects and their functions
---------------------------------
-
-Every test step can call a function of one of the supported test objects. The list
-of supported objects and their most common functions follows:
-
- * ``vswitch`` - provides functions for vSwitch configuration
-
- List of supported functions:
-
- * ``add_switch br_name`` - creates a new switch (bridge) with given ``br_name``
- * ``del_switch br_name`` - deletes switch (bridge) with given ``br_name``
- * ``add_phy_port br_name`` - adds a physical port into bridge specified by ``br_name``
- * ``add_vport br_name`` - adds a virtual port into bridge specified by ``br_name``
- * ``del_port br_name port_name`` - removes physical or virtual port specified by
- ``port_name`` from bridge ``br_name``
- * ``add_flow br_name flow`` - adds flow specified by ``flow`` dictionary into
- the bridge ``br_name``; Content of flow dictionary will be passed to the vSwitch.
- In case of Open vSwitch it will be passed to the ``ovs-ofctl add-flow`` command.
- Please see Open vSwitch documentation for the list of supported flow parameters.
- * ``del_flow br_name [flow]`` - deletes flow specified by ``flow`` dictionary from
- bridge ``br_name``; In case that optional parameter ``flow`` is not specified
- or set to an empty dictionary ``{}``, then all flows from bridge ``br_name``
- will be deleted.
- * ``dump_flows br_name`` - dumps all flows from bridge specified by ``br_name``
- * ``enable_stp br_name`` - enables Spanning Tree Protocol for bridge ``br_name``
- * ``disable_stp br_name`` - disables Spanning Tree Protocol for bridge ``br_name``
- * ``enable_rstp br_name`` - enables Rapid Spanning Tree Protocol for bridge ``br_name``
- * ``disable_rstp br_name`` - disables Rapid Spanning Tree Protocol for bridge ``br_name``
-
- Examples:
-
- .. code-block:: python
-
- ['vswitch', 'add_switch', 'int_br0']
-
- ['vswitch', 'del_switch', 'int_br0']
-
- ['vswitch', 'add_phy_port', 'int_br0']
-
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]']
-
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '1', 'actions': ['output:2'],
- 'idle_timeout': '0'}],
-
- ['vswitch', 'enable_rstp', 'int_br0']
-
- * ``vnf[ID]`` - provides functions for deployment and termination of VNFs; Optional
- alfanumerical ``ID`` is used for VNF identification in case that testcase
- deploys multiple VNFs.
-
- List of supported functions:
-
- * ``start`` - starts a VNF based on VSPERF configuration
- * ``stop`` - gracefully terminates given VNF
-
- Examples:
-
- .. code-block:: python
-
- ['vnf1', 'start']
- ['vnf2', 'start']
- ['vnf2', 'stop']
- ['vnf1', 'stop']
-
- * ``trafficgen`` - triggers traffic generation
-
- List of supported functions:
-
- * ``send_traffic traffic`` - starts a traffic based on the vsperf configuration
- and given ``traffic`` dictionary. More details about ``traffic`` dictionary
- and its possible values are available at `Traffic Generator Integration Guide
- <http://artifacts.opnfv.org/vswitchperf/docs/design/trafficgen_integration_guide.html#step-5-supported-traffic-types>`__
-
- Examples:
-
- .. code-block:: python
-
- ['trafficgen', 'send_traffic', {'traffic_type' : 'throughput'}]
-
- ['trafficgen', 'send_traffic', {'traffic_type' : 'back2back', 'bidir' : 'True'}]
-
- * ``settings`` - reads or modifies VSPERF configuration
-
- List of supported functions:
-
- * ``getValue param`` - returns value of given ``param``
- * ``setValue param value`` - sets value of ``param`` to given ``value``
-
- Examples:
-
- .. code-block:: python
-
- ['settings', 'getValue', 'TOOLS']
-
- ['settings', 'setValue', 'GUEST_USERNAME', ['root']]
-
- * ``namespace`` - creates or modifies network namespaces
-
- List of supported functions:
-
- * ``create_namespace name`` - creates new namespace with given ``name``
- * ``delete_namespace name`` - deletes namespace specified by its ``name``
- * ``assign_port_to_namespace port name [port_up]`` - assigns NIC specified by ``port``
- into given namespace ``name``; If optional parameter ``port_up`` is set to ``True``,
- then port will be brought up.
- * ``add_ip_to_namespace_eth port name addr cidr`` - assigns an IP address ``addr``/``cidr``
- to the NIC specified by ``port`` within namespace ``name``
- * ``reset_port_to_root port name`` - returns given ``port`` from namespace ``name`` back
- to the root namespace
-
- Examples:
-
- .. code-block:: python
-
- ['namespace', 'create_namespace', 'testns']
-
- ['namespace', 'assign_port_to_namespace', 'eth0', 'testns']
-
- * ``veth`` - manipulates with eth and veth devices
-
- List of supported functions:
-
- * ``add_veth_port port peer_port`` - adds a pair of veth ports named ``port`` and
- ``peer_port``
- * ``del_veth_port port peer_port`` - deletes a veth port pair specified by ``port``
- and ``peer_port``
- * ``bring_up_eth_port eth_port [namespace]`` - brings up ``eth_port`` in (optional)
- ``namespace``
-
- Examples:
-
- .. code-block:: python
-
- ['veth', 'add_veth_port', 'veth', 'veth1']
-
- ['veth', 'bring_up_eth_port', 'eth1']
-
- * ``tools`` - provides a set of helper functions
-
- List of supported functions:
-
- * ``Assert condition`` - evaluates given ``condition`` and raises ``AssertionError``
- in case that condition is not ``True``
- * ``Eval expression`` - evaluates given expression as a python code and returns
- its result
- * ``Exec command [regex]`` - executes a shell command and filters its output by
- (optional) regular expression
-
- Examples:
-
- .. code-block:: python
-
- ['tools', 'exec', 'numactl -H', 'available: ([0-9]+)']
- ['tools', 'assert', '#STEP[-1][0]>1']
-
- * ``wait`` - is used for test case interruption. This object doesn't have
- any functions. Once reached, vsperf will pause test execution and waits
- for press of ``Enter key``. It can be used during testcase design
- for debugging purposes.
-
- Examples:
-
- .. code-block:: python
-
- ['wait']
-
-Test Macros
------------
-
-Test profiles can include macros as part of the test step. Each step in the
-profile may return a value such as a port name. Recall macros use #STEP to
-indicate the recalled value inside the return structure. If the method the
-test step calls returns a value it can be later recalled, for example:
-
-.. code-block:: python
-
- {
- "Name": "vswitch_add_del_vport",
- "Deployment": "clean",
- "Description": "vSwitch - add and delete virtual port",
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_vport', 'int_br0'], # STEP 1
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], # STEP 2
- ['vswitch', 'del_switch', 'int_br0'], # STEP 3
- ]
- }
-
-This test profile uses the vswitch add_vport method which returns a string
-value of the port added. This is later called by the del_port method using the
-name from step 1.
-
-It is also possible to use negative indexes in step macros. In that case
-``#STEP[-1]`` will refer to the result from previous step, ``#STEP[-2]``
-will refer to result of step called before previous step, etc. It means,
-that you could change ``STEP 2`` from previous example to achieve the same
-functionality:
-
-.. code-block:: python
-
- ['vswitch', 'del_port', 'int_br0', '#STEP[-1][0]'], # STEP 2
-
-Also commonly used steps can be created as a separate profile.
-
-.. code-block:: python
-
- STEP_VSWITCH_PVP_INIT = [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 3
- ['vswitch', 'add_vport', 'int_br0'], # STEP 4
- ]
-
-This profile can then be used inside other testcases
-
-.. code-block:: python
-
- {
- "Name": "vswitch_pvp",
- "Deployment": "clean",
- "Description": "vSwitch - configure switch and one vnf",
- "TestSteps": STEP_VSWITCH_PVP_INIT +
- [
- ['vnf', 'start'],
- ['vnf', 'stop'],
- ] +
- STEP_VSWITCH_PVP_FINIT
- }
-
-HelloWorld and other basic Testcases
-------------------------------------
-
-The following examples are for demonstration purposes.
-You can run them by copying and pasting into the
-conf/integration/01_testcases.conf file.
-A command-line instruction is shown at the end of each
-example.
-
-HelloWorld
-^^^^^^^^^^
-
-The first example is a HelloWorld testcase.
-It simply creates a bridge with 2 physical ports, then sets up a flow to drop
-incoming packets from the port that was instantiated at the STEP #1.
-There's no interaction with the traffic generator.
-Then the flow, the 2 ports and the bridge are deleted.
-'add_phy_port' method creates a 'dpdk' type interface that will manage the
-physical port. The string value returned is the port name that will be referred
-by 'del_port' later on.
-
-.. code-block:: python
-
- {
- "Name": "HelloWorld",
- "Description": "My first testcase",
- "Deployment": "clean",
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'actions': ['drop'], 'idle_timeout': '0'}],
- ['vswitch', 'del_flow', 'int_br0'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
-
- }
-
-To run HelloWorld test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --integration HelloWorld
-
-Specify a Flow by the IP address
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The next example shows how to explicitly set up a flow by specifying a
-destination IP address.
-All packets received from the port created at STEP #1 that have a destination
-IP address = 90.90.90.90 will be forwarded to the port created at the STEP #2.
-
-.. code-block:: python
-
- {
- "Name": "p2p_rule_l3da",
- "Description": "Phy2Phy with rule on L3 Dest Addr",
- "Deployment": "clean",
- "biDirectional": "False",
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_dst': '90.90.90.90', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous'}],
- ['vswitch', 'dump_flows', 'int_br0'], # STEP 5
- ['vswitch', 'del_flow', 'int_br0'], # STEP 7 == del-flows
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
- },
-
-To run the test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --integration p2p_rule_l3da
-
-Multistream feature
-^^^^^^^^^^^^^^^^^^^
-
-The next testcase uses the multistream feature.
-The traffic generator will send packets with different UDP ports.
-That is accomplished by using "Stream Type" and "MultiStream" keywords.
-4 different flows are set to forward all incoming packets.
-
-.. code-block:: python
-
- {
- "Name": "multistream_l4",
- "Description": "Multistream on UDP ports",
- "Deployment": "clean",
- "Stream Type": "L4",
- "MultiStream": 4,
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- # Setup Flows
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '0', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '1', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '2', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '3', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- # Send mono-dir traffic
- ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
- 'bidir' : 'False'}],
- # Clean up
- ['vswitch', 'del_flow', 'int_br0'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
- },
-
-To run the test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --integration multistream_l4
-
-PVP with a VM Replacement
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example launches a 1st VM in a PVP topology, then the VM is replaced
-by another VM.
-When VNF setup parameter in ./conf/04_vnf.conf is "QemuDpdkVhostUser"
-'add_vport' method creates a 'dpdkvhostuser' type port to connect a VM.
-
-.. code-block:: python
-
- {
- "Name": "ex_replace_vm",
- "Description": "PVP with VM replacement",
- "Deployment": "clean",
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
- ['vswitch', 'add_vport', 'int_br0'], # STEP 4
-
- # Setup Flows
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', \
- 'actions': ['output:#STEP[4][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[3][1]', \
- 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}],
-
- # Start VM 1
- ['vnf1', 'start'],
- # Now we want to replace VM 1 with another VM
- ['vnf1', 'stop'],
-
- ['vswitch', 'add_vport', 'int_br0'], # STEP 11 vm2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 12
- ['vswitch', 'del_flow', 'int_br0'],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'actions': ['output:#STEP[11][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[12][1]', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
-
- # Start VM 2
- ['vnf2', 'start'],
- ['vnf2', 'stop'],
- ['vswitch', 'dump_flows', 'int_br0'],
-
- # Clean up
- ['vswitch', 'del_flow', 'int_br0'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1
- ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[11][0]'], # vm2
- ['vswitch', 'del_port', 'int_br0', '#STEP[12][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
- },
-
-To run the test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --integration ex_replace_vm
-
-VM with a Linux bridge
-^^^^^^^^^^^^^^^^^^^^^^
-
-In this example a command-line parameter allows to set up a Linux bridge into
-the guest VM.
-That's one of the available ways to specify the guest application.
-Packets matching the flow will be forwarded to the VM.
-
-.. code-block:: python
-
- {
- "Name": "ex_pvp_rule_l3da",
- "Description": "PVP with flow on L3 Dest Addr",
- "Deployment": "clean",
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
- ['vswitch', 'add_vport', 'int_br0'], # STEP 4
- # Setup Flows
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_dst': '90.90.90.90', \
- 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
- # Each pkt from the VM is forwarded to the 2nd dpdk port
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- # Start VMs
- ['vnf1', 'start'],
- ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
- 'bidir' : 'False'}],
- ['vnf1', 'stop'],
- # Clean up
- ['vswitch', 'dump_flows', 'int_br0'], # STEP 10
- ['vswitch', 'del_flow', 'int_br0'], # STEP 11
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1 ports
- ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
- },
-
-To run the test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --test-params
- "guest_loopback=linux_bridge" --integration ex_pvp_rule_l3da
-
-Forward packets based on UDP port
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This examples launches 2 VMs connected in parallel.
-Incoming packets will be forwarded to one specific VM depending on the
-destination UDP port.
-
-.. code-block:: python
-
- {
- "Name": "ex_2pvp_rule_l4dp",
- "Description": "2 PVP with flows on L4 Dest Port",
- "Deployment": "clean",
- "Stream Type": "L4", # loop UDP ports
- "MultiStream": 2,
- "TestSteps": [
- ['vswitch', 'add_switch', 'int_br0'], # STEP 0
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
- ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
- ['vswitch', 'add_vport', 'int_br0'], # STEP 4
- ['vswitch', 'add_vport', 'int_br0'], # STEP 5 vm2
- ['vswitch', 'add_vport', 'int_br0'], # STEP 6
- # Setup Flows to reply ICMPv6 and similar packets, so to
- # avoid flooding internal port with their re-transmissions
- ['vswitch', 'add_flow', 'int_br0', \
- {'priority': '1', 'dl_src': '00:00:00:00:00:01', \
- 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', \
- {'priority': '1', 'dl_src': '00:00:00:00:00:02', \
- 'actions': ['output:#STEP[4][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', \
- {'priority': '1', 'dl_src': '00:00:00:00:00:03', \
- 'actions': ['output:#STEP[5][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', \
- {'priority': '1', 'dl_src': '00:00:00:00:00:04', \
- 'actions': ['output:#STEP[6][1]'], 'idle_timeout': '0'}],
- # Forward UDP packets depending on dest port
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '0', \
- 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
- 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '1', \
- 'actions': ['output:#STEP[5][1]'], 'idle_timeout': '0'}],
- # Send VM output to phy port #2
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[6][1]', \
- 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
- # Start VMs
- ['vnf1', 'start'], # STEP 16
- ['vnf2', 'start'], # STEP 17
- ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
- 'bidir' : 'False'}],
- ['vnf1', 'stop'],
- ['vnf2', 'stop'],
- ['vswitch', 'dump_flows', 'int_br0'],
- # Clean up
- ['vswitch', 'del_flow', 'int_br0'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1 ports
- ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
- ['vswitch', 'del_port', 'int_br0', '#STEP[5][0]'], # vm2 ports
- ['vswitch', 'del_port', 'int_br0', '#STEP[6][0]'],
- ['vswitch', 'del_switch', 'int_br0'],
- ]
- },
-
-To run the test:
-
- .. code-block:: console
-
- ./vsperf --conf-file user_settings.py --integration ex_2pvp_rule_l4dp
-
Executing Tunnel encapsulation tests
------------------------------------
diff --git a/docs/userguide/teststeps.rst b/docs/userguide/teststeps.rst
new file mode 100644
index 00000000..51e62020
--- /dev/null
+++ b/docs/userguide/teststeps.rst
@@ -0,0 +1,651 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) OPNFV, Intel Corporation, AT&T and others.
+
+Step driven tests
+=================
+
+In general, test scenarios are defined by a ``deployment`` used in the particular
+test case definition. The chosen deployment scenario will take care of the vSwitch
+configuration, deployment of VNFs and it can also affect configuration of a traffic
+generator. In order to allow a more flexible way of testcase scripting, VSPERF supports
+a detailed step driven testcase definition. It can be used to configure and
+program vSwitch, deploy and terminate VNFs, execute a traffic generator,
+modify a VSPERF configuration, execute external commands, etc.
+
+Execution of step driven tests is done on a step by step work flow starting
+with step 0 as defined inside the test case. Each step of the test increments
+the step number by one which is indicated in the log.
+
+.. code-block:: console
+
+ (testcases.integration) - Step 0 'vswitch add_vport ['br0']' start
+
+Step driven tests can be used for both performance and integration testing.
+In case of integration test, each step in the test case is validated. If a step
+does not pass validation the test will fail and terminate. The test will continue
+until a failure is detected or all steps pass. A csv report file is generated after
+a test completes with an OK or FAIL result.
+
+In case of performance test, the validation of steps is not performed and
+standard output files with results from traffic generator and underlying OS
+details are generated by vsperf.
+
+Step driven testcases can be used in two different ways:
+
+ # description of full testcase - in this case ``clean`` deployment is used
+ to indicate that vsperf should neither configure vSwitch nor deploy any VNF.
+ Test shall perform all required vSwitch configuration and programming and
+ deploy required number of VNFs.
+
+ # modification of existing deployment - in this case, any of supported
+ deployments can be used to perform initial vSwitch configuration and
+ deployment of VNFs. Additional actions defined by TestSteps can be used
+ to alter vSwitch configuration or deploy additional VNFs. After the last
+ step is processed, the test execution will continue with traffic execution.
+
+Test objects and their functions
+--------------------------------
+
+Every test step can call a function of one of the supported test objects. The list
+of supported objects and their most common functions follows:
+
+ * ``vswitch`` - provides functions for vSwitch configuration
+
+ List of supported functions:
+
+ * ``add_switch br_name`` - creates a new switch (bridge) with given ``br_name``
+ * ``del_switch br_name`` - deletes switch (bridge) with given ``br_name``
+ * ``add_phy_port br_name`` - adds a physical port into bridge specified by ``br_name``
+ * ``add_vport br_name`` - adds a virtual port into bridge specified by ``br_name``
+ * ``del_port br_name port_name`` - removes physical or virtual port specified by
+ ``port_name`` from bridge ``br_name``
+ * ``add_flow br_name flow`` - adds flow specified by ``flow`` dictionary into
+ the bridge ``br_name``; Content of flow dictionary will be passed to the vSwitch.
+ In case of Open vSwitch it will be passed to the ``ovs-ofctl add-flow`` command.
+ Please see Open vSwitch documentation for the list of supported flow parameters.
+ * ``del_flow br_name [flow]`` - deletes flow specified by ``flow`` dictionary from
+ bridge ``br_name``; In case that optional parameter ``flow`` is not specified
+ or set to an empty dictionary ``{}``, then all flows from bridge ``br_name``
+ will be deleted.
+ * ``dump_flows br_name`` - dumps all flows from bridge specified by ``br_name``
+ * ``enable_stp br_name`` - enables Spanning Tree Protocol for bridge ``br_name``
+ * ``disable_stp br_name`` - disables Spanning Tree Protocol for bridge ``br_name``
+ * ``enable_rstp br_name`` - enables Rapid Spanning Tree Protocol for bridge ``br_name``
+ * ``disable_rstp br_name`` - disables Rapid Spanning Tree Protocol for bridge ``br_name``
+
+ Examples:
+
+ .. code-block:: python
+
+ ['vswitch', 'add_switch', 'int_br0']
+
+ ['vswitch', 'del_switch', 'int_br0']
+
+ ['vswitch', 'add_phy_port', 'int_br0']
+
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]']
+
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '1', 'actions': ['output:2'],
+ 'idle_timeout': '0'}],
+
+ ['vswitch', 'enable_rstp', 'int_br0']
+
+ * ``vnf[ID]`` - provides functions for deployment and termination of VNFs; Optional
+ alfanumerical ``ID`` is used for VNF identification in case that testcase
+ deploys multiple VNFs.
+
+ List of supported functions:
+
+ * ``start`` - starts a VNF based on VSPERF configuration
+ * ``stop`` - gracefully terminates given VNF
+
+ Examples:
+
+ .. code-block:: python
+
+ ['vnf1', 'start']
+ ['vnf2', 'start']
+ ['vnf2', 'stop']
+ ['vnf1', 'stop']
+
+ * ``trafficgen`` - triggers traffic generation
+
+ List of supported functions:
+
+ * ``send_traffic traffic`` - starts a traffic based on the vsperf configuration
+ and given ``traffic`` dictionary. More details about ``traffic`` dictionary
+ and its possible values are available at `Traffic Generator Integration Guide
+ <http://artifacts.opnfv.org/vswitchperf/docs/design/trafficgen_integration_guide.html#step-5-supported-traffic-types>`__
+
+ Examples:
+
+ .. code-block:: python
+
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'throughput'}]
+
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'back2back', 'bidir' : 'True'}]
+
+ * ``settings`` - reads or modifies VSPERF configuration
+
+ List of supported functions:
+
+ * ``getValue param`` - returns value of given ``param``
+ * ``setValue param value`` - sets value of ``param`` to given ``value``
+
+ Examples:
+
+ .. code-block:: python
+
+ ['settings', 'getValue', 'TOOLS']
+
+ ['settings', 'setValue', 'GUEST_USERNAME', ['root']]
+
+ * ``namespace`` - creates or modifies network namespaces
+
+ List of supported functions:
+
+ * ``create_namespace name`` - creates new namespace with given ``name``
+ * ``delete_namespace name`` - deletes namespace specified by its ``name``
+ * ``assign_port_to_namespace port name [port_up]`` - assigns NIC specified by ``port``
+ into given namespace ``name``; If optional parameter ``port_up`` is set to ``True``,
+ then port will be brought up.
+ * ``add_ip_to_namespace_eth port name addr cidr`` - assigns an IP address ``addr``/``cidr``
+ to the NIC specified by ``port`` within namespace ``name``
+ * ``reset_port_to_root port name`` - returns given ``port`` from namespace ``name`` back
+ to the root namespace
+
+ Examples:
+
+ .. code-block:: python
+
+ ['namespace', 'create_namespace', 'testns']
+
+ ['namespace', 'assign_port_to_namespace', 'eth0', 'testns']
+
+ * ``veth`` - manipulates with eth and veth devices
+
+ List of supported functions:
+
+ * ``add_veth_port port peer_port`` - adds a pair of veth ports named ``port`` and
+ ``peer_port``
+ * ``del_veth_port port peer_port`` - deletes a veth port pair specified by ``port``
+ and ``peer_port``
+ * ``bring_up_eth_port eth_port [namespace]`` - brings up ``eth_port`` in (optional)
+ ``namespace``
+
+ Examples:
+
+ .. code-block:: python
+
+ ['veth', 'add_veth_port', 'veth', 'veth1']
+
+ ['veth', 'bring_up_eth_port', 'eth1']
+
+ * ``tools`` - provides a set of helper functions
+
+ List of supported functions:
+
+ * ``Assert condition`` - evaluates given ``condition`` and raises ``AssertionError``
+ in case that condition is not ``True``
+ * ``Eval expression`` - evaluates given expression as a python code and returns
+ its result
+ * ``Exec command [regex]`` - executes a shell command and filters its output by
+ (optional) regular expression
+
+ Examples:
+
+ .. code-block:: python
+
+ ['tools', 'exec', 'numactl -H', 'available: ([0-9]+)']
+ ['tools', 'assert', '#STEP[-1][0]>1']
+
+ * ``wait`` - is used for test case interruption. This object doesn't have
+ any functions. Once reached, vsperf will pause test execution and waits
+ for press of ``Enter key``. It can be used during testcase design
+ for debugging purposes.
+
+ Examples:
+
+ .. code-block:: python
+
+ ['wait']
+
+Test Macros
+-----------
+
+Test profiles can include macros as part of the test step. Each step in the
+profile may return a value such as a port name. Recall macros use #STEP to
+indicate the recalled value inside the return structure. If the method the
+test step calls returns a value it can be later recalled, for example:
+
+.. code-block:: python
+
+ {
+ "Name": "vswitch_add_del_vport",
+ "Deployment": "clean",
+ "Description": "vSwitch - add and delete virtual port",
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 1
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], # STEP 2
+ ['vswitch', 'del_switch', 'int_br0'], # STEP 3
+ ]
+ }
+
+This test profile uses the vswitch add_vport method which returns a string
+value of the port added. This is later called by the del_port method using the
+name from step 1.
+
+It is also possible to use negative indexes in step macros. In that case
+``#STEP[-1]`` will refer to the result from previous step, ``#STEP[-2]``
+will refer to result of step called before previous step, etc. It means,
+that you could change ``STEP 2`` from previous example to achieve the same
+functionality:
+
+.. code-block:: python
+
+ ['vswitch', 'del_port', 'int_br0', '#STEP[-1][0]'], # STEP 2
+
+Also commonly used steps can be created as a separate profile.
+
+.. code-block:: python
+
+ STEP_VSWITCH_PVP_INIT = [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 3
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 4
+ ]
+
+This profile can then be used inside other testcases
+
+.. code-block:: python
+
+ {
+ "Name": "vswitch_pvp",
+ "Deployment": "clean",
+ "Description": "vSwitch - configure switch and one vnf",
+ "TestSteps": STEP_VSWITCH_PVP_INIT +
+ [
+ ['vnf', 'start'],
+ ['vnf', 'stop'],
+ ] +
+ STEP_VSWITCH_PVP_FINIT
+ }
+
+HelloWorld and other basic Testcases
+------------------------------------
+
+The following examples are for demonstration purposes.
+You can run them by copying and pasting into the
+conf/integration/01_testcases.conf file.
+A command-line instruction is shown at the end of each
+example.
+
+HelloWorld
+^^^^^^^^^^
+
+The first example is a HelloWorld testcase.
+It simply creates a bridge with 2 physical ports, then sets up a flow to drop
+incoming packets from the port that was instantiated at the STEP #1.
+There's no interaction with the traffic generator.
+Then the flow, the 2 ports and the bridge are deleted.
+'add_phy_port' method creates a 'dpdk' type interface that will manage the
+physical port. The string value returned is the port name that will be referred
+by 'del_port' later on.
+
+.. code-block:: python
+
+ {
+ "Name": "HelloWorld",
+ "Description": "My first testcase",
+ "Deployment": "clean",
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'actions': ['drop'], 'idle_timeout': '0'}],
+ ['vswitch', 'del_flow', 'int_br0'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+
+ },
+
+To run HelloWorld test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --integration HelloWorld
+
+Specify a Flow by the IP address
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The next example shows how to explicitly set up a flow by specifying a
+destination IP address.
+All packets received from the port created at STEP #1 that have a destination
+IP address = 90.90.90.90 will be forwarded to the port created at the STEP #2.
+
+.. code-block:: python
+
+ {
+ "Name": "p2p_rule_l3da",
+ "Description": "Phy2Phy with rule on L3 Dest Addr",
+ "Deployment": "clean",
+ "biDirectional": "False",
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_dst': '90.90.90.90', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous'}],
+ ['vswitch', 'dump_flows', 'int_br0'], # STEP 5
+ ['vswitch', 'del_flow', 'int_br0'], # STEP 7 == del-flows
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --integration p2p_rule_l3da
+
+Multistream feature
+^^^^^^^^^^^^^^^^^^^
+
+The next testcase uses the multistream feature.
+The traffic generator will send packets with different UDP ports.
+That is accomplished by using "Stream Type" and "MultiStream" keywords.
+4 different flows are set to forward all incoming packets.
+
+.. code-block:: python
+
+ {
+ "Name": "multistream_l4",
+ "Description": "Multistream on UDP ports",
+ "Deployment": "clean",
+ "Stream Type": "L4",
+ "MultiStream": 4,
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ # Setup Flows
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '0', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '1', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '2', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '3', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ # Send mono-dir traffic
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
+ 'bidir' : 'False'}],
+ # Clean up
+ ['vswitch', 'del_flow', 'int_br0'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --integration multistream_l4
+
+PVP with a VM Replacement
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This example launches a 1st VM in a PVP topology, then the VM is replaced
+by another VM.
+When VNF setup parameter in ./conf/04_vnf.conf is "QemuDpdkVhostUser"
+'add_vport' method creates a 'dpdkvhostuser' type port to connect a VM.
+
+.. code-block:: python
+
+ {
+ "Name": "ex_replace_vm",
+ "Description": "PVP with VM replacement",
+ "Deployment": "clean",
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 4
+
+ # Setup Flows
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', \
+ 'actions': ['output:#STEP[4][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[3][1]', \
+ 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}],
+
+ # Start VM 1
+ ['vnf1', 'start'],
+ # Now we want to replace VM 1 with another VM
+ ['vnf1', 'stop'],
+
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 11 vm2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 12
+ ['vswitch', 'del_flow', 'int_br0'],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'actions': ['output:#STEP[11][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[12][1]', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+
+ # Start VM 2
+ ['vnf2', 'start'],
+ ['vnf2', 'stop'],
+ ['vswitch', 'dump_flows', 'int_br0'],
+
+ # Clean up
+ ['vswitch', 'del_flow', 'int_br0'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1
+ ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[11][0]'], # vm2
+ ['vswitch', 'del_port', 'int_br0', '#STEP[12][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --integration ex_replace_vm
+
+VM with a Linux bridge
+^^^^^^^^^^^^^^^^^^^^^^
+
+This example setups a PVP topology and routes traffic to the VM based on
+the destination IP address. A command-line parameter is used to select a Linux
+bridge as a guest loopback application. It is also possible to select a guest
+loopback application by a configuration option ``GUEST_LOOPBACK``.
+
+.. code-block:: python
+
+ {
+ "Name": "ex_pvp_rule_l3da",
+ "Description": "PVP with flow on L3 Dest Addr",
+ "Deployment": "clean",
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 4
+ # Setup Flows
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_dst': '90.90.90.90', \
+ 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
+ # Each pkt from the VM is forwarded to the 2nd dpdk port
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ # Start VMs
+ ['vnf1', 'start'],
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
+ 'bidir' : 'False'}],
+ ['vnf1', 'stop'],
+ # Clean up
+ ['vswitch', 'dump_flows', 'int_br0'], # STEP 10
+ ['vswitch', 'del_flow', 'int_br0'], # STEP 11
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1 ports
+ ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --test-params
+ "guest_loopback=linux_bridge" --integration ex_pvp_rule_l3da
+
+Forward packets based on UDP port
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This examples launches 2 VMs connected in parallel.
+Incoming packets will be forwarded to one specific VM depending on the
+destination UDP port.
+
+.. code-block:: python
+
+ {
+ "Name": "ex_2pvp_rule_l4dp",
+ "Description": "2 PVP with flows on L4 Dest Port",
+ "Deployment": "clean",
+ "Stream Type": "L4", # loop UDP ports
+ "MultiStream": 2,
+ "TestSteps": [
+ ['vswitch', 'add_switch', 'int_br0'], # STEP 0
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 1
+ ['vswitch', 'add_phy_port', 'int_br0'], # STEP 2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 3 vm1
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 4
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 5 vm2
+ ['vswitch', 'add_vport', 'int_br0'], # STEP 6
+ # Setup Flows to reply ICMPv6 and similar packets, so to
+ # avoid flooding internal port with their re-transmissions
+ ['vswitch', 'add_flow', 'int_br0', \
+ {'priority': '1', 'dl_src': '00:00:00:00:00:01', \
+ 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', \
+ {'priority': '1', 'dl_src': '00:00:00:00:00:02', \
+ 'actions': ['output:#STEP[4][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', \
+ {'priority': '1', 'dl_src': '00:00:00:00:00:03', \
+ 'actions': ['output:#STEP[5][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', \
+ {'priority': '1', 'dl_src': '00:00:00:00:00:04', \
+ 'actions': ['output:#STEP[6][1]'], 'idle_timeout': '0'}],
+ # Forward UDP packets depending on dest port
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '0', \
+ 'actions': ['output:#STEP[3][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', \
+ 'dl_type': '0x0800', 'nw_proto': '17', 'udp_dst': '1', \
+ 'actions': ['output:#STEP[5][1]'], 'idle_timeout': '0'}],
+ # Send VM output to phy port #2
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[4][1]', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[6][1]', \
+ 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}],
+ # Start VMs
+ ['vnf1', 'start'], # STEP 16
+ ['vnf2', 'start'], # STEP 17
+ ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', \
+ 'bidir' : 'False'}],
+ ['vnf1', 'stop'],
+ ['vnf2', 'stop'],
+ ['vswitch', 'dump_flows', 'int_br0'],
+ # Clean up
+ ['vswitch', 'del_flow', 'int_br0'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], # vm1 ports
+ ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'],
+ ['vswitch', 'del_port', 'int_br0', '#STEP[5][0]'], # vm2 ports
+ ['vswitch', 'del_port', 'int_br0', '#STEP[6][0]'],
+ ['vswitch', 'del_switch', 'int_br0'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py --integration ex_2pvp_rule_l4dp
+
+Modification of existing PVVP deployment
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This is an example of modification of a standard deployment scenario with additional TestSteps.
+Standard PVVP scenario is used to configure a vSwitch and to deploy two VNFs connected
+in series. Additional TestSteps will deploy a 3rd VNF and connect it in parallel to
+already configured VNFs. Traffic generator is instructed (by Multistream feature) to send
+two separate traffic streams. One stream will be sent to the standalone VNF and second
+to two chained VNFs.
+
+In case, that test is defined as a performance test, then traffic results will be collected
+and available in both csv and rst report files.
+
+.. code-block:: python
+
+ {
+ "Name": "pvvp_pvp_cont",
+ "Traffic Type": "continuous",
+ "Deployment": "pvvp",
+ "Description": "PVVP and PVP in parallel with Continuous Stream",
+ "biDirectional": "True",
+ "iLoad": "100",
+ "MultiStream": "2",
+ "TestSteps": [
+ ['vswitch', 'add_vport', 'br0'],
+ ['vswitch', 'add_vport', 'br0'],
+ # priority must be higher than default 32768, otherwise flows won't match
+ ['vswitch', 'add_flow', 'br0',
+ {'in_port': '1', 'actions': ['output:#STEP[-2][1]'], 'idle_timeout': '0', 'dl_type':'0x800',
+ 'nw_proto':'17', 'tp_dst':'0', 'priority': '33000'}],
+ ['vswitch', 'add_flow', 'br0',
+ {'in_port': '2', 'actions': ['output:#STEP[-2][1]'], 'idle_timeout': '0', 'dl_type':'0x800',
+ 'nw_proto':'17', 'tp_dst':'0', 'priority': '33000'}],
+ ['vswitch', 'add_flow', 'br0', {'in_port': '#STEP[-4][1]', 'actions': ['output:1'],
+ 'idle_timeout': '0'}],
+ ['vswitch', 'add_flow', 'br0', {'in_port': '#STEP[-4][1]', 'actions': ['output:2'],
+ 'idle_timeout': '0'}],
+ ['vswitch', 'dump_flows', 'br0'],
+ ['vnf1', 'start'],
+ ]
+ },
+
+To run the test:
+
+ .. code-block:: console
+
+ ./vsperf --conf-file user_settings.py pvvp_pvp_cont
+
diff --git a/testcases/integration.py b/testcases/integration.py
index bec38624..4b9dacfd 100644
--- a/testcases/integration.py
+++ b/testcases/integration.py
@@ -28,9 +28,6 @@ from tools import veth
from tools.teststepstools import TestStepsTools
from core.loader import Loader
-CHECK_PREFIX = 'validate_'
-
-
class IntegrationTestCase(TestCase):
"""IntegrationTestCase class
"""
@@ -41,193 +38,20 @@ class IntegrationTestCase(TestCase):
self._type = 'integration'
super(IntegrationTestCase, self).__init__(cfg)
self._logger = logging.getLogger(__name__)
- self._inttest = None
-
- def report_status(self, label, status):
- """ Log status of test step
- """
- self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
-
- def run_initialize(self):
- """ Prepare test execution environment
- """
- super(IntegrationTestCase, self).run_initialize()
- self._inttest = {'status' : True, 'details' : ''}
-
- def run(self):
- """Run the test
-
- All setup and teardown through controllers is included.
- """
- def eval_step_params(params, step_result):
- """ Evaluates referrences to results from previous steps
- """
- def eval_param(param, STEP):
- # pylint: disable=invalid-name
- """ Helper function
- """
- if isinstance(param, str):
- # evaluate every #STEP reference inside parameter itself
- macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
- if macros:
- for macro in macros:
- # pylint: disable=eval-used
- tmp_val = str(eval(macro[1:]))
- param = param.replace(macro, tmp_val)
- return param
- elif isinstance(param, list) or isinstance(param, tuple):
- tmp_list = []
- for item in param:
- tmp_list.append(eval_param(item, STEP))
- return tmp_list
- elif isinstance(param, dict):
- tmp_dict = {}
- for (key, value) in param.items():
- tmp_dict[key] = eval_param(value, STEP)
- return tmp_dict
- else:
- return param
-
- eval_params = []
- # evaluate all parameters if needed
- for param in params:
- eval_params.append(eval_param(param, step_result))
- return eval_params
-
- # prepare test execution environment
- self.run_initialize()
-
- try:
- with self._vswitch_ctl, self._loadgen:
- with self._vnf_ctl, self._collector:
- if not self._vswitch_none:
- self._add_flows()
-
- # run traffic generator if requested, otherwise wait for manual termination
- if S.getValue('mode') == 'trafficgen-off':
- time.sleep(2)
- self._logger.debug("All is set. Please run traffic generator manually.")
- input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
- else:
- with self._traffic_ctl:
- if not self.test:
- self._traffic_ctl.send_traffic(self._traffic)
- else:
- vnf_list = {}
- loader = Loader()
- # execute test based on TestSteps definition
- if self.test:
- # initialize list with results
- step_result = [None] * len(self.test)
-
- # count how many VNFs are involved in the test
- for step in self.test:
- if step[0].startswith('vnf'):
- vnf_list[step[0]] = None
-
- # check/expand GUEST configuration and copy data to shares
- if len(vnf_list):
- S.check_vm_settings(len(vnf_list))
- self._copy_fwd_tools_for_all_guests(len(vnf_list))
-
- # run test step by step...
- for i, step in enumerate(self.test):
- step_ok = False
- if step[0] == 'vswitch':
- test_object = self._vswitch_ctl.get_vswitch()
- elif step[0] == 'namespace':
- test_object = namespace
- elif step[0] == 'veth':
- test_object = veth
- elif step[0] == 'settings':
- test_object = S
- elif step[0] == 'tools':
- test_object = TestStepsTools()
- step[1] = step[1].title()
- elif step[0] == 'trafficgen':
- test_object = self._traffic_ctl
- # in case of send_traffic method, ensure that specified
- # traffic values are merged with existing self._traffic
- if step[1] == 'send_traffic':
- tmp_traffic = copy.deepcopy(self._traffic)
- tmp_traffic.update(step[2])
- step[2] = tmp_traffic
- elif step[0].startswith('vnf'):
- if not vnf_list[step[0]]:
- # initialize new VM
- vnf_list[step[0]] = loader.get_vnf_class()()
- test_object = vnf_list[step[0]]
- elif step[0] == 'wait':
- input(os.linesep + "Step {}: Press Enter to continue with "
- "the next step...".format(i) + os.linesep + os.linesep)
- continue
- else:
- self._logger.error("Unsupported test object %s", step[0])
- self._inttest = {'status' : False, 'details' : ' '.join(step)}
- self.report_status("Step '{}'".format(' '.join(step)),
- self._inttest['status'])
- break
-
- test_method = getattr(test_object, step[1])
- test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
-
- step_params = []
- if test_method and test_method_check and \
- callable(test_method) and callable(test_method_check):
-
- try:
- # eval parameters, but use only valid step_results
- # to support negative indexes
- step_params = eval_step_params(step[2:], step_result[:i])
- step_log = '{} {}'.format(' '.join(step[:2]), step_params)
- step_result[i] = test_method(*step_params)
- self._logger.debug("Step %s '%s' results '%s'", i,
- step_log, step_result[i])
- time.sleep(5)
- step_ok = test_method_check(step_result[i], *step_params)
- except AssertionError:
- self._inttest = {'status' : False, 'details' : step_log}
- self._logger.error("Step %s raised assertion error", i)
- # stop vnfs in case of error
- for vnf in vnf_list:
- vnf_list[vnf].stop()
- break
- except IndexError:
- self._inttest = {'status' : False, 'details' : step_log}
- self._logger.error("Step %s result index error %s", i,
- ' '.join(step[2:]))
- # stop vnfs in case of error
- for vnf in vnf_list:
- vnf_list[vnf].stop()
- break
-
- self.report_status("Step {} - '{}'".format(i, step_log), step_ok)
- if not step_ok:
- self._inttest = {'status' : False, 'details' : step_log}
- # stop vnfs in case of error
- for vnf in vnf_list:
- vnf_list[vnf].stop()
- break
-
- # dump vswitch flows before they are affected by VNF termination
- if not self._vswitch_none:
- self._vswitch_ctl.dump_vswitch_flows()
- finally:
- # tear down test execution environment and log results
- self.run_finalize()
-
- # report test results
- self.run_report()
+ # enforce check of step result for step driven testcases
+ self._step_check = True
def run_report(self):
""" Report test results
"""
if self.test:
results = OrderedDict()
- results['status'] = 'OK' if self._inttest['status'] else 'FAILED'
- results['details'] = self._inttest['details']
+ results['status'] = 'OK' if self._step_status['status'] else 'FAILED'
+ results['details'] = self._step_status['details']
TestCase.write_result_to_file([results], self._output_file)
- self.report_status("Test '{}'".format(self.name), self._inttest['status'])
+ self.step_report_status("Test '{}'".format(self.name), self._step_status['status'])
# inform vsperf about testcase failure
- if not self._inttest['status']:
+ if not self._step_status['status']:
raise Exception
+ else:
+ super(IntegrationTestCase, self).run_report()
diff --git a/testcases/performance.py b/testcases/performance.py
index a4769a28..240d04a9 100644
--- a/testcases/performance.py
+++ b/testcases/performance.py
@@ -34,5 +34,5 @@ class PerformanceTestCase(TestCase):
def run_report(self):
super(PerformanceTestCase, self).run_report()
- if S.getValue('mode') != 'trafficgen-off':
+ if self._tc_results:
report.generate(self._output_file, self._tc_results, self._collector.get_results(), self._type)
diff --git a/testcases/testcase.py b/testcases/testcase.py
index 7f22c18f..00164ea2 100644
--- a/testcases/testcase.py
+++ b/testcases/testcase.py
@@ -34,6 +34,7 @@ from tools import hugepages
from tools import functions
from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
+CHECK_PREFIX = 'validate_'
class TestCase(object):
"""TestCase base class
@@ -60,6 +61,11 @@ class TestCase(object):
self._settings_original = {}
self._settings_paths_modified = False
self._testcast_run_time = None
+ # initialization of step driven specific members
+ self._step_check = False # by default don't check result for step driven testcases
+ self._step_vnf_list = {}
+ self._step_result = []
+ self._step_status = None
# store all GUEST_ specific settings to keep original values before their expansion
for key in S.__dict__:
@@ -170,6 +176,12 @@ class TestCase(object):
else:
self._logger.debug("MAC addresses can not be read")
+ # count how many VNFs are involved in TestSteps
+ if self.test:
+ for step in self.test:
+ if step[0].startswith('vnf'):
+ self._step_vnf_list[step[0]] = None
+
def run_initialize(self):
""" Prepare test execution environment
"""
@@ -186,26 +198,31 @@ class TestCase(object):
self._vnf_ctl = component_factory.create_vnf(
self.deployment,
- loader.get_vnf_class())
+ loader.get_vnf_class(),
+ len(self._step_vnf_list))
# verify enough hugepages are free to run the testcase
if not self._check_for_enough_hugepages():
raise RuntimeError('Not enough hugepages free to run test.')
# perform guest related handling
- if self._vnf_ctl.get_vnfs_number():
+ tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
+ if tmp_vm_count:
# copy sources of l2 forwarding tools into VM shared dir if needed
- self._copy_fwd_tools_for_all_guests(self._vnf_ctl.get_vnfs_number())
+ self._copy_fwd_tools_for_all_guests(tmp_vm_count)
# in case of multi VM in parallel, set the number of streams to the number of VMs
if self.deployment.startswith('pvpv'):
# for each VM NIC pair we need an unique stream
streams = 0
- for vm_nic in S.getValue('GUEST_NICS_NR')[:self._vnf_ctl.get_vnfs_number()]:
+ for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
streams += int(vm_nic / 2) if vm_nic > 1 else 1
self._logger.debug("VMs with parallel connection were detected. "
"Thus Number of streams was set to %s", streams)
- self._traffic.update({'multistream': streams})
+ # update streams if needed; In case of additional VNFs deployed by TestSteps
+ # user can define a proper stream count manually
+ if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
+ self._traffic.update({'multistream': streams})
# OVS Vanilla requires guest VM MAC address and IPs to work
if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
@@ -235,11 +252,16 @@ class TestCase(object):
self._output_file = os.path.join(self._results_dir, "result_" + self.name +
"_" + self.deployment + ".csv")
+ self._step_status = {'status' : True, 'details' : ''}
+
self._logger.debug("Setup:")
def run_finalize(self):
""" Tear down test execution environment and record test results
"""
+ # Stop all VNFs started by TestSteps in case that something went wrong
+ self.step_stop_vnfs()
+
# umount hugepages if mounted
self._umount_hugepages()
@@ -272,11 +294,12 @@ class TestCase(object):
self._logger.debug("self._collector Results:")
self._collector.print_results()
- if S.getValue('mode') != 'trafficgen-off':
+ results = self._traffic_ctl.get_results()
+ if results:
self._logger.debug("Traffic Results:")
self._traffic_ctl.print_results()
- self._tc_results = self._append_results(self._traffic_ctl.get_results())
+ self._tc_results = self._append_results(results)
TestCase.write_result_to_file(self._tc_results, self._output_file)
def run(self):
@@ -287,36 +310,31 @@ class TestCase(object):
# prepare test execution environment
self.run_initialize()
- with self._vswitch_ctl, self._loadgen:
- with self._vnf_ctl, self._collector:
- if not self._vswitch_none:
- self._add_flows()
+ try:
+ with self._vswitch_ctl, self._loadgen:
+ with self._vnf_ctl, self._collector:
+ if not self._vswitch_none:
+ self._add_flows()
- # run traffic generator if requested, otherwise wait for manual termination
- if S.getValue('mode') == 'trafficgen-off':
- time.sleep(2)
- self._logger.debug("All is set. Please run traffic generator manually.")
- input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
- else:
- if S.getValue('mode') == 'trafficgen-pause':
- time.sleep(2)
- true_vals = ('yes', 'y', 'ye', None)
- while True:
- choice = input(os.linesep + 'Transmission paused, should'
- ' transmission be resumed? ' + os.linesep).lower()
- if not choice or choice not in true_vals:
- print('Please respond with \'yes\' or \'y\' ', end='')
- else:
- break
with self._traffic_ctl:
- self._traffic_ctl.send_traffic(self._traffic)
+ # execute test based on TestSteps definition if needed...
+ if self.step_run():
+ # ...and continue with traffic generation, but keep
+ # in mind, that clean deployment does not configure
+ # OVS nor executes the traffic
+ if self.deployment != 'clean':
+ self._traffic_ctl.send_traffic(self._traffic)
- # dump vswitch flows before they are affected by VNF termination
- if not self._vswitch_none:
- self._vswitch_ctl.dump_vswitch_flows()
+ # dump vswitch flows before they are affected by VNF termination
+ if not self._vswitch_none:
+ self._vswitch_ctl.dump_vswitch_flows()
- # tear down test execution environment and log results
- self.run_finalize()
+ # garbage collection for case that TestSteps modify existing deployment
+ self.step_stop_vnfs()
+
+ finally:
+ # tear down test execution environment and log results
+ self.run_finalize()
self._testcase_run_time = time.strftime("%H:%M:%S",
time.gmtime(time.time() - self._testcase_start_time))
@@ -635,3 +653,143 @@ class TestCase(object):
vswitch.add_flow(bridge, flow)
else:
pass
+
+
+ #
+ # TestSteps realted methods
+ #
+ def step_report_status(self, label, status):
+ """ Log status of test step
+ """
+ self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
+
+ def step_stop_vnfs(self):
+ """ Stop all VNFs started by TestSteps
+ """
+ for vnf in self._step_vnf_list:
+ self._step_vnf_list[vnf].stop()
+
+ @staticmethod
+ def step_eval_param(param, STEP):
+ # pylint: disable=invalid-name
+ """ Helper function for #STEP macro evaluation
+ """
+ if isinstance(param, str):
+ # evaluate every #STEP reference inside parameter itself
+ macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
+ if macros:
+ for macro in macros:
+ # pylint: disable=eval-used
+ tmp_val = str(eval(macro[1:]))
+ param = param.replace(macro, tmp_val)
+ return param
+ elif isinstance(param, list) or isinstance(param, tuple):
+ tmp_list = []
+ for item in param:
+ tmp_list.append(TestCase.step_eval_param(item, STEP))
+ return tmp_list
+ elif isinstance(param, dict):
+ tmp_dict = {}
+ for (key, value) in param.items():
+ tmp_dict[key] = TestCase.step_eval_param(value, STEP)
+ return tmp_dict
+ else:
+ return param
+
+ @staticmethod
+ def step_eval_params(params, step_result):
+ """ Evaluates referrences to results from previous steps
+ """
+ eval_params = []
+ # evaluate all parameters if needed
+ for param in params:
+ eval_params.append(TestCase.step_eval_param(param, step_result))
+ return eval_params
+
+ def step_run(self):
+ """ Execute actions specified by TestSteps list
+
+ :return: False if any error was detected
+ True otherwise
+ """
+ # anything to do?
+ if not self.test:
+ return True
+
+ # required for VNFs initialization
+ loader = Loader()
+ # initialize list with results
+ self._step_result = [None] * len(self.test)
+
+ # run test step by step...
+ for i, step in enumerate(self.test):
+ step_ok = not self._step_check
+ if step[0] == 'vswitch':
+ test_object = self._vswitch_ctl.get_vswitch()
+ elif step[0] == 'namespace':
+ test_object = namespace
+ elif step[0] == 'veth':
+ test_object = veth
+ elif step[0] == 'settings':
+ test_object = S
+ elif step[0] == 'tools':
+ test_object = TestStepsTools()
+ step[1] = step[1].title()
+ elif step[0] == 'trafficgen':
+ test_object = self._traffic_ctl
+ # in case of send_traffic or send_traffic_async methods, ensure
+ # that specified traffic values are merged with existing self._traffic
+ if step[1].startswith('send_traffic'):
+ tmp_traffic = copy.deepcopy(self._traffic)
+ tmp_traffic.update(step[2])
+ step[2] = tmp_traffic
+ elif step[0].startswith('vnf'):
+ if not self._step_vnf_list[step[0]]:
+ # initialize new VM
+ self._step_vnf_list[step[0]] = loader.get_vnf_class()()
+ test_object = self._step_vnf_list[step[0]]
+ elif step[0] == 'wait':
+ input(os.linesep + "Step {}: Press Enter to continue with "
+ "the next step...".format(i) + os.linesep + os.linesep)
+ continue
+ else:
+ self._logger.error("Unsupported test object %s", step[0])
+ self._step_status = {'status' : False, 'details' : ' '.join(step)}
+ self.step_report_status("Step '{}'".format(' '.join(step)),
+ self._step_status['status'])
+ return False
+
+ test_method = getattr(test_object, step[1])
+ if self._step_check:
+ test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
+ else:
+ test_method_check = None
+
+ step_params = []
+ try:
+ # eval parameters, but use only valid step_results
+ # to support negative indexes
+ step_params = TestCase.step_eval_params(step[2:], self._step_result[:i])
+ step_log = '{} {}'.format(' '.join(step[:2]), step_params)
+ self._logger.debug("Step %s '%s' start", i, step_log)
+ self._step_result[i] = test_method(*step_params)
+ self._logger.debug("Step %s '%s' results '%s'", i,
+ step_log, self._step_result[i])
+ time.sleep(5)
+ if self._step_check:
+ step_ok = test_method_check(self._step_result[i], *step_params)
+ except (AssertionError, AttributeError, IndexError) as ex:
+ step_ok = False
+ self._logger.error("Step %s raised %s", i, type(ex).__name__)
+
+ if self._step_check:
+ self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
+
+ if not step_ok:
+ self._step_status = {'status' : False, 'details' : step_log}
+ # Stop all VNFs started by TestSteps
+ self.step_stop_vnfs()
+ return False
+
+ # all steps processed without any issue
+ return True
diff --git a/vnfs/qemu/qemu.py b/vnfs/qemu/qemu.py
index 87126da8..51553277 100644
--- a/vnfs/qemu/qemu.py
+++ b/vnfs/qemu/qemu.py
@@ -151,27 +151,28 @@ class IVnfQemu(IVnf):
"""
Stops VNF instance gracefully first.
"""
- try:
- # exit testpmd if needed
- if self._guest_loopback == 'testpmd':
- self.execute_and_wait('stop', 120, "Done")
- self.execute_and_wait('quit', 120, "[bB]ye")
-
- # turn off VM
- self.execute_and_wait('poweroff', 120, "Power down")
-
- except pexpect.TIMEOUT:
- self.kill()
-
- # wait until qemu shutdowns
- self._logger.debug('Wait for QEMU to terminate')
- for dummy in range(30):
- time.sleep(1)
- if not self.is_running():
- break
-
- # just for case that graceful shutdown failed
- super(IVnfQemu, self).stop()
+ if self.is_running():
+ try:
+ # exit testpmd if needed
+ if self._guest_loopback == 'testpmd':
+ self.execute_and_wait('stop', 120, "Done")
+ self.execute_and_wait('quit', 120, "[bB]ye")
+
+ # turn off VM
+ self.execute_and_wait('poweroff', 120, "Power down")
+
+ except pexpect.TIMEOUT:
+ self.kill()
+
+ # wait until qemu shutdowns
+ self._logger.debug('Wait for QEMU to terminate')
+ for dummy in range(30):
+ time.sleep(1)
+ if not self.is_running():
+ break
+
+ # just for case that graceful shutdown failed
+ super(IVnfQemu, self).stop()
# helper functions