Merge "paths: Modify algorithm for PATHS verification"
authorMartin Klozik <martinx.klozik@intel.com>
Mon, 20 Nov 2017 09:30:10 +0000 (09:30 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Mon, 20 Nov 2017 09:30:10 +0000 (09:30 +0000)
25 files changed:
3rd_party/ixia/ixnetrfc2544.tcl
conf/02_vswitch.conf
conf/03_traffic.conf
conf/04_vnf.conf
conf/__init__.py
conf/integration/02_vswitch.conf
conf/integration/03_traffic.conf
core/traffic_controller.py
core/traffic_controller_rfc2544.py
core/traffic_controller_rfc2889.py
docs/testing/developer/devguide/design/vswitchperf_design.rst
docs/testing/user/userguide/integration.rst
docs/testing/user/userguide/teststeps.rst
testcases/testcase.py
tools/functions.py
tools/pkt_gen/ixnet/ixnet.py
tools/teststepstools.py
vnfs/qemu/qemu.py
vnfs/qemu/qemu_dpdk_vhost_user.py
vnfs/vnf/vnf.py
vsperf
vswitches/ovs.py
vswitches/ovs_dpdk_vhost.py
vswitches/vpp_dpdk_vhost.py
vswitches/vswitch.py

index 73e6d78..c47e8fc 100644 (file)
@@ -113,7 +113,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
         set loadType            custom
     }
 
-    set learningFrames          True
+    set learningFrames          [dict get $testSpec learningFrames]
 
     set L2CountValue            1
     set L2Increment             False
@@ -150,6 +150,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
         }
     }
 
+    set flowControl             [dict get $testSpec flowControl]
     set fastConvergence         True
     set convergenceDuration     [expr $duration/10]
 
@@ -403,7 +404,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
      -txIgnoreRxLinkFaults False \
      -loopback False \
      -enableLASIMonitoring False \
-     -enabledFlowControl True
+     -enabledFlowControl $flowControl
     ixNet setMultiAttrs $sg_vport/l1Config/tenGigLan/oam \
      -tlvType {00} \
      -linkEvents False \
@@ -432,7 +433,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
      -txIgnoreRxLinkFaults False \
      -loopback False \
      -enableLASIMonitoring False \
-     -enabledFlowControl False
+     -enabledFlowControl $flowControl
     ixNet setMultiAttrs $sg_vport/l1Config/fortyGigLan/fcoe \
      -supportDataCenterMode False \
      -priorityGroupSize priorityGroupSize-8 \
@@ -784,7 +785,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
          -txIgnoreRxLinkFaults False \
          -loopback False \
          -enableLASIMonitoring False \
-         -enabledFlowControl False
+         -enabledFlowControl $flowControl
         ixNet setMultiAttrs $sg_vport/l1Config/tenGigLan/oam \
          -tlvType {00} \
          -linkEvents False \
