This tool will be a replacement for the current ovs-vlan-test
utility. Besides from connectivity issues it will also be able
to detect performance related issues in Open vSwitch setups.
Currently it uses UDP and TCP protocols for stressing.

Issue #6976
---
 Makefile.am                       |    1 +
 NEWS                              |    4 +
 debian/automake.mk                |    3 +
 debian/changelog                  |    4 +
 debian/control                    |    9 ++
 debian/openvswitch-test.dirs      |    1 +
 debian/openvswitch-test.install   |    2 +
 debian/openvswitch-test.manpages  |    1 +
 debian/python-openvswitch.install |    2 +-
 manpages.mk                       |   10 +++
 python/ovstest/__init__.py        |    1 +
 python/ovstest/automake.mk        |   32 ++++++++
 python/ovstest/dirs.py            |    8 ++
 python/ovstest/ovsargs.py         |   84 ++++++++++++++++++++
 python/ovstest/ovsrpcserver.py    |  157 +++++++++++++++++++++++++++++++++++++
 python/ovstest/ovstcp.py          |  123 +++++++++++++++++++++++++++++
 python/ovstest/ovsudp.py          |   76 ++++++++++++++++++
 utilities/automake.mk             |    6 ++
 utilities/ovs-test.8.in           |  108 +++++++++++++++++++++++++
 utilities/ovs-test.in             |  139 ++++++++++++++++++++++++++++++++
 utilities/ovs-vlan-test.8.in      |    1 +
 21 files changed, 771 insertions(+), 1 deletions(-)
 create mode 100644 debian/openvswitch-test.dirs
 create mode 100644 debian/openvswitch-test.install
 create mode 100644 debian/openvswitch-test.manpages
 create mode 100644 python/ovstest/__init__.py
 create mode 100644 python/ovstest/automake.mk
 create mode 100644 python/ovstest/dirs.py
 create mode 100644 python/ovstest/ovsargs.py
 create mode 100644 python/ovstest/ovsrpcserver.py
 create mode 100644 python/ovstest/ovstcp.py
 create mode 100644 python/ovstest/ovsudp.py
 create mode 100644 utilities/ovs-test.8.in
 create mode 100644 utilities/ovs-test.in

diff --git a/Makefile.am b/Makefile.am
index 401d23a..de6fca3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -196,3 +196,4 @@ include rhel/automake.mk
 include xenserver/automake.mk
 include python/ovs/automake.mk
 include python/compat/automake.mk
+include python/ovstest/automake.mk
diff --git a/NEWS b/NEWS
index f0baf37..5caa5ca 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,10 @@ v1.3.0 - xx xxx xxxx
         information about all ports with LACP enabled.
     - ovs-dpctl:
       - New "set-if" command to modify a datapath port's configuration.
+    - ovs-test:
+      - A new distributed testing tool that allows to diagnose performance and
+        connectivity issues. This tool currently is not included in RH or
+        Xen packages.
     - ovs-vswitchd:
       - The software switch now supports 255 OpenFlow tables, instead
         of just one.  By default, only table 0 is consulted, but the
diff --git a/debian/automake.mk b/debian/automake.mk
index d289830..755d727 100644
--- a/debian/automake.mk
+++ b/debian/automake.mk
@@ -40,6 +40,9 @@ EXTRA_DIST += \
        debian/openvswitch-switch.postinst \
        debian/openvswitch-switch.postrm \
        debian/openvswitch-switch.template \
+       debian/openvswitch-test.dirs \
+       debian/openvswitch-test.install \
+       debian/openvswitch-test.manpages \
        debian/ovsdbmonitor.install \
        debian/ovsdbmonitor.manpages \
        debian/ovs-monitor-ipsec \
diff --git a/debian/changelog b/debian/changelog
index dbeb764..9e3d3ac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -12,6 +12,10 @@ openvswitch (1.3.0-1) unstable; urgency=low
         information about all interfaces with CFM enabled.
       - If no argument is provided for "lacp/show", displays detailed
         information about all ports with LACP enabled.
+    - ovs-test:
+      - A new distributed testing tool that allows to diagnose performance and
+        connectivity issues. This tool currently is not included in RH or
+        Xen packages.
     - ovs-vswitchd:
       - The software switch now supports 255 OpenFlow tables, instead
         of just one.  By default, only table 0 is consulted, but the
diff --git a/debian/control b/debian/control
index 1f3387a..c350fac 100644
--- a/debian/control
+++ b/debian/control
@@ -138,3 +138,12 @@ Description: Open vSwitch graphical monitoring tool
  to "ovs-vsctl list <table>").
  .
  Open vSwitch is a full-featured software-based Ethernet switch.
