[ovs-dev] [PATCH 1/2] Add documentation for the vswitch database schema.

Ben Pfaff blp at nicira.com
Wed Mar 3 23:04:23 UTC 2010


We can do better than this (I already have some comments) but this is
still much better than what we had.
---
 ovsdb/OVSDB.py           |  495 +++++++++++++++++++++++++++++++
 ovsdb/automake.mk        |   13 +-
 ovsdb/ovsdb-doc.in       |  329 +++++++++++++++++++++
 ovsdb/ovsdb-idlc.in      |  385 +------------------------
 utilities/ovs-vsctl.8.in |    6 +-
 vswitchd/.gitignore      |    3 +-
 vswitchd/automake.mk     |   15 +-
 vswitchd/vswitch.xml     |  726 ++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1579 insertions(+), 393 deletions(-)
 create mode 100644 ovsdb/OVSDB.py
 create mode 100755 ovsdb/ovsdb-doc.in
 create mode 100644 vswitchd/vswitch.xml

diff --git a/ovsdb/OVSDB.py b/ovsdb/OVSDB.py
new file mode 100644
index 0000000..f40af1e
--- /dev/null
+++ b/ovsdb/OVSDB.py
@@ -0,0 +1,495 @@
+import re
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def getMember(json, name, validTypes, description, default=None):
+    if name in json:
+        member = json[name]
+        if len(validTypes) and type(member) not in validTypes:
+            raise Error("%s: type mismatch for '%s' member"
+                        % (description, name))
+        return member
+    return default
+
+def mustGetMember(json, name, expectedType, description):
+    member = getMember(json, name, expectedType, description)
+    if member == None:
+        raise Error("%s: missing '%s' member" % (description, name))
+    return member
+
+class DbSchema:
+    def __init__(self, name, comment, tables):
+        self.name = name
+        self.comment = comment
+        self.tables = tables
+
+    @staticmethod
+    def fromJson(json):
+        name = mustGetMember(json, 'name', [unicode], 'database')
+        comment = getMember(json, 'comment', [unicode], 'database')
+        tablesJson = mustGetMember(json, 'tables', [dict], 'database')
+        tables = {}
+        for tableName, tableJson in tablesJson.iteritems():
+            tables[tableName] = TableSchema.fromJson(tableJson,
+                                                     "%s table" % tableName)
+        return DbSchema(name, comment, tables)
+
+class IdlSchema(DbSchema):
+    def __init__(self, name, comment, tables, idlPrefix, idlHeader):
+        DbSchema.__init__(self, name, comment, tables)
+        self.idlPrefix = idlPrefix
+        self.idlHeader = idlHeader
+
+    @staticmethod
+    def fromJson(json):
+        schema = DbSchema.fromJson(json)
+        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+        return IdlSchema(schema.name, schema.comment, schema.tables,
+                         idlPrefix, idlHeader)
+
+class TableSchema:
+    def __init__(self, comment, columns):
+        self.comment = comment
+        self.columns = columns
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        columnsJson = mustGetMember(json, 'columns', [dict], description)
+        columns = {}
+        for name, json in columnsJson.iteritems():
+            columns[name] = ColumnSchema.fromJson(
+                json, "column %s in %s" % (name, description))
+        return TableSchema(comment, columns)
+
+class ColumnSchema:
+    def __init__(self, comment, type, persistent):
+        self.comment = comment
+        self.type = type
+        self.persistent = persistent
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
+                                           description),
+                             'type of %s' % description)
+        ephemeral = getMember(json, 'ephemeral', [bool], description)
+        persistent = ephemeral != True
+        return ColumnSchema(comment, type, persistent)
+
+def escapeCString(src):
+    dst = ""
+    for c in src:
+        if c in "\\\"":
+            dst += "\\" + c
+        elif ord(c) < 32:
+            if c == '\n':
+                dst += '\\n'
+            elif c == '\r':
+                dst += '\\r'
+            elif c == '\a':
+                dst += '\\a'
+            elif c == '\b':
+                dst += '\\b'
+            elif c == '\f':
+                dst += '\\f'
+            elif c == '\t':
+                dst += '\\t'
+            elif c == '\v':
+                dst += '\\v'
+            else:
+                dst += '\\%03o' % ord(c)
+        else:
+            dst += c
+    return dst
+
+def returnUnchanged(x):
+    return x
+
+class UUID:
+    x = "[0-9a-fA-f]"
+    uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
+                        % (x, x, x, x, x, x))
+
+    def __init__(self, value):
+        self.value = value
+
+    @staticmethod
+    def fromString(s):
+        if not uuidRE.match(s):
+            raise Error("%s is not a valid UUID" % s)
+        return UUID(s)
+
+    @staticmethod
+    def fromJson(json):
+        if UUID.isValidJson(json):
+            return UUID(json[1])
+        else:
+            raise Error("%s is not valid JSON for a UUID" % json)
+
+    @staticmethod
+    def isValidJson(json):
+        return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
+            
+    def toJson(self):
+        return ["uuid", self.value]
+
+    def cInitUUID(self, var):
+        m = re.match(self.value)
+        return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
+                "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
+                "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
+                "%s.parts[3] = 0x%s;" % (var, m.group(6))]
+
+class Atom:
+    def __init__(self, type, value):
+        self.type = type
+        self.value = value
+
+    @staticmethod
+    def fromJson(type_, json):
+        if ((type_ == 'integer' and type(json) in [int, long])
+            or (type_ == 'real' and type(json) in [int, long, float])
+            or (type_ == 'boolean' and json in [True, False])
+            or (type_ == 'string' and type(json) in [str, unicode])):
+            return Atom(type_, json)
+        elif type_ == 'uuid':
+            return UUID.fromJson(json)
+        else:
+            raise Error("%s is not valid JSON for type %s" % (json, type_))
+
+    def toJson(self):
+        if self.type == 'uuid':
+            return self.value.toString()
+        else:
+            return self.value
+
+    def cInitAtom(self, var):
+        if self.type == 'integer':
+            return ['%s.integer = %d;' % (var, self.value)]
+        elif self.type == 'real':
+            return ['%s.real = %.15g;' % (var, self.value)]
+        elif self.type == 'boolean':
+            if self.value:
+                return ['%s.boolean = true;']
+            else:
+                return ['%s.boolean = false;']
+        elif self.type == 'string':
+            return ['%s.string = xstrdup("%s");'
+                    % (var, escapeCString(self.value))]
+        elif self.type == 'uuid':
+            return self.value.cInitUUID(var)
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        if self.type == 'integer':
+            return '%d' % self.value
+        elif self.type == 'real':
+            return '%.15g' % self.value
+        elif self.type == 'boolean':
+            if self.value:
+                return 'true'
+            else:
+                return 'false'
+        elif self.type == 'string':
+            return escapeLiteral(self.value)
+        elif self.type == 'uuid':
+            return self.value.value
+
+class BaseType:
+    def __init__(self, type,
+                 enum=None,
+                 refTable=None,
+                 minInteger=None, maxInteger=None,
+                 minReal=None, maxReal=None,
+                 minLength=None, maxLength=None):
+        self.type = type
+        self.enum = enum
+        self.refTable = refTable
+        self.minInteger = minInteger
+        self.maxInteger = maxInteger
+        self.minReal = minReal
+        self.maxReal = maxReal
+        self.minLength = minLength
+        self.maxLength = maxLength
+
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return BaseType(json)
+        else:
+            atomicType = mustGetMember(json, 'type', [unicode], description)
+            enum = getMember(json, 'enum', [], description)
+            if enum:
+                enumType = Type(atomicType, None, 0, 'unlimited')
+                enum = Datum.fromJson(enumType, enum)
+            refTable = getMember(json, 'refTable', [unicode], description)
+            minInteger = getMember(json, 'minInteger', [int, long], description)
+            maxInteger = getMember(json, 'maxInteger', [int, long], description)
+            minReal = getMember(json, 'minReal', [int, long, float], description)
+            maxReal = getMember(json, 'maxReal', [int, long, float], description)
+            minLength = getMember(json, 'minLength', [int], description)
+            maxLength = getMember(json, 'minLength', [int], description)
+            return BaseType(atomicType, enum, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        if self.type == 'uuid' and self.refTable:
+            return escapeLiteral(self.refTable)
+        else:
+            return self.type
+
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
+        if self.enum:
+            literals = [value.toEnglish(escapeLiteral)
+                        for value in self.enum.values]
+            if len(literals) == 2:
+                return 'either %s or %s' % (literals[0], literals[1])
+            else:
+                return 'one of %s, %s, or %s' % (literals[0],
+                                                 ', '.join(literals[1:-1]),
+                                                 literals[-1])
+        elif self.minInteger != None and self.maxInteger != None:
+            return 'in range [%d,%d]' % (self.minInteger, self.maxInteger)
+        elif self.minInteger != None:
+            return 'at least %d' % self.minInteger
+        elif self.maxInteger != None:
+            return 'at most %d' % self.maxInteger
+        elif self.minReal != None and self.maxReal != None:
+            return 'in range [%g, %g]' % (self.minReal, self.maxReal)
+        elif self.minReal != None:
+            return 'at least %g' % self.minReal
+        elif self.maxReal != None:
+            return 'at most %g' % self.maxReal
+        elif self.minLength != None and self.maxLength != None:
+            if self.minLength == self.maxLength:
+                return 'exactly %d characters long' % (self.minLength)
+            else:
+                return 'between %d and %d characters long' % (self.minLength, self.maxLength)
+        elif self.minLength != None:
+            return 'at least %d characters long' % self.minLength
+        elif self.maxLength != None:
+            return 'at most %d characters long' % self.maxLength
+        else:
+            return ''
+
+    def toCType(self, prefix):
+        if self.refTable:
+            return "struct %s%s *" % (prefix, self.refTable.lower())
+        else:
+            return {'integer': 'int64_t ',
+                    'real': 'double ',
+                    'uuid': 'struct uuid ',
+                    'boolean': 'bool ',
+                    'string': 'char *'}[self.type]
+
+    def copyCValue(self, dst, src):
+        args = {'dst': dst, 'src': src}
+        if self.refTable:
+            return ("%(dst)s = %(src)s->header_.uuid;") % args
+        elif self.type == 'string':
+            return "%(dst)s = xstrdup(%(src)s);" % args
+        else:
+            return "%(dst)s = %(src)s;" % args
+
+    def initCDefault(self, var, isOptional):
+        if self.refTable:
+            return "%s = NULL;" % var
+        elif self.type == 'string' and not isOptional:
+            return "%s = \"\";" % var
+        else:
+            return {'integer': '%s = 0;',
+                    'real': '%s = 0.0;',
+                    'uuid': 'uuid_zero(&%s);',
+                    'boolean': '%s = false;',
+                    'string': '%s = NULL;'}[self.type] % var
+
+    def cInitBaseType(self, indent, var):
+        stmts = []
+        stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
+                var, self.type.upper()),)
+        if self.enum:
+            stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
+                         % (var, var))
+            stmts += self.enum.cInitDatum("%s.enum_" % var)
+        if self.type == 'integer':
+            if self.minInteger != None:
+                stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
+            if self.maxInteger != None:
+                stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger))
+        elif self.type == 'real':
+            if self.minReal != None:
+                stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
+            if self.maxReal != None:
+                stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
+        elif self.type == 'string':
+            if self.minLength != None:
+                stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
+            if self.maxLength != None:
+                stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+        elif self.type == 'uuid':
+            if self.refTable != None:
+                stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
+        return '\n'.join([indent + stmt for stmt in stmts])
+
+class Type:
+    def __init__(self, key, value=None, min=1, max=1):
+        self.key = key
+        self.value = value
+        self.min = min
+        self.max = max
+    
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return Type(BaseType(json))
+        else:
+            keyJson = mustGetMember(json, 'key', [dict, unicode], description)
+            key = BaseType.fromJson(keyJson, 'key in %s' % description)
+
+            valueJson = getMember(json, 'value', [dict, unicode], description)
+            if valueJson:
+                value = BaseType.fromJson(valueJson,
+                                          'value in %s' % description)
+            else:
+                value = None
+
+            min = getMember(json, 'min', [int], description, 1)
+            max = getMember(json, 'max', [int, unicode], description, 1)
+            return Type(key, value, min, max)
+
+    def isScalar(self):
+        return self.min == 1 and self.max == 1 and not self.value
+
+    def isOptional(self):
+        return self.min == 0 and self.max == 1
+
+    def isOptionalPointer(self):
+        return (self.min == 0 and self.max == 1 and not self.value
+                and (self.key.type == 'string' or self.key.refTable))
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        keyName = self.key.toEnglish(escapeLiteral)
+        if self.value:
+            valueName = self.value.toEnglish(escapeLiteral)
+
+        if self.isScalar():
+            return keyName
+        elif self.isOptional():
+            if self.value:
+                return "optional %s-%s pair" % (keyName, valueName)
+            else:
+                return "optional %s" % keyName
+        else:
+            if self.max == "unlimited":
+                if self.min:
+                    quantity = "%d or more " % self.min
+                else:
+                    quantity = ""
+            elif self.min:
+                quantity = "%d to %d " % (self.min, self.max)
+            else:
+                quantity = "up to %d " % self.max
+
+            if self.value:
+                return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
+            else:
+                if keyName.endswith('s'):
+                    plural = keyName + "es"
+                else:
+                    plural = keyName + "s"
+                return "set of %s%s" % (quantity, plural)
+
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
+        s = ""
+
+        constraints = []
+        keyConstraints = self.key.constraintsToEnglish(escapeLiteral)
+        if keyConstraints:
+            if self.value:
+                constraints += ['key ' + keyConstraints]
+            else:
+                constraints += [keyConstraints]
+
+        if self.value:
+            valueConstraints = self.value.constraintsToEnglish(escapeLiteral)
+            if valueConstraints:
+                constraints += ['value ' + valueConstraints]
+
+        return ', '.join(constraints)
+                
+    def cDeclComment(self):
+        if self.min == 1 and self.max == 1 and self.key.type == "string":
+            return "\t/* Always nonnull. */"
+        else:
+            return ""
+
+    def cInitType(self, indent, var):
+        initKey = self.key.cInitBaseType(indent, "%s.key" % var)
+        if self.value:
+            initValue = self.value.cInitBaseType(indent, "%s.value" % var)
+        else:
+            initValue = ('%sovsdb_base_type_init(&%s.value, '
+                         'OVSDB_TYPE_VOID);' % (indent, var))
+        initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
+        if self.max == "unlimited":
+            max = "UINT_MAX"
+        else:
+            max = self.max
+        initMax = "%s%s.n_max = %s;" % (indent, var, max)
+        return "\n".join((initKey, initValue, initMin, initMax))
+
+class Datum:
+    def __init__(self, type, values):
+        self.type = type
+        self.values = values
+
+    @staticmethod
+    def fromJson(type_, json):
+        if not type_.value:
+            if len(json) == 2 and json[0] == "set":
+                values = []
+                for atomJson in json[1]:
+                    values += [Atom.fromJson(type_.key, atomJson)]
+            else:
+                values = [Atom.fromJson(type_.key, json)]
+        else:
+            if len(json) != 2 or json[0] != "map":
+                raise Error("%s is not valid JSON for a map" % json)
+            values = []
+            for pairJson in json[1]:
+                values += [(Atom.fromJson(type_.key, pairJson[0]),
+                            Atom.fromJson(type_.value, pairJson[1]))]
+        return Datum(type_, values)
+
+    def cInitDatum(self, var):
+        if len(self.values) == 0:
+            return ["ovsdb_datum_init_empty(%s);" % var]
+
+        s = ["%s->n = %d;" % (var, len(self.values))]
+        s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
+              % (var, len(self.values), var)]
+
+        for i in range(len(self.values)):
+            key = self.values[i]
+            if self.type.value:
+                key = key[0]
+            s += key.cInitAtom("%s->keys[%d]" % (var, i))
+        
+        if self.type.value:
+            s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
+                  % (var, len(self.values), var)]
+            for i in range(len(self.values)):
+                value = self.values[i][1]
+                s += key.cInitAtom("%s->values[%d]" % (var, i))
+        else:
+            s += ["%s->values = NULL;" % var]
+
+        if len(self.values) > 1:
+            s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
+                  % (var, self.type.key.upper())]
+
+        return s
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
index 8605410..7d578a8 100644
--- a/ovsdb/automake.mk
+++ b/ovsdb/automake.mk
@@ -82,12 +82,12 @@ EXTRA_DIST += \
 	ovsdb/simplejson/tests/test_separators.py		\
 	ovsdb/simplejson/tests/test_unicode.py			\
 	ovsdb/simplejson/tool.py
