2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-05 00:35:33 +00:00

ovs-test: A new tool that allows to diagnose connectivity and performance issues

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
This commit is contained in:
Ansis Atteka
2011-10-31 14:56:08 -07:00
parent 5e9ceccdb6
commit 0be6140a9a
20 changed files with 976 additions and 5 deletions

View File

@@ -194,5 +194,5 @@ include vswitchd/automake.mk
include ovsdb/automake.mk
include rhel/automake.mk
include xenserver/automake.mk
include python/ovs/automake.mk
include python/automake.mk
include python/compat/automake.mk

5
NEWS
View File

@@ -8,8 +8,13 @@ post-v1.3.0
- Added ability to modify TTL in IPv4.
- ovs-appctl:
- New "fdb/flush" command to flush bridge's MAC learning table.
- ovs-test:
- A new distributed testing tool that allows one to diagnose performance
and connectivity issues. This tool currently is not included in RH or
Xen packages.
- RHEL packaging now supports integration with Red Hat network scripts.
v1.3.0 - xx xxx xxxx
------------------------
- OpenFlow:

3
debian/automake.mk vendored
View File

@@ -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 \

9
debian/control vendored
View File

@@ -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.

1
debian/openvswitch-test.dirs vendored Normal file
View File

@@ -0,0 +1 @@
usr/share/pyshared/ovstest/

2
debian/openvswitch-test.install vendored Normal file
View File

@@ -0,0 +1,2 @@
usr/share/openvswitch/python/ovstest usr/lib/python2.4/site-packages/
usr/bin/ovs-test

1
debian/openvswitch-test.manpages vendored Normal file
View File

@@ -0,0 +1 @@
_debian/utilities/ovs-test.8

View File

