diff options
-rwxr-xr-x | conf/01_testcases.conf | 188 | ||||
-rw-r--r-- | conf/02_vswitch.conf | 52 | ||||
-rw-r--r-- | conf/05_collector.conf | 2 | ||||
-rw-r--r-- | conf/06_pktfwd.conf | 2 | ||||
-rw-r--r-- | core/loader/loader_servant.py | 3 | ||||
-rw-r--r-- | docs/configguide/installation.rst | 32 | ||||
-rw-r--r-- | docs/configguide/trafficgen.rst | 1 | ||||
-rw-r--r-- | docs/design/vswitchperf_design.rst | 2 | ||||
-rw-r--r-- | docs/userguide/teststeps.rst | 2 | ||||
-rw-r--r-- | docs/userguide/testusage.rst | 49 | ||||
-rw-r--r-- | testcases/testcase.py | 29 | ||||
-rw-r--r-- | tools/pkt_fwd/testpmd.py | 8 | ||||
-rwxr-xr-x | vsperf | 31 | ||||
-rw-r--r-- | vswitches/ovs_dpdk_vhost.py | 50 | ||||
-rw-r--r-- | vswitches/ovs_vanilla.py | 15 | ||||
-rw-r--r-- | vswitches/vpp_dpdk_vhost.py | 403 | ||||
-rw-r--r-- | vswitches/vswitch.py | 33 |
17 files changed, 837 insertions, 65 deletions
diff --git a/conf/01_testcases.conf b/conf/01_testcases.conf index b30afc8f..4851b043 100755 --- a/conf/01_testcases.conf +++ b/conf/01_testcases.conf @@ -78,6 +78,85 @@ # "Test Modifier": [FrameMod|Other], # "Dependency": [Test_Case_Name |None], +# +# VPP specific macros used in TC defintions +# +VPP_P2P = [ + ['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_connection', 'int_br0', '#STEP[1][0]', '#STEP[2][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[2][0]', '#STEP[1][0]', True], + ['trafficgen', 'send_traffic', {}], + ['vswitch', 'dump_connections', 'int_br0'], + ['vswitch', 'del_connection', 'int_br0', '#STEP[1][0]', '#STEP[2][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[2][0]', '#STEP[1][0]', True], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] +VPP_PVP = [ + ['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 + ['vswitch', 'add_connection', 'int_br0', '#STEP[1][0]', '#STEP[3][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[4][0]', '#STEP[2][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[2][0]', '#STEP[4][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[3][0]', '#STEP[1][0]', True], + ['vnf', 'start'], + ['trafficgen', 'send_traffic', {}], + ['vnf', 'stop'], + ['vswitch', 'dump_connections', 'int_br0'], + ['vswitch', 'del_connection', 'int_br0', '#STEP[1][0]', '#STEP[3][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[4][0]', '#STEP[2][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[2][0]', '#STEP[4][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[3][0]', '#STEP[1][0]', True], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] +VPP_PVVP = [ + ['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 + ['vswitch', 'add_vport', 'int_br0'], # STEP 5 + ['vswitch', 'add_vport', 'int_br0'], # STEP 6 + ['vswitch', 'add_connection', 'int_br0', '#STEP[1][0]', '#STEP[3][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[4][0]', '#STEP[5][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[6][0]', '#STEP[2][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[2][0]', '#STEP[6][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[5][0]', '#STEP[4][0]', True], + ['vswitch', 'add_connection', 'int_br0', '#STEP[3][0]', '#STEP[1][0]', True], + ['vnf1', 'start'], + ['vnf2', 'start'], + ['trafficgen', 'send_traffic', {}], + ['vnf2', 'stop'], + ['vnf1', 'stop'], + ['vswitch', 'dump_connections', 'int_br0'], + ['vswitch', 'del_connection', 'int_br0', '#STEP[1][0]', '#STEP[3][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[4][0]', '#STEP[5][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[6][0]', '#STEP[2][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[2][0]', '#STEP[6][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[5][0]', '#STEP[4][0]', True], + ['vswitch', 'del_connection', 'int_br0', '#STEP[3][0]', '#STEP[1][0]', True], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[3][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[4][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[5][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[6][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + +# +# Generic performance TC definitions +# PERFORMANCE_TESTS = [ { "Name": "phy2phy_tput", @@ -269,4 +348,113 @@ PERFORMANCE_TESTS = [ }, }, }, + { + "Name": "phy2phy_tput_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.PacketLossRatio", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + "TestSteps": VPP_P2P, + }, + { + "Name": "phy2phy_cont_vpp", + "Deployment": "clean", + "Description": "VPP: Phy2Phy Continuous Stream", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_continuous", + "frame_rate" : 100, + }, + }, + "TestSteps": VPP_P2P, + }, + { + "Name": "phy2phy_back2back_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.BackToBackFrames", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_back2back", + }, + }, + "TestSteps": VPP_P2P, + }, + { + "Name": "pvp_tput_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.PacketLossRatio", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + "TestSteps": VPP_PVP, + }, + { + "Name": "pvp_cont_vpp", + "Deployment": "clean", + "Description": "VPP: PVP Continuous Stream", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_continuous", + }, + }, + "TestSteps": VPP_PVP, + }, + { + "Name": "pvp_back2back_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.BackToBackFrames", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_back2back", + }, + }, + "TestSteps": VPP_PVP, + }, + { + "Name": "pvvp_tput_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.PacketLossRatio", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + "TestSteps": VPP_PVVP, + }, + { + "Name": "pvvp_cont_vpp", + "Deployment": "clean", + "Description": "VPP: PVP Continuous Stream", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_continuous", + }, + }, + "TestSteps": VPP_PVVP, + }, + { + "Name": "pvvp_back2back_vpp", + "Deployment": "clean", + "Description": "VPP: LTD.Throughput.RFC2544.BackToBackFrames", + "vSwitch" : "VppDpdkVhost", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_back2back", + }, + }, + "TestSteps": VPP_PVVP, + }, ] diff --git a/conf/02_vswitch.conf b/conf/02_vswitch.conf index 2ca7591c..2bac1732 100644 --- a/conf/02_vswitch.conf +++ b/conf/02_vswitch.conf @@ -33,6 +33,12 @@ RTE_TARGET = 'x86_64-native-linuxapp-gcc' # will be used for testing WHITELIST_NICS = [] +# List defines an amount of memory to be allocated by DPDK at NUMA nodes. This +# option is shared by all vSwitches with DPDK support. In case, that there is +# a socket-mem configuration specified in vSwitch specific configuration option, +# then it will be overridden by DPDK_SOCKET_MEM value. +DPDK_SOCKET_MEM = ['1024', '0'] + # vhost character device file used by dpdkvhostport QemuWrap cases VHOST_DEV_FILE = 'ovs-vhost-net' @@ -103,6 +109,18 @@ PATHS['vswitch'] = { }, 'ovs_var_tmp': '/usr/local/var/run/openvswitch/', 'ovs_etc_tmp': '/usr/local/etc/openvswitch/', + 'VppDpdkVhost': { + 'type' : 'bin', + 'src': { + 'path': os.path.join(ROOT_DIR, 'src/vpp/vpp/build-root/build-vpp-native'), + 'vpp': 'vpp', + 'vppctl': 'vppctl', + }, + 'bin': { + 'vpp': 'vpp', + 'vppctl': 'vppctl', + } + }, } # default OvsVanilla configuration is similar to OvsDpdkVhost except 'path' and 'modules' @@ -116,18 +134,19 @@ PATHS['vswitch']['OvsVanilla']['bin']['modules'] = ['openvswitch'] # ############################ # These are DPDK EAL parameters and they may need to be changed depending on # hardware configuration, like cpu numbering and NUMA. -# + # parameters used for legacy DPDK configuration through '--dpdk' option of ovs-vswitchd # e.g. ovs-vswitchd --dpdk --socket-mem 1024,0 # This config line is also used for pkt_fwd option (TestPMD phy2phy and pvp tests) -VSWITCHD_DPDK_ARGS = ['-c', '0x4', '-n', '4', '--socket-mem 1024,0'] +# NOTE: DPDK socket mem allocation is driven by parameter DPDK_SOCKET_MEM +VSWITCHD_DPDK_ARGS = ['-c', '0x4', '-n', '4'] # options used for new type of OVS configuration via calls to ovs-vsctl # e.g. ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem="1024,0" +# NOTE: DPDK socket mem allocation is driven by parameter DPDK_SOCKET_MEM VSWITCHD_DPDK_CONFIG = { 'dpdk-init' : 'true', 'dpdk-lcore-mask' : '0x4', - 'dpdk-socket-mem' : '1024,0', } # Note: VSPERF will automatically detect, which type of DPDK configuration should # be used. @@ -169,3 +188,30 @@ LOG_FILE_OVS = 'ovs.log' # default vswitch implementation VSWITCH = "OvsDpdkVhost" + +######################### +## VPP +######################### +# Set of arguments used for startup of VPP +# NOTE: DPDK socket mem allocation is driven by parameter DPDK_SOCKET_MEM +VSWITCH_VPP_ARGS = { + 'unix' : [ + 'interactive', # required by VSPERF to detect successful VPP startup + 'log /tmp/vpp.log', + 'full-coredump', + ], + 'cpu' : [ + 'main-core 3', + 'corelist-workers 4,5', + ], +} + +# log file for VPP +LOG_FILE_VPP = 'vsperf-vpp.log' + +# Select l2 connection method used by VPP. +# Supported values are: 'xconnect', 'l2patch' and 'bridge' +VSWITCH_VPP_L2_CONNECT_MODE = 'xconnect' + +# Options used during creation of dpdkvhostuser interface +VSWITCH_VPP_VHOSTUSER_ARGS = ['feature-mask', '0xFF'] diff --git a/conf/05_collector.conf b/conf/05_collector.conf index bda0ac8d..9fd2558c 100644 --- a/conf/05_collector.conf +++ b/conf/05_collector.conf @@ -20,7 +20,7 @@ COLLECTOR = 'Pidstat' COLLECTOR_DIR = os.path.join(ROOT_DIR, 'tools/collectors') # processes to be monitored by pidstat -PIDSTAT_MONITOR = ['ovs-vswitchd', 'ovsdb-server', 'qemu-system-x86_64'] +PIDSTAT_MONITOR = ['ovs-vswitchd', 'ovsdb-server', 'qemu-system-x86_64', 'vpp'] # options which will be passed to pidstat PIDSTAT_OPTIONS = '-dur' diff --git a/conf/06_pktfwd.conf b/conf/06_pktfwd.conf index 6175aa6a..bb4c1d79 100644 --- a/conf/06_pktfwd.conf +++ b/conf/06_pktfwd.conf @@ -36,4 +36,4 @@ TESTPMD_CSUM_CALC = 'sw' # recognize tunnel headers: on|off TESTPMD_CSUM_PARSE_TUNNEL = 'off' -PIDSTAT_MONITOR = ['ovs-vswitchd', 'ovsdb-server', 'qemu-system-x86_64', 'testpmd'] +PIDSTAT_MONITOR = ['ovs-vswitchd', 'ovsdb-server', 'qemu-system-x86_64', 'vpp', 'testpmd'] diff --git a/core/loader/loader_servant.py b/core/loader/loader_servant.py index bbb4ea9d..8bad9ab9 100644 --- a/core/loader/loader_servant.py +++ b/core/loader/loader_servant.py @@ -86,7 +86,8 @@ class LoaderServant(object): interface=self._interface) results = [] - for (name, mod) in list(out.items()): + # sort modules to produce the same output everytime + for (name, mod) in sorted(out.items()): desc = (mod.__doc__ or 'No description').strip().split('\n')[0] results.append((name, desc)) diff --git a/docs/configguide/installation.rst b/docs/configguide/installation.rst index bda5a0bc..1965a8f5 100644 --- a/docs/configguide/installation.rst +++ b/docs/configguide/installation.rst @@ -165,6 +165,22 @@ built from upstream source due to kernel incompatibilities. Please see the instructions in the vswitchperf_design document for details on configuring OVS Vanilla for binary package usage. +.. _vpp-installation: + +VPP installation +================ + +Currently vswitchperf installation scripts do not support automatic build +of VPP. In order to execute tests with VPP, it is required to install it +manually. Please refer to the official documentation of `fd.io`_ project to +install VPP from `packages`_ or from the `sources`_. + +See details about :ref:`vpp-test`. + +.. _fd.io: https://fd.io/ +.. _packages: https://wiki.fd.io/view/VPP/Installing_VPP_binaries_from_packages +.. _sources: https://wiki.fd.io/view/VPP/Build,_install,_and_test_images + Using vswitchperf ----------------- @@ -260,20 +276,10 @@ your configuration in the ``02_vswitch.conf`` file. .. code:: bash - VSWITCHD_DPDK_ARGS = ['-c', '0x4', '-n', '4', '--socket-mem 1024,1024'] - VSWITCHD_DPDK_CONFIG = { - 'dpdk-init' : 'true', - 'dpdk-lcore-mask' : '0x4', - 'dpdk-socket-mem' : '1024,1024', - } - -**NOTE:** Option ``VSWITCHD_DPDK_ARGS`` is used for vswitchd, which supports ``--dpdk`` -parameter. In recent vswitchd versions, option ``VSWITCHD_DPDK_CONFIG`` is -used to configure vswitchd via ``ovs-vsctl`` calls. + DPDK_SOCKET_MEM = ['1024', '0'] -With the ``--socket-mem`` argument set to use 1 hugepage on the specified sockets as -seen above, the configuration will need 10 hugepages total to run all tests -within vsperf if the pagesize is set correctly to 1GB. +**NOTE:** Option ``DPDK_SOCKET_MEM`` is used by all vSwitches with DPDK support. +It means Open vSwitch, VPP and TestPMD. VSPerf will verify hugepage amounts are free before executing test environments. In case of hugepage amounts not being free, test initialization diff --git a/docs/configguide/trafficgen.rst b/docs/configguide/trafficgen.rst index 3c33d4ef..4e42b2be 100644 --- a/docs/configguide/trafficgen.rst +++ b/docs/configguide/trafficgen.rst @@ -469,6 +469,7 @@ For RFC2889 tests, specifying the locations for the monitoring ports is mandator Necessary parameters are: .. code-block:: console + TRAFFICGEN_STC_RFC2889_TEST_FILE_NAME TRAFFICGEN_STC_EAST_CHASSIS_ADDR = " " TRAFFICGEN_STC_EAST_SLOT_NUM = " " diff --git a/docs/design/vswitchperf_design.rst b/docs/design/vswitchperf_design.rst index 9e74e599..da7ec6fd 100644 --- a/docs/design/vswitchperf_design.rst +++ b/docs/design/vswitchperf_design.rst @@ -498,6 +498,8 @@ Other Configuration ``conf.settings`` also loads configuration from the command line and from the environment. +.. _pxp-deployment: + PXP Deployment ============== diff --git a/docs/userguide/teststeps.rst b/docs/userguide/teststeps.rst index 4e6c0808..870c3d80 100644 --- a/docs/userguide/teststeps.rst +++ b/docs/userguide/teststeps.rst @@ -2,6 +2,8 @@ .. http://creativecommons.org/licenses/by/4.0 .. (c) OPNFV, Intel Corporation, AT&T and others. +.. _step-driven-tests: + Step driven tests ================= diff --git a/docs/userguide/testusage.rst b/docs/userguide/testusage.rst index 0055164e..c6037aaf 100644 --- a/docs/userguide/testusage.rst +++ b/docs/userguide/testusage.rst @@ -335,6 +335,43 @@ To run tests using Vanilla OVS: $ ./vsperf --conf-file<path_to_custom_conf>/10_custom.conf +.. _vpp-test: + +Executing VPP tests +^^^^^^^^^^^^^^^^^^^ + +Currently it is not possible to use standard scenario deployments for execution of +tests with VPP. It means, that deployments ``p2p``, ``pvp``, ``pvvp`` and in general any +:ref:`pxp-deployment` won't work with VPP. However it is possible to use VPP in +:ref:`step-driven-tests`. A basic set of VPP testcases covering ``phy2phy``, ``pvp`` +and ``pvvp`` tests are already prepared. + +List of performance tests with VPP support follows: + +* phy2phy_tput_vpp: VPP: LTD.Throughput.RFC2544.PacketLossRatio +* phy2phy_cont_vpp: VPP: Phy2Phy Continuous Stream +* phy2phy_back2back_vpp: VPP: LTD.Throughput.RFC2544.BackToBackFrames +* pvp_tput_vpp: VPP: LTD.Throughput.RFC2544.PacketLossRatio +* pvp_cont_vpp: VPP: PVP Continuous Stream +* pvp_back2back_vpp: VPP: LTD.Throughput.RFC2544.BackToBackFrames +* pvvp_tput_vpp: VPP: LTD.Throughput.RFC2544.PacketLossRatio +* pvvp_cont_vpp: VPP: PVP Continuous Stream +* pvvp_back2back_vpp: VPP: LTD.Throughput.RFC2544.BackToBackFrames + +In order to execute testcases with VPP it is required to: + +* install VPP manually, see :ref:`vpp-installation` +* configure ``WHITELIST_NICS``, with two physical NICs connected to the traffic generator +* configure traffic generator, see :ref:`trafficgen-installation` + +After that it is possible to execute VPP testcases listed above. + +For example: + +.. code-block:: console + + $ ./vsperf --conf-file=<path_to_custom_conf> phy2phy_tput_vpp + .. _vfio-pci: Using vfio_pci with DPDK @@ -689,7 +726,8 @@ application to use the correct number of nb-cores. .. code-block:: python - VSWITCHD_DPDK_ARGS = ['-l', '46,44,42,40,38', '-n', '4', '--socket-mem 1024,0'] + DPDK_SOCKET_MEM = ['1024', '0'] + VSWITCHD_DPDK_ARGS = ['-l', '46,44,42,40,38', '-n', '4'] TESTPMD_ARGS = ['--nb-cores=4', '--txq=1', '--rxq=1'] For guest TestPMD 3 VCpus should be assigned with the following TestPMD params. @@ -790,16 +828,17 @@ an appropriate amount of memory: .. code-block:: python - VSWITCHD_DPDK_ARGS = ['-c', '0x4', '-n', '4', '--socket-mem 1024,0'] + DPDK_SOCKET_MEM = ['1024', '0'] + VSWITCHD_DPDK_ARGS = ['-c', '0x4', '-n', '4'] VSWITCHD_DPDK_CONFIG = { 'dpdk-init' : 'true', 'dpdk-lcore-mask' : '0x4', 'dpdk-socket-mem' : '1024,0', } -Note: Option VSWITCHD_DPDK_ARGS is used for vswitchd, which supports --dpdk -parameter. In recent vswitchd versions, option VSWITCHD_DPDK_CONFIG will be -used to configure vswitchd via ovs-vsctl calls. +Note: Option ``VSWITCHD_DPDK_ARGS`` is used for vswitchd, which supports ``--dpdk`` +parameter. In recent vswitchd versions, option ``VSWITCHD_DPDK_CONFIG`` will be +used to configure vswitchd via ``ovs-vsctl`` calls. More information diff --git a/testcases/testcase.py b/testcases/testcase.py index d74f34ad..4fbf9c04 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -257,9 +257,6 @@ class TestCase(object): # umount hugepages if mounted self._umount_hugepages() - # restore original settings - S.load_from_dict(self._settings_original) - # cleanup any namespaces created if os.path.isdir('/tmp/namespaces'): namespace_list = os.listdir('/tmp/namespaces') @@ -333,6 +330,9 @@ class TestCase(object): # report test results self.run_report() + # restore original settings + S.load_from_dict(self._settings_original) + def _update_settings(self, param, value): """ Check value of given configuration parameter In case that new value is different, then testcase @@ -460,26 +460,11 @@ class TestCase(object): # get hugepage amounts for each socket on dpdk sock0_mem, sock1_mem = 0, 0 + if S.getValue('VSWITCH').lower().count('dpdk'): - # the import below needs to remain here and not put into the module - # imports because of an exception due to settings not yet loaded - from vswitches import ovs_dpdk_vhost - if ovs_dpdk_vhost.OvsDpdkVhost.old_dpdk_config(): - match = re.search( - r'-socket-mem\s+(\d+),(\d+)', - ''.join(S.getValue('VSWITCHD_DPDK_ARGS'))) - if match: - sock0_mem, sock1_mem = (int(match.group(1)) * 1024 / hugepage_size, - int(match.group(2)) * 1024 / hugepage_size) - else: - logging.info( - 'Could not parse socket memory config in dpdk params.') - else: - sock0_mem, sock1_mem = ( - S.getValue( - 'VSWITCHD_DPDK_CONFIG')['dpdk-socket-mem'].split(',')) - sock0_mem, sock1_mem = (int(sock0_mem) * 1024 / hugepage_size, - int(sock1_mem) * 1024 / hugepage_size) + sock_mem = S.getValue('DPDK_SOCKET_MEM') + sock0_mem, sock1_mem = (int(sock_mem[0]) * 1024 / hugepage_size, + int(sock_mem[1]) * 1024 / hugepage_size) # If hugepages needed, verify the amounts are free if any([hugepages_needed, sock0_mem, sock1_mem]): diff --git a/tools/pkt_fwd/testpmd.py b/tools/pkt_fwd/testpmd.py index 8255f1d8..970259dc 100644 --- a/tools/pkt_fwd/testpmd.py +++ b/tools/pkt_fwd/testpmd.py @@ -41,6 +41,14 @@ class TestPMD(IPktFwd): def __init__(self, guest=False): vswitchd_args = settings.getValue('VSWITCHD_DPDK_ARGS') + + # override socket-mem settings + for tmp_arg in vswitchd_args: + if tmp_arg.startswith('--socket-mem'): + vswitchd_args.remove(tmp_arg) + vswitchd_args += ['--socket-mem ' + + ','.join(settings.getValue('DPDK_SOCKET_MEM'))] + if guest: vswitchd_args += _TESTPMD_PVP_CONST_ARGS vswitchd_args += _VSWITCHD_CONST_ARGS @@ -285,6 +285,28 @@ def check_and_set_locale(): _LOGGER.warning("Locale was not properly configured. Default values were set. Old locale: %s, New locale: %s", system_locale, locale.getdefaultlocale()) +def get_vswitch_names(rst_files): + """ Function will return a list of vSwitches detected in given ``rst_files``. + """ + vswitch_names = set() + if len(rst_files): + try: + output = subprocess.check_output(['grep', '-h', '^* vSwitch'] + rst_files).decode().splitlines() + for line in output: + match = re.search(r'^\* vSwitch: ([^,]+)', str(line)) + if match: + vswitch_names.add(match.group(1)) + + if len(vswitch_names): + return list(vswitch_names) + + except subprocess.CalledProcessError: + _LOGGER.warning('Cannot detect vSwitches used during testing.') + + # fallback to the default value + return ['vSwitch'] + + def generate_final_report(): """ Function will check if partial test results are available @@ -294,18 +316,15 @@ def generate_final_report(): path = settings.getValue('RESULTS_PATH') # check if there are any results in rst format rst_results = glob.glob(os.path.join(path, 'result*rst')) + pkt_processors = get_vswitch_names(rst_results) if len(rst_results): try: - test_report = os.path.join(path, '{}_{}'.format(settings.getValue('VSWITCH'), _TEMPLATE_RST['final'])) + test_report = os.path.join(path, '{}_{}'.format('_'.join(pkt_processors), _TEMPLATE_RST['final'])) # create report caption directly - it is not worth to execute jinja machinery - if settings.getValue('VSWITCH').lower() != 'none': - pkt_processor = Loader().get_vswitches()[settings.getValue('VSWITCH')].__doc__.strip().split('\n')[0] - else: - pkt_processor = Loader().get_pktfwds()[settings.getValue('PKTFWD')].__doc__.strip().split('\n')[0] report_caption = '{}\n{} {}\n{}\n\n'.format( '============================================================', 'Performance report for', - pkt_processor, + ', '.join(pkt_processors), '============================================================') with open(_TEMPLATE_RST['tmp'], 'w') as file_: diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py index a49c8dd7..3387fda7 100644 --- a/vswitches/ovs_dpdk_vhost.py +++ b/vswitches/ovs_dpdk_vhost.py @@ -20,7 +20,7 @@ import subprocess from src.ovs import OFBridge from src.dpdk import dpdk -from conf import settings +from conf import settings as S from vswitches.ovs import IVSwitchOvs class OvsDpdkVhost(IVSwitchOvs): @@ -43,7 +43,14 @@ class OvsDpdkVhost(IVSwitchOvs): # legacy DPDK configuration through --dpdk option of vswitchd if self.old_dpdk_config(): - vswitchd_args = ['--dpdk'] + settings.getValue('VSWITCHD_DPDK_ARGS') + # override socket-mem settings + tmp_dpdk_args = S.getValue('VSWITCHD_DPDK_ARGS') + for tmp_arg in tmp_dpdk_args: + if tmp_arg.startswith('--socket-mem'): + tmp_dpdk_args.remove(tmp_arg) + tmp_dpdk_args += ['--socket-mem ' + ','.join(S.getValue('DPDK_SOCKET_MEM'))] + vswitchd_args = ['--dpdk'] + tmp_dpdk_args + # add dpdk args to generic ovs-vswitchd settings if self._vswitchd_args: self._vswitchd_args = vswitchd_args + ['--'] + self._vswitchd_args else: @@ -52,8 +59,10 @@ class OvsDpdkVhost(IVSwitchOvs): def configure(self): """ Configure vswitchd DPDK options through ovsdb if needed """ - dpdk_config = settings.getValue('VSWITCHD_DPDK_CONFIG') + dpdk_config = S.getValue('VSWITCHD_DPDK_CONFIG') if dpdk_config and not self.old_dpdk_config(): + # override socket-mem settings + dpdk_config['dpdk-socket-mem'] = ','.join(S.getValue('DPDK_SOCKET_MEM')) # enforce calls to ovs-vsctl with --no-wait tmp_br = OFBridge(timeout=-1) for option in dpdk_config: @@ -68,12 +77,12 @@ class OvsDpdkVhost(IVSwitchOvs): dpdk.init() super(OvsDpdkVhost, self).start() # old style OVS <= 2.5.0 multi-queue enable - if settings.getValue('OVS_OLD_STYLE_MQ') and \ - int(settings.getValue('VSWITCH_DPDK_MULTI_QUEUES')): + if S.getValue('OVS_OLD_STYLE_MQ') and \ + int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES')): tmp_br = OFBridge(timeout=-1) tmp_br.set_db_attribute( 'Open_vSwitch', '.', 'other_config:' + - 'n-dpdk-rxqs', settings.getValue('VSWITCH_DPDK_MULTI_QUEUES')) + 'n-dpdk-rxqs', S.getValue('VSWITCH_DPDK_MULTI_QUEUES')) def stop(self): """See IVswitch for general description @@ -92,12 +101,12 @@ class OvsDpdkVhost(IVSwitchOvs): switch_params = switch_params + params super(OvsDpdkVhost, self).add_switch(switch_name, switch_params) - if settings.getValue('VSWITCH_AFFINITIZATION_ON') == 1: + if S.getValue('VSWITCH_AFFINITIZATION_ON') == 1: # Sets the PMD core mask to VSWITCH_PMD_CPU_MASK # for CPU core affinitization self._bridges[switch_name].set_db_attribute('Open_vSwitch', '.', 'other_config:pmd-cpu-mask', - settings.getValue('VSWITCH_PMD_CPU_MASK')) + S.getValue('VSWITCH_PMD_CPU_MASK')) def add_phy_port(self, switch_name): """See IVswitch for general description @@ -110,15 +119,15 @@ class OvsDpdkVhost(IVSwitchOvs): port_name = 'dpdk' + str(dpdk_count) # PCI info. Please note there must be no blank space, eg must be # like 'options:dpdk-devargs=0000:06:00.0' - _nics = settings.getValue('NICS') + _nics = S.getValue('NICS') nic_pci = 'options:dpdk-devargs=' + _nics[dpdk_count]['pci'] params = ['--', 'set', 'Interface', port_name, 'type=dpdk', nic_pci] # multi-queue enable - if int(settings.getValue('VSWITCH_DPDK_MULTI_QUEUES')) and \ - not settings.getValue('OVS_OLD_STYLE_MQ'): + if int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES')) and \ + not S.getValue('OVS_OLD_STYLE_MQ'): params += ['options:n_rxq={}'.format( - settings.getValue('VSWITCH_DPDK_MULTI_QUEUES'))] + S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))] of_port = bridge.add_port(port_name, params) return (port_name, of_port) @@ -144,9 +153,24 @@ class OvsDpdkVhost(IVSwitchOvs): :returns: True if legacy --dpdk option is supported, otherwise it returns False """ - ovs_vswitchd_bin = settings.getValue('TOOLS')['ovs-vswitchd'] + ovs_vswitchd_bin = S.getValue('TOOLS')['ovs-vswitchd'] try: subprocess.check_output(ovs_vswitchd_bin + r' --help | grep "\-\-dpdk"', shell=True) return True except subprocess.CalledProcessError: return False + + def add_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + """ + raise NotImplementedError() + + def del_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + """ + raise NotImplementedError() + + def dump_connections(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError() diff --git a/vswitches/ovs_vanilla.py b/vswitches/ovs_vanilla.py index 649a5ab9..75870ab7 100644 --- a/vswitches/ovs_vanilla.py +++ b/vswitches/ovs_vanilla.py @@ -125,3 +125,18 @@ class OvsVanilla(IVSwitchOvs): bridge = self._bridges[switch_name] of_port = bridge.add_port(tap_name, []) return (tap_name, of_port) + + def add_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + """ + raise NotImplementedError() + + def del_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + """ + raise NotImplementedError() + + def dump_connections(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError() diff --git a/vswitches/vpp_dpdk_vhost.py b/vswitches/vpp_dpdk_vhost.py new file mode 100644 index 00000000..d0d9e2ac --- /dev/null +++ b/vswitches/vpp_dpdk_vhost.py @@ -0,0 +1,403 @@ +# Copyright 2017 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. + +"""VSPERF VPP implementation using DPDK and vhostuser vports +""" + +import logging +import os +import copy +import re +import pexpect + +from src.dpdk import dpdk +from conf import settings as S +from vswitches.vswitch import IVSwitch +from tools import tasks + +# pylint: disable=too-many-public-methods +class VppDpdkVhost(IVSwitch, tasks.Process): + """ VPP with DPDK support + """ + _proc_name = 'vpp' + _bridge_idx_counter = 100 + + def __init__(self): + """See IVswitch for general description + """ + self._logfile = os.path.join(S.getValue('LOG_DIR'), + S.getValue('LOG_FILE_VPP')) + self._logger = logging.getLogger(__name__) + self._expect = r'vpp#' + self._timeout = 30 + self._vswitch_args = [] + self._cmd = [] + self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']] + self._stamp = None + self._logger = logging.getLogger(__name__) + self._phy_ports = [] + self._virt_ports = [] + self._switches = {} + + # configure DPDK NICs + tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS')) + if 'dpdk' not in tmp_args: + tmp_args['dpdk'] = [] + + # override socket-mem settings + for tmp_arg in tmp_args['dpdk']: + if tmp_arg.startswith('socket-mem'): + tmp_args['dpdk'].remove(tmp_arg) + tmp_args['dpdk'].append('socket-mem ' + + ','.join(S.getValue('DPDK_SOCKET_MEM'))) + + for nic in S.getValue('NICS'): + tmp_args['dpdk'].append("dev {}".format(nic['pci'])) + self._vswitch_args = self._process_vpp_args(tmp_args) + + def _get_nic_info(self, key='Name'): + """Read NIC info from VPP and return NIC details in a dictionary + indexed by given ``key`` + + :param key: Name of the key to be used for indexing result dictionary + + :returns: Dictionary with NIC infos including their PCI addresses + """ + result = {} + output = self.run_vppctl(['show', 'hardware', 'brief']) + # parse output and store basic info about NICS + ifaces = output[0].split('\n') + keys = ifaces[0].split() + keys.append('Pci') + keyidx = keys.index(key) + for iface in ifaces[1:]: + tmpif = iface.split() + # get PCI address of given interface + output = self.run_vppctl(['show', 'hardware', tmpif[1], 'detail']) + match = re.search(r'pci address:\s*([\d:\.]+)', output[0]) + if match: + # normalize PCI address, e.g. 0000:05:10.01 => 0000:05:10.1 + tmp_pci = match.group(1).split('.') + tmp_pci[1] = str(int(tmp_pci[1])) + tmpif.append('.'.join(tmp_pci)) + else: + tmpif.append(None) + # store only NICs with reasonable index + if tmpif[keyidx] is not None: + result[tmpif[keyidx]] = dict(zip(keys, tmpif)) + + return result + + def _process_vpp_args(self, args): + """Produce VPP CLI args from input dictionary ``args`` + """ + cli_args = [] + for cfg_key in args: + cli_args.append(cfg_key) + cli_args.append("{{ {} }}".format(' '.join(args[cfg_key]))) + + self._logger.debug("VPP CLI args: %s", cli_args) + return cli_args + + + def start(self): + """Activates DPDK kernel modules and starts VPP + + :raises: pexpect.EOF, pexpect.TIMEOUT + """ + dpdk.init() + self._logger.info("Starting VPP...") + + self._cmd = self._cmd_template + self._vswitch_args + + try: + tasks.Process.start(self) + self.relinquish() + except (pexpect.EOF, pexpect.TIMEOUT) as exc: + logging.error("Exception during VPP start.") + raise exc + + self._logger.info("VPP...Started.") + + def stop(self): + """See IVswitch for general description + + Kills VPP and removes DPDK kernel modules. + """ + self._logger.info("Terminating VPP...") + self.kill() + self._logger.info("VPP...Terminated.") + dpdk.cleanup() + + def kill(self, signal='-15', sleep=10): + """See IVswitch for general description + + Kills ``vpp`` + """ + # try to get VPP pid + output = self.run_vppctl(['show', 'version', 'verbose']) + match = re.search(r'Current PID:\s*([0-9]+)', output[0]) + if match: + vpp_pid = match.group(1) + tasks.terminate_task(vpp_pid, logger=self._logger) + + # in case, that pid was not detected or sudo envelope + # has not been terminated yet + tasks.Process.kill(self, signal, sleep) + + def add_switch(self, switch_name, dummy_params=None): + """See IVswitch for general description + """ + if switch_name in self._switches: + self._logger.warning("switch %s already exists...", switch_name) + else: + self._switches[switch_name] = self._bridge_idx_counter + self._bridge_idx_counter += 1 + + def del_switch(self, switch_name): + """See IVswitch for general description + """ + if switch_name in self._switches: + del self._switches[switch_name] + + def add_phy_port(self, dummy_switch_name): + """See IVswitch for general description + :raises: RuntimeError + """ + # get list of physical interfaces with PCI addresses + vpp_nics = self._get_nic_info(key='Pci') + # check if there are any NICs left + if len(self._phy_ports) >= len(S.getValue('NICS')): + raise RuntimeError('All available NICs are already configured!') + + nic = S.getValue('NICS')[len(self._phy_ports)] + if not nic['pci'] in vpp_nics: + raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci'])) + nic_name = vpp_nics[nic['pci']]['Name'] + self._phy_ports.append(nic_name) + self.run_vppctl(['set', 'int', 'state', nic_name, 'up']) + return (nic_name, vpp_nics[nic['pci']]['Idx']) + + def add_vport(self, dummy_switch_name): + """See IVswitch for general description + """ + socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports)) + output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name, 'server'] + + S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS')) + nic_name = output[0] + self._virt_ports.append(nic_name) + self.run_vppctl(['set', 'int', 'state', nic_name, 'up']) + return (nic_name, None) + + def del_port(self, switch_name, port_name): + """See IVswitch for general description + """ + if port_name in self._phy_ports: + self.run_vppctl(['set', 'int', 'state', port_name, 'down']) + self._phy_ports.remove(port_name) + elif port_name in self._virt_ports: + self.run_vppctl(['set', 'int', 'state', port_name, 'down']) + self.run_vppctl(['delete', 'vhost-user', port_name]) + self._virt_ports.remove(port_name) + else: + self._logger.warning("Port %s is not configured.", port_name) + + def add_l2patch(self, port1, port2, bidir=False): + """Create l2patch connection between given ports + """ + self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2]) + if bidir: + self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1]) + + def add_xconnect(self, port1, port2, bidir=False): + """Create l2patch connection between given ports + """ + self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2]) + if bidir: + self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port2, port1]) + + def add_bridge(self, switch_name, port1, port2, dummy_bidir=False): + """Add given ports to bridge ``switch_name`` + """ + self.run_vppctl(['set', 'interface', 'l2', 'bridge', port1, + str(self._switches[switch_name])]) + self.run_vppctl(['set', 'interface', 'l2', 'bridge', port2, + str(self._switches[switch_name])]) + + def add_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + + :raises: RuntimeError + """ + mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE') + if mode == 'l2patch': + self.add_l2patch(port1, port2, bidir) + elif mode == 'xconnect': + self.add_xconnect(port1, port2, bidir) + elif mode == 'bridge': + self.add_bridge(switch_name, port1, port2) + else: + raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode) + + def del_l2patch(self, port1, port2, bidir=False): + """Remove l2patch connection between given ports + + :param port1: port to be used in connection + :param port2: port to be used in connection + :param bidir: switch between uni and bidirectional traffic + """ + self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del']) + if bidir: + self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1, 'del']) + + def del_xconnect(self, dummy_port1, dummy_port2, dummy_bidir=False): + """Remove xconnect connection between given ports + """ + self._logger.warning('VPP: Removal of l2 xconnect is not implemented.') + + def del_bridge(self, dummy_switch_name, dummy_port1, dummy_port2): + """Remove given ports from the bridge + """ + self._logger.warning('VPP: Removal of interfaces from bridge is not implemented.') + + def del_connection(self, switch_name, port1, port2, bidir=False): + """See IVswitch for general description + + :raises: RuntimeError + """ + mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE') + if mode == 'l2patch': + self.del_l2patch(port1, port2, bidir) + elif mode == 'xconnect': + self.del_xconnect(port1, port2, bidir) + elif mode == 'bridge': + self.del_bridge(switch_name, port1, port2) + else: + raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode) + + def dump_l2patch(self): + """Dump l2patch connections + """ + self.run_vppctl(['show', 'l2patch']) + + def dump_xconnect(self): + """Dump l2 xconnect connections + """ + self._logger.warning("VPP: Dump of l2 xconnections is not supported.") + + def dump_bridge(self, switch_name): + """Show bridge details + + :param switch_name: switch on which to operate + """ + self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int']) + + def dump_connections(self, switch_name): + """See IVswitch for general description + + :raises: RuntimeError + """ + mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE') + if mode == 'l2patch': + self.dump_l2patch() + elif mode == 'xconnect': + self.dump_xconnect() + elif mode == 'bridge': + self.dump_bridge(switch_name) + else: + raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode) + + def run_vppctl(self, args, check_error=False): + """Run ``vppctl`` with supplied arguments. + + :param args: Arguments to pass to ``vppctl`` + :param check_error: Throw exception on error + + :return: None + """ + cmd = ['sudo', S.getValue('TOOLS')['vppctl']] + args + return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error) + + # + # Validate methods + # + def validate_add_switch(self, dummy_result, switch_name, dummy_params=None): + """Validate - Create a new logical switch with no ports + """ + return switch_name in self._switches + + def validate_del_switch(self, dummy_result, switch_name): + """Validate removal of switch + """ + return not self.validate_add_switch(dummy_result, switch_name) + + def validate_add_phy_port(self, result, dummy_switch_name): + """ Validate that physical port was added to bridge. + """ + return result[0] in self._phy_ports + + def validate_add_vport(self, result, dummy_switch_name): + """ Validate that virtual port was added to bridge. + """ + return result[0] in self._virt_ports + + def validate_del_port(self, dummy_result, dummy_switch_name, port_name): + """ Validate that port_name was removed from bridge. + """ + return not (port_name in self._phy_ports or port_name in self._virt_ports) + + # pylint: disable=no-self-use + def validate_run_vppctl(self, result, dummy_args, dummy_check_error=False): + """validate execution of ``vppctl`` with supplied arguments. + """ + # there shouldn't be any stderr + return not result[1] + + # + # Non implemented methods + # + def add_flow(self, switch_name, flow, cache='off'): + """See IVswitch for general description + """ + raise NotImplementedError() + + def del_flow(self, switch_name, flow=None): + """See IVswitch for general description + """ + raise NotImplementedError() + + def dump_flows(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError() + + def add_route(self, switch_name, network, destination): + """See IVswitch for general description + """ + raise NotImplementedError() + + def set_tunnel_arp(self, ip_addr, mac_addr, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError() + + def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None): + """See IVswitch for general description + """ + raise NotImplementedError() + + def get_ports(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError() diff --git a/vswitches/vswitch.py b/vswitches/vswitch.py index 73e0a0c3..893bd1ff 100644 --- a/vswitches/vswitch.py +++ b/vswitches/vswitch.py @@ -130,6 +130,39 @@ class IVSwitch(object): """ raise NotImplementedError() + def add_connection(self, switch_name, port1, port2, bidir=False): + """Creates connection between given ports. + + :param switch_name: switch on which to operate + :param port1: port to be used in connection + :param port2: port to be used in connection + :param bidir: switch between uni and bidirectional traffic + + :raises: RuntimeError + """ + raise NotImplementedError() + + def del_connection(self, switch_name, port1, port2, bidir=False): + """Remove connection between two interfaces. + + :param switch_name: switch on which to operate + :param port1: port to be used in connection + :param port2: port to be used in connection + :param bidir: switch between uni and bidirectional traffic + + :raises: RuntimeError + """ + raise NotImplementedError() + + def dump_connections(self, switch_name): + """Dump connections between interfaces. + + :param switch_name: switch on which to operate + + :raises: RuntimeError + """ + raise NotImplementedError() + def dump_flows(self, switch_name): """Dump flows from the logical switch |