New utility ovs-vsctl.
authorBen Pfaff <blp@nicira.com>
Mon, 14 Sep 2009 17:05:13 +0000 (10:05 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 14 Sep 2009 17:05:13 +0000 (10:05 -0700)
Makefile.am
README
configure.ac
m4/openvswitch.m4
tests/automake.mk
tests/ovs-vsctl.at [new file with mode: 0644]
tests/testsuite.at
utilities/automake.mk
utilities/ovs-vsctl.8.in [new file with mode: 0644]
utilities/ovs-vsctl.in [new file with mode: 0755]
xenserver/vswitch-xen.spec

index 1b49456..60fd21e 100644 (file)
@@ -51,11 +51,22 @@ ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
 SUFFIXES = .in
 .in:
        $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \
-           sed -e 's,[@]LOGDIR[@],$(LOGDIR),g' \
+           sed \
                -e 's,[@]PKIDIR[@],$(PKIDIR),g' \
+                -e 's,[@]LOGDIR[@],$(LOGDIR),g' \
+                -e 's,[@]PERL[@],$(PERL),g' \
+                -e 's,[@]PYTHON[@],$(PYTHON),g' \
                 -e 's,[@]RUNDIR[@],$(RUNDIR),g' \
+                -e 's,[@]VERSION[@],$(VERSION),g' \
+                -e 's,[@]localstatedir[@],$(localstatedir),g' \
                 -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
-                -e 's,[@]PERL[@],$(PERL),g' > $@
+                -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
+            > $@.tmp
+       @if head -n 1 $@.tmp | grep -q '#!'; then \
+           echo chmod +x $@.tmp; \
+           chmod +x $@.tmp; \
+       fi
+       mv $@.tmp $@
 
 include lib/automake.mk
 include ofproto/automake.mk
diff --git a/README b/README
index 146795b..2bdbfdc 100644 (file)
--- a/README
+++ b/README
@@ -51,8 +51,11 @@ The main components of this distribution are:
       to be installed on a Citrix XenServer host as a drop-in
       replacement for its switch, with additional functionality.
 
-    * ovs-appctl, a utility that can control Open vSwitch daemons,
-      adjusting their logging levels among other uses.
+    * ovs-vsctl, a utility for querying and updating the configuration
+      of ovs-vswitchd.
+
+    * ovs-appctl, a utility that sends commands to running Open
+      vSwitch daemons.
 
 Open vSwitch also provides an OpenFlow implementation and tools for
 those interested in OpenFlow but not additional Open vSwitch features:
index d674ab4..d5b5938 100644 (file)
@@ -46,6 +46,7 @@ OVS_CHECK_LOGDIR
 OVS_CHECK_CURSES
 OVS_CHECK_LINUX_VT_H
 OVS_CHECK_PCRE
+OVS_CHECK_PYTHON
 OVS_CHECK_IF_PACKET
 OVS_CHECK_STRTOK_R
 
index d65baba..b726743 100644 (file)
@@ -233,3 +233,31 @@ AC_DEFUN([OVS_CHECK_PCRE],
    if test "$HAVE_PCRE" = yes; then
       AC_DEFINE([HAVE_PCRE], [1], [Define to 1 if libpcre is installed.])
    fi])
+
+dnl Checks for Python 2.x, x >= 4.
+AC_DEFUN([OVS_CHECK_PYTHON],
+  [AC_ARG_VAR([PYTHON], [path to Python 2.x])
+   AC_CACHE_CHECK(
+     [for Python 2.x for x >= 4],
+     [ovs_cv_python],
+     [if test -n "$PYTHON"; then
+        ovs_cv_python=$PYTHON
+      else
+        ovs_cv_python=no
+        for binary in python python2.4 python2.5; do
+          ovs_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+          for dir in $PATH; do
+            IFS=$ovs_save_IFS
+            test -z "$dir" && dir=.
+            if test -x $dir/$binary && $dir/$binary -c 'import sys
+if sys.hexversion >= 0x02040000 and sys.hexversion < 0x03000000:
+    sys.exit(0)
+else:
+    sys.exit(1)'; then
+              ovs_cv_python=$dir/$binary
+              break 2
+            fi
+          done
+        done
+      fi])
+   PYTHON=$ovs_cv_python])
index 07e56ff..7659a26 100644 (file)
@@ -9,6 +9,7 @@ TESTSUITE_AT = \
        tests/lcov-pre.at \
        tests/library.at \
        tests/stp.at \
