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