[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