[ovs-dev] [PATCHv4] ovs-test: Enhancements to the ovs-test tool

Reid Price reid at nicira.com
Tue Apr 17 17:56:25 PDT 2012


Thanks, skimmed over the whole thing again, looked good to me.  One comment
inline
that you might want to address, I think it would break if the first line
ever changes.

On Tue, Apr 17, 2012 at 5:18 PM, Ansis Atteka <aatteka at nicira.com> wrote:

> -Implemented support for ovs-test client, so that it could automatically
> spawn an ovs-test server process from itself. This reduces the number of
> commands the user have to type to get tests running.
> -Automated creation of OVS bridges and ports (for VLAN and GRE tests), so
> that
> user would not need to invoke ovs-vsctl manually to switch from direct,
> 802.1Q
> and GRE tests.
> -Fixed some pylint reported warnings.
> -Fixed ethtool invocation so that we always try to query the physical
> interface
> to get the driver name and version.
> -and some others enhancements.
>
> The new usage:
> Node1:ovs-test -s 15531
> Node2:ovs-test -c 127.0.0.1,1.1.1.1 192.168.122.151,1.1.1.2 -d -l 125 -t
> gre
>
> Signed-off-by: Ansis Atteka <aatteka at nicira.com>
> ---
>  NEWS                        |    4 +
>  python/automake.mk          |    3 +-
>  python/ovstest/args.py      |  180 ++++++++++++++++++-------
>  python/ovstest/rpcserver.py |  176 +++++++++++++++++++++++--
>  python/ovstest/tcp.py       |   27 +----
>  python/ovstest/udp.py       |   19 +--
>  python/ovstest/util.py      |  105 ++++++++++++--
>  python/ovstest/vswitch.py   |  108 +++++++++++++++
>  utilities/ovs-test.8.in     |  152 ++++++++++++---------
>  utilities/ovs-test.in       |  314
> +++++++++++++++++++++++++++++++++++++------
>  10 files changed, 870 insertions(+), 218 deletions(-)
>  create mode 100644 python/ovstest/vswitch.py
>
> diff --git a/NEWS b/NEWS
> index ed3fc88..ac00335 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -8,6 +8,10 @@ post-v1.6.0
>       Internetwork Control (0xc0).
>     - Added the granular link health statistics, 'cfm_health', to an
>       interface.
> +    - ovs-test:
> +        - Added support for spawning ovs-test server from the client.
> +        - Now ovs-test is able to automatically create test bridges and
> ports.
> +
>
>
>  v1.6.0 - xx xxx xxxx
> diff --git a/python/automake.mk b/python/automake.mk
> index b60e26b..59db000 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -6,7 +6,8 @@ ovstest_pyfiles = \
>        python/ovstest/rpcserver.py \
>        python/ovstest/tcp.py \
>        python/ovstest/udp.py \
> -       python/ovstest/util.py
> +       python/ovstest/util.py \
> +       python/ovstest/vswitch.py
>
>  ovs_pyfiles = \
>        python/ovs/__init__.py \
> diff --git a/python/ovstest/args.py b/python/ovstest/args.py
> index d6b4756..3f8d0d9 100644
> --- a/python/ovstest/args.py
> +++ b/python/ovstest/args.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2011 Nicira Networks
> +# Copyright (c) 2011, 2012 Nicira Networks
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -17,11 +17,14 @@ ovsargs provide argument parsing for ovs-test utility
>  """
>
>  import argparse
> -import socket
>  import re
> +import socket
> +import sys
>
> +CONTROL_PORT = 15531
> +DATA_PORT = 15532
>
> -def ip(string):
> +def ip_address(string):
>     """Verifies if string is a valid IP address"""
>     try:
>         socket.inet_aton(string)
> @@ -30,8 +33,28 @@ def ip(string):
>     return string
>
>
> +def ip_optional_mask(string):
> +    """
> +    Verifies if string contains a valid IP address and an optional mask in
> +    CIDR notation.
> +    """
> +    token = string.split("/")
> +    if len(token) > 2:
> +        raise argparse.ArgumentTypeError("IP address and netmask must be "
> +                                         "separated by a single slash")
> +    elif len(token) == 2:
> +        try:
> +            mask = int(token[1])
> +        except ValueError:
> +            raise argparse.ArgumentTypeError("Netmask is not a valid
> integer")
> +        if mask < 0 or mask > 31:
> +            raise argparse.ArgumentTypeError("Netmask must be in range
> 0..31")
> +    ip_address(token[0])
> +    return string
> +
> +
>  def port(string):
> -    """Convert a string into a Port (integer)"""
> +    """Convert a string into a TCP/UDP Port (integer)"""
>     try:
>         port_number = int(string)
>         if port_number < 1 or port_number > 65535:
> @@ -41,75 +64,136 @@ def port(string):
>     return port_number
>
>
> -def ip_optional_port(string, default_port):
> +def ip_optional_port(string, default_port, ip_callback):
>     """Convert a string into IP and Port pair. If port was absent then use
> -    default_port as the port"""
> +    default_port as the port. The third argument is a callback that
> verifies
> +    whether IP address is given in correct format."""
>     value = string.split(':')
>     if len(value) == 1:
> -        return (ip(value[0]), default_port)
> +        return (ip_callback(value[0]), default_port)
>     elif len(value) == 2:
> -        return (ip(value[0]), port(value[1]))
> +        return (ip_callback(value[0]), port(value[1]))
>     else:
>         raise argparse.ArgumentTypeError("IP address from the optional
> Port "
>                                          "must be colon-separated")
>
>
> +def vlan_tag(string):
> +    """
> +    This function verifies whether given string is a correct VLAN tag.
> +    """
> +    try:
> +        value = int(string)
> +    except ValueError:
> +        raise argparse.ArgumentTypeError("VLAN tag is not a valid
> integer")
> +    if value < 1 or value > 4094:
> +        raise argparse.ArgumentTypeError("Not a valid VLAN tag. "
> +                                         "VLAN tag should be in the "
> +                                         "range 1..4094.")
> +    return string
> +
>
>  def server_endpoint(string):
> -    """Converts a string in ControlIP[:ControlPort][,TestIP[:TestPort]]
> format
> +    """Converts a string OuterIP[:OuterPort],InnerIP[/Mask][:InnerPort]
>     into a 4-tuple, where:
> -    1. First element is ControlIP
> -    2. Second element is ControlPort (if omitted will use default value
> 15531)
> -    3  Third element is TestIP (if omitted will be the same as ControlIP)
> -    4. Fourth element is TestPort (if omitted will use default value
> 15532)"""
> +    1. First element is OuterIP
> +    2. Second element is OuterPort (if omitted will use default value
> 15531)
> +    3  Third element is InnerIP with optional mask
> +    4. Fourth element is InnerPort (if omitted will use default value
> 15532)
> +    """
>     value = string.split(',')
> -    if len(value) == 1: #  TestIP and TestPort are not present
> -        ret = ip_optional_port(value[0], 15531)
> -        return (ret[0], ret[1], ret[0], 15532)
> -    elif len(value) == 2:
> -        ret1 = ip_optional_port(value[0], 15531)
> -        ret2 = ip_optional_port(value[1], 15532)
> +    if len(value) == 2:
> +        ret1 = ip_optional_port(value[0], CONTROL_PORT, ip_address)
> +        ret2 = ip_optional_port(value[1], DATA_PORT, ip_optional_mask)
>         return (ret1[0], ret1[1], ret2[0], ret2[1])
>     else:
> -        raise argparse.ArgumentTypeError("ControlIP:ControlPort and
> TestIP:"
> -                                         "TestPort must be comma "
> -                                         "separated")
> +        raise argparse.ArgumentTypeError("OuterIP:OuterPort and
> InnerIP/Mask:"
> +                                         "InnerPort must be comma
> separated")
> +
> +
> +class UniqueServerAction(argparse.Action):
> +    """
> +    This custom action class will prevent user from entering multiple
> ovs-test
> +    servers with the same OuterIP. If there is an server with 127.0.0.1
> outer
> +    IP address then it will be inserted in the front of the list.
> +    """
> +    def __call__(self, parser, namespace, values, option_string=None):
> +        outer_ips = set()
> +        endpoints = []
> +        for server in values:
> +            try:
> +                endpoint = server_endpoint(server)
> +            except argparse.ArgumentTypeError:
> +                raise argparse.ArgumentError(self, str(sys.exc_info()[1]))
> +            if endpoint[0] in outer_ips:
> +                raise argparse.ArgumentError(self, "Duplicate OuterIPs
> found")
> +            else:
> +                outer_ips.add(endpoint[0])
> +                if endpoint[0] == "127.0.0.1":
> +                    endpoints.insert(0, endpoint)
> +                else:
> +                    endpoints.append(endpoint)
> +        setattr(namespace, self.dest, endpoints)
>
>
>  def bandwidth(string):
>     """Convert a string (given in bits/second with optional magnitude for
>     units) into a long (bytes/second)"""
> -    if re.match("^[1-9][0-9]*[MK]?$", string) == None:
> +    if re.match("^[1-9][0-9]*[MK]?$", string) is None:
>         raise argparse.ArgumentTypeError("Not a valid target bandwidth")
>     bwidth = string.replace("M", "000000")
>     bwidth = bwidth.replace("K", "000")
> -    return long(bwidth) / 8 #  Convert from bits to bytes
> +    return long(bwidth) / 8  # Convert from bits to bytes
> +
> +
> +def tunnel_types(string):
> +    """
> +    This function converts a string into a list that contains all tunnel
> types
> +    that user intended to test.
> +    """
> +    return string.split(',')
>
>
>  def ovs_initialize_args():
> -    """Initialize args for ovstest utility"""
> -    parser = argparse.ArgumentParser(description = 'Test ovs
> connectivity')
> -    parser.add_argument('-v', '--version', action = 'version',
> -                version = 'ovs-test (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 '
> +    """
> +    Initialize argument parsing for ovs-test utility.
> +    """
> +    parser = argparse.ArgumentParser(description='Test connectivity '
> +                                                'between two Open
> vSwitches.')
> +
> +    parser.add_argument('-v', '--version', action='version',
> +                version='ovs-test (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.')
> -    group = parser.add_mutually_exclusive_group(required = True)
> -    group.add_argument("-s", "--server", action = "store", dest = "port",
> -                type = port,
> -                help = 'run in server mode and wait client to connect to
> this '
> -                'port')
> -    group.add_argument('-c', "--client", action = "store", nargs = 2,
> -                dest = "servers", type = server_endpoint,
> -                metavar = ("SERVER1", "SERVER2"),
> -                help = 'run in client mode and do tests between these '
> -                'two servers. Each server must be specified in following '
> -                'format - ControlIP[:ControlPort][,TestIP[:TestPort]]. If
> '
> -                'TestIP is omitted then ovs-test server will also use the
> '
> -                'ControlIP for testing purposes. ControlPort is TCP port '
> -                'where server will listen for incoming XML/RPC control '
> -                'connections to schedule tests (by default 15531).
> TestPort '
> -                'is port which will be used by server to send test
> traffic '
> -                '(by default 15532)')
> +    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-modes", action='store',
> +                dest="tunnelModes", default=(), type=tunnel_types,
> +                help='Do L3 tests with the given tunnel modes.')
> +    parser.add_argument("-l", "--vlan-tag", action='store',
> +                dest="vlanTag", default=None, type=vlan_tag,
> +                help='Do VLAN tests and use the given VLAN tag.')
> +    parser.add_argument("-d", "--direct", action='store_true',
> +                dest="direct", default=None,
> +                help='Do direct tests between both ovs-test servers.')
> +
> +    group = parser.add_mutually_exclusive_group(required=True)
> +    group.add_argument("-s", "--server", action="store", dest="port",
> +                type=port,
> +                help='Run in server mode and wait for the client to '
> +                'connect to this port.')
> +    group.add_argument('-c', "--client", nargs=2,
> +                dest="servers", action=UniqueServerAction,
> +                metavar=("SERVER1", "SERVER2"),
> +                help='Run in client mode and do tests between these '
> +                'two ovs-test servers. Each server must be specified in '
> +                'following format - OuterIP:OuterPort,InnerIP[/mask] '
> +                ':InnerPort. It is possible to start local instance of '
> +                'ovs-test server in the client mode by using 127.0.0.1 as
> '
> +                'OuterIP.')
>     return parser.parse_args()
> diff --git a/python/ovstest/rpcserver.py b/python/ovstest/rpcserver.py
> index 41d2569..0f680e1 100644
> --- a/python/ovstest/rpcserver.py
> +++ b/python/ovstest/rpcserver.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2011 Nicira Networks
> +# Copyright (c) 2011, 2012 Nicira Networks
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -16,26 +16,36 @@
>  rpcserver is an XML RPC server that allows RPC client to initiate tests
>  """
>
> +import exceptions
> +import sys
> +import xmlrpclib
> +
>  from twisted.internet import reactor
> -from twisted.web import xmlrpc, server
>  from twisted.internet.error import CannotListenError
> -import udp
> +from twisted.web import xmlrpc
> +from twisted.web import server
> +
>  import tcp
> -import args
> +import udp
>  import util
> +import vswitch
>
>
>  class TestArena(xmlrpc.XMLRPC):
>     """
> -    This class contains all the functions that ovstest will call
> +    This class contains all the functions that ovs-test client will call
>     remotely. The caller is responsible to use designated handleIds
>     for designated methods (e.g. do not mix UDP and TCP handles).
>     """
>
>     def __init__(self):
> -        xmlrpc.XMLRPC.__init__(self)
> +        xmlrpc.XMLRPC.__init__(self, allowNone=True)
>         self.handle_id = 1
>         self.handle_map = {}
> +        self.bridges = set()
> +        self.pbridges = set()
> +        self.ports = set()
> +        self.request = None
>
>     def __acquire_handle(self, value):
>         """
> @@ -58,6 +68,46 @@ class TestArena(xmlrpc.XMLRPC):
>         """
>         del self.handle_map[handle]
>
> +    def cleanup(self):
> +        """
> +        Delete all remaining bridges and ports if ovs-test client did not
> had
> +        a chance to remove them. It is necessary to call this function if
> +        ovs-test server is abruptly terminated when doing the tests.
> +        """
> +        for port in self.ports:
> +            # Remove ports that were added to existing bridges
> +            vswitch.ovs_vsctl_del_port_from_bridge(port)
> +
> +        for bridge in self.bridges:
> +            # Remove bridges that were added for L3 tests
> +            vswitch.ovs_vsctl_del_bridge(bridge)
> +
> +        for pbridge in self.pbridges:
> +            # Remove bridges that were added for VLAN tests
> +            vswitch.ovs_vsctl_del_pbridge(pbridge[0], pbridge[1])
> +
> +    def render(self, request):
> +        """
> +        This method overrides the original XMLRPC.render method so that it
> +        would be possible to get the XML RPC client IP address from the
> +        request object.
> +        """
> +        self.request = request
> +        return xmlrpc.XMLRPC.render(self, request)
> +
> +    def xmlrpc_get_my_address(self):
> +        """
> +        Returns the RPC client's IP address.
> +        """
> +        return self.request.getClientIP()
> +
> +    def xmlrpc_get_my_address_from(self, his_ip, his_port):
> +        """
> +        Returns the ovs-test server IP address that the other ovs-test
> server
> +        with the given ip will see.
> +        """
> +        server1 = xmlrpclib.Server("http://%s:%u/" % (his_ip, his_port))
> +        return server1.get_my_address()
>
>     def xmlrpc_create_udp_listener(self, port):
>         """
> @@ -171,6 +221,103 @@ class TestArena(xmlrpc.XMLRPC):
>             return -1
>         return 0
>
> +    def xmlrpc_create_test_bridge(self, bridge, iface):
> +        """
> +        This function creates a physical bridge from iface. It moves the
> +        IP configuration from the physical interface to the bridge.
> +        """
> +        ret = vswitch.ovs_vsctl_add_bridge(bridge)
> +        if ret == 0:
> +            self.pbridges.add((bridge, iface))
> +            util.interface_up(bridge)
> +            (ip_addr, mask) = util.interface_get_ip(iface)
> +            util.interface_assign_ip(bridge, ip_addr, mask)
> +            util.move_routes(iface, bridge)
> +            util.interface_assign_ip(iface, "0.0.0.0", "255.255.255.255")
> +            ret = vswitch.ovs_vsctl_add_port_to_bridge(bridge, iface)
> +            if ret == 0:
> +                self.ports.add(iface)
> +            else:
> +                util.interface_assign_ip(iface, ip_addr, mask)
> +                util.move_routes(bridge, iface)
> +                vswitch.ovs_vsctl_del_bridge(bridge)
> +
> +        return ret
> +
> +    def xmlrpc_del_test_bridge(self, bridge, iface):
> +        """
> +        This function deletes the test bridge and moves its IP
> configuration
> +        back to the physical interface.
> +        """
> +        ret = vswitch.ovs_vsctl_del_pbridge(bridge, iface)
> +        self.pbridges.discard((bridge, iface))
> +        return ret
> +
> +    def xmlrpc_get_iface_from_bridge(self, brname):
> +        """
> +        Tries to figure out physical interface from bridge.
> +        """
> +        return vswitch.ovs_get_physical_interface(brname)
> +
> +    def xmlrpc_create_bridge(self, brname):
> +        """
> +        Creates an OVS bridge.
> +        """
> +        ret = vswitch.ovs_vsctl_add_bridge(brname)
> +        if ret == 0:
> +            self.bridges.add(brname)
> +        return ret
> +
> +    def xmlrpc_del_bridge(self, brname):
> +        """
> +        Deletes an OVS bridge.
> +        """
> +        ret = vswitch.ovs_vsctl_del_bridge(brname)
> +        if ret == 0:
> +            self.bridges.discard(brname)
> +        return ret
> +
> +    def xmlrpc_is_ovs_bridge(self, bridge):
> +        """
> +        This function verifies whether given interface is an ovs bridge.
> +        """
> +        return vswitch.ovs_vsctl_is_ovs_bridge(bridge)
> +
> +    def xmlrpc_add_port_to_bridge(self, bridge, port):
> +        """
> +        Adds a port to the OVS bridge.
> +        """
> +        ret = vswitch.ovs_vsctl_add_port_to_bridge(bridge, port)
> +        if ret == 0:
> +            self.ports.add(port)
> +        return ret
> +
> +    def xmlrpc_del_port_from_bridge(self, port):
> +        """
> +        Removes a port from OVS bridge.
> +        """
> +        ret = vswitch.ovs_vsctl_del_port_from_bridge(port)
> +        if ret == 0:
> +            self.ports.discard(port)
> +        return ret
> +
> +    def xmlrpc_ovs_vsctl_set(self, table, record, column, key, value):
> +        """
> +        This function allows to alter OVS database.
> +        """
> +        return vswitch.ovs_vsctl_set(table, record, column, key, value)
> +
> +    def xmlrpc_interface_up(self, iface):
> +        """
> +        This function brings up given interface.
> +        """
> +        return util.interface_up(iface)
> +
> +    def xmlrpc_interface_assign_ip(self, iface, ip_address, mask):
> +        """
> +        This function allows to assing ip address to the given interface.
> +        """
> +        return util.interface_assign_ip(iface, ip_address, mask)
>
>     def xmlrpc_get_interface(self, address):
>         """
> @@ -198,6 +345,17 @@ class TestArena(xmlrpc.XMLRPC):
>
>
>  def start_rpc_server(port):
> -    RPC_SERVER = TestArena()
> -    reactor.listenTCP(port, server.Site(RPC_SERVER))
> -    reactor.run()
> +    """
> +    This function creates a RPC server and adds it to the Twisted Reactor.
> +    """
> +    rpc_server = TestArena()
> +    reactor.listenTCP(port, server.Site(rpc_server))
> +    try:
> +        print "Starting RPC server\n"
> +        sys.stdout.flush()
> +         # If this server was started from ovs-test client then we must
> flush
> +         # STDOUT so that client would know that server is ready to accept
> +         # XML RPC connections.
> +        reactor.run()
> +    finally:
> +        rpc_server.cleanup()
> diff --git a/python/ovstest/tcp.py b/python/ovstest/tcp.py
> index 33dc719..8413343 100644
> --- a/python/ovstest/tcp.py
> +++ b/python/ovstest/tcp.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2011 Nicira Networks
> +# Copyright (c) 2011, 2012 Nicira Networks
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -29,14 +29,10 @@ class TcpListenerConnection(Protocol):
>     def __init__(self):
>         self.stats = 0
>
> -    def connectionMade(self):
> -        print "Started TCP Listener connection"
> -
>     def dataReceived(self, data):
>         self.stats += len(data)
>
>     def connectionLost(self, reason):
> -        print "Stopped TCP Listener connection"
>         self.factory.stats += self.stats
>
>
> @@ -50,16 +46,10 @@ class TcpListenerFactory(Factory):
>     def __init__(self):
>         self.stats = 0
>
> -    def startFactory(self):
> -        print "Starting TCP listener factory"
> -
> -    def stopFactory(self):
> -        print "Stopping TCP listener factory"
> -
>     def getResults(self):
>         """ returns the number of bytes received as string"""
> -        #XML RPC does not support 64bit int (
> http://bugs.python.org/issue2985)
> -        #so we have to convert the amount of bytes into a string
> +        # XML RPC does not support 64bit int (
> http://bugs.python.org/issue2985)
> +        # so we have to convert the amount of bytes into a string
>         return str(self.stats)
>
>
> @@ -104,18 +94,13 @@ class TcpSenderConnection(Protocol):
>     """
>
>     def connectionMade(self):
> -        print "Started TCP sender connection"
>         producer = Producer(self, self.factory.duration)
>         self.transport.registerProducer(producer, True)
>         producer.resumeProducing()
>
>     def dataReceived(self, data):
> -        print "Sender received data!", data
>         self.transport.loseConnection()
>
> -    def connectionLost(self, reason):
> -        print "Stopped TCP sender connection"
> -
>
>  class TcpSenderFactory(ClientFactory):
>     """
> @@ -128,12 +113,6 @@ class TcpSenderFactory(ClientFactory):
>         self.duration = duration
>         self.stats = 0
>
> -    def startFactory(self):
> -        print "Starting TCP sender factory"
> -
> -    def stopFactory(self):
> -        print "Stopping TCP sender factory"
> -
>     def getResults(self):
>         """Returns amount of bytes sent to the Listener (as a string)"""
>         return str(self.stats)
> diff --git a/python/ovstest/udp.py b/python/ovstest/udp.py
> index e09569d..f34ae86 100644
> --- a/python/ovstest/udp.py
> +++ b/python/ovstest/udp.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2011 Nicira Networks
> +# Copyright (c) 2011, 2012 Nicira Networks
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -16,9 +16,12 @@
>  ovsudp contains listener and sender classes for UDP protocol
>  """
>
> +import array
> +import struct
> +import time
> +
>  from twisted.internet.protocol import DatagramProtocol
>  from twisted.internet.task import LoopingCall
> -import array, struct, time
>
>
>  class UdpListener(DatagramProtocol):
> @@ -28,18 +31,12 @@ class UdpListener(DatagramProtocol):
>     def __init__(self):
>         self.stats = []
>
> -    def startProtocol(self):
> -        print "Starting UDP listener"
> -
> -    def stopProtocol(self):
> -        print "Stopping UDP listener"
> -
>     def datagramReceived(self, data, (_1, _2)):
>         """This function is called each time datagram is received"""
>         try:
>             self.stats.append(struct.unpack_from("Q", data, 0))
>         except struct.error:
> -            pass #ignore packets that are less than 8 bytes of size
> +            pass  # ignore packets that are less than 8 bytes of size
>
>     def getResults(self):
>         """Returns number of packets that were actually received"""
> @@ -51,7 +48,7 @@ class UdpSender(DatagramProtocol):
>     Class that will send UDP packets to UDP Listener
>     """
>     def __init__(self, host, count, size, duration):
> -        #LoopingCall does not know whether UDP socket is actually writable
> +        # LoopingCall does not know whether UDP socket is actually
> writable
>         self.looper = None
>         self.host = host
>         self.count = count
> @@ -61,13 +58,11 @@ class UdpSender(DatagramProtocol):
>         self.data = array.array('c', 'X' * size)
>
>     def startProtocol(self):
> -        print "Starting UDP sender"
>         self.looper = LoopingCall(self.sendData)
>         period = self.duration / float(self.count)
>         self.looper.start(period , now = False)
>
>     def stopProtocol(self):
> -        print "Stopping UDP sender"
>         if (self.looper is not None):
>             self.looper.stop()
>             self.looper = None
> diff --git a/python/ovstest/util.py b/python/ovstest/util.py
> index 3321e69..7bc7795 100644
> --- a/python/ovstest/util.py
> +++ b/python/ovstest/util.py
> @@ -1,4 +1,4 @@
> -# Copyright (c) 2011 Nicira Networks
> +# Copyright (c) 2011, 2012 Nicira Networks
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -15,13 +15,28 @@
>  """
>  util module contains some helper function
>  """
> -import socket, struct, fcntl, array, os, subprocess, exceptions
> +import array
> +import exceptions
> +import fcntl
> +import os
> +import socket
> +import struct
> +import subprocess
> +import re
>
> -def str_ip(ip):
> -    (x1, x2, x3, x4) = struct.unpack("BBBB", ip)
> +
> +def str_ip(ip_address):
> +    """
> +    Converts an IP address from binary format to a string.
> +    """
> +    (x1, x2, x3, x4) = struct.unpack("BBBB", ip_address)
>     return ("%u.%u.%u.%u") % (x1, x2, x3, x4)
>
> +
>  def get_interface_mtu(iface):
> +    """
> +    Returns MTU of the given interface.
> +    """
>     s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>     indata = iface + ('\0' * (32 - len(iface)))
>     try:
> @@ -32,6 +47,7 @@ def get_interface_mtu(iface):
>
>     return mtu
>
> +
>  def get_interface(address):
>     """
>     Finds first interface that has given address
> @@ -50,25 +66,84 @@ def get_interface(address):
>         name = namestr[i:i + 16].split('\0', 1)[0]
>         if address == str_ip(namestr[i + 20:i + 24]):
>             return name
> -    return "" #  did not find interface we were looking for
> +    return None  # did not find interface we were looking for
> +
>
>  def uname():
>     os_info = os.uname()
> -    return os_info[2] #return only the kernel version number
> +    return os_info[2]  # return only the kernel version number
>
> -def get_driver(iface):
> +
> +def start_process(args):
>     try:
> -        p = subprocess.Popen(
> -            ["ethtool", "-i", iface],
> +        p = subprocess.Popen(args,
>             stdin = subprocess.PIPE,
>             stdout = subprocess.PIPE,
>             stderr = subprocess.PIPE)
>         out, err = p.communicate()
> -        if p.returncode == 0:
> -            lines = out.split("\n")
> -            driver = "%s(%s)" % (lines[0], lines[1]) #driver name +
> version
> -        else:
> -            driver = "no support for ethtool"
> +        return (p.returncode, out, err)
>     except exceptions.OSError:
> -        driver = ""
> +        return (-1, None, None)
> +
> +
> +def get_driver(iface):
> +    ret, out, _err = start_process(["ethtool", "-i", iface])
> +    if ret == 0:
> +        lines = out.splitlines()
> +        driver = "%s(%s)" % (lines[0], lines[1])  # driver name + version
> +    else:
> +        driver = None
>     return driver
> +
> +
> +def interface_up(iface):
> +    """
> +    This function brings given iface up.
> +    """
> +    ret, _out, _err = start_process(["ifconfig", iface, "up"])
> +    return ret
> +
> +
> +def interface_assign_ip(iface, ip_addr, mask):
> +    """
> +    This function allows to assign IP address to an interface. If mask is
> an
> +    empty string then ifconfig will decide what kind of mask to use. The
> +    caller can also specify the mask by using CIDR notation in ip
> argument by
> +    leaving the mask argument as an empty string. In case of success this
> +    function returns 0.
> +    """
> +    args = ["ifconfig", iface, ip_addr]
> +    if mask is not None:
> +        args.append("netmask")
> +        args.append(mask)
> +    ret, _out, _err = start_process(args)
> +    return ret
> +
> +
> +def interface_get_ip(iface):
> +    """
> +    This function returns tuple - ip and mask that was assigned to the
> +    interface.
> +    """
> +    args = ["ifconfig", iface]
> +    ret, out, _err = start_process(args)
> +
> +    if ret == 0:
> +        ip = re.search(r'inet addr:(\S+)', out)
> +        mask = re.search(r'Mask:(\S+)', out)
> +        if ip is not None and mask is not None:
> +            return (ip.group(1), mask.group(1))
> +    else:
> +        return ret
> +
> +
> +def move_routes(iface1, iface2):
> +    """
> +    This function moves routes from iface1 to iface2.
> +    """
> +    args = ["ip", "route", "show", "dev", iface1]
> +    ret, out, _err = start_process(args)
> +    if ret == 0:
> +        for route in out.splitlines():
> +            args = ["ip", "route", "replace", "dev", iface2] +
> route.split()
> +            start_process(args)
> diff --git a/python/ovstest/vswitch.py b/python/ovstest/vswitch.py
> new file mode 100644
> index 0000000..1d677be
> --- /dev/null
> +++ b/python/ovstest/vswitch.py
> @@ -0,0 +1,108 @@
> +# Copyright (c) 2012 Nicira Networks
> +#
> +# 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.
> +
> +"""
> +vswitch module allows its callers to interact with OVS DB.
> +"""
> +import exceptions
> +import subprocess
> +
> +import util
> +
> +
> +def ovs_vsctl_add_bridge(bridge):
> +    """
> +    This function creates an OVS bridge.
> +    """
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "add-br", bridge])
> +    return ret
> +
> +
> +def ovs_vsctl_del_bridge(bridge):
> +    """
> +    This function deletes the OVS bridge.
> +    """
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "del-br", bridge])
> +    return ret
> +
> +def ovs_vsctl_del_pbridge(bridge, iface):
> +    """
> +    This function deletes the OVS bridge and assigns the bridge IP address
> +    back to the iface.
> +    """
> +    (ip_addr, mask) = util.interface_get_ip(bridge)
> +    util.interface_assign_ip(iface, ip_addr, mask)
> +    util.move_routes(bridge, iface)
> +    return ovs_vsctl_del_bridge(bridge)
> +
> +
> +def ovs_vsctl_is_ovs_bridge(bridge):
> +    """
> +    This function verifies whether given port is an OVS bridge. If it is
> an
> +    OVS bridge then it will return True.
> +    """
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "br-exists",
> bridge])
> +    return ret == 0
> +
> +
> +def ovs_vsctl_add_port_to_bridge(bridge, iface):
> +    """
> +    This function adds given interface to the bridge.
> +    """
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "add-port", bridge,
> +                                          iface])
> +    return ret
> +
> +
> +def ovs_vsctl_del_port_from_bridge(port):
> +    """
> +    This function removes given port from a OVS bridge.
> +    """
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "del-port", port])
> +    return ret
> +
> +
> +def ovs_vsctl_set(table, record, column, key, value):
> +    """
> +    This function allows to alter the OVS database. If column is a map,
> then
> +    caller should also set the key, otherwise the key should be left as an
> +    empty string.
> +    """
> +    if key is None:
> +        index = column
> +    else:
> +        index = "%s:%s" % (column, key)
> +    index_value = "%s=%s" % (index, value)
> +    ret, _out, _err = util.start_process(["ovs-vsctl", "set", table,
> record,
> +                                          index_value])
> +    return ret
> +
> +
> +def ovs_get_physical_interface(bridge):
> +    """
> +    This function tries to figure out which is the physical interface that
> +    belongs to the bridge. If there are multiple physical interfaces
> assigned
> +    to this bridge then it will return the first match.
> +    """
> +    ret, out, _err = util.start_process(["ovs-vsctl", "list-ifaces",
> bridge])
> +
> +    if ret == 0:
> +        ifaces = out.splitlines()
> +        for iface in ifaces:
> +            ret, out, _err = util.start_process(["ovs-vsctl", "get",
> +                                                 "Interface", iface,
> "type"])
> +            if ret == 0:
> +                if ('""' in out) or ('system' in out):
> +                    return iface  # this should be the physical interface
> +    return None
> diff --git a/utilities/ovs-test.8.in b/utilities/ovs-test.8.in
> index 87c8944..9704f72 100644
> --- a/utilities/ovs-test.8.in
> +++ b/utilities/ovs-test.8.in
> @@ -3,41 +3,47 @@
>  .  ns
>  .  IP "\\$1"
>  ..
> -.TH ovs\-test 1 "October 2011" "Open vSwitch" "Open vSwitch Manual"
> +.TH ovs\-test 1 "April 2012" "Open vSwitch" "Open vSwitch Manual"
>  .
>  .SH NAME
> -\fBovs\-test\fR \- check Linux drivers for performance and vlan problems
> +\fBovs\-test\fR \- check Linux drivers for performance, vlan and L3
> tunneling
> +problems
>  .
>  .SH SYNOPSIS
>  \fBovs\-test\fR \fB\-s\fR \fIport\fR
>  .PP
> -\fBovs\-test\fR \fB\-c\fR \fIserver1\fR
> -\fIserver2\fR [\fB\-b\fR \fIbandwidth\fR]
> +\fBovs\-test\fR \fB\-c\fR \fIserver1\fR \fIserver2\fR
> +[\fB\-b\fR \fItargetbandwidth\fR] [\fB\-i\fR \fItestinterval\fR]
> +[\fB\-d\fR]
> +[\fB\-l\fR \fIvlantag\fR]
> +[\fB\-t\fR \fItunnelmodes\fR]
>  .so lib/common-syn.man
>  .
>  .SH DESCRIPTION
>  The \fBovs\-test\fR program may be used to check for problems sending
> -802.1Q traffic that Open vSwitch may uncover. These problems can
> -occur when Open vSwitch is used to send 802.1Q traffic through physical
> -interfaces running certain drivers of certain Linux kernel versions. To
> run a
> -test, configure Open vSwitch to tag traffic originating from
> \fIserver1\fR and
> -forward it to the \fIserver2\fR. On both servers run \fBovs\-test\fR
> -in server mode. Then, on any other host, run the \fBovs\-test\fR in client
> -mode. The client will connect to both \fBovs\-test\fR servers and schedule
> -tests between them. \fBovs\-test\fR will perform UDP and TCP tests.
> +802.1Q or GRE traffic that Open vSwitch may uncover. These problems,
> +for example, can occur when Open vSwitch is used to send 802.1Q traffic
> +through physical interfaces running certain drivers of certain Linux
> kernel
> +versions. To run a test, configure IP addresses on \fIserver1\fR and
> +\fIserver2\fR for interfaces you intended to test. These interfaces could
> +also be already configured OVS bridges that have a physical interface
> attached
> +to them. Then, on one of the nodes, run \fBovs\-test\fR in server mode
> and on
> +the other node run it in client mode. The client will connect to
> +\fBovs\-test\fR server and schedule tests between both of them. The
> +\fBovs\-test\fR client will perform UDP and TCP tests.
>  .PP
> -UDP tests can report packet loss and achieved bandwidth, because UDP flow
> -control is done inside \fBovs\-test\fR. It is also possible to specify
> target
> -bandwidth for UDP. By default it is 1Mbit/s.
> +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. TCP tests are essential to
> detect
> -potential TSO related VLAN issues.
> +potential TSO related issues.
>  .PP
> -To determine whether Open vSwitch is encountering any 802.1Q related
> problems,
> +To determine whether Open vSwitch is encountering any problems,
>  the user must compare packet loss and achieved bandwidth in a setup where
> -traffic is being tagged against one where it is not. If in the tagged
> setup
> -both servers are unable to communicate or the achieved bandwidth is lower,
> +traffic is being directly sent and in one where it is not. If in the
> +802.1Q or L3 tunneled tests both \fBovs\-test\fR processes are unable to
> +communicate or the achieved bandwidth is much lower compared to direct
> setup,
>  then, most likely, Open vSwitch has encountered a pre-existing kernel or
>  driver bug.
>  .PP
> @@ -46,71 +52,87 @@ Some examples of the types of problems that may be
> encountered are:
>  .
>  .SS "Client Mode"
>  An \fBovs\-test\fR client will connect to two \fBovs\-test\fR servers and
> -will ask them to exchange traffic.
> +will ask them to exchange test traffic. It is also possible to spawn an
> +\fBovs\-test\fR server automatically from the client.
>  .
>  .SS "Server Mode"
>  To conduct tests, two \fBovs\-test\fR servers must be running on two
> different
> -hosts where client can connect. The actual test traffic is exchanged only
> -between both \fBovs\-test\fR server test IP addresses. It is recommended
> that
> -both servers have their test IP addresses in the same subnet, otherwise
> one
> -will need to change routing so that the test traffic actually goes
> through the
> -interface that he originally intended to test.
> +hosts where the client can connect. The actual test traffic is exchanged
> only
> +between both \fBovs\-test\fR servers. It is recommended that both servers
> have
> +their IP addresses in the same subnet, otherwise one would have to make
> sure
> +that routing is set up correctly.
>  .
>  .SH OPTIONS
>  .
>  .IP "\fB\-s \fIport\fR"
>  .IQ "\fB\-\-server\fR \fIport\fR"
> -Run in server mode and wait for a client to establish XML RPC Control
> -Connection on TCP \fIport\fR. It is recommended to have ethtool installed
> on
> -the server so that it could retrieve information about NIC driver.
> +Run in server mode and wait for the client to establish XML RPC Control
> +Connection on this TCP \fIport\fR. It is recommended to have
> \fBethtool\fR(8)
> +installed on the server so that it could retrieve information about the
> NIC
> +driver.
>  .
>  .IP "\fB\-c \fIserver1\fR \fIserver2\fR"
>  .IQ "\fB\-\-client \fIserver1\fR \fIserver2\fR"
>  Run in client mode and schedule tests between \fIserver1\fR and
> \fIserver2\fR,
> -where each \fIserver\fR must be given in following format -
> -ControlIP[:ControlPort][,TestIP[:TestPort]]. If TestIP is omitted then
> -ovs-test server will use the ControlIP for testing purposes. ControlPort
> is
> -TCP port where server will listen for incoming XML/RPC control
> -connections to schedule tests (by default it is 15531). TestPort
> -is port which will be used by server to listen for test traffic
> -(by default it is 15532).
> -.
> -.IP "\fB\-b \fIbandwidth\fR"
> -.IQ "\fB\-\-bandwidth\fR \fIbandwidth\fR"
> -Target bandwidth for UDP tests. The \fIbandwidth\fR must be given in bits
> per
> -second. It is possible to use postfix M or K to alter the target bandwidth
> -magnitude.
> +where each \fIserver\fR must be given in the following format -
> +\fIOuterIP[:OuterPort],InnerIP[/Mask][:InnerPort]\fR. The \fIOuterIP\fR
> must
> +be already assigned to the physical interface which is going to be tested.
> +This is the IP address where client will try to establish XML RPC
> connection.
> +If \fIOuterIP\fR is 127.0.0.1 then client will automatically spawn a local
> +instance of \fBovs\-test\fR server. \fIOuterPort\fR is TCP port where
> server
> +is listening for incoming XML/RPC control connections to schedule tests
> (by
> +default it is 15531). The \fBovs\-test\fR will automatically assign
> +\fIInnerIP[/Mask]\fR to the interfaces that will be created on the fly for
> +testing purposes. It is important that \fIInnerIP[/Mask]\fR does not
> interfere
> +with already existing IP addresses on both \fBovs\-test\fR servers and
> client.
> +\fIInnerPort\fR is port which will be used by server to listen for test
> +traffic that will be encapsulated (by default it is 15532).
> +.
> +.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.
>  .
>  .so lib/common.man
> -.SH EXAMPLES
> -.PP
> -Set up a bridge which forwards traffic originating from \fB1.2.3.4\fR out
> -\fBeth1\fR with VLAN tag 10.
> -.IP
> -.B ovs\-vsctl \-\- add\-br vlan\-br \(rs
> -.IP
> -.B \-\- add\-port vlan\-br eth1 \(rs
> -.IP
> -.B \-\- add\-port vlan\-br vlan\-br\-tag tag=10 \(rs
> -.IP
> -.B \-\- set Interface vlan\-br\-tag type=internal
> -.IP
> -.B ifconfig vlan\-br\-tag up 1.2.3.4
>  .
> +.SH "Test Modes"
> +The following test modes are supported by \fBovs\-test\fR. It is possible
> +to combine multiple of them in a single \fBovs\-test\fR invocation.
> +.
> +.IP "\fB\-d \fR"
> +.IQ "\fB\-\-direct\fR"
> +Perform direct tests between both \fIOuterIP\fR addresses. These tests
> could
> +be used as a reference to compare 802.1Q or L3 tunneling test results.
> +.
> +.IP "\fB\-l \fIvlantag\fR"
> +.IQ "\fB\-\-vlan\-tag\fR \fIvlantag\fR"
> +Perform 802.1Q tests between both servers. These tests will create a
> temporary
> +OVS bridge, if necessary, and attach a VLAN tagged port to it for testing
> +purposes.
> +.
> +.IP "\fB\-t \fItunnelmodes\fR"
> +.IQ "\fB\-\-tunnel\-modes\fR \fItunnelmodes\fR"
> +Perform L3 tunneling tests. The given argument is a comma separated string
> +that specifies all the L3 tunnel modes that should be tested (e.g. gre).
> The L3
> +tunnels are terminated on interface that has the \fIOuterIP\fR address
> +assigned.
> +.
> +.SH EXAMPLES
>  .PP
> -On two different hosts start \fBovs\-test\fR in server mode and tell them
> to
> -listen on port 15531 for incoming client control connections:
> -.IP
> -.B 1.2.3.4: ovs\-test \-s 15531
> +On host 1.2.3.4 start \fBovs\-test\fR in server mode:
>  .IP
> -.B 1.2.3.5: ovs\-test \-s 15531
> +.B ovs\-test \-s 15531
>  .
>  .PP
> -On any other host start \fBovs\-test\fR in client mode and ask it to
> connect
> -to those two servers - one at 1.2.3.4 and another at 1.2.3.5 (by default
> -client will use TCP port 15531 to establish control channel).
> +On host 1.2.3.5 start \fBovs\-test\fR in client mode and do direct, VLAN
> and
> +GRE tests between both nodes:
>  .IP
> -.B ovs\-test -c 1.2.3.4 1.2.3.5
> +.B ovs\-test \-c 127.0.0.1,1.1.1.1/30 1.2.3.4,1.1.1.2/30 -d -l 123 -t gre
>  .
>  .SH SEE ALSO
>  .
> @@ -119,4 +141,4 @@ client will use TCP port 15531 to establish control
> channel).
>  .BR ovs\-vsctl (8),
>  .BR ovs\-vlan\-test (8),
>  .BR ethtool (8),
> -.BR uname (1)
> +.BR uname (1)
> \ No newline at end of file
> diff --git a/utilities/ovs-test.in b/utilities/ovs-test.in
> index 6518dbc..9c34050 100644
> --- a/utilities/ovs-test.in
> +++ b/utilities/ovs-test.in
> @@ -16,17 +16,85 @@
>  ovs test utility that allows to do tests between remote hosts
>  """
>
> -import twisted
> -import xmlrpclib
> -import time
> -import socket
> +import fcntl
>  import math
> -from ovstest import args, rpcserver
> +import os
> +import select
> +import signal
> +import socket
> +import subprocess
> +import sys
> +import time
> +import xmlrpclib
> +
> +import argparse
> +import twisted
> +
> +import ovstest.args as args
> +import ovstest.rpcserver as rpcserver
> +
> +DEFAULT_TEST_BRIDGE = "ovstestbr0"
> +DEFAULT_TEST_PORT = "ovstestport0"
> +DEFAULT_TEST_TUN = "ovstesttun0"
> +
> +
> +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(["/usr/bin/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)
> +
> +    out = ""
> +    while p.poll() is None:
> +        fd = select.select([p.stdout.fileno()], [], [])[0]
> +        if fd:
> +            out += p.stdout.readline()
>

Apparently this doesn't matter, but out will never match if it doesn't
match the first time, since readline waits for (and retains) a newline.
 Should this be checking each line as it is printed instead of saving out?


> +            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
> +    """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:
> @@ -38,36 +106,45 @@ def bandwidth_to_string(bwidth):
>  def collect_information(node):
>     """Print information about hosts that will do testing"""
>     print "Node %s:%u " % (node[0], node[1])
> -    server1 = xmlrpclib.Server("http://%s:%u/" % (node[0], node[1]))
> -    interface_name = server1.get_interface(node[2])
> -    uname = server1.uname()
> +    server = rpc_client(node[0], node[1])
> +    interface_name = server.get_interface(node[0])
> +    phys_iface = None
> +    uname = server.uname()
>     mtu = 1500
>
> -    if interface_name == "":
> +    if not interface_name:
>         print ("Could not find interface that has %s IP address."
> -               "Make sure that you specified correct Test IP." %
> (node[2]))
> +               "Make sure that you specified correct Outer IP." %
> (node[0]))
>     else:
> -        mtu = server1.get_interface_mtu(interface_name)
> -        driver = server1.get_driver(interface_name)
> -        print "Will be using %s(%s) with MTU %u" % (interface_name,
> node[2],
> +        if server.is_ovs_bridge(interface_name):
> +            phys_iface = server.get_iface_from_bridge(interface_name)
> +        else:
> +            phys_iface = interface_name
> +
> +    if phys_iface:
> +        driver = server.get_driver(phys_iface)
> +        mtu = server.get_interface_mtu(phys_iface)
> +
> +        print "Will be using %s (%s) with MTU %u" % (phys_iface, node[0],
>                                                     mtu)
> -        if driver == "":
> -            print "Install ethtool on this host to get NIC driver
> information"
> +        if not driver:
> +            print "Unable to get driver information from ethtool."
>         else:
> -            print "On this host %s has %s." % (interface_name, driver)
> +            print "On this host %s has %s." % (phys_iface, driver)
>
> -    if uname == "":
> +    if not uname:
>         print "Unable to retrieve kernel information. Is this Linux?"
>     else:
>         print "Running kernel %s." % uname
>     print "\n"
> +
>     return mtu
>
>
> -def do_udp_tests(receiver, sender, tbwidth, duration, sender_mtu):
> +def do_udp_tests(receiver, sender, tbwidth, duration, port_sizes):
>     """Schedule UDP tests between receiver and sender"""
> -    server1 = xmlrpclib.Server("http://%s:%u/" % (receiver[0],
> receiver[1]))
> -    server2 = xmlrpclib.Server("http://%s:%u/" % (sender[0], sender[1]))
> +    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}'
>
> @@ -77,7 +154,7 @@ def do_udp_tests(receiver, sender, tbwidth, duration,
> sender_mtu):
>     print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv
> Datagrams",
>                             "Datagram Loss", "Bandwidth")
>
> -    for size in [8, sender_mtu - 100, sender_mtu - 28, sender_mtu]:
> +    for size in port_sizes:
>         listen_handle = -1
>         send_handle = -1
>         try:
> @@ -89,11 +166,12 @@ def do_udp_tests(receiver, sender, tbwidth, duration,
> sender_mtu):
>                         " %u. Try to restart the server.\n" % receiver[3])
>                 return
>             send_handle = server2.create_udp_sender(
> -                                            (receiver[2], receiver[3]),
> -                                            packetcnt, size, duration)
> +                                            (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
> +            # 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)
> @@ -115,8 +193,8 @@ def do_udp_tests(receiver, sender, tbwidth, duration,
> sender_mtu):
>
>  def do_tcp_tests(receiver, sender, duration):
>     """Schedule TCP tests between receiver and sender"""
> -    server1 = xmlrpclib.Server("http://%s:%u/" % (receiver[0],
> receiver[1]))
> -    server2 = xmlrpclib.Server("http://%s:%u/" % (sender[0], sender[1]))
> +    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],
> @@ -131,8 +209,8 @@ def do_tcp_tests(receiver, sender, duration):
>             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(receiver[2], receiver[3],
> -                                                    duration)
> +        send_handle = server2.create_tcp_sender(ip_from_cidr(receiver[2]),
> +                                                receiver[3], duration)
>
>         time.sleep(duration + 1)
>
> @@ -151,30 +229,178 @@ def do_tcp_tests(receiver, sender, duration):
>     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:
>         ovs_args = args.ovs_initialize_args()
>
> -        if ovs_args.port is not None: #  Start in server mode
> -            print "Starting RPC server"
> -            try:
> -                rpcserver.start_rpc_server(ovs_args.port)
> -            except twisted.internet.error.CannotListenError:
> -                print "Couldn't start XMLRPC server on port %u" %
> ovs_args.port
> +        if ovs_args.port is not None:  # Start in pure server mode
> +            rpcserver.start_rpc_server(ovs_args.port)
>
> -        elif ovs_args.servers is not None: #  Run in client mode
> +        elif ovs_args.servers is not None:  # Run in client mode
>             node1 = ovs_args.servers[0]
>             node2 = ovs_args.servers[1]
> -            bandwidth = ovs_args.targetBandwidth
>
> -            mtu_node1 = collect_information(node1)
> +            # Verify whether client will need to spawn a local instance of
> +            # 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])
> +                # We must determine the IP address that local ovs-test
> server
> +                # will use:
> +                me = 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])
> +
>             mtu_node2 = collect_information(node2)
> +            mtu_node1 = collect_information(node1)
> +
> +            bandwidth = ovs_args.targetBandwidth
> +            interval = ovs_args.testInterval
> +            ps = get_datagram_sizes(mtu_node1, mtu_node2)
> +
> +            direct = ovs_args.direct
> +            vlan_tag = ovs_args.vlanTag
> +            tunnel_modes = ovs_args.tunnelModes
> +
> +            if direct is not None:
> +                print "Performing direct 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)
> +
> +            for tmode in tunnel_modes:
> +                print "Performing", tmode, "tests"
> +                do_l3_tests(node2, node1, bandwidth, interval, ps, tmode)
>
> -            do_udp_tests(node1, node2, bandwidth, 5, mtu_node1)
> -            do_udp_tests(node2, node1, bandwidth, 5, mtu_node2)
> -            do_tcp_tests(node1, node2, 5)
> -            do_tcp_tests(node2, node1, 5)
>     except KeyboardInterrupt:
>         pass
> +    except xmlrpclib.Fault:
> +        print "Couldn't establish XMLRPC control channel"
>     except socket.error:
>         print "Couldn't establish XMLRPC control channel"
> +    except xmlrpclib.ProtocolError:
> +        print "XMLRPC control channel was abruptly terminated"
> +    except twisted.internet.error.CannotListenError:
> +        print "Couldn't start XMLRPC server on port %u" % ovs_args.port
> +    finally:
> +        if local_server is not None:
> +            local_server.terminate()
> --
> 1.7.9.1
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://openvswitch.org/pipermail/dev/attachments/20120417/ddafd1e4/attachment.htm>


More information about the dev mailing list