@@ -1 +1 @@
usr/share/openvswitch/python/* usr/lib/python2.4/site-packages/
usr/share/openvswitch/python/ovs usr/lib/python2.4/site-packages/

View File

@@ -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 \

View File

@@ -1,5 +1,13 @@
run_python = PYTHONPATH=$(top_srcdir)/python:$$PYTHON_PATH $(PYTHON)
ovstest_pyfiles = \
python/ovstest/__init__.py \
python/ovstest/args.py \
python/ovstest/rpcserver.py \
python/ovstest/tcp.py \
python/ovstest/udp.py \
python/ovstest/util.py
ovs_pyfiles = \
python/ovs/__init__.py \
python/ovs/daemon.py \
@@ -22,10 +30,10 @@ ovs_pyfiles = \
python/ovs/timeval.py \
python/ovs/vlog.py \
python/ovs/util.py
EXTRA_DIST += $(ovs_pyfiles) python/ovs/dirs.py
EXTRA_DIST += $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles)
if HAVE_PYTHON
nobase_pkgdata_DATA = $(ovs_pyfiles)
nobase_pkgdata_DATA = $(ovs_pyfiles) $(ovstest_pyfiles)
ovs-install-data-local:
$(MKDIR_P) python/ovs
(echo "import os" && \

View File

@@ -0,0 +1 @@
# This file intentionally left blank.

115
python/ovstest/args.py Normal file
View File

@@ -0,0 +1,115 @@
# Copyright (c) 2011 Nicira Networks
#
# 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.
"""
ovsargs provide argument parsing for ovs-test utility
"""
import argparse
import socket
import re
def ip(string):
"""Verifies if string is a valid IP address"""
try:
socket.inet_aton(string)
except socket.error:
raise argparse.ArgumentTypeError("Not a valid IPv4 address")
return string
def port(string):
"""Convert a string into a Port (integer)"""
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_optional_port(string, default_port):
"""Convert a string into IP and Port pair. If port was absent then use
default_port as the port"""
value = string.split(':')
if len(value) == 1:
return (ip(value[0]), default_port)
elif len(value) == 2:
return (ip(value[0]), port(value[1]))
else:
raise argparse.ArgumentTypeError("IP address from the optional Port "
"must be colon-separated")
def server_endpoint(string):
"""Converts a string in ControlIP[:ControlPort][,TestIP[:TestPort]] format
into a 4-tuple, where:
1. First element is ControlIP
2. Second element is ControlPort (if omitted will use default value 15531)
3 Third element is TestIP (if omitted will be the same as ControlIP)
4. Fourth element is TestPort (if omitted will use default value 15532)"""
value = string.split(',')
if len(value) == 1: # TestIP and TestPort are not present
ret = ip_optional_port(value[0], 15531)
return (ret[0], ret[1], ret[0], 15532)
elif len(value) == 2:
ret1 = ip_optional_port(value[0], 15531)
ret2 = ip_optional_port(value[1], 15532)
return (ret1[0], ret1[1], ret2[0], ret2[1])
else:
raise argparse.ArgumentTypeError("ControlIP:ControlPort and TestIP:"
"TestPort must be comma "
"separated")
def bandwidth(string):
"""Convert a string (given in bits/second with optional magnitude for
units) into a long (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) / 8 # Convert from bits to bytes
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 = 'target bandwidth for UDP tests in bits/second. Use '
'postfix M or K to alter unit magnitude.')
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 = server_endpoint,
metavar = ("SERVER1", "SERVER2"),
help = 'run in client mode and do tests between these '
'two servers. Each server must be specified in following '
'format - ControlIP[:ControlPort][,TestIP[:TestPort]]. If '
'TestIP is omitted then ovs-test server will also use the '
'ControlIP for testing purposes. ControlPort is TCP port '
'where server will listen for incoming XML/RPC control '
'connections to schedule tests (by default 15531). TestPort '
'is port which will be used by server to send test traffic '
'(by default 15532)')
return parser.parse_args()

203
python/ovstest/rpcserver.py Normal file
View File

@@ -0,0 +1,203 @@
# Copyright (c) 2011 Nicira Networks
#
# 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.
"""
rpcserver 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 udp
import tcp
import args
import util
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 = udp.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 = udp.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.stopListening()
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.stopListening()
self.__delete_handle(handle)
return 0
def xmlrpc_create_tcp_listener(self, port):
"""
Creates a TcpListener that will accept connection from TcpSender
"""
try:
listener = tcp.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 = tcp.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 TcpListener and all its resources
"""
try:
(_, port) = self.__get_handle_resources(handle)
port.loseConnection()
self.__delete_handle(handle)
except exceptions.KeyError:
return -1
return 0
def xmlrpc_close_tcp_sender(self, handle):
"""
Releases TcpSender and all its resources
"""
try:
(_, connector) = self.__get_handle_resources(handle)
connector.disconnect()
self.__delete_handle(handle)
except exceptions.KeyError:
return -1
return 0
def xmlrpc_get_interface(self, address):
"""
Finds first interface that has given address
"""
return util.get_interface(address)
def xmlrpc_get_interface_mtu(self, iface):
"""
Returns MTU of the given interface
"""
return util.get_interface_mtu(iface)
def xmlrpc_uname(self):
"""
Return information about running kernel
"""
return util.uname()
def xmlrpc_get_driver(self, iface):
"""
Returns driver version
"""
return util.get_driver(iface)
def start_rpc_server(port):
RPC_SERVER = TestArena()
reactor.listenTCP(port, server.Site(RPC_SERVER))
reactor.run()

139
python/ovstest/tcp.py Normal file
View File

@@ -0,0 +1,139 @@
# Copyright (c) 2011 Nicira Networks
#
# 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.
"""
tcp module 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 int (http://bugs.python.org/issue2985)
#so we have to convert the amount of bytes into a string
return str(self.stats)
class Producer(object):
implements(interfaces.IPushProducer)
"""
This producer class generates infinite byte stream for a specified time
duration
"""
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 sends all traffic at full speed.
"""
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)

90
python/ovstest/udp.py Normal file
View File

@@ -0,0 +1,90 @@
# Copyright (c) 2011 Nicira Networks
#
# 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.
"""
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)):
"""This function is called each time datagram is received"""
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 actually received"""
return len(self.stats)
class UdpSender(DatagramProtocol):
"""
Class that will 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 is not None):
self.looper.stop()
self.looper = None
def datagramReceived(self, data, (host, port)):
pass
def sendData(self):
"""This function is called from LoopingCall"""
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

74
python/ovstest/util.py Normal file
View File

@@ -0,0 +1,74 @@
# Copyright (c) 2011 Nicira Networks
#
# 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.
"""
util module contains some helper function
"""
import socket, struct, fcntl, array, os, subprocess, exceptions
def str_ip(ip):
(x1, x2, x3, x4) = struct.unpack("BBBB", ip)
return ("%u.%u.%u.%u") % (x1, x2, x3, x4)
def get_interface_mtu(iface):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
indata = iface + ('\0' * (32 - len(iface)))
try:
outdata = fcntl.ioctl(s.fileno(), 0x8921, indata) # socket.SIOCGIFMTU
mtu = struct.unpack("16si12x", outdata)[1]
except:
return 0
return mtu
def get_interface(address):
"""
Finds first interface that has given address
"""
bytes = 256 * 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array('B', '\0' * bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', bytes, names.buffer_info()[0])
))[0]
namestr = names.tostring()
for i in range(0, outbytes, 40):
name = namestr[i:i + 16].split('\0', 1)[0]
if address == str_ip(namestr[i + 20:i + 24]):
return name
return "" # did not find interface we were looking for
def uname():
os_info = os.uname()
return os_info[2] #return only the kernel version number
def get_driver(iface):
try:
p = subprocess.Popen(
["ethtool", "-i", iface],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = p.communicate()
if p.returncode == 0:
lines = out.split("\n")
driver = "%s(%s)" % (lines[0], lines[1]) #driver name + version
else:
driver = "no support for ethtool"
except exceptions.OSError:
driver = ""
return driver

View File

@@ -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

117
utilities/ovs-test.8.in Normal file
View File

@@ -0,0 +1,117 @@
.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 that Open vSwitch may uncover. 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. 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 \fBovs\-test\fR. It is also possible to specify target
bandwidth for UDP. By default it is 1Mbit/s.
.PP
TCP tests report only achieved bandwidth, because kernel TCP stack
takes care of flow control and packet loss. TCP tests are essential to detect
potential TSO related VLAN issues.
.PP
To determine whether Open vSwitch is encountering any 802.1Q related problems,
the user must compare packet loss and achieved bandwidth in a setup where
traffic is being tagged against one where it is not. If in the tagged setup
both servers are unable to communicate or the achieved bandwidth is lower,
then, most likely, Open vSwitch has encountered a pre-existing kernel or
driver bug.
.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 on two different
hosts where client can connect. The actual test traffic is exchanged only
between both \fBovs\-test\fR server test IP addresses. It is recommended that
both servers have their test IP addresses in the same subnet, otherwise one
will need to change routing so that the test traffic actually goes through the
interface that he originally intended to test.
.
.SH OPTIONS
.
.TP
\fB\-s\fR, \fB\-\-server\fR \fIport\fR
Run in server mode and wait for a client to establish XML RPC Control
Connection on TCP \fIport\fR. It is recommended to have ethtool installed on
the server so that it could retrieve information about NIC driver.
.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 each \fIserver\fR must be given in following format -
ControlIP[:ControlPort][,TestIP[:TestPort]]. If TestIP is omitted then
ovs-test server will use the ControlIP for testing purposes. ControlPort is
TCP port where server will listen for incoming XML/RPC control
connections to schedule tests (by default it is 15531). TestPort
is port which will be used by server to listen for test traffic
(by default it is 15532).
.TP
\fB\-b\fR, \fB\-\-bandwidth\fR \fIbandwidth\fR
Target bandwidth for UDP tests. The \fIbandwidth\fR must be given in bits per
second. It is possible to use postfix M or K to alter the target bandwidth
magnitude.
.
.so lib/common.man
.SH EXAMPLES
.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
On two different hosts start \fBovs\-test\fR in server mode and tell them to
listen on port 15531 for incoming client control connections:
.IP
.B 1.2.3.4: ovs\-test \-s 15531
.IP
.B 1.2.3.5: ovs\-test \-s 15531
.
.PP
On any other host start \fBovs\-test\fR in client mode and ask it to connect
to those two servers - one at 1.2.3.4 and another at 1.2.3.5 (by default
client will use TCP port 15531 to establish control channel).
.IP
.B ovs\-test -c 1.2.3.4 1.2.3.5
.
.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)

180
utilities/ovs-test.in Normal file
View File

@@ -0,0 +1,180 @@
#! @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 twisted
import xmlrpclib
import time
import socket
import math
from ovstest import args, rpcserver
def bandwidth_to_string(bwidth):
"""Convert bandwidth from long to string and add units"""
bwidth = bwidth * 8 # Convert back to bits/second
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 collect_information(node):
"""Print information about hosts that will do testing"""
print "Node %s:%u " % (node[0], node[1])
server1 = xmlrpclib.Server("http://%s:%u/" % (node[0], node[1]))
interface_name = server1.get_interface(node[2])
uname = server1.uname()
mtu = 1500
if interface_name == "":
print ("Could not find interface that has %s IP address."
"Make sure that you specified correct Test IP." % (node[2]))
else:
mtu = server1.get_interface_mtu(interface_name)
driver = server1.get_driver(interface_name)
print "Will be using %s(%s) with MTU %u" % (interface_name, node[2],
mtu)
if driver == "":
print "Install ethtool on this host to get NIC driver information"
else:
print "On this host %s has %s." % (interface_name, driver)
if uname == "":
print "Unable to retrieve kernel information. Is this Linux?"
else:
print "Running kernel %s." % uname
print "\n"
return mtu
def do_udp_tests(receiver, sender, tbwidth, duration, sender_mtu):
"""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:%u to %s:%u with target bandwidth %s" %
(sender[0], sender[1], receiver[0], receiver[1],
bandwidth_to_string(tbwidth)))
print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv Datagrams",
"Datagram Loss", "Bandwidth")
for size in [8, sender_mtu - 100, sender_mtu - 28, sender_mtu]:
listen_handle = -1
send_handle = -1
try:
packetcnt = (tbwidth * duration) / size
listen_handle = server1.create_udp_listener(receiver[3])
if listen_handle == -1:
print ("Server could not open UDP listening socket on port"
" %u. Try to restart the server.\n" % receiver[3])
return
send_handle = server2.create_udp_sender(
(receiver[2], receiver[3]),
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))
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:%u to %s:%u (full speed)" % (sender[0], sender[1],
receiver[0], receiver[1])
print tcpformat.format("Snt Bytes", "Rcv Bytes", "Bandwidth")
listen_handle = -1
send_handle = -1
try:
listen_handle = server1.create_tcp_listener(receiver[3])
if listen_handle == -1:
print ("Server was unable to open TCP listening socket on port"
" %u. Try to restart the server.\n" % receiver[3])
return
send_handle = server2.create_tcp_sender(receiver[2], receiver[3],
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))
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 = args.ovs_initialize_args()
if ovs_args.port is not None: # Start in server mode
print "Starting RPC server"
try:
rpcserver.start_rpc_server(ovs_args.port)
except twisted.internet.error.CannotListenError:
print "Couldn't start XMLRPC server on port %u" % ovs_args.port
elif ovs_args.servers is not None: # Run in client mode
node1 = ovs_args.servers[0]
node2 = ovs_args.servers[1]
bandwidth = ovs_args.targetBandwidth
mtu_node1 = collect_information(node1)
mtu_node2 = collect_information(node2)
do_udp_tests(node1, node2, bandwidth, 5, mtu_node1)
do_udp_tests(node2, node1, bandwidth, 5, mtu_node2)
do_tcp_tests(node1, node2, 5)
do_tcp_tests(node2, node1, 5)
except KeyboardInterrupt:
pass
except socket.error:
print "Couldn't establish XMLRPC control channel"

View File

@@ -8,6 +8,12 @@
.so lib/common-syn.man
.
.SH DESCRIPTION
The \fBovs\-vlan\-test\fR utility has some limitations, for example, it does
not use TCP in its tests. Also it does not take into account MTU to detect
potential edge cases. To overcome those limitations a new tool was
developed \- \fBovs\-test\fR. \fBovs\-test\fR is currently supported only
on Debian so, if possible try to use that on instead of \fBovs\-vlan\-test\fR.
.PP
The \fBovs\-vlan\-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
@@ -82,5 +88,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)