2
0
mirror of https://github.com/openvswitch/ovs synced 2025-10-27 15:18:06 +00:00

ovs-test: Enhancements to the ovs-test tool

-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@nicira.com>
This commit is contained in:
Ansis Atteka
2012-03-29 19:03:08 -07:00
parent b20a8f7c11
commit 8d25d9a254
10 changed files with 869 additions and 218 deletions

4
NEWS
View File

@@ -8,6 +8,10 @@ post-v1.6.0
Internetwork Control (0xc0). Internetwork Control (0xc0).
- Added the granular link health statistics, 'cfm_health', to an - Added the granular link health statistics, 'cfm_health', to an
interface. 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 v1.6.0 - xx xxx xxxx

View File

@@ -6,7 +6,8 @@ ovstest_pyfiles = \
python/ovstest/rpcserver.py \ python/ovstest/rpcserver.py \
python/ovstest/tcp.py \ python/ovstest/tcp.py \
python/ovstest/udp.py \ python/ovstest/udp.py \
python/ovstest/util.py python/ovstest/util.py \
python/ovstest/vswitch.py
ovs_pyfiles = \ ovs_pyfiles = \
python/ovs/__init__.py \ python/ovs/__init__.py \

View File

@@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 argparse
import socket
import re 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""" """Verifies if string is a valid IP address"""
try: try:
socket.inet_aton(string) socket.inet_aton(string)
@@ -30,8 +33,28 @@ def ip(string):
return 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): def port(string):
"""Convert a string into a Port (integer)""" """Convert a string into a TCP/UDP Port (integer)"""
try: try:
port_number = int(string) port_number = int(string)
if port_number < 1 or port_number > 65535: if port_number < 1 or port_number > 65535:
@@ -41,75 +64,136 @@ def port(string):
return port_number 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 """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(':') value = string.split(':')
if len(value) == 1: if len(value) == 1:
return (ip(value[0]), default_port) return (ip_callback(value[0]), default_port)
elif len(value) == 2: elif len(value) == 2:
return (ip(value[0]), port(value[1])) return (ip_callback(value[0]), port(value[1]))
else: else:
raise argparse.ArgumentTypeError("IP address from the optional Port " raise argparse.ArgumentTypeError("IP address from the optional Port "
"must be colon-separated") "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): 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: into a 4-tuple, where:
1. First element is ControlIP 1. First element is OuterIP
2. Second element is ControlPort (if omitted will use default value 15531) 2. Second element is OuterPort (if omitted will use default value 15531)
3 Third element is TestIP (if omitted will be the same as ControlIP) 3 Third element is InnerIP with optional mask
4. Fourth element is TestPort (if omitted will use default value 15532)""" 4. Fourth element is InnerPort (if omitted will use default value 15532)
"""
value = string.split(',') value = string.split(',')
if len(value) == 1: # TestIP and TestPort are not present if len(value) == 2:
ret = ip_optional_port(value[0], 15531) ret1 = ip_optional_port(value[0], CONTROL_PORT, ip_address)
return (ret[0], ret[1], ret[0], 15532) ret2 = ip_optional_port(value[1], DATA_PORT, ip_optional_mask)
elif len(value) == 2:
ret1 = ip_optional_port(value[0], 15531)
ret2 = ip_optional_port(value[1], 15532)
return (ret1[0], ret1[1], ret2[0], ret2[1]) return (ret1[0], ret1[1], ret2[0], ret2[1])
else: else:
raise argparse.ArgumentTypeError("ControlIP:ControlPort and TestIP:" raise argparse.ArgumentTypeError("OuterIP:OuterPort and InnerIP/Mask:"
"TestPort must be comma " "InnerPort must be comma separated")
"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): def bandwidth(string):
"""Convert a string (given in bits/second with optional magnitude for """Convert a string (given in bits/second with optional magnitude for
units) into a long (bytes/second)""" 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") raise argparse.ArgumentTypeError("Not a valid target bandwidth")
bwidth = string.replace("M", "000000") bwidth = string.replace("M", "000000")
bwidth = bwidth.replace("K", "000") 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(): def ovs_initialize_args():
"""Initialize args for ovstest utility""" """
parser = argparse.ArgumentParser(description = 'Test ovs connectivity') Initialize argument parsing for ovs-test utility.
parser.add_argument('-v', '--version', action = 'version', """
version = 'ovs-test (Open vSwitch) @VERSION@') parser = argparse.ArgumentParser(description='Test connectivity '
parser.add_argument("-b", "--bandwidth", action = 'store', 'between two Open vSwitches.')
dest = "targetBandwidth", default = "1M", type = bandwidth,
help = 'target bandwidth for UDP tests in bits/second. Use ' 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.') 'postfix M or K to alter unit magnitude.')
group = parser.add_mutually_exclusive_group(required = True) parser.add_argument("-i", "--interval", action='store',
group.add_argument("-s", "--server", action = "store", dest = "port", dest="testInterval", default=5, type=int,
type = port, help='Interval for how long to run each test in seconds.')
help = 'run in server mode and wait client to connect to this '
'port') parser.add_argument("-t", "--tunnel-modes", action='store',
group.add_argument('-c', "--client", action = "store", nargs = 2, dest="tunnelModes", default=(), type=tunnel_types,
dest = "servers", type = server_endpoint, help='Do L3 tests with the given tunnel modes.')
metavar = ("SERVER1", "SERVER2"), parser.add_argument("-l", "--vlan-tag", action='store',
help = 'run in client mode and do tests between these ' dest="vlanTag", default=None, type=vlan_tag,
'two servers. Each server must be specified in following ' help='Do VLAN tests and use the given VLAN tag.')
'format - ControlIP[:ControlPort][,TestIP[:TestPort]]. If ' parser.add_argument("-d", "--direct", action='store_true',
'TestIP is omitted then ovs-test server will also use the ' dest="direct", default=None,
'ControlIP for testing purposes. ControlPort is TCP port ' help='Do direct tests between both ovs-test servers.')
'where server will listen for incoming XML/RPC control '
'connections to schedule tests (by default 15531). TestPort ' group = parser.add_mutually_exclusive_group(required=True)
'is port which will be used by server to send test traffic ' group.add_argument("-s", "--server", action="store", dest="port",
'(by default 15532)') 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() return parser.parse_args()

View File

@@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 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.internet import reactor
from twisted.web import xmlrpc, server
from twisted.internet.error import CannotListenError from twisted.internet.error import CannotListenError
import udp from twisted.web import xmlrpc
from twisted.web import server
import tcp import tcp
import args import udp
import util import util
import vswitch
class TestArena(xmlrpc.XMLRPC): 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 remotely. The caller is responsible to use designated handleIds
for designated methods (e.g. do not mix UDP and TCP handles). for designated methods (e.g. do not mix UDP and TCP handles).
""" """
def __init__(self): def __init__(self):
xmlrpc.XMLRPC.__init__(self) xmlrpc.XMLRPC.__init__(self, allowNone=True)
self.handle_id = 1 self.handle_id = 1
self.handle_map = {} self.handle_map = {}
self.bridges = set()
self.pbridges = set()
self.ports = set()
self.request = None
def __acquire_handle(self, value): def __acquire_handle(self, value):
""" """
@@ -58,6 +68,46 @@ class TestArena(xmlrpc.XMLRPC):
""" """
del self.handle_map[handle] 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): def xmlrpc_create_udp_listener(self, port):
""" """
@@ -171,6 +221,103 @@ class TestArena(xmlrpc.XMLRPC):
return -1 return -1
return 0 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): def xmlrpc_get_interface(self, address):
""" """
@@ -198,6 +345,17 @@ class TestArena(xmlrpc.XMLRPC):
def start_rpc_server(port): def start_rpc_server(port):
RPC_SERVER = TestArena() """
reactor.listenTCP(port, server.Site(RPC_SERVER)) This function creates a RPC server and adds it to the Twisted Reactor.
reactor.run() """
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()

View File

@@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -29,14 +29,10 @@ class TcpListenerConnection(Protocol):
def __init__(self): def __init__(self):
self.stats = 0 self.stats = 0
def connectionMade(self):
print "Started TCP Listener connection"
def dataReceived(self, data): def dataReceived(self, data):
self.stats += len(data) self.stats += len(data)
def connectionLost(self, reason): def connectionLost(self, reason):
print "Stopped TCP Listener connection"
self.factory.stats += self.stats self.factory.stats += self.stats
@@ -50,16 +46,10 @@ class TcpListenerFactory(Factory):
def __init__(self): def __init__(self):
self.stats = 0 self.stats = 0
def startFactory(self):
print "Starting TCP listener factory"
def stopFactory(self):
print "Stopping TCP listener factory"
def getResults(self): def getResults(self):
""" returns the number of bytes received as string""" """ returns the number of bytes received as string"""
#XML RPC does not support 64bit int (http://bugs.python.org/issue2985) # XML RPC does not support 64bit int (http://bugs.python.org/issue2985)
#so we have to convert the amount of bytes into a string # so we have to convert the amount of bytes into a string
return str(self.stats) return str(self.stats)
@@ -104,18 +94,13 @@ class TcpSenderConnection(Protocol):
""" """
def connectionMade(self): def connectionMade(self):
print "Started TCP sender connection"
producer = Producer(self, self.factory.duration) producer = Producer(self, self.factory.duration)
self.transport.registerProducer(producer, True) self.transport.registerProducer(producer, True)
producer.resumeProducing() producer.resumeProducing()
def dataReceived(self, data): def dataReceived(self, data):
print "Sender received data!", data
self.transport.loseConnection() self.transport.loseConnection()
def connectionLost(self, reason):
print "Stopped TCP sender connection"
class TcpSenderFactory(ClientFactory): class TcpSenderFactory(ClientFactory):
""" """
@@ -128,12 +113,6 @@ class TcpSenderFactory(ClientFactory):
self.duration = duration self.duration = duration
self.stats = 0 self.stats = 0
def startFactory(self):
print "Starting TCP sender factory"
def stopFactory(self):
print "Stopping TCP sender factory"
def getResults(self): def getResults(self):
"""Returns amount of bytes sent to the Listener (as a string)""" """Returns amount of bytes sent to the Listener (as a string)"""
return str(self.stats) return str(self.stats)

View File

@@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 ovsudp contains listener and sender classes for UDP protocol
""" """
import array
import struct
import time
from twisted.internet.protocol import DatagramProtocol from twisted.internet.protocol import DatagramProtocol
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
import array, struct, time
class UdpListener(DatagramProtocol): class UdpListener(DatagramProtocol):
@@ -28,18 +31,12 @@ class UdpListener(DatagramProtocol):
def __init__(self): def __init__(self):
self.stats = [] self.stats = []
def startProtocol(self):
print "Starting UDP listener"
def stopProtocol(self):
print "Stopping UDP listener"
def datagramReceived(self, data, (_1, _2)): def datagramReceived(self, data, (_1, _2)):
"""This function is called each time datagram is received""" """This function is called each time datagram is received"""
try: try:
self.stats.append(struct.unpack_from("Q", data, 0)) self.stats.append(struct.unpack_from("Q", data, 0))
except struct.error: 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): def getResults(self):
"""Returns number of packets that were actually received""" """Returns number of packets that were actually received"""
@@ -51,7 +48,7 @@ class UdpSender(DatagramProtocol):
Class that will send UDP packets to UDP Listener Class that will send UDP packets to UDP Listener
""" """
def __init__(self, host, count, size, duration): 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.looper = None
self.host = host self.host = host
self.count = count self.count = count
@@ -61,13 +58,11 @@ class UdpSender(DatagramProtocol):
self.data = array.array('c', 'X' * size) self.data = array.array('c', 'X' * size)
def startProtocol(self): def startProtocol(self):
print "Starting UDP sender"
self.looper = LoopingCall(self.sendData) self.looper = LoopingCall(self.sendData)
period = self.duration / float(self.count) period = self.duration / float(self.count)
self.looper.start(period , now = False) self.looper.start(period , now = False)
def stopProtocol(self): def stopProtocol(self):
print "Stopping UDP sender"
if (self.looper is not None): if (self.looper is not None):
self.looper.stop() self.looper.stop()
self.looper = None self.looper = None

View File

@@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,13 +15,28 @@
""" """
util module contains some helper function 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) return ("%u.%u.%u.%u") % (x1, x2, x3, x4)
def get_interface_mtu(iface): def get_interface_mtu(iface):
"""
Returns MTU of the given interface.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
indata = iface + ('\0' * (32 - len(iface))) indata = iface + ('\0' * (32 - len(iface)))
try: try:
@@ -32,6 +47,7 @@ def get_interface_mtu(iface):
return mtu return mtu
def get_interface(address): def get_interface(address):
""" """
Finds first interface that has given 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] name = namestr[i:i + 16].split('\0', 1)[0]
if address == str_ip(namestr[i + 20:i + 24]): if address == str_ip(namestr[i + 20:i + 24]):
return name return name
return "" # did not find interface we were looking for return None # did not find interface we were looking for
def uname(): def uname():
os_info = os.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: try:
p = subprocess.Popen( p = subprocess.Popen(args,
["ethtool", "-i", iface],
stdin = subprocess.PIPE, stdin = subprocess.PIPE,
stdout = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE) stderr = subprocess.PIPE)
out, err = p.communicate() out, err = p.communicate()
if p.returncode == 0: return (p.returncode, out, err)
lines = out.split("\n")
driver = "%s(%s)" % (lines[0], lines[1]) #driver name + version
else:
driver = "no support for ethtool"
except exceptions.OSError: 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 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)

108
python/ovstest/vswitch.py Normal file
View File

@@ -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

View File

@@ -3,41 +3,47 @@
. ns . ns
. IP "\\$1" . 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 .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 .SH SYNOPSIS
\fBovs\-test\fR \fB\-s\fR \fIport\fR \fBovs\-test\fR \fB\-s\fR \fIport\fR
.PP .PP
\fBovs\-test\fR \fB\-c\fR \fIserver1\fR \fBovs\-test\fR \fB\-c\fR \fIserver1\fR \fIserver2\fR
\fIserver2\fR [\fB\-b\fR \fIbandwidth\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 .so lib/common-syn.man
. .
.SH DESCRIPTION .SH DESCRIPTION
The \fBovs\-test\fR program may be used to check for problems sending The \fBovs\-test\fR program may be used to check for problems sending
802.1Q traffic that Open vSwitch may uncover. These problems can 802.1Q or GRE traffic that Open vSwitch may uncover. These problems,
occur when Open vSwitch is used to send 802.1Q traffic through physical for example, can occur when Open vSwitch is used to send 802.1Q traffic
interfaces running certain drivers of certain Linux kernel versions. To run a through physical interfaces running certain drivers of certain Linux kernel
test, configure Open vSwitch to tag traffic originating from \fIserver1\fR and versions. To run a test, configure IP addresses on \fIserver1\fR and
forward it to the \fIserver2\fR. On both servers run \fBovs\-test\fR \fIserver2\fR for interfaces you intended to test. These interfaces could
in server mode. Then, on any other host, run the \fBovs\-test\fR in client also be already configured OVS bridges that have a physical interface attached
mode. The client will connect to both \fBovs\-test\fR servers and schedule to them. Then, on one of the nodes, run \fBovs\-test\fR in server mode and on
tests between them. \fBovs\-test\fR will perform UDP and TCP tests. 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 .PP
UDP tests can report packet loss and achieved bandwidth, because UDP flow UDP tests can report packet loss and achieved bandwidth for various
control is done inside \fBovs\-test\fR. It is also possible to specify target datagram sizes. By default target bandwidth for UDP tests is 1Mbit/s.
bandwidth for UDP. By default it is 1Mbit/s.
.PP .PP
TCP tests report only achieved bandwidth, because kernel TCP stack TCP tests report only achieved bandwidth, because kernel TCP stack
takes care of flow control and packet loss. TCP tests are essential to detect takes care of flow control and packet loss. TCP tests are essential to detect
potential TSO related VLAN issues. potential TSO related issues.
.PP .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 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 traffic is being directly sent and in one where it is not. If in the
both servers are unable to communicate or the achieved bandwidth is lower, 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 then, most likely, Open vSwitch has encountered a pre-existing kernel or
driver bug. driver bug.
.PP .PP
@@ -46,71 +52,87 @@ Some examples of the types of problems that may be encountered are:
. .
.SS "Client Mode" .SS "Client Mode"
An \fBovs\-test\fR client will connect to two \fBovs\-test\fR servers and 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" .SS "Server Mode"
To conduct tests, two \fBovs\-test\fR servers must be running on two different 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 hosts where the client can connect. The actual test traffic is exchanged only
between both \fBovs\-test\fR server test IP addresses. It is recommended that between both \fBovs\-test\fR servers. It is recommended that both servers have
both servers have their test IP addresses in the same subnet, otherwise one their IP addresses in the same subnet, otherwise one would have to make sure
will need to change routing so that the test traffic actually goes through the that routing is set up correctly.
interface that he originally intended to test.
. .
.SH OPTIONS .SH OPTIONS
. .
.IP "\fB\-s \fIport\fR" .IP "\fB\-s \fIport\fR"
.IQ "\fB\-\-server\fR \fIport\fR" .IQ "\fB\-\-server\fR \fIport\fR"
Run in server mode and wait for a client to establish XML RPC Control Run in server mode and wait for the client to establish XML RPC Control
Connection on TCP \fIport\fR. It is recommended to have ethtool installed on Connection on this TCP \fIport\fR. It is recommended to have \fBethtool\fR(8)
the server so that it could retrieve information about NIC driver. installed on the server so that it could retrieve information about the NIC
driver.
. .
.IP "\fB\-c \fIserver1\fR \fIserver2\fR" .IP "\fB\-c \fIserver1\fR \fIserver2\fR"
.IQ "\fB\-\-client \fIserver1\fR \fIserver2\fR" .IQ "\fB\-\-client \fIserver1\fR \fIserver2\fR"
Run in client mode and schedule tests between \fIserver1\fR and \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 - where each \fIserver\fR must be given in the following format -
ControlIP[:ControlPort][,TestIP[:TestPort]]. If TestIP is omitted then \fIOuterIP[:OuterPort],InnerIP[/Mask][:InnerPort]\fR. The \fIOuterIP\fR must
ovs-test server will use the ControlIP for testing purposes. ControlPort is be already assigned to the physical interface which is going to be tested.
TCP port where server will listen for incoming XML/RPC control This is the IP address where client will try to establish XML RPC connection.
connections to schedule tests (by default it is 15531). TestPort If \fIOuterIP\fR is 127.0.0.1 then client will automatically spawn a local
is port which will be used by server to listen for test traffic instance of \fBovs\-test\fR server. \fIOuterPort\fR is TCP port where server
(by default it is 15532). 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 \fIbandwidth\fR" .IP "\fB\-b \fItargetbandwidth\fR"
.IQ "\fB\-\-bandwidth\fR \fIbandwidth\fR" .IQ "\fB\-\-bandwidth\fR \fItargetbandwidth\fR"
Target bandwidth for UDP tests. The \fIbandwidth\fR must be given in bits per Target bandwidth for UDP tests. The \fItargetbandwidth\fR must be given in
second. It is possible to use postfix M or K to alter the target bandwidth bits per second. It is possible to use postfix M or K to alter the target
magnitude. 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 .so lib/common.man
.
.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 .SH EXAMPLES
.PP .PP
Set up a bridge which forwards traffic originating from \fB1.2.3.4\fR out On host 1.2.3.4 start \fBovs\-test\fR in server mode:
\fBeth1\fR with VLAN tag 10.
.IP .IP
.B ovs\-vsctl \-\- add\-br vlan\-br \(rs .B ovs\-test \-s 15531
.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
. .
.PP .PP
On two different hosts start \fBovs\-test\fR in server mode and tell them to On host 1.2.3.5 start \fBovs\-test\fR in client mode and do direct, VLAN and
listen on port 15531 for incoming client control connections: GRE tests between both nodes:
.IP .IP
.B 1.2.3.4: ovs\-test \-s 15531 .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
.IP
.B 1.2.3.5: 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).
.IP
.B ovs\-test -c 1.2.3.4 1.2.3.5
. .
.SH SEE ALSO .SH SEE ALSO
. .
@@ -119,4 +141,4 @@ client will use TCP port 15531 to establish control channel).
.BR ovs\-vsctl (8), .BR ovs\-vsctl (8),
.BR ovs\-vlan\-test (8), .BR ovs\-vlan\-test (8),
.BR ethtool (8), .BR ethtool (8),
.BR uname (1) .BR uname (1)

View File

@@ -16,17 +16,84 @@
ovs test utility that allows to do tests between remote hosts ovs test utility that allows to do tests between remote hosts
""" """
import twisted import fcntl
import xmlrpclib
import time
import socket
import math 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 = "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): def bandwidth_to_string(bwidth):
"""Convert bandwidth from long to string and add units""" """Convert bandwidth from long to string and add units."""
bwidth = bwidth * 8 # Convert back to bits/second bwidth = bwidth * 8 # Convert back to bits/second
if bwidth >= 10000000: if bwidth >= 10000000:
return str(int(bwidth / 1000000)) + "Mbps" return str(int(bwidth / 1000000)) + "Mbps"
elif bwidth > 10000: elif bwidth > 10000:
@@ -38,36 +105,45 @@ def bandwidth_to_string(bwidth):
def collect_information(node): def collect_information(node):
"""Print information about hosts that will do testing""" """Print information about hosts that will do testing"""
print "Node %s:%u " % (node[0], node[1]) print "Node %s:%u " % (node[0], node[1])
server1 = xmlrpclib.Server("http://%s:%u/" % (node[0], node[1])) server = rpc_client(node[0], node[1])
interface_name = server1.get_interface(node[2]) interface_name = server.get_interface(node[0])
uname = server1.uname() phys_iface = None
uname = server.uname()
mtu = 1500 mtu = 1500
if interface_name == "": if not interface_name:
print ("Could not find interface that has %s IP address." 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: else:
mtu = server1.get_interface_mtu(interface_name) if server.is_ovs_bridge(interface_name):
driver = server1.get_driver(interface_name) phys_iface = server.get_iface_from_bridge(interface_name)
print "Will be using %s(%s) with MTU %u" % (interface_name, node[2],
mtu)
if driver == "":
print "Install ethtool on this host to get NIC driver information"
else: else:
print "On this host %s has %s." % (interface_name, driver) phys_iface = interface_name
if uname == "": 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 not driver:
print "Unable to get driver information from ethtool."
else:
print "On this host %s has %s." % (phys_iface, driver)
if not uname:
print "Unable to retrieve kernel information. Is this Linux?" print "Unable to retrieve kernel information. Is this Linux?"
else: else:
print "Running kernel %s." % uname print "Running kernel %s." % uname
print "\n" print "\n"
return mtu 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""" """Schedule UDP tests between receiver and sender"""
server1 = xmlrpclib.Server("http://%s:%u/" % (receiver[0], receiver[1])) server1 = rpc_client(receiver[0], receiver[1])
server2 = xmlrpclib.Server("http://%s:%u/" % (sender[0], sender[1])) server2 = rpc_client(sender[0], sender[1])
udpformat = '{0:>15} {1:>15} {2:>15} {3:>15} {4:>15}' udpformat = '{0:>15} {1:>15} {2:>15} {3:>15} {4:>15}'
@@ -77,7 +153,7 @@ def do_udp_tests(receiver, sender, tbwidth, duration, sender_mtu):
print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv Datagrams", print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv Datagrams",
"Datagram Loss", "Bandwidth") "Datagram Loss", "Bandwidth")
for size in [8, sender_mtu - 100, sender_mtu - 28, sender_mtu]: for size in port_sizes:
listen_handle = -1 listen_handle = -1
send_handle = -1 send_handle = -1
try: try:
@@ -89,11 +165,12 @@ def do_udp_tests(receiver, sender, tbwidth, duration, sender_mtu):
" %u. Try to restart the server.\n" % receiver[3]) " %u. Try to restart the server.\n" % receiver[3])
return return
send_handle = server2.create_udp_sender( send_handle = server2.create_udp_sender(
(receiver[2], receiver[3]), (ip_from_cidr(receiver[2]),
packetcnt, size, duration) receiver[3]), packetcnt, size,
duration)
#Using sleep here because there is no other synchronization source # Using sleep here because there is no other synchronization source
#that would notify us when all sent packets were received # that would notify us when all sent packets were received
time.sleep(duration + 1) time.sleep(duration + 1)
rcv_packets = server1.get_udp_listener_results(listen_handle) rcv_packets = server1.get_udp_listener_results(listen_handle)
@@ -115,8 +192,8 @@ def do_udp_tests(receiver, sender, tbwidth, duration, sender_mtu):
def do_tcp_tests(receiver, sender, duration): def do_tcp_tests(receiver, sender, duration):
"""Schedule TCP tests between receiver and sender""" """Schedule TCP tests between receiver and sender"""
server1 = xmlrpclib.Server("http://%s:%u/" % (receiver[0], receiver[1])) server1 = rpc_client(receiver[0], receiver[1])
server2 = xmlrpclib.Server("http://%s:%u/" % (sender[0], sender[1])) server2 = rpc_client(sender[0], sender[1])
tcpformat = '{0:>15} {1:>15} {2:>15}' tcpformat = '{0:>15} {1:>15} {2:>15}'
print "TCP test from %s:%u to %s:%u (full speed)" % (sender[0], sender[1], print "TCP test from %s:%u to %s:%u (full speed)" % (sender[0], sender[1],
@@ -131,8 +208,8 @@ def do_tcp_tests(receiver, sender, duration):
print ("Server was unable to open TCP listening socket on port" print ("Server was unable to open TCP listening socket on port"
" %u. Try to restart the server.\n" % receiver[3]) " %u. Try to restart the server.\n" % receiver[3])
return return
send_handle = server2.create_tcp_sender(receiver[2], receiver[3], send_handle = server2.create_tcp_sender(ip_from_cidr(receiver[2]),
duration) receiver[3], duration)
time.sleep(duration + 1) time.sleep(duration + 1)
@@ -151,30 +228,178 @@ def do_tcp_tests(receiver, sender, duration):
print "\n" 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__': if __name__ == '__main__':
local_server = None
try: try:
ovs_args = args.ovs_initialize_args() ovs_args = args.ovs_initialize_args()
if ovs_args.port is not None: # Start in server mode if ovs_args.port is not None: # Start in pure server mode
print "Starting RPC server" rpcserver.start_rpc_server(ovs_args.port)
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
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] node1 = ovs_args.servers[0]
node2 = ovs_args.servers[1] 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_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: except KeyboardInterrupt:
pass pass
except xmlrpclib.Fault:
print "Couldn't establish XMLRPC control channel"
except socket.error: except socket.error:
print "Couldn't establish XMLRPC control channel" 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()