+
+Package: openvswitch-test
+Architecture: all
+Depends: python-twisted-web, python-argparse
+Description: Open vSwitch test package
+ This package contains utilities that are useful to diagnose
+ performance and connectivity issues in Open vSwitch setup.
+ .
+ Open vSwitch is a full-featured software-based Ethernet switch.
diff --git a/debian/openvswitch-test.dirs b/debian/openvswitch-test.dirs
new file mode 100644
index 0000000..daaae31
--- /dev/null
+++ b/debian/openvswitch-test.dirs
@@ -0,0 +1 @@
+usr/share/pyshared/ovstest/
diff --git a/debian/openvswitch-test.install b/debian/openvswitch-test.install
new file mode 100644
index 0000000..a152aff
--- /dev/null
+++ b/debian/openvswitch-test.install
@@ -0,0 +1,2 @@
+usr/share/openvswitch/python/ovstest usr/lib/python2.4/site-packages/
+usr/bin/ovs-test
diff --git a/debian/openvswitch-test.manpages b/debian/openvswitch-test.manpages
new file mode 100644
index 0000000..683c978
--- /dev/null
+++ b/debian/openvswitch-test.manpages
@@ -0,0 +1 @@
+_debian/utilities/ovs-test.8
diff --git a/debian/python-openvswitch.install 
b/debian/python-openvswitch.install
index ef84d2b..6779298 100644
--- a/debian/python-openvswitch.install
+++ b/debian/python-openvswitch.install
@@ -1 +1 @@
-usr/share/openvswitch/python/* usr/lib/python2.4/site-packages/
+usr/share/openvswitch/python/ovs usr/lib/python2.4/site-packages/
diff --git a/manpages.mk b/manpages.mk
index c722d5d..48f2db5 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -150,6 +150,16 @@ utilities/ovs-tcpundump.1.in:
 lib/common-syn.man:
 lib/common.man:
 
+utilities/ovs-test.8: \
+       utilities/ovs-test.8.in \
+       lib/common-syn.man \
+       lib/common.man \
+       utilities/ovs-vlan-bugs.man
+utilities/ovs-test.8.in:
+lib/common-syn.man:
+lib/common.man:
+utilities/ovs-vlan-bugs.man:
+
 utilities/ovs-vlan-bug-workaround.8: \
        utilities/ovs-vlan-bug-workaround.8.in \
        lib/common.man \
diff --git a/python/ovstest/__init__.py b/python/ovstest/__init__.py
new file mode 100644
index 0000000..218d892
--- /dev/null
+++ b/python/ovstest/__init__.py
@@ -0,0 +1 @@
+# This file intentionally left blank.
diff --git a/python/ovstest/automake.mk b/python/ovstest/automake.mk
new file mode 100644
index 0000000..d7858da
--- /dev/null
+++ b/python/ovstest/automake.mk
@@ -0,0 +1,32 @@
+run_python = PYTHONPATH=$(top_srcdir)/python:$$PYTHON_PATH $(PYTHON)
+
+ovstest_pyfiles = \
+       python/ovstest/__init__.py \
+       python/ovstest/ovsargs.py \
+       python/ovstest/ovsrpcserver.py \
+       python/ovstest/ovstcp.py \
+       python/ovstest/ovsudp.py
+EXTRA_DIST += $(ovstest_pyfiles) python/ovstest/dirs.py
+
+if HAVE_PYTHON
+nobase_pkgdata_DATA += $(ovstest_pyfiles)
+ovstest-install-data-local:
+       $(MKDIR_P) python/ovstest
+       (echo "import os" && \
+        echo 'PKGDATADIR = os.environ.get("OVS_PKGDATADIR", 
"""$(pkgdatadir)""")' && \
+        echo 'RUNDIR = os.environ.get("OVS_RUNDIR", """@RUNDIR@""")' && \
+        echo 'LOGDIR = os.environ.get("OVS_LOGDIR", """@LOGDIR@""")' && \
+        echo 'BINDIR = os.environ.get("OVS_BINDIR", """$(bindir)""")') \
+               > python/ovstest/dirs.py.tmp
+       $(MKDIR_P) $(DESTDIR)$(pkgdatadir)/python/ovstest
+       $(INSTALL_DATA) python/ovstest/dirs.py.tmp 
$(DESTDIR)$(pkgdatadir)/python/ovstest/dirs.py
+       rm python/ovstest/dirs.py.tmp
+else
+ovstest-install-data-local:
+       @:
+endif
+install-data-local: ovstest-install-data-local
+
+UNINSTALL_LOCAL += ovstest-uninstall-local
+ovstest-uninstall-local:
+       rm -f $(DESTDIR)$(pkgdatadir)/python/ovstest/dirs.py
diff --git a/python/ovstest/dirs.py b/python/ovstest/dirs.py
new file mode 100644
index 0000000..5b006cc
--- /dev/null
+++ b/python/ovstest/dirs.py
@@ -0,0 +1,8 @@
+# These are the default directories.  They will be replaced by the
+# configured directories at install time.
+
+import os
+PKGDATADIR = os.environ.get("OVS_PKGDATADIR", "/usr/local/share/openvswitch")
+RUNDIR = os.environ.get("OVS_RUNDIR", "/var/run")
+LOGDIR = os.environ.get("OVS_LOGDIR", "/usr/local/var/log")
+BINDIR = os.environ.get("OVS_BINDIR", "/usr/local/bin")
diff --git a/python/ovstest/ovsargs.py b/python/ovstest/ovsargs.py
new file mode 100644
index 0000000..c42b056
--- /dev/null
+++ b/python/ovstest/ovsargs.py
@@ -0,0 +1,84 @@
+"""
+ovsargs provide argument parsing for ovs-test utility
+"""
+
+import argparse
+import socket
+import re
+
+
+def ip( string ):
+    """Verifies if string is valid IP address"""
+    try:
+        socket.inet_aton( string )
+    except socket.error:
+        raise argparse.ArgumentTypeError( "Not a valid IPv4 address" )
+    return string
+
+
+def port( string ):
+    """Converts string into Port"""
+    try:
+        port_number = int( string )
+        if port_number < 1 or port_number > 65535:
+            raise argparse.ArgumentTypeError( "Port is out of range" )
+    except ValueError:
+        raise argparse.ArgumentTypeError( "Port is not an integer" )
+    return port_number
+
+
+def ip_port( string ):
+    """Converts a string into IP and Port pair"""
+    value = string.split( ':' )
+    if len( value ) == 2:
+        ip( value[0] )
+    else:
+        raise argparse.ArgumentTypeError( "IP address and Port must be "\
+                                          "colon-separated" )
+    return ( value[0], port( value[1] ) )
+
+
+def ip_port_optionalip( string ):
+    """Converts a string into IP and port pair plus another optional IP that
+    in case of absence is the same as the first IP address"""
+    value = string.split( ',' )
+    if len( value ) == 1: #  second IP address will be the same as first IP
+        ret = ip_port( value[0] )
+        return ( ret[0], ret[1], ret[0] )
+    elif len( value ) == 2:
+        ret1 = ip_port( value[0] )
+        ret2 = ip( value[1] )
+        return ( ret1[0], ret1[1], ret2 )
+    else:
+        raise argparse.ArgumentTypeError( "IP address and port from second" \
+        "IP address must be comma separated " )
+
+
+def bandwidth( string ):
+    """Converts string into bandwidth (long in units bytes/second)"""
+    if re.match( "^[1-9][0-9]*[MK]?$", string ) == None:
+        raise argparse.ArgumentTypeError( "Not a valid target bandwidth" )
+    bwidth = string.replace( "M", "000000" )
+    bwidth = bwidth.replace( "K", "000" )
+    return long( bwidth )
+
+
+def ovs_initialize_args():
+    """Initialize args for ovstest utility"""
+    parser = argparse.ArgumentParser( description = 'Test ovs connectivity' )
+    parser.add_argument( '-v', '--version', action = 'version',
+                version = 'ovs-test (Open vSwitch) @VERSION@' )
+    parser.add_argument( "-b", "--bandwidth", action = 'store',
+                dest = "targetBandwidth", default = "1M", type = bandwidth,
+                help = 'Specify target bandwidth for UDP tests' )
+    group = parser.add_mutually_exclusive_group( required = True )
+    group.add_argument( "-s", "--server", action = "store", dest = "port",
+                type = port, help = 'run in server mode and wait client '\
+                'to connect to this port' )
+    group.add_argument( '-c', "--client", action = "store", nargs = 2,
+                dest = "servers", type = ip_port_optionalip,
+                help = 'run in client mode and do tests between these '\
+                'two servers (controlipX:controlportX[,testipX])' )
+    return parser.parse_args()
+
+
diff --git a/python/ovstest/ovsrpcserver.py b/python/ovstest/ovsrpcserver.py
new file mode 100644
index 0000000..79807cc
--- /dev/null
+++ b/python/ovstest/ovsrpcserver.py
@@ -0,0 +1,157 @@
+"""
+ovsrpcserver is an XML RPC server that allows RPC client to initiate tests
+"""
+
+from twisted.internet import reactor
+from twisted.web import xmlrpc, server
+from twisted.internet.error import CannotListenError
+import ovsudp
+import ovstcp
+import ovsargs
+
+
+class TestArena( xmlrpc.XMLRPC ):
+    """
+    This class contains all the functions that ovstest will call 
+    remotely. The caller is responsible to use designated handleIds
+    for designated methods (e.g. do not mix UDP and TCP handles).
+    """
+
+    def __init__( self ):
+        xmlrpc.XMLRPC.__init__( self )
+        self.handle_id = 1
+        self.handle_map = {}
+
+    def __acquire_handle( self, value ):
+        """
+        Allocates new handle and assigns value object to it
+        """
+        handle = self.handle_id
+        self.handle_map[handle] = value
+        self.handle_id += 1
+        return handle
+
+    def __get_handle_resources( self, handle ):
+        """
+        Return resources that were assigned to handle
+        """
+        return self.handle_map[handle]
+
+    def __delete_handle( self, handle ):
+        """
+        Releases handle from handle_map
+        """
+        del self.handle_map[handle]
+
+
+    def xmlrpc_create_udp_listener( self, port ):
+        """
+        Creates a UDP listener that will receive packets from UDP sender
+        """
+        try:
+            listener = ovsudp.UdpListener()
+            reactor.listenUDP( port, listener )
+            handle_id = self.__acquire_handle( listener )
+        except CannotListenError:
+            return -1
+        return handle_id
+
+    def xmlrpc_create_udp_sender( self, host, count, size, duration ):
+        """
+        Send UDP datagrams to UDP listener
+        """
+        sender = ovsudp.UdpSender( tuple( host ), count, size, duration )
+        reactor.listenUDP( 0, sender )
+        handle_id = self.__acquire_handle( sender )
+        return handle_id
+
+    def xmlrpc_get_udp_listener_results( self, handle ):
+        """
+        Returns number of datagrams that were received
+        """
+        ( listener ) = self.__get_handle_resources( handle )
+        return listener.getResults()
+
+    def xmlrpc_get_udp_sender_results( self, handle ):
+        """
+        Returns number of datagrams that were sent
+        """
+        ( sender ) = self.__get_handle_resources( handle )
+        return sender.getResults()
+
+    def xmlrpc_close_udp_listener( self, handle ):
+        """
+        Releases UdpListener and all its resources 
+        """
+        ( listener ) = self.__get_handle_resources( handle )
+        listener.transport.loseConnection()
+        self.__delete_handle( handle )
+        return 0
+
+    def xmlrpc_close_udp_sender( self, handle ):
+        """
+        Releases UdpSender and all its resources
+        """
+        ( sender ) = self.__get_handle_resources( handle )
+        sender.transport.loseConnection()
+        self.__delete_handle( handle )
+        return 0
+
+    def xmlrpc_create_tcp_listener( self, port ):
+        """
+        Creates a TcpListener that will accept connection from TcpSender
+        """
+        try:
+            listener = ovstcp.TcpListenerFactory()
+            port = reactor.listenTCP( port, listener )
+            handle_id = self.__acquire_handle( ( listener, port ) )
+            return handle_id
+        except CannotListenError:
+            return -1
+
+    def xmlrpc_create_tcp_sender( self, his_ip, his_port, duration ):
+        """
+        Creates a TcpSender that will connect to TcpListener
+        """
+        sender = ovstcp.TcpSenderFactory( duration )
+        connector = reactor.connectTCP( his_ip, his_port, sender )
+        handle_id = self.__acquire_handle( ( sender, connector ) )
+        return handle_id
+
+    def xmlrpc_get_tcp_listener_results( self, handle ):
+        """
+        Returns number of bytes received
+        """
+        ( listener, _ ) = self.__get_handle_resources( handle )
+        return listener.getResults()
+
+    def xmlrpc_get_tcp_sender_results( self, handle ):
+        """
+        Returns number of bytes sent
+        """
+        ( sender, _ ) = self.__get_handle_resources( handle )
+        return sender.getResults()
+
+    def xmlrpc_close_tcp_listener( self, handle ):
+        """
+        Releases UdpListener and all its resources 
+        """
+        ( _, port ) = self.__get_handle_resources( handle )
+        port.loseConnection()
+        self.__delete_handle( handle )
+        return 0
+
+    def xmlrpc_close_tcp_sender( self, handle ):
+        """
+        Releases UdpSender and all its resources
+        """
+        ( _, connector ) = self.__get_handle_resources( handle )
+        connector.disconnect()
+        self.__delete_handle( handle )
+        return 0
+
+
+def start_rpc_server( port ):
+    RPC_SERVER = TestArena()
+    reactor.listenTCP( port, server.Site( RPC_SERVER ) )
+    reactor.run()
diff --git a/python/ovstest/ovstcp.py b/python/ovstest/ovstcp.py
new file mode 100644
index 0000000..47f3870
--- /dev/null
+++ b/python/ovstest/ovstcp.py
@@ -0,0 +1,123 @@
+"""
+ovstcp contains listener and sender classes for TCP protocol
+"""
+
+from twisted.internet.protocol import Factory, ClientFactory, Protocol
+from twisted.internet import interfaces
+from zope.interface import implements
+import time
+
+
+class TcpListenerConnection( Protocol ):
+    """
+    This per-connection class is instantiated each time sender connects
+    """
+    def __init__( self ):
+        self.stats = 0
+
+    def connectionMade( self ):
+        print "Started TCP Listener connection"
+
+    def dataReceived( self, data ):
+        self.stats += len( data )
+
+    def connectionLost( self, reason ):
+        print "Stopped TCP Listener connection"
+        self.factory.stats += self.stats
+
+
+class TcpListenerFactory( Factory ):
+    """
+    This per-listening socket class is used to 
+    instantiate TcpListenerConnections
+    """
+    protocol = TcpListenerConnection
+
+    def __init__( self ):
+        self.stats = 0
+
+    def startFactory( self ):
+        print "Starting TCP listener factory"
+
+    def stopFactory( self ):
+        print "Stopping TCP listener factory"
+
+    def getResults( self ):
+        """ returns the number of bytes received as string"""
+    #XML RPC does not support 64bit ints (http://bugs.python.org/issue2985)
+    #so we have to convert the amount of bytes into a string
+        return str( self.stats )
+
+
+class Producer:
+    """This producer generates infinite byte stream for given amount time"""
+    implements( interfaces.IPushProducer )
+
+    def __init__( self, proto, duration ):
+        self.proto = proto
+        self.start = time.time()
+        self.produced = 0
+        self.paused = False
+        self.data = "X" * 65535
+        self.duration = duration
+
+    def pauseProducing( self ):
+        """ This function is called whenever write() to socket would block"""
+        self.paused = True
+
+    def resumeProducing( self ):
+        """This function is called whenever socket becomes writable"""
+        self.paused = False
+        current = time.time()
+        while not self.paused and current < self.start + self.duration:
+            self.proto.transport.write( self.data )
+            self.produced += len( self.data )
+            current = time.time()
+        if current >= self.start + self.duration:
+            self.proto.factory.stats += self.produced
+            self.proto.transport.unregisterProducer()
+            self.proto.transport.loseConnection()
+
+    def stopProducing( self ):
+        pass
+
+
+class TcpSenderConnection( Protocol ):
+    """
+    TCP connection instance class that delegates all sending to Producer.
+    """
+
+    def connectionMade( self ):
+        print "Started TCP sender connection"
+        producer = Producer( self, self.factory.duration )
+        self.transport.registerProducer( producer, True )
+        producer.resumeProducing()
+
+    def dataReceived( self, data ):
+        print "Sender received data!", data
+        self.transport.loseConnection()
+
+    def connectionLost( self, reason ):
+        print "Stopped TCP sender connection"
+
+
+class TcpSenderFactory( ClientFactory ):
+    """
+    This factory is responsible to instantiate TcpSenderConnection classes 
+    each time sender initiates connection
+    """
+    protocol = TcpSenderConnection
+
+    def __init__( self, duration ):
+        self.duration = duration
+        self.stats = 0
+
+    def startFactory( self ):
+        print "Starting TCP sender factory"
+
+    def stopFactory( self ):
+        print "Stopping TCP sender factory"
+
+    def getResults( self ):
+        """Returns amount of bytes sent to the Listener as a string"""
+        return str( self.stats )
diff --git a/python/ovstest/ovsudp.py b/python/ovstest/ovsudp.py
new file mode 100644
index 0000000..059ecd1
--- /dev/null
+++ b/python/ovstest/ovsudp.py
@@ -0,0 +1,76 @@
+"""
+ovsudp contains listener and sender classes for UDP protocol
+"""
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet.task import LoopingCall
+import array, struct, time
+
+
+class UdpListener( DatagramProtocol ):
+    """
+    Class that will listen for incoming UDP packets
+    """
+    def __init__( self ):
+        self.stats = []
+
+    def startProtocol( self ):
+        print "Starting UDP listener"
+
+    def stopProtocol( self ):
+        print "Stopping UDP listener"
+
+    def datagramReceived( self, data, ( _1, _2 ) ):
+        """Each time datagram is received this function remembers its ID"""
+        try:
+            self.stats.append( struct.unpack_from( "Q", data, 0 ) )
+        except struct.error:
+            pass #ignore packets that are less than 8 bytes of size
+
+    def getResults( self ):
+        """Returns number of packets that were actualy received"""
+        return len( self.stats )
+
+
+class UdpSender( DatagramProtocol ):
+    """
+    Class that wil send UDP packets to UDP Listener
+    """
+    def __init__( self, host, count, size, duration ):
+        #LoopingCall does not know whether UDP socket is actually writable
+        self.looper = None
+        self.host = host
+        self.count = count
+        self.duration = duration
+        self.start = time.time()
+        self.sent = 0
+        self.data = array.array( 'c', 'X' * size )
+
+    def startProtocol( self ):
+        print "Starting UDP sender"
+        self.looper = LoopingCall( self.sendData )
+        period = self.duration / float( self.count )
+        self.looper.start( period , now = False )
+
+    def stopProtocol( self ):
+        print "Stopping UDP sender"
+        if ( self.looper != None ):
+            self.looper.stop()
+            self.looper = None
+
+    def datagramReceived( self, data, ( host, port ) ):
+        pass
+
+    def sendData( self ):
+        """This function is called """
+        if self.start + self.duration < time.time():
+            self.looper.stop()
+            self.looper = None
+
+        self.sent += 1
+        struct.pack_into( 'Q', self.data, 0, self.sent )
+        self.transport.write( self.data, self.host )
+
+    def getResults( self ):
+        """ Returns number of packets that were sent"""
+        return self.sent
diff --git a/utilities/automake.mk b/utilities/automake.mk
index 420d5fc..df94dd1 100644
--- a/utilities/automake.mk
+++ b/utilities/automake.mk
@@ -9,6 +9,7 @@ if HAVE_PYTHON
 bin_SCRIPTS += \
        utilities/ovs-pcap \
        utilities/ovs-tcpundump \
+       utilities/ovs-test \
        utilities/ovs-vlan-test
 endif
 noinst_SCRIPTS += utilities/ovs-pki-cgi
@@ -23,6 +24,7 @@ EXTRA_DIST += \
        utilities/ovs-pki.in \
        utilities/ovs-save \
        utilities/ovs-tcpundump.in \
+       utilities/ovs-test.in \
        utilities/ovs-vlan-test.in
 MAN_ROOTS += \
        utilities/ovs-appctl.8.in \
@@ -36,6 +38,7 @@ MAN_ROOTS += \
        utilities/ovs-pki.8.in \
        utilities/ovs-tcpundump.1.in \
        utilities/ovs-vlan-bug-workaround.8.in \
+       utilities/ovs-test.8.in \
        utilities/ovs-vlan-test.8.in \
        utilities/ovs-vsctl.8.in
 MAN_FRAGMENTS += utilities/ovs-vlan-bugs.man
@@ -55,6 +58,8 @@ DISTCLEANFILES += \
        utilities/ovs-pki.8 \
        utilities/ovs-tcpundump \
        utilities/ovs-tcpundump.1 \
+       utilities/ovs-test \
+       utilities/ovs-test.8 \
        utilities/ovs-vlan-test \
        utilities/ovs-vlan-test.8 \
        utilities/ovs-vlan-bug-workaround.8 \
@@ -71,6 +76,7 @@ man_MANS += \
        utilities/ovs-pki.8 \
        utilities/ovs-tcpundump.1 \
        utilities/ovs-vlan-bug-workaround.8 \
+       utilities/ovs-test.8 \
        utilities/ovs-vlan-test.8 \
        utilities/ovs-vsctl.8
 dist_man_MANS += utilities/ovs-ctl.8
diff --git a/utilities/ovs-test.8.in b/utilities/ovs-test.8.in
new file mode 100644
index 0000000..376cc52
--- /dev/null
+++ b/utilities/ovs-test.8.in
@@ -0,0 +1,108 @@
+.TH ovs\-test 1 "October 2011" "Open vSwitch" "Open vSwitch Manual"
+.
+.SH NAME
+\fBovs\-test\fR \- check Linux drivers for performance and vlan problems
+.
+.SH SYNOPSIS
+\fBovs\-test\fR \fB\-s\fR  \fIport\fR
+.PP
+\fBovs\-test\fR \fB\-c\fR  \fIserver1\fR
+\fIserver2\fR  [\fB\-b\fR  \fIbandwidth\fR]
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovs\-test\fR program may be used to check for problems sending
+802.1Q traffic which may occur when running Open vSwitch. These problems can
+occur when Open vSwitch is used to send 802.1Q traffic through physical
+interfaces running certain drivers of certain Linux kernel versions. To run a
+test, configure Open vSwitch to tag traffic originating from \fIserver1\fR and
+forward it to the \fIserver2\fR. On both servers run \fBovs\-test\fR
+in server mode. And then on any other host run the \fBovs\-test\fR in client
+mode. The client will connect to both  \fBovs\-test\fR servers and schedule
+tests between them. \fBovs\-test\fR will perform UDP and TCP tests.
+.PP
+UDP tests can report packet loss and achieved bandwidth, because UDP flow
+control is done inside this tool. It is also possible to specify target 
+bandwidth for UDP. By default it is 1MBps.
+.PP
+TCP tests report only achieved bandwidth, because kernel TCP stack
+takes care of flow control and packet loss.
+.PP
+To verify whether Open vSwitch is causing any problems user must
+compare packet loss and achieved bandwidth in setups
+when traffic is being tagged and when it is not. If in VLAN setup both servers
+are unable to communicate or achieved bandwidth is lower, then most likely
+Open vSwitch is causing issues.
+.PP
+Some examples of the types of problems that may be encountered are:
+.so utilities/ovs-vlan-bugs.man
+.
+.SS "Client Mode"
+An \fBovs\-test\fR client will connect to two \fBovs\-test\fR servers and
+will ask them to exchange traffic.
+.
+.SS "Server Mode"
+To conduct tests, two \fBovs\-test\fR servers must be running where client
+will connect. The test traffic is excanged only between servers.
+.
+.SH OPTIONS
+.
+.TP
+\fB\-s\fR, \fB\-\-server\fR \fIport\fR
+Run in server mode and wait a client to connect to \fIport\fR
+.TP
+\fB\-c\fR, \fB\-\-client\fR \fIserver1\fR \fIserver2\fR
+Run in client mode and schedule tests between \fIserver1\fR and \fIserver2\fR,
+where \fIserverX\fR must be given in following format 
+\fIcontrolIPX\fR:\fIcontrolPortX\fR\fI[,testIPX]\fR. If \fI testIPX\fR was 
+not specified then this tool will use \fIcontrolIPX\fR for testing.
+.TP
+\fB\-b\fR, \fB\-\-bandwidth\fR \fIbandwidth\fR
+Target bandwidth for UDP tests. The \fIbandwidth\fR is given in bytes per
+second. It is possible to use postfix M or K to change magnitude.
+.
+.so lib/common.man
+.SH EXAMPLES
+Display the Linux kernel version and driver of \fBeth1\fR.
+.IP
+.B uname \-r
+.IP
+.B ethtool \-i eth1
+.
+.PP
+Set up a bridge which forwards traffic originating from \fB1.2.3.4\fR out
+\fBeth1\fR with VLAN tag 10.
+.IP
+.B ovs\-vsctl \-\- add\-br vlan\-br \(rs
+.IP
+.B \-\- add\-port vlan\-br eth1 \(rs
+.IP
+.B \-\- add\-port vlan\-br vlan\-br\-tag tag=10 \(rs
+.IP
+.B \-\- set Interface vlan\-br\-tag type=internal
+.IP
+.B ifconfig vlan\-br\-tag up 1.2.3.4
+.
+.PP
+Run two \fBovs\-test\fR servers listening for client control traffic on
+port 8080 (one on host which has IP address 1.2.3.4 and another one on
+1.2.3.5).
+.IP
+.B ovs\-test \-s 8080
+.
+.PP
+Run \fBovs\-test\fR in client mode and ask it to connect to two servers
+one at 1.2.3.4 port 8080 and another at 1.2.3.5 port 8080.
+.IP
+.B ovs\-test -c 1.2.3.4:8080 1.2.3.5:8080 -b 1M
+.
+.TP
+
+.SH SEE ALSO
+.
+.BR ovs\-vswitchd (8),
+.BR ovs\-ofctl (8),
+.BR ovs\-vsctl (8),
+.BR ovs\-vlan\-test (8),
+.BR ethtool (8),
+.BR uname (1)
diff --git a/utilities/ovs-test.in b/utilities/ovs-test.in
new file mode 100644
index 0000000..70ffa5d
--- /dev/null
+++ b/utilities/ovs-test.in
@@ -0,0 +1,139 @@
+#! @PYTHON@
+#
+# 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.
+
+"""
+ovs test utility that allows to do tests between remote hosts
+"""
+
+import xmlrpclib
+import time
+import socket
+import math
+from ovstest import ovsargs, ovsrpcserver
+
+
+def bandwidth_to_string( bwidth ):
+    """convert bandwidth from long to string and adds units"""
+    if bwidth >= 10000000:
+        return str( int( bwidth / 1000000 ) ) + "MBps"
+    elif bwidth > 10000:
+        return str( int( bwidth / 1000 ) ) + "KBps"
+    else:
+        return str( int( bwidth ) ) + "Bps"
+
+
+def do_udp_tests( receiver, sender, tbwidth, duration ):
+    """schedule UDP tests between receiver and sender"""
+    server1 = xmlrpclib.Server( "http://%s:%u/"; % ( receiver[0], receiver[1] ) 
)
+    server2 = xmlrpclib.Server( "http://%s:%u/"; % ( sender[0], sender[1] ) )
+
+    udpformat = '{0:>15} {1:>15} {2:>15} {3:>15} {4:>15}'
+    print "UDP test from %s to %s with target BANDWIDTH %s" % \
+                            ( sender[0], receiver[0],
+                             bandwidth_to_string( tbwidth ) )
+    print udpformat.format( "Packet Size", "Snt Packets", "Rcv Packets",
+                            "Packet Loss", "Bandwidth" )
+
+    for size in [50, 500, 1000, 1500]: #iterate over different UDP packet sizes
+        listen_handle = -1
+        send_handle = -1
+        try:
+            packetcnt = ( tbwidth * duration ) / size
+
+            listen_handle = server1.create_udp_listener( 15532 )
+            if listen_handle == -1:
+                print "Server could not open UDP listening socket on port"\
+                        " 15532. Try to restart the server.\n"
+                return
+            send_handle = server2.create_udp_sender( ( receiver[2], 15532 ) ,
+                                            packetcnt, size, duration )
+
+            #Using sleep here because there is no other synchronization source 
+            #that would notify us when all sent packets were received
+            time.sleep( duration + 1 )
+
+            rcv_packets = server1.get_udp_listener_results( listen_handle )
+            snt_packets = server2.get_udp_sender_results( send_handle )
+
+            loss = math.ceil( ( ( snt_packets - rcv_packets ) * 10000.0 ) /
+                                                        snt_packets ) / 100
+            bwidth = ( rcv_packets * size ) / duration
+
+            print udpformat.format( size, snt_packets, rcv_packets,
+                                '%.2f%%' % loss, bandwidth_to_string( bwidth ) 
)
+        except socket.error:
+            print "Couldn't establish XMLRPC control channel"
+        finally:
+            if listen_handle != -1:
+                server1.close_udp_listener( listen_handle )
+            if send_handle != -1:
+                server2.close_udp_sender( send_handle )
+    print "\n"
+
+
+def do_tcp_tests( receiver, sender, duration ):
+    """schedule TCP tests between receiver and sender"""
+    server1 = xmlrpclib.Server( "http://%s:%u/"; % ( receiver[0], receiver[1] ) 
)
+    server2 = xmlrpclib.Server( "http://%s:%u/"; % ( sender[0], sender[1] ) )
+
+    tcpformat = '{0:>15} {1:>15} {2:>15}'
+    print "TCP test from %s to %s (full speed)" % ( sender[0], receiver[0] )
+    print tcpformat.format( "Snt Bytes", "Rcv Bytes", "Bandwidth" )
+
+    listen_handle = -1
+    send_handle = -1
+    try:
+        listen_handle = server1.create_tcp_listener( 15532 )
+        if listen_handle == -1:
+            print "Server was unable to open TCP listening socket on port"\
+                    " 15532. Try to restart the server.\n"
+            return
+        send_handle = server2.create_tcp_sender( receiver[2], 15532, duration )
+
+        time.sleep( duration + 1 )
+
+        rcv_bytes = long( server1.get_tcp_listener_results( listen_handle ) )
+        snt_bytes = long( server2.get_tcp_sender_results( send_handle ) )
+
+        bwidth = rcv_bytes / duration
+
+        print tcpformat.format( snt_bytes, rcv_bytes,
+                               bandwidth_to_string( bwidth ) )
+    except socket.error:
+        print "Couldn't establish XMLRPC control channel"
+    finally:
+        if listen_handle != -1:
+            server1.close_tcp_listener( listen_handle )
+        if send_handle != -1:
+            server2.close_tcp_sender( send_handle )
+    print "\n"
+
+
+if __name__ == '__main__':
+    try:
+        OVS_ARGS = ovsargs.ovs_initialize_args()
+
+        if OVS_ARGS.port: #  Start in server mode
+            ovsrpcserver.start_rpc_server( OVS_ARGS.port )
+        elif OVS_ARGS.servers: #  Run in client mode
+            NODE1 = OVS_ARGS.servers[0]
+            NODE2 = OVS_ARGS.servers[1]
+            BANDWIDTH = OVS_ARGS.targetBandwidth
+
+            do_udp_tests( NODE1, NODE2, BANDWIDTH, 10 )
+            do_udp_tests( NODE2, NODE1, BANDWIDTH, 10 )
+            do_tcp_tests( NODE1, NODE2, 10 )
+            do_tcp_tests( NODE2, NODE1, 10 )
+    except KeyboardInterrupt:
+        pass
diff --git a/utilities/ovs-vlan-test.8.in b/utilities/ovs-vlan-test.8.in
index 602d785..e686198 100644
--- a/utilities/ovs-vlan-test.8.in
+++ b/utilities/ovs-vlan-test.8.in
@@ -82,5 +82,6 @@ Run an \fBovs\-vlan\-test\fR client with a control server 
located at
 .BR ovs\-vswitchd (8),
 .BR ovs\-ofctl (8),
 .BR ovs\-vsctl (8),
+.BR ovs\-test (8),
 .BR ethtool (8),
 .BR uname (1)
-- 
1.7.4.1

_______________________________________________
dev mailing list
[email protected]
http://openvswitch.org/mailman/listinfo/dev

Reply via email to