+       tests/ovs-vsctl.at \
        tests/lcov-post.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal $(TESTSUITE)
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
new file mode 100644 (file)
index 0000000..aa3dab7
--- /dev/null
@@ -0,0 +1,257 @@
+AT_BANNER([ovs-vsctl unit tests -- real bridges])
+
+dnl RUN_OVS_VSCTL(COMMAND, ...)
+dnl
+dnl Executes each ovs-vsctl COMMAND on a file named "conf" in the
+dnl current directory.  Creates "conf" if it does not already exist.
+m4_define([RUN_OVS_VSCTL],
+  [: >> conf
+m4_foreach([command], [$@], [ovs-vsctl --no-reload --config=conf command
+])])
+
+dnl CHECK_BRIDGES(BRIDGE, ...)
+dnl
+dnl Verifies that "ovs-vsctl list-br" prints the specified list of bridges,
+dnl which must be in alphabetical order.
+m4_define([CHECK_BRIDGES],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL([list-br])],
+     [0],
+     [m4_foreach([port], [$@], [port
+])])
+   m4_foreach([port], [$@], [AT_CHECK([RUN_OVS_VSCTL([br-exists port])])])
+   AT_CHECK([RUN_OVS_VSCTL([br-exists nonexistent])], [2])])
+
+dnl CHECK_PORTS(BRIDGE, PORT[, PORT...])
+dnl
+dnl Verifies that "ovs-vsctl list-ports BRIDGE" prints the specified
+dnl list of ports, which must be in alphabetical order.  Also checks
+dnl that "ovs-vsctl port-to-br" reports that each port is
+dnl in BRIDGE.
+m4_define([CHECK_PORTS],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL([list-ports $1])],
+     [0],
+     [m4_foreach([port], m4_cdr($@), [port
+])])
+   AT_CHECK([RUN_OVS_VSCTL([port-to-br $1])], [1], [], [ovs-vsctl: no port named $1
+])
+   m4_foreach(
+     [port], m4_cdr($@), 
+     [AT_CHECK([RUN_OVS_VSCTL([[port-to-br] port])], [0], [$1
+])])])
+
+dnl CHECK_IFACES(BRIDGE, IFACE[, IFACE...])
+dnl
+dnl Verifies that "ovs-vsctl list-ifaces BRIDGE" prints the specified
+dnl list of ifaces, which must be in alphabetical order.  Also checks
+dnl that "ovs-vsctl iface-to-br" reports that each interface is
+dnl in BRIDGE.
+m4_define([CHECK_IFACES],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL([list-ifaces $1])],
+     [0],
+     [m4_foreach([iface], m4_cdr($@), [iface
+])])
+   AT_CHECK([RUN_OVS_VSCTL([iface-to-br $1])], [1], [], [ovs-vsctl: no interface named $1
+])
+   m4_foreach(
+     [iface], m4_cdr($@), 
+     [AT_CHECK([RUN_OVS_VSCTL([[iface-to-br] iface])], [0], [$1
+])])])
+
+AT_SETUP([add-br a])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL([add-br a])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.a.port=a
+])
+CHECK_BRIDGES([a])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-br b])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.a.port=a
+bridge.b.port=b
+])
+CHECK_BRIDGES([a], [b])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+CHECK_PORTS([b])
+CHECK_IFACES([b])
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-br b, del-br a])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b], [del-br a])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.b.port=b
+])
+CHECK_BRIDGES([b])
+CHECK_PORTS([b])
+CHECK_IFACES([b])
+AT_CLEANUP
+
+AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-br a])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL(
+   [add-br a], 
+   [add-br b], 
+   [add-port a a1],
+   [add-port b b1],
+   [del-br a])])
+AT_CHECK([cat conf], [0],
+  [bridge.b.port=b
+bridge.b.port=b1
+])
+CHECK_BRIDGES([b])
+CHECK_PORTS([b], [b1])
+CHECK_IFACES([b], [b1])
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL(
+   [add-br a], 
+   [add-bond a bond0 a1 a2 a3])])
+AT_CHECK([cat conf], [0], [dnl
+bonding.bond0.slave=a1
+bonding.bond0.slave=a2
+bonding.bond0.slave=a3
+bridge.a.port=a
+bridge.a.port=bond0
+])
+CHECK_BRIDGES([a])
+CHECK_PORTS([a], [bond0])
+CHECK_IFACES([a], [a1], [a2], [a3])
+AT_CLEANUP
+
+AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-port a a1])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL(
+  [add-br a], 
+  [add-br b], 
+  [add-port a a1],
+  [add-port b b1],
+  [del-port a a1])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.a.port=a
+bridge.b.port=b
+bridge.b.port=b1
+])
+CHECK_BRIDGES([a], [b])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+CHECK_PORTS([b], [b1])
+CHECK_IFACES([b], [b1])
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3, del-port a bond0])
+AT_KEYWORDS([ovs-vsctl])
+AT_CHECK([RUN_OVS_VSCTL(
+  [add-br a], 
+  [add-bond a bond0 a1 a2 a3],
+  [del-port a bond0])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.a.port=a
+])
+CHECK_BRIDGES([a])
+CHECK_PORTS([a])
+AT_CLEANUP
+
+AT_BANNER([ovs-vsctl unit tests -- fake bridges])
+
+m4_define([SIMPLE_FAKE_CONF], [dnl
+bridge.xenbr0.port=eth0
+bridge.xenbr0.port=eth0.9
+bridge.xenbr0.port=xapi1
+bridge.xenbr0.port=xenbr0
+iface.xapi1.fake-bridge=true
+iface.xapi1.internal=true
+vlan.eth0.9.tag=9
+vlan.xapi1.tag=9
+])
+
+AT_SETUP([simple fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_CHECK([RUN_OVS_VSCTL(
+  [add-br xenbr0],
+  [add-port xenbr0 eth0],
+  [add-br xapi1 xenbr0 9],
+  [add-port xapi1 eth0.9])])
+AT_CHECK([cat conf], [0], [SIMPLE_FAKE_CONF])
+CHECK_BRIDGES([xenbr0], [xapi1])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+CHECK_PORTS([xapi1], [eth0.9])
+CHECK_IFACES([xapi1], [eth0.9])
+AT_CLEANUP
+
+AT_SETUP([simple fake bridge + del-br fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_DATA([conf], [SIMPLE_FAKE_CONF])
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])])
+AT_CHECK([cat conf], [0], [dnl
+bridge.xenbr0.port=eth0
+bridge.xenbr0.port=xenbr0
+])
+CHECK_BRIDGES([xenbr0])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+AT_CLEANUP
+
+AT_SETUP([simple fake bridge + del-br real bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_DATA([conf], [SIMPLE_FAKE_CONF])
+AT_CHECK([RUN_OVS_VSCTL([del-br xenbr0])])
+AT_CHECK([cat conf], [0], [])
+CHECK_BRIDGES
+AT_CLEANUP
+
+m4_define([BOND_FAKE_CONF], [dnl
+bonding.bond0.slave=eth0
+bonding.bond0.slave=eth1
+bridge.xapi1.port=bond0
+bridge.xapi1.port=bond0.11
+bridge.xapi1.port=xapi1
+bridge.xapi1.port=xapi2
+iface.xapi2.fake-bridge=true
+iface.xapi2.internal=true
+vlan.bond0.11.tag=11
+vlan.xapi2.tag=11
+])
+
+AT_SETUP([fake bridge on bond])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_CHECK([RUN_OVS_VSCTL(
+  [add-br xapi1],
+  [add-bond xapi1 bond0 eth0 eth1],
+  [add-br xapi2 xapi1 11],
+  [add-port xapi2 bond0.11])])
+AT_CHECK([cat conf], [0], [BOND_FAKE_CONF])
+CHECK_BRIDGES([xapi1], [xapi2])
+CHECK_PORTS([xapi1], [bond0])
+CHECK_IFACES([xapi1], [eth0], [eth1])
+CHECK_PORTS([xapi2], [bond0.11])
+CHECK_IFACES([xapi2], [bond0.11])
+AT_CLEANUP
+
+AT_SETUP([fake bridge on bond + del-br fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_DATA([conf], [BOND_FAKE_CONF])
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi2])])
+CHECK_BRIDGES([xapi1])
+CHECK_PORTS([xapi1], [bond0])
+CHECK_IFACES([xapi1], [eth0], [eth1])
+AT_CLEANUP
+
+AT_SETUP([fake bridge on bond + del-br real bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+AT_DATA([conf], [BOND_FAKE_CONF])
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])])
+CHECK_BRIDGES
+AT_CLEANUP
index f894c2a..c232a87 100644 (file)
@@ -15,8 +15,10 @@ See the License for the specific language governing permissions and
 limitations under the License.])
 
 AT_TESTED([ovs-vswitchd])