@@ -813,7 +814,7 @@ proc startRfc2544Test { testSpec trafficSpec } {
          -txIgnoreRxLinkFaults False \
          -loopback False \
          -enableLASIMonitoring False \
-         -enabledFlowControl False
+         -enabledFlowControl $flowControl
         ixNet setMultiAttrs $sg_vport/l1Config/fortyGigLan/fcoe \
          -supportDataCenterMode False \
          -priorityGroupSize priorityGroupSize-8 \
index 92521f9..6a830a0 100644 (file)
@@ -206,6 +206,7 @@ VSWITCH_JUMBO_FRAMES_SIZE = 9000
 #########################
 # Set of arguments used for startup of VPP
 # NOTE: DPDK socket mem allocation is driven by parameter DPDK_SOCKET_MEM
+VSWITCH_VPP_CLI_SOCK = ''
 VSWITCH_VPP_ARGS = {
     'unix' : [
         'interactive',      # required by VSPERF to detect successful VPP startup
index c16b0d6..a88e4bc 100644 (file)
@@ -71,6 +71,18 @@ LOG_FILE_TRAFFIC_GEN = 'traffic-gen.log'
 #                         "IP"   - flow is defined by ingress ports
 #                                  and src and dst IP addresses
 #                      Default value: "port"
+#    'flow_control'  - Controls flow control support by traffic generator.
+#                      Supported values:
+#                         False  - flow control is disabled
+#                         True   - flow control is enabled
+#                      Default value: False
+#                      Note: Currently it is supported by IxNet only
+#    'learning_frames' - Controls learning frames support by traffic generator.
+#                      Supported values:
+#                         False  - learning freames are disabled
+#                         True   - learning freames are enabled
+#                      Default value: True
+#                      Note: Currently it is supported by IxNet only
 #    'l2'            - A dictionary with l2 network layer details. Supported
 #                      values are:
 #        'srcmac'    - Specifies source MAC address filled by traffic generator.
@@ -143,7 +155,8 @@ TRAFFIC = {
     'stream_type' : 'L4',
     'pre_installed_flows' : 'No',           # used by vswitch implementation
     'flow_type' : 'port',                   # used by vswitch implementation
-
+    'flow_control' : False,                 # supported only by IxNet
+    'learning_frames' : True,               # supported only by IxNet
     'l2': {
         'framesize': 64,
         'srcmac': '00:00:00:00:00:00',
index eafec74..37fbe2b 100644 (file)
@@ -96,6 +96,8 @@ GUEST_SHARED_DRIVE_TYPE = ['scsi']
 #       'linux_bridge'  - linux bridge will be configured
 #       'buildin'       - nothing will be configured by vsperf; VM image must
 #                         ensure traffic forwarding between its interfaces
+#       'clean'         - nothing will be configured, but automatic login will
+#                         be performed; Useful for stepdriven testcases.
 # For 2 VNFs you may use ['testpmd', 'l2fwd']
 GUEST_LOOPBACK = ['testpmd']
 
index 808cfc9..a7c0ee5 100644 (file)
@@ -189,6 +189,18 @@ class Settings(object):
                 else:
                     setattr(self, key.upper(), conf[key])
 
+    def restore_from_dict(self, conf):
+        """
+        Restore ``settings`` with values found in ``conf``.
+
+        Method will drop all configuration options and restore their
+        values from conf dictionary
+        """
+        self.__dict__.clear()
+        tmp_conf = copy.deepcopy(conf)
+        for key in tmp_conf:
+            self.setValue(key, tmp_conf[key])
+
     def load_from_env(self):
         """
         Update ``settings`` with values found in the environment.
index 68eaf73..63ffe1b 100644 (file)
@@ -23,12 +23,6 @@ TUNNEL_EXTERNAL_BRIDGE_IP = '192.168.240.1/24'
 # vxlan|gre|geneve
 TUNNEL_TYPE = 'vxlan'
 
-# The receiving NIC of VXLAN traffic
-# Used for OVS Vanilla Decap
-DUT_NIC1_MAC = ''
-# Used for OVS DPDK Decap
-DUT_NIC2_MAC = ''
-
 #Tunnel bridge configuration for P-TUN-P(VxLAN) deployment scenario
 # to test VxLAN performance without any overlay ingress traffic by doing the
 # encap decap inside the virtual switch itself.
index e78e266..5126f51 100644 (file)
@@ -29,7 +29,7 @@ VXLAN_VNI = '99'
 #                  '00:1b:21:b3:48:a9'}
 
 VXLAN_FRAME_L2 = {'srcmac': '01:02:03:04:05:06',
-                  'dstmac': DUT_NIC2_MAC,
+                  'dstmac': '06:05:04:03:02:01',
                  }
 
 # VXLAN is supported both in IxNetwork and IXIA IxExplorer
@@ -71,7 +71,7 @@ VXLAN_FRAME_L4 = {'srcport': 4789,
 # TEST frame
 # dstmac should be set to the MAC address of the DUT's receiving port
 GRE_FRAME_L2 = {'srcmac': '01:02:03:04:05:06',
-                'dstmac': DUT_NIC2_MAC,
+                'dstmac': '06:05:04:03:02:01',
                }
 
 GRE_FRAME_L3 = {'proto': 'gre',
@@ -95,7 +95,7 @@ GRE_FRAME_L4 = {'srcport': 0,
 # TEST frame
 # dstmac should be set to the MAC address of the DUT's receiving port
 GENEVE_FRAME_L2 = {'srcmac': '01:02:03:04:05:06',
-                   'dstmac': DUT_NIC2_MAC,
+                   'dstmac': '06:05:04:03:02:01',
                   }
 
 GENEVE_FRAME_L3 = {'proto': 'udp',
index 5ebdc0d..d6e7629 100644 (file)
@@ -40,13 +40,25 @@ class TrafficController(object):
         self._traffic_gen_class = traffic_gen_class()
         self._traffic_started = False
         self._traffic_started_call_count = 0
+        self._duration = None
+        self._lossrate = None
+        self._packet_sizes = None
+
+        self._mode = str(settings.getValue('mode')).lower()
+        self._results = []
+
+    def configure(self, traffic):
+        """Set configuration values just before test execution so they
+           can be changed during runtime by test steps.
+        """
         self._duration = int(settings.getValue('TRAFFICGEN_DURATION'))
         self._lossrate = float(settings.getValue('TRAFFICGEN_LOSSRATE'))
         self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES')
-
-        self._mode = str(settings.getValue('mode')).lower()
         self._results = []
 
+        # update type with detailed traffic value
+        self._type = traffic['traffic_type']
+
     def __enter__(self):
         """Call initialisation function.
         """
@@ -101,18 +113,18 @@ class TrafficController(object):
                     print("Please respond with 'yes', 'y', 'no' or 'n' ", end='')
         return True
 
-    def send_traffic(self, dummy_traffic):
+    def send_traffic(self, traffic):
         """Triggers traffic to be sent from the traffic generator.
 
         This is a blocking function.
 
         :param traffic: A dictionary describing the traffic to send.
         """
-        raise NotImplementedError(
-            "The TrafficController does not implement",
-            "the \"send_traffic\" function.")
+        self._logger.debug('send_traffic with ' +
+                           str(self._traffic_gen_class))
+        self.configure(traffic)
 
-    def send_traffic_async(self, dummy_traffic, dummy_function):
+    def send_traffic_async(self, traffic, dummy_function):
         """Triggers traffic to be sent  asynchronously.
 
         This is not a blocking function.
@@ -127,9 +139,9 @@ class TrafficController(object):
              If this function requires more than one argument, all should be
              should be passed using the args list and appropriately handled.
          """
-        raise NotImplementedError(
-            "The TrafficController does not implement",
-            "the \"send_traffic_async\" function.")
+        self._logger.debug('send_traffic_async with ' +
+                           str(self._traffic_gen_class))
+        self.configure(traffic)
 
     def stop_traffic(self):
         """Kills traffic being sent from the traffic generator.
@@ -155,7 +167,7 @@ class TrafficController(object):
     def validate_send_traffic(self, dummy_result, dummy_traffic):
         """Verify that send traffic has succeeded
         """
-        if len(self._results):
+        if 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]:
@@ -164,3 +176,8 @@ class TrafficController(object):
                 return True
         else:
             return False
+
+    def validate_get_results(self, result):
+        """Verify that results has been returned
+        """
+        return self._results == result
index cb83951..488dde6 100644 (file)
@@ -30,8 +30,14 @@ class TrafficControllerRFC2544(TrafficController, IResults):
 
         :param traffic_gen_class: The traffic generator class to be used.
         """
-        super(TrafficControllerRFC2544, self).__init__(traffic_gen_class)
+        super().__init__(traffic_gen_class)
         self._type = 'rfc2544'
+        self._tests = None
+
+    def configure(self, traffic):
+        """See TrafficController for description
+        """
+        super().configure(traffic)
         self._tests = int(settings.getValue('TRAFFICGEN_RFC2544_TESTS'))
 
     def send_traffic(self, traffic):
@@ -39,11 +45,8 @@ class TrafficControllerRFC2544(TrafficController, IResults):
         """
         if not self.traffic_required():
             return
-        self._logger.debug('send_traffic with ' +
-                           str(self._traffic_gen_class))
 
-        # update type with detailed traffic value
-        self._type = traffic['traffic_type']
+        super().send_traffic(traffic)
 
         for packet_size in self._packet_sizes:
             # Merge framesize with the default traffic definition
@@ -74,11 +77,8 @@ class TrafficControllerRFC2544(TrafficController, IResults):
         """
         if not self.traffic_required():
             return
-        self._logger.debug('send_traffic_async with ' +
-                           str(self._traffic_gen_class))
 
-        # update type with detailed traffic value
-        self._type = traffic['traffic_type']
+        super().send_traffic_async(traffic, function)
 
         for packet_size in self._packet_sizes:
             traffic['l2'] = {'framesize': packet_size}
index 01aaa72..64ab0ba 100644 (file)
@@ -30,8 +30,14 @@ class TrafficControllerRFC2889(TrafficController, IResults):
 
         :param traffic_gen_class: The traffic generator class to be used.
         """
-        super(TrafficControllerRFC2889, self).__init__(traffic_gen_class)
+        super().__init__(traffic_gen_class)
         self._type = 'rfc2889'
+        self._trials = None
+
+    def configure(self, traffic):
+        """See TrafficController for description
+        """
+        super().configure(traffic)
         self._trials = int(settings.getValue('TRAFFICGEN_RFC2889_TRIALS'))
 
     def send_traffic(self, traffic):
@@ -39,11 +45,8 @@ class TrafficControllerRFC2889(TrafficController, IResults):
         """
         if not self.traffic_required():
             return
-        self._logger.debug('send_traffic with ' +
-                           str(self._traffic_gen_class))
 
-        # update type with detailed traffic value
-        self._type = traffic['traffic_type']
+        super().send_traffic(traffic)
 
         for packet_size in self._packet_sizes:
             # Merge framesize with the default traffic definition
@@ -71,11 +74,8 @@ class TrafficControllerRFC2889(TrafficController, IResults):
         """
         if not self.traffic_required():
             return
-        self._logger.debug('send_traffic_async with ' +
-                           str(self._traffic_gen_class))
 
-        # update type with detailed traffic value
-        self._type = traffic['traffic_type']
+        super().send_traffic_async(traffic, function)
 
         for packet_size in self._packet_sizes:
             traffic['l2'] = {'framesize': packet_size}
index 55614d3..3305149 100644 (file)
@@ -339,6 +339,18 @@ Detailed description of ``TRAFFIC`` dictionary items follows:
                          "IP"   - flow is defined by ingress ports
                                   and src and dst IP addresses
                       Default value: "port"
+    'flow_control'  - Controls flow control support by traffic generator.
+                      Supported values:
+                         False  - flow control is disabled
+                         True   - flow control is enabled
+                      Default value: False
+                      Note: Currently it is supported by IxNet only
+    'learning_frames' - Controls learning frames support by traffic generator.
+                      Supported values:
+                         False  - learning frames are disabled
+                         True   - learning frames are enabled
+                      Default value: True
+                      Note: Currently it is supported by IxNet only
     'l2'            - A dictionary with l2 network layer details. Supported
                       values are:
         'srcmac'    - Specifies source MAC address filled by traffic generator.
index 249a03c..6680840 100644 (file)
@@ -140,13 +140,7 @@ To run VXLAN decapsulation tests:
 
 1. Set the variables used in "Executing Tunnel encapsulation tests"
 
-2. Set dstmac of DUT_NIC2_MAC to the MAC adddress of the 2nd NIC of your DUT
-
-  .. code-block:: python
-
-    DUT_NIC2_MAC = '<DUT NIC2 MAC>'
-
-3. Run test:
+2. Run test:
 
   .. code-block:: console
 
@@ -181,13 +175,7 @@ To run GRE decapsulation tests:
 
 1. Set the variables used in "Executing Tunnel encapsulation tests"
 
-2. Set dstmac of DUT_NIC2_MAC to the MAC adddress of the 2nd NIC of your DUT
-
-  .. code-block:: python
-
-    DUT_NIC2_MAC = '<DUT NIC2 MAC>'
-
-3. Run test:
+2. Run test:
 
   .. code-block:: console
 
@@ -238,13 +226,7 @@ To run GENEVE decapsulation tests:
 
 1. Set the variables used in "Executing Tunnel encapsulation tests"
 
-2. Set dstmac of DUT_NIC2_MAC to the MAC adddress of the 2nd NIC of your DUT
-
-  .. code-block:: python
-
-    DUT_NIC2_MAC = '<DUT NIC2 MAC>'
-
-3. Run test:
+2. Run test:
 
   .. code-block:: console
 
@@ -289,8 +271,6 @@ To run VXLAN decapsulation tests:
         'datapath/linux/openvswitch.ko',
     ]
 
-    DUT_NIC1_MAC = '<DUT NIC1 MAC ADDRESS>'
-
     TRAFFICGEN_PORT1_IP = '172.16.1.2'
     TRAFFICGEN_PORT2_IP = '192.168.1.11'
 
@@ -302,7 +282,8 @@ To run VXLAN decapsulation tests:
 
     VXLAN_FRAME_L2 = {'srcmac':
                       '01:02:03:04:05:06',
-                      'dstmac': DUT_NIC1_MAC
+                      'dstmac':
+                      '06:05:04:03:02:01',
                      }
 
     VXLAN_FRAME_L3 = {'proto': 'udp',
@@ -349,8 +330,6 @@ To run GRE decapsulation tests:
         'datapath/linux/openvswitch.ko',
     ]
 
-    DUT_NIC1_MAC = '<DUT NIC1 MAC ADDRESS>'
-
     TRAFFICGEN_PORT1_IP = '172.16.1.2'
     TRAFFICGEN_PORT2_IP = '192.168.1.11'
 
@@ -362,7 +341,8 @@ To run GRE decapsulation tests:
 
     GRE_FRAME_L2 = {'srcmac':
                     '01:02:03:04:05:06',
-                    'dstmac': DUT_NIC1_MAC
+                    'dstmac':
+                    '06:05:04:03:02:01',
                    }
 
     GRE_FRAME_L3 = {'proto': 'udp',
@@ -408,8 +388,6 @@ To run GENEVE decapsulation tests:
         'datapath/linux/openvswitch.ko',
     ]
 
-    DUT_NIC1_MAC = '<DUT NIC1 MAC ADDRESS>'
-
     TRAFFICGEN_PORT1_IP = '172.16.1.2'
     TRAFFICGEN_PORT2_IP = '192.168.1.11'
 
@@ -421,7 +399,8 @@ To run GENEVE decapsulation tests:
 
     GENEVE_FRAME_L2 = {'srcmac':
                        '01:02:03:04:05:06',
-                       'dstmac': DUT_NIC1_MAC
+                       'dstmac':
+                       '06:05:04:03:02:01',
                       }
 
     GENEVE_FRAME_L3 = {'proto': 'udp',
index 5349d2e..8be6731 100644 (file)
@@ -29,6 +29,14 @@ does not pass validation the test will fail and terminate. The test will continu
 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.
 
+**NOTE**: It is possible to suppress validation process of given step by prefixing
+it by ``!`` (exclamation mark).
+In following example test execution won't fail if all traffic is dropped:
+
+.. code-block:: python
+
+    ['!trafficgen', 'send_traffic', {}]
+
 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.
@@ -75,6 +83,7 @@ of supported objects and their most common functions follows:
         * ``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``
+        * ``restart`` - restarts switch, which is useful for failover testcases
 
         Examples:
 
@@ -101,15 +110,26 @@ of supported objects and their most common functions follows:
 
         * ``start`` - starts a VNF based on VSPERF configuration
         * ``stop`` - gracefully terminates given VNF
+        * ``execute command [delay]`` - executes command `cmd` inside VNF; Optional
+          delay defines number of seconds to wait before next step is executed. Method
+          returns command output as a string.
+        * ``execute_and_wait command [timeout] [prompt]`` - executes command `cmd` inside
+          VNF; Optional timeout defines number of seconds to wait until ``prompt`` is detected.
+          Optional ``prompt`` defines a string, which is used as detection of successful command
+          execution. In case that prompt is not defined, then content of ``GUEST_PROMPT_LOGIN``
+          parameter will be used. Method returns command output as a string.
 
         Examples:
 
         .. code-block:: python
 
-            ['vnf1', 'start']
-            ['vnf2', 'start']
-            ['vnf2', 'stop']
-            ['vnf1', 'stop']
+            ['vnf1', 'start'],
+            ['vnf2', 'start'],
+            ['vnf1', 'execute_and_wait', 'ifconfig eth0 5.5.5.1/24 up'],
+            ['vnf2', 'execute_and_wait', 'ifconfig eth0 5.5.5.2/24 up', 120, 'root.*#'],
+            ['vnf2', 'execute_and_wait', 'ping -c1 5.5.5.1'],
+            ['vnf2', 'stop'],
+            ['vnf1', 'stop'],
 
     * ``trafficgen`` - triggers traffic generation
 
@@ -119,6 +139,8 @@ of supported objects and their most common functions follows:
           and given ``traffic`` dictionary. More details about ``traffic`` dictionary
           and its possible values are available at :ref:`Traffic Generator Integration Guide
           <step-5-supported-traffic-types>`
+        * ``get_results`` - returns dictionary with results collected from previous execution
+          of ``send_traffic``
 
         Examples:
 
@@ -126,7 +148,12 @@ of supported objects and their most common functions follows:
 
             ['trafficgen', 'send_traffic', {'traffic_type' : 'rfc2544_throughput'}]
 
-            ['trafficgen', 'send_traffic', {'traffic_type' : 'rfc2544_back2back', 'bidir' : 'True'}]
+            ['trafficgen', 'send_traffic', {'traffic_type' : 'rfc2544_back2back', 'bidir' : 'True'}],
+            ['trafficgen', 'get_results'],
+            ['tools', 'assert', '#STEP[-1][0]["frame_loss_percent"] < 0.05'],
+
+
+.. _step-driven-tests-variable-usage:
 
     * ``settings`` - reads or modifies VSPERF configuration
 
@@ -229,8 +256,7 @@ of supported objects and their most common functions follows:
           in case that condition is not ``True``
         * ``Eval expression`` - evaluates given expression as a python code and returns
           its result
-        * ``Exec_Shell command [regex]`` - executes a shell command and filters its output by
-          (optional) regular expression
+        * ``Exec_Shell command`` - executes a shell command
         * ``Exec_Python code`` - executes a python code
 
 
@@ -260,6 +286,23 @@ of supported objects and their most common functions follows:
 
         ['sleep', '60']
 
+    * ``log level message`` - is used to log ``message`` of given ``level`` into vsperf output.
+      Level is one of info, debug, warning or error.
+
+      Examples:
+
+      .. code-block:: python
+
+        ['log', 'error', 'tools $TOOLS']
+
+    * ``pdb`` - executes python debugger
+
+      Examples:
+
+      .. code-block:: python
+
+        ['pdb']
+
 Test Macros
 -----------
 
@@ -296,6 +339,15 @@ functionality:
 
                 ['vswitch', 'del_port', 'int_br0', '#STEP[-1][0]'],  # STEP 2
 
+Another option to refer to previous values, is to define an alias for given step
+by its first argument with '#' prefix. Alias must be unique and it can't be a number.
+Example of step alias usage:
+
+.. code-block:: python
+
+                ['#port1', 'vswitch', 'add_vport', 'int_br0'],
+                ['vswitch', 'del_port', 'int_br0', '#STEP[port1][0]'],
+
 Also commonly used steps can be created as a separate profile.
 
 .. code-block:: python
@@ -324,6 +376,28 @@ This profile can then be used inside other testcases
                      STEP_VSWITCH_PVP_FINIT
     }
 
+It is possible to refer to vsperf configuration parameters within step macros. Please
+see :ref:`step-driven-tests-variable-usage` for more details.
+
+In case that step returns a string or list of strings, then it is possible to
+filter such output by regular expression. This optional filter can be specified
+as a last step parameter with prefix '|'. Output will be split into separate lines
+and only matching records will be returned. It is also possible to return a specified
+group of characters from the matching lines, e.g. by regex ``|ID (\d+)``.
+
+Examples:
+
+.. code-block:: python
+
+   ['tools', 'exec_shell', "sudo $TOOLS['ovs-appctl'] dpif-netdev/pmd-rxq-show",
+    '|dpdkvhostuser0\s+queue-id: \d'],
+   ['tools', 'assert', 'len(#STEP[-1])==1'],
+
+   ['vnf', 'execute_and_wait', 'ethtool -L eth0 combined 2'],
+   ['vnf', 'execute_and_wait', 'ethtool -l eth0', '|Combined:\s+2'],
+   ['tools', 'assert', 'len(#STEP[-1])==2']
+
+
 HelloWorld and other basic Testcases
 ------------------------------------
 
index 3d9ffe4..37cdefa 100644 (file)
@@ -68,7 +68,6 @@ class TestCase(object):
         self._loadgen = None
         self._output_file = None
         self._tc_results = None
-        self._settings_original = {}
         self._settings_paths_modified = False
         self._testcast_run_time = None
         self._versions = []
@@ -76,21 +75,18 @@ class TestCase(object):
         self._step_check = False    # by default don't check result for step driven testcases
         self._step_vnf_list = {}
         self._step_result = []
+        self._step_result_mapping = {}
         self._step_status = None
+        self._step_send_traffic = False # indication if send_traffic was called within test steps
         self._testcase_run_time = None
 
-        # store all GUEST_ specific settings to keep original values before their expansion
-        for key in S.__dict__:
-            if key.startswith('GUEST_'):
-                self._settings_original[key] = S.getValue(key)
-
-        self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
-        self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
-        self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
+        S.setValue('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
+        S.setValue('VNF', cfg.get('VNF', S.getValue('VNF')))
+        S.setValue('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
         test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
         tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
         test_params = merge_spec(test_params, tc_test_params)
-        self._update_settings('TEST_PARAMS', test_params)
+        S.setValue('TEST_PARAMS', test_params)
         S.check_test_params()
 
         # override all redefined GUEST_ values to have them expanded correctly
@@ -109,6 +105,15 @@ class TestCase(object):
         self.desc = cfg.get('Description', 'No description given.')
         self.test = cfg.get('TestSteps', None)
 
+        # log testcase name and details
+        tmp_desc = functions.format_description(self.desc, 50)
+        self._logger.info('############################################################')
+        self._logger.info('# Test:    %s', self.name)
+        self._logger.info('# Details: %s', tmp_desc[0])
+        for i in range(1, len(tmp_desc)):
+            self._logger.info('#          %s', tmp_desc[i])
+        self._logger.info('############################################################')
+
         bidirectional = S.getValue('TRAFFIC')['bidir']
         if not isinstance(S.getValue('TRAFFIC')['bidir'], str):
             raise TypeError(
@@ -162,6 +167,7 @@ class TestCase(object):
                 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
                 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
                 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
+                self._traffic['l2']['dstmac'] = S.getValue('NICS')[1]['mac']
         elif len(S.getValue('NICS')) and \
              (S.getValue('NICS')[0]['type'] == 'vf' or
               S.getValue('NICS')[1]['type'] == 'vf'):
@@ -181,8 +187,6 @@ class TestCase(object):
     def run_initialize(self):
         """ Prepare test execution environment
         """
-        self._logger.debug(self.name)
-
         # mount hugepages if needed
         self._mount_hugepages()
 
@@ -338,7 +342,7 @@ class TestCase(object):
                             # ...and continue with traffic generation, but keep
                             # in mind, that clean deployment does not configure
                             # OVS nor executes the traffic
-                            if self.deployment != 'clean':
+                            if self.deployment != 'clean' and not self._step_send_traffic:
                                 self._traffic_ctl.send_traffic(self._traffic)
 
                         # dump vswitch flows before they are affected by VNF termination
@@ -360,23 +364,6 @@ class TestCase(object):
         # report test results
         self.run_report()
 
-        # restore original settings
-        for key in self._settings_original:
-            S.setValue(key, self._settings_original[key])
-
-    def _update_settings(self, param, value):
-        """ Check value of given configuration parameter
-        In case that new value is different, then testcase
-        specific settings is updated and original value stored
-
-        :param param: Name of parameter inside settings
-        :param value: Disired parameter value
-        """
-        orig_value = S.getValue(param)
-        if orig_value != value:
-            self._settings_original[param] = copy.deepcopy(orig_value)
-            S.setValue(param, value)
-
     def _append_results(self, results):
         """
         Method appends mandatory Test Case results to list of dictionaries.
@@ -684,18 +671,22 @@ class TestCase(object):
             if self._step_vnf_list[vnf]:
                 self._step_vnf_list[vnf].stop()
 
-    def step_eval_param(self, param, STEP):
-        # pylint: disable=invalid-name
+    def step_eval_param(self, param, step_result):
         """ Helper function for #STEP macro evaluation
         """
         if isinstance(param, str):
             # evaluate every #STEP reference inside parameter itself
-            macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
+            macros = re.findall(r'(#STEP\[([\w\-:]+)\]((\[[\w\-\'\"]+\])*))', param)
+
             if macros:
                 for macro in macros:
+                    if macro[1] in self._step_result_mapping:
+                        key = self._step_result_mapping[macro[1]]
+                    else:
+                        key = macro[1]
                     # pylint: disable=eval-used
-                    tmp_val = str(eval(macro[1:]))
-                    param = param.replace(macro, tmp_val)
+                    tmp_val = str(eval('step_result[{}]{}'.format(key, macro[2])))
+                    param = param.replace(macro[0], tmp_val)
 
             # evaluate references to vsperf configuration options
             macros = re.findall(r'\$(([\w\-]+)(\[[\w\[\]\-\'\"]+\])*)', param)
@@ -713,12 +704,12 @@ class TestCase(object):
         elif isinstance(param, list) or isinstance(param, tuple):
             tmp_list = []
             for item in param:
-                tmp_list.append(self.step_eval_param(item, STEP))
+                tmp_list.append(self.step_eval_param(item, step_result))
             return tmp_list
         elif isinstance(param, dict):
             tmp_dict = {}
             for (key, value) in param.items():
-                tmp_dict[key] = self.step_eval_param(value, STEP)
+                tmp_dict[key] = self.step_eval_param(value, step_result)
             return tmp_dict
         else:
             return param
@@ -732,6 +723,7 @@ class TestCase(object):
             eval_params.append(self.step_eval_param(param, step_result))
         return eval_params
 
+    # pylint: disable=too-many-locals, too-many-branches, too-many-statements
     def step_run(self):
         """ Execute actions specified by TestSteps list
 
@@ -753,6 +745,30 @@ class TestCase(object):
         # run test step by step...
         for i, step in enumerate(self.test):
             step_ok = not self._step_check
+            step_check = self._step_check
+            regex = None
+            # configure step result mapping if step alias/label is detected
+            if step[0].startswith('#'):
+                key = step[0][1:]
+                if key.isdigit():
+                    raise RuntimeError('Step alias can\'t be an integer value {}'.format(key))
+                if key in self._step_result_mapping:
+                    raise RuntimeError('Step alias {} has been used already for step '
+                                       '{}'.format(key, self._step_result_mapping[key]))
+                self._step_result_mapping[step[0][1:]] = i
+                step = step[1:]
+
+            # store regex filter if it is specified
+            if isinstance(step[-1], str) and step[-1].startswith('|'):
+                # evalute macros and variables used in regex
+                regex = self.step_eval_params([step[-1][1:]], self._step_result[:i])[0]
+                step = step[:-1]
+
+            # check if step verification should be suppressed
+            if step[0].startswith('!'):
+                step_check = False
+                step_ok = True
+                step[0] = step[0][1:]
             if step[0] == 'vswitch':
                 test_object = self._vswitch_ctl.get_vswitch()
             elif step[0] == 'namespace':
@@ -772,6 +788,9 @@ class TestCase(object):
                     tmp_traffic = copy.deepcopy(self._traffic)
                     tmp_traffic.update(step[2])
                     step[2] = tmp_traffic
+                    # store indication that traffic has been sent
+                    # so it is not sent again after the execution of teststeps
+                    self._step_send_traffic = True
             elif step[0].startswith('vnf'):
                 if not self._step_vnf_list[step[0]]:
                     # initialize new VM
@@ -785,6 +804,15 @@ class TestCase(object):
                 self._logger.debug("Sleep %s seconds", step[1])
                 time.sleep(int(step[1]))
                 continue
+            elif step[0] == 'log':
+                test_object = self._logger
+                # there isn't a need for validation of log entry
+                step_check = False
+                step_ok = True
+            elif step[0] == 'pdb':
+                import pdb
+                pdb.set_trace()
+                continue
             else:
                 self._logger.error("Unsupported test object %s", step[0])
                 self._step_status = {'status' : False, 'details' : ' '.join(step)}
@@ -793,7 +821,7 @@ class TestCase(object):
                 return False
 
             test_method = getattr(test_object, step[1])
-            if self._step_check:
+            if step_check:
                 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
             else:
                 test_method_check = None
@@ -804,18 +832,24 @@ class TestCase(object):
                 # to support negative indexes
                 step_params = self.step_eval_params(step[2:], self._step_result[:i])
                 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
+                step_log += ' filter "{}"'.format(regex) if regex else ''
                 self._logger.debug("Step %s '%s' start", i, step_log)
                 self._step_result[i] = test_method(*step_params)
+                if regex:
+                    # apply regex to step output
+                    self._step_result[i] = functions.filter_output(
+                        self._step_result[i], regex)
+
                 self._logger.debug("Step %s '%s' results '%s'", i,
                                    step_log, self._step_result[i])
                 time.sleep(S.getValue('TEST_STEP_DELAY'))
-                if self._step_check:
+                if 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:
+            if step_check:
                 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
 
             if not step_ok:
index aeaa2ed..c0d1e5f 100644 (file)
@@ -19,6 +19,7 @@ import os
 import logging
 import glob
 import shutil
+import re
 from conf import settings as S
 
 MAX_L4_FLOWS = 65536
@@ -179,3 +180,44 @@ def check_traffic(traffic):
                 traffic['multistream'] = MAX_L4_FLOWS
 
     return traffic
+
+def filter_output(output, regex):
+    """Filter output by defined regex. Output can be either string, list or tuple.
+       Every string is split into list line by line. After that regex is applied
+       to filter only matching lines, which are returned back.
+
+       :returns: list of matching records
+    """
+    result = []
+    if isinstance(output, str):
+        for line in output.split('\n'):
+            result += re.findall(regex, line)
+        return result
+    elif isinstance(output, list) or isinstance(output, tuple):
+        tmp_res = []
+        for item in output:
+            tmp_res.append(filter_output(item, regex))
+        return tmp_res
+    else:
+        raise RuntimeError('Only strings and lists are supported by filter_output(), '
+                           'but output has type {}'.format(type(output)))
+
+def format_description(desc, length):
+    """ Split description into multiple lines based on given line length.
+
+    :param desc: A string with testcase description
+    :param length: A maximum line length
+    """
+    # split description to multiple lines
+    words = desc.split()
+    output = []
+    line = ''
+    for word in words:
+        if len(line) + len(word) < length:
+            line += '{} '.format(word)
+        else:
+            output.append(line.strip())
+            line = '{} '.format(word)
+
+    output.append(line.strip())
+    return output
index 972fa33..b8fb187 100755 (executable)
@@ -153,9 +153,8 @@ class IxNet(trafficgen.ITrafficGenerator):
         """Initialize IXNET members
         """
         super().__init__()
-        self._script = os.path.join(settings.getValue('TRAFFICGEN_IXIA_3RD_PARTY'),
-                                    settings.getValue('TRAFFICGEN_IXNET_TCL_SCRIPT'))
         self._tclsh = tkinter.Tcl()
+        self._script = None
         self._cfg = None
         self._logger = logging.getLogger(__name__)
         self._params = None
@@ -177,6 +176,8 @@ class IxNet(trafficgen.ITrafficGenerator):
     def configure(self):
         """Configure system for IxNetwork.
         """
+        self._script = os.path.join(settings.getValue('TRAFFICGEN_IXIA_3RD_PARTY'),
+                                    settings.getValue('TRAFFICGEN_IXNET_TCL_SCRIPT'))
         self._cfg = {
             'lib_path': settings.getValue('TRAFFICGEN_IXNET_LIB_PATH'),
             # IxNetwork machine configuration
@@ -225,6 +226,8 @@ class IxNet(trafficgen.ITrafficGenerator):
             'multipleStreams': traffic['multistream'],
             'streamType': traffic['stream_type'],
             'rfc2544TestType': 'throughput',
+            'flowControl': "True" if traffic['flow_control'] else "False",
+            'learningFrames': "True" if traffic['learning_frames'] else "False",
         }
         self._params['traffic'] = self.traffic_defaults.copy()
 
@@ -280,6 +283,8 @@ class IxNet(trafficgen.ITrafficGenerator):
             'multipleStreams': traffic['multistream'],
             'streamType': traffic['stream_type'],
             'rfc2544TestType': 'throughput',
+            'flowControl': "True" if traffic['flow_control'] else "False",
+            'learningFrames': "True" if traffic['learning_frames'] else "False",
         }
         self._params['traffic'] = self.traffic_defaults.copy()
 
@@ -418,6 +423,8 @@ class IxNet(trafficgen.ITrafficGenerator):
             'multipleStreams': traffic['multistream'],
             'streamType': traffic['stream_type'],
             'rfc2544TestType': 'back2back',
+            'flowControl': "True" if traffic['flow_control'] else "False",
+            'learningFrames': "True" if traffic['learning_frames'] else "False",
         }
         self._params['traffic'] = self.traffic_defaults.copy()
 
index 5d551c6..639e343 100644 (file)
 """Various helper functions for step driven testcases
 """
 
-import re
 import logging
 import subprocess
 import locale
+from tools.functions import filter_output
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -93,11 +93,7 @@ class TestStepsTools(object):
         output = output.decode(locale.getdefaultlocale()[1])
 
         if regex:
-            for line in output.split('\n'):
-                result = re.findall(regex, line)
-                if result:
-                    return result
-            return []
+            return filter_output(output, regex)
 
         return output
 
index 30f073e..8e3d44d 100644 (file)
@@ -146,13 +146,14 @@ class IVnfQemu(IVnf):
         """
         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")
+                if self._login_active:
+                    # 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")
+                    # turn off VM
+                    self.execute_and_wait('poweroff', 120, "Power down")
 
             except pexpect.TIMEOUT:
                 self.kill()
@@ -169,7 +170,7 @@ class IVnfQemu(IVnf):
 
     # helper functions
 
-    def _login(self, timeout=120):
+    def login(self, timeout=120):
         """
         Login to QEMU instance.
 
@@ -178,8 +179,11 @@ class IVnfQemu(IVnf):
 
         :param timeout: Timeout to wait for login to complete.
 
-        :returns: None
+        :returns: True if login is active
         """
+        if self._login_active:
+            return self._login_active
+
         # if no timeout was set, we likely started QEMU without waiting for it
         # to boot. This being the case, we best check that it has finished
         # first.
@@ -191,21 +195,8 @@ class IVnfQemu(IVnf):
         self._child.sendline(S.getValue('GUEST_PASSWORD')[self._number])
 
         self._expect_process(S.getValue('GUEST_PROMPT')[self._number], timeout=5)
-
-    def send_and_pass(self, cmd, timeout=30):
-        """
-        Send ``cmd`` and wait ``timeout`` seconds for it to pass.
-
-        :param cmd: Command to send to guest.
-        :param timeout: Time to wait for prompt before checking return code.
-
-        :returns: None
-        """
-        self.execute(cmd)
-        self.wait(S.getValue('GUEST_PROMPT')[self._number], timeout=timeout)
-        self.execute('echo $?')
-        self._child.expect('^0$', timeout=1)  # expect a 0
-        self.wait(S.getValue('GUEST_PROMPT')[self._number], timeout=timeout)
+        self._login_active = True
+        return self._login_active
 
     def _affinitize(self):
         """
@@ -277,29 +268,31 @@ class IVnfQemu(IVnf):
         """
         Configure VM to run VNF, e.g. port forwarding application based on the configuration
         """
+        if self._guest_loopback == 'buildin':
+            return
+
+        self.login()
+
         if self._guest_loopback == 'testpmd':
-            self._login()
             self._configure_testpmd()
         elif self._guest_loopback == 'l2fwd':
-            self._login()
             self._configure_l2fwd()
         elif self._guest_loopback == 'linux_bridge':
-            self._login()
             self._configure_linux_bridge()
-        elif self._guest_loopback != 'buildin':
-            self._logger.error('Unsupported guest loopback method "%s" was specified. Option'
-                               ' "buildin" will be used as a fallback.', self._guest_loopback)
+        elif self._guest_loopback != 'clean':
+            raise RuntimeError('Unsupported guest loopback method "%s" was specified.',
+                               self._guest_loopback)
 
     def wait(self, prompt=None, timeout=30):
         if prompt is None:
             prompt = S.getValue('GUEST_PROMPT')[self._number]
-        super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
+        return super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
 
     def execute_and_wait(self, cmd, timeout=30, prompt=None):
         if prompt is None:
             prompt = S.getValue('GUEST_PROMPT')[self._number]
-        super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
-                                               prompt=prompt)
+        return super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
+                                                      prompt=prompt)
 
     def _modify_dpdk_makefile(self):
         """
@@ -393,7 +386,7 @@ class IVnfQemu(IVnf):
                 'VSWITCH_JUMBO_FRAMES_SIZE'))
 
         self.execute_and_wait('./testpmd {}'.format(testpmd_params), 60, "Done")
-        self.execute('set fwd ' + self._testpmd_fwd_mode, 1)
+        self.execute_and_wait('set fwd ' + self._testpmd_fwd_mode, 20, 'testpmd>')
         self.execute_and_wait('start', 20, 'testpmd>')
 
     def _configure_l2fwd(self):
@@ -407,12 +400,12 @@ class IVnfQemu(IVnf):
 
         # configure all interfaces
         for nic in self._nics:
-            self.execute('ip addr add ' +
-                         nic['ip'] + ' dev ' + nic['device'])
+            self.execute_and_wait('ip addr add ' +
+                                  nic['ip'] + ' dev ' + nic['device'])
             if S.getValue('VSWITCH_JUMBO_FRAMES_ENABLED'):
-                self.execute('ifconfig {} mtu {}'.format(
+                self.execute_and_wait('ifconfig {} mtu {}'.format(
                     nic['device'], S.getValue('VSWITCH_JUMBO_FRAMES_SIZE')))
-            self.execute('ip link set dev ' + nic['device'] + ' up')
+            self.execute_and_wait('ip link set dev ' + nic['device'] + ' up')
 
         # build and configure system for l2fwd
         self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR')[self._number] +
@@ -437,43 +430,42 @@ class IVnfQemu(IVnf):
         self._configure_disable_firewall()
 
         # configure linux bridge
-        self.execute('brctl addbr br0')
+        self.execute_and_wait('brctl addbr br0')
 
         # add all NICs into the bridge
         for nic in self._nics:
-            self.execute('ip addr add ' +
-                         nic['ip'] + ' dev ' + nic['device'])
+            self.execute_and_wait('ip addr add ' + nic['ip'] + ' dev ' + nic['device'])
             if S.getValue('VSWITCH_JUMBO_FRAMES_ENABLED'):
-                self.execute('ifconfig {} mtu {}'.format(
+                self.execute_and_wait('ifconfig {} mtu {}'.format(
                     nic['device'], S.getValue('VSWITCH_JUMBO_FRAMES_SIZE')))
-            self.execute('ip link set dev ' + nic['device'] + ' up')
-            self.execute('brctl addif br0 ' + nic['device'])
+            self.execute_and_wait('ip link set dev ' + nic['device'] + ' up')
+            self.execute_and_wait('brctl addif br0 ' + nic['device'])
 
-        self.execute('ip addr add ' +
-                     S.getValue('GUEST_BRIDGE_IP')[self._number] +
-                     ' dev br0')
-        self.execute('ip link set dev br0 up')
+        self.execute_and_wait('ip addr add {} dev br0'.format(
+            S.getValue('GUEST_BRIDGE_IP')[self._number]))
+        self.execute_and_wait('ip link set dev br0 up')
 
         # Add the arp entries for the IXIA ports and the bridge you are using.
         # Use command line values if provided.
         trafficgen_mac = S.getValue('VANILLA_TGEN_PORT1_MAC')
         trafficgen_ip = S.getValue('VANILLA_TGEN_PORT1_IP')
 
-        self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
+        self.execute_and_wait('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
 
         trafficgen_mac = S.getValue('VANILLA_TGEN_PORT2_MAC')
         trafficgen_ip = S.getValue('VANILLA_TGEN_PORT2_IP')
 
-        self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
+        self.execute_and_wait('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
 
         # Enable forwarding
-        self.execute('sysctl -w net.ipv4.ip_forward=1')
+        self.execute_and_wait('sysctl -w net.ipv4.ip_forward=1')
 
         # Controls source route verification
         # 0 means no source validation
-        self.execute('sysctl -w net.ipv4.conf.all.rp_filter=0')
+        self.execute_and_wait('sysctl -w net.ipv4.conf.all.rp_filter=0')
         for nic in self._nics:
-            self.execute('sysctl -w net.ipv4.conf.' + nic['device'] + '.rp_filter=0')
+            self.execute_and_wait('sysctl -w net.ipv4.conf.' + nic['device'] +
+                                  '.rp_filter=0')
 
     def _bind_dpdk_driver(self, driver, pci_slots):
         """
index 9314783..b53af6f 100644 (file)
@@ -68,11 +68,14 @@ class QemuDpdkVhostUser(IVnfQemu):
             else:
                 vhost_folder = S.getValue('TOOLS')['ovs_var_tmp']
 
-            nic_mode = '' if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE') else ',server'
+            if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
+                nic_name = 'dpdkvhostuser' + ifi
+            else:
+                nic_name = 'dpdkvhostuserclient' + ifi + ',server'
+
             self._cmd += ['-chardev',
                           'socket,id=char' + ifi +
-                          ',path=' + vhost_folder +
-                          'dpdkvhostuser' + ifi + nic_mode,
+                          ',path=' + vhost_folder + nic_name,
                           '-netdev',
                           'type=vhost-user,id=' + net +
                           ',chardev=char' + ifi + ',vhostforce' + queue_str,
index 759bdd6..5ac2ada 100644 (file)
@@ -17,6 +17,7 @@ Interface for VNF.
 """
 
 import time
+import pexpect
 from tools import tasks
 
 class IVnf(tasks.Process):
@@ -40,14 +41,7 @@ class IVnf(tasks.Process):
                            self._number + 1, self._number)
         IVnf._number_vnfs = IVnf._number_vnfs + 1
         self._log_prefix = 'vnf_%d_cmd : ' % self._number
-
-    def start(self):
-        """
-        Starts VNF instance.
-
-        This is a blocking function
-        """
-        super(IVnf, self).start()
+        self._login_active = False
 
     def stop(self):
         """
@@ -60,6 +54,18 @@ class IVnf(tasks.Process):
             # sporadic reboot of host. (caused by hugepages or DPDK ports)
             super(IVnf, self).kill(signal='-9', sleep=10)
 
+    def login(self, dummy_timeout=120):
+        """
+        Login to GUEST instance.
+
+        This can be used after booting the machine
+
+        :param timeout: Timeout to wait for login to complete.
+
+        :returns: True if login is active
+        """
+        raise NotImplementedError()
+
     def execute(self, cmd, delay=0):
         """
         execute ``cmd`` with given ``delay``.
@@ -77,6 +83,14 @@ class IVnf(tasks.Process):
         :returns: None.
         """
         self._logger.debug('%s%s', self._log_prefix, cmd)
+
+        # ensure that output from previous commands is flushed
+        try:
+            while not self._child.expect(r'.+', timeout=0.1):
+                pass
+        except (pexpect.TIMEOUT, pexpect.EOF):
+            pass
+
         self._child.sendline(cmd)
         time.sleep(delay)
 
@@ -95,10 +109,10 @@ class IVnf(tasks.Process):
                         Please note that this value can be floating
                         point which allows to pass milliseconds.
 
-        :returns: True if result_cmd has been detected before
-                  timeout has been reached, False otherwise.
+        :returns: output of executed command
         """
         self._child.expect(prompt, timeout=timeout)
+        return self._child.before
 
     def execute_and_wait(self, cmd, timeout=30, prompt=''):
         """
@@ -119,26 +133,36 @@ class IVnf(tasks.Process):
                              ``prompt`` passed in __init__
                              method will be used.
 
-        :returns: True if end of execution has been detected
-                  before timeout has been reached, False otherwise.
+        :returns: output of executed command
         """
         self.execute(cmd)
-        self.wait(prompt=prompt, timeout=timeout)
+        return self.wait(prompt=prompt, timeout=timeout)
 
-    # pylint: disable=simplifiable-if-statement
-    def validate_start(self, dummy_result):
+    def validate_start(self, dummyresult):
         """ Validate call of VNF start()
         """
         if self._child and self._child.isalive():
             return True
-        else:
-            return False
+
+        return False
 
     def validate_stop(self, result):
-        """ Validate call of fVNF stop()
+        """ Validate call of VNF stop()
         """
         return not self.validate_start(result)
 
+    @staticmethod
+    def validate_execute_and_wait(result, dummy_cmd, dummy_timeout=30, dummy_prompt=''):
+        """ Validate command execution within VNF
+        """
+        return len(result) > 0
+
+    @staticmethod
+    def validate_login(result):
+        """ Validate successful login into guest
+        """
+        return result
+
     @staticmethod
     def reset_vnf_counter():
         """
diff --git a/vsperf b/vsperf
index c14f9bb..6f248f8 100755 (executable)
--- a/vsperf
+++ b/vsperf
@@ -451,18 +451,36 @@ def handle_list_options(args):
         sys.exit(0)
 
     if args['list']:
-        # configure tests
-        if args['integration']:
-            testcases = settings.getValue('INTEGRATION_TESTS')
+        list_testcases(args)
+        sys.exit(0)
+
+
+def list_testcases(args):
+    """ Print list of testcases requested by --list CLI argument
+
+    :param args: A dictionary with all CLI arguments
+    """
+    # configure tests
+    if args['integration']:
+        testcases = settings.getValue('INTEGRATION_TESTS')
+    else:
+        testcases = settings.getValue('PERFORMANCE_TESTS')
+
+    print("Available Tests:")
+    print("================")
+
+    for test in testcases:
+        description = functions.format_description(test['Description'], 70)
+        if len(test['Name']) < 40:
+            print('* {:40} {}'.format('{}:'.format(test['Name']), description[0]))
         else:
-            testcases = settings.getValue('PERFORMANCE_TESTS')
+            print('* {}'.format('{}:'.format(test['Name'])))
+            print('  {:40} {}'.format('', description[0]))
+        for i in range(1, len(description)):
+            print('  {:40} {}'.format('', description[i]))
+
 
-        print("Available Tests:")
-        print("================")
 
-        for test in testcases:
-            print('* %-30s %s' % ('%s:' % test['Name'], test['Description']))
-        sys.exit(0)
 
 
 def vsperf_finalize():
@@ -693,6 +711,7 @@ def main():
         # testcases.integration.IntegrationTestCase to testcases.performance.PerformanceTestCase
         # pylint: disable=redefined-variable-type
         suite = unittest.TestSuite()
+        settings_snapshot = copy.deepcopy(settings.__dict__)
         for cfg in selected_tests:
             test_name = cfg.get('Name', '<Name not set>')
             try:
@@ -707,6 +726,8 @@ def main():
                 _LOGGER.exception("Failed to run test: %s", test_name)
                 suite.addTest(MockTestCase(str(ex), False, test_name))
                 _LOGGER.info("Continuing with next test...")
+            finally:
+                settings.restore_from_dict(settings_snapshot)
 
         # generate final rst report with results of all executed TCs
         generate_final_report()
index 7e16c14..76cabb0 100644 (file)
@@ -91,6 +91,27 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
 
         self._logger.info("Vswitchd...Started.")
 
+    def restart(self):
+        """ Restart ``ovs-vswitchd`` instance. ``ovsdb-server`` is not restarted.
+
+        :raises: pexpect.EOF, pexpect.TIMEOUT
+        """
+        self._logger.info("Restarting vswitchd...")
+        if os.path.isfile(self._vswitchd_pidfile_path):
+            self._logger.info('Killing ovs-vswitchd...')
+            with open(self._vswitchd_pidfile_path, "r") as pidfile:
+                vswitchd_pid = pidfile.read().strip()
+                tasks.terminate_task(vswitchd_pid, logger=self._logger)
+
+        try:
+            tasks.Process.start(self)
+            self.relinquish()
+        except (pexpect.EOF, pexpect.TIMEOUT) as exc:
+            logging.error("Exception during VSwitch start.")
+            self._kill_ovsdb()
+            raise exc
+        self._logger.info("Vswitchd...Started.")
+
     def configure(self):
         """ Configure vswitchd through ovsdb if needed
         """
@@ -109,6 +130,7 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         self._logger.info("Terminating vswitchd...")
         self.kill()
+        self._bridges = {}
         self._logger.info("Vswitchd...Terminated.")
 
     def add_switch(self, switch_name, params=None):
@@ -488,3 +510,8 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         bridge = self._bridges[switch_name]
         return 'stp_enable          : true' in ''.join(bridge.bridge_info())
+
+    def validate_restart(self, dummy_result):
+        """ Validate restart
+        """
+        return True
index 3b20be3..11b32c8 100644 (file)
@@ -149,7 +149,7 @@ class OvsDpdkVhost(IVSwitchOvs):
             nic_type = 'dpdkvhostuserclient'
 
         vhost_count = self._get_port_count('type={}'.format(nic_type))
-        port_name = 'dpdkvhostuser' + str(vhost_count)
+        port_name = nic_type + str(vhost_count)
         params = ['--', 'set', 'Interface', port_name, 'type={}'.format(nic_type)]
         if not S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
             params += ['--', 'set', 'Interface', port_name, 'options:vhost-server-path='
index bb47278..c62e28d 100644 (file)
@@ -50,6 +50,7 @@ class VppDpdkVhost(IVSwitch, tasks.Process):
         self._phy_ports = []
         self._virt_ports = []
         self._switches = {}
+        self._vpp_ctl = ['sudo', S.getValue('TOOLS')['vppctl']]
 
         # configure DPDK NICs
         tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
@@ -71,6 +72,12 @@ class VppDpdkVhost(IVSwitch, tasks.Process):
         # configure path to the plugins
         tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
 
+        # cli sock file must be used for VPP 17.10 and newer
+        if S.getValue('VSWITCH_VPP_CLI_SOCK'):
+            self._vpp_ctl += ['-s', S.getValue('VSWITCH_VPP_CLI_SOCK')]
+            tmp_args['unix'].append('cli-listen {}'.format(
+                S.getValue('VSWITCH_VPP_CLI_SOCK')))
+
         mqs = int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))
         tmp_rxqs = ''
         if mqs:
@@ -371,7 +378,7 @@ class VppDpdkVhost(IVSwitch, tasks.Process):
 
         :return: None
         """
-        cmd = ['sudo', S.getValue('TOOLS')['vppctl']] + args
+        cmd = self._vpp_ctl + args
         return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
 
     #
index dd69e6d..efa3a34 100644 (file)
@@ -36,6 +36,13 @@ class IVSwitch(object):
         """
         raise NotImplementedError()
 
+    def restart(self):
+        """Retart the vSwitch
+
+        Restart of vSwitch is required for failover testcases.
+        """
+        raise NotImplementedError()
+
     def stop(self):
         """Stop the vSwitch