-noinst_SCRIPTS += ovsdb/ovsdb-idlc
+noinst_SCRIPTS += ovsdb/ovsdb-idlc 
 EXTRA_DIST += \
 	ovsdb/ovsdb-idlc.in \
 	ovsdb/ovsdb-idlc.1
 DISTCLEANFILES += ovsdb/ovsdb-idlc
-SUFFIXES += .ovsidl .txt
+SUFFIXES += .ovsidl
 OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in
 .ovsidl.c:
 	$(OVSDB_IDLC) c-idl-source $< > $@.tmp
@@ -95,9 +95,6 @@ OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in
 .ovsidl.h:
 	$(OVSDB_IDLC) c-idl-header $< > $@.tmp
 	mv $@.tmp $@
-.ovsidl.txt:
-	$(OVSDB_IDLC) doc $< | fmt -s > $@.tmp
-	mv $@.tmp $@
 
 EXTRA_DIST += $(OVSIDL_BUILT)
 BUILT_SOURCES += $(OVSIDL_BUILT)
@@ -111,3 +108,9 @@ BUILT_SOURCES += $(OVSIDL_BUILT)
 # assignments before any targets, so it doesn't seem to be a problem,
 # at least for now.
 $(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in
+
+# ovsdb-doc
+EXTRA_DIST += ovsdb/ovsdb-doc.in
+noinst_SCRIPTS += ovsdb/ovsdb-doc
+DISTCLEANFILES += ovsdb/ovsdb-doc
+OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in
diff --git a/ovsdb/ovsdb-doc.in b/ovsdb/ovsdb-doc.in
new file mode 100755
index 0000000..cb21c1f
--- /dev/null
+++ b/ovsdb/ovsdb-doc.in
@@ -0,0 +1,329 @@
+#! @PYTHON@
+
+from datetime import date
+import getopt
+import os
+import re
+import sys
+import xml.dom.minidom
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+from OVSDB import *
+
+argv0 = sys.argv[0]
+
+def textToNroff(s):
+    def escape(match):
+        c = match.group(0)
+        if c == '\\':
+            return r'\e'
+        elif c == '"':
+            return r'\(dq'
+        elif c == "'":
+            return r'\(cq'
+        else:
+            raise Error("bad escape")
+
+    s = re.sub('([\\\\"\'])', escape, s)
+    if s.startswith('.'):
+        s = '\\' + s
+    return s
+
+def escapeNroffLiteral(s):
+    return r'\fB%s\fR' % textToNroff(s)
+
+def inlineXmlToNroff(node, font):
+    if node.nodeType == node.TEXT_NODE:
+        return textToNroff(node.data)
+    elif node.nodeType == node.ELEMENT_NODE:
+        if node.tagName == 'code' or node.tagName == 'em':
+            s = r'\fB'
+            for child in node.childNodes:
+                s += inlineXmlToNroff(child, r'\fB')
+            return s + font
+        elif node.tagName == 'ref':
+            s = r'\fB'
+            if node.hasAttribute('column'):
+                s += node.attributes['column'].nodeValue
+            elif node.hasAttribute('table'):
+                s += node.attributes['table'].nodeValue
+            elif node.hasAttribute('group'):
+                s += node.attributes['group'].nodeValue
+            else:
+                raise Error("'ref' lacks column and table attributes")
+            return s + font
+        elif node.tagName == 'var':
+            s = r'\fI'
+            for child in node.childNodes:
+                s += inlineXmlToNroff(child, r'\fI')
+            return s + font
+        else:
+            raise Error("element <%s> unknown or invalid here" % node.tagName)
+    else:
+        raise Error("unknown node %s in inline xml" % node)
+
+def blockXmlToNroff(nodes, para='.PP'):
+    s = ''
+    for node in nodes:
+        if node.nodeType == node.TEXT_NODE:
+            s += textToNroff(node.data)
+            s = s.lstrip()
+        elif node.nodeType == node.ELEMENT_NODE:
+            if node.tagName == 'ul':
+                if s != "":
+                    s += "\n"
+                s += ".RS\n"
+                for liNode in node.childNodes:
+                    if (liNode.nodeType == node.ELEMENT_NODE
+                        and liNode.tagName == 'li'):
+                        s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
+                    elif (liNode.nodeType != node.TEXT_NODE
+                          or not liNode.data.isspace()):
+                        raise Error("<ul> element may only have <li> children")
+                s += ".RE\n"
+            elif node.tagName == 'dl':
+                if s != "":
+                    s += "\n"
+                s += ".RS\n"
+                prev = "dd"
+                for liNode in node.childNodes:
+                    if (liNode.nodeType == node.ELEMENT_NODE
+                        and liNode.tagName == 'dt'):
+                        if prev == 'dd':
+                            s += '.TP\n'
+                        else:
+                            s += '.TQ\n'
+                        prev = 'dt'
+                    elif (liNode.nodeType == node.ELEMENT_NODE
+                          and liNode.tagName == 'dd'):
+                        if prev == 'dd':
+                            s += '.IP\n'
+                        prev = 'dd'
+                    elif (liNode.nodeType != node.TEXT_NODE
+                          or not liNode.data.isspace()):
+                        raise Error("<dl> element may only have <dt> and <dd> children")
+                    s += blockXmlToNroff(liNode.childNodes, ".IP")
+                s += ".RE\n"
+            elif node.tagName == 'p':
+                if s != "":
+                    if not s.endswith("\n"):
+                        s += "\n"
+                    s += para + "\n"
+                s += blockXmlToNroff(node.childNodes, para)
+            else:
+                s += inlineXmlToNroff(node, r'\fR')
+        else:
+            raise Error("unknown node %s in block xml" % node)
+    if s != "" and not s.endswith('\n'):
+        s += '\n'
+    return s
+
+def typeAndConstraintsToNroff(column):
+    type = column.type.toEnglish(escapeNroffLiteral)
+    constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
+    if constraints:
+        type += ", " + constraints
+    return type
+
+def columnToNroff(columnName, column, node):
+    type = typeAndConstraintsToNroff(column)
+    s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
+    s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
+    return s
+
+def columnGroupToNroff(table, groupXml):
+    introNodes = []
+    columnNodes = []
+    for node in groupXml.childNodes:
+        if (node.nodeType == node.ELEMENT_NODE
+            and node.tagName in ('column', 'group')):
+            columnNodes += [node]
+        else:
+            introNodes += [node]
+
+    summary = []
+    intro = blockXmlToNroff(introNodes)
+    body = ''
+    for node in columnNodes:
+        if node.tagName == 'column':
+            columnName = node.attributes['name'].nodeValue
+            column = table.columns[columnName]
+            body += columnToNroff(columnName, column, node)
+            summary += [('column', columnName, column)]
+        elif node.tagName == 'group':
+            title = node.attributes["title"].nodeValue
+            subSummary, subIntro, subBody = columnGroupToNroff(table, node)
+            summary += [('group', title, subSummary)]
+            body += '.ST "%s:"\n' % textToNroff(title)
+            body += subIntro + subBody
+        else:
+            raise Error("unknown element %s in <table>" % node.tagName)
+    return summary, intro, body
+
+def tableSummaryToNroff(summary, level=0):
+    s = ""
+    for type, name, arg in summary:
+        if type == 'column':
+            
+            s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
+                r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
+        else:
+            if s != "":
+                s += "_\n"
+            s += """.T&
+li | s
+l | l.
+%s%s
+_
+""" % (r'\ \ ' * level, name)
+            s += tableSummaryToNroff(arg, level + 1)
+    return s
+
+def tableToNroff(schema, tableXml):
+    tableName = tableXml.attributes['name'].nodeValue
+    table = schema.tables[tableName]
+
+    s = """.bp
+.SS "%s Table"
+""" % tableName
+    summary, intro, body = columnGroupToNroff(table, tableXml)
+    s += intro
+
+    s += r"""
+.sp
+.ce 1
+\fB%s\fR Table Columns:
+.TS
+center box;
+l | l.
+Column	Type
+=
+""" % tableName
+    s += tableSummaryToNroff(summary)
+    s += ".TE\n"
+
+    s += body
+    return s
+
+def docsToNroff(schemaFile, xmlFile, title=None):
+    schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
+    doc = xml.dom.minidom.parse(xmlFile).documentElement
+
+    schemaDate = os.stat(schemaFile).st_mtime
+    xmlDate = os.stat(xmlFile).st_mtime
+    d = date.fromtimestamp(max(schemaDate, xmlDate))
+    
+    if title == None:
+        title = schema.name
+
+    s = r'''.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
+.\" -*- nroff -*-
+.de TQ
+.  br
+.  ns
+.  TP "\\$1"
+..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+..
+''' % (title, d.strftime("%B %Y"))
+
+    s += '.SH "%s DATABASE"\n' % schema.name
+
+    tables = ""
+    introNodes = []
+    tableNodes = []
+    summary = []
+    for dbNode in doc.childNodes:
+        if (dbNode.nodeType == dbNode.ELEMENT_NODE
+            and dbNode.tagName == "table"):
+            tableNodes += [dbNode]
+
+            name = dbNode.attributes['name'].nodeValue
+            if dbNode.hasAttribute("title"):
+                title = dbNode.attributes['title'].nodeValue
+            else:
+                title = name + " configuration."
+            summary += [(name, title)]
+        else:
+            introNodes += [dbNode]
+
+    s += blockXmlToNroff(introNodes) + "\n"
+    tableSummary = r"""
+.sp
+.ce 1
+\fB%s\fR Database Tables:
+.TS
+center box;
+l | l
+lb | l.
+Table	Purpose
+=
+""" % schema.name
+    for name, title in summary:
+        tableSummary += "%s\t%s\n" % (name, textToNroff(title))
+    tableSummary += '.TE\n'
+    s += tableSummary
+    for node in tableNodes:
+        s += tableToNroff(schema, node) + "\n"
+    return s
+
+def usage():
+    print """\
+%(argv0)s: ovsdb schema documentation generator
+Prints documentation for an OVSDB schema as an nroff-formatted manpage.
+usage: %(argv0)s [OPTIONS] SCHEMA XML
+where SCHEMA is an OVSDB schema in JSON format
+  and XML is OVSDB documentation in XML format.
+
+The following options are also available:
+  --title=TITLE               use TITLE as title instead of schema name
+  -h, --help                  display this help message
+  -V, --version               display version information\
+""" % {'argv0': argv0}
+    sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        try:
+            options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
+                                              ['title=', 'help', 'version'])
+        except getopt.GetoptError, geo:
+            sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+            sys.exit(1)
+
+        title = None
+        for key, value in options:
+            if key == '--title':
+                title = value
+            elif key in ['-h', '--help']:
+                usage()
+            elif key in ['-V', '--version']:
+                print "ovsdb-doc (Open vSwitch) @VERSION@"
+            else:
+                sys.exit(0)
+            
+        if len(args) != 2:
+            sys.stderr.write("%s: exactly 2 non-option arguments required "
+                             "(use --help for help)\n" % argv0)
+            sys.exit(1)
+        
+        # XXX we should warn about undocumented tables or columns
+        s = docsToNroff(args[0], args[1])
+        for line in s.split("\n"):
+            line = line.strip()
+            if len(line):
+                print line
+            
+    except Error, e:
+        sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+        sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in
index 2426e2d..e99917a 100755
--- a/ovsdb/ovsdb-idlc.in
+++ b/ovsdb/ovsdb-idlc.in
@@ -8,365 +8,9 @@ import sys
 sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
 import simplejson as json
 