+AT_TESTED([ovs-vsctl])
 
 m4_include([tests/lcov-pre.at])
 m4_include([tests/library.at])
 m4_include([tests/stp.at])
+m4_include([tests/ovs-vsctl.at])
 m4_include([tests/lcov-post.at])
index 5bf3cbb..9ac12c9 100644 (file)
@@ -9,7 +9,7 @@ bin_PROGRAMS += \
        utilities/ovs-openflowd \
        utilities/ovs-wdt
 noinst_PROGRAMS += utilities/nlmon
-bin_SCRIPTS += utilities/ovs-pki
+bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl
 noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
 dist_sbin_SCRIPTS += utilities/ovs-monitor 
 
@@ -25,7 +25,9 @@ EXTRA_DIST += \
        utilities/ovs-parse-leaks.in \
        utilities/ovs-pki-cgi.in \
        utilities/ovs-pki.8.in \
-       utilities/ovs-pki.in
+       utilities/ovs-pki.in \
+       utilities/ovs-vsctl.8.in \
+       utilities/ovs-vsctl.in
 DISTCLEANFILES += \
        utilities/ovs-appctl.8 \
        utilities/ovs-cfg-mod.8 \
@@ -37,8 +39,10 @@ DISTCLEANFILES += \
        utilities/ovs-openflowd.8 \
        utilities/ovs-parse-leaks \
        utilities/ovs-pki \
+       utilities/ovs-pki-cgi \
        utilities/ovs-pki.8 \
-       utilities/ovs-pki-cgi
+       utilities/ovs-vsctl \
+       utilities/ovs-vsctl.8
 
 man_MANS += \
        utilities/ovs-appctl.8 \
@@ -49,7 +53,8 @@ man_MANS += \
        utilities/ovs-kill.8 \
        utilities/ovs-ofctl.8 \
        utilities/ovs-openflowd.8 \
