mirror of
https://github.com/openvswitch/ovs
synced 2025-08-30 22:05:19 +00:00
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 <aatteka@nicira.com>
This commit is contained in:
@@ -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
|
||||
|
@@ -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()
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
237
python/ovstest/tests.py
Normal file
237
python/ovstest/tests.py
Normal file
@@ -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
|
@@ -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"
|
||||
|
Reference in New Issue
Block a user