-argv0 = sys.argv[0]
-
-class Error(Exception):
-    def __init__(self, msg):
-        Exception.__init__(self)
-        self.msg = msg
-
-def getMember(json, name, validTypes, description, default=None):
-    if name in json:
-        member = json[name]
-        if len(validTypes) and type(member) not in validTypes:
-            raise Error("%s: type mismatch for '%s' member"
-                        % (description, name))
-        return member
-    return default
-
-def mustGetMember(json, name, expectedType, description):
-    member = getMember(json, name, expectedType, description)
-    if member == None:
-        raise Error("%s: missing '%s' member" % (description, name))
-    return member
-
-class DbSchema:
-    def __init__(self, name, comment, tables, idlPrefix, idlHeader):
-        self.name = name
-        self.comment = comment
-        self.tables = tables
-        self.idlPrefix = idlPrefix
-        self.idlHeader = idlHeader
+from OVSDB import *
 
-    @staticmethod
-    def fromJson(json):
-        name = mustGetMember(json, 'name', [unicode], 'database')
-        comment = getMember(json, 'comment', [unicode], 'database')
-        tablesJson = mustGetMember(json, 'tables', [dict], 'database')
-        tables = {}
-        for tableName, tableJson in tablesJson.iteritems():
-            tables[tableName] = TableSchema.fromJson(tableJson,
-                                                     "%s table" % tableName)
-        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
-        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
-        return DbSchema(name, comment, tables, idlPrefix, idlHeader)
-
-class TableSchema:
-    def __init__(self, comment, columns):
-        self.comment = comment
-        self.columns = columns
-
-    @staticmethod
-    def fromJson(json, description):
-        comment = getMember(json, 'comment', [unicode], description)
-        columnsJson = mustGetMember(json, 'columns', [dict], description)
-        columns = {}
-        for name, json in columnsJson.iteritems():
-            columns[name] = ColumnSchema.fromJson(
-                json, "column %s in %s" % (name, description))
-        return TableSchema(comment, columns)
-
-class ColumnSchema:
-    def __init__(self, comment, type, persistent):
-        self.comment = comment
-        self.type = type
-        self.persistent = persistent
-
-    @staticmethod
-    def fromJson(json, description):
-        comment = getMember(json, 'comment', [unicode], description)
-        type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
-                                           description),
-                             'type of %s' % description)
-        ephemeral = getMember(json, 'ephemeral', [bool], description)
-        persistent = ephemeral != True
-        return ColumnSchema(comment, type, persistent)
-
-def escapeCString(src):
-    dst = ""
-    for c in src:
-        if c in "\\\"":
-            dst += "\\" + c
-        elif ord(c) < 32:
-            if c == '\n':
-                dst += '\\n'
-            elif c == '\r':
-                dst += '\\r'
-            elif c == '\a':
-                dst += '\\a'
-            elif c == '\b':
-                dst += '\\b'
-            elif c == '\f':
-                dst += '\\f'
-            elif c == '\t':
-                dst += '\\t'
-            elif c == '\v':
-                dst += '\\v'
-            else:
-                dst += '\\%03o' % ord(c)
-        else:
-            dst += c
-    return dst
-
-class UUID:
-    x = "[0-9a-fA-f]"
-    uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
-                        % (x, x, x, x, x, x))
-
-    def __init__(self, value):
-        self.value = value
-
-    @staticmethod
-    def fromString(s):
-        if not uuidRE.match(s):
-            raise Error("%s is not a valid UUID" % s)
-        return UUID(s)
-
-    @staticmethod
-    def fromJson(json):
-        if UUID.isValidJson(json):
-            return UUID(json[1])
-        else:
-            raise Error("%s is not valid JSON for a UUID" % json)
-
-    @staticmethod
-    def isValidJson(json):
-        return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
-            
-    def toJson(self):
-        return ["uuid", self.value]
-
-    def cInitUUID(self, var):
-        m = re.match(self.value)
-        return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
-                "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
-                "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
-                "%s.parts[3] = 0x%s;" % (var, m.group(6))]
-
-class Atom:
-    def __init__(self, type, value):
-        self.type = type
-        self.value = value
-
-    @staticmethod
-    def fromJson(type_, json):
-        if ((type_ == 'integer' and type(json) in [int, long])
-            or (type_ == 'real' and type(json) in [int, long, float])
-            or (type_ == 'boolean' and json in [True, False])
-            or (type_ == 'string' and type(json) in [str, unicode])):
-            return Atom(type_, json)
-        elif type_ == 'uuid':
-            return UUID.fromJson(json)
-        else:
-            raise Error("%s is not valid JSON for type %s" % (json, type_))
-
-    def toJson(self):
-        if self.type == 'uuid':
-            return self.value.toString()
-        else:
-            return self.value
-
-    def cInitAtom(self, var):
-        if self.type == 'integer':
-            return ['%s.integer = %d;' % (var, self.value)]
-        elif self.type == 'real':
-            return ['%s.real = %.15g;' % (var, self.value)]
-        elif self.type == 'boolean':
-            if self.value:
-                return ['%s.boolean = true;']
-            else:
-                return ['%s.boolean = false;']
-        elif self.type == 'string':
-            return ['%s.string = xstrdup("%s");'
-                    % (var, escapeCString(self.value))]
-        elif self.type == 'uuid':
-            return self.value.cInitUUID(var)        
-
-class BaseType:
-    def __init__(self, type,
-                 enum=None,
-                 refTable=None,
-                 minInteger=None, maxInteger=None,
-                 minReal=None, maxReal=None,
-                 minLength=None, maxLength=None):
-        self.type = type
-        self.enum = enum
-        self.refTable = refTable
-        self.minInteger = minInteger
-        self.maxInteger = maxInteger
-        self.minReal = minReal
-        self.maxReal = maxReal
-        self.minLength = minLength
-        self.maxLength = maxLength
-
-    @staticmethod
-    def fromJson(json, description):
-        if type(json) == unicode:
-            return BaseType(json)
-        else:
-            atomicType = mustGetMember(json, 'type', [unicode], description)
-            enum = getMember(json, 'enum', [], description)
-            if enum:
-                enumType = Type(atomicType, None, 0, 'unlimited')
-                enum = Datum.fromJson(enumType, enum)
-            refTable = getMember(json, 'refTable', [unicode], description)
-            minInteger = getMember(json, 'minInteger', [int, long], description)
-            maxInteger = getMember(json, 'maxInteger', [int, long], description)
-            minReal = getMember(json, 'minReal', [int, long, float], description)
-            maxReal = getMember(json, 'maxReal', [int, long, float], description)
-            minLength = getMember(json, 'minLength', [int], description)
-            maxLength = getMember(json, 'minLength', [int], description)
-            return BaseType(atomicType, enum, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
-
-    def toEnglish(self):
-        if self.type == 'uuid' and self.refTable:
-            return self.refTable
-        else:
-            return self.type
-
-    def toCType(self, prefix):
-        if self.refTable:
-            return "struct %s%s *" % (prefix, self.refTable.lower())
-        else:
-            return {'integer': 'int64_t ',
-                    'real': 'double ',
-                    'uuid': 'struct uuid ',
-                    'boolean': 'bool ',
-                    'string': 'char *'}[self.type]
-
-    def copyCValue(self, dst, src):
-        args = {'dst': dst, 'src': src}
-        if self.refTable:
-            return ("%(dst)s = %(src)s->header_.uuid;") % args
-        elif self.type == 'string':
-            return "%(dst)s = xstrdup(%(src)s);" % args
-        else:
-            return "%(dst)s = %(src)s;" % args
-
-    def initCDefault(self, var, isOptional):
-        if self.refTable:
-            return "%s = NULL;" % var
-        elif self.type == 'string' and not isOptional:
-            return "%s = \"\";" % var
-        else:
-            return {'integer': '%s = 0;',
-                    'real': '%s = 0.0;',
-                    'uuid': 'uuid_zero(&%s);',
-                    'boolean': '%s = false;',
-                    'string': '%s = NULL;'}[self.type] % var
-
-    def cInitBaseType(self, indent, var):
-        stmts = []
-        stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
-                var, self.type.upper()),)
-        if self.enum:
-            stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
-                         % (var, var))
-            stmts += self.enum.cInitDatum("%s.enum_" % var)
-        if self.type == 'integer':
-            if self.minInteger != None:
-                stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
-            if self.maxInteger != None:
-                stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger))
-        elif self.type == 'real':
-            if self.minReal != None:
-                stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
-            if self.maxReal != None:
-                stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
-        elif self.type == 'string':
-            if self.minLength != None:
-                stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
-            if self.maxLength != None:
-                stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
-        elif self.type == 'uuid':
-            if self.refTable != None:
-                stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
-        return '\n'.join([indent + stmt for stmt in stmts])
-
-class Type:
-    def __init__(self, key, value=None, min=1, max=1):
-        self.key = key
-        self.value = value
-        self.min = min
-        self.max = max
-    
-    @staticmethod
-    def fromJson(json, description):
-        if type(json) == unicode:
-            return Type(BaseType(json))
-        else:
-            keyJson = mustGetMember(json, 'key', [dict, unicode], description)
-            key = BaseType.fromJson(keyJson, 'key in %s' % description)
-
-            valueJson = getMember(json, 'value', [dict, unicode], description)
-            if valueJson:
-                value = BaseType.fromJson(valueJson,
-                                          'value in %s' % description)
-            else:
-                value = None
-
-            min = getMember(json, 'min', [int], description, 1)
-            max = getMember(json, 'max', [int, unicode], description, 1)
-            return Type(key, value, min, max)
-
-    def isScalar(self):
-        return self.min == 1 and self.max == 1 and not self.value
-
-    def isOptional(self):
-        return self.min == 0 and self.max == 1
-
-    def isOptionalPointer(self):
-        return (self.min == 0 and self.max == 1 and not self.value
-                and (self.key.type == 'string' or self.key.refTable))
-
-    def toEnglish(self):
-        keyName = self.key.toEnglish()
-        if self.value:
-            valueName = self.value.toEnglish()
-
-        if self.isScalar():
-            return keyName
-        elif self.isOptional():
-            if self.value:
-                return "optional %s-%s pair" % (keyName, valueName)
-            else:
-                return "optional %s" % keyName
-        else:
-            if self.max == "unlimited":
-                if self.min:
-                    quantity = "%d or more " % self.min
-                else:
-                    quantity = ""
-            elif self.min:
-                quantity = "%d to %d " % (self.min, self.max)
-            else:
-                quantity = "up to %d " % self.max
-
-            if self.value:
-                return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
-            else:
-                return "set of %s%s" % (quantity, keyName)
-                
-    def cDeclComment(self):
-        if self.min == 1 and self.max == 1 and self.key.type == "string":
-            return "\t/* Always nonnull. */"
-        else:
-            return ""
-
-    def cInitType(self, indent, var):
-        initKey = self.key.cInitBaseType(indent, "%s.key" % var)
-        if self.value:
-            initValue = self.value.cInitBaseType(indent, "%s.value" % var)
-        else:
-            initValue = ('%sovsdb_base_type_init(&%s.value, '
-                         'OVSDB_TYPE_VOID);' % (indent, var))
-        initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
-        if self.max == "unlimited":
-            max = "UINT_MAX"
-        else:
-            max = self.max
-        initMax = "%s%s.n_max = %s;" % (indent, var, max)
-        return "\n".join((initKey, initValue, initMin, initMax))
+argv0 = sys.argv[0]
 
 class Datum:
     def __init__(self, type, values):
