Merged from Ed Swierk test-day pkt-in/out
diff --git a/README b/README
index 5d1b5c5..ea5b7a4 100644
--- a/README
+++ b/README
@@ -1,5 +1,6 @@
OpenFlow Testing Framework
July, 2010
+Last updated March 2012
Copyright (c) 2010 The Board of Trustees of The Leland Stanford
Junior University
@@ -30,7 +31,7 @@
You can check out OFTest with git with the following command:
- git clone git://openflow.org/oftest
+ git clone git://github.com/floodlight/oftest
Introduction
++++++++++++
@@ -62,15 +63,15 @@
Currently, switches must be running version 1.0 of OpenFlow.
- # git clone yuba:/usr/local/git/openflow-projects/oftest
+ # git clone git://github.com/floodlight/oftest
# cd oftest/tools/munger
# make install
# cd ../../tests
Make sure the switch you want to test is running --
see (4) below for the reference switch example.
# ./oft --list
- # sudo ./oft
- # sudo ./oft --verbose --log-file=""
+ # sudo ./oft --test-spec=Echo
+ # sudo ./oft --verbose --log-file=""
# sudo ./oft --test-spec=<mod> --platform=remote --host=...
Longer Start
@@ -81,8 +82,8 @@
* Root privilege on host running oft
* Switch running OpenFlow 1.0 and attempting to connect
to a controller on the machine running oft.
- * Python 2.5. You can run platforms using eth interfaces
- with Python 2.4.
+ * Python 2.5 or 2.6. You can run platforms using eth interfaces
+ with Python 2.4. Python 2.7 may work.
* Python setup tools (e.g.: sudo apt-get install python-setuptools)
* oftest checked out (called <oftest> here)
* scapy installed: http://www.secdev.org/projects/scapy/
@@ -139,6 +140,10 @@
4F. To clean up the virtual ethernet interfaces, use
sudo rmmod veth
+ New tools allow you to run an OVS instance as well. See
+ oftest/tools/ovs-ctl. You will need to install a version of
+ openvswitch. See http://openvswitch.org/.
+
5. Run oft
See Warning above; requires sudo to control the dataplane
cd <oftest>/tests
diff --git a/tests/oft b/tests/oft
index bbbb0fa..c397372 100755
--- a/tests/oft
+++ b/tests/oft
@@ -246,7 +246,9 @@
parser.add_option("--param", type="int",
help="Parameter sent to test (for debugging)")
parser.add_option("-t", "--test-params",
- help="Set test parameters: key=val;... See --list")
+ help="""Set test parameters: key=val;...
+ NOTE: key MUST be a valid Python identifier, egr_count not egr-count
+ See --list""")
# Might need this if other parsers want command line
# parser.allow_interspersed_args = False
(options, args) = parser.parse_args()
diff --git a/tests/pktact.py b/tests/pktact.py
index 5357b7c..9230afd 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -667,7 +667,12 @@
"""
def runTest(self):
for wc in WILDCARD_VALUES:
- flow_match_test(self, pa_port_map, wildcards=wc, max_test=10)
+ if wc & ofp.OFPFW_DL_VLAN:
+ dl_vlan = 0
+ else:
+ dl_vlan = -1
+ flow_match_test(self, pa_port_map, wildcards=wc,
+ dl_vlan=dl_vlan, max_test=10)
class SingleWildcardMatchTagged(BaseMatchCase):
"""
@@ -693,7 +698,12 @@
def runTest(self):
for wc in WILDCARD_VALUES:
all_exp_one_wildcard = ofp.OFPFW_ALL ^ wc
- flow_match_test(self, pa_port_map, wildcards=all_exp_one_wildcard)
+ if all_exp_one_wildcard & ofp.OFPFW_DL_VLAN:
+ dl_vlan = 0
+ else:
+ dl_vlan = -1
+ flow_match_test(self, pa_port_map, wildcards=all_exp_one_wildcard,
+ dl_vlan=dl_vlan)
class AllExceptOneWildcardMatchTagged(BaseMatchCase):
"""
@@ -943,7 +953,105 @@
(pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['ip_tos'],
check_test_params=True)
flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
- action_list=acts, max_test=2)
+ action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2DstMC(BaseMatchCase):
+ """
+ Modify the L2 dest and send to 2 ports
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+ skip_message_emit(self, "ModifyL2dstMC test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2DstIngress(BaseMatchCase):
+ """
+ Modify the L2 dest and send to the ingress port
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+ skip_message_emit(self, "ModifyL2dstMC test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=0,
+ ing_port=True)
+
+class ModifyL2DstIngressMC(BaseMatchCase):
+ """
+ Modify the L2 dest and send to the ingress port
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+ skip_message_emit(self, "ModifyL2dstMC test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=-1,
+ ing_port=True)
+
+class ModifyL2SrcMC(BaseMatchCase):
+ """
+ Modify the source MAC address (TP1) and send to multiple
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if not (sup_acts & 1 << ofp.OFPAT_SET_DL_SRC):
+ skip_message_emit(self, "ModifyL2SrcMC test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_src'],
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2SrcDstMC(BaseMatchCase):
+ """
+ Modify the L2 source and dest and send to 2 ports
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if (not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST) or
+ not (sup_acts & 1 << ofp.OFPAT_SET_DL_SRC)):
+ skip_message_emit(self, "ModifyL2SrcDstMC test")
+ return
+
+ mod_fields = ['dl_dst', 'dl_src']
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=mod_fields,
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2DstVIDMC(BaseMatchCase):
+ """
+ Modify the L2 dest and send to 2 ports
+ """
+ def runTest(self):
+ sup_acts = supported_actions_get(self)
+ if (not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST) or
+ not (sup_acts & 1 << ofp.OFPAT_SET_VLAN_VID)):
+ skip_message_emit(self, "ModifyL2DstVIDMC test")
+ return
+
+ mod_fields = ['dl_dst', 'dl_vlan']
+ (pkt, exp_pkt, acts) = pkt_action_setup(self,
+ start_field_vals={'dl_vlan_enable':True}, mod_fields=mod_fields,
+ check_test_params=True)
+ flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2, egr_count=-1)
+
#@todo Need to implement tagged versions of the above tests
#
diff --git a/tests/testutils.py b/tests/testutils.py
index c6252bc..5a99a00 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -256,9 +256,10 @@
"Unexpected pkt on port " + str(ofport))
-def receive_pkt_verify(parent, egr_port, exp_pkt):
+def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
"""
Receive a packet and verify it matches an expected value
+ @param egr_port A single port or list of ports
parent must implement dataplane, assertTrue and assertEqual
"""
@@ -266,27 +267,37 @@
if parent.config["relax"]:
exp_pkt_arg = exp_pkt
- (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(port_number=egr_port,
- timeout=1,
- exp_pkt=exp_pkt_arg)
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
- if rcv_pkt is None:
- parent.logger.error("ERROR: No packet received from " + str(egr_port))
+ # Expect a packet from each port on egr port list
+ for egr_port in egr_port_list:
+ check_port = egr_port
+ if egr_port == ofp.OFPP_IN_PORT:
+ check_port = ing_port
+ (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
+ port_number=check_port, timeout=1, exp_pkt=exp_pkt_arg)
- parent.assertTrue(rcv_pkt is not None,
- "Did not receive packet port " + str(egr_port))
- parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
- str(rcv_port))
+ if rcv_pkt is None:
+ parent.logger.error("ERROR: No packet received from " +
+ str(check_port))
- if str(exp_pkt) != str(rcv_pkt):
- parent.logger.error("ERROR: Packet match failed.")
- parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
- + str(exp_pkt).encode('hex'))
- parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
- + str(rcv_pkt).encode('hex'))
- parent.assertEqual(str(exp_pkt), str(rcv_pkt),
- "Packet match error on port " + str(egr_port))
-
+ parent.assertTrue(rcv_pkt is not None,
+ "Did not receive packet port " + str(check_port))
+ parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
+ str(rcv_port))
+
+ if str(exp_pkt) != str(rcv_pkt):
+ parent.logger.error("ERROR: Packet match failed.")
+ parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
+ + str(exp_pkt).encode('hex'))
+ parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
+ + str(rcv_pkt).encode('hex'))
+ parent.assertEqual(str(exp_pkt), str(rcv_pkt),
+ "Packet match error on port " + str(check_port))
+
def match_verify(parent, req_match, res_match):
"""
Verify flow matches agree; if they disagree, report where
@@ -387,7 +398,7 @@
str(byte_count))
def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
- egr_port=None, egr_queue=None, check_expire=False, in_band=True):
+ egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
"""
Create a flow message
@@ -395,6 +406,7 @@
See flow_match_test for other parameter descriptoins
@param egr_queue if not None, make the output an enqueue action
@param in_band if True, do not wildcard ingress port
+ @param egr_ports None (drop), single port or list of ports
"""
match = parse.packet_to_flow_match(pkt)
parent.assertTrue(match is not None, "Flow match from pkt failed")
@@ -403,6 +415,11 @@
match.wildcards = wildcards
match.in_port = ing_port
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
+
request = message.flow_mod()
request.match = match
request.buffer_id = 0xffffffff
@@ -418,18 +435,21 @@
# Set up output/enqueue action if directed
if egr_queue is not None:
- parent.assertTrue(egr_port is not None, "Egress port not set")
+ parent.assertTrue(egr_ports is not None, "Egress port not set")
act = action.action_enqueue()
- act.port = egr_port
- act.queue_id = egr_queue
- rv = request.actions.add(act)
- parent.assertTrue(rv, "Could not add enqueue action " +
- str(egr_port) + " Q: " + str(egr_queue))
- elif egr_port is not None:
- act = action.action_output()
- act.port = egr_port
- rv = request.actions.add(act)
- parent.assertTrue(rv, "Could not add output action " + str(egr_port))
+ for egr_port in egr_port_list:
+ act.port = egr_port
+ act.queue_id = egr_queue
+ rv = request.actions.add(act)
+ parent.assertTrue(rv, "Could not add enqueue action " +
+ str(egr_port) + " Q: " + str(egr_queue))
+ elif egr_ports is not None:
+ for egr_port in egr_port_list:
+ act = action.action_output()
+ act.port = egr_port
+ rv = request.actions.add(act)
+ parent.assertTrue(rv, "Could not add output action " +
+ str(egr_port))
parent.logger.debug(request.show())
@@ -454,42 +474,69 @@
parent.assertTrue(rv != -1, "Error installing flow mod")
do_barrier(parent.controller)
-def flow_match_test_port_pair(parent, ing_port, egr_port, wildcards=0,
+def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
dl_vlan=-1, pkt=None, exp_pkt=None,
action_list=None, check_expire=False):
"""
Flow match test on single TCP packet
+ @param egr_ports A single port or list of ports
Run test with packet through switch from ing_port to egr_port
See flow_match_test for parameter descriptions
"""
- parent.logger.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
+ parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
+ str(egr_ports))
parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
" expire: " + str(check_expire))
if pkt is None:
pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
request = flow_msg_create(parent, pkt, ing_port=ing_port,
- wildcards=wildcards, egr_port=egr_port,
+ wildcards=wildcards, egr_ports=egr_ports,
action_list=action_list)
flow_msg_install(parent, request)
- parent.logger.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+ parent.logger.debug("Send packet: " + str(ing_port) + " to " +
+ str(egr_ports))
parent.dataplane.send(ing_port, str(pkt))
if exp_pkt is None:
exp_pkt = pkt
- receive_pkt_verify(parent, egr_port, exp_pkt)
+ receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
if check_expire:
#@todo Not all HW supports both pkt and byte counters
flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
+def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
+ """
+ Generate a list of ports avoiding those in the exclude list
+ @param parent Supplies logger
+ @param of_ports List of OF port numbers
+ @param how_many Number of ports to be added to the list
+ @param exclude_list List of ports not to be used
+ @returns An empty list if unable to find enough ports
+ """
+
+ if how_many == 0:
+ return []
+
+ count = 0
+ egr_ports = []
+ for egr_idx in range(len(of_ports)):
+ if of_ports[egr_idx] not in exclude_list:
+ egr_ports.append(of_ports[egr_idx])
+ count += 1
+ if count >= how_many:
+ return egr_ports
+ parent.logger.debug("Could not generate enough egress ports for test")
+ return []
+
def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
exp_pkt=None, action_list=None, check_expire=False,
- max_test=0):
+ max_test=0, egr_count=1, ing_port=False):
"""
Run flow_match_test_port_pair on all port pairs
@@ -502,27 +549,34 @@
@param exp_pkt If not None, use this as the expected output pkt; els use pkt
@param action_list Additional actions to add to flow mod
@param check_expire Check for flow expiration message
+ @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
"""
of_ports = port_map.keys()
of_ports.sort()
parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
test_count = 0
+ if egr_count == -1:
+ egr_count = test_param_get(parent.config, 'egr_count', default=2)
+
for ing_idx in range(len(of_ports)):
ingress_port = of_ports[ing_idx]
- for egr_idx in range(len(of_ports)):
- if egr_idx == ing_idx:
- continue
- egress_port = of_ports[egr_idx]
- flow_match_test_port_pair(parent, ingress_port, egress_port,
- wildcards=wildcards, dl_vlan=dl_vlan,
- pkt=pkt, exp_pkt=exp_pkt,
- action_list=action_list,
- check_expire=check_expire)
- test_count += 1
- if (max_test > 0) and (test_count > max_test):
- parent.logger.info("Ran " + str(test_count) + " tests; exiting")
- return
+ egr_ports = get_egr_list(parent, of_ports, egr_count,
+ exclude_list=[ingress_port])
+ if ing_port:
+ egr_ports.append(ofp.OFPP_IN_PORT)
+ if len(egr_ports) == 0:
+ parent.assertTrue(0, "Failed to generate egress port list")
+
+ flow_match_test_port_pair(parent, ingress_port, egr_ports,
+ wildcards=wildcards, dl_vlan=dl_vlan,
+ pkt=pkt, exp_pkt=exp_pkt,
+ action_list=action_list,
+ check_expire=check_expire)
+ test_count += 1
+ if (max_test > 0) and (test_count > max_test):
+ parent.logger.info("Ran " + str(test_count) + " tests; exiting")
+ return
def test_param_get(config, key, default=None):
"""
@@ -535,6 +589,9 @@
If the pair 'key=val' appeared in the string passed to --test-params
on the command line, return val (as interpreted by exec). Otherwise
return default value.
+
+ WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
+ eg egr_count, not egr-count.
"""
try:
exec config["test_params"]
diff --git a/tools/ovs-ctl/ovs-ctl-default.example.conf b/tools/ovs-ctl/ovs-ctl-default.example.conf
new file mode 100644
index 0000000..2996dce
--- /dev/null
+++ b/tools/ovs-ctl/ovs-ctl-default.example.conf
@@ -0,0 +1,96 @@
+###############################################################################
+#
+# This is the example/default ovs-ctl config file.
+#
+# ovs-ctl config files will be searched and read, by default, int he following
+# order:
+#
+# /opt/ovs/ovs-ctl-default.conf
+# ~/.ovs-ctl
+#
+# This behavior can be modified and change specifically on the command line:
+#
+# Change the location of the default config file:
+# > ovs-ctl --default-config-file=/path/to/file
+#
+# Disable the default config file:
+# > ovs-ctl --no-default
+#
+# Read these specific config files:
+# > ovs-ctl --config-file file1 file2 file3
+#
+# Note: The default config file reading behavior is equivalent to:
+#
+# > ovs-ctl --default /opt/ovs/ovs-ctl-default.conf --config-file ~/.ovs-ctl
+#
+# This is also equivalent to:
+# > ovs-ctl --no-default --config-file /opt/ovs/ovs-ctl-default.conf ~/.ovs-ctl
+#
+#
+###############################################################################
+
+#
+# Default options can be specified here
+#
+[Defaults]
+# Default all configurations to 1.4.0
+config:1.4.0
+
+#
+# Specific OVS configurations go here
+#
+[1.4.0]
+# ovs-1.4.0, configured and built into /opt/ovs/1.4.0
+# Can be selected with 'ovs-ctl.py --config 1.4.0'
+ovs_src_dir:/opt/ovs/src/openvswitch-1.4.0
+ovs_base_dir:/opt/ovs/1.4.0
+ovs_runtime_dir:/var/run/ovs/1.4.0
+
+
+[1.3.0]
+# ovs-1.3.0, configured and built into /opt/ovs/1.3.0
+# Can be selected with 'ovsctl.py --config 1.3.0'
+ovs_src_dir:/opt/ovs/src/openvswitch-1.3.0
+ovs_base_dir:/opt/ovs/1.3.0
+ovs_runtime_dir:/var/run/ovs/1.3.0
+
+[1.2.2]
+# ovs-1.2.2, configured and built into /opt/ovs/1.2.2
+# Can be selected with 'ovsctl.py --config 1.2.2'
+ovs_src_dir:/opt/ovs/src/openvswitch-1.2.2
+ovs_base_dir:/opt/ovs/1.2.2
+ovs_runtime_dir:/var/run/ovs/1.2.2
+
+#
+# In the above configurations, the locations of the tools
+# are derived by ovs-ctl relative to 'ovs_src_dir' and 'ovs_base_dir'
+#
+# Individual tools and locations can be specified directly in
+# the configuration (as well as the command line):
+#
+# Try:
+# 'ovs-ctl.py --no-default --config-file ovs-ctl-default.example.conf --config MyExample'
+#
+# In general, every command line option can be specified in a config file section.
+#
+# The precedence of options is:
+# 1. Command Line Options
+# 2. [The Config You Specified]
+# 3. [The Defaults sections]
+#
+
+[MyExampleConfig]
+ovs_vswitchd_schema:/path/to/vswitch/schema/file
+ovs_vswitchdL:/path/to/vswitchd
+ovs_vsctl:/path/to/vsctl
+ovs_ofctl:/path/to/ofctl
+ovsdb_tool:/path/to/ovsdb_tool
+ovsd_db_file:/path/to/ovs_db_file
+ovs_db_sock:/path/to/ovs_db_sock
+ovs_kmod:/path/to/ovs_kmod
+
+
+
+
+
+
diff --git a/tools/ovs-ctl/ovs-ctl.py b/tools/ovs-ctl/ovs-ctl.py
new file mode 100755
index 0000000..5df57d9
--- /dev/null
+++ b/tools/ovs-ctl/ovs-ctl.py
@@ -0,0 +1,465 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+import argparse
+import sys
+import signal
+import time
+import ConfigParser
+import pprint
+
+###############################################################################
+#
+# Arguments
+#
+# Arguments can be specified directly, or via config file.
+# TODO -- Make this a separate reusable class
+#
+################################################################################
+gBasename = "ovs-ctl"
+
+gConfigParser = argparse.ArgumentParser(description=gBasename, add_help=False)
+
+gConfigParser.add_argument('-cf', '--config-file',
+ help="Load settings from the given config file",
+ nargs='+', metavar="FILE",
+ default=os.path.expanduser("~/."+gBasename))
+
+gConfigParser.add_argument('-c', '--config',
+ help="Use the specified configuration section",
+ default=None)
+
+gConfigParser.add_argument('-d', '--default-config-file',
+ help="Location of the local default config file",
+ metavar="FILE",
+ default="/opt/ovs/%s-default.conf" % (gBasename))
+
+gConfigParser.add_argument('-nd', '--no-default',
+ help="Do not load the default config file",
+ action='store_true', default=False)
+gConfigParser.add_argument('--dump-config',
+ help="Dump the loaded configuration settings",
+ action='store_true', default=False)
+
+#
+# Parse the config files first, then parse remaining command line arguments
+#
+gConfig = ConfigParser.SafeConfigParser()
+configArgs, remainingArgs = gConfigParser.parse_known_args()
+
+if not configArgs.no_default:
+ gConfig.read([configArgs.default_config_file])
+
+if isinstance(configArgs.config_file, str):
+ configFiles = [ configArgs.config_file]
+else:
+ configFiles = configArgs.config_file
+
+configFiles = [ os.path.expanduser(x) if x.startswith('~') else x
+ for x in configFiles ]
+
+gConfig.read(configFiles)
+
+# Dump loaded config if requested
+if configArgs.dump_config:
+ for section in gConfig.sections():
+ print section
+ for option in gConfig.options(section):
+ print " ", option, "=", gConfig.get(section, option)
+ sys.exit()
+
+
+
+#
+# Functional arguments go here
+#
+
+#
+# OVS target binaries -- these can all be specified individually.
+# If not specified, they are determined by the base directory settings
+#
+gParser = argparse.ArgumentParser(parents=[gConfigParser])
+
+gParser.add_argument('--ovs-vswitchd-schema',
+ help="""Path to the vswitchd.ovsschema file""")
+gParser.add_argument('--ovs-vswitchd-log',
+ help="""Path to the vswitchd log file""")
+gParser.add_argument('--ovs-vswitchd',
+ help="""Path to the target ovs-vswitchd binary""")
+gParser.add_argument('--ovs-vsctl',
+ help="""Path to the target ovs-vsctl binary""")
+gParser.add_argument('--ovs-ofctl',
+ help="""Path to the target ovs-ofctl binary""")
+gParser.add_argument('--ovsdb-tool',
+ help="""Path to the target ovsdb-tool binary""")
+gParser.add_argument('--ovsdb-server',
+ help="""Path to the target ovsdb-server binary""")
+gParser.add_argument('--ovs-kmod',
+ help="""Path to the OVS kernel module""")
+gParser.add_argument('--ovs-src-dir',
+ help="""Directory for the OVS Source Files""")
+
+gParser.add_argument('--ovs-log-dir',
+ help="""Directory for the OVS Runtime Log Files""")
+
+gParser.add_argument('--ovs-version')
+
+gParser.add_argument("--ovs-base-dir", help="OVS Base Installation Directory")
+
+gParser.add_argument("--ovs-runtime-dir",
+ help="OVS Runtime Directory",
+ default="/var/run/ovs")
+
+gParser.add_argument('--ovs-db-sock',
+ help="Domain Socket Location")
+
+gParser.add_argument('--ovs-db-file',
+ help="Location for the OVS database file")
+
+
+#
+# Logging/Debugging/Testing options
+#
+gParser.add_argument('--dry',
+ help="""Dry run only. Don't actually do anything""",
+ action='store_true', default=False)
+
+gParser.add_argument('--log',
+ help='Enable logging',
+ action='store_true', default=False)
+
+gParser.add_argument('--verbose',
+ help='Enable verbose output information',
+ action='store_true', default=False)
+
+gParser.add_argument("--kill", help="Kill running OVS",
+ default=False, action='store_true')
+
+gParser.add_argument("--keep-veths",
+ help="""By default, all existing veths will be destroyed and
+the veth module removed before initializing. If you don't want the veth module
+removed, specify this argument. Your mileage may vary if you use this.""",
+ default=False, action='store_true')
+
+gParser.add_argument("--no-kmod",
+ help="""Do not attempt to insert or remove the OVS kernel module.
+Your mileage may vary.""",
+ default=False, action='store_true')
+
+gParser.add_argument("--vlog",
+ help="""Tail the running vswitch.log file in a new xterm""",
+ default=False, action='store_true')
+
+#
+# Runtime and setup arguments
+#
+gParser.add_argument('-p', '--port-count', type=int,
+ help="Number of veth ports to connect.",
+ default='4')
+
+
+gParser.add_argument("--bridge", help="Name of OF OVS Bridge",
+ default="ofbr0")
+
+gParser.add_argument("--cip", help="Controller Connection",
+ default="127.0.0.1")
+
+gParser.add_argument("--cport", type=int, help="Controller Port",
+ default=6633)
+
+gParser.add_argument("--max_backoff", help="VSwitchD max backoff value",
+ default=1000, type=int)
+
+
+gParser.add_argument("--keep-db",
+ help="""By default, a new database is initialized at each
+execution. If you want to keep and use the old database (if it exists),
+use this option""",
+ action='store_true', default=False)
+
+
+
+#
+# Reset defaults based on config files and override
+#
+# Anything in the "Defaults" section gets slurped first:
+defaults = {}
+if gConfig.has_section('Defaults'):
+ defaults = dict(gConfig.items('Defaults'))
+ gParser.set_defaults(**defaults)
+
+#
+# The configuration to execute might be specified in on the command line, or in the Defaults section(s)
+# of the config files.
+#
+# If its specified on the command line, it will be present in configArgs.config
+# If its specified in the section, it will only be present in the defaults dict.
+# Need to check both. Command line takes precedence.
+#
+gConfigSection = None
+if configArgs.config != None:
+ gConfigSection = configArgs.config
+elif defaults.has_key('config'):
+ gConfigSection = defaults['config']
+
+
+if gConfigSection != None:
+ if gConfig.has_section(gConfigSection):
+ section = dict(gConfig.items(gConfigSection))
+ gParser.set_defaults(**section)
+ else:
+ print >>sys.stderr, "Requestion configuration '%s' does not exist" % (configArgs.config)
+ sys.exit()
+
+
+###############################################################################
+#
+# Done with argument setup. Parser remaining arguments
+#
+###############################################################################
+gArgs = gParser.parse_args(remainingArgs)
+
+
+#
+# At the end of all of this, we need the following things to be defined
+# or derived:
+#
+gRequiredOptions = [
+ [ 'ovs_vswitchd_schema', 'ovs_src_dir', '/vswitchd/vswitch.ovsschema', True, True ],
+ [ 'ovs_vswitchd', 'ovs_base_dir', '/sbin/ovs-vswitchd', True, True ],
+ [ 'ovs_vsctl', 'ovs_base_dir', '/bin/ovs-vsctl', True, True ],
+ [ 'ovs_ofctl', 'ovs_base_dir', '/bin/ovs-ofctl', True, True ],
+ [ 'ovsdb_tool', 'ovs_base_dir', '/bin/ovsdb-tool', True, True, ],
+ [ 'ovsdb_server', 'ovs_base_dir', '/sbin/ovsdb-server', True, True, ],
+ [ 'ovs_db_file', 'ovs_base_dir', '/ovs.db', False, True, ],
+ [ 'ovs_db_sock', 'ovs_runtime_dir', '/db.sock', False, True, ],
+ [ 'ovs_kmod', 'ovs_base_dir', '/sbin/openvswitch_mod.ko', True, not gArgs.no_kmod ],
+]
+
+def __require_option(key, basedir, path, exists=True):
+ value = getattr(gArgs, key)
+ if value is None:
+ # Unspecified -- try to default based on given path
+ value = getattr(gArgs, basedir)
+
+ if value is None:
+ return False
+
+ value += path
+
+ if exists and not os.path.exists(value):
+ return False
+
+ if gArgs.verbose:
+ print '--v-- option: %s @ %s' % (key, value)
+
+ setattr(gArgs, key, value)
+
+
+Validated = True
+# Try to validate as many things as we can
+for option in gRequiredOptions:
+ if option[4]:
+ if __require_option(option[0], option[1], option[2], option[3]) == False:
+ Validated = False
+
+# If any of them failed, try to be as helpful as possible
+if Validated == False:
+ print >>sys.stdout, "\nConfiguration problem. One or more required settings are missing or could not be derived:\n"
+ for option in gRequiredOptions:
+ if option[4] is False:
+ continue
+
+ value = getattr(gArgs, option[0])
+ if value:
+ print >>sys.stdout, " %s: %s" % (option[0], value),
+ if option[3] and not os.path.exists(value):
+ print >>sys.stdout, "-- does not exist"
+ else:
+ print "(OK)"
+ else:
+ print >>sys.stdout, " %s: Unknown. " % (option[0]),
+ base = getattr(gArgs, option[1])
+ if base:
+ print >>sys.stdout, "Search path was '%s'." % (base + option[2])
+ else:
+ print >>sys.stdout, "Could not derive path because '%s' was also unspecified." % (option[1])
+ print >>sys.stdout
+ sys.exit()
+
+
+
+
+#
+# If we aren't in a dry run, you must execute as root to accompish anything
+#
+if not os.geteuid() == 0 and gArgs.dry == False:
+ print >>sys.stderr, "Must run as root to accomplish any of this."
+ sys.exit()
+
+
+###############################################################################
+#
+# Helpers
+#
+###############################################################################
+
+def createVeths(count):
+ for idx in range(0, count):
+ lcall(["/sbin/ip", "link", "add", "type", "veth"])
+
+def vethsUp(count):
+ for idx in range(0, count*2):
+ lcall(["/sbin/ifconfig", 'veth%s' % (idx), "up"])
+
+def lcall(cmd, log=gArgs.log, dry=gArgs.dry, popen=False,
+ pidFile=None):
+
+ if log or gArgs.verbose:
+ print "%s: %s" % ('popen' if popen else 'call', cmd)
+
+ if not dry:
+ if not popen:
+ subprocess.call(cmd)
+ else:
+ p = subprocess.Popen(cmd)
+ if pidFile != None:
+ pidf = open(pidFile, "w");
+ print >>pidf, p.pid
+ pidf.close()
+
+
+
+def vsctl(argsList):
+ argsList.insert(0, "--db=unix:%s" % (gArgs.ovs_db_sock))
+ argsList.insert(0, gArgs.ovs_vsctl)
+ lcall(argsList)
+
+def ofctl(argsList):
+ argsList.insert(0, gArgs.ovs_ofctl)
+ lcall(argsList)
+
+def killpid(pid):
+ try:
+ os.kill(pid, signal.SIGTERM)
+ return False
+ except OSError, e:
+ return True
+
+
+def killp(pidfile):
+ if os.path.exists(pidfile):
+ pid=int(open(pidfile).read())
+ print "Killing %s, pid=%s..." % (pidfile, pid),
+ if not gArgs.dry:
+ while not killpid(pid):
+ time.sleep(1)
+ pass
+ print "dead"
+
+
+###############################################################################
+#
+# main
+#
+###############################################################################
+
+gServerPid = gArgs.ovs_runtime_dir + "/ovsdb-server.pid"
+gSwitchPid = gArgs.ovs_runtime_dir + "/ovs-vswitchd.pid"
+gLogPid= gArgs.ovs_runtime_dir + "/ovs-vswitchd-tail.pid"
+
+# Kill previous execution based on contents of the runtime dir
+if os.path.exists(gServerPid):
+ print gServerPid
+ vsctl(["del-br", gArgs.bridge])
+
+# Kill existing DB/vswitchd
+killp(gSwitchPid)
+killp(gServerPid)
+killp(gLogPid)
+
+# Remove old logpid file, since this does not happen automagically
+if os.path.exists(gLogPid):
+ os.remove(gLogPid)
+
+if gArgs.keep_veths == False:
+ lcall(['/sbin/rmmod', 'veth'])
+ lcall(['/sbin/modprobe', 'veth'])
+
+# Remove kmod
+lcall(['/sbin/rmmod', gArgs.ovs_kmod])
+
+if gArgs.kill == True:
+ # Don't do anything else
+ sys.exit()
+
+
+# Remove bridge module
+lcall(['/sbin/rmmod', 'bridge'])
+# Insert openvswitch module
+lcall(['/sbin/insmod', gArgs.ovs_kmod])
+
+createVeths(gArgs.port_count)
+vethsUp(gArgs.port_count)
+
+if not os.path.exists(gArgs.ovs_db_file) or gArgs.keep_db == False:
+ print "Initializing DB @ %s" % (gArgs.ovs_db_file)
+ if os.path.exists(gArgs.ovs_db_file) and not gArgs.dry:
+ os.remove(gArgs.ovs_db_file)
+
+ lcall([gArgs.ovsdb_tool, "create", gArgs.ovs_db_file,
+ gArgs.ovs_vswitchd_schema])
+else:
+ print "Keeping existing DB @ %s" % (gArgs.ovs_db_file)
+
+
+if not os.path.exists(gArgs.ovs_runtime_dir):
+ os.makedirs(gArgs.ovs_runtime_dir)
+
+# Start dbserver
+lcall([gArgs.ovsdb_server, gArgs.ovs_db_file,
+ "--remote=punix:%s" % (gArgs.ovs_db_sock),
+ "--detach", "--pidfile=%s" % (gServerPid)])
+
+# Init db
+vsctl(["--no-wait", "init"])
+
+# Start vswitchd
+startV = [ gArgs.ovs_vswitchd,
+ "unix:%s" % (gArgs.ovs_db_sock),
+ "--verbose", "--detach",
+ "--pidfile=%s" % (gSwitchPid) ]
+
+if gArgs.ovs_vswitchd_log:
+ startV.append("--log-file=%s" % (gArgs.ovs_vswitchd_log))
+
+lcall(startV)
+
+if gArgs.vlog:
+ lcall(["xterm", "-T", "vswitchd-log", "-e", "tail", "-f",
+ gArgs.ovs_vswitchd_log],
+ popen=True, pidFile=gLogPid)
+
+
+# Add a bridge
+vsctl(["add-br", gArgs.bridge])
+ofctl(["show", gArgs.bridge])
+
+# Add Veths to bridge
+for idx in range(0, gArgs.port_count):
+ vsctl(["add-port", gArgs.bridge, "veth%s" % (idx*2)])
+
+
+# Set controller
+vsctl(["set-controller", gArgs.bridge, "tcp:%s:%s" % (
+ gArgs.cip, gArgs.cport)])
+
+# Minimize default backoff for controller connections
+vsctl(["set", "Controller", gArgs.bridge,
+ "max_backoff=%s" % (gArgs.max_backoff)])
+
+
+ofctl(["show", gArgs.bridge])
+
+