ovs-l3ping: A new test utility that allows to detect L3 tunneling issues
authorAnsis Atteka <aatteka@nicira.com>
Thu, 28 Jun 2012 22:52:40 +0000 (15:52 -0700)
committerAnsis Atteka <aatteka@nicira.com>
Mon, 2 Jul 2012 19:23:49 +0000 (12:23 -0700)
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>
16 files changed:
NEWS
debian/openvswitch-test.install
debian/openvswitch-test.manpages
manpages.mk
python/automake.mk
python/ovstest/args.py
python/ovstest/rpcserver.py
python/ovstest/tests.py [new file with mode: 0644]
python/ovstest/util.py
rhel/openvswitch.spec.in
utilities/.gitignore
utilities/automake.mk
utilities/ovs-l3ping.8.in [new file with mode: 0644]
utilities/ovs-l3ping.in [new file with mode: 0644]
utilities/ovs-test.in
xenserver/openvswitch-xen.spec.in

diff --git a/NEWS b/NEWS
index a858f9e8b83094d22fb7da7fba3679cdceaf843e..1966ac21a0f6d34b174da4672127532f1680b7a3 100644 (file)
--- 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:
index a36c82812c3939393c529c0ae78927d83968d654..a2ea413488756d82dbe25926b270f514326f9a66 100644 (file)
@@ -1,2 +1,3 @@
 usr/share/openvswitch/python/ovstest usr/lib/python2.6/dist-packages/
 usr/bin/ovs-test
+usr/bin/ovs-l3ping
index 683c978163cb49ce193176e311b97f9e9741d9f9..1a7f6d4a551bbe28f8e37e37352bb44549a8ce69 100644 (file)
@@ -1 +1,2 @@
 _debian/utilities/ovs-test.8
+_debian/utilities/ovs-l3ping.8
index 1773263da3236bc003ca2245c01acd3daa605c45..f391fdf9d2107d208788f63a5e6b1a0f06210772 100644 (file)
@@ -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 \
index 96869e30e3ba1bf073ae99c520dd5f0880eefd71..b656f088f6bae7da0d4a04d9f5cc85446e976669 100644 (file)
@@ -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
index 8e23a894603f7e5cf5527d5cc14b5e423f55009b..e90db2a53b7794086a41b0822b053d1507fe0a92 100644 (file)
@@ -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()
index 82bec3badce0dc6a239ceacce7b501a74204062c..5c9201441b165c047e56b43304cc84df12fd43d5 100644 (file)
@@ -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 (file)
index 0000000..5d5a85f
--- /dev/null
@@ -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
index ee2a87867af3b66b83cecbc99f01592389b87fca..d61e4ba44dfaeb08d7fa70abe34596357ea54bb0 100644 (file)
@@ -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"
index 54442fbfbd1fdd63f9d77ee7c96957a09fe4e892..de22c86c5b340e53bf08aec6b5635133a760d37a 100644 (file)
@@ -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
 
index 90f47ffece2927f7866cd16817a4105f2c066be1..4f96a4f6a2b781011aae8f84ffdf63342a080a08 100644 (file)
@@ -13,6 +13,8 @@
 /ovs-ctl
 /ovs-dpctl
 /ovs-dpctl.8
+/ovs-l3ping
+/ovs-l3ping.8
 /ovs-lib
 /ovs-ofctl
 /ovs-ofctl.8
index f3da1b168c2037b5ee78b4f4a10691afba1c509e..04a7fdccac7c64c6819fc218f5d2e6dce6b075c5 100644 (file)
@@ -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 (file)
index 0000000..d3ce3cb
--- /dev/null
@@ -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 (file)
index 0000000..1b07972
--- /dev/null
@@ -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()
index b53302f0aeeddec3b955aedf5250682a749fa59c..fb1f9ad2abb860ec9d854762a157a2853ba929da 100644 (file)
@@ -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
index e051b3147d6d281fb78c2d1aee14c8b591ab6ac0..3b9582ebac25dd35f4e136eb5c665bbebe614932 100644 (file)
@@ -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