@@ -421,7 +65,7 @@ class Datum:
         return s
 
 def parseSchema(filename):
-    return DbSchema.fromJson(json.load(open(filename, "r")))
+    return IdlSchema.fromJson(json.load(open(filename, "r")))
 
 def annotateSchema(schemaFile, annotationFile):
     schemaJson = json.load(open(schemaFile, "r"))
@@ -887,25 +531,7 @@ def ovsdb_escape(string):
             return '\\x%02x' % ord(c)
     return re.sub(r'["\\\000-\037]', escape, string)
 
-def printDoc(schemaFile):
-    schema = parseSchema(schemaFile)
-    print schema.name
-    if schema.comment:
-        print schema.comment
-
-    for tableName, table in sorted(schema.tables.iteritems()):
-        title = "%s table" % tableName
-        print
-        print title
-        print '-' * len(title)
-        if table.comment:
-            print table.comment
 
-        for columnName, column in sorted(table.columns.iteritems()):
-            print
-            print "%s (%s)" % (columnName, column.type.toEnglish())
-            if column.comment:
-                print "\t%s" % column.comment
 
 def usage():
     print """\
@@ -916,7 +542,7 @@ The following commands are supported:
   annotate SCHEMA ANNOTATIONS print SCHEMA combined with ANNOTATIONS
   c-idl-header IDL            print C header file for IDL
   c-idl-source IDL            print C source file for IDL implementation
-  doc IDL                     print schema documentation
+  nroff IDL                   print schema documentation in nroff format
 
 The following options are also available:
   -h, --help                  display this help message
@@ -954,8 +580,7 @@ if __name__ == "__main__":
 
         commands = {"annotate": (annotateSchema, 2),
                     "c-idl-header": (printCIDLHeader, 1),
-                    "c-idl-source": (printCIDLSource, 1),
-                    "doc": (printDoc, 1)}
+                    "c-idl-source": (printCIDLSource, 1)}
 
         if not args[0] in commands:
             sys.stderr.write("%s: unknown command \"%s\" "
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
index 7db3ddc..6c362c3 100644
--- a/utilities/ovs-vsctl.8.in
+++ b/utilities/ovs-vsctl.8.in
@@ -9,7 +9,6 @@
 .  RS -0.15in
 .  I "\\$1"
 .  RE
-.  PP
 ..
 .TH ovs\-vsctl 8 "November 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-vsctl
@@ -312,7 +311,7 @@ the following forms:
 .RE
 .
 .ST "Controller Failure Settings"
-.
+.PP
 When a controller is configured, it is, ordinarily, responsible for
 setting up all flows on the switch.  Thus, if the connection to
 the controller fails, no new network connections can be set up.  If
@@ -376,6 +375,7 @@ Sets the SSL configuration.  The \fB\-\-bootstrap\fR option is described
 below.
 .
 .ST "CA Certificate Bootstrap"
+.PP
 Ordinarily, all of the files named in the SSL configuration must exist
 when \fBovs\-vswitchd\fR starts.  However, if the \fB\-\-bootstrap\fR 
 option is given, then \fBovs\-vswitchd\fR will attempt to obtain the
@@ -442,6 +442,7 @@ abbreviations are acceptable, e.g. \fBnet\fR or \fRn\fR is sufficient
 to identify the \fBNetFlow\fR table.
 .
 .ST "Database Values"
+.PP
 Each column in the database accepts a fixed type of data.  The
 currently defined basic types, and their representations, are:
 .IP "integer"
@@ -482,7 +483,6 @@ as \fB{}\fR, and curly braces may be optionally enclose non-empty maps
 as well.
 .
 .ST "Database Command Syntax"
-.
 .IP "\fBlist \fItable \fR[\fIrecord\fR]..."
 List the values of all columns of each specified \fIrecord\fR.  If no
 records are specified, lists all the records in \fItable\fR.
diff --git a/vswitchd/.gitignore b/vswitchd/.gitignore
index 872a726..bfc4373 100644
--- a/vswitchd/.gitignore
+++ b/vswitchd/.gitignore
@@ -4,8 +4,7 @@
 /ovs-brcompatd.8
 /ovs-vswitchd
 /ovs-vswitchd.8
-/ovs-vswitchd.conf.5
+/ovs-vswitchd.conf.db.5
 /vswitch-idl.c
 /vswitch-idl.h
 /vswitch-idl.ovsidl
-/vswitch-idl.txt
diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk
index c38add6..2cff64b 100644
--- a/vswitchd/automake.mk
+++ b/vswitchd/automake.mk
@@ -33,15 +33,24 @@ EXTRA_DIST += \
 	vswitchd/ovs-vswitchd.8.in \
 	vswitchd/ovs-brcompatd.8.in
 
-
 # vswitch schema and IDL
 OVSIDL_BUILT += \
 	vswitchd/vswitch-idl.c \
 	vswitchd/vswitch-idl.h \
 	vswitchd/vswitch-idl.ovsidl
 VSWITCH_IDL_FILES = vswitchd/vswitch.ovsschema vswitchd/vswitch-idl.ann
-noinst_DATA += vswitchd/vswitch-idl.txt
-EXTRA_DIST += $(VSWITCH_IDL_FILES) vswitchd/vswitch-idl.txt
+EXTRA_DIST += $(VSWITCH_IDL_FILES)
 vswitchd/vswitch-idl.ovsidl: $(VSWITCH_IDL_FILES)
 	$(OVSDB_IDLC) -C $(srcdir) annotate $(VSWITCH_IDL_FILES) > $@.tmp
 	mv $@.tmp $@
+
+# vswitch schema documentation
+EXTRA_DIST += vswitchd/vswitch.xml
+dist_man_MANS += vswitchd/ovs-vswitchd.conf.db.5
+vswitchd/ovs-vswitchd.conf.db.5: \
+	ovsdb/ovsdb-doc.in vswitchd/vswitch.xml vswitchd/vswitch.ovsschema
+	$(OVSDB_DOC) \
+		--title="ovs-vswitchd.conf.db" \
+		$(srcdir)/vswitchd/vswitch.ovsschema \
+		$(srcdir)/vswitchd/vswitch.xml > $@.tmp
+	mv $@.tmp $@
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
new file mode 100644
index 0000000..b21b560
--- /dev/null
+++ b/vswitchd/vswitch.xml
@@ -0,0 +1,726 @@
+<database title="Open vSwitch Configuration Database">
+  <p>A database with this schema holds the configuration for one Open
+    vSwitch daemon.  The root of the configuration for the daemon is
+    the <ref table="Open_vSwitch"/> table, which must have exactly one
+    record.  Records in other tables are significant only when they
+    can be reached directly or indirectly from the
+    <ref table="Open_vSwitch"/> table.</p>
+
+  <table name="Open_vSwitch" title="Open vSwitch configuration.">
+    Configuration for an Open vSwitch daemon.  There must be exactly one record
+    in the <ref table="Open_vSwitch"/> table.
+
+    <group title="Configuration">
+      <column name="bridges">
+        Set of bridges managed by the daemon.
+      </column>
+
+      <column name="controller">
+        Default <ref table="Controller"/> used by bridges.  If a
+        <ref table="Bridge"/> record has a <ref table="Bridge"
+        column="controller"/> column, then this
+        <ref table="Controller"/> is used instead.
+      </column>
+
+      <column name="managers">
+        Remote database clients to which the Open vSwitch's database server
+        should connect or to which it should listen.
+      </column>
+
+      <column name="ssl">
+        SSL used globally by the daemon.
+      </column>
+    </group>
+
+    <group title="Status">
+      <column name="next_cfg">
+        Sequence number for client to increment.  When a client modifies
+        any part of the database configuration and wishes to wait for
+        Open vSwitch to finish applying the changes, it may increment
+        this sequence number.
+      </column>
+
+      <column name="cur_cfg">
+        Sequence number that Open vSwitch sets to the current value of
+        <ref column="next_cfg"/> after it finishing applying a set of
+        configuration changes.
+      </column>
+    </group>
+  </table>
+
+  <table name="Bridge">
+    <p>
+      Configuration for a bridge within an
+      <ref table="Open_vSwitch"/>.
+    </p>
+    <p>
+      A <ref table="Bridge"/> record represents an Ethernet switch with one or
+      more ``ports,'' which are the <ref table="Port"/> records pointed to by
+      the <ref table="Bridge"/>'s <ref column="ports"/> column.
+    </p>
+
+    <group title="Core Features">
+      <column name="name">
+        Bridge identifier.  Should be alphanumeric and no more than about 8
+        bytes long.  Must be unique among the names of ports, interfaces, and
+        bridges on a host.
+      </column>
+
+      <column name="ports">
+        Ports included in the bridge.
+      </column>
+
+      <column name="mirrors">
+        Port mirroring configuration.
+      </column>
+
+      <column name="netflow">
+        NetFlow configuration.
+      </column>
+
+      <column name="sflow">
+        sFlow configuration.
+      </column>
+
+      <column name="flood_vlans">
+        VLAN IDs of VLANs on which MAC address learning should be disabled, so
+        that packets are flooded instead of being sent to specific ports that
+        are believed to contain packets' destination MACs.  This should
+        ordinarily be used to disable MAC learning on VLANs used for mirroring
+        (RSPAN VLANs).  It may also be useful for debugging.
+      </column>
+    </group>
+
+    <group title="OpenFlow Configuration">
+      <column name="controller">
+        OpenFlow controller.  If unset, defaults to that specified by
+        <ref column="controller" table="Open_vSwitch"/> in the
+        <ref table="Open_vSwitch"/> table.  If the default is also unset, then
+        no OpenFlow controller will be used.
+      </column>
+
+      <column name="datapath_id">
+        Reports the OpenFlow datapath ID in use.  Exactly 12 hex digits.
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="datapath_type">
+        Name of datapath provider.  The kernel datapath has
+        type <code>system</code>.  The userspace datapath has
+        type <code>netdev</code>.
+      </column>
+
+      <column name="external_ids">
+        Key-value pairs that identify this bridge's role in external systems.
+        The currently defined key-value pairs are:
+        <dl>
+          <dt><code>xs-network-uuids</code></dt>
+          <dd>Space-delimited set of the Citrix XenServer network UUIDs with
+            which this bridge is associated.</dd>
+          <dt><code>xs-network-names</code></dt>
+          <dd>Semicolon-delimited set of Citrix XenServer network names with
+            which this bridge is associated.</dd>
+        </dl>
+      </column>
+
+      <column name="other_config">
+        Key-value pairs for configuring rarely used bridge
+        features.  The currently defined key-value pairs are:
+        <dl>
+          <dt><code>datapath-id</code></dt>
+          <dd>Exactly 12 hex
+            digits to set the OpenFlow datapath ID to a specific
+            value.</dd>
+          <dt><code>hwaddr</code></dt>
+          <dd>Exactly 12 hex digits in the form
+            <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>
+            to set the hardware address of the local port and influence the
+            datapath ID.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Port" table="Port or bond configuration.">
+    <p>A port within a <ref table="Bridge"/>.</p>
+    <p>Most commonly, a port has exactly one ``interface,'' pointed to by its
+      <ref column="interface"/> column.  Such a port logically
+      corresponds to a port on a physical Ethernet switch.  A port
+      with more than one interface is a ``bonded port'' (see
+      <ref group="Bonding Configuration"/>).</p>
+    <p>Some properties that one might think as belonging to a port are actually
+      part of the port's <ref table="Interface"/> members.</p>
+
+    <column name="name">
+      Port name.  Should be alphanumeric and no more than about 8
+      bytes long.  May be the same as the interface name, for
+      non-bonded ports.  Must otherwise be unique among the names of
+      ports, interfaces, and bridges on a host.
+    </column>
+
+    <column name="interfaces">
+      The port's interfaces.  If there is more than one, this is a
+      bonded Port.
+    </column>
+
+    <group title="VLAN Configuration">
+      <p>A bridge port must be configured for VLANs in one of two
+        mutually exclusive ways:
+        <ul>
+          <li>A ``trunk port'' has an empty value for
+            <ref column="tag"/> and a possibly non-empty
+            <ref column="trunks"/> value.</li>
+          <li>An ``implicitly tagged VLAN port'' or ``access port''
+            has an nonempty value for <ref column="tag"/> and an empty
+            <ref column="trunks"/> value.</li>
+        </ul>
+        If <ref column="trunks"/> and <ref column="tag"/> are both
+        nonempty, the configuration is ill-formed.
+      </p>
+
+      <column name="tag">
+        <p>If nonempty, this port's implicitly tagged VLAN.  Frames
+          arriving on trunk ports will be forwarded to this port only
+          if they are tagged with the given VLAN.  Frames arriving on
+          other VLAN ports will be forwarded to this port only if they
+          have the same <ref column="tag"/> value.  Frames forwarded
+          to this port will not have an 802.1Q header.</p>
+        <p>When a frame with a 802.1Q header that indicates a nonzero VLAN is
+          received on an implicit VLAN port, it is discarded.</p>
+        <p>Must be empty if this is a trunk port.</p>
+      </column>
+
+      <column name="trunks">
+        <p>The 802.1Q VLAN(s) that this port trunks.  If the column is
+          empty, then the port trunks all VLANs as well as packets that
+          have no VLAN header.  Otherwise, only frames that have an
+          802.1Q header with one of the specified VLANs are accepted.
+          If <code>0</code> is included, then frames without an 802.1Q
+          header are also accepted.</p>
+        <p>Must be empty unless this is a trunk port.</p>
+      </column>
+    </group>
+
+    <group title="Bonding Configuration">
+      <p>A port that has more than one interface is a ``bonded port.''
+        Bonding allows for load balancing and fail-over.  Open vSwitch
+        supports ``source load balancing'' (SLB) bonding, which
+        assigns flows to slaves based on source MAC address, with
+        periodic rebalancing as traffic patterns change.  This form of
+        bonding does not require 802.3ad or other special support from
+        the upstream switch to which the slave devices are
+        connected.</p>
+
+      <p>These columns apply only to bonded ports.  Their values are
+        otherwise ignored.</p>
+
+      <column name="bond_updelay">
+        <p>For a bonded port, the number of milliseconds for which carrier must
+          stay up on an interface before the interface is considered to be up.
+          Specify <code>0</code> to enable the interface immediately.</p>
+        <p>This setting is honored only when at least one bonded interface is
+          already enabled.  When no interfaces are enabled, then the first bond
+          interface to come up is enabled immediately.</p>
+      </column>
+
+      <column name="bond_downdelay">
+        For a bonded port, the number of milliseconds for which carrier must
+        stay down on an interface before the interface is considered to be
+        down.  Specify <code>0</code> to enable the interface immediately.
+      </column>
+
+      <column name="bond_fake_iface">
+        For a bonded port, whether to create a fake interface with the name of
+        the port.  Use only for compatibility with legacy software that
+        requires this.
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="mac">
+        The MAC address to use for this port for the purpose of choosing the
+        bridge's MAC address.  This column does not necessarily reflect the
+        port's actual MAC address, nor will setting it change the port's actual
+        MAC address.
+      </column>
+
+      <column name="fake_bridge">
+        Does this port represent a sub-bridge for its tagged VLAN within the
+        Bridge?  See ovs-vsctl(8) for more information.
+      </column>
+
+      <column name="external_ids">
+        Key-value pairs that identify this port's role in external systems.  No
+        key-value pairs native to <ref table="Port"/> are currently defined.
+        For fake bridges (see the <ref column="fake_bridge"/> column), external
+        IDs for the fake bridge are defined here by prefixing their keys
+        with <code>fake-bridge-</code>,
+        e.g. <code>fake-bridge-xs-network-uuids</code>.
+      </column>
+
+      <column name="other_config">
+        Key-value pairs for configuring rarely used port features.  The
+        currently defined key-value pairs are:
+        <dl>
+          <dt><code>hwaddr</code></dt>
+          <dd>Exactly 12 hex digits in the form
+            <code><var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var></code>.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Interface" title="One physical network device in a Port.">
+    An interface within a <ref table="Port"/>.
+
+    <group title="Core Features">
+      <column name="name">
+        Interface name.  Should be alphanumeric and no more than about 8 bytes
+        long.  May be the same as the port name, for non-bonded ports.  Must
+        otherwise be unique among the names of ports, interfaces, and bridges
+        on a host.
+      </column>
+
+      <column name="mac">
+        <p>Ethernet address to set for this interface.  If unset then the
+          default MAC address is used:</p>
+        <ul>
+          <li>For the local interface, the default is the lowest-numbered MAC
+            address among the other bridge ports, either the value of the
+            <ref table="Port" column="mac"/> in its <ref table="Port"/> record,
+            if set, or its actual MAC (for bonded ports, the MAC of its slave
+            whose name is first in alphabetical order).  Internal ports and
+            bridge ports that are used as port mirroring destinations (see the
+            <ref table="Mirror"/> table) are ignored.</li>
+          <li>For other internal interface, the default MAC is randomly
+            generated.</li>
+          <li>External interfaces typically have a MAC address associated with
+            their hardware.</li>
+        </ul>
+        <p>Some interfaces may not have a software-controllable MAC
+        address.</p>
+      </column>
+
+      <column name="ofport">
+        <p>OpenFlow port number for this interface.  Unlike most columns, this
+          column's value should be set only by Open vSwitch itself.  Other
+          clients should set this column to an empty set (the default) when
+          creating an <ref table="Interface"/>.</p>
+        <p>Open vSwitch populates this column when the port number becomes
+          known.  If the interface is successfully added,
+          <ref column="ofport"/> will be set to a number between 1 and 65535
+          (generally either in the range 1 to <code>0xff00</code>, exclusive,
+          or <code>0xfffe</code>, the port number for the OpenFlow ``local
+          port'').  If the interface cannot be added then Open vSwitch sets
+          this column to <code>-1</code>.</p>
+      </column>
+    </group>
+
+    <group title="System-Specific Details">
+      <column name="type">
+        The interface type, one of:
+        <dl>
+          <dt><code>system</code></dt>
+          <dd>An ordinary network device, e.g. <code>eth0</code> on Linux.
+            Sometimes referred to as ``external interfaces'' since they are
+            generally connected to hardware external to that on which the Open
+            vSwitch is running.  The empty string is a synonym for
+            <code>system</code>.</dd>
+          <dt><code>internal</code></dt>
+          <dd>A simulated network devices that sent and receive traffic.  An
+            internal interface whose <ref column="name"/> is the same as its
+            bridge's <ref table="Open_vSwitch" column="name"/> is called the
+            ``local interface.''  It does not make sense to bond an internal
+            interface, so the terms ``port'' and ``interface'' are often used
+            imprecisely for internal interfaces.</dd>
+          <dt><code>tap</code></dt>
+          <dd>A TUN/TAP device managed by Open vSwitch.</dd>
+          <dt><code>gre</code></dt>
+          <dd>A GRE tunnel device managed by Open vSwitch.</dd>
+        </dl>
+      </column>
+
+      <column name="options">
+        Configuration options whose interpretation varies based on
+        <ref column="type"/>.
+      </column>
+    </group>
+
+    <group title="Ingress Policing">
+      <column name="ingress_policing_burst">
+        <p>Maximum burst size for data received on this interface, in kb.  The
+          default burst size if set to <code>0</code> is 1000 kb.  This value
+          has no effect if <ref column="ingress_policing_rate"/>
+          is <code>0</code>.</p>
+        <p>The burst size should be at least the size of the interface's
+          MTU.</p>
+      </column>
+
+      <column name="ingress_policing_rate">
+        <p>Maximum rate for data received on this interface, in kbps.  Data
+          received faster than this rate is dropped.  Set to <code>0</code> to
+          disable policing.</p>
+        <p>The meaning of ``ingress'' is from Open vSwitch's perspective.  If
+          configured on a physical interface, then it limits the rate at which
+          traffic is allowed into the system from the outside.  If configured
+          on a virtual interface that is connected to a virtual machine, then
+          it limits the rate at which the guest is able to transmit.</p>
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="external_ids">
+        Key-value pairs that identify this interface's role in external
+        systems.  The currently defined key-value pairs are:
+        <dl>
+          <dt><code>xs-vif-uuid</code></dt>
+          <dd>UUID of the Citrix XenServer VIF associated with this
+            interface</dd>
+          <dt><code>xs-network-uuid</code></dt>
+          <dd>UUID of the Citrix XenServer network to which this interface is
+            attached</dd>
+          <dt><code>xs-vif-vm-uuid</code></dt>
+          <dd>UUID of the Citrix XenServer VM to which this interface
+            belongs</dd>
+          <dt><code>xs-vif-mac</code></dt>
+          <dd>The value of the "MAC" field in the Citrix XenServer VIF record
+            for this interface.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Mirror" title="Port mirroring (SPAN/RSPAN).">
+    <p>A port mirror within a <ref table="Bridge"/>.</p>
+    <p>A port mirror configures a bridge to send selected frames to special
+      ``mirrored'' ports, in addition to their normal destinations.  Mirroring
+      traffic may also be referred to as SPAN or RSPAN, depending on the
+      mechanism used for delivery.</p>
+
+    <column name="name">
+      Arbitrary identifier for the <ref table="Mirror"/>.
+    </column>
+
+    <group title="Selecting Packets for Mirroring">
+      <column name="select_dst_port">
+        Ports on which departing packets are selected for mirroring.
+      </column>
+
+      <column name="select_src_port">
+        Ports on which arriving packets are selected for mirroring.  If this
+        column and <ref column="select_dst_port"/> are both empty, then all
+        packets on all ports are selected for mirroring.
+      </column>
+
+      <column name="select_vlan">
+        VLANs on which packets are selected for mirroring.  An empty set
+        selects packets on all VLANs.
+      </column>
+    </group>
+
+    <group title="Mirroring Destination Configuration">
+      <column name="output_port">
+        <p>Output port for selected packets, if nonempty.  Mutually exclusive
+          with <ref column="output_vlan"/>.</p>
+        <p>Specifying a port for mirror output reserves that port exclusively
+          for mirroring.  No frames other than those selected for mirroring
+          will be forwarded to the port, and any frames received on the port
+          will be discarded.</p>
+        <p>This type of mirroring is sometimes called SPAN.</p>
+      </column>
+
+      <column name="output_vlan">
+        <p>Output VLAN for selected packets, if nonempty.  Mutually exclusive
+          with <ref column="output_port"/>.</p>
+        <p>The frames will be sent out all ports that trunk
+          <ref column="output_vlan"/>, as well as any ports with implicit VLAN
+          <ref column="output_vlan"/>.  When a mirrored frame is sent out a
+          trunk port, the frame's VLAN tag will be set to
+          <ref column="output_vlan"/>, replacing any existing tag; when it is
+          sent out an implicit VLAN port, the frame will not be tagged.  This
+          type of mirroring is sometimes called RSPAN.</p>
+        <p><em>Please note:</em> Mirroring to a VLAN can disrupt a network that
+          contains unmanaged switches.  Consider an unmanaged physical switch
+          with two ports: port 1, connected to an end host, and port 2,
+          connected to an Open vSwitch configured to mirror received packets
+          into VLAN 123 on port 2.  Suppose that the end host sends a packet on
+          port 1 that the physical switch forwards to port 2.  The Open vSwitch
+          forwards this packet to its destination and then reflects it back on
+          port 2 in VLAN 123.  This reflected packet causes the unmanaged
+          physical switch to replace the MAC learning table entry, which
+          correctly pointed to port 1, with one that incorrectly points to port
+          2.  Afterward, the physical switch will direct packets destined for
+          the end host to the Open vSwitch on port 2, instead of to the end
+          host on port 1, disrupting connectivity.  If mirroring to a VLAN is
+          desired in this scenario, then the physical switch must be replaced
+          by one that learns Ethernet addresses on a per-VLAN basis.  In
+          addition, learning should be disabled on the VLAN containing mirrored
+          traffic. If this is not done then intermediate switches will learn
+          the MAC address of each end host from the mirrored traffic.  If
+          packets being sent to that end host are also mirrored, then they will
+          be dropped since the switch will attempt to send them out the input
+          port. Disabling learning for the VLAN will cause the switch to
+          correctly send the packet out all ports configured for that VLAN.  If
+          Open vSwitch is being used as an intermediate switch, learning can be
+          disabled by adding the mirrored VLAN to <ref column="flood_vlans"/>
+          in the appropriate <ref table="Bridge"/> table or tables.</p>
+      </column>
+    </group>
+  </table>
+
+  <table name="Controller" title="OpenFlow controller configuration.">
+    An OpenFlow controller.
+
+    <group title="Core Features">
+      <column name="target">
+        Connection method for controller.
+        The following connection methods are currently
+        supported:
+        <dl>
+          <dt><code>ssl:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            <p>The specified SSL <var>port</var> (default: 6633) on the host at
+              the given <var>ip</var>, which must be expressed as an IP address
+              (not a DNS name).  The <ref table="Open_vSwitch" column="ssl"/>
+              column in the <ref table="Open_vSwitch"/> must point to a valid
+              SSL configuration when this form is used.</p>
+            <p>SSL support is an optional feature that is not always built as
+              part of Open vSwitch.</p>
+          </dd>
+          <dt><code>tcp:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>The specified TCP <var>port</var> (default: 6633) on the host at
+            the given <var>ip</var>, which must be expressed as an IP address
+            (not a DNS name).</dd>
+          <dt><code>discover</code></dt>
+          <dd>Enables controller discovery.</dd>
+          <dt><code>none</code></dt>
+          <dd>Disables the controller.</dd>
+        </dl>
+      </column>
+
+      <column name="connection_mode">
+        Either <code>in-band</code> or <code>out-of-band</code>.  If not
+        specified, the default is implementation-specific.
+      </column>
+    </group>
+
+    <group title="Controller Failure Detection and Handling">
+      <column name="max_backoff">
+        Maximum number of milliseconds to wait between connection attempts.
+        Default is implementation-specific.
+      </column>
+
+      <column name="inactivity_probe">
+        Maximum number of milliseconds of idle time on connection to
+        controller before sending an inactivity probe message.  If Open
+        vSwitch does not communicate with the controller for the specified
+        number of seconds, it will send a probe.  If a response is not
+        received for the same additional amount of time, Open vSwitch
+        assumes the connection has been broken and attempts to reconnect.
+        Default is implementation-specific.
+      </column>
+
+      <column name="fail_mode">
+        <p>When a controller is configured, it is, ordinarily, responsible
+          for setting up all flows on the switch.  Thus, if the connection to
+          the controller fails, no new network connections can be set up.
+          If the connection to the controller stays down long enough,
+          no packets can pass through the switch at all.  This setting
+          determines the switch's response to such a situation.  It may be set
+          to one of the following:
+          <dl>
+            <dt><code>standalone</code></dt>
+            <dd>If no message is received from the controller for three
+              times the inactivity probe interval
+              (see <ref column="inactivity_probe"/>), then Open vSwitch
+              will take over responsibility for setting up flows.  In
+              this mode, Open vSwitch causes the datapath to act like an
+              ordinary MAC-learning switch.  Open vSwitch will continue
+              to retry connecting to the controller in the background
+              and, when the connection succeeds, it will discontinue its
+              standalone behavior.</dd>
+            <dt><code>secure</code></dt>
+            <dd>Open vSwitch will not set up flows on its own when the
+              controller connection fails.  It will continue retry
+              connecting to the controller forever.</dd>
+          </dl>
+        </p>
+        <p>If this value is unset, the default is
+        implementation-specific.</p>
+      </column>
+    </group>
+
+    <group title="OpenFlow Rate Limiting">
+        <column name="controller_burst_limit">
+          In conjunction with <ref column="controller_rate_limit"/>,
+          the maximum number of unused packet credits that the bridge will
+          allow to accumulate, in packets.  If not specified, the default
+          is implementation-specific.
+        </column>
+
+        <column name="controller_rate_limit">
+          <p>The maximum rate at which packets in unknown flows will be
+            forwarded to the OpenFlow controller, in packets per second.  This
+            feature prevents a single bridge from overwhelming the controller.
+            If not specified, the default is implementation-specific.</p>
+          <p>In addition, when a high rate triggers rate-limiting, Open
+            vSwitch queues controller packets for each port and transmits
+            them to the controller at the configured rate.  The number of
+            queued packets is limited by
+            the <ref column="controller_burst_limit"/> value.  The packet
+            queue is shared fairly among the ports on a bridge.</p><p>Open
+            vSwitch maintains two such packet rate-limiters per bridge.
+            One of these applies to packets sent up to the controller
+            because they do not correspond to any flow.  The other applies
+            to packets sent up to the controller by request through flow
+            actions. When both rate-limiters are filled with packets, the
+            actual rate that packets are sent to the controller is up to
+            twice the specified rate.</p>
+        </column>
+    </group>
+
+    <group title="Additional Configuration for Discovery">
+      <column name="discover_accept_regex">
+        If <ref column="target"/> is <code>discover</code>, a POSIX
+        extended regular expression against which the discovered controller
+        location is validated.  The regular expression is implicitly
+        anchored at the beginning of the controller location string, as
+        if it begins with <code>^</code>.  If not specified, the default
+        is implementation-specific.
+      </column>
+
+      <column name="discover_update_resolv_conf">
+        If <ref column="target"/> is <code>discover</code>,
+        whether to update <code>/etc/resolv.conf</code> when the
+        controller is discovered.  If not specified, the default
+        is implementation-specific.  Open vSwitch will only modify
+        <code>/etc/resolv.conf</code> if the DHCP response that it receives
+        specifies one or more DNS servers.
+      </column>
+    </group>
+
+    <group title="Additional Configuration without Discovery">
+      <column name="local_gateway">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        address of the gateway to configure on the local port.
+      </column>
+
+      <column name="local_ip">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        address to configure on the local port.
+      </column>
+
+      <column name="local_netmask">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        netmask to configure on the local port.
+      </column>
+    </group>
+  </table>
+
+  <table name="NetFlow">
+    A NetFlow target.  NetFlow is a protocol that exports a number of
+    details about terminating IP flows, such as the principals involved
+    and duration.
+
+    <column name="targets">
+      NetFlow targets in the form
+      <code><var>ip</var>:<var>port</var></code>.  The <var>ip</var>
+      must be specified numerically, not as a DNS name.
+    </column>
+
+    <column name="engine_id">
+      Engine ID to use in NetFlow messages.  Defaults to datapath index
+      if not specified.
+    </column>
+
+    <column name="engine_type">
+      Engine type to use in NetFlow messages.  Defaults to datapath
+      index if not specified.
+    </column>
+
+    <column name="active_timeout">
+      The interval at which NetFlow records are sent for flows that are
+      still active, in seconds.  A value of <code>0</code> requests the
+      default timeout (currently 600 seconds); a value of <code>-1</code>
+      disables active timeouts.
+    </column>
+
+    <column name="add_id_to_interface">
+      <p>If this column's value is <code>false</code>, the ingress and egress
+        interface fields of NetFlow flow records are derived from OpenFlow port
+        numbers.  When it is <code>true</code>, the 7 most significant bits of
+        these fields will be replaced by the least significant 7 bits of the
+        engine id.  This is useful because many NetFlow collectors do not
+        expect multiple switches to be sending messages from the same host, so
+        they do not store the engine information which could be used to
+        disambiguate the traffic.</p>
+      <p>When this option is enabled, a maximum of 508 ports are supported.</p>
+    </column>
+  </table>
+
+  <table name="SSL">
+    SSL configuration for an Open_vSwitch.
+
+    <column name="private_key">
+      Name of a PEM file containing the private key used as the switch's
+      identity for SSL connections to the controller.
+    </column>
+
+    <column name="certificate">
+      Name of a PEM file containing a certificate, signed by the
+      certificate authority (CA) used by the controller and manager,
+      that certifies the switch's private key, identifying a trustworthy
+      switch.
+    </column>
+
+    <column name="ca_cert">
+      Name of a PEM file containing the CA certificate used to verify
+      that the switch is connected to a trustworthy controller.
+    </column>
+
+    <column name="bootstrap_ca_cert">
+      If set to <code>true</code>, then Open vSwitch will attempt to
+      obtain the CA certificate from the controller on its first SSL
+      connection and save it to the named PEM file. If it is successful,
+      it will immediately drop the connection and reconnect, and from then
+      on all SSL connections must be authenticated by a certificate signed
+      by the CA certificate thus obtained.  <em>This option exposes the
+        SSL connection to a man-in-the-middle attack obtaining the initial
+        CA certificate.</em>  It may still be useful for bootstrapping.
+    </column>
+  </table>
+
+  <table name="sFlow">
+    <p>An sFlow(R) target.  sFlow is a protocol for remote monitoring
+      of switches.</p>
+
+    <column name="agent">
+      IP address to report as ``agent address'' to collectors.  If not
+      specified, defaults to the <ref table="Controller" column="local_ip"/> in
+      the collector's <ref table="Controller"/>.  If neither is specified,
+      sFlow is disabled.
+    </column>
+
+    <column name="header">
+      Number of bytes of a sampled packet to send to the collector.
+      If not specified, the default is 128 bytes.
+    </column>
+
+    <column name="polling">
+      Polling rate in seconds to send port statistics to the collector.
+      If not specified, defaults to 30 seconds.
+    </column>
+
+    <column name="sampling">
+      Rate at which packets should be sampled and sent to the collector.
+      If not specified, defaults to 400, which means one out of 400
+      packets, on average, will be sent to the collector.
+    </column>
+
+    <column name="targets">
+      sFlow targets in the form
+      <code><var>ip</var>:<var>port</var></code>.
+    </column>
+  </table>
+</database>
-- 
1.6.6.1





More information about the dev mailing list