-       utilities/ovs-pki.8
+       utilities/ovs-pki.8 \
+       utilities/ovs-vsctl.8
 
 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c
 utilities_ovs_appctl_LDADD = lib/libopenvswitch.a
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
new file mode 100644 (file)
index 0000000..de78a64
--- /dev/null
@@ -0,0 +1,180 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.TH ovs\-vsctl 8 "September 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-vsctl
+.
+.SH NAME
+ovs\-vsctl \- utility for querying and configuring \fBovs\-vswitchd\fR
+.
+.SH SYNOPSIS
+\fBovs\-vsctl\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR\&...]
+.
+.SH DESCRIPTION
+The \fBovs\-vsctl\fR program configures \fBovs\-vswitchd\fR(8), mainly
+by providing a high\-level interface to editing its configuration file
+\fBovs\-vswitchd.conf\fR(5).  This program is mainly intended for use
+when \fBovs\-vswitchd\fR is running, but it can also be used when
+\fBovs\-vswitchd\fR is not running.  In the latter case configuration
+changes will only take effect when \fBovs\-vswitchd\fR is started.
+.PP
+By default, each time \fBovs\-vsctl\fR runs, it examines and,
+depending on the requested command, possibly applies changes to an
+\fBovs\-vswitchd.conf\fR file.  Then, if it applied any changes and if
+\fBovs\-vswitchd\fR is running, it tells \fBovs\-vswitchd\fR to reload
+the modified configuration file and waits for the reload to complete
+before exiting.
+.
+.SS "Linux VLAN Bridging Compatibility"
+The \fBovs\-vsctl\fR program supports the model of a bridge
+implemented by Open vSwitch, in which a single bridge supports ports
+on multiple VLANs.  In this model, each port on a bridge is either a
+trunk port that potentially passes packets tagged with 802.1Q headers
+that designate VLANs or it is assigned a single implicit VLAN that is
+never tagged with an 802.1Q header.
+.PP
+For compatibility with software designed for the Linux bridge,
+\fBovs\-vsctl\fR also supports a model in which traffic associated
+with a given 802.1Q VLAN is segregated into a separate bridge.  A
+special form of the \fBadd\-br\fR command (see below) creates a ``fake
+bridge'' within an Open vSwitch bridge to simulate this behavior.
+When such a ``fake bridge'' is active, \fBovs\-vsctl\fR will treat it
+much like a bridge separate from its ``parent bridge,'' but the actual
+implementation in Open vSwitch uses only a single bridge, with ports on
+the fake bridge assigned the implicit VLAN of the fake bridge of which
+they are members.
+.
+.SH OPTIONS
+.
+The following options affect the general outline of \fBovs\-vsctl\fR's
+activities:
+.
+.IP "\fB\-c \fIfile\fR"
+.IQ "\fB\-\-config=\fIfile\fR"
+Sets the configuration file that \fBovs\-vsctl\fR reads and possibly
+modifies.  The default is \fB@localstatedir@/ovs\-vswitchd.conf\fR.
+.IP
+If \fIfile\fR is specified as \fB\-\fR, then \fBovs\-vsctl\fR reads
+the configuration file from standard input and, for commands that
+modify the configuration, writes the new one to standard output.  This
+is useful for testing but it should not be used in production because
+it bypasses the Open vSwitch configuration file locking protocol.
+.
+.IP "\fB\-t \fItarget\fR"
+.IQ "\fB\-\-target=\fItarget\fR"
+Configures how \fBovs\-vsctl\fR contacts \fBovs\-vswitchd\fR to
+instruct it to reload its configuration file.  The \fItarget\fR takes
+one of two forms:
+.RS
+.IP \(bu
+The name of a Unix domain socket on which \fBovs\-vswitchd\fR is
+listening for control channel connections.  By default,
+\fBovs\-vswitchd\fR listens on a Unix domain socket named
+\fB@RUNDIR@/ovs\-vswitchd.\fIpid\fR.ctl\fR, where \fIpid\fR is the
+\fBovs\-vswitchd\fR process's process ID.
+.IP \(bu
+The name of a pidfile, that is, a file whose contents are the process
+ID of a running process as a decimal number.  \fBovs\-vswitchd\fR
+creates a pidfile if it is invoked with the \fB\-\-pidfile\fR option.
+\fBovs\-vsctl\fR reads the pidfile, then looks for a Unix socket named
+\fB@RUNDIR@/ovs\-vswitchd.\fIpid\fR.ctl\fR, where \fIpid\fR is
+replaced by the process ID read from \fItarget\fR, and uses that file
+as if it had been specified directly as the target.
+.RE
+.IP
+If \fItarget\fR does not begin with \fB/\fR, then \fB@RUNDIR@/\fR is
+implicitly prefixed to it.
+.IP
+If neither \fB\-t\fR nor \fB\-\-target\fR is specified, the default target is
+\fB@RUNDIR@/ovs\-vswitchd.pid\fR.
+.IP "\fB\-\-no\-reload\fR"
+Prevents \fBovs\-vsctl\fR from telling \fBovs\-vswitchd\fR to reload
+its configuration file.
+.
+.SH COMMANDS
+The commands implemented by \fBovs\-vsctl\fR are described in the
+sections below.
+.
+.SS "Bridge Commands"
+These commands examine and manipulate Open vSwitch bridges.
+.
+.IP "\fBadd\-br \fIbridge\fR"
+Creates a new bridge named \fIbridge\fR.  Initially the bridge will
+have no ports (other than \fIbridge\fR itself).
+.
+.IP "\fBadd\-br \fIbridge parent vlan\fR"
+Creates a ``fake bridge'' named \fIbridge\fR within the existing Open
+vSwitch bridge \fIparent\fR, which must already exist and must not
+itself be a fake bridge.  The new fake bridge will be on 802.1Q VLAN
+\fIvlan\fR, which must be an integer between 1 and 4095.  Initially
+\fIbridge\fR will have no ports (other than \fIbridge\fR itself).
+.
+.IP "\fBdel\-br \fIbridge\fR"
+Deletes \fIbridge\fR and all of its ports.  If \fIbridge\fR is a real
+bridge, this command also deletes any fake bridges that were created
+with \fIbridge\fR as parent, including all of their ports.
+.
+.IP "\fBlist\-br\fR"
+Lists all existing real and fake bridges on standard output, one per
+line.
+.
+.IP "\fBbr\-exists \fIbridge\fR"
+Tests whether \fIbridge\fR exists as a real or fake bridge.  If so,
+\fBovs\-vsctl\fR exits successfully with exit code 0.  If not,
+\fBovs\-vsctl\fR exits unsuccessfully with exit code 2.
+.
+.SS "Port Commands"
+.
+These commands examine and manipulate Open vSwitch ports.  These
+commands treat a bonded port as a single entity.
+.
+.IP "\fBlist\-ports \fIbridge\fR"
+Lists all of the ports within \fIbridge\fR on standard output, one per
+line.  The local port \fIbridge\fR is not included in the list.
+.
+.IP "\fBadd\-port \fIbridge port\fR"
+Creates on \fIbridge\fR a new port named \fIport\fR from the network
+device of the same name.
+.
+.IP "\fBadd\-bond \fIbridge port iface\fR\&..."
+Creates on \fIbridge\fR a new port named \fIport\fR that bonds
+together the network devices given as each \fIiface\fR.  At least two
+interfaces must be named.
+.
+.IP "\fBdel\-port \fIbridge port\fR"
+Deletes \fBport\fR from \fIbridge\fR.
+.
+.IP "\fBport\-to\-br \fIport\fR"
+Prints the name of the bridge that contains \fIport\fR on standard
+output.
+.
+.SS "Interface Commands"
+.
+These commands examine the interfaces attached to an Open vSwitch
+bridge.  These commands treat a bonded port as a collection of two or
+more interfaces, rather than as a single port.
+.
+.IP "\fBlist\-ifaces \fIbridge\fR"
+Lists all of the interfaces within \fIbridge\fR on standard output,
+one per line.  The local port \fIbridge\fR is not included in the
+list.
+.
+.IP "\fBiface\-to\-br \fIiface\fR"
+Prints the name of the bridge that contains \fIiface\fR on standard
+output.
+.
+.SH "EXIT STATUS"
+.IP "0"
+Successful program execution.
+.IP "1"
+Usage, syntax, or configuration file error.
+.IP "2"
+The \fIbridge\fR argument to \fBbr\-exists\fR specified the name of a
+bridge that does not exist.
+.SH "SEE ALSO"
+.
+.BR ovs\-vswitchd.conf (5),
+.BR ovs\-vswitchd (8).
diff --git a/utilities/ovs-vsctl.in b/utilities/ovs-vsctl.in
new file mode 100755 (executable)
index 0000000..ce3f8d3
--- /dev/null
@@ -0,0 +1,503 @@
+#! @PYTHON@
+# Copyright (c) 2009 Nicira Networks.                       -*- python -*-
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import errno
+import fcntl
+import fnmatch
+import getopt
+import os
+import re
+import stat
+import sys
+
+argv0 = sys.argv[0]
+if argv0.find('/') >= 0:
+    argv0 = argv0[argv0.rfind('/') + 1:]
+
+DEFAULT_VSWITCHD_CONF = "@sysconfdir@/ovs-vswitchd.conf"
+VSWITCHD_CONF = DEFAULT_VSWITCHD_CONF
+
+DEFAULT_VSWITCHD_TARGET = "@RUNDIR@/ovs-vswitchd.pid"
+VSWITCHD_TARGET = DEFAULT_VSWITCHD_TARGET
+
+RELOAD_VSWITCHD = True
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+# XXX Most of the functions below should be integrated into a
+# VSwitchConfiguration object with logically named fields and methods
+# instead of this mishmash of functionality.
+
+# Locks 'filename' for writing.
+def cfg_lock(filename):
+    if filename == '-':
+        return
+
+    if '/' in filename:
+        lastSlash = filename.rfind('/')
+        prefix = filename[:lastSlash]
+        suffix = filename[lastSlash + 1:]
+        lock_name = "%s/.%s.~lock~" % (prefix, suffix)
+    else:
+        lock_name = ".%s.~lock~" % filename
+
+    while True:
+        # Try to open an existing lock file.
+        try:
+            f = open(lock_name, 'r')
+        except IOError, e:
+            if e.errno != errno.ENOENT:
+                raise
+
+            # Try to create a new lock file.
+            try:
+                fd = os.open(lock_name, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0600)
+            except OSError, e:
+                if e.errno != errno.EEXIST:
+                    raise
+                # Someone else created the lock file, try again.
+            os.close(fd)
+            continue
+    
+        fcntl.flock(f, fcntl.LOCK_EX)
+        return
+
+# Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
+# dictionary that maps from string keys to lists of string values.  (Even
+# singleton values are represented as lists.)
+def cfg_read(filename, lock=False):
+    if lock:
+        cfg_lock(filename)
+
+    try:
+        if filename == '-':
+            f = open('/dev/stdin')
+        else:
+            f = open(filename)
+    except IOError, e:
+        sys.stderr.write("%s: could not open %s (%s)\n"
+                         % (argv0, filename, e.strerror))
+        sys.exit(1)
+
+    cfg = {}
+    rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
+    for line in f:
+        line = line.strip()
+        if len(line) == 0 or line[0] == '#':
+            continue
+
+        match = rx.match(line)
+        if match == None:
+            continue
+
+        key, value = match.groups()
+        if key not in cfg:
+            cfg[key] = []
+        cfg[key].append(value)
+    return cfg
+
+def do_cfg_save(cfg, file):
+    for key in sorted(cfg.keys()):
+        for value in sorted(cfg[key]):
+            file.write("%s=%s\n" % (key, value))
+
+def cfg_reload():
+    target = VSWITCHD_TARGET
+    s = os.stat(target)
+    if stat.S_ISREG(s.st_mode):
+        pid = read_first_line_of_file(target)
+        target = "@RUNDIR@/ovs-vswitchd.%s.ctl" % pid
+        s = os.stat(target)
+    if not stat.S_ISSOCK(s.st_mode):
+        raise Error("%s is not a Unix domain socket, cannot reload" % target)
+    f = open(target, "r+")
+    f.write("vswitchd/reload\n")
+    f.flush()
+    f.readline()
+    f.close()
+
+def cfg_save(cfg, filename):
+    if filename == '-':
+        do_cfg_save(cfg, sys.stdout)
+    else:
+        tmp_name = filename + ".~tmp~"
+        f = open(tmp_name, 'w')
+        do_cfg_save(cfg, f)
+        f.close()
+        os.rename(tmp_name, filename)
+        if RELOAD_VSWITCHD:
+            cfg_reload()
+
+# Returns a set of the immediate subsections of 'section' within 'cfg'.  For
+# example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
+# and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
+def cfg_get_subsections(cfg, section):
+    subsections = set()
+    for key in cfg:
+        if key.startswith(section + "."):
+            dot = key.find(".", len(section) + 1)
+            if dot == -1:
+                dot = len(key)
+            subsections.add(key[len(section) + 1:dot])
+    return subsections
+
+# Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
+# returns False.
+def cfg_get_bool(cfg, name):
+    return name in cfg and cfg[name] == ['true']
+
+# If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
+# that VLAN number.  Otherwise, returns 0.
+def get_port_vlan(cfg, port):
+    try:
+        return int(cfg["vlan.%s.tag" % port][0])
+    except (ValueError, KeyError):
+        return 0
+
+# Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
+# the ports returned are only those configured with implicit VLAN 'vlan'.
+def get_bridge_ports(cfg, bridge, vlan):
+    ports = []
+    for port in cfg["bridge.%s.port" % bridge]:
+        if vlan < 0 or get_port_vlan(cfg, port) == vlan:
+            ports.append(port)
+    return ports
+
+# Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
+# nonnegative, the interfaces returned are only those whose ports are
+# configured with implicit VLAN 'vlan'.
+def get_bridge_ifaces(cfg, bridge, vlan):
+    ifaces = []
+    for port in get_bridge_ports(cfg, bridge, vlan):
+        ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
+    return ifaces
+
+# Returns the first line of the file named 'name', with the trailing new-line
+# (if any) stripped off.
+def read_first_line_of_file(name):
+    file = None
+    try:
+        file = open(name, 'r')
+        return file.readline().rstrip('\n')
+    finally:
+        if file != None:
+            file.close()
+
+# Returns a bridge ID constructed from the MAC address of network device
+# 'netdev', in the format "8000.000102030405".
+def get_bridge_id(netdev):
+    try:
+        hwaddr = read_first_line_of_file("/sys/class/net/%s/address" % netdev)
+        return "8000.%s" % (hwaddr.replace(":", ""))
+    except:
+        return "8000.002320ffffff"
+
+# Returns a list of 3-tuples based on 'cfg'.  Each 3-tuple represents
+# one real bridge or one fake bridge and has the form (bridge, parent,
+# vlan), where 'bridge' is the real or fake bridge name, 'parent' is
+# the same as 'bridge' for a real bridge or the name of the containing
+# bridge for a fake bridge, and 'vlan' is 0 for a real bridge or a
+# VLAN number for a fake bridge.
+def get_bridge_info(cfg):
+    real_bridges = [(br, br, 0) for br in get_real_bridges(cfg)]
+    fake_bridges = []
+    for linux_bridge, ovs_bridge, vlan in real_bridges:
+        for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
+            if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
+                fake_bridges.append((iface, ovs_bridge,
+                                     get_port_vlan(cfg, iface)))
+    return real_bridges + fake_bridges
+
+# Returns the real bridges configured in 'cfg'.
+def get_real_bridges(cfg):
+    return cfg_get_subsections(cfg, "bridge")
+
+# Returns the fake bridges configured in 'cfg'.
+def get_fake_bridges(cfg):
+    return [bridge for bridge, parent, vlan in get_bridge_info(cfg)
+            if bridge != parent]
+
+# Returns all the real and fake bridges configured in 'cfg'.
+def get_all_bridges(cfg):
+    return [bridge for bridge, parent, vlan in get_bridge_info(cfg)]
+
+# Returns the parent bridge and VLAN of real or fake 'bridge' in
+# 'cfg', where the parent bridge and VLAN are as defined in the
+# description of get_bridge_info().  Raises an error if no bridge
+# named 'bridge' exists in 'cfg'.
+def find_bridge(cfg, bridge):
+    for br, parent, vlan in get_bridge_info(cfg):
+        if br == bridge:
+            return parent, vlan
+    raise Error("no bridge named %s" % bridge)
+
+def del_matching_keys(cfg, pattern):
+    for key in [key for key in cfg.keys() if fnmatch.fnmatch(key, pattern)]:
+        del cfg[key]
+
+# Deletes anything related to a port named 'port' from 'cfg'.  No port
+# named 'port' need actually exist; this function will clean up
+# regardless.
+def del_port(cfg, port):
+    # The use of [!0-9] keeps an interface of 'eth0' from matching
+    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
+    # interfaces.
+    for iface in cfg.get('bonding.%s.slave' % port, [port]):
+        del_matching_keys(cfg, 'iface.%s.[!0-9]*' % iface)
+        # Yes, this "port" setting applies to interfaces, not ports, *sigh*.
+        del_matching_keys(cfg, 'port.%s.ingress-policing*' % iface)
+    del_matching_keys(cfg, 'bonding.%s.[!0-9]*' % port)
+    del_matching_keys(cfg, 'vlan.%s.[!0-9]*' % port)
+    for key in cfg.keys():
+        if fnmatch.fnmatch(key, 'bridge.*.port'):
+            cfg[key] = [s for s in cfg[key] if s != port]
+
+def usage():
+    print """%(argv0)s: ovs-vswitchd management utility
+usage: %(argv0)s [OPTIONS] COMMAND [ARG...]
+
+Bridge commands:
+  add-br BRIDGE               create a new bridge named BRIDGE
+  add-br BRIDGE PARENT VLAN   create new fake bridge BRIDGE in PARENT on VLAN
+  del-br BRIDGE               delete BRIDGE and all of its ports
+  list-br                     print the names of all the bridges
+  br-exists BRIDGE            test whether BRIDGE exists
+
+Port commands:
+  list-ports BRIDGE           print the names of all the ports on BRIDGE
+  add-port BRIDGE PORT        add network device PORT to BRIDGE
+  add-bond BRIDGE PORT IFACE...  add new bonded port PORT in BRIDGE from IFACES
+  del-port BRIDGE PORT        delete PORT (which may be bonded) from BRIDGE
+  port-to-br PORT             print name of bridge that contains PORT
+A bond is considered to be a single port.
+
+Interface commands (a bond consists of multiple interfaces):
+  list-ifaces BRIDGE          print the names of all the interfaces on BRIDGE
+  iface-to-br IFACE           print name of bridge that contains IFACE
+A bond is considered to consist of interfaces.
+
+General options:
+  -c, --config=FILE           set configuration file
+                              (default: %(config)s)
+  -t, --target=PIDFILE|SOCKET set ovs-vswitchd target
+                              (default: %(target)s)
+  --no-reload                 do not make ovs-vswitchd reload its configuration
+  -h, --help                  display this help message and exit
+  -V, --version               display version information and exit
+Report bugs to bugs@openvswitch.org.""" % {'argv0': argv0,
+                                           'config': DEFAULT_VSWITCHD_CONF,
+                                           'target': DEFAULT_VSWITCHD_TARGET}
+    sys.exit(0)
+
+def version():
+    print "ovs-vsctl (Open vSwitch) @VERSION@"
+    sys.exit(0)
+
+def check_conflicts(cfg, name, op):
+    bridges = get_bridge_info(cfg)
+    if name in [bridge for bridge, parent, vlan in bridges]:
+        raise Error("%s because a bridge named %s already exists" % (op, name))
+
+    for bridge, parent, vlan in bridges:
+        if name in get_bridge_ports(cfg, parent, vlan):
+            raise Error("%s because a port named %s already exists on bridge %s" % (op, name, bridge))
+        if name in get_bridge_ifaces(cfg, parent, vlan):
+            raise Error("%s because an interface named %s already exists on bridge %s" % (op, name, bridge))
+    
+def cmd_add_br(bridge, parent=None, vlan=None):
+    cfg = cfg_read(VSWITCHD_CONF, True)
+
+    check_conflicts(cfg, bridge, "cannot create a bridge named %s" % bridge)
+    
+    if parent and vlan:
+        if parent in get_fake_bridges(cfg):
+            raise Error("cannot create bridge with fake bridge as parent")
+        if parent not in get_real_bridges(cfg):
+            raise Error("parent bridge %s does not exist" % bridge)
+        try:
+            if int(vlan) < 0 or int(vlan) > 4095:
+                raise ValueError
+        except ValueError:
+            raise Error("invalid VLAN number %s" % vlan)
+
+        # Create fake bridge internal port.
+        cfg['iface.%s.internal' % bridge] = ['true']
+        cfg['iface.%s.fake-bridge' % bridge] = ['true']
+        cfg['vlan.%s.tag' % bridge] = [vlan]
+
+        # Add fake bridge port to parent.
+        cfg['bridge.%s.port' % parent].append(bridge)
+    else:
+        cfg['bridge.%s.port' % bridge] = [bridge]
+    cfg_save(cfg, VSWITCHD_CONF)
+
+def cmd_del_br(bridge):
+    cfg = cfg_read(VSWITCHD_CONF, True)
+    parent, vlan = find_bridge(cfg, bridge)
+    if vlan == 0:
+        vlan = -1
+    for port in set(get_bridge_ports(cfg, parent, vlan) + [bridge]):
+        del_port(cfg, port)
+    if vlan < 0: 
+        del_matching_keys(cfg, 'bridge.%s.[!0-9]*' % bridge)
+    cfg_save(cfg, VSWITCHD_CONF)
+
+def cmd_list_br():
+    cfg = cfg_read(VSWITCHD_CONF)
+    for bridge in get_all_bridges(cfg):
+        print bridge
+
+def cmd_br_exists(bridge):
+    cfg = cfg_read(VSWITCHD_CONF)
+    if bridge not in get_all_bridges(cfg):
+        sys.exit(2)
+
+def cmd_list_ports(bridge):
+    cfg = cfg_read(VSWITCHD_CONF)
+    parent, vlan = find_bridge(cfg, bridge)
+    for port in get_bridge_ports(cfg, parent, vlan):
+        if port != bridge:
+            print port
+
+def do_add_port(cfg, bridge, parent, port, vlan):
+    check_conflicts(cfg, port, "cannot create a port named %s" % port)
+    cfg['bridge.%s.port' % parent].append(port)
+    if vlan > 0:
+        cfg['vlan.%s.tag' % port] = [vlan]
+
+def cmd_add_port(bridge, port):
+    cfg = cfg_read(VSWITCHD_CONF, True)
+    parent, vlan = find_bridge(cfg, bridge)
+    do_add_port(cfg, bridge, parent, port, vlan)
+    cfg_save(cfg, VSWITCHD_CONF)
+
+def cmd_add_bond(bridge, port, *slaves):
+    cfg = cfg_read(VSWITCHD_CONF, True)
+    parent, vlan = find_bridge(cfg, bridge)
+    do_add_port(cfg, bridge, parent, port, vlan)
+    cfg['bonding.%s.slave' % port] = list(slaves)
+    cfg_save(cfg, VSWITCHD_CONF)
+
+def cmd_del_port(bridge, port):
+    cfg = cfg_read(VSWITCHD_CONF, True)
+    parent, vlan = find_bridge(cfg, bridge)
+    if port not in get_bridge_ports(cfg, parent, vlan):
+        if port in get_bridge_ports(cfg, parent, -1):
+            raise Error("bridge %s does not have a port %s (although its parent bridge %s does)" % (bridge, port, parent))
+        else:
+            raise Error("bridge %s does not have a port %s" % (bridge, port))
+    del_port(cfg, port)
+    cfg_save(cfg, VSWITCHD_CONF)
+
+def cmd_port_to_br(port):
+    cfg = cfg_read(VSWITCHD_CONF)
+    for bridge, parent, vlan in get_bridge_info(cfg):
+        if port != bridge and port in get_bridge_ports(cfg, parent, vlan):
+            print bridge
+            return
+    raise Error("no port named %s" % port)
+
+def cmd_list_ifaces(bridge):
+    cfg = cfg_read(VSWITCHD_CONF)
+    parent, vlan = find_bridge(cfg, bridge)
+    for iface in get_bridge_ifaces(cfg, parent, vlan):
+        if iface != bridge:
+            print iface
+
+def cmd_iface_to_br(iface):
+    cfg = cfg_read(VSWITCHD_CONF)
+    for bridge, parent, vlan in get_bridge_info(cfg):
+        if iface != bridge and iface in get_bridge_ifaces(cfg, parent, vlan):
+            print bridge
+            return
+    raise Error("no interface named %s" % iface)
+
+def main():
+    # Parse command line.
+    try:
+        options, args = getopt.gnu_getopt(sys.argv[1:], "c:t:hV",
+                                          ["config=",
+                                           "target=",
+                                           "no-reload",
+                                           "help",
+                                           "version"])
+    except getopt.GetoptError, msg:
+        sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
+        sys.exit(1)
+
+    # Handle options.
+    for opt, optarg in options:
+        if opt == "-c" or opt == "--config":
+            global VSWITCHD_CONF
+            VSWITCHD_CONF = optarg
+        elif opt == "-t" or opt == "--target":
+            global VSWITCHD_TARGET
+            if optarg[0] != '/':
+                optarg = '@RUNDIR@/' + optarg
+            VSWITCHD_TARGET = optarg
+        elif opt == "--no-reload":
+            global RELOAD_VSWITCHD
+            RELOAD_VSWITCHD = False
+        elif opt == "-h" or opt == "--help":
+            usage()
+        elif opt == "-V" or opt == "--version":
+            version()
+        else:
+            raise RuntimeError("unhandled option %s" % opt)
+
+    # Execute commands.
+    if not args:
+        sys.stderr.write("%s: missing command name (use --help for help)\n"
+                         % argv0)
+        sys.exit(1)
+
+    commands = {'add-br': (cmd_add_br, lambda n: n == 1 or n == 3),
+                'del-br': (cmd_del_br, 1),
+                'list-br': (cmd_list_br, 0),
+                'br-exists': (cmd_br_exists, 1),
+                'list-ports': (cmd_list_ports, 1),
+                'add-port': (cmd_add_port, 2),
+                'add-bond': (cmd_add_bond, lambda n: n >= 4),
+                'del-port': (cmd_del_port, 2),
+                'port-to-br': (cmd_port_to_br, 1),
+                'list-ifaces': (cmd_list_ifaces, 1),
+                'iface-to-br': (cmd_iface_to_br, 1)}
+    command = args[0]
+    args = args[1:]
+    if command not in commands:
+        sys.stderr.write("%s: unknown command '%s' (use --help for help)\n"
+                         % (argv0, command))
+        sys.exit(1)
+
+    function, nargs = commands[command]
+    if callable(nargs) and not nargs(len(args)):
+        sys.stderr.write("%s: '%s' command does not accept %d arguments (use --help for help)\n" % (argv0, command, len(args)))
+        sys.exit(1)
+    elif not callable(nargs) and len(args) != nargs:
+        sys.stderr.write("%s: '%s' command takes %d arguments but %d were supplied (use --help for help)\n" % (argv0, command, nargs, len(args)))
+        sys.exit(1)
+    else:
+        function(*args)
+        sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        main()
+    except Error, msg:
+        sys.stderr.write("%s: %s\n" % (argv0, msg.msg))
+        sys.exit(1)
index 3132bb7..79acd20 100644 (file)
@@ -322,11 +322,13 @@ fi
 /usr/bin/ovs-cfg-mod
 /usr/bin/ovs-dpctl
 /usr/bin/ovs-ofctl
+/usr/bin/ovs-vsctl
 /usr/share/man/man5/ovs-vswitchd.conf.5.gz
 /usr/share/man/man8/ovs-appctl.8.gz
 /usr/share/man/man8/ovs-brcompatd.8.gz
 /usr/share/man/man8/ovs-cfg-mod.8.gz
 /usr/share/man/man8/ovs-dpctl.8.gz
 /usr/share/man/man8/ovs-ofctl.8.gz
+/usr/share/man/man8/ovs-vsctl.8.gz
 /usr/share/man/man8/ovs-vswitchd.8.gz
 /var/lib/openvswitch