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 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev