Skip to content

Commit

Permalink
ovs-l3ping: A new test utility that allows to detect L3 tunneling issues
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Ansis Atteka committed Jul 2, 2012
1 parent 969e46a commit 2d8bdd8
Show file tree
Hide file tree
Showing 16 changed files with 635 additions and 277 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
1 change: 1 addition & 0 deletions debian/openvswitch-test.install
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
usr/share/openvswitch/python/ovstest usr/lib/python2.6/dist-packages/
usr/bin/ovs-test
usr/bin/ovs-l3ping
1 change: 1 addition & 0 deletions debian/openvswitch-test.manpages
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
_debian/utilities/ovs-test.8
_debian/utilities/ovs-l3ping.8
8 changes: 8 additions & 0 deletions manpages.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions python/automake.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 82 additions & 0 deletions python/ovstest/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
6 changes: 6 additions & 0 deletions python/ovstest/rpcserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ def xmlrpc_get_driver(self, iface):
"""
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):
"""
Expand Down
237 changes: 237 additions & 0 deletions python/ovstest/tests.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 2d8bdd8

Please sign in to comment.