[ovs-dev] [PATCH 1/2] ovs-vlan-test: Add iperf to test basic connectivity.
Ansis Atteka
aatteka at nicira.com
Wed Oct 19 04:23:46 UTC 2011
ovs-vlan-test runs through a number of tests to identify VLAN issues. This
is useful when trying to debug why a particular driver has issues, but it made
the testing environment a bit harder to set up. This commit adds an iperf
test to check basic functionality. It also useful in detecting performance
issues.
Issue #6976
---
utilities/ovs-vlan-test.8.in | 69 ++++---
utilities/ovs-vlan-test.in | 545 +++++++++++-------------------------------
2 files changed, 184 insertions(+), 430 deletions(-)
diff --git a/utilities/ovs-vlan-test.8.in b/utilities/ovs-vlan-test.8.in
index 602d785..6e69f23 100644
--- a/utilities/ovs-vlan-test.8.in
+++ b/utilities/ovs-vlan-test.8.in
@@ -1,45 +1,58 @@
-.TH ovs\-vlan\-test 1 "December 2010" "Open vSwitch" "Open vSwitch Manual"
+.TH ovs\-vlan\-test 1 "October 2011" "Open vSwitch" "Open vSwitch Manual"
.
.SH NAME
\fBovs\-vlan\-test\fR \- check Linux drivers for problems with vlan traffic
.
.SH SYNOPSIS
-\fBovs\-vlan\-test\fR [\fB\-s\fR | \fB\-\-server\fR] \fIcontrol_ip\fR \fIvlan_ip\fR
+\fBovs\-vlan\-test\fR [\fB\-s\fR \fISERVERPORT\fR|\fB\-c\fR \fISERVERIPPORT\fR]
+[\fB\-b\fR \fITARGETBANDWIDTH\fR]
.so lib/common-syn.man
.
.SH DESCRIPTION
-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
-interfaces running certain drivers of certain Linux kernel versions. To run a
-test, configure Open vSwitch to tag traffic originating from \fIvlan_ip\fR and
-forward it out the target interface. Then run the \fBovs\-vlan\-test\fR in
-client mode connecting to an \fBovs\-vlan\-test\fR server.
-\fBovs\-vlan\-test\fR will display "OK" if it did not detect problems.
+The \fBovs\-vlan\-test\fR program automates \fBiperf\fR utility and allows to
+detect connectivity and performance problems. 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 interface where \fBovs\-vlan\-test\fR
+client will connect and forward it to the target interface where
+\fBovs\-vlan\-test\fR server will be listening. Then run the
+\fBovs\-vlan\-test\fR in client mode so that it connects to an
+\fBovs\-vlan\-test\fR server. \fBovs\-vlan\-test\fR client will display "OK" if
+it did not detect any problems. Besides from that client will also display
+count of lost and out-of-order packets for all UDP tests.
+
+.PP
+UDP do not have effective flow control, so user is responsible to adjust target
+bandwidth (-b parameter). If target bandwidth is greater than the network
+is able to handle then packet loss will occur.
+
.PP
Some examples of the types of problems that may be encountered are:
.so utilities/ovs-vlan-bugs.man
.
.SS "Client Mode"
An \fBovs\-vlan\-test\fR client may be run on a host to check for VLAN
-connectivity problems. The client must be able to establish HTTP connections
-with an \fBovs\-vlan\-test\fR server located at the specified \fIcontrol_ip\fR
-address. UDP traffic sourced at \fIvlan_ip\fR should be tagged and directed out
-the interface whose connectivity is being tested.
+connectivity and performance problems. Traffic originating from client should
+be tagged and directed out to the interface whose connectivity is being tested.
.
.SS "Server Mode"
-To conduct tests, an \fBovs\-vlan\-test\fR server must be running on a host
-known not to have VLAN connectivity problems. The server must have a
-\fIcontrol_ip\fR on a non\-VLAN network which clients can establish
-connectivity with. It must also have a \fIvlan_ip\fR address on a VLAN network
-which clients will use to test their VLAN connectivity. Multiple clients may
-test against a single \fBovs\-vlan\-test\fR server concurrently.
+To conduct tests, an \fBovs\-vlan\-test\fR server must be running on another
+host. Multiple clients may test against a single \fBovs\-vlan\-test\fR server
+concurrently.
.
.SH OPTIONS
.
.TP
-\fB\-s\fR, \fB\-\-server\fR
-Run in server mode.
+\fB\-s, \-\-server SERVERPORT\fR
+Run in server mode and wait for client connections on SERVERPORT.
+.TP
+\fB\-c, \-\-client SERVERIPPORT\fR
+Run in client mode and connect to the server that is listening at SERVERIPPORT
+(in IP:PORT format)
+.TP
+\fB\-b, \-\-bandwidth TARGETBANDWIDTH[KM]\fR
+Set the target bandwidth to TARGETBANDWIDTH bits/sec (default 1 Mbps). Must be
+set at client-side.
.
.so lib/common.man
.SH EXAMPLES
@@ -64,16 +77,16 @@ Set up a bridge which forwards traffic originating from \fB1.2.3.4\fR out
.B ifconfig vlan\-br\-tag up 1.2.3.4
.
.PP
-Run an \fBovs\-vlan\-test\fR server listening for client control traffic on
-172.16.0.142 port 8080 and VLAN traffic on the default port of 1.2.3.3.
+Run an \fBovs\-vlan\-test\fR server listening for client traffic on
+port 10000.
.IP
-.B ovs\-vlan\-test \-s 172.16.0.142:8080 1.2.3.3
+.B ovs\-vlan\-test \-s 10000
.
.PP
-Run an \fBovs\-vlan\-test\fR client with a control server located at
-172.16.0.142 port 8080 and a local VLAN ip of 1.2.3.4.
+Run an \fBovs\-vlan\-test\fR client that connects to a server located at
+172.16.0.142 port 10000.
.IP
-.B ovs\-vlan\-test 172.16.0.142:8080 1.2.3.4
+.B ovs\-vlan\-test \-c 172.16.0.142:10000
.
.TP
diff --git a/utilities/ovs-vlan-test.in b/utilities/ovs-vlan-test.in
index f937845..ed35a7f 100755
--- a/utilities/ovs-vlan-test.in
+++ b/utilities/ovs-vlan-test.in
@@ -1,6 +1,6 @@
#! @PYTHON@
#
-# Copyright (c) 2010 Nicira Networks.
+# 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.
@@ -14,422 +14,163 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import BaseHTTPServer
-import getopt
-import httplib
import os
-import threading
-import time
-import signal #Causes keyboard interrupts to go to the main thread.
+import re
import socket
-import sys
-
-print_safe_lock = threading.Lock()
-def print_safe(s):
- print_safe_lock.acquire()
- print(s)
- print_safe_lock.release()
-
-def start_thread(target, args):
- t = threading.Thread(target=target, args=args)
- t.setDaemon(True)
- t.start()
- return t
-
-#Caller is responsible for catching socket.error exceptions.
-def send_packet(key, length, dest_ip, dest_port):
-
- length -= 20 + 8 #IP and UDP headers.
-
- packet = str(key)
- packet += chr(0) * (length - len(packet))
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.sendto(packet, (dest_ip, dest_port))
- sock.close()
-
-#UDP Receiver
-class UDPReceiver:
- def __init__(self, vlan_ip, vlan_port):
- self.vlan_ip = vlan_ip
- self.vlan_port = vlan_port
- self.recv_callbacks = {}
- self.udp_run = False
-
- def recv_packet(self, key, success_callback, timeout_callback):
-
- event = threading.Event()
-
- def timeout_cb():
- timeout_callback()
- event.set()
-
- timer = threading.Timer(30, timeout_cb)
- timer.daemon = True
-
- def success_cb():
- timer.cancel()
- success_callback()
- event.set()
-
- # Start the timer first to avoid a timer.cancel() race condition.
- timer.start()
- self.recv_callbacks[key] = success_cb
- return event
-
- def udp_receiver(self):
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.settimeout(1)
-
- try:
- sock.bind((self.vlan_ip, self.vlan_port))
- except socket.error, e:
- print_safe('Failed to bind to %s:%d with error: %s'
- % (self.vlan_ip, self.vlan_port, e))
- os._exit(1) #sys.exit only exits the current thread.
-
- while self.udp_run:
-
- try:
- data, _ = sock.recvfrom(4096)
- except socket.timeout:
- continue
- except socket.error, e:
- print_safe('Failed to receive from %s:%d with error: %s'
- % (self.vlan_ip, self.vlan_port, e))
- os._exit(1)
-
- data_str = data.split(chr(0))[0]
-
- if not data_str.isdigit():
- continue
-
- key = int(data_str)
-
- if key in self.recv_callbacks:
- self.recv_callbacks[key]()
- del self.recv_callbacks[key]
-
- def start(self):
- self.udp_run = True
- start_thread(self.udp_receiver, ())
-
- def stop(self):
- self.udp_run = False
-
-#Server
-vlan_server = None
-class VlanServer:
-
- def __init__(self, server_ip, server_port, vlan_ip, vlan_port):
- global vlan_server
-
- vlan_server = self
-
- self.server_ip = server_ip
- self.server_port = server_port
-
- self.recv_response = '%s:%d:' % (vlan_ip, vlan_port)
-
- self.result = {}
- self.result_lock = threading.Lock()
-
- self._test_id = 0
- self._test_id_lock = threading.Lock()
-
- self.udp_recv = UDPReceiver(vlan_ip, vlan_port)
-
- def get_test_id(self):
- self._test_id_lock.acquire()
-
- self._test_id += 1
- ret = self._test_id
-
- self._test_id_lock.release()
- return ret
-
- def set_result(self, key, value):
-
- self.result_lock.acquire()
-
- if key not in self.result:
- self.result[key] = value
-
- self.result_lock.release()
-
- def recv(self, test_id):
- self.udp_recv.recv_packet(test_id,
- lambda : self.set_result(test_id, 'Success'),
- lambda : self.set_result(test_id, 'Timeout'))
-
- return self.recv_response + str(test_id)
-
- def send(self, test_id, data):
- try:
- ip, port, size = data.split(':')
- port = int(port)
- size = int(size)
- except ValueError:
- self.set_result(test_id,
- 'Server failed to parse send request: %s' % data)
- return
-
- def send_thread():
- send_time = 10
- for _ in range(send_time * 2):
+import argparse
+from twisted.internet import protocol
+from twisted.internet import reactor
+
+
+class IperfUDPClient(protocol.ProcessProtocol):
+
+ def __init__(self, scheduler, datagramSize):
+ self.out_data = ""
+ self.err_data = ""
+ self.scheduler = scheduler
+ self.datagramSize = datagramSize
+ self.serverUnreachable = False
+
+ def outReceived(self, data):
+ self.out_data = self.out_data + data #Concatenate STDOUT and parse later
+
+ def errReceived(self, data):
+ self.err_data = self.err_data + data
+ if ("connect failed" in self.err_data) or \
+ ("Connection refused" in self.err_data) or \
+ ("did not receive ack" in self.err_data):
+ self.serverUnreachable = True
+ self.transport.signalProcess('KILL') #Server did not respond back
+
+ def processEnded(self, reason):
+ if (reason.value.exitCode != 0) and (self.serverUnreachable == False):
+ print "Iperf returned with an error code"
+ else:
+ if self.serverUnreachable == True:
+ print "Connectivity problems were detected with datagrams "\
+ "of size %u bytes. Test FAILED" % (self.datagramSize)
+ else:
try:
- send_packet(test_id, size, ip, port)
- except socket.error, e:
- self.set_result(test_id, 'Failure: ' + str(e))
- return
- time.sleep(.5)
-
- self.set_result(test_id, 'Success')
-
- start_thread(send_thread, ())
-
- return str(test_id)
-
- def run(self):
- self.udp_recv.start()
- try:
- BaseHTTPServer.HTTPServer((self.server_ip, self.server_port),
- VlanServerHandler).serve_forever()
- except socket.error, e:
- print_safe('Failed to start control server: %s' % e)
- self.udp_recv.stop()
-
- return 1
-
-class VlanServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
- def do_GET(self):
-
- #Guarantee three arguments.
- path = (self.path.lower().lstrip('/') + '//').split('/')
-
- resp = 404
- body = None
-
- if path[0] == 'start':
- test_id = vlan_server.get_test_id()
-
- if path[1] == 'recv':
- resp = 200
- body = vlan_server.recv(test_id)
- elif path[1] == 'send':
- resp = 200
- body = vlan_server.send(test_id, path[2])
- elif (path[0] == 'result'
- and path[1].isdigit()
- and int(path[1]) in vlan_server.result):
- resp = 200
- body = vlan_server.result[int(path[1])]
- elif path[0] == 'ping':
- resp = 200
- body = 'pong'
-
- self.send_response(resp)
- self.end_headers()
-
- if body:
- self.wfile.write(body)
-
-#Client
-class VlanClient:
-
- def __init__(self, server_ip, server_port, vlan_ip, vlan_port):
- self.server_ip_port = '%s:%d' % (server_ip, server_port)
- self.vlan_ip_port = "%s:%d" % (vlan_ip, vlan_port)
- self.udp_recv = UDPReceiver(vlan_ip, vlan_port)
-
- def request(self, resource):
- conn = httplib.HTTPConnection(self.server_ip_port)
- conn.request('GET', resource)
- return conn
-
- def send(self, size):
-
- def error_msg(e):
- print_safe('Send size %d unsuccessful: %s' % (size, e))
-
- try:
- conn = self.request('/start/recv')
- data = conn.getresponse().read()
- except (socket.error, httplib.HTTPException), e:
- error_msg(e)
- return False
-
- try:
- ip, port, test_id = data.split(':')
- port = int(port)
- test_id = int(test_id)
- except ValueError:
- error_msg("Received invalid response from control server (%s)" %
- data)
- return False
-
- send_time = 5
-
- for _ in range(send_time * 4):
-
- try:
- send_packet(test_id, size, ip, port)
- resp = self.request('/result/%d' % test_id).getresponse()
- data = resp.read()
- except (socket.error, httplib.HTTPException), e:
- error_msg(e)
- return False
-
- if resp.status == 200 and data == 'Success':
- print_safe('Send size %d successful' % size)
- return True
- elif resp.status == 200:
- error_msg(data)
- return False
-
- time.sleep(.25)
-
- error_msg('Timeout')
- return False
-
- def recv(self, size):
-
- def error_msg(e):
- print_safe('Receive size %d unsuccessful: %s' % (size, e))
-
- resource = '/start/send/%s:%d' % (self.vlan_ip_port, size)
- try:
- conn = self.request(resource)
- test_id = conn.getresponse().read()
- except (socket.error, httplib.HTTPException), e:
- error_msg(e)
- return False
-
- if not test_id.isdigit():
- error_msg('Invalid response %s' % test_id)
- return False
-
- success = [False] #Primitive datatypes can't be set from closures.
-
- def success_cb():
- success[0] = True
-
- def failure_cb():
- success[0] = False
-
- self.udp_recv.recv_packet(int(test_id), success_cb, failure_cb).wait()
-
- if success[0]:
- print_safe('Receive size %d successful' % size)
+ lines = self.out_data.split('\n')
+ sl = lines[1].split(',')#Send Line
+ rl = lines[2].split(',')#Receive Line
+
+ print "Sent %s UDP datagrams of size %u bytes; %s%% "\
+ "packetloss(%s) and %s out of order datagrams; OK"%\
+ (sl[11], self.datagramSize, sl[12], sl[10], sl[13])
+
+ print "Received %s UDP datagrams of size %u bytes; %s%% "\
+ "packetloss(%s) and %s out of order datagrams; OK"%\
+ (rl[11], self.datagramSize, rl[12], rl[10], rl[13])
+ except IndexError:
+ print "Parsing failed. Please Report a bug against "\
+ "ovs-vlan-test and include this error message: %s"%\
+ (self.out_data)
+ self.scheduler.scheduleNextTest()
+
+
+class IperfServer(protocol.ProcessProtocol):
+
+ def __init__(self):
+ pass
+
+ def processEnded(self, reason):
+ if reason.value.exitCode != 0:
+ print "Iperf did not start up properly."
+
+
+class ClientTestScheduler:
+
+ def __init__(self, ip, port, targetBandwidth):
+ self.serverIp = ip
+ self.serverPort = port
+ self.size = [1500, 1000, 500, 55]
+ self.targetBandwidth = targetBandwidth
+
+ def scheduleNextTest(self):
+ if len(self.size) > 0: #iterate over different datagram sizes
+ s = self.size.pop()
+ clientProcessProtocolHandler = IperfUDPClient(self, s)
+ t = reactor.spawnProcess(clientProcessProtocolHandler, "iperf",
+ ["iperf", "-c", self.serverIp, "-u", "-p", self.serverPort, "-y",
+ "C", "-l", str(s), "-r", "-b", self.targetBandwidth], {})
else:
- error_msg('Timeout')
-
- return success[0]
-
- def server_up(self):
+ reactor.stop()#All tests are completed; Exit from the event-loop
- def error_msg(e):
- print_safe('Failed control server connectivity test: %s' % e)
+def ipPort(string):
+ value = string.split(':')
+ if len(value) == 2:
try:
- resp = self.request('/ping').getresponse()
- data = resp.read()
- except (socket.error, httplib.HTTPException), e:
- error_msg(e)
- return False
-
- if resp.status != 200:
- error_msg('Invalid status %d' % resp.status)
- elif data != 'pong':
- error_msg('Invalid response %s' % data)
-
- return True
-
- def run(self):
-
- if not self.server_up():
- return 1
-
- self.udp_recv.start()
-
- success = True
- for size in [50, 500, 1000, 1500]:
- success = self.send(size) and success
- success = self.recv(size) and success
-
- self.udp_recv.stop()
-
- if success:
- print_safe('OK')
- return 0
- else:
- print_safe('FAILED')
- return 1
-
-def usage():
- print_safe("""\
-%(argv0)s: Test vlan connectivity
-usage: %(argv0)s server vlan
-
-The following options are also available:
- -s, --server run in server mode
- -h, --help display this help message
- -V, --version display version information\
-""" % {'argv0': sys.argv[0]})
+ socket.inet_aton(value[0])
+ except socket.error:
+ raise argparse.ArgumentTypeError("Not a valid IPv4 address")
+ port(value[1]) #this function might throw an ArgumentTypeError exception
+ else:
+ raise argparse.ArgumentTypeError("IP address and Port must be "\
+ "comma-separated")
+ return string
-def main():
+def port(string):
try:
- options, args = getopt.gnu_getopt(sys.argv[1:], 'hVs',
- ['help', 'version', 'server'])
- except getopt.GetoptError, geo:
- print_safe('%s: %s\n' % (sys.argv[0], geo.msg))
- return 1
+ 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 string
- server = False
- for key, _ in options:
- if key in ['-h', '--help']:
- usage()
- return 0
- elif key in ['-V', '--version']:
- print_safe('ovs-vlan-test (Open vSwitch) @VERSION@')
- return 0
- elif key in ['-s', '--server']:
- server = True
- else:
- print_safe('Unexpected option %s. (use --help for help)' % key)
- return 1
+
+def bandwidth(string):
+ if re.match("^[1-9][0-9]*[MK]?$",string) == None: #Number + K or M character
+ raise argparse.ArgumentTypeError("Not a valid target bandwidth")
+ return string
- if len(args) != 2:
- print_safe('Expecting two arguments. (use --help for help)')
- return 1
- try:
- server_ip, server_port = args[0].split(':')
- server_port = int(server_port)
- except ValueError:
- server_ip = args[0]
- server_port = 80
+def which(program):
+ def is_exe(fpath):
+ return os.path.exists(fpath) and os.access(fpath, os.X_OK)
- try:
- vlan_ip, vlan_port = args[1].split(':')
- vlan_port = int(vlan_port)
- except ValueError:
- vlan_ip = args[1]
- vlan_port = 15213
-
- if server:
- return VlanServer(server_ip, server_port, vlan_ip, vlan_port).run()
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
else:
- return VlanClient(server_ip, server_port, vlan_ip, vlan_port).run()
+ for path in os.environ["PATH"].split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+ return None
-if __name__ == '__main__':
- main_ret = main()
- # Python can throw exceptions if threads are running at exit.
- for th in threading.enumerate():
- if th != threading.currentThread():
- th.join()
+if __name__ == '__main__':
- sys.exit(main_ret)
+ parser = argparse.ArgumentParser(description='Test vlan connectivity')
+ parser.add_argument('-v', '--version', action='version',
+ version='ovs-vlan-test (Open vSwitch) @VERSION@')
+ parser.add_argument("-b", "--bandwidth", action='store',
+ dest="targetBandwidth", default="1M", type=bandwidth,
+ help='Specify target bandwidth for UDP tests (default 1M)')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("-s", "--server",action="store", dest="serverPort",
+ type=port, help='run in server mode and open listening '\
+ 'UDP socket at SERVERPORT')
+ group.add_argument('-c', "--client", action="store", dest="serverIpPort",
+ type=ipPort, help='run in client mode and connect '\
+ 'to a server that is listening at SERVERIPPORT')
+ args = parser.parse_args()
+
+ if which("iperf"):
+ if args.serverPort:
+ serverProcessProtocolHandler = IperfServer()
+ reactor.spawnProcess(serverProcessProtocolHandler, "iperf",
+ ["iperf", "-s", "-u", "-p", args.serverPort , "-y", "C"], {})
+ else:
+ [ServerIP, ServerPort] = args.serverIpPort.split(':')
+ scheduler = ClientTestScheduler(ServerIP, ServerPort,
+ args.targetBandwidth)
+ scheduler.scheduleNextTest();
+ reactor.run() #Enter the main event-loop
+ else:
+ print "iperf is not in the PATH (install it with yum or apt-get)"
--
1.7.4.1
More information about the dev
mailing list