From 2d8bdd8f220848611722bce97b3258600530e95e Mon Sep 17 00:00:00 2001 From: Ansis Atteka Date: Thu, 28 Jun 2012 15:52:40 -0700 Subject: [PATCH] ovs-l3ping: A new test utility that allows to detect L3 tunneling issues ovs-l3ping is similar to ovs-test, but the main difference is that it does not require administrator to open firewall holes for the XML/RPC control connection. This is achieved by encapsulating the Control Connection over the L3 tunnel itself. This tool is not intended as a replacement for ovs-test, because ovs-test covers much broader set of test cases. Sample usage: Node1: ovs-l3ping -s 192.168.122.236,10.1.1.1 -t gre Node2: ovs-l3ping -c 192.168.122.220,10.1.1.2,10.1.1.1 -t gre Issue#11791 Signed-off-by: Ansis Atteka --- NEWS | 3 + debian/openvswitch-test.install | 1 + debian/openvswitch-test.manpages | 1 + manpages.mk | 8 + python/automake.mk | 1 + python/ovstest/args.py | 82 +++++++++ python/ovstest/rpcserver.py | 6 + python/ovstest/tests.py | 237 +++++++++++++++++++++++++ python/ovstest/util.py | 81 +++++++++ rhel/openvswitch.spec.in | 2 + utilities/.gitignore | 2 + utilities/automake.mk | 6 + utilities/ovs-l3ping.8.in | 114 ++++++++++++ utilities/ovs-l3ping.in | 76 ++++++++ utilities/ovs-test.in | 286 ++---------------------------- xenserver/openvswitch-xen.spec.in | 6 +- 16 files changed, 635 insertions(+), 277 deletions(-) create mode 100644 python/ovstest/tests.py create mode 100644 utilities/ovs-l3ping.8.in create mode 100644 utilities/ovs-l3ping.in diff --git a/NEWS b/NEWS index a858f9e8..1966ac21 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ post-v1.7.0 ------------------------ - New FAQ. Please send updates and additions! + - ovs-l3ping: + - A new test utility that can create L3 tunnel between two Open + vSwitches and detect connectivity issues. - ovs-ofctl: - "mod-port" command can now control all OpenFlow config flags. - OpenFlow: diff --git a/debian/openvswitch-test.install b/debian/openvswitch-test.install index a36c8281..a2ea4134 100644 --- a/debian/openvswitch-test.install +++ b/debian/openvswitch-test.install @@ -1,2 +1,3 @@ usr/share/openvswitch/python/ovstest usr/lib/python2.6/dist-packages/ usr/bin/ovs-test +usr/bin/ovs-l3ping diff --git a/debian/openvswitch-test.manpages b/debian/openvswitch-test.manpages index 683c9781..1a7f6d4a 100644 --- a/debian/openvswitch-test.manpages +++ b/debian/openvswitch-test.manpages @@ -1 +1,2 @@ _debian/utilities/ovs-test.8 +_debian/utilities/ovs-l3ping.8 diff --git a/manpages.mk b/manpages.mk index 1773263d..f391fdf9 100644 --- a/manpages.mk +++ b/manpages.mk @@ -120,6 +120,14 @@ utilities/ovs-dpctl.8.in: lib/common.man: lib/vlog.man: +utilities/ovs-l3ping.8: \ + utilities/ovs-l3ping.8.in \ + lib/common-syn.man \ + lib/common.man +utilities/ovs-l3ping.8.in: +lib/common-syn.man: +lib/common.man: + utilities/ovs-ofctl.8: \ utilities/ovs-ofctl.8.in \ lib/common.man \ diff --git a/python/automake.mk b/python/automake.mk index 96869e30..b656f088 100644 --- a/python/automake.mk +++ b/python/automake.mk @@ -3,6 +3,7 @@ ovstest_pyfiles = \ python/ovstest/args.py \ python/ovstest/rpcserver.py \ python/ovstest/tcp.py \ + python/ovstest/tests.py \ python/ovstest/udp.py \ python/ovstest/util.py \ python/ovstest/vswitch.py diff --git a/python/ovstest/args.py b/python/ovstest/args.py index 8e23a894..e90db2a5 100644 --- a/python/ovstest/args.py +++ b/python/ovstest/args.py @@ -78,6 +78,23 @@ def ip_optional_port(string, default_port, ip_callback): "must be colon-separated") +def ip_optional_port_port(string, default_port1, default_port2, ip_callback): + """Convert a string into IP, Port1, Port2 tuple. If any of ports were + missing, then default ports will be used. The fourth argument is a + callback that verifies whether IP address is given in the expected + format.""" + value = string.split(':') + if len(value) == 1: + return (ip_callback(value[0]), default_port1, default_port2) + elif len(value) == 2: + return (ip_callback(value[0]), port(value[1]), default_port2) + elif len(value) == 3: + return (ip_callback(value[0]), port(value[1]), port(value[2])) + else: + raise argparse.ArgumentTypeError("Expected IP address and at most " + "two colon-separated ports") + + def vlan_tag(string): """ This function verifies whether given string is a correct VLAN tag. @@ -154,6 +171,37 @@ def tunnel_types(string): return string.split(',') +def l3_endpoint_client(string): + """ + This function parses command line argument string in + remoteIP,localInnerIP[/mask][:ControlPort[:TestPort]],remoteInnerIP[: + ControlPort[:TestPort]] format. + """ + try: + remote_ip, me, he = string.split(',') + except ValueError: + raise argparse.ArgumentTypeError("All 3 IP addresses must be comma " + "separated.") + r = (ip_address(remote_ip), + ip_optional_port_port(me, CONTROL_PORT, DATA_PORT, ip_optional_mask), + ip_optional_port_port(he, CONTROL_PORT, DATA_PORT, ip_address)) + return r + + +def l3_endpoint_server(string): + """ + This function parses a command line argument string in + remoteIP,localInnerIP[/mask][:ControlPort] format. + """ + try: + remote_ip, me = string.split(',') + except ValueError: + raise argparse.ArgumentTypeError("Both IP addresses must be comma " + "separated.") + return (ip_address(remote_ip), + ip_optional_port(me, CONTROL_PORT, ip_optional_mask)) + + def ovs_initialize_args(): """ Initialize argument parsing for ovs-test utility. @@ -197,3 +245,37 @@ def ovs_initialize_args(): 'ovs-test server in the client mode by using 127.0.0.1 as ' 'OuterIP.') return parser.parse_args() + +def l3_initialize_args(): + """ + Initialize argument parsing for ovs-l3ping utility. + """ + parser = argparse.ArgumentParser(description='Test L3 tunnel ' + 'connectivity between two Open vSwitch instances.') + + parser.add_argument('-v', '--version', action='version', + version='ovs-l3ping (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.') + parser.add_argument("-i", "--interval", action='store', + dest="testInterval", default=5, type=int, + help='Interval for how long to run each test in seconds.') + + parser.add_argument("-t", "--tunnel-mode", action='store', + dest="tunnelMode", required=True, + help='Do L3 tests with this tunnel type.') + + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("-s", "--server", action="store", dest="server", + metavar="TUNNELIP,SERVER", + type=l3_endpoint_server, + help='Run in server mode and wait for the client to ' + 'connect.') + group.add_argument('-c', "--client", action="store", dest="client", + metavar="TUNNELIP,CLIENT,SERVER", + type=l3_endpoint_client, + help='Run in client mode and connect to the server.') + return parser.parse_args() diff --git a/python/ovstest/rpcserver.py b/python/ovstest/rpcserver.py index 82bec3ba..5c920144 100644 --- a/python/ovstest/rpcserver.py +++ b/python/ovstest/rpcserver.py @@ -343,6 +343,12 @@ class TestArena(xmlrpc.XMLRPC): """ return util.get_driver(iface) + def xmlrpc_get_interface_from_routing_decision(self, ip): + """ + Returns driver version + """ + return util.get_interface_from_routing_decision(ip) + def start_rpc_server(port): """ diff --git a/python/ovstest/tests.py b/python/ovstest/tests.py new file mode 100644 index 00000000..5d5a85f2 --- /dev/null +++ b/python/ovstest/tests.py @@ -0,0 +1,237 @@ +import math +import time + +import ovstest.util as util + +DEFAULT_TEST_BRIDGE = "ovstestbr0" +DEFAULT_TEST_PORT = "ovstestport0" +DEFAULT_TEST_TUN = "ovstestport1" +NO_HANDLE = -1 + + +def do_udp_tests(receiver, sender, tbwidth, duration, port_sizes): + """Schedule UDP tests between receiver and sender""" + server1 = util.rpc_client(receiver[0], receiver[1]) + server2 = util.rpc_client(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], + util.bandwidth_to_string(tbwidth))) + print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv Datagrams", + "Datagram Loss", "Bandwidth") + + for size in port_sizes: + listen_handle = NO_HANDLE + send_handle = NO_HANDLE + try: + packetcnt = (tbwidth * duration) / size + + listen_handle = server1.create_udp_listener(receiver[3]) + if listen_handle == NO_HANDLE: + 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( + (util.ip_from_cidr(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, util.bandwidth_to_string(bwidth)) + finally: + if listen_handle != NO_HANDLE: + server1.close_udp_listener(listen_handle) + if send_handle != NO_HANDLE: + server2.close_udp_sender(send_handle) + print "\n" + + +def do_tcp_tests(receiver, sender, duration): + """Schedule TCP tests between receiver and sender""" + server1 = util.rpc_client(receiver[0], receiver[1]) + server2 = util.rpc_client(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 = NO_HANDLE + send_handle = NO_HANDLE + try: + listen_handle = server1.create_tcp_listener(receiver[3]) + if listen_handle == NO_HANDLE: + 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(util.ip_from_cidr(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, + util.bandwidth_to_string(bwidth)) + finally: + if listen_handle != NO_HANDLE: + server1.close_tcp_listener(listen_handle) + if send_handle != NO_HANDLE: + server2.close_tcp_sender(send_handle) + print "\n" + + +def do_l3_tests(node1, node2, bandwidth, duration, ps, type): + """ + Do L3 tunneling tests. Each node is given as 4 tuple - physical + interface IP, control port, test IP and test port. + """ + server1 = util.rpc_client(node1[0], node1[1]) + server2 = util.rpc_client(node2[0], node2[1]) + servers_with_bridges = [] + try: + server1.create_bridge(DEFAULT_TEST_BRIDGE) + servers_with_bridges.append(server1) + server2.create_bridge(DEFAULT_TEST_BRIDGE) + servers_with_bridges.append(server2) + + server1.interface_up(DEFAULT_TEST_BRIDGE) + server2.interface_up(DEFAULT_TEST_BRIDGE) + + server1.interface_assign_ip(DEFAULT_TEST_BRIDGE, node1[2], None) + server2.interface_assign_ip(DEFAULT_TEST_BRIDGE, node2[2], None) + + server1.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN) + server2.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN) + + server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type", + None, type) + server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type", + None, type) + server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options", + "remote_ip", node2[0]) + server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options", + "remote_ip", node1[0]) + + do_udp_tests(node1, node2, bandwidth, duration, ps) + do_udp_tests(node2, node1, bandwidth, duration, ps) + do_tcp_tests(node1, node2, duration) + do_tcp_tests(node2, node1, duration) + + finally: + for server in servers_with_bridges: + server.del_bridge(DEFAULT_TEST_BRIDGE) + + + +def do_vlan_tests(node1, node2, bandwidth, duration, ps, tag): + """ + Do VLAN tests between node1 and node2. Each node is given + as 4 tuple - physical interface IP, control port, test IP and + test port. + """ + server1 = util.rpc_client(node1[0], node1[1]) + server2 = util.rpc_client(node2[0], node2[1]) + + br_name1 = None + br_name2 = None + + servers_with_test_ports = [] + + try: + interface_node1 = server1.get_interface(node1[0]) + interface_node2 = server2.get_interface(node2[0]) + + if server1.is_ovs_bridge(interface_node1): + br_name1 = interface_node1 + else: + br_name1 = DEFAULT_TEST_BRIDGE + server1.create_test_bridge(br_name1, interface_node1) + + if server2.is_ovs_bridge(interface_node2): + br_name2 = interface_node2 + else: + br_name2 = DEFAULT_TEST_BRIDGE + server2.create_test_bridge(br_name2, interface_node2) + + server1.add_port_to_bridge(br_name1, DEFAULT_TEST_PORT) + servers_with_test_ports.append(server1) + server2.add_port_to_bridge(br_name2, DEFAULT_TEST_PORT) + servers_with_test_ports.append(server2) + + server1.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag) + server2.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag) + + server1.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None, + "internal") + server2.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None, + "internal") + + server1.interface_assign_ip(DEFAULT_TEST_PORT, node1[2], None) + server2.interface_assign_ip(DEFAULT_TEST_PORT, node2[2], None) + + server1.interface_up(DEFAULT_TEST_PORT) + server2.interface_up(DEFAULT_TEST_PORT) + + do_udp_tests(node1, node2, bandwidth, duration, ps) + do_udp_tests(node2, node1, bandwidth, duration, ps) + do_tcp_tests(node1, node2, duration) + do_tcp_tests(node2, node1, duration) + + finally: + for server in servers_with_test_ports: + server.del_port_from_bridge(DEFAULT_TEST_PORT) + if br_name1 == DEFAULT_TEST_BRIDGE: + server1.del_test_bridge(br_name1, interface_node1) + if br_name2 == DEFAULT_TEST_BRIDGE: + server2.del_test_bridge(br_name2, interface_node2) + + +def do_direct_tests(node1, node2, bandwidth, duration, ps): + """ + Do tests between outer IPs without involving Open vSwitch. Each + node is given as 4 tuple - physical interface IP, control port, + test IP and test port. Direct tests will use physical interface + IP as the test IP address. + """ + n1 = (node1[0], node1[1], node1[0], node1[3]) + n2 = (node2[0], node2[1], node2[0], node2[3]) + + do_udp_tests(n1, n2, bandwidth, duration, ps) + do_udp_tests(n2, n1, bandwidth, duration, ps) + do_tcp_tests(n1, n2, duration) + do_tcp_tests(n2, n1, duration) + + +def configure_l3(conf, tunnel_mode): + """ + This function creates a temporary test bridge and adds an L3 tunnel. + """ + s = util.start_local_server(conf[1][1]) + server = util.rpc_client("127.0.0.1", conf[1][1]) + server.create_bridge(DEFAULT_TEST_BRIDGE) + server.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_PORT) + server.interface_up(DEFAULT_TEST_BRIDGE) + server.interface_assign_ip(DEFAULT_TEST_BRIDGE, conf[1][0], + None) + server.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", + None, tunnel_mode) + server.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "options", + "remote_ip", conf[0]) + return s diff --git a/python/ovstest/util.py b/python/ovstest/util.py index ee2a8786..d61e4ba4 100644 --- a/python/ovstest/util.py +++ b/python/ovstest/util.py @@ -19,10 +19,13 @@ import array import exceptions import fcntl import os +import select import socket import struct +import signal import subprocess import re +import xmlrpclib def str_ip(ip_address): @@ -147,3 +150,81 @@ def move_routes(iface1, iface2): for route in out.splitlines(): args = ["ip", "route", "replace", "dev", iface2] + route.split() start_process(args) + + +def get_interface_from_routing_decision(ip): + """ + This function returns the interface through which the given ip address + is reachable. + """ + args = ["ip", "route", "get", ip] + ret, out, _err = start_process(args) + if ret == 0: + iface = re.search(r'dev (\S+)', out) + if iface: + return iface.group(1) + return None + + +def rpc_client(ip, port): + return xmlrpclib.Server("http://%s:%u/" % (ip, port), allow_none=True) + + +def sigint_intercept(): + """ + Intercept SIGINT from child (the local ovs-test server process). + """ + signal.signal(signal.SIGINT, signal.SIG_IGN) + + +def start_local_server(port): + """ + This function spawns an ovs-test server that listens on specified port + and blocks till the spawned ovs-test server is ready to accept XML RPC + connections. + """ + p = subprocess.Popen(["ovs-test", "-s", str(port)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + preexec_fn=sigint_intercept) + fcntl.fcntl( p.stdout.fileno(),fcntl.F_SETFL, + fcntl.fcntl(p.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK) + + while p.poll() is None: + fd = select.select([p.stdout.fileno()], [], [])[0] + if fd: + out = p.stdout.readline() + if out.startswith("Starting RPC server"): + break + if p.poll() is not None: + raise RuntimeError("Couldn't start local instance of ovs-test server") + return p + + +def get_datagram_sizes(mtu1, mtu2): + """ + This function calculates all the "interesting" datagram sizes so that + we test both - receive and send side with different packets sizes. + """ + s1 = set([8, mtu1 - 100, mtu1 - 28, mtu1]) + s2 = set([8, mtu2 - 100, mtu2 - 28, mtu2]) + return sorted(s1.union(s2)) + + +def ip_from_cidr(string): + """ + This function removes the netmask (if present) from the given string and + returns the IP address. + """ + token = string.split("/") + return token[0] + + +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" diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in index 54442fbf..de22c86c 100644 --- a/rhel/openvswitch.spec.in +++ b/rhel/openvswitch.spec.in @@ -59,7 +59,9 @@ rm \ $RPM_BUILD_ROOT/usr/bin/ovs-controller \ $RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \ $RPM_BUILD_ROOT/usr/bin/ovs-test \ + $RPM_BUILD_ROOT/usr/bin/ovs-l3ping \ $RPM_BUILD_ROOT/usr/share/man/man8/ovs-test.8 \ + $RPM_BUILD_ROOT/usr/share/man/man8/ovs-l3ping.8 \ $RPM_BUILD_ROOT/usr/sbin/ovs-vlan-bug-workaround \ $RPM_BUILD_ROOT/usr/share/man/man8/ovs-vlan-bug-workaround.8 diff --git a/utilities/.gitignore b/utilities/.gitignore index 90f47ffe..4f96a4f6 100644 --- a/utilities/.gitignore +++ b/utilities/.gitignore @@ -13,6 +13,8 @@ /ovs-ctl /ovs-dpctl /ovs-dpctl.8 +/ovs-l3ping +/ovs-l3ping.8 /ovs-lib /ovs-ofctl /ovs-ofctl.8 diff --git a/utilities/automake.mk b/utilities/automake.mk index f3da1b16..04a7fdcc 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -7,6 +7,7 @@ bin_PROGRAMS += \ bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl utilities/ovs-parse-leaks if HAVE_PYTHON bin_SCRIPTS += \ + utilities/ovs-l3ping \ utilities/ovs-pcap \ utilities/ovs-tcpundump \ utilities/ovs-test \ @@ -22,6 +23,7 @@ scripts_DATA += utilities/ovs-lib EXTRA_DIST += \ utilities/ovs-check-dead-ifs.in \ utilities/ovs-ctl.in \ + utilities/ovs-l3ping.in \ utilities/ovs-lib.in \ utilities/ovs-parse-leaks.in \ utilities/ovs-pcap.in \ @@ -37,6 +39,7 @@ MAN_ROOTS += \ utilities/ovs-controller.8.in \ utilities/ovs-ctl.8 \ utilities/ovs-dpctl.8.in \ + utilities/ovs-l3ping.8.in \ utilities/ovs-ofctl.8.in \ utilities/ovs-parse-leaks.8 \ utilities/ovs-pcap.1.in \ @@ -54,6 +57,8 @@ DISTCLEANFILES += \ utilities/ovs-check-dead-ifs \ utilities/ovs-controller.8 \ utilities/ovs-dpctl.8 \ + utilities/ovs-l3ping \ + utilities/ovs-l3ping.8 \ utilities/ovs-lib \ utilities/ovs-ofctl.8 \ utilities/ovs-parse-leaks \ @@ -76,6 +81,7 @@ man_MANS += \ utilities/ovs-benchmark.1 \ utilities/ovs-controller.8 \ utilities/ovs-dpctl.8 \ + utilities/ovs-l3ping.8 \ utilities/ovs-ofctl.8 \ utilities/ovs-parse-leaks.8 \ utilities/ovs-pcap.1 \ diff --git a/utilities/ovs-l3ping.8.in b/utilities/ovs-l3ping.8.in new file mode 100644 index 00000000..d3ce3cb5 --- /dev/null +++ b/utilities/ovs-l3ping.8.in @@ -0,0 +1,114 @@ +.de IQ +. br +. ns +. IP "\\$1" +.. +.TH ovs\-l3ping 1 "June 2012" "Open vSwitch" "Open vSwitch Manual" +. +.SH NAME +\fBovs\-l3ping\fR \- check network deployment for L3 tunneling +problems +. +.SH SYNOPSIS +\fBovs\-l3ping\fR \fB\-s\fR \fITunnelRemoteIP,InnerIP[/mask]\fR \fB\-t\fR \fItunnelmode\fR +.br +\fBovs\-l3ping\fR \fB\-s\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR \fB\-t\fR \fItunnelmode\fR +.PP +\fBovs\-l3ping\fR \fB\-c\fR \fITunnelRemoteIP,InnerIP[/mask],RemoteInnerIP\fR \fB\-t\fR \fItunnelmode\fR +.br +\fBovs\-l3ping\fR \fB\-c\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\ +[:DataPort]],RemoteInnerIP[:ControlPort[:DataPort]]\fR +[\fB\-b\fR \fItargetbandwidth\fR] [\fB\-i\fR \fItestinterval\fR] +\fB\-t\fR \fItunnelmode\fR +.so lib/common-syn.man +. +.SH DESCRIPTION +The \fBovs\-l3ping\fR program may be used to check for problems that could +be caused by invalid routing policy, misconfigured firewall in the tunnel +path or a bad NIC driver. On one of the nodes, run \fBovs\-l3ping\fR in +server mode and on the other node run it in client mode. The client and +server will establish L3 tunnel, over which client will give further testing +instructions. The \fBovs\-l3ping\fR client will perform UDP and TCP tests. +This tool is different from \fBovs\-test\fR that it encapsulates XML/RPC +control connection over the tunnel, so there is no need to open special holes +in firewall. +.PP +UDP tests can report packet loss and achieved bandwidth for various +datagram sizes. By default target bandwidth for UDP tests is 1Mbit/s. +.PP +TCP tests report only achieved bandwidth, because kernel TCP stack +takes care of flow control and packet loss. +. +.SS "Client Mode" +An \fBovs\-l3ping\fR client will create a L3 tunnel and connect over it to the +\fBovs\-l3ping\fR server to schedule the tests. \fITunnelRemoteIP\fR is the +peer's IP address, where tunnel will be terminated. \fIInnerIP\fR is the +address that will be temporarily assigned during testing. All test traffic +originating from this IP address to the \fIRemoteInnerIP\fR will be tunneled. +It is possible to override default \fIControlPort\fR and \fIDataPort\fR, if +there is any other application that already listens on those two ports. +. +.SS "Server Mode" +To conduct tests, \fBovs\-l3ping\fR server must be running. It is required +that both client and server \fIInnerIP\fR addresses are in the same subnet. +It is possible to specify \fIInnerIP\fR with netmask in CIDR format. +. +.SH OPTIONS +One of \fB\-s\fR or \fB\-c\fR is required. The \fB\-t\fR option is +also required. +. +.IP "\fB\-s \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR" +.IQ "\fB\-\-server\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR" +Run in server mode and create L3 tunnel with the client that will be +accepting tunnel at \fITunnelRemoteIP\fR address. The socket on +\fIInnerIP[:ControlPort]\fR will be used to receive further instructions +from the client. +. +.IP "\fB\-c \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\ +[:DataPort]],RemoteInnerIP[:ControlPort[:DataPort]]\fR" +.IQ "\fB\-\-client \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\ +[:DataPort]],RemoteInnerIP[:ControlPort[:DataPort]]\fR" +Run in client mode and create L3 tunnel with the server on +\fITunnelRemoteIP\fR. The client will use \fIInnerIP\fR to generate test +traffic with the server's \fIRemoteInnerIP\fR. +. +.IP "\fB\-b \fItargetbandwidth\fR" +.IQ "\fB\-\-bandwidth\fR \fItargetbandwidth\fR" +Target bandwidth for UDP tests. The \fItargetbandwidth\fR must be given in +bits per second. It is possible to use postfix M or K to alter the target +bandwidth magnitude. +. +.IP "\fB\-i \fItestinterval\fR" +.IQ "\fB\-\-interval\fR \fItestinterval\fR" +How long each test should run. By default 5 seconds. +. +.IP "\fB\-t \fItunnelmode\fR" +.IQ "\fB\-\-tunnel\-mode\fR \fItunnelmode\fR" +Specify the tunnel type. This option must match on server and client. +. +.so lib/common.man +. +.SH EXAMPLES +.PP +On host 192.168.122.220 start \fBovs\-l3ping\fR in server mode. This command +will create a temporary GRE tunnel with the host 192.168.122.236 and assign +10.1.1.1/28 as the inner IP address, where client will have to connect: +.IP +.B ovs\-l3ping -s 192.168.122.236,10.1.1.1/28 -t gre +. +.PP +On host 192.168.122.236 start \fBovs\-l3ping\fR in client mode. This command +will use 10.1.1.2/28 as the local inner IP address and will connect over the +L3 tunnel to the server's inner IP address at 10.1.1.1. +.IP +.B ovs\-l3ping -c 192.168.122.220,10.1.1.2/28,10.1.1.1 -t gre +. +.SH SEE ALSO +. +.BR ovs\-vswitchd (8), +.BR ovs\-ofctl (8), +.BR ovs\-vsctl (8), +.BR ovs\-vlan\-test (8), +.BR ovs\-test (8), +.BR ethtool (8), +.BR uname (1) diff --git a/utilities/ovs-l3ping.in b/utilities/ovs-l3ping.in new file mode 100644 index 00000000..1b079729 --- /dev/null +++ b/utilities/ovs-l3ping.in @@ -0,0 +1,76 @@ +#! @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 L3 ping utility allows to do tests between two remote hosts without +opening holes in the firewall for the XML RPC control connection. This is +achieved by tunneling the control connection inside the tunnel itself. +""" + +import socket +import xmlrpclib + +import ovstest.args as args +import ovstest.tests as tests +import ovstest.util as util + + +def get_packet_sizes(me, he, remote_ip): + """ + This function retrieves MTUs from both hosts and returns a list of + packet sizes, that are more likely to uncover possible configuration + issues. + """ + mtu_node1 = 1500 + mtu_node2 = 1500 + server1 = util.rpc_client(me[0], me[1]) + server2 = util.rpc_client(he[0], he[1]) + iface1 = server2.get_interface(remote_ip) + iface2 = server1.get_interface_from_routing_decision(remote_ip) + if iface1: + mtu_node1 = server2.get_interface_mtu(iface1) + if iface2: + mtu_node2 = server1.get_interface_mtu(iface2) + return util.get_datagram_sizes(mtu_node1, mtu_node2) + + +if __name__ == '__main__': + local_server = None + try: + args = args.l3_initialize_args() + tunnel_mode = args.tunnelMode + if args.server is not None: # Start in server mode + local_server = tests.configure_l3(args.server, tunnel_mode) + local_server.wait() + elif args.client is not None: # Run in client mode + bandwidth = args.targetBandwidth + interval = args.testInterval + me = (util.ip_from_cidr(args.client[1][0]), args.client[1][1], + args.client[1][0], args.client[1][2]) + he = (args.client[2][0], args.client[2][1], + args.client[2][0], args.client[2][2]) + local_server = tests. configure_l3(args.client, tunnel_mode) + ps = get_packet_sizes(me, he, args.client[0]) + tests.do_direct_tests(me, he, bandwidth, interval, ps) + except KeyboardInterrupt: + print "Terminating" + except xmlrpclib.Fault: + print "Couldn't contact peer" + except socket.error: + print "Couldn't contact peer" + except xmlrpclib.ProtocolError: + print "XMLRPC control channel was abruptly terminated" + finally: + if local_server is not None: + local_server.terminate() diff --git a/utilities/ovs-test.in b/utilities/ovs-test.in index b53302f0..fb1f9ad2 100644 --- a/utilities/ovs-test.in +++ b/utilities/ovs-test.in @@ -32,80 +32,18 @@ import twisted import ovstest.args as args import ovstest.rpcserver as rpcserver +import ovstest.tests as tests +import ovstest.util as util DEFAULT_TEST_BRIDGE = "ovstestbr0" DEFAULT_TEST_PORT = "ovstestport0" DEFAULT_TEST_TUN = "ovstestport1" -def rpc_client(ip, port): - return xmlrpclib.Server("http://%s:%u/" % (ip, port), allow_none=True) - - -def sigint_intercept(): - """ - Intercept SIGINT from child (the local ovs-test server process). - """ - signal.signal(signal.SIGINT, signal.SIG_IGN) - - -def start_local_server(port): - """ - This function spawns an ovs-test server that listens on specified port - and blocks till the spawned ovs-test server is ready to accept XML RPC - connections. - """ - p = subprocess.Popen(["ovs-test", "-s", str(port)], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - preexec_fn = sigint_intercept) - fcntl.fcntl( p.stdout.fileno(),fcntl.F_SETFL, - fcntl.fcntl(p.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK) - - while p.poll() is None: - fd = select.select([p.stdout.fileno()], [], [])[0] - if fd: - out = p.stdout.readline() - if out.startswith("Starting RPC server"): - break - if p.poll() is not None: - raise RuntimeError("Couldn't start local instance of ovs-test server") - return p - - -def get_datagram_sizes(mtu1, mtu2): - """ - This function calculates all the "interesting" datagram sizes so that - we test both - receive and send side with different packets sizes. - """ - s1 = set([8, mtu1 - 100, mtu1 - 28, mtu1]) - s2 = set([8, mtu2 - 100, mtu2 - 28, mtu2]) - return sorted(s1.union(s2)) - - -def ip_from_cidr(string): - """ - This function removes the netmask (if present) from the given string and - returns the IP address. - """ - token = string.split("/") - return token[0] - - -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]) - server = rpc_client(node[0], node[1]) + server = util.rpc_client(node[0], node[1]) interface_name = server.get_interface(node[0]) phys_iface = None uname = server.uname() @@ -140,210 +78,6 @@ def collect_information(node): return mtu -def do_udp_tests(receiver, sender, tbwidth, duration, port_sizes): - """Schedule UDP tests between receiver and sender""" - server1 = rpc_client(receiver[0], receiver[1]) - server2 = rpc_client(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 port_sizes: - 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( - (ip_from_cidr(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 = rpc_client(receiver[0], receiver[1]) - server2 = rpc_client(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(ip_from_cidr(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" - - -def do_l3_tests(node1, node2, bandwidth, duration, ps, type): - """ - Do L3 tunneling tests. - """ - server1 = rpc_client(node1[0], node1[1]) - server2 = rpc_client(node2[0], node2[1]) - servers_with_bridges = [] - try: - server1.create_bridge(DEFAULT_TEST_BRIDGE) - servers_with_bridges.append(server1) - server2.create_bridge(DEFAULT_TEST_BRIDGE) - servers_with_bridges.append(server2) - - server1.interface_up(DEFAULT_TEST_BRIDGE) - server2.interface_up(DEFAULT_TEST_BRIDGE) - - server1.interface_assign_ip(DEFAULT_TEST_BRIDGE, node1[2], None) - server2.interface_assign_ip(DEFAULT_TEST_BRIDGE, node2[2], None) - - server1.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN) - server2.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN) - - server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type", - None, type) - server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type", - None, type) - server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options", - "remote_ip", node2[0]) - server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options", - "remote_ip", node1[0]) - - do_udp_tests(node1, node2, bandwidth, duration, ps) - do_udp_tests(node2, node1, bandwidth, duration, ps) - do_tcp_tests(node1, node2, duration) - do_tcp_tests(node2, node1, duration) - - finally: - for server in servers_with_bridges: - server.del_bridge(DEFAULT_TEST_BRIDGE) - - - -def do_vlan_tests(node1, node2, bandwidth, duration, ps, tag): - """ - Do VLAN tests between node1 and node2. - """ - server1 = rpc_client(node1[0], node1[1]) - server2 = rpc_client(node2[0], node2[1]) - - br_name1 = None - br_name2 = None - - servers_with_test_ports = [] - - try: - interface_node1 = server1.get_interface(node1[0]) - interface_node2 = server2.get_interface(node2[0]) - - if server1.is_ovs_bridge(interface_node1): - br_name1 = interface_node1 - else: - br_name1 = DEFAULT_TEST_BRIDGE - server1.create_test_bridge(br_name1, interface_node1) - - if server2.is_ovs_bridge(interface_node2): - br_name2 = interface_node2 - else: - br_name2 = DEFAULT_TEST_BRIDGE - server2.create_test_bridge(br_name2, interface_node2) - - server1.add_port_to_bridge(br_name1, DEFAULT_TEST_PORT) - servers_with_test_ports.append(server1) - server2.add_port_to_bridge(br_name2, DEFAULT_TEST_PORT) - servers_with_test_ports.append(server2) - - server1.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag) - server2.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag) - - server1.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None, - "internal") - server2.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None, - "internal") - - server1.interface_assign_ip(DEFAULT_TEST_PORT, node1[2], None) - server2.interface_assign_ip(DEFAULT_TEST_PORT, node2[2], None) - - server1.interface_up(DEFAULT_TEST_PORT) - server2.interface_up(DEFAULT_TEST_PORT) - - do_udp_tests(node1, node2, bandwidth, duration, ps) - do_udp_tests(node2, node1, bandwidth, duration, ps) - do_tcp_tests(node1, node2, duration) - do_tcp_tests(node2, node1, duration) - - finally: - for server in servers_with_test_ports: - server.del_port_from_bridge(DEFAULT_TEST_PORT) - if br_name1 == DEFAULT_TEST_BRIDGE: - server1.del_test_bridge(br_name1, interface_node1) - if br_name2 == DEFAULT_TEST_BRIDGE: - server2.del_test_bridge(br_name2, interface_node2) - - -def do_direct_tests(node1, node2, bandwidth, duration, ps): - """ - Do tests between outer IPs without involving Open vSwitch - """ - n1 = (node1[0], node1[1], node1[0], node1[3]) - n2 = (node2[0], node2[1], node2[0], node2[3]) - - do_udp_tests(n1, n2, bandwidth, duration, ps) - do_udp_tests(n2, n1, bandwidth, duration, ps) - do_tcp_tests(n1, n2, duration) - do_tcp_tests(n2, n1, duration) - - if __name__ == '__main__': local_server = None try: @@ -360,10 +94,10 @@ if __name__ == '__main__': # ovs-test server by looking at the first OuterIP. if it is a # 127.0.0.1 then spawn local ovs-test server. if node1[0] == "127.0.0.1": - local_server = start_local_server(node1[1]) + local_server = util.start_local_server(node1[1]) # We must determine the IP address that local ovs-test server # will use: - me = rpc_client(node1[0], node1[1]) + me = util.rpc_client(node1[0], node1[1]) my_ip = me.get_my_address_from(node2[0], node2[1]) node1 = (my_ip, node1[1], node1[2], node1[3]) @@ -372,7 +106,7 @@ if __name__ == '__main__': bandwidth = ovs_args.targetBandwidth interval = ovs_args.testInterval - ps = get_datagram_sizes(mtu_node1, mtu_node2) + ps = util.get_datagram_sizes(mtu_node1, mtu_node2) direct = ovs_args.direct vlan_tag = ovs_args.vlanTag @@ -380,15 +114,17 @@ if __name__ == '__main__': if direct is not None: print "Performing direct tests" - do_direct_tests(node2, node1, bandwidth, interval, ps) + tests.do_direct_tests(node2, node1, bandwidth, interval, ps) if vlan_tag is not None: print "Performing VLAN tests" - do_vlan_tests(node2, node1, bandwidth, interval, ps, vlan_tag) + tests.do_vlan_tests(node2, node1, bandwidth, interval, ps, + vlan_tag) for tmode in tunnel_modes: print "Performing", tmode, "tests" - do_l3_tests(node2, node1, bandwidth, interval, ps, tmode) + tests.do_l3_tests(node2, node1, bandwidth, interval, ps, + tmode) except KeyboardInterrupt: pass diff --git a/xenserver/openvswitch-xen.spec.in b/xenserver/openvswitch-xen.spec.in index e051b314..3b9582eb 100644 --- a/xenserver/openvswitch-xen.spec.in +++ b/xenserver/openvswitch-xen.spec.in @@ -125,13 +125,15 @@ rm \ $RPM_BUILD_ROOT/usr/bin/ovs-benchmark \ $RPM_BUILD_ROOT/usr/sbin/ovs-bugtool \ $RPM_BUILD_ROOT/usr/bin/ovs-controller \ + $RPM_BUILD_ROOT/usr/bin/ovs-l3ping \ $RPM_BUILD_ROOT/usr/bin/ovs-pki \ $RPM_BUILD_ROOT/usr/bin/ovs-test \ - $RPM_BUILD_ROOT/usr/share/man/man8/ovs-test.8 \ $RPM_BUILD_ROOT/usr/share/man/man1/ovs-benchmark.1 \ $RPM_BUILD_ROOT/usr/share/man/man8/ovs-bugtool.8 \ $RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \ - $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8 + $RPM_BUILD_ROOT/usr/share/man/man8/ovs-l3ping.8 + $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8 \ + $RPM_BUILD_ROOT/usr/share/man/man8/ovs-test.8 install -d -m 755 $RPM_BUILD_ROOT/var/lib/openvswitch -- 2.30.2