diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 0a80a5f41277..0cb5ef571676 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -54,46 +54,6 @@ The remainder are still in roff format can be found below:
      - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovs-actions.7.pdf>`__
      - `(html) <http://www.openvswitch.org/support/dist-docs/ovs-actions.7.html>`__
      - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovs-actions.7.txt>`__
-   * - ovn-architecture(7)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-architecture.7.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-architecture.7.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-architecture.7.txt>`__
-   * - ovn-controller(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-controller.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-controller.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-controller.8.txt>`__
-   * - ovn-controller-vtep(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-controller-vtep.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-controller-vtep.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-controller-vtep.8.txt>`__
-   * - ovn-ctl(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-ctl.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-ctl.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-ctl.8.txt>`__
-   * - ovn-nb(5)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-nb.5.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-nb.5.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-nb.5.txt>`__
-   * - ovn-nbctl(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-nbctl.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-nbctl.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-nbctl.8.txt>`__
-   * - ovn-northd(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-northd.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-northd.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-northd.8.txt>`__
-   * - ovn-sb(5)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-sb.5.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-sb.5.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-sb.5.txt>`__
-   * - ovn-sbctl(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-sbctl.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-sbctl.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-sbctl.8.txt>`__
-   * - ovn-trace(8)
-     - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovn-trace.8.pdf>`__
-     - `(html) <http://www.openvswitch.org/support/dist-docs/ovn-trace.8.html>`__
-     - `(plain text) <http://www.openvswitch.org/support/dist-docs/ovn-trace.8.txt>`__
    * - ovs-appctl(8)
      - `(pdf) <http://www.openvswitch.org/support/dist-docs/ovs-appctl.8.pdf>`__
      - `(html) <http://www.openvswitch.org/support/dist-docs/ovs-appctl.8.html>`__
diff --git a/Documentation/ref/ovsdb.7.rst b/Documentation/ref/ovsdb.7.rst
index b12d8066c846..b1f3f5d494c3 100644
--- a/Documentation/ref/ovsdb.7.rst
+++ b/Documentation/ref/ovsdb.7.rst
@@ -47,11 +47,11 @@ While OVSDB is general-purpose and not particularly specialized for use with
 Open vSwitch, Open vSwitch does use it for multiple purposes.  The leading use
 of OVSDB is for configuring and monitoring ``ovs-vswitchd(8)``, the Open
 vSwitch switch daemon, using the schema documented in
-``ovs-vswitchd.conf.db(5)``.  The Open Virtual Network (OVN) sub-project of OVS
-uses two OVSDB schemas, documented in ``ovn-nb(5)`` and ``ovn-sb(5)``.
-Finally, Open vSwitch includes the "VTEP" schema, documented in
-``vtep(5)`` that many third-party hardware switches support for
-configuring VXLAN, although OVS itself does not directly use this schema.
+``ovs-vswitchd.conf.db(5)``.  The Open Virtual Network (OVN) project uses two
+OVSDB schemas, documented as part of that project.  Finally, Open vSwitch
+includes the "VTEP" schema, documented in ``vtep(5)`` that many third-party
+hardware switches support for configuring VXLAN, although OVS itself does not
+directly use this schema.
 
 The OVSDB protocol specification allows independent, interoperable
 implementations of OVSDB to be developed.  Open vSwitch includes an OVSDB
@@ -65,9 +65,8 @@ otherwise be unclear from the context.
 
 In addition to these generic OVSDB server and client tools, Open vSwitch
 includes tools for working with databases that have specific schemas:
-``ovs-vsctl`` works with the ``ovs-vswitchd`` configuration database,
-``vtep-ctl`` works with the VTEP database, ``ovn-nbctl`` works with
-the OVN Northbound database, and so on.
+``ovs-vsctl`` works with the ``ovs-vswitchd`` configuration database and
+``vtep-ctl`` works with the VTEP database.
 
 RFC 7047 specifies the OVSDB protocol but it does not specify an on-disk
 storage format.  Open vSwitch includes ``ovsdb-tool(1)`` for working with its
@@ -183,8 +182,8 @@ can switch the backup server to an active role with the ``ovs-appctl`` command
 access to the now-active server.  Of course, administrators are slow to respond
 compared to software, so in practice external management software detects the
 active server's failure and changes the backup server's role.  For example, the
-"Integration Guide for Centralized Control" in the Open vSwitch documentation
-describes how to use Pacemaker for this purpose in OVN.
+"Integration Guide for Centralized Control" in the OVN documentation describes
+how to use Pacemaker for this purpose in OVN.
 
 Suppose an active server fails and its backup is promoted to active.  If the
 failed server is revived, it must be started as a backup server.  Otherwise, if
@@ -222,11 +221,10 @@ To set up a clustered database, first initialize it on a single node by running
 arguments, the ``create-cluster`` command can create an empty database or copy
 a standalone database's contents into the new database.
 
-To configure a client, such as ``ovn-controller`` or ``ovn-sbctl``, to use a
-clustered database, first configure all of the servers to listen on a
-connection method that the client can reach, then point the client to all of
-the servers' connection methods, comma-separated.  See `Connection Methods`_,
-below, for more detail.
+To configure a client to use a clustered database, first configure all of the
+servers to listen on a connection method that the client can reach, then point
+the client to all of the servers' connection methods, comma-separated.  See
+`Connection Methods`_, below, for more detail.
 
 Open vSwitch 2.9 introduced support for the clustered service model.
 
@@ -328,8 +326,8 @@ following consequences:
 * When a client conducts a mix of read and write transactions across more than
   one server in a cluster, it can see inconsistent results because a read
   transaction might read stale data whose updates have not yet propagated from
-  the leader.  By default, ``ovn-sbctl`` and similar utilities connect to the
-  cluster leader to avoid this issue.
+  the leader.  By default, utilities such as ``ovn-sbctl`` (in OVN) connect to
+  the cluster leader to avoid this issue.
 
   The same might occur for transactions against a single follower except that
   the OVSDB server ensures that the results of a write forwarded to the leader
@@ -649,7 +647,9 @@ Open vSwitch implementations of generic OVSDB functionality:
 ``ovsdb-server(1)``, ``ovsdb-client(1)``, ``ovsdb-tool(1)``.
 
 Tools for working with databases that have specific OVSDB schemas:
-``ovs-vsctl(8)``, ``vtep-ctl(8)``, ``ovn-nbctl(8)``, ``ovn-sbctl(8)``.
+``ovs-vsctl(8)``, ``vtep-ctl(8)``, and (in OVN) ``ovn-nbctl(8)``,
+``ovn-sbctl(8)``.
 
 OVSDB schemas for Open vSwitch and related functionality:
-``ovs-vswitchd.conf.db(5)``, ``vtep(5)``, ``ovn-nb(5)``, ``ovn-sb(5)``.
+``ovs-vswitchd.conf.db(5)``, ``vtep(5)``, and (in OVN) ``ovn-nb(5)``,
+``ovn-sb(5)``.
diff --git a/Documentation/topics/testing.rst b/Documentation/topics/testing.rst
index eef61e5d5847..6c93826fbc40 100644
--- a/Documentation/topics/testing.rst
+++ b/Documentation/topics/testing.rst
@@ -127,7 +127,7 @@ using the ``check-lcov`` target::
 
 All the same options are available via TESTSUITEFLAGS. For example::
 
-    $ make check-lcov TESTSUITEFLAGS='-j8 -k ovn'
+    $ make check-lcov TESTSUITEFLAGS='-j8 -k ovsdb'
 
 .. _testing-valgrind:
 
diff --git a/Documentation/topics/tracing.rst b/Documentation/topics/tracing.rst
index 6150698d538f..0e82cfea0044 100644
--- a/Documentation/topics/tracing.rst
+++ b/Documentation/topics/tracing.rst
@@ -31,9 +31,7 @@ to know what is happening with packets as they go through the data plane
 processing.
 
 The `ovs-vswitchd(8)`_ manpage describes basic usage of the
-ofproto/trace command used for tracing in Open vSwitch.  For a tool
-with a goal similar to ofproto/trace for tracing packets through OVN
-logical switches, see `ovn-trace(8)`_.
+ofproto/trace command used for tracing in Open vSwitch.
 
 Packet Tracing
 --------------
@@ -132,4 +130,3 @@ This document is heavily based on content from Flavio Bruno Leitner at Red Hat:
 
 .. _ovs-vswitchd(8): http://openvswitch.org/support/dist-docs/ovs-vswitchd.8.html
 .. _ovs-fields(7): http://openvswitch.org/support/dist-docs/ovs-fields.7.pdf
-.. _ovn-trace(8): http://openvswitch.org/support/dist-docs/ovn-trace.8.html
diff --git a/Documentation/tutorials/faucet.rst b/Documentation/tutorials/faucet.rst
index 9696dfd0230b..b7bfb575bc45 100644
--- a/Documentation/tutorials/faucet.rst
+++ b/Documentation/tutorials/faucet.rst
@@ -580,7 +580,7 @@ Tracing
 Let's go a level deeper.  So far, everything we've done has been
 fairly general.  We can also look at something more specific: the path
 that a particular packet would take through Open vSwitch.  We can use
-OVN ``ofproto/trace`` command to play "what-if?" games.  This command
+the ``ofproto/trace`` command to play "what-if?" games.  This command
 is one that we send directly to ``ovs-vswitchd``, using the
 ``ovs-appctl`` utility.
 
diff --git a/Makefile.am b/Makefile.am
index 6030cdb495d6..b279303d186c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -495,6 +495,5 @@ include vtep/automake.mk
 include datapath-windows/automake.mk
 include datapath-windows/include/automake.mk
 include windows/automake.mk
-include ovn/automake.mk
 include selinux/automake.mk
 include build-aux/automake.mk
diff --git a/configure.ac b/configure.ac
index afd1e83450eb..3213e061a248 100644
--- a/configure.ac
+++ b/configure.ac
@@ -147,7 +147,6 @@ AC_CONFIG_FILES([
     ofproto/libofproto.sym
     lib/libsflow.sym
     lib/libopenvswitch.sym
-    ovn/lib/libovn.sym
     vtep/libvtep.sym])
 
 OVS_ENABLE_OPTION([-Wall])
diff --git a/ovn/.gitignore b/ovn/.gitignore
deleted file mode 100644
index d971938aa745..000000000000
--- a/ovn/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-/ovn-architecture.7
-/ovn-nb.5
-/ovn-nb.gv
-/ovn-nb.pic
-/ovn-sb.5
-/ovn-sb.gv
-/ovn-sb.pic
-/*.ovsschema.stamp
diff --git a/ovn/automake.mk b/ovn/automake.mk
deleted file mode 100644
index 7d16c6036a3a..000000000000
--- a/ovn/automake.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-EXTRA_DIST += ovn/ovn-sb.ovsschema \
-	      ovn/ovn-sb.xml \
-	      ovn/ovn-nb.ovsschema \
-	      ovn/ovn-nb.xml
-
-include ovn/lib/automake.mk
-include ovn/utilities/automake.mk
diff --git a/ovn/lib/.gitignore b/ovn/lib/.gitignore
deleted file mode 100644
index a80a1bce174b..000000000000
--- a/ovn/lib/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/libovn.sym
-/ovn-nb-idl.c
-/ovn-nb-idl.h
-/ovn-nb-idl.ovsidl
-/ovn-sb-idl.c
-/ovn-sb-idl.h
-/ovn-sb-idl.ovsidl
diff --git a/ovn/lib/acl-log.c b/ovn/lib/acl-log.c
deleted file mode 100644
index f47b0af437da..000000000000
--- a/ovn/lib/acl-log.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) 2017 Nicira, Inc.
- *
- * 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.
- */
-
-#include <config.h>
-#include "ovn/lib/acl-log.h"
-#include <string.h>
-#include "flow.h"
-#include "openvswitch/json.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/vlog.h"
-
-
-VLOG_DEFINE_THIS_MODULE(acl_log);
-
-const char *
-log_verdict_to_string(uint8_t verdict)
-{
-    if (verdict == LOG_VERDICT_ALLOW) {
-        return "allow";
-    } else if (verdict == LOG_VERDICT_DROP) {
-        return "drop";
-    } else if (verdict == LOG_VERDICT_REJECT) {
-        return "reject";
-    } else {
-        return "<unknown>";
-    }
-}
-
-const char *
-log_severity_to_string(uint8_t severity)
-{
-    if (severity == LOG_SEVERITY_ALERT) {
-        return "alert";
-    } else if (severity == LOG_SEVERITY_WARNING) {
-        return "warning";
-    } else if (severity == LOG_SEVERITY_NOTICE) {
-        return "notice";
-    } else if (severity == LOG_SEVERITY_INFO) {
-        return "info";
-    } else if (severity == LOG_SEVERITY_DEBUG) {
-        return "debug";
-    } else {
-        return "<unknown>";
-    }
-}
-
-uint8_t
-log_severity_from_string(const char *name)
-{
-    if (!strcmp(name, "alert")) {
-        return LOG_SEVERITY_ALERT;
-    } else if (!strcmp(name, "warning")) {
-        return LOG_SEVERITY_WARNING;
-    } else if (!strcmp(name, "notice")) {
-        return LOG_SEVERITY_NOTICE;
-    } else if (!strcmp(name, "info")) {
-        return LOG_SEVERITY_INFO;
-    } else if (!strcmp(name, "debug")) {
-        return LOG_SEVERITY_DEBUG;
-    } else {
-        return UINT8_MAX;
-    }
-}
-
-void
-handle_acl_log(const struct flow *headers, struct ofpbuf *userdata)
-{
-    if (!VLOG_IS_INFO_ENABLED()) {
-        return;
-    }
-
-    struct log_pin_header *lph = ofpbuf_try_pull(userdata, sizeof *lph);
-    if (!lph) {
-        VLOG_WARN("log data missing");
-        return;
-    }
-
-    size_t name_len = userdata->size;
-    char *name = name_len ? xmemdup0(userdata->data, name_len) : NULL;
-
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    ds_put_cstr(&ds, "name=");
-    json_string_escape(name_len ? name : "<unnamed>", &ds);
-    ds_put_format(&ds, ", verdict=%s, severity=%s: ",
-                  log_verdict_to_string(lph->verdict),
-                  log_severity_to_string(lph->severity));
-    flow_format(&ds, headers, NULL);
-
-    VLOG_INFO("%s", ds_cstr(&ds));
-    ds_destroy(&ds);
-    free(name);
-}
diff --git a/ovn/lib/acl-log.h b/ovn/lib/acl-log.h
deleted file mode 100644
index 55dc75b7f446..000000000000
--- a/ovn/lib/acl-log.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2017 Nicira, Inc.
- *
- * 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.
- */
-
-#ifndef ACL_LOG_H
-#define ACL_LOG_H 1
-
-#include <stdint.h>
-#include "openvswitch/types.h"
-
-struct ofpbuf;
-struct flow;
-
-struct log_pin_header {
-    uint8_t verdict;            /* One of LOG_VERDICT_*. */
-    uint8_t severity;           /* One of LOG_SEVERITY*. */
-    /* Followed by an optional string containing the rule's name. */
-};
-
-enum log_verdict {
-    LOG_VERDICT_ALLOW,
-    LOG_VERDICT_DROP,
-    LOG_VERDICT_REJECT,
-    LOG_VERDICT_UNKNOWN = UINT8_MAX
-};
-
-const char *log_verdict_to_string(uint8_t verdict);
-
-
-/* Severity levels.  Based on RFC5424 levels. */
-#define LOG_SEVERITY_ALERT    1
-#define LOG_SEVERITY_WARNING  4
-#define LOG_SEVERITY_NOTICE   5
-#define LOG_SEVERITY_INFO     6
-#define LOG_SEVERITY_DEBUG    7
-
-const char *log_severity_to_string(uint8_t severity);
-uint8_t log_severity_from_string(const char *name);
-
-void handle_acl_log(const struct flow *headers, struct ofpbuf *userdata);
-
-#endif /* ovn/lib/acl-log.h */
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
deleted file mode 100644
index 53a10e21104a..000000000000
--- a/ovn/lib/automake.mk
+++ /dev/null
@@ -1,42 +0,0 @@
-lib_LTLIBRARIES += ovn/lib/libovn.la
-ovn_lib_libovn_la_LDFLAGS = \
-        $(OVS_LTINFO) \
-        -Wl,--version-script=$(top_builddir)/ovn/lib/libovn.sym \
-        $(AM_LDFLAGS)
-ovn_lib_libovn_la_SOURCES = \
-	ovn/lib/acl-log.c \
-	ovn/lib/acl-log.h \
-	ovn/lib/ovn-util.c \
-	ovn/lib/ovn-util.h
-nodist_ovn_lib_libovn_la_SOURCES = \
-	ovn/lib/ovn-nb-idl.c \
-	ovn/lib/ovn-nb-idl.h \
-	ovn/lib/ovn-sb-idl.c \
-	ovn/lib/ovn-sb-idl.h
-
-# ovn-sb IDL
-OVSIDL_BUILT += \
-	ovn/lib/ovn-sb-idl.c \
-	ovn/lib/ovn-sb-idl.h \
-	ovn/lib/ovn-sb-idl.ovsidl
-EXTRA_DIST += ovn/lib/ovn-sb-idl.ann
-OVN_SB_IDL_FILES = \
-	$(srcdir)/ovn/ovn-sb.ovsschema \
-	$(srcdir)/ovn/lib/ovn-sb-idl.ann
-ovn/lib/ovn-sb-idl.ovsidl: $(OVN_SB_IDL_FILES)
-	$(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_SB_IDL_FILES) > $@.tmp && \
-	mv $@.tmp $@
-
-# ovn-nb IDL
-OVSIDL_BUILT += \
-	ovn/lib/ovn-nb-idl.c \
-	ovn/lib/ovn-nb-idl.h \
-	ovn/lib/ovn-nb-idl.ovsidl
-EXTRA_DIST += ovn/lib/ovn-nb-idl.ann
-OVN_NB_IDL_FILES = \
-	$(srcdir)/ovn/ovn-nb.ovsschema \
-	$(srcdir)/ovn/lib/ovn-nb-idl.ann
-ovn/lib/ovn-nb-idl.ovsidl: $(OVN_NB_IDL_FILES)
-	$(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_NB_IDL_FILES) > $@.tmp && \
-	mv $@.tmp $@
-
diff --git a/ovn/lib/libovn.sym.in b/ovn/lib/libovn.sym.in
deleted file mode 100644
index 360de0fe8db2..000000000000
--- a/ovn/lib/libovn.sym.in
+++ /dev/null
@@ -1,4 +0,0 @@
-libovn_@LT_CURRENT@ {
-global:
-        *;
-};
diff --git a/ovn/lib/ovn-nb-idl.ann b/ovn/lib/ovn-nb-idl.ann
deleted file mode 100644
index 76d7384fcece..000000000000
--- a/ovn/lib/ovn-nb-idl.ann
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- python -*-
-
-# This code, when invoked by "ovsdb-idlc annotate" (by the build
-# process), annotates vswitch.ovsschema with additional data that give
-# the ovsdb-idl engine information about the types involved, so that
-# it can generate more programmer-friendly data structures.
-
-s["idlPrefix"] = "nbrec_"
-s["idlHeader"] = "\"ovn/lib/ovn-nb-idl.h\""
diff --git a/ovn/lib/ovn-sb-idl.ann b/ovn/lib/ovn-sb-idl.ann
deleted file mode 100644
index e51238b92e97..000000000000
--- a/ovn/lib/ovn-sb-idl.ann
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- python -*-
-
-# This code, when invoked by "ovsdb-idlc annotate" (by the build
-# process), annotates vswitch.ovsschema with additional data that give
-# the ovsdb-idl engine information about the types involved, so that
-# it can generate more programmer-friendly data structures.
-
-s["idlPrefix"] = "sbrec_"
-s["idlHeader"] = "\"ovn/lib/ovn-sb-idl.h\""
-
-s["hDecls"] = '#include "ovn/lib/ovn-util.h"'
-
-# Adds an integer column named 'column' to 'table' in 's'.  The column
-# values is calculated with 'expression' based on the values of the columns
-# named in the array 'dependencies'.
-def synthesize_integer_column(s, table, column, dependencies, expression):
-    s["tables"][table]["columns"][column] = {
-        "type": "integer",
-        "extensions": {
-            "dependencies": dependencies,
-            "parse": "row->%s = %s;" % (column, expression),
-            "synthetic": True
-        }
-    }
-
-synthesize_integer_column(s, "Logical_Flow", "hash",
-                          ["logical_datapath", "table_id", "pipeline",
-                           "priority", "match", "actions"],
-                          "sbrec_logical_flow_hash(row)")
diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
deleted file mode 100644
index 0f07d80ac5e7..000000000000
--- a/ovn/lib/ovn-util.c
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * 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.
- */
-
-#include <config.h>
-#include "ovn-util.h"
-#include "dirs.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-nb-idl.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(ovn_util);
-
-static void
-add_ipv4_netaddr(struct lport_addresses *laddrs, ovs_be32 addr,
-                 unsigned int plen)
-{
-    laddrs->n_ipv4_addrs++;
-    laddrs->ipv4_addrs = xrealloc(laddrs->ipv4_addrs,
-        laddrs->n_ipv4_addrs * sizeof *laddrs->ipv4_addrs);
-
-    struct ipv4_netaddr *na = &laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1];
-
-    na->addr = addr;
-    na->mask = be32_prefix_mask(plen);
-    na->network = addr & na->mask;
-    na->plen = plen;
-
-    ovs_be32 bcast = addr | ~na->mask;
-    inet_ntop(AF_INET, &addr, na->addr_s, sizeof na->addr_s);
-    inet_ntop(AF_INET, &na->network, na->network_s, sizeof na->network_s);
-    inet_ntop(AF_INET, &bcast, na->bcast_s, sizeof na->bcast_s);
-}
-
-static void
-add_ipv6_netaddr(struct lport_addresses *laddrs, struct in6_addr addr,
-                 unsigned int plen)
-{
-    laddrs->n_ipv6_addrs++;
-    laddrs->ipv6_addrs = xrealloc(laddrs->ipv6_addrs,
-        laddrs->n_ipv6_addrs * sizeof *laddrs->ipv6_addrs);
-
-    struct ipv6_netaddr *na = &laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1];
-
-    memcpy(&na->addr, &addr, sizeof na->addr);
-    na->mask = ipv6_create_mask(plen);
-    na->network = ipv6_addr_bitand(&addr, &na->mask);
-    na->plen = plen;
-    in6_addr_solicited_node(&na->sn_addr, &addr);
-
-    inet_ntop(AF_INET6, &addr, na->addr_s, sizeof na->addr_s);
-    inet_ntop(AF_INET6, &na->sn_addr, na->sn_addr_s, sizeof na->sn_addr_s);
-    inet_ntop(AF_INET6, &na->network, na->network_s, sizeof na->network_s);
-}
-
-/* Returns true if specified address specifies a dynamic address,
- * supporting the following formats:
- *
- *    "dynamic":
- *        Both MAC and IP are to be allocated dynamically.
- *
- *    "xx:xx:xx:xx:xx:xx dynamic":
- *        Use specified MAC address, but allocate an IP address
- *        dynamically.
- *
- *    "dynamic x.x.x.x":
- *        Use specified IP address, but allocate a MAC address
- *        dynamically.
- */
-bool
-is_dynamic_lsp_address(const char *address)
-{
-    char ipv6_s[IPV6_SCAN_LEN + 1];
-    struct eth_addr ea;
-    ovs_be32 ip;
-    int n;
-    return (!strcmp(address, "dynamic")
-            || (ovs_scan(address, "dynamic "IP_SCAN_FMT"%n",
-                         IP_SCAN_ARGS(&ip), &n)
-                         && address[n] == '\0')
-            || (ovs_scan(address, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
-                         IP_SCAN_ARGS(&ip), ipv6_s, &n)
-                         && address[n] == '\0')
-            || (ovs_scan(address, "dynamic "IPV6_SCAN_FMT"%n",
-                         ipv6_s, &n) && address[n] == '\0')
-            || (ovs_scan(address, ETH_ADDR_SCAN_FMT" dynamic%n",
-                         ETH_ADDR_SCAN_ARGS(ea), &n) && address[n] == '\0'));
-}
-
-static bool
-parse_and_store_addresses(const char *address, struct lport_addresses *laddrs,
-                          int *ofs, bool extract_eth_addr)
-{
-    memset(laddrs, 0, sizeof *laddrs);
-
-    const char *buf = address;
-    const char *const start = buf;
-    int buf_index = 0;
-    const char *buf_end = buf + strlen(address);
-
-    if (extract_eth_addr) {
-        if (!ovs_scan_len(buf, &buf_index, ETH_ADDR_SCAN_FMT,
-                          ETH_ADDR_SCAN_ARGS(laddrs->ea))) {
-            laddrs->ea = eth_addr_zero;
-            *ofs = 0;
-            return false;
-        }
-
-        snprintf(laddrs->ea_s, sizeof laddrs->ea_s, ETH_ADDR_FMT,
-                 ETH_ADDR_ARGS(laddrs->ea));
-    }
-
-    ovs_be32 ip4;
-    struct in6_addr ip6;
-    unsigned int plen;
-    char *error;
-
-    /* Loop through the buffer and extract the IPv4/IPv6 addresses
-     * and store in the 'laddrs'. Break the loop if invalid data is found.
-     */
-    buf += buf_index;
-    while (buf < buf_end) {
-        buf_index = 0;
-        error = ip_parse_cidr_len(buf, &buf_index, &ip4, &plen);
-        if (!error) {
-            add_ipv4_netaddr(laddrs, ip4, plen);
-            buf += buf_index;
-            continue;
-        }
-        free(error);
-        error = ipv6_parse_cidr_len(buf, &buf_index, &ip6, &plen);
-        if (!error) {
-            add_ipv6_netaddr(laddrs, ip6, plen);
-        } else {
-            free(error);
-            break;
-        }
-        buf += buf_index;
-    }
-
-    *ofs = buf - start;
-    return true;
-}
-
-/* Extracts the mac, IPv4 and IPv6 addresses from * 'address' which
- * should be of the format "MAC [IP1 IP2 ..] .." where IPn should be a
- * valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
- * 'ipv6_addrs' fields of 'laddrs'.  There may be additional content in
- * 'address' after "MAC [IP1 IP2 .. ]".  The value of 'ofs' that is
- * returned indicates the offset where that additional content begins.
- *
- * Returns true if at least 'MAC' is found in 'address', false otherwise.
- *
- * The caller must call destroy_lport_addresses(). */
-bool
-extract_addresses(const char *address, struct lport_addresses *laddrs,
-                  int *ofs)
-{
-    return parse_and_store_addresses(address, laddrs, ofs, true);
-}
-
-/* Extracts the mac, IPv4 and IPv6 addresses from * 'address' which
- * should be of the format 'MAC [IP1 IP2 ..]" where IPn should be a
- * valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
- * 'ipv6_addrs' fields of 'laddrs'.
- *
- * Return true if at least 'MAC' is found in 'address', false otherwise.
- *
- * The caller must call destroy_lport_addresses(). */
-bool
-extract_lsp_addresses(const char *address, struct lport_addresses *laddrs)
-{
-    int ofs;
-    bool success = extract_addresses(address, laddrs, &ofs);
-
-    if (success && ofs < strlen(address)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", address);
-    }
-
-    return success;
-}
-
-/* Extracts the IPv4 and IPv6 addresses from * 'address' which
- * should be of the format 'IP1 IP2 .." where IPn should be a
- * valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
- * 'ipv6_addrs' fields of 'laddrs'.
- *
- * Return true if at least one IP address is found in 'address',
- * false otherwise.
- *
- * The caller must call destroy_lport_addresses(). */
-bool
-extract_ip_addresses(const char *address, struct lport_addresses *laddrs)
-{
-    int ofs;
-    if (parse_and_store_addresses(address, laddrs, &ofs, false)) {
-        return (laddrs->n_ipv4_addrs || laddrs->n_ipv6_addrs);
-    }
-
-    return false;
-}
-
-/* Extracts the mac, IPv4 and IPv6 addresses from the
- * "nbrec_logical_router_port" parameter 'lrp'.  Stores the IPv4 and
- * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of
- * 'laddrs', respectively.  In addition, a link local IPv6 address
- * based on the 'mac' member of 'lrp' is added to the 'ipv6_addrs'
- * field.
- *
- * Return true if a valid 'mac' address is found in 'lrp', false otherwise.
- *
- * The caller must call destroy_lport_addresses(). */
-bool
-extract_lrp_networks(const struct nbrec_logical_router_port *lrp,
-                     struct lport_addresses *laddrs)
-{
-    memset(laddrs, 0, sizeof *laddrs);
-
-    if (!eth_addr_from_string(lrp->mac, &laddrs->ea)) {
-        laddrs->ea = eth_addr_zero;
-        return false;
-    }
-    snprintf(laddrs->ea_s, sizeof laddrs->ea_s, ETH_ADDR_FMT,
-             ETH_ADDR_ARGS(laddrs->ea));
-
-    for (int i = 0; i < lrp->n_networks; i++) {
-        ovs_be32 ip4;
-        struct in6_addr ip6;
-        unsigned int plen;
-        char *error;
-
-        error = ip_parse_cidr(lrp->networks[i], &ip4, &plen);
-        if (!error) {
-            if (!ip4) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "bad 'networks' %s", lrp->networks[i]);
-                continue;
-            }
-
-            add_ipv4_netaddr(laddrs, ip4, plen);
-            continue;
-        }
-        free(error);
-
-        error = ipv6_parse_cidr(lrp->networks[i], &ip6, &plen);
-        if (!error) {
-            add_ipv6_netaddr(laddrs, ip6, plen);
-        } else {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_INFO_RL(&rl, "invalid syntax '%s' in networks",
-                         lrp->networks[i]);
-            free(error);
-        }
-    }
-
-    /* Always add the IPv6 link local address. */
-    struct in6_addr lla;
-    in6_generate_lla(laddrs->ea, &lla);
-    add_ipv6_netaddr(laddrs, lla, 64);
-
-    return true;
-}
-
-void
-destroy_lport_addresses(struct lport_addresses *laddrs)
-{
-    free(laddrs->ipv4_addrs);
-    free(laddrs->ipv6_addrs);
-}
-
-/* Allocates a key for NAT conntrack zone allocation for a provided
- * 'key' record and a 'type'.
- *
- * It is the caller's responsibility to free the allocated memory. */
-char *
-alloc_nat_zone_key(const struct uuid *key, const char *type)
-{
-    return xasprintf(UUID_FMT"_%s", UUID_ARGS(key), type);
-}
-
-const char *
-default_nb_db(void)
-{
-    static char *def;
-    if (!def) {
-        def = getenv("OVN_NB_DB");
-        if (!def) {
-            def = xasprintf("unix:%s/ovnnb_db.sock", ovs_rundir());
-        }
-    }
-    return def;
-}
-
-const char *
-default_sb_db(void)
-{
-    static char *def;
-    if (!def) {
-        def = getenv("OVN_SB_DB");
-        if (!def) {
-            def = xasprintf("unix:%s/ovnsb_db.sock", ovs_rundir());
-        }
-    }
-    return def;
-}
-
-/* l3gateway, chassisredirect, and patch
- * are not in this list since they are
- * only set in the SB DB by northd
- */
-static const char *OVN_NB_LSP_TYPES[] = {
-    "l2gateway",
-    "localnet",
-    "localport",
-    "router",
-    "vtep",
-    "external",
-};
-
-bool
-ovn_is_known_nb_lsp_type(const char *type)
-{
-    int i;
-
-    if (!type || !type[0]) {
-        return true;
-    }
-
-    for (i = 0; i < ARRAY_SIZE(OVN_NB_LSP_TYPES); ++i) {
-        if (!strcmp(OVN_NB_LSP_TYPES[i], type)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-uint32_t
-sbrec_logical_flow_hash(const struct sbrec_logical_flow *lf)
-{
-    const struct sbrec_datapath_binding *ld = lf->logical_datapath;
-    if (!ld) {
-        return 0;
-    }
-
-    return ovn_logical_flow_hash(&ld->header_.uuid,
-                                 lf->table_id, lf->pipeline,
-                                 lf->priority, lf->match, lf->actions);
-}
-
-uint32_t
-ovn_logical_flow_hash(const struct uuid *logical_datapath,
-                      uint8_t table_id, const char *pipeline,
-                      uint16_t priority,
-                      const char *match, const char *actions)
-{
-    size_t hash = uuid_hash(logical_datapath);
-    hash = hash_2words((table_id << 16) | priority, hash);
-    hash = hash_string(pipeline, hash);
-    hash = hash_string(match, hash);
-    return hash_string(actions, hash);
-}
diff --git a/ovn/lib/ovn-util.h b/ovn/lib/ovn-util.h
deleted file mode 100644
index 6d5e1dfb5ef7..000000000000
--- a/ovn/lib/ovn-util.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.
- */
-
-
-#ifndef OVN_UTIL_H
-#define OVN_UTIL_H 1
-
-#include "lib/packets.h"
-
-struct nbrec_logical_router_port;
-struct sbrec_logical_flow;
-struct uuid;
-
-struct ipv4_netaddr {
-    ovs_be32 addr;            /* 192.168.10.123 */
-    ovs_be32 mask;            /* 255.255.255.0 */
-    ovs_be32 network;         /* 192.168.10.0 */
-    unsigned int plen;        /* CIDR Prefix: 24. */
-
-    char addr_s[INET_ADDRSTRLEN + 1];     /* "192.168.10.123" */
-    char network_s[INET_ADDRSTRLEN + 1];  /* "192.168.10.0" */
-    char bcast_s[INET_ADDRSTRLEN + 1];    /* "192.168.10.255" */
-};
-
-struct ipv6_netaddr {
-    struct in6_addr addr;     /* fc00::1 */
-    struct in6_addr mask;     /* ffff:ffff:ffff:ffff:: */
-    struct in6_addr sn_addr;  /* ff02:1:ff00::1 */
-    struct in6_addr network;  /* fc00:: */
-    unsigned int plen;        /* CIDR Prefix: 64 */
-
-    char addr_s[INET6_ADDRSTRLEN + 1];    /* "fc00::1" */
-    char sn_addr_s[INET6_ADDRSTRLEN + 1]; /* "ff02:1:ff00::1" */
-    char network_s[INET6_ADDRSTRLEN + 1]; /* "fc00::" */
-};
-
-struct lport_addresses {
-    char ea_s[ETH_ADDR_STRLEN + 1];
-    struct eth_addr ea;
-    size_t n_ipv4_addrs;
-    struct ipv4_netaddr *ipv4_addrs;
-    size_t n_ipv6_addrs;
-    struct ipv6_netaddr *ipv6_addrs;
-};
-
-bool is_dynamic_lsp_address(const char *address);
-bool extract_addresses(const char *address, struct lport_addresses *,
-                       int *ofs);
-bool extract_lsp_addresses(const char *address, struct lport_addresses *);
-bool extract_ip_addresses(const char *address, struct lport_addresses *);
-bool extract_lrp_networks(const struct nbrec_logical_router_port *,
-                          struct lport_addresses *);
-void destroy_lport_addresses(struct lport_addresses *);
-
-char *alloc_nat_zone_key(const struct uuid *key, const char *type);
-
-const char *default_nb_db(void);
-const char *default_sb_db(void);
-
-struct ovsdb_idl_table_class;
-const char *db_table_usage(struct ds *tables,
-                           const struct ovsdb_idl_table_class *class,
-                           int n_tables);
-
-bool ovn_is_known_nb_lsp_type(const char *type);
-
-uint32_t sbrec_logical_flow_hash(const struct sbrec_logical_flow *);
-uint32_t ovn_logical_flow_hash(const struct uuid *logical_datapath,
-                               uint8_t table_id, const char *pipeline,
-                               uint16_t priority,
-                               const char *match, const char *actions);
-
-#endif
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
deleted file mode 100644
index 2c87cbba7136..000000000000
--- a/ovn/ovn-nb.ovsschema
+++ /dev/null
@@ -1,449 +0,0 @@
-{
-    "name": "OVN_Northbound",
-    "version": "5.16.0",
-    "cksum": "923459061 23095",
-    "tables": {
-        "NB_Global": {
-            "columns": {
-                "nb_cfg": {"type": {"key": "integer"}},
-                "sb_cfg": {"type": {"key": "integer"}},
-                "hv_cfg": {"type": {"key": "integer"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "connections": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "Connection"},
-                                     "min": 0,
-                                     "max": "unlimited"}},
-                "ssl": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "SSL"},
-                                     "min": 0, "max": 1}},
-                "options": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "ipsec": {"type": "boolean"}},
-            "maxRows": 1,
-            "isRoot": true},
-        "Logical_Switch": {
-            "columns": {
-                "name": {"type": "string"},
-                "ports": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Logical_Switch_Port",
-                                           "refType": "strong"},
-                                   "min": 0,
-                                   "max": "unlimited"}},
-                "acls": {"type": {"key": {"type": "uuid",
-                                          "refTable": "ACL",
-                                          "refType": "strong"},
-                                  "min": 0,
-                                  "max": "unlimited"}},
-                "qos_rules": {"type": {"key": {"type": "uuid",
-                                          "refTable": "QoS",
-                                          "refType": "strong"},
-                                  "min": 0,
-                                  "max": "unlimited"}},
-                "load_balancer": {"type": {"key": {"type": "uuid",
-                                                  "refTable": "Load_Balancer",
-                                                  "refType": "weak"},
-                                           "min": 0,
-                                           "max": "unlimited"}},
-                "dns_records": {"type": {"key": {"type": "uuid",
-                                         "refTable": "DNS",
-                                         "refType": "weak"},
-                                  "min": 0,
-                                  "max": "unlimited"}},
-                "other_config": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "Logical_Switch_Port": {
-            "columns": {
-                "name": {"type": "string"},
-                "type": {"type": "string"},
-                "options": {
-                     "type": {"key": "string",
-                              "value": "string",
-                              "min": 0,
-                              "max": "unlimited"}},
-                "parent_name": {"type": {"key": "string", "min": 0, "max": 1}},
-                "tag_request": {
-                     "type": {"key": {"type": "integer",
-                                      "minInteger": 0,
-                                      "maxInteger": 4095},
-                              "min": 0, "max": 1}},
-                "tag": {
-                     "type": {"key": {"type": "integer",
-                                      "minInteger": 1,
-                                      "maxInteger": 4095},
-                              "min": 0, "max": 1}},
-                "addresses": {"type": {"key": "string",
-                                       "min": 0,
-                                       "max": "unlimited"}},
-                "dynamic_addresses": {"type": {"key": "string",
-                                       "min": 0,
-                                       "max": 1}},
-                "port_security": {"type": {"key": "string",
-                                           "min": 0,
-                                           "max": "unlimited"}},
-                "up": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "dhcpv4_options": {"type": {"key": {"type": "uuid",
-                                            "refTable": "DHCP_Options",
-                                            "refType": "weak"},
-                                 "min": 0,
-                                 "max": 1}},
-                "dhcpv6_options": {"type": {"key": {"type": "uuid",
-                                            "refTable": "DHCP_Options",
-                                            "refType": "weak"},
-                                 "min": 0,
-                                 "max": 1}},
-                "ha_chassis_group": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "HA_Chassis_Group",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": 1}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": false},
-        "Address_Set": {
-            "columns": {
-                "name": {"type": "string"},
-                "addresses": {"type": {"key": "string",
-                                       "min": 0,
-                                       "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Port_Group": {
-            "columns": {
-                "name": {"type": "string"},
-                "ports": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Logical_Switch_Port",
-                                           "refType": "weak"},
-                                   "min": 0,
-                                   "max": "unlimited"}},
-                "acls": {"type": {"key": {"type": "uuid",
-                                          "refTable": "ACL",
-                                          "refType": "strong"},
-                                  "min": 0,
-                                  "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Load_Balancer": {
-            "columns": {
-                "name": {"type": "string"},
-                "vips": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "protocol": {
-                    "type": {"key": {"type": "string",
-                             "enum": ["set", ["tcp", "udp"]]},
-                             "min": 0, "max": 1}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "ACL": {
-            "columns": {
-                "name": {"type": {"key": {"type": "string",
-                                          "maxLength": 63},
-                                          "min": 0, "max": 1}},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "direction": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["from-lport", "to-lport"]]}}},
-                "match": {"type": "string"},
-                "action": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["allow", "allow-related", "drop", "reject"]]}}},
-                "log": {"type": "boolean"},
-                "severity": {"type": {"key": {"type": "string",
-                                              "enum": ["set",
-                                                       ["alert", "warning",
-                                                        "notice", "info",
-                                                        "debug"]]},
-                                      "min": 0, "max": 1}},
-                "meter": {"type": {"key": "string", "min": 0, "max": 1}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "QoS": {
-            "columns": {
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "direction": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["from-lport", "to-lport"]]}}},
-                "match": {"type": "string"},
-                "action": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["dscp"]]},
-                                    "value": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 63},
-                                    "min": 0, "max": "unlimited"}},
-                "bandwidth": {"type": {"key": {"type": "string",
-                                               "enum": ["set", ["rate",
-                                                                "burst"]]},
-                                       "value": {"type": "integer",
-                                                 "minInteger": 1,
-                                                 "maxInteger": 4294967295},
-                                       "min": 0, "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "Meter": {
-            "columns": {
-                "name": {"type": "string"},
-                "unit": {"type": {"key": {"type": "string",
-                                          "enum": ["set", ["kbps", "pktps"]]}}},
-                "bands": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Meter_Band",
-                                           "refType": "strong"},
-                                   "min": 1,
-                                   "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Meter_Band": {
-            "columns": {
-                "action": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["drop"]]}}},
-                "rate": {"type": {"key": {"type": "integer",
-                                          "minInteger": 1,
-                                          "maxInteger": 4294967295}}},
-                "burst_size": {"type": {"key": {"type": "integer",
-                                                "minInteger": 0,
-                                                "maxInteger": 4294967295}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "Logical_Router": {
-            "columns": {
-                "name": {"type": "string"},
-                "ports": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Logical_Router_Port",
-                                           "refType": "strong"},
-                                   "min": 0,
-                                   "max": "unlimited"}},
-                "static_routes": {"type": {"key": {"type": "uuid",
-                                            "refTable": "Logical_Router_Static_Route",
-                                            "refType": "strong"},
-                                   "min": 0,
-                                   "max": "unlimited"}},
-                "policies": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "Logical_Router_Policy",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": "unlimited"}},
-                "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "nat": {"type": {"key": {"type": "uuid",
-                                         "refTable": "NAT",
-                                         "refType": "strong"},
-                                 "min": 0,
-                                 "max": "unlimited"}},
-                "load_balancer": {"type": {"key": {"type": "uuid",
-                                                  "refTable": "Load_Balancer",
-                                                  "refType": "weak"},
-                                           "min": 0,
-                                           "max": "unlimited"}},
-                "options": {
-                     "type": {"key": "string",
-                              "value": "string",
-                              "min": 0,
-                              "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "Logical_Router_Port": {
-            "columns": {
-                "name": {"type": "string"},
-                "gateway_chassis": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "Gateway_Chassis",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": "unlimited"}},
-                "ha_chassis_group": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "HA_Chassis_Group",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": 1}},
-                "options": {
-                    "type": {"key": "string",
-                             "value": "string",
-                             "min": 0,
-                             "max": "unlimited"}},
-                "networks": {"type": {"key": "string",
-                                      "min": 1,
-                                      "max": "unlimited"}},
-                "mac": {"type": "string"},
-                "peer": {"type": {"key": "string", "min": 0, "max": 1}},
-                "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "ipv6_ra_configs": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": false},
-        "Logical_Router_Static_Route": {
-            "columns": {
-                "ip_prefix": {"type": "string"},
-                "policy": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["src-ip",
-                                                             "dst-ip"]]},
-                                    "min": 0, "max": 1}},
-                "nexthop": {"type": "string"},
-                "output_port": {"type": {"key": "string", "min": 0, "max": 1}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "Logical_Router_Policy": {
-            "columns": {
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "match": {"type": "string"},
-                "action": {"type": {
-                    "key": {"type": "string",
-                            "enum": ["set", ["allow", "drop", "reroute"]]}}},
-                "nexthop": {"type": {"key": "string", "min": 0, "max": 1}}},
-            "isRoot": false},
-        "NAT": {
-            "columns": {
-                "external_ip": {"type": "string"},
-                "external_mac": {"type": {"key": "string",
-                                          "min": 0, "max": 1}},
-                "logical_ip": {"type": "string"},
-                "logical_port": {"type": {"key": "string",
-                                          "min": 0, "max": 1}},
-                "type": {"type": {"key": {"type": "string",
-                                           "enum": ["set", ["dnat",
-                                                             "snat",
-                                                             "dnat_and_snat"
-                                                               ]]}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "DHCP_Options": {
-            "columns": {
-                "cidr": {"type": "string"},
-                "options": {"type": {"key": "string", "value": "string",
-                                     "min": 0, "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "Connection": {
-            "columns": {
-                "target": {"type": "string"},
-                "max_backoff": {"type": {"key": {"type": "integer",
-                                         "minInteger": 1000},
-                                         "min": 0,
-                                         "max": 1}},
-                "inactivity_probe": {"type": {"key": "integer",
-                                              "min": 0,
-                                              "max": 1}},
-                "other_config": {"type": {"key": "string",
-                                          "value": "string",
-                                          "min": 0,
-                                          "max": "unlimited"}},
-                "external_ids": {"type": {"key": "string",
-                                 "value": "string",
-                                 "min": 0,
-                                 "max": "unlimited"}},
-                "is_connected": {"type": "boolean", "ephemeral": true},
-                "status": {"type": {"key": "string",
-                                    "value": "string",
-                                    "min": 0,
-                                    "max": "unlimited"},
-                                    "ephemeral": true}},
-            "indexes": [["target"]]},
-        "DNS": {
-            "columns": {
-                "records": {"type": {"key": "string",
-                                     "value": "string",
-                                     "min": 0,
-                                     "max": "unlimited"}},
-                "external_ids": {"type": {"key": "string",
-                                 "value": "string",
-                                 "min": 0,
-                                 "max": "unlimited"}}},
-            "isRoot": true},
-        "SSL": {
-            "columns": {
-                "private_key": {"type": "string"},
-                "certificate": {"type": "string"},
-                "ca_cert": {"type": "string"},
-                "bootstrap_ca_cert": {"type": "boolean"},
-                "ssl_protocols": {"type": "string"},
-                "ssl_ciphers": {"type": "string"},
-                "external_ids": {"type": {"key": "string",
-                                          "value": "string",
-                                          "min": 0,
-                                          "max": "unlimited"}}},
-            "maxRows": 1},
-        "Gateway_Chassis": {
-            "columns": {
-                "name": {"type": "string"},
-                "chassis_name": {"type": "string"},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "options": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": false},
-        "HA_Chassis": {
-            "columns": {
-                "chassis_name": {"type": "string"},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "HA_Chassis_Group": {
-            "columns": {
-                "name": {"type": "string"},
-                "ha_chassis": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "HA_Chassis",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true}}
-    }
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
deleted file mode 100644
index 57b6edbf8764..000000000000
--- a/ovn/ovn-nb.xml
+++ /dev/null
@@ -1,2917 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<database name="ovn-nb" title="OVN Northbound Database">
-  <p>
-    This database is the interface between OVN and the cloud management system
-    (CMS), such as OpenStack, running above it.  The CMS produces almost all of
-    the contents of the database.  The <code>ovn-northd</code> program
-    monitors the database contents, transforms it, and stores it into the <ref
-    db="OVN_Southbound"/> database.
-  </p>
-
-  <p>
-    We generally speak of ``the'' CMS, but one can imagine scenarios in
-    which multiple CMSes manage different parts of an OVN deployment.
-  </p>
-
-  <h2>External IDs</h2>
-
-  <p>
-    Each of the tables in this database contains a special column, named
-    <code>external_ids</code>.  This column has the same form and purpose each
-    place it appears.
-  </p>
-
-  <dl>
-    <dt><code>external_ids</code>: map of string-string pairs</dt>
-    <dd>
-      Key-value pairs for use by the CMS.  The CMS might use certain pairs, for
-      example, to identify entities in its own configuration that correspond to
-      those in this database.
-    </dd>
-  </dl>
-
-  <table name="NB_Global" title="Northbound configuration">
-    <p>
-      Northbound configuration for an OVN system.  This table must have exactly
-      one row.
-    </p>
-
-    <group title="Status">
-      These columns allow a client to track the overall configuration state of
-      the system.
-
-      <column name="nb_cfg">
-        Sequence number for client to increment.  When a client modifies any
-        part of the northbound database configuration and wishes to wait for
-        <code>ovn-northd</code> and possibly all of the hypervisors to finish
-        applying the changes, it may increment this sequence number.
-      </column>
-
-      <column name="sb_cfg">
-        Sequence number that <code>ovn-northd</code> sets to the value of <ref
-        column="nb_cfg"/> after it finishes applying the corresponding
-        configuration changes to the <ref db="OVN_Southbound"/> database.
-      </column>
-
-      <column name="hv_cfg">
-        Sequence number that <code>ovn-northd</code> sets to the smallest
-        sequence number of all the chassis in the system, as reported in the
-        <code>Chassis</code> table in the southbound database.  Thus, <ref
-        column="hv_cfg"/> equals <ref column="nb_cfg"/> if all chassis are
-        caught up with the northbound configuration (which may never happen, if
-        any chassis is down).  This value can regress, if a chassis was removed
-        from the system and rejoins before catching up.
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-
-    <group title="Common options">
-      <column name="options">
-        This column provides general key/value settings. The supported
-        options are described individually below.
-      </column>
-
-      <group title="Options for configuring BFD">
-        <p>
-          These options apply when <code>ovn-controller</code> configures
-          BFD on tunnels interfaces.
-        </p>
-
-        <column name="options" key="bfd-min-rx">
-          BFD option <code>min-rx</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-decay-min-rx">
-          BFD option <code>decay-min-rx</code> value to use when configuring
-          BFD on tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-min-tx">
-          BFD option <code>min-tx</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-mult">
-          BFD option <code>mult</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-      </group>
-
-      <column name="options" key="mac_prefix">
-        Configure a given OUI to be used as prefix when L2 address is
-        dynamically assigned, e.g. <code>00:11:22</code>
-      </column>
-
-      <column name="options" key="controller_event" type='{"type": "boolean"}'>
-        Value set by the CMS to enable/disable ovn-controller event reporting.
-        Traffic into OVS can raise a 'controller' event that results in a
-        Controller_Event being written to the <ref table="Controller_Event"/>
-        table in SBDB. When the CMS has seen the event and taken appropriate
-        action, it can remove the correponding row in
-        <ref table="Controller_Event"/> table.
-        The intention is for a CMS to see the events and take some sort of
-        action. Please see the <ref table="Controller_Event"/> table in SBDB.
-      </column>
-    </group>
-
-    <group title="Connection Options">
-      <column name="connections">
-        Database clients to which the Open vSwitch database server should
-        connect or on which it should listen, along with options for how these
-        connections should be configured.  See the <ref table="Connection"/>
-        table for more information.
-      </column>
-      <column name="ssl">
-        Global SSL configuration.
-      </column>
-    </group>
-    <group title="Security Configurations">
-      <column name="ipsec">
-        Tunnel encryption configuration. If this column is set to be true, all
-        OVN tunnels will be encrypted with IPsec.
-      </column>
-    </group>
-  </table>
-
-  <table name="Logical_Switch" title="L2 logical switch">
-    <p>
-      Each row represents one L2 logical switch.
-    </p>
-
-    <p>
-      There are two kinds of logical switches, that is, ones that fully
-      virtualize the network (overlay logical switches) and ones that provide
-      simple connectivity to a physical network (bridged logical switches).
-      They work in the same way when providing connectivity between logical
-      ports on same chasis, but differently when connecting remote logical
-      ports.  Overlay logical switches connect remote logical ports by tunnels,
-      while bridged logical switches provide connectivity to remote ports by
-      bridging the packets to directly connected physical L2 segment with the
-      help of <code>localnet</code> ports.  Each bridged logical switch has
-      one and only one <code>localnet</code> port, which has only one special
-      address <code>unknown</code>.
-    </p>
-
-    <column name="ports">
-      <p>
-        The logical ports connected to the logical switch.
-      </p>
-
-      <p>
-        It is an error for multiple logical switches to include the same
-        logical port.
-      </p>
-    </column>
-
-    <column name="load_balancer">
-      Load balance a virtual ip address to a set of logical port endpoint
-      ip addresses.
-    </column>
-
-    <column name="acls">
-      Access control rules that apply to packets within the logical switch.
-    </column>
-
-    <column name="qos_rules">
-      QoS marking and metering rules that apply to packets within the
-      logical switch.
-    </column>
-
-    <column name="dns_records">
-      This column defines the DNS records to be used for resolving internal
-      DNS queries within the logical switch by the native DNS resolver.
-      Please see the <ref table="DNS"/> table.
-    </column>
-
-    <group title="Naming">
-      <p>
-        These columns provide names for the logical switch.  From OVN's
-        perspective, these names have no special meaning or purpose other than
-        to provide convenience for human interaction with the  database.
-        There is no requirement for the name to be unique.  (For a unique
-        identifier for a logical switch, use its row UUID.)
-      </p>
-
-      <p>
-        (Originally, <ref column="name"/> was intended to serve the purpose of
-        a human-friendly name, but the Neutron integration used it to uniquely
-        identify its own switch object, in the format
-        <code>neutron-<var>uuid</var></code>.  Later on, Neutron started
-        propagating the friendly name of a switch as <ref column="external_ids"
-        key="neutron:network_name"/>.  Perhaps this can be cleaned up someday.)
-      </p>
-
-      <column name="name">
-        A name for the logical switch.
-      </column>
-
-      <column name="external_ids" key="neutron:network_name">
-        Another name for the logical switch.
-      </column>
-    </group>
-
-    <group title="IP Address Assignment">
-      <p>
-        These options control automatic IP address management (IPAM) for ports
-        attached to the logical switch.  To enable IPAM for IPv4, set <ref
-        column="other_config" key="subnet"/> and optionally <ref
-        column="other_config:exclude_ips"/>.  To enable IPAM for IPv6, set
-        <ref column="other_config" key="ipv6_prefix"/>.  IPv4 and IPv6 may
-        be enabled together or separately.
-      </p>
-
-      <p>
-        To request dynamic address assignment for a particular port, use the
-        <code>dynamic</code> keyword in the <ref table="Logical_Switch_Port"
-        column="addresses"/> column of the port's <ref
-        table="Logical_Switch_Port"/> row.  This requests both an IPv4 and an
-        IPv6 address, if IPAM for IPv4 and IPv6 are both enabled.
-      </p>
-
-      <column name="other_config" key="subnet">
-        Set this to an IPv4 subnet, e.g. <code>192.168.0.0/24</code>, to enable
-        <code>ovn-northd</code> to automatically assign IP addresses within
-        that subnet.
-      </column>
-
-      <column name="other_config" key="exclude_ips">
-        <p>
-          To exclude some addresses from automatic IP address management, set
-          this to a list of the IPv4 addresses or <code>..</code>-delimited
-          ranges to exclude.  The addresses or ranges should be a subset of
-          those in <ref column="other_config" key="subnet"/>.
-        </p>
-        <p>
-          Whether listed or not, <code>ovn-northd</code> will never allocate
-          the first or last address in a subnet, such as 192.168.0.0 or
-          192.168.0.255 in 192.168.0.0/24.
-        </p>
-        <p>
-          Examples:
-        </p>
-        <ul>
-          <li><code>192.168.0.2 192.168.0.10</code></li>
-          <li><code>192.168.0.4 192.168.0.30..192.168.0.60 192.168.0.110..192.168.0.120</code></li>
-          <li><code>192.168.0.110..192.168.0.120 192.168.0.25..192.168.0.30 192.168.0.144</code></li>
-        </ul>
-      </column>
-
-      <column name="other_config" key="ipv6_prefix">
-        Set this to an IPv6 prefix to enable <code>ovn-northd</code> to
-        automatically assign IPv6 addresses using this prefix.  The assigned
-        IPv6 address will be generated using the IPv6 prefix and the MAC
-        address (converted to an IEEE EUI64 identifier) of the port.  The IPv6
-        prefix defined here should be a valid IPv6 address ending with
-        <code>::</code>.
-        <p>
-          Examples:
-        </p>
-        <ul>
-          <li><code>aef0::</code></li>
-          <li><code>bef0:1234:a890:5678::</code></li>
-          <li><code>8230:5678::</code></li>
-        </ul>
-      </column>
-
-      <column name="other_config" key="mac_only" type='{"type": "boolean"}'>
-        Value used to request to assign L2 address only if neither subnet
-        nor ipv6_prefix are specified
-      </column>
-    </group>
-
-    <group title="IP Multicast Snooping Options">
-      <p>
-        These options control IP Multicast Snooping configuration of the
-        logical switch. To enable IP Multicast Snooping set
-        <ref column="other_config" key="mcast_snoop"/> to true. To enable IP
-        Multicast Querier set <ref column="other_config" key="mcast_snoop"/>
-        to true. If IP Multicast Querier is enabled
-        <ref column="other_config" key="mcast_eth_src"/> and
-        <ref column="other_config" key="mcast_ip4_src"/> must be set.
-      </p>
-      <column name="other_config" key="mcast_snoop"
-          type='{"type": "boolean"}'>
-        Enables/disables IP Multicast Snooping on the logical switch.
-      </column>
-      <column name="other_config" key="mcast_querier"
-          type='{"type": "boolean"}'>
-        Enables/disables IP Multicast Querier on the logical switch.
-      </column>
-      <column name="other_config" key="mcast_flood_unregistered"
-          type='{"type": "boolean"}'>
-        Determines whether unregistered multicast traffic should be flooded
-        or not. Only applicable if
-        <ref column="other_config" key="mcast_snoop"/> is enabled.
-      </column>
-      <column name="other_config" key="mcast_table_size"
-          type='{"type": "integer", "minInteger": 1, "maxInteger": 32766}'>
-        Number of multicast groups to be stored. Default: 2048.
-      </column>
-      <column name="other_config" key="mcast_idle_timeout"
-          type='{"type": "integer", "minInteger": 15, "maxInteger": 3600}'>
-        Configures the IP Multicast Snooping group idle timeout (in seconds).
-        Default: 300 seconds.
-      </column>
-      <column name="other_config" key="mcast_query_interval"
-          type='{"type": "integer", "minInteger": 1, "maxInteger": 3600}'>
-        Configures the IP Multicast Querier interval between queries (in
-        seconds). Default:
-        <ref column="other_config" key="mcast_idle_timeout"/> / 2.
-      </column>
-      <column name="other_config" key="mcast_query_max_response"
-          type='{"type": "integer", "minInteger": 1, "maxInteger": 10}'>
-        Configures the value of the "max-response" field in the multicast
-        queries originated by the logical switch. Default: 1 second.
-      </column>
-      <column name="other_config" key="mcast_eth_src">
-        Configures the source Ethernet address for queries originated by the
-        logical switch.
-      </column>
-      <column name="other_config" key="mcast_ip4_src">
-        Configures the source IPv4 address for queries originated by the
-        logical switch.
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Logical_Switch_Port" title="L2 logical switch port">
-    <p>
-      A port within an L2 logical switch.
-    </p>
-
-    <group title="Core Features">
-      <column name="name">
-        <p>
-          The logical port name.
-        </p>
-
-        <p>
-          For entities (VMs or containers) that are spawned in the hypervisor,
-          the name used here must match those used in the <ref key="iface-id"
-          table="Interface" column="external_ids" db="Open_vSwitch"/> in the
-          <ref db="Open_vSwitch"/> database's <ref table="Interface"
-          db="Open_vSwitch"/> table, because hypervisors use <ref key="iface-id"
-          table="Interface" column="external_ids" db="Open_vSwitch"/> as a lookup
-          key to identify the network interface of that entity.
-        </p>
-
-        <p>
-          For containers that share a VIF within a VM, the name can be any
-          unique identifier.  See <code>Containers</code>, below, for more
-          information.
-        </p>
-      </column>
-
-      <column name="type">
-        <p>
-          Specify a type for this logical port.  Logical ports can be used to
-          model other types of connectivity into an OVN logical switch.  The
-          following types are defined:
-        </p>
-
-        <dl>
-          <dt>(empty string)</dt>
-          <dd>
-            A VM (or VIF) interface.
-          </dd>
-
-          <dt><code>router</code></dt>
-          <dd>
-            A connection to a logical router.
-          </dd>
-
-          <dt><code>localnet</code></dt>
-          <dd>
-            A connection to a locally accessible network from each
-            <code>ovn-controller</code> instance.  A logical switch can only
-            have a single <code>localnet</code> port attached.  This is used
-            to model direct connectivity to an existing network.
-          </dd>
-
-          <dt><code>localport</code></dt>
-          <dd>
-            A connection to a local VIF. Traffic that arrives on a
-            <code>localport</code> is never forwarded over a tunnel to another
-            chassis. These ports are present on every chassis and have the same
-            address in all of them. This is used to model connectivity to local
-            services that run on every hypervisor.
-          </dd>
-
-          <dt><code>l2gateway</code></dt>
-          <dd>
-            A connection to a physical network.
-          </dd>
-
-          <dt><code>vtep</code></dt>
-          <dd>
-            A port to a logical switch on a VTEP gateway.
-          </dd>
-
-          <dt><code>external</code></dt>
-          <dd>
-            <p>
-              Represents a logical port which is external and not having
-              an OVS port in the integration bridge.
-              <code>OVN</code> will never receive any traffic from this port or
-              send any traffic to this port. <code>OVN</code> can support
-              native services like DHCPv4/DHCPv6/DNS for this port.
-              If <ref column="ha_chassis_group"/> is defined,
-              <code>ovn-controller</code> running in the master chassis of
-              the HA chassis group will bind this port to provide these native
-              services. It is expected that this port belong to a bridged
-              logical switch (with a <code>localnet</code> port).
-            </p>
-
-            <p>
-              It is recommended to use the same HA chassis group for all the
-              external ports of a logical switch. Otherwise, the physical
-              switch might see MAC flap issue when different chassis provide
-              the native services. For example when supporting native DHCPv4
-              service, DHCPv4 server mac (configured in
-              <ref column="options:server_mac" table="DHCP_Options"
-              db="OVN_NB"/> column in table <ref table="DHCP_Options"/>)
-              originating from different ports can cause MAC flap issue.
-              The MAC of the logical router IP(s) can also flap if the
-              same HA chassis group is not set for all the external ports
-              of a logical switch.
-            </p>
-
-            <p>
-              Below are some of the use cases where <code>external</code>
-              ports can be used.
-            </p>
-
-            <ul>
-              <li>
-                VMs connected to SR-IOV nics - Traffic from these VMs by passes
-                the kernel stack and local <code>ovn-controller</code> do not
-                bind these ports and cannot serve the native services.
-              </li>
-
-              <li>
-                When CMS supports provisioning baremetal servers.
-              </li>
-            </ul>
-          </dd>
-        </dl>
-      </column>
-    </group>
-
-    <group title="Options">
-      <column name="options">
-        This column provides key/value settings specific to the logical port
-        <ref column="type"/>.  The type-specific options are described
-        individually below.
-      </column>
-
-      <group title="Options for router ports">
-        <p>
-          These options apply when <ref column="type"/> is <code>router</code>.
-        </p>
-
-        <column name="options" key="router-port">
-          Required.  The <ref column="name"/> of the <ref
-          table="Logical_Router_Port"/> to which this logical switch port is
-          connected.
-        </column>
-
-        <column name="options" key="nat-addresses">
-          <p>
-            This is used to send gratuitous ARPs for SNAT and DNAT IP
-            addresses via the <code>localnet</code> port that is attached
-            to the same logical switch as this type <code>router</code>
-            port.  This option is specified on a logical switch port that is
-            connected to a gateway router, or a logical switch port that is
-            connected to a distributed gateway port on a logical router.
-          </p>
-
-          <p>
-            This must take one of the following forms:
-          </p>
-
-          <dl>
-            <dt><code>router</code></dt>
-            <dd>
-              <p>
-                Gratuitous ARPs will be sent for all SNAT and DNAT external IP
-                addresses and for all load balancer IP addresses defined on the
-                <ref column="options" key="router-port"/>'s logical router,
-                using the <ref column="options" key="router-port"/>'s MAC
-                address.
-              </p>
-
-              <p>
-                This form of <ref column="options" key="nat-addresses"/> is
-                valid for logical switch ports where <ref column="options"
-                key="router-port"/> is the name of a port on a gateway router,
-                or the name of a distributed gateway port.
-              </p>
-
-              <p>
-                Supported only in OVN 2.8 and later.  Earlier versions required
-                NAT addresses to be manually synchronized.
-              </p>
-            </dd>
-
-            <dt><code>Ethernet address followed by one or more IPv4 addresses</code></dt>
-            <dd>
-              <p>
-                Example: <code>80:fa:5b:06:72:b7 158.36.44.22
-                158.36.44.24</code>. This would result in generation of
-                gratuitous ARPs for IP addresses 158.36.44.22 and 158.36.44.24
-                with a MAC address of 80:fa:5b:06:72:b7.
-              </p>
-
-              <p>
-                This form of <ref column="options" key="nat-addresses"/> is
-                only valid for logical switch ports where <ref column="options"
-                key="router-port"/> is the name of a port on a gateway router.
-              </p>
-            </dd>
-          </dl>
-        </column>
-      </group>
-
-      <group title="Options for localnet ports">
-        <p>
-          These options apply when <ref column="type"/> is
-          <code>localnet</code>.
-        </p>
-
-        <column name="options" key="network_name">
-          Required.  The name of the network to which the <code>localnet</code>
-          port is connected.  Each hypervisor, via <code>ovn-controller</code>,
-          uses its local configuration to determine exactly how to connect to
-          this locally accessible network.
-        </column>
-      </group>
-
-      <group title="Options for l2gateway ports">
-        <p>
-          These options apply when <ref column="type"/> is
-          <code>l2gateway</code>.
-        </p>
-
-        <column name="options" key="network_name">
-          Required.  The name of the network to which the <code>l2gateway</code>
-          port is connected.  The L2 gateway, via <code>ovn-controller</code>,
-          uses its local configuration to determine exactly how to connect to
-          this network.
-        </column>
-
-        <column name="options" key="l2gateway-chassis">
-          Required. The chassis on which the <code>l2gateway</code> logical
-          port should be bound to. <code>ovn-controller</code> running on the
-          defined chassis will connect this logical port to the physical network.
-        </column>
-
-      </group>
-
-      <group title="Options for vtep ports">
-        <p>
-          These options apply when <ref column="type"/> is <code>vtep</code>.
-        </p>
-
-        <column name="options" key="vtep-physical-switch">
-          Required.  The name of the VTEP gateway.
-        </column>
-
-        <column name="options" key="vtep-logical-switch">
-          Required.  A logical switch name connected by the VTEP gateway.
-        </column>
-      </group>
-
-      <group title="VMI (or VIF) Options">
-        <p>
-          These options apply to logical ports with <ref column="type"/> having
-          (empty string)
-        </p>
-
-        <column name="options" key="requested-chassis">
-          If set, identifies a specific chassis (by name or hostname) that
-          is allowed to bind this port. Using this option will prevent
-          thrashing between two chassis trying to bind the same port during
-          a live migration. It can also prevent similar thrashing due to a
-          mis-configuration, if a port is accidentally created on more than
-          one chassis.
-        </column>
-
-        <column name="options" key="qos_max_rate">
-          If set, indicates the maximum rate for data sent from this interface,
-          in bit/s. The traffic will be shaped according to this limit.
-        </column>
-
-        <column name="options" key="qos_burst">
-          If set, indicates the maximum burst size for data sent from this
-          interface, in bits.
-        </column>
-      </group>
-    </group>
-
-    <group title="Containers">
-      <p>
-        When a large number of containers are nested within a VM, it may be too
-        expensive to dedicate a VIF to each container.  OVN can use VLAN tags
-        to support such cases.  Each container is assigned a VLAN ID and each
-        packet that passes between the hypervisor and the VM is tagged with the
-        appropriate ID for the container.  Such VLAN IDs never appear on a
-        physical wire, even inside a tunnel, so they need not be unique except
-        relative to a single VM on a hypervisor.
-      </p>
-
-      <p>
-        These columns are used for VIFs that represent nested containers using
-        shared VIFs.  For VMs and for containers that have dedicated VIFs, they
-        are empty.
-      </p>
-
-      <column name="parent_name">
-        The VM interface through which the nested container sends its network
-        traffic.  This must match the <ref column="name"/> column for some
-        other <ref table="Logical_Switch_Port"/>.
-      </column>
-
-      <column name="tag_request">
-        <p>
-          The VLAN tag in the network traffic associated with a container's
-          network interface.  The client can request <code>ovn-northd</code>
-          to allocate a tag that is unique within the scope of a specific
-          parent (specified in <ref column="parent_name"/>) by setting a value
-          of <code>0</code> in this column.  The allocated value is written
-          by <code>ovn-northd</code> in the <ref column="tag"/> column.
-          (Note that these tags are allocated and managed locally in
-          <code>ovn-northd</code>, so they cannot be reconstructed in the event
-          that the database is lost.)  The client can also request a specific
-          non-zero tag and <code>ovn-northd</code> will honor it and copy that
-          value to the <ref column="tag"/> column.
-        </p>
-
-        <p>
-          When <ref column="type"/> is set to <code>localnet</code> or
-          <code>l2gateway</code>, this can
-          be set to indicate that the port represents a connection to a
-          specific VLAN on a locally accessible network. The VLAN ID is used
-          to match incoming traffic and is also added to outgoing traffic.
-        </p>
-      </column>
-
-      <column name="tag">
-        <p>
-          The VLAN tag allocated by <code>ovn-northd</code> based on the
-          contents of the <ref column="tag_request"/> column.
-        </p>
-      </column>
-    </group>
-
-    <group title="Port State">
-      <column name="up">
-        <p>
-          This column is populated by <code>ovn-northd</code>, rather
-          than by the CMS plugin as is most of this database.  When a
-          logical port is bound to a physical location in the OVN
-          Southbound database <ref db="OVN_Southbound"
-          table="Binding"/> table, <code>ovn-northd</code> sets this
-          column to <code>true</code>; otherwise, or if the port
-          becomes unbound later, it sets it to <code>false</code>.
-          This allows the CMS to wait for a VM's (or container's)
-          networking to become active before it allows the VM (or
-          container) to start.
-        </p>
-
-        <p>
-          Logical ports of router type are an exception to this rule.
-          They are considered to be always up, that is this column is
-          always set to <code>true</code>.
-        </p>
-      </column>
-
-      <column name="enabled">
-        This column is used to administratively set port state.  If this column
-        is empty or is set to <code>true</code>, the port is enabled.  If this
-        column is set to <code>false</code>, the port is disabled.  A disabled
-        port has all ingress and egress traffic dropped.
-      </column>
-
-    </group>
-
-    <group title="Addressing">
-      <column name="addresses">
-        <p>
-          Addresses owned by the logical port.
-        </p>
-
-        <p>
-          Each element in the set must take one of the following forms:
-        </p>
-
-        <dl>
-          <dt><code>Ethernet address followed by zero or more IPv4 or IPv6 addresses (or both)</code></dt>
-          <dd>
-            <p>
-              An Ethernet address defined is owned by the logical port.
-              Like a physical Ethernet NIC, a logical port ordinarily has
-              a single fixed Ethernet address.
-            </p>
-
-            <p>
-              When a OVN logical switch processes a unicast Ethernet frame
-              whose destination MAC address is in a logical port's <ref
-              column="addresses"/> column, it delivers it only to that port, as
-              if a MAC learning process had learned that MAC address on the
-              port.
-            </p>
-
-            <p>
-              If IPv4 or IPv6 address(es) (or both) are defined, it indicates
-              that the logical port owns the given IP addresses.
-            </p>
-
-            <p>
-              If IPv4 address(es) are defined, the OVN logical switch uses this
-              information to synthesize responses to ARP requests without
-              traversing the physical network. The OVN logical router connected
-              to the logical switch, if any, uses this information to avoid
-              issuing ARP requests for logical switch ports.
-            </p>
-
-            <p>
-              Note that the order here is important. The Ethernet address must
-              be listed before the IP address(es) if defined.
-            </p>
-
-            <p>
-              Examples:
-            </p>
-
-            <dl>
-              <dt><code>80:fa:5b:06:72:b7</code></dt>
-              <dd>
-                This indicates that the logical port owns the above mac address.
-              </dd>
-
-              <dt><code>80:fa:5b:06:72:b7 10.0.0.4 20.0.0.4</code></dt>
-              <dd>
-                This indicates that the logical port owns the mac address and two
-                IPv4 addresses.
-              </dd>
-
-              <dt><code>80:fa:5b:06:72:b7 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41</code></dt>
-              <dd>
-                This indicates that the logical port owns the mac address and
-                1 IPv6 address.
-              </dd>
-
-              <dt><code>80:fa:5b:06:72:b7 10.0.0.4 fdaa:15f2:72cf:0:f816:3eff:fe20:3f41</code></dt>
-              <dd>
-                This indicates that the logical port owns the mac address and
-                1 IPv4 address and 1 IPv6 address.
-              </dd>
-            </dl>
-          </dd>
-
-          <dt><code>unknown</code></dt>
-          <dd>
-            This indicates that the logical port has an unknown set of Ethernet
-            addresses.  When an OVN logical switch processes a unicast Ethernet
-            frame whose destination MAC address is not in any logical port's
-            <ref column="addresses"/> column, it delivers it to the port (or
-            ports) whose <ref column="addresses"/> columns include
-            <code>unknown</code>.
-          </dd>
-
-          <dt><code>dynamic</code></dt>
-          <dd>
-            Use this keyword to make <code>ovn-northd</code> generate a
-            globally unique MAC address and choose an unused IPv4 address with
-            the logical port's subnet and store them in the port's <ref
-            column="dynamic_addresses"/> column.  <code>ovn-northd</code> will
-            use the subnet specified in <ref table="Logical_Switch"
-            column="other_config" key="subnet"/> in the port's <ref
-            table="Logical_Switch"/>.
-          </dd>
-
-          <dt><code>Ethernet address followed by keyword "dynamic"</code></dt>
-          <dd>
-
-            <p>
-              The keyword <code>dynamic</code> after the MAC address indicates
-              that <code>ovn-northd</code> should choose an unused IPv4 address
-              from the logical port's subnet and store it with the specified
-              MAC in the port's <ref column="dynamic_addresses"/> column.
-              <code>ovn-northd</code> will use the subnet specified in <ref
-              table="Logical_Switch" column="other_config" key="subnet"/> in
-              the port's <ref table="Logical_Switch"/> table.
-            </p>
-
-            <p>
-              Examples:
-            </p>
-
-            <dl>
-              <dt><code>80:fa:5b:06:72:b7 dynamic</code></dt>
-              <dd>
-                This indicates that the logical port owns the specified
-                MAC address and <code>ovn-northd</code> should allocate an
-                unused IPv4 address for the logical port from the corresponding
-                logical switch subnet.
-              </dd>
-            </dl>
-          </dd>
-
-          <dt><code>Keyword "dynamic" followed by an IPv4/IPv6 address</code></dt>
-          <dd>
-
-            <p>
-              The keyword <code>dynamic</code> followed by an IPv4/IPv6
-              address indicates that <code>ovn-northd</code> should choose
-              a dynamic ethernet address and use the provided IPv4/IPv6 address
-              as network address.
-            </p>
-
-            <p>
-              Examples:
-            </p>
-
-            <dl>
-              <dt><code>dynamic 192.168.0.1 2001::1</code></dt>
-              <dd>
-                This indicates that <code>ovn-northd</code> should allocate
-                a unique MAC address and use the provided IPv4/IPv6 address
-                for the related port
-              </dd>
-            </dl>
-          </dd>
-
-          <dt><code>router</code></dt>
-          <dd>
-            <p>
-              Accepted only when <ref column="type"/> is <code>router</code>.
-              This indicates that the Ethernet, IPv4, and IPv6 addresses for
-              this logical switch port should be obtained from the connected
-              logical router port, as specified by <code>router-port</code> in
-              <ref column="options"/>.
-            </p>
-
-            <p>
-              The resulting addresses are used to populate the logical
-              switch's destination lookup, and also for the logical switch
-              to generate ARP and ND replies.
-            </p>
-
-            <p>
-              If the connected logical router port has a
-              <code>redirect-chassis</code> specified and the logical router
-              has rules specified in <ref column="nat" table="Logical_Router"/>
-              with <ref column="external_mac" table="NAT"/>, then those
-              addresses are also used to populate the switch's destination
-              lookup.
-            </p>
-
-            <p>
-              Supported only in OVN 2.7 and later.  Earlier versions required
-              router addresses to be manually synchronized.
-            </p>
-          </dd>
-
-        </dl>
-      </column>
-
-      <column name="dynamic_addresses">
-        <p>
-          Addresses assigned to the logical port by <code>ovn-northd</code>, if
-          <code>dynamic</code> is specified in <ref column="addresses"/>.
-          Addresses will be of the same format as those that populate the <ref
-          column="addresses"/> column.  Note that dynamically assigned
-          addresses are constructed and managed locally in ovn-northd, so they
-          cannot be reconstructed in the event that the database is lost.
-        </p>
-      </column>
-
-      <column name="port_security">
-        <p>
-          This column controls the addresses from which the host attached to the
-          logical port (``the host'') is allowed to send packets and to which it
-          is allowed to receive packets.  If this column is empty, all addresses
-          are permitted.
-        </p>
-
-        <p>
-          Each element in the set must begin with one Ethernet address.
-          This would restrict the host to sending packets from and receiving
-          packets to the ethernet addresses defined in the logical port's
-          <ref column="port_security"/> column. It also restricts the inner
-          source MAC addresses that the host may send in ARP and IPv6
-          Neighbor Discovery packets. The host is always allowed to receive packets
-          to multicast and broadcast Ethernet addresses.
-        </p>
-
-        <p>
-          Each element in the set may additionally contain one or more IPv4 or
-          IPv6 addresses (or both), with optional masks.  If a mask is given, it
-          must be a CIDR mask.  In addition to the restrictions described for
-          Ethernet addresses above, such an element restricts the IPv4 or IPv6
-          addresses from which the host may send and to which it may receive
-          packets to the specified addresses.  A masked address, if the host part
-          is zero, indicates that the host is allowed to use any address in the
-          subnet; if the host part is nonzero, the mask simply indicates the size
-          of the subnet. In addition:
-        </p>
-
-        <ul>
-          <li>
-            <p>
-              If any IPv4 address is given, the host is also allowed to receive
-              packets to the IPv4 local broadcast address 255.255.255.255 and to
-              IPv4 multicast addresses (224.0.0.0/4).  If an IPv4 address with a
-              mask is given, the host is also allowed to receive packets to the
-              broadcast address in that specified subnet.
-            </p>
-
-            <p>
-              If any IPv4 address is given, the host is additionally restricted
-              to sending ARP packets with the specified source IPv4 address.
-              (RARP is not restricted.)
-            </p>
-          </li>
-
-          <li>
-            <p>
-              If any IPv6 address is given, the host is also allowed to receive
-              packets to IPv6 multicast addresses (ff00::/8).
-            </p>
-
-            <p>
-              If any IPv6 address is given, the host is additionally restricted
-              to sending IPv6 Neighbor Discovery Solicitation or Advertisement
-              packets with the specified source address or, for solicitations,
-              the unspecified address.
-            </p>
-          </li>
-        </ul>
-
-        <p>
-          If an element includes an IPv4 address, but no IPv6 addresses, then
-          IPv6 traffic is not allowed.  If an element includes an IPv6 address,
-          but no IPv4 address, then IPv4 and ARP traffic is not allowed.
-        </p>
-
-        <p>
-          This column uses the same lexical syntax as the <ref column="match"
-          table="Pipeline" db="OVN_Southbound"/> column in the OVN Southbound
-          database's <ref table="Pipeline" db="OVN_Southbound"/> table.  Multiple
-          addresses within an element may be space or comma separated.
-        </p>
-
-        <p>
-          This column is provided as a convenience to cloud management systems,
-          but all of the features that it implements can be implemented as ACLs
-          using the <ref table="ACL"/> table.
-        </p>
-
-        <p>
-          Examples:
-        </p>
-
-        <dl>
-          <dt><code>80:fa:5b:06:72:b7</code></dt>
-          <dd>
-            The host may send traffic from and receive traffic to the specified
-            MAC address, and to receive traffic to Ethernet multicast and
-            broadcast addresses, but not otherwise.  The host may not send ARP or
-            IPv6 Neighbor Discovery packets with inner source Ethernet addresses
-            other than the one specified.
-          </dd>
-
-          <dt><code>80:fa:5b:06:72:b7 192.168.1.10/24</code></dt>
-          <dd>
-            This adds further restrictions to the first example.  The host may
-            send IPv4 packets from or receive IPv4 packets to only 192.168.1.10,
-            except that it may also receive IPv4 packets to 192.168.1.255 (based
-            on the subnet mask), 255.255.255.255, and any address in 224.0.0.0/4.
-            The host may not send ARPs with a source Ethernet address other than
-            80:fa:5b:06:72:b7 or source IPv4 address other than 192.168.1.10.
-            The host may not send or receive any IPv6 (including IPv6 Neighbor
-            Discovery) traffic.
-          </dd>
-
-          <dt><code>"80:fa:5b:12:42:ba", "80:fa:5b:06:72:b7 192.168.1.10/24"</code></dt>
-          <dd>
-            The host may send traffic from and receive traffic to the
-            specified MAC addresses, and
-            to receive traffic to Ethernet multicast and broadcast addresses,
-            but not otherwise.   With MAC 80:fa:5b:12:42:ba, the host may
-            send traffic from and receive traffic to any L3 address.
-            With MAC 80:fa:5b:06:72:b7, the host may send IPv4 packets from or
-            receive IPv4 packets to only 192.168.1.10, except that it may also
-            receive IPv4 packets to 192.168.1.255 (based on the subnet mask),
-            255.255.255.255, and any address in 224.0.0.0/4.  The host may not
-            send or receive any IPv6 (including IPv6 Neighbor Discovery) traffic.
-          </dd>
-        </dl>
-      </column>
-    </group>
-
-    <group title="DHCP">
-      <column name="dhcpv4_options">
-        This column defines the DHCPv4 Options to be included by the
-        <code>ovn-controller</code> when it replies to the DHCPv4 requests.
-        Please see the <ref table="DHCP_Options"/> table.
-      </column>
-
-      <column name="dhcpv6_options">
-        This column defines the DHCPv6 Options to be included by the
-        <code>ovn-controller</code> when it replies to the DHCPv6 requests.
-        Please see the <ref table="DHCP_Options"/> table.
-      </column>
-    </group>
-
-    <column name="ha_chassis_group">
-      References a row in the OVN Northbound database's
-      <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
-      It indicates the HA chassis group to use if the
-      <ref column="type"/> is set to <code>external</code>.
-      If <ref column="type"/> is not <code>external</code>, this
-      column is ignored.
-    </column>
-
-    <group title="Naming">
-      <column name="external_ids" key="neutron:port_name">
-        <p>
-          This column gives an optional human-friendly name for the port.  This
-          name has no special meaning or purpose other than to provide
-          convenience for human interaction with the northbound database.
-        </p>
-
-        <p>
-          Neutron copies this from its own port object's name.  (Neutron ports
-          do are not assigned human-friendly names by default, so it will often
-          be empty.)
-        </p>
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        <p>
-          See <em>External IDs</em> at the beginning of this document.
-        </p>
-
-        <p>
-          The <code>ovn-northd</code> program copies all these pairs into the
-          <ref column="external_ids"/> column of the
-          <ref table="Port_Binding"/> table in <ref db="OVN_Southbound"/>
-          database.
-        </p>
-      </column>
-    </group>
-  </table>
-
-  <table name="Address_Set" title="Address Sets">
-    <p>
-      Each row in this table represents a named set of addresses.
-      An address set may contain Ethernet, IPv4, or IPv6 addresses
-      with optional bitwise or CIDR masks.
-      Address set may ultimately be used in ACLs to compare against
-      fields such as <code>ip4.src</code> or <code>ip6.src</code>.
-      A single address set must contain addresses of the
-      same type. As an example, the following would create an address set
-      with three IP addresses:
-    </p>
-
-    <pre>
-      ovn-nbctl create Address_Set name=set1 addresses='10.0.0.1 10.0.0.2 10.0.0.3'
-    </pre>
-
-    <p>
-      Address sets may be used in the <ref column="match" table="ACL"/> column
-      of the <ref table="ACL"/> table.  For syntax information, see the details
-      of the expression language used for the <ref column="match"
-      table="Logical_Flow" db="OVN_Southbound"/> column in the <ref
-      table="Logical_Flow" db="OVN_Southbound"/> table of the <ref
-      db="OVN_Southbound"/> database.
-    </p>
-
-    <column name="name">
-      A name for the address set.  Names are ASCII and must match
-      <code>[a-zA-Z_.][a-zA-Z_.0-9]*</code>.
-    </column>
-
-    <column name="addresses">
-      The set of addresses in string form.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Port_Group" title="Port Groups">
-    <p>
-      Each row in this table represents a named group of logical switch ports.
-    </p>
-
-    <p>
-      Port groups may be used in the <ref column="match" table="ACL"/> column
-      of the <ref table="ACL"/> table.  For syntax information, see the details
-      of the expression language used for the <ref column="match"
-      table="Logical_Flow" db="OVN_Southbound"/> column in the <ref
-      table="Logical_Flow" db="OVN_Southbound"/> table of the <ref
-      db="OVN_Southbound"/> database.
-    </p>
-
-    <p>
-      For each port group, there are two address sets generated to the
-      <ref table="Address_Set" db="OVN_Southbound"/> table of the
-      <ref db="OVN_Southbound"/> database, containing the IP addresses
-      of the group of ports, one for IPv4, and the other for IPv6, with
-      <ref column="name" table="Address_Set" db="OVN_Southbound"/> being
-      the <ref column="name" table="Port_Group" db="OVN_Northbound"/>
-      of the <ref table="Port_Group" db="OVN_Northbound"/> followed by
-      a suffix <code>_ip4</code> for IPv4 and <code>_ip6</code> for IPv6.
-      The generated address sets can be used in the same way as regular
-      address sets in the <ref column="match" table="ACL"/> column
-      of the <ref table="ACL"/> table. For syntax information, see the details
-      of the expression language used for the <ref column="match"
-      table="Logical_Flow" db="OVN_Southbound"/> column in the <ref
-      table="Logical_Flow" db="OVN_Southbound"/> table of the <ref
-      db="OVN_Southbound"/> database.
-    </p>
-
-    <column name="name">
-      A name for the port group.  Names are ASCII and must match
-      <code>[a-zA-Z_.][a-zA-Z_.0-9]*</code>.
-    </column>
-
-    <column name="ports">
-      The logical switch ports belonging to the group in uuids.
-    </column>
-
-    <column name="acls">
-      Access control rules that apply to the port group. Applying an ACL
-      to a port group has the same effect as applying the ACL to all logical
-      lswitches that the ports of the port group belong to.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Load_Balancer" title="load balancer">
-    <p>
-      Each row represents one load balancer.
-    </p>
-
-    <column name="name">
-      A name for the load balancer.  This name has no special meaning or
-      purpose other than to provide convenience for human interaction with
-      the ovn-nb database.
-    </column>
-
-    <column name="vips">
-      <p>
-        A map of virtual IP addresses (and an optional port number with
-        <code>:</code> as a separator) associated with this load balancer and
-        their corresponding endpoint IP addresses (and optional port numbers
-        with <code>:</code> as separators) separated by commas.  If
-        the destination IP address (and port number) of a packet leaving a
-        container or a VM matches the virtual IP address (and port number)
-        provided here as a key, then OVN will statefully replace the
-        destination IP address by one of the provided IP address (and port
-        number) in this map as a value.  IPv4 and IPv6 addresses are supported
-        for load balancing; however a VIP of one address family may not be
-        mapped to a destination IP address of a different family.  If
-        specifying an IPv6 address with a port, the address portion must be
-        enclosed in square brackets.  Examples for keys are "192.168.1.4" and
-        "[fd0f::1]:8800".  Examples for value are "10.0.0.1, 10.0.0.2" and
-        "20.0.0.10:8800, 20.0.0.11:8800".
-      </p>
-      <p>
-        When the <code>Load_Balancer</code> is added to the
-        <code>logical_switch</code>, the VIP has to be in a different subnet
-        than the one used for the <code>logical_switch</code>.  Since VIP is
-        in a different subnet, you should connect your logical switch to
-        either a OVN logical router or a real router (this is because the
-        client can now send a packet with VIP as the destination IP address
-        and router's mac address as the destination MAC address).
-      </p>
-    </column>
-
-    <column name="protocol">
-      <p>
-        Valid protocols are <code>tcp</code> or <code>udp</code>.  This column
-        is useful when a port number is provided as part of the
-        <code>vips</code> column.  If this column is empty and a port number
-        is provided as part of <code>vips</code> column, OVN assumes the
-        protocol to be <code>tcp</code>.
-      </p>
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="ACL" title="Access Control List (ACL) rule">
-    <p>
-      Each row in this table represents one ACL rule for a logical switch
-      or a port group that points to it through its <ref column="acls"/>
-      column.  The <ref column="action"/> column for the
-      highest-<ref column="priority"/> matching row in this table determines a
-      packet's treatment.  If no row matches, packets are allowed by default.
-      (Default-deny treatment is possible: add a rule with
-      <ref column="priority"/> 0, <code>1</code> as <ref column="match"/>,
-      and <code>deny</code> as <ref column="action"/>.)
-    </p>
-
-    <column name="priority">
-      <p>
-        The ACL rule's priority.  Rules with numerically higher priority
-        take precedence over those with lower.  If two ACL rules with
-        the same priority both match, then the one actually applied to a
-        packet is undefined.
-      </p>
-
-      <p>
-        Return traffic from an <code>allow-related</code> flow is always
-        allowed and cannot be changed through an ACL.
-      </p>
-    </column>
-
-    <column name="direction">
-      <p>Direction of the traffic to which this rule should apply:</p>
-      <ul>
-        <li>
-          <code>from-lport</code>: Used to implement filters on traffic
-          arriving from a logical port.  These rules are applied to the
-          logical switch's ingress pipeline.
-        </li>
-        <li>
-          <code>to-lport</code>: Used to implement filters on traffic
-          forwarded to a logical port.  These rules are applied to the
-          logical switch's egress pipeline.
-        </li>
-      </ul>
-    </column>
-
-    <column name="match">
-      <p>
-        The packets that the ACL should match, in the same expression
-        language used for the <ref column="match" table="Logical_Flow"
-        db="OVN_Southbound"/> column in the OVN Southbound database's
-        <ref table="Logical_Flow" db="OVN_Southbound"/> table.  The
-        <code>outport</code> logical port is only available in the
-        <code>to-lport</code> direction (the <code>inport</code> is
-        available in both directions).
-      </p>
-
-      <p>
-        By default all traffic is allowed.  When writing a more
-        restrictive policy, it is important to remember to allow flows
-        such as ARP and IPv6 neighbor discovery packets.
-      </p>
-
-      <p>
-        Note that you can not create an ACL matching on a port with
-        type=router or type=localnet.
-      </p>
-    </column>
-
-    <column name="action">
-      <p>The action to take when the ACL rule matches:</p>
-      <ul>
-        <li>
-          <code>allow</code>: Forward the packet.
-        </li>
-
-        <li>
-          <code>allow-related</code>: Forward the packet and related traffic
-          (e.g. inbound replies to an outbound connection).
-        </li>
-
-        <li>
-          <code>drop</code>: Silently drop the packet.
-        </li>
-
-        <li>
-          <code>reject</code>: Drop the packet, replying with a RST for TCP or
-          ICMPv4/ICMPv6 unreachable message for other IPv4/IPv6-based
-          protocols.
-        </li>
-      </ul>
-    </column>
-
-    <group title="Logging">
-      <p>
-        These columns control whether and how OVN logs packets that match an
-        ACL.
-      </p>
-
-      <column name="log">
-        <p>
-          If set to <code>true</code>, packets that match the ACL will trigger
-          a log message on the transport node or nodes that perform ACL
-          processing.  Logging may be combined with any <ref column="action"/>.
-        </p>
-
-        <p>
-          If set to <code>false</code>, the remaining columns in this group
-          have no significance.
-        </p>
-      </column>
-
-      <column name="name">
-        <p>
-          This name, if it is provided, is included in log records.  It
-          provides the administrator and the cloud management system a way to
-          associate a log record with a particular ACL.
-        </p>
-      </column>
-
-      <column name="severity">
-        <p>
-          The severity of the ACL.  The severity levels match those of syslog,
-          in decreasing level of severity: <code>alert</code>,
-          <code>warning</code>, <code>notice</code>, <code>info</code>, or
-          <code>debug</code>.  When the column is empty, the default is
-          <code>info</code>.
-        </p>
-      </column>
-
-      <column name="meter">
-        <p>
-            The name of a meter to rate-limit log messages for the ACL.
-            The string must match the <ref column="name" table="meter"/>
-            column of a row in the <ref table="Meter"/> table.  By
-            default, log messages are not rate-limited.
-        </p>
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Logical_Router" title="L3 logical router">
-    <p>
-      Each row represents one L3 logical router.
-    </p>
-
-    <column name="ports">
-      The router's ports.
-    </column>
-
-    <column name="static_routes">
-      Zero or more static routes for the router.
-    </column>
-
-    <column name="policies">
-      Zero or more routing policies for the router.
-    </column>
-
-    <column name="enabled">
-      This column is used to administratively set router state.  If this column
-      is empty or is set to <code>true</code>, the router is enabled.  If this
-      column is set to <code>false</code>, the router is disabled.  A disabled
-      router has all ingress and egress traffic dropped.
-    </column>
-
-    <column name="nat">
-      One or more NAT rules for the router.  NAT rules only work on
-      Gateway routers, and on distributed routers with one logical router
-      port with a <code>redirect-chassis</code> specified.
-    </column>
-
-    <column name="load_balancer">
-      Load balance a virtual ip address to a set of logical port ip
-      addresses.  Load balancer rules only work on the Gateway routers.
-    </column>
-
-    <group title="Naming">
-      <p>
-        These columns provide names for the logical router.  From OVN's
-        perspective, these names have no special meaning or purpose other than
-        to provide convenience for human interaction with the northbound
-        database.  There is no requirement for the name to be unique.  (For a
-        unique identifier for a logical router, use its row UUID.)
-      </p>
-
-      <p>
-        (Originally, <ref column="name"/> was intended to serve the purpose of
-        a human-friendly name, but the Neutron integration used it to uniquely
-        identify its own router object, in the format
-        <code>neutron-<var>uuid</var></code>.  Later on, Neutron started
-        propagating the friendly name of a router as <ref column="external_ids"
-        key="neutron:router_name"/>.  Perhaps this can be cleaned up someday.)
-      </p>
-
-      <column name="name">
-        A name for the logical router.
-      </column>
-
-      <column name="external_ids" key="neutron:router_name">
-        Another name for the logical router.
-      </column>
-    </group>
-
-    <group title="Options">
-      <p>
-        Additional options for the logical router.
-      </p>
-
-      <column name="options" key="chassis">
-        <p>
-          If set, indicates that the logical router in question is a Gateway
-          router (which is centralized) and resides in the set chassis.  The
-          same value is also used by <code>ovn-controller</code> to
-          uniquely identify the chassis in the OVN deployment and
-          comes from <code>external_ids:system-id</code> in the
-          <code>Open_vSwitch</code> table of Open_vSwitch database.
-        </p>
-
-        <p>
-          The Gateway router can only be connected to a distributed router
-          via a switch if SNAT and DNAT are to be configured in the Gateway
-          router.
-        </p>
-      </column>
-      <column name="options" key="dnat_force_snat_ip">
-        <p>
-          If set, indicates the IP address to use to force SNAT a packet
-          that has already been DNATed in the gateway router.  When multiple
-          gateway routers are configured, a packet can potentially enter any
-          of the gateway router, get DNATted and eventually reach the logical
-          switch port.  For the return traffic to go back to the same gateway
-          router (for unDNATing), the packet needs a SNAT in the first place.
-          This can be achieved by setting the above option with a gateway
-          specific IP address.
-        </p>
-      </column>
-      <column name="options" key="lb_force_snat_ip">
-        <p>
-          If set, indicates the IP address to use to force SNAT a packet
-          that has already been load-balanced in the gateway router.  When
-          multiple gateway routers are configured, a packet can potentially
-          enter any of the gateway routers, get DNATted as part of the load-
-          balancing and eventually reach the logical switch port.
-          For the return traffic to go back to the same gateway router (for
-          unDNATing), the packet needs a SNAT in the first place.  This can be
-          achieved by setting the above option with a gateway specific IP
-          address.
-        </p>
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="QoS" title="QoS rule">
-    <p>
-      Each row in this table represents one QoS rule for a logical switch
-      that points to it through its <ref column="qos_rules"/> column.
-      Two types of QoS are supported: DSCP marking and metering.  A
-      <ref column="match"/> with the highest-<ref column="priority"/>
-      will have QoS applied to it.  If the <ref column="action"/> column is
-      specified, then matching packets will have DSCP marking applied.
-      If the <ref column="bandwdith"/> column is specified, then matching
-      packets will have metering applied.  <ref column="action"/> and
-      <ref column="bandwdith"/> are not exclusive, so both marking and
-      metering by defined for the same QoS entry. If no row matches,
-      packets will not have any QoS applied.
-    </p>
-
-    <column name="priority">
-      <p>
-        The QoS rule's priority.  Rules with numerically higher priority
-        take precedence over those with lower.  If two QoS rules with
-        the same priority both match, then the one actually applied to a
-        packet is undefined.
-      </p>
-    </column>
-
-    <column name="direction">
-      <p>
-        The value of this field is similar to <ref colun="direction"
-        table="ACL" db="OVN_Northbound"/> column in the OVN Northbound
-        database's <ref table="ACL" db="OVN_Northbound"/> table.
-      </p>
-    </column>
-
-    <column name="match">
-      <p>
-        The packets that the QoS rules should match, in the same expression
-        language used for the <ref column="match" table="Logical_Flow"
-        db="OVN_Southbound"/> column in the OVN Southbound database's
-        <ref table="Logical_Flow" db="OVN_Southbound"/> table.  The
-        <code>outport</code> logical port is only available in the
-        <code>to-lport</code> direction (the <code>inport</code> is
-        available in both directions).
-      </p>
-    </column>
-
-    <column name="action">
-      <p>When specified, matching flows will have DSCP marking applied.</p>
-      <ul>
-        <li>
-          <code>dscp</code>: The value of this action should be in the
-          range of 0 to 63 (inclusive).
-        </li>
-      </ul>
-    </column>
-
-    <column name="bandwidth">
-      <p>
-         When specified, matching packets will have bandwidth metering
-         applied.  Traffic over the limit will be dropped.
-      </p>
-      <ul>
-        <li>
-          <code>rate</code>: The value of rate limit in kbps.
-        </li>
-        <li>
-          <code>burst</code>: The value of burst rate limit in kilobits.
-          This is optional and needs to specify the <code>rate</code>.
-        </li>
-      </ul>
-    </column>
-
-    <column name="external_ids">
-      See <em>External IDs</em> at the beginning of this document.
-    </column>
-  </table>
-
-  <table name="Meter" title="Meter entry">
-    <p>
-      Each row in this table represents a meter that can be used for QoS or
-      rate-limiting.
-    </p>
-
-    <column name="name">
-      <p>
-        A name for this meter.
-      </p>
-
-      <p>
-        Names that begin with "__" (two underscores) are reserved for
-        OVN internal use and should not be added manually.
-      </p>
-    </column>
-
-    <column name="unit">
-      <p>
-        The unit for <ref column="rate" table="Meter_Band"/> and
-        <ref column="burst_rate" table="Meter_Band"/> parameters in
-        the <ref column="bands"/> entry.  <code>kbps</code> specifies
-        kilobits per second, and <code>pktps</code> specifies packets
-        per second.
-      </p>
-    </column>
-
-    <column name="bands">
-      <p>
-        The bands associated with this meter.  Each band specifies a
-        rate above which the band is to take the action
-        <code>action</code>.  If multiple bands' rates are exceeded,
-        then the band with the highest rate among the exceeded bands is
-        selected.
-      </p>
-    </column>
-
-    <column name="external_ids">
-      See <em>External IDs</em> at the beginning of this document.
-    </column>
-  </table>
-
-  <table name="Meter_Band" title="Band for meter entries">
-    <p>
-      Each row in this table represents a meter band which specifies the
-      rate above which the configured action should be applied.  These bands
-      are referenced by the <ref column="bands" table="Meter"/> column in
-      the <ref table="Meter"/> table.
-    </p>
-
-    <column name="action">
-      <p>
-        The action to execute when this band matches.  The only supported
-        action is <code>drop</code>.
-      </p>
-    </column>
-
-    <column name="rate">
-      <p>
-        The rate limit for this band, in kilobits per second or bits per
-        second, depending on whether the parent <ref table="Meter"/>
-        entry's <ref column="unit" table="Meter"/> column specified
-        <code>kbps</code> or <code>pktps</code>.
-      </p>
-    </column>
-
-    <column name="burst_size">
-      <p>
-        The maximum burst allowed for the band in kilobits or packets,
-        depending on whether <code>kbps</code> or <code>pktps</code> was
-        selected in the parent <ref table="Meter"/> entry's
-        <ref column="unit" table="Meter"/> column.  If the size is zero,
-        the switch is free to select some reasonable value depending on
-        its configuration.
-      </p>
-    </column>
-
-    <column name="external_ids">
-      See <em>External IDs</em> at the beginning of this document.
-    </column>
-  </table>
-
-  <table name="Logical_Router_Port" title="L3 logical router port">
-    <p>
-      A port within an L3 logical router.
-    </p>
-
-    <p>
-      Exactly one <ref table="Logical_Router"/> row must reference a given
-      logical router port.
-    </p>
-
-    <column name="name">
-      <p>
-        A name for the logical router port.
-      </p>
-
-      <p>
-        In addition to provide convenience for human interaction with the
-        northbound database, this column is used as reference by its patch port
-        in <ref table="Logical_Switch_Port"/> or another logical router port in
-        <ref table="Logical_Router_Port"/>.
-      </p>
-    </column>
-
-    <column name="gateway_chassis">
-      <p>
-        This column is ignored if the column
-        <ref column="ha_chassis_group" table="Logical_Router_Port"/>.
-        is set.
-      </p>
-
-      <p>
-        If set, this indicates that this logical router port represents
-        a distributed gateway port that connects this router to a logical
-        switch with a localnet port.  There may be at most one such
-        logical router port on each logical router.
-      </p>
-
-      <p>
-        Several <ref table="Gateway_Chassis"/> can be referenced for a given
-        logical router port.  A single <ref table="Gateway_Chassis"/> is
-        functionally equivalent to setting
-        <ref column="options" key="redirect-chassis"/>.  Refer to the
-        description of <ref column="options" key="redirect-chassis"/>
-        for additional details on gateway handling.
-      </p>
-
-      <p>
-        Defining more than one <ref table="Gateway_Chassis"/> will enable
-        gateway high availability.  Only one gateway will be active at a
-        time.  OVN chassis will use BFD to monitor connectivity to a
-        gateway.  If connectivity to the active gateway is interrupted,
-        another gateway will become active.
-        The <ref column="priority" table="Gateway_Chassis"/> column
-        specifies the order that gateways will be chosen by OVN.
-      </p>
-    </column>
-
-    <column name="ha_chassis_group">
-      <p>
-        If set, this indicates that this logical router port represents
-        a distributed gateway port that connects this router to a logical
-        switch with a localnet port.  There may be at most one such
-        logical router port on each logical router. The HA chassis which
-        are part of the HA chassis group will provide the gateway high
-        availability. Please see the <ref table="HA_Chassis_Group"/> for
-        more details.
-      </p>
-
-      <p>
-        When this column is set, the column
-        <ref column="gateway_chassis" table="Logical_Router_Port"/> will
-        be ignored.
-      </p>
-    </column>
-
-    <column name="networks">
-      <p>
-        The IP addresses and netmasks of the router.  For example,
-        <code>192.168.0.1/24</code> indicates that the router's IP
-        address is 192.168.0.1 and that packets destined to
-        192.168.0.<var>x</var> should be routed to this port.
-      </p>
-
-      <p>
-        A logical router port always adds a link-local IPv6 address
-        (fe80::/64) automatically generated from the interface's MAC
-        address using the modified EUI-64 format.
-      </p>
-    </column>
-
-    <column name="mac">
-      The Ethernet address that belongs to this router port.
-    </column>
-
-    <column name="enabled">
-      This column is used to administratively set port state.  If this column
-      is empty or is set to <code>true</code>, the port is enabled.  If this
-      column is set to <code>false</code>, the port is disabled.  A disabled
-      port has all ingress and egress traffic dropped.
-    </column>
-
-    <group title="ipv6_ra_configs">
-      <p>
-        This column defines the IPv6 ND RA address mode and ND MTU Option to be
-        included by <code>ovn-controller</code> when it replies to the IPv6
-        Router solicitation requests.
-      </p>
-
-      <column name="ipv6_ra_configs" key="address_mode">
-        The address mode to be used for IPv6 address configuration.
-        The supported values are:
-        <ul>
-          <li>
-            <code>slaac</code>: Address configuration using Router
-            Advertisement (RA) packet. The IPv6 prefixes defined in the
-            <ref table="Logical_Router_Port"/> table's
-            <ref table="Logical_Router_Port" column="networks"/> column will
-            be included in the RA's ICMPv6 option - Prefix information.
-          </li>
-
-          <li>
-            <code>dhcpv6_stateful</code>: Address configuration using DHCPv6.
-          </li>
-
-          <li>
-            <code>dhcpv6_stateless</code>: Address configuration using Router
-            Advertisement (RA) packet. Other IPv6 options are provided by
-            DHCPv6.
-          </li>
-        </ul>
-      </column>
-
-      <column name="ipv6_ra_configs" key="mtu">
-        The recommended MTU for the link. Default is 0, which means no MTU
-        Option will be included in RA packet replied by ovn-controller.
-        Per RFC 2460, the mtu value is recommended no less than 1280, so
-        any mtu value less than 1280 will be considered as no MTU Option.
-      </column>
-
-      <column name="ipv6_ra_configs" key="send_periodic">
-        If set to true, then this router interface will send router
-        advertisements periodically.  The default is false.
-      </column>
-
-      <column name="ipv6_ra_configs" key="max_interval">
-        The maximum number of seconds to wait between sending periodic router
-        advertisements.  This option has no effect if <ref
-        column="ipv6_ra_configs" key="send_periodic"/> is false.  The default
-        is 600.
-      </column>
-
-      <column name="ipv6_ra_configs" key="min_interval">
-        The minimum number of seconds to wait between sending periodic router
-        advertisements.  This option has no effect if <ref
-        column="ipv6_ra_configs" key="send_periodic"/> is false.  The default
-        is one-third of <ref column="ipv6_ra_configs" key="max_interval"/>,
-        i.e. 200 seconds if that key is unset.
-      </column>
-    </group>
-
-    <group title="Options">
-      <p>
-        Additional options for the logical router port.
-      </p>
-
-      <column name="options" key="redirect-chassis">
-        <p>
-          If set, this indicates that this logical router port represents
-          a distributed gateway port that connects this router to a logical
-          switch with a localnet port.  There may be at most one such
-          logical router port on each logical router.
-        </p>
-
-        <p>
-          Even when a <code>redirect-chassis</code> is specified, the
-          logical router port still effectively resides on each chassis.
-          However, due to the implications of the use of L2 learning in the
-          physical network, as well as the need to support advanced features
-          such as one-to-many NAT (aka IP masquerading), a subset of the
-          logical router processing is handled in a centralized manner on
-          the specified <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          When this option is specified, the peer logical switch port's
-          <ref column="addresses" table="Logical_Switch_Port"/> must be
-          set to <code>router</code>.  With this setting, the <ref
-          column="external_mac" table="NAT"/>s specified in NAT rules are
-          automatically programmed in the peer logical switch's
-          destination lookup on the chassis where the <ref
-          column="logical_port" table="NAT"/> resides.  In addition, the
-          logical router's MAC address is automatically programmed in the
-          peer logical switch's destination lookup flow on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          When this option is specified and it is desired to generate
-          gratuitous ARPs for NAT addresses, then the peer logical switch
-          port's <ref column="options" key="nat-addresses"
-          table="Logical_Switch_Port"/> should be set to
-          <code>router</code>.
-        </p>
-
-        <p>
-          While <ref column="options" key="redirect-chassis"/> is still
-          supported for backwards compatibility, it is now preferred to
-          specify one or more <ref column="gateway_chassis"/> instead.
-          It is functionally equivalent, but allows you to specify multiple
-          chassis to enable high availability.
-        </p>
-      </column>
-
-      <column name="options" key="reside-on-redirect-chassis">
-        <p>
-          Generally routing is distributed in <code>OVN</code>. The packet
-          from a logical port which needs to be routed hits the router pipeline
-          in the source chassis. For the East-West traffic, the packet is
-          sent directly to the destination chassis. For the outside traffic
-          the packet is sent to the gateway chassis.
-        </p>
-
-        <p>
-          When this option is set, <code>OVN</code> considers this only if
-        </p>
-
-        <ul>
-          <li>
-            The logical router to which this logical router port belongs to
-            has a distributed gateway port.
-          </li>
-
-          <li>
-            The peer's logical switch has a localnet port (representing
-            a VLAN tagged network)
-          </li>
-        </ul>
-
-        <p>
-          When this option is set to <code>true</code>, then the packet
-          which needs to be routed hits the router pipeline in the chassis
-          hosting the distributed gateway router port. The source chassis
-          pushes out this traffic via the localnet port. With this the
-          East-West traffic is no more distributed and will always go through
-          the gateway chassis.
-        </p>
-
-        <p>
-          Without this option set, for any traffic destined to outside from a
-          logical port which belongs to a logical switch with localnet port,
-          the source chassis will send the traffic to the gateway chassis via
-          the tunnel port instead of the localnet port and this could cause MTU
-          issues.
-        </p>
-      </column>
-    </group>
-
-    <group title="Attachment">
-      <p>
-        A given router port serves one of two purposes:
-      </p>
-
-      <ul>
-        <li>
-          To attach a logical switch to a logical router.  A logical router
-          port of this type is referenced by exactly one <ref
-          table="Logical_Switch_Port"/> of type <code>router</code>.
-          The value of <ref column="name"/> is set as
-          <code>router-port</code> in column <ref column="options"/> of
-          <ref table="Logical_Switch_Port"/>.  In this case <ref
-          column="peer"/> column is empty.
-        </li>
-
-        <li>
-          To connect one logical router to another.  This requires a pair of
-          logical router ports, each connected to a different router.  Each
-          router port in the pair specifies the other in its <ref
-          column="peer"/> column.  No <ref table="Logical_Switch"/> refers to
-          the router port.
-        </li>
-      </ul>
-
-      <column name="peer">
-        <p>
-          For a router port used to connect two logical routers, this
-          identifies the other router port in the pair by <ref column="name"/>.
-        </p>
-
-        <p>
-          For a router port attached to a logical switch, this column is empty.
-        </p>
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Logical_Router_Static_Route" title="Logical router static routes">
-    <p>
-      Each record represents a static route.
-    </p>
-
-    <p>
-      When multiple routes match a packet, the longest-prefix match is chosen.
-      For a given prefix length, a <code>dst-ip</code> route is preferred over
-      a <code>src-ip</code> route.
-    </p>
-
-    <column name="ip_prefix">
-      <p>
-        IP prefix of this route (e.g. 192.168.100.0/24).
-      </p>
-    </column>
-
-    <column name="policy">
-      <p>
-        If it is specified, this setting describes the policy used to make
-        routing decisions.  This setting must be one of the following strings:
-      </p>
-      <ul>
-        <li>
-          <code>src-ip</code>: This policy sends the packet to the
-          <ref column="nexthop"/> when the packet's source IP address matches
-          <ref column="ip_prefix"/>.
-       </li>
-        <li>
-          <code>dst-ip</code>: This policy sends the packet to the
-          <ref column="nexthop"/> when the packet's destination IP address
-          matches <ref column="ip_prefix"/>.
-        </li>
-      </ul>
-      <p>
-        If not specified, the default is <code>dst-ip</code>.
-     </p>
-    </column>
-
-    <column name="nexthop">
-      <p>
-        Nexthop IP address for this route.  Nexthop IP address should be the IP
-        address of a connected router port or the IP address of a logical port.
-      </p>
-    </column>
-
-    <column name="output_port">
-      <p>
-        The name of the <ref table="Logical_Router_Port"/> via which the packet
-        needs to be sent out.  This is optional and when not specified,
-        OVN will automatically figure this out based on the
-        <ref column="nexthop"/>.  When this is specified and there are
-        multiple IP addresses on the router port and none of them are in the
-        same subnet of <ref column="nexthop"/>, OVN chooses the first IP
-        address as the one via which the <ref column="nexthop"/> is reachable.
-      </p>
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-
-  </table>
-
-  <table name="Logical_Router_Policy" title="Logical router policies">
-    <p>
-      Each row in this table represents one routing policy for a logical router
-      that points to it through its <ref column="policies"/> column.  The <ref
-      column="action"/> column for the highest-<ref column="priority"/>
-      matching row in this table determines a packet's treatment.  If no row
-      matches, packets are allowed by default. (Default-deny treatment is
-      possible: add a rule with <ref column="priority"/> 0, <code>1</code> as
-      <ref column="match"/>, and <code>drop</code> as <ref column="action"/>.)
-    </p>
-
-    <column name="priority">
-      <p>
-        The routing policy's priority.  Rules with numerically higher priority
-        take precedence over those with lower. A rule is uniquely identified
-        by the priority and match string.
-      </p>
-    </column>
-
-    <column name="match">
-      <p>
-        The packets that the routing policy should match,
-        in the same expression language used for the
-        <ref column="match" table="Logical_Flow" db="OVN_Southbound"/>
-        column in the OVN Southbound database's
-        <ref table="Logical_Flow" db="OVN_Southbound"/> table.
-      </p>
-
-      <p>
-        By default all traffic is allowed.  When writing a more
-        restrictive policy, it is important to remember to allow flows
-        such as ARP and IPv6 neighbor discovery packets.
-      </p>
-    </column>
-
-    <column name="action">
-      <p>The action to take when the routing policy matches:</p>
-      <ul>
-        <li>
-          <code>allow</code>: Forward the packet.
-        </li>
-
-        <li>
-          <code>drop</code>: Silently drop the packet.
-        </li>
-
-        <li>
-          <code>reroute</code>: Reroute packet to <ref column="nexthop"/>.
-        </li>
-      </ul>
-    </column>
-
-    <column name="nexthop">
-      <p>
-        Next-hop IP address for this route, which should be the IP
-        address of a connected router port or the IP address of a logical port.
-      </p>
-    </column>
-  </table>
-
-  <table name="NAT" title="NAT rules">
-    <p>
-      Each record represents a NAT rule.
-    </p>
-
-    <column name="type">
-      <p>Type of the NAT rule.</p>
-      <ul>
-        <li>
-          When <ref column="type"/> is <code>dnat</code>, the externally
-          visible IP address <ref column="external_ip"/> is DNATted to the IP
-          address <ref column="logical_ip"/> in the logical space.
-        </li>
-        <li>
-          When <ref column="type"/> is <code>snat</code>, IP packets
-          with their source IP address that either matches the IP address
-          in <ref column="logical_ip"/> or is in the network provided by
-          <ref column="logical_ip"/> is SNATed into the IP address in
-          <ref column="external_ip"/>.
-        </li>
-        <li>
-          When <ref column="type"/> is <code>dnat_and_snat</code>, the
-          externally visible IP address <ref column="external_ip"/> is
-          DNATted to the IP address <ref column="logical_ip"/> in the
-          logical space. In addition, IP packets with the source IP
-          address that matches <ref column="logical_ip"/> is SNATed into
-          the IP address in <ref column="external_ip"/>.
-        </li>
-      </ul>
-    </column>
-
-    <column name="external_ip">
-      An IPv4 address.
-    </column>
-
-    <column name="external_mac">
-      <p>
-        A MAC address.
-      </p>
-
-      <p>
-        This is only used on the gateway port on distributed routers.
-        This must be specified in order for the NAT rule to be
-        processed in a distributed manner on all chassis.  If this is
-        not specified for a NAT rule on a distributed router, then
-        this NAT rule will be processed in a centralized manner on
-        the gateway port instance on the <code>redirect-chassis</code>.
-      </p>
-
-      <p>
-        This MAC address must be unique on the logical switch that the
-        gateway port is attached to.  If the MAC address used on the
-        <ref column="logical_port"/> is globally unique, then that MAC
-        address can be specified as this <ref column="external_mac"/>.
-      </p>
-    </column>
-
-    <column name="logical_ip">
-      An IPv4 network (e.g 192.168.1.0/24) or an IPv4 address.
-    </column>
-
-    <column name="logical_port">
-      <p>
-        The name of the logical port where the <ref column="logical_ip"/>
-        resides.
-      </p>
-
-      <p>
-        This is only used on distributed routers.  This must be
-        specified in order for the NAT rule to be processed in a
-        distributed manner on all chassis.  If this is not specified
-        for a NAT rule on a distributed router, then this NAT rule
-        will be processed in a centralized manner on the gateway
-        port instance on the <code>redirect-chassis</code>.
-      </p>
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-
-  </table>
-
-  <table name="DHCP_Options" title="DHCP options">
-    <p>
-      OVN implements native DHCPv4 support which caters to the common
-      use case of providing an IPv4 address to a booting instance by
-      providing stateless replies to DHCPv4 requests based on statically
-      configured address mappings. To do this it allows a short list of
-      DHCPv4 options to be configured and applied at each compute host
-      running <code>ovn-controller</code>.
-    </p>
-
-    <p>
-      OVN also implements native DHCPv6 support which provides stateless
-      replies to DHCPv6 requests.
-    </p>
-
-    <column name="cidr">
-      <p>
-        The DHCPv4/DHCPv6 options will be included if the logical port has its
-        IP address in this <ref column="cidr"/>.
-      </p>
-    </column>
-
-    <group title="DHCPv4 options">
-      <p>
-        The CMS should define the set of DHCPv4 options as key/value pairs
-        in the <ref column="options"/> column of this table. For
-        <code>ovn-controller</code> to include these DHCPv4 options, the
-        <ref column="dhcpv4_options"/> of <ref table="Logical_Switch_Port"/>
-        should refer to an entry in this table.
-      </p>
-
-      <group title="Mandatory DHCPv4 options">
-        <p>
-          The following options must be defined.
-        </p>
-
-        <column name="options" key="server_id">
-          The IP address for the DHCP server to use.  This should be in the
-          subnet of the offered IP.  This is also included in the DHCP offer as
-          option 54, ``server identifier.''
-        </column>
-
-        <column name="options" key="server_mac">
-          The Ethernet address for the DHCP server to use.
-        </column>
-
-        <column name="options" key="lease_time"
-                type='{"type": "integer", "minInteger": 0, "maxInteger": 4294967295}'>
-          <p>
-            The offered lease time in seconds,
-          </p>
-
-          <p>
-            The DHCPv4 option code for this option is 51.
-          </p>
-        </column>
-      </group>
-
-      <group title="IPv4 DHCP Options">
-        <p>
-          Below are the supported DHCPv4 options whose values are an IPv4
-          address, e.g. <code>192.168.1.1</code>.  Some options accept multiple
-          IPv4 addresses enclosed within curly braces, e.g. <code>{192.168.1.2,
-          192.168.1.3}</code>. Please refer to RFC 2132 for more details on
-          DHCPv4 options and their codes.
-        </p>
-
-        <column name="options" key="router">
-          <p>
-            The IP address of a gateway for the client to use.  This should be
-            in the subnet of the offered IP.  The DHCPv4 option code for this
-            option is 3.
-          </p>
-        </column>
-
-        <column name="options" key="netmask">
-          <p>
-            The DHCPv4 option code for this option is 1.
-          </p>
-        </column>
-
-        <column name="options" key="dns_server">
-          <p>
-            The DHCPv4 option code for this option is 6.
-          </p>
-        </column>
-
-        <column name="options" key="log_server">
-          <p>
-            The DHCPv4 option code for this option is 7.
-          </p>
-        </column>
-
-        <column name="options" key="lpr_server">
-          <p>
-            The DHCPv4 option code for this option is 9.
-          </p>
-        </column>
-
-        <column name="options" key="swap_server">
-          <p>
-            The DHCPv4 option code for this option is 16.
-          </p>
-        </column>
-
-        <column name="options" key="policy_filter">
-          <p>
-            The DHCPv4 option code for this option is 21.
-          </p>
-        </column>
-
-        <column name="options" key="router_solicitation">
-          <p>
-            The DHCPv4 option code for this option is 32.
-          </p>
-        </column>
-
-        <column name="options" key="nis_server">
-          <p>
-            The DHCPv4 option code for this option is 41.
-          </p>
-        </column>
-
-        <column name="options" key="ntp_server">
-          <p>
-            The DHCPv4 option code for this option is 42.
-          </p>
-        </column>
-
-        <column name="options" key="tftp_server">
-          <p>
-            The DHCPv4 option code for this option is 66.
-          </p>
-        </column>
-
-        <column name="options" key="classless_static_route">
-          <p>
-            The DHCPv4 option code for this option is 121.
-          </p>
-
-          <p>
-             This option can contain one or more static routes, each of which
-             consists of a destination descriptor and the IP address of the
-             router that should be used to reach that destination. Please see
-             RFC 3442 for more details.
-          </p>
-
-          <p>
-            Example: <code>{30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1}</code>
-          </p>
-        </column>
-
-        <column name="options" key="ms_classless_static_route">
-          <p>
-            The DHCPv4 option code for this option is 249. This option is
-            similar to <code>classless_static_route</code> supported by
-            Microsoft Windows DHCPv4 clients.
-          </p>
-        </column>
-
-      </group>
-
-      <group title="Boolean DHCP Options">
-        <p>
-          These options accept a Boolean value, expressed as <code>0</code> for
-          false or <code>1</code> for true.
-        </p>
-
-        <column name="options" key="ip_forward_enable"
-                type='{"type": "string", "enum": ["set", ["0", "1"]]}'>
-          <p>
-            The DHCPv4 option code for this option is 19.
-          </p>
-        </column>
-
-        <column name="options" key="router_discovery"
-                type='{"type": "string", "enum": ["set", ["0", "1"]]}'>
-          <p>
-            The DHCPv4 option code for this option is 31.
-          </p>
-        </column>
-
-        <column name="options" key="ethernet_encap"
-                type='{"type": "string", "enum": ["set", ["0", "1"]]}'>
-          <p>
-            The DHCPv4 option code for this option is 36.
-          </p>
-        </column>
-      </group>
-
-      <group title="Integer DHCP Options">
-        <p>
-          These options accept a nonnegative integer value.
-        </p>
-
-        <column name="options" key="default_ttl"
-                type='{"type": "integer", "minInteger": 0, "maxInteger": 255}'>
-          The DHCPv4 option code for this option is 23.
-        </column>
-
-        <column name="options" key="tcp_ttl"
-                type='{"type": "integer", "minInteger": 0, "maxInteger": 255}'>
-          The DHCPv4 option code for this option is 37.
-        </column>
-
-        <column name="options" key="mtu"
-                type='{"type": "integer", "minInteger": 68, "maxInteger": 65535}'>
-          The DHCPv4 option code for this option is 26.
-        </column>
-
-        <column name="options" key="T1"
-                type='{"type": "integer", "minInteger": 68, "maxInteger": 4294967295}'>
-          This specifies the time interval from address assignment until the
-          client begins trying to renew its address.  The DHCPv4 option code
-          for this option is 58.
-        </column>
-
-        <column name="options" key="T2"
-                type='{"type": "integer", "minInteger": 68, "maxInteger": 4294967295}'>
-          This specifies the time interval from address assignment until the
-          client begins trying to rebind its address.  The DHCPv4 option code
-          for this option is 59.
-        </column>
-      </group>
-
-      <group title="String DHCP Options">
-        <p>
-          These options accept a string value.
-        </p>
-
-        <column name="options" key="wpad">
-          <p>
-            The DHCPv4 option code for this option is 252. This option is used
-            as part of web proxy auto discovery to provide a URL for a web
-            proxy.
-          </p>
-        </column>
-
-        <column name="options" key="bootfile_name">
-          <p>
-            The DHCPv4 option code for this option is 67. This option is used
-            to identify a bootfile.
-          </p>
-        </column>
-
-        <column name="options" key="path_prefix">
-          <p>
-            The DHCPv4 option code for this option is 210. In PXELINUX'
-            case this option is used to set a common path prefix,
-            instead of deriving it from the bootfile name.
-          </p>
-        </column>
-
-        <column name="options" key="tftp_server_address">
-          <p>
-            The DHCPv4 option code for this option is 150. The option
-            contains one or more IPv4 addresses that the client MAY
-            use. This option is Cisco proprietary, the IEEE standard
-            that matches with this requirement is option 66 (tftp_server).
-          </p>
-        </column>
-
-        <column name="options" key="domain_name">
-          <p>
-            The DHCPv4 option code for this option is 15. This option
-            specifies the domain name that client should use when
-            resolving hostnames via the Domain Name System.
-          </p>
-        </column>
-      </group>
-    </group>
-
-    <group title="DHCPv6 options">
-      <p>
-        OVN also implements native DHCPv6 support. The CMS should define
-        the set of DHCPv6 options as key/value pairs. The define DHCPv6
-        options will be included in the DHCPv6 response to the DHCPv6
-        Solicit/Request/Confirm packet from the logical ports having the
-        IPv6 addresses in the <ref column="cidr"/>.
-      </p>
-
-      <group title="Mandatory DHCPv6 options">
-        <p>
-          The following options must be defined.
-        </p>
-
-        <column name="options" key="server_id">
-          <p>
-            The Ethernet address for the DHCP server to use. This is also
-            included in the DHCPv6 reply as option 2, ``Server Identifier''
-            to carry a DUID identifying a server between a client and a server.
-            <code>ovn-controller</code> defines DUID based on
-            Link-layer Address [DUID-LL].
-          </p>
-        </column>
-      </group>
-
-      <group title="IPv6 DHCPv6 options">
-        <p>
-          Below are the supported DHCPv6 options whose values are an IPv6
-          address, e.g. <code>aef0::4</code>.  Some options accept multiple
-          IPv6 addresses enclosed within curly braces, e.g. <code>{aef0::4,
-          aef0::5}</code>. Please refer to RFC 3315 for more details on
-          DHCPv6 options and their codes.
-        </p>
-
-        <column name="options" key="dns_server">
-          <p>
-            The DHCPv6 option code for this option is 23. This option specifies
-            the DNS servers that the VM should use.
-          </p>
-        </column>
-      </group>
-
-      <group title="String DHCPv6 options">
-        <p>
-          These options accept string values.
-        </p>
-
-        <column name="options" key="domain_search">
-          <p>
-            The DHCPv6 option code for this option is 24. This option specifies
-            the domain search list the client should use to resolve hostnames
-            with DNS.
-          </p>
-
-          <p>
-            Example: <code>"ovn.org"</code>.
-          </p>
-        </column>
-
-        <column name="options" key="dhcpv6_stateless">
-          <p>
-            This option specifies the OVN native DHCPv6 will work in stateless
-            mode, which means OVN native DHCPv6 will not offer IPv6 addresses
-            for VM/VIF ports, but only reply other configurations, such as
-            DNS and domain search list. When setting this option with string
-            value "true", VM/VIF will configure IPv6 addresses by stateless
-            way. Default value for this option is false.
-          </p>
-        </column>
-      </group>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="Connection" title="OVSDB client connections.">
-    <p>
-      Configuration for a database connection to an Open vSwitch database
-      (OVSDB) client.
-    </p>
-
-    <p>
-      This table primarily configures the Open vSwitch database server
-      (<code>ovsdb-server</code>).
-    </p>
-
-    <p>
-      The Open vSwitch database server can initiate and maintain active
-      connections to remote clients.  It can also listen for database
-      connections.
-    </p>
-
-    <group title="Core Features">
-      <column name="target">
-        <p>Connection methods for clients.</p>
-        <p>
-          The following connection methods are currently supported:
-        </p>
-        <dl>
-          <dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
-          <dd>
-            <p>
-              The specified SSL <var>port</var> on the host at the given
-              <var>host</var>, which can either be a DNS name (if built with
-              unbound library) or an IP address. A valid SSL configuration must
-              be provided when this form is used, this configuration can be
-              specified via command-line options or the <ref table="SSL"/> table.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-            <p>
-              SSL support is an optional feature that is not always
-              built as part of Open vSwitch.
-            </p>
-          </dd>
-
-          <dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
-          <dd>
-            <p>
-              The specified TCP <var>port</var> on the host at the given
-              <var>host</var>, which can either be a DNS name (if built with
-              unbound library) or an IP address.  If <var>host</var> is an IPv6
-              address, wrap it in square brackets, e.g. <code>tcp:[::1]:6640</code>.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-          </dd>
-          <dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
-          <dd>
-            <p>
-              Listens for SSL connections on the specified TCP <var>port</var>.
-              Specify 0 for <var>port</var> to have the kernel automatically
-              choose an available port.  If <var>host</var>, which can either
-              be a DNS name (if built with unbound library) or an IP address,
-              is specified, then connections are restricted to the resolved or
-              specified local IPaddress (either IPv4 or IPv6 address).  If
-              <var>host</var> is an IPv6 address, wrap in square brackets,
-              e.g. <code>pssl:6640:[::1]</code>.  If <var>host</var> is not
-              specified then it listens only on IPv4 (but not IPv6) addresses.
-              A valid SSL configuration must be provided when this form is used,
-             this can be specified either via command-line options or the
-             <ref table="SSL"/> table.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-            <p>
-              SSL support is an optional feature that is not always built as
-              part of Open vSwitch.
-            </p>
-          </dd>
-          <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
-          <dd>
-            <p>
-              Listens for connections on the specified TCP <var>port</var>.
-              Specify 0 for <var>port</var> to have the kernel automatically
-              choose an available port.  If <var>host</var>, which can either
-              be a DNS name (if built with unbound library) or an IP address,
-              is specified, then connections are restricted to the resolved or
-              specified local IP address (either IPv4 or IPv6 address).  If
-              <var>host</var> is an IPv6 address, wrap it in square brackets,
-              e.g. <code>ptcp:6640:[::1]</code>.  If <var>host</var> is not
-              specified then it listens only on IPv4 addresses.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-          </dd>
-        </dl>
-        <p>When multiple clients are configured, the <ref column="target"/>
-        values must be unique.  Duplicate <ref column="target"/> values yield
-        unspecified results.</p>
-      </column>
-    </group>
-
-    <group title="Client 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 the client
-        before sending an inactivity probe message.  If Open vSwitch does not
-        communicate with the client 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.
-        A value of 0 disables inactivity probes.
-      </column>
-    </group>
-
-    <group title="Status">
-      <p>
-        Key-value pair of <ref column="is_connected"/> is always updated.
-        Other key-value pairs in the status columns may be updated depends
-        on the <ref column="target"/> type.
-      </p>
-
-      <p>
-        When <ref column="target"/> specifies a connection method that
-        listens for inbound connections (e.g. <code>ptcp:</code> or
-        <code>punix:</code>), both <ref column="n_connections"/> and
-        <ref column="is_connected"/> may also be updated while the
-        remaining key-value pairs are omitted.
-      </p>
-
-      <p>
-        On the other hand, when <ref column="target"/> specifies an
-        outbound connection, all key-value pairs may be updated, except
-        the above-mentioned two key-value pairs associated with inbound
-        connection targets. They are omitted.
-      </p>
-
-    <column name="is_connected">
-        <code>true</code> if currently connected to this client,
-        <code>false</code> otherwise.
-      </column>
-
-      <column name="status" key="last_error">
-        A human-readable description of the last error on the connection
-        to the manager; i.e. <code>strerror(errno)</code>.  This key
-        will exist only if an error has occurred.
-      </column>
-
-      <column name="status" key="state"
-              type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'>
-        <p>
-          The state of the connection to the manager:
-        </p>
-        <dl>
-          <dt><code>VOID</code></dt>
-          <dd>Connection is disabled.</dd>
-
-          <dt><code>BACKOFF</code></dt>
-          <dd>Attempting to reconnect at an increasing period.</dd>
-
-          <dt><code>CONNECTING</code></dt>
-          <dd>Attempting to connect.</dd>
-
-          <dt><code>ACTIVE</code></dt>
-          <dd>Connected, remote host responsive.</dd>
-
-          <dt><code>IDLE</code></dt>
-          <dd>Connection is idle.  Waiting for response to keep-alive.</dd>
-        </dl>
-        <p>
-          These values may change in the future.  They are provided only for
-          human consumption.
-        </p>
-      </column>
-
-      <column name="status" key="sec_since_connect"
-              type='{"type": "integer", "minInteger": 0}'>
-        The amount of time since this client last successfully connected
-        to the database (in seconds). Value is empty if client has never
-        successfully been connected.
-      </column>
-
-      <column name="status" key="sec_since_disconnect"
-              type='{"type": "integer", "minInteger": 0}'>
-        The amount of time since this client last disconnected from the
-        database (in seconds). Value is empty if client has never
-        disconnected.
-      </column>
-
-      <column name="status" key="locks_held">
-        Space-separated list of the names of OVSDB locks that the connection
-        holds.  Omitted if the connection does not hold any locks.
-      </column>
-
-      <column name="status" key="locks_waiting">
-        Space-separated list of the names of OVSDB locks that the connection is
-        currently waiting to acquire.  Omitted if the connection is not waiting
-        for any locks.
-      </column>
-
-      <column name="status" key="locks_lost">
-        Space-separated list of the names of OVSDB locks that the connection
-        has had stolen by another OVSDB client.  Omitted if no locks have been
-        stolen from this connection.
-      </column>
-
-      <column name="status" key="n_connections"
-              type='{"type": "integer", "minInteger": 2}'>
-        When <ref column="target"/> specifies a connection method that
-        listens for inbound connections (e.g. <code>ptcp:</code> or
-        <code>pssl:</code>) and more than one connection is actually active,
-        the value is the number of active connections.  Otherwise, this
-        key-value pair is omitted.
-      </column>
-
-      <column name="status" key="bound_port" type='{"type": "integer"}'>
-        When <ref column="target"/> is <code>ptcp:</code> or
-        <code>pssl:</code>, this is the TCP port on which the OVSDB server is
-        listening.  (This is particularly useful when <ref
-        column="target"/> specifies a port of 0, allowing the kernel to
-        choose any available port.)
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-      <column name="other_config"/>
-    </group>
-  </table>
-  <table name="DNS" title="Native DNS resolution">
-    <p>
-      Each row in this table stores the DNS records. The
-      <ref table="Logical_Switch"/> table's <ref table="Logical_Switch"
-      column="dns_records"/> references these records.
-    </p>
-
-    <column name="records">
-      Key-value pair of DNS records with <code>DNS query name</code> as the key
-      and value as a string of IP address(es) separated by comma or space.
-
-      <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p>
-    </column>
-
-    <column name="external_ids">
-      See <em>External IDs</em> at the beginning of this document.
-    </column>
-  </table>
-  <table name="SSL">
-    SSL configuration for ovn-nb database access.
-
-    <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>
-
-    <column name="ssl_protocols">
-      List of SSL protocols to be enabled for SSL connections. The default
-      when this option is omitted is <code>TLSv1,TLSv1.1,TLSv1.2</code>.
-    </column>
-
-    <column name="ssl_ciphers">
-      List of ciphers (in OpenSSL cipher string format) to be supported
-      for SSL connections. The default when this option is omitted is
-      <code>HIGH:!aNULL:!MD5</code>.
-    </column>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-  </table>
-  <table name="Gateway_Chassis">
-    <p>
-      Association of one or more chassis to a logical router port. The traffic
-      going out through an specific router port will be redirected to a
-      chassis, or a set of them in high availability configurations.
-      A single <ref table="Gateway_Chassis"/> is equivalent to setting
-      <ref column="options" key="redirect-chassis"/>.  Using
-      <ref table="Gateway_Chassis"/> allows associating multiple prioritized
-      chassis with a single logical router port.
-    </p>
-
-    <column name="name">
-      <p>
-        Name of the <ref table="Gateway_Chassis"/>.
-      </p>
-      <p>
-        A suggested, but not required naming convention is
-        <code>${port_name}_${chassis_name}</code>.
-      </p>
-    </column>
-
-    <column name="chassis_name">
-      <p>
-        Name of the chassis that we want to redirect traffic through for the
-        associated logical router port.  The value must match the
-        <ref db="OVN_Southbound" table="Chassis" column="name"/> column
-        of the <ref db="OVN_Southbound" table="Chassis"/> table in the
-        <ref db="OVN_Southbound"/> database.
-      </p>
-    </column>
-
-    <column name="priority">
-      <p>
-        This is the priority of a chassis among all
-        <ref table="Gateway_Chassis"/> belonging to the same logical router
-        port.
-      </p>
-    </column>
-
-    <column name="options">
-      Reserved for future use.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="HA_Chassis_Group">
-    <p>
-      Table representing a group of chassis which can provide High availability
-      services. Each chassis in the group is represented by the table
-      <ref table="HA_Chassis"/>. The HA chassis with highest priority will
-      be the master of this group. If the master chassis failover is detected,
-      the HA chassis with the next higher priority takes over the
-      responsibility of providing the HA. If a distributed gateway router port
-      references a row in this table, then the master HA chassis in this group
-      provides the gateway functionality.
-    </p>
-
-    <column name="name">
-      Name of the <ref table="HA_Chassis_Group"/>. Name should be unique.
-    </column>
-
-    <column name="ha_chassis">
-      A list of HA chassis which belongs to this group.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="HA_Chassis">
-    <column name="chassis_name">
-      <p>
-        Name of the chassis which is part of the HA chassis group.
-        The value must match the
-        <ref db="OVN_Southbound" table="Chassis" column="name"/> column
-        of the <ref db="OVN_Southbound" table="Chassis"/> table in the
-        <ref db="OVN_Southbound"/> database.
-      </p>
-    </column>
-
-    <column name="priority">
-      <p>
-        Priority of the chassis. Chassis with highest priority will be
-        the master.
-      </p>
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-</database>
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
deleted file mode 100644
index 2b7bc57a7f8f..000000000000
--- a/ovn/ovn-sb.ovsschema
+++ /dev/null
@@ -1,404 +0,0 @@
-{
-    "name": "OVN_Southbound",
-    "version": "2.4.0",
-    "cksum": "3059284885 20260",
-    "tables": {
-        "SB_Global": {
-            "columns": {
-                "nb_cfg": {"type": {"key": "integer"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "connections": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "Connection"},
-                                     "min": 0,
-                                     "max": "unlimited"}},
-                "ssl": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "SSL"},
-                                     "min": 0, "max": 1}},
-                "options": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "ipsec": {"type": "boolean"}},
-            "maxRows": 1,
-            "isRoot": true},
-        "Chassis": {
-            "columns": {
-                "name": {"type": "string"},
-                "hostname": {"type": "string"},
-                "encaps": {"type": {"key": {"type": "uuid",
-                                            "refTable": "Encap"},
-                                    "min": 1, "max": "unlimited"}},
-                "vtep_logical_switches" : {"type": {"key": "string",
-                                                    "min": 0,
-                                                    "max": "unlimited"}},
-                "nb_cfg": {"type": {"key": "integer"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "transport_zones" : {"type": {"key": "string",
-                                              "min": 0,
-                                              "max": "unlimited"}}},
-            "isRoot": true,
-            "indexes": [["name"]]},
-        "Encap": {
-            "columns": {
-                "type": {"type": {"key": {
-                           "type": "string",
-                           "enum": ["set", ["geneve", "stt", "vxlan"]]}}},
-                "options": {"type": {"key": "string",
-                                     "value": "string",
-                                     "min": 0,
-                                     "max": "unlimited"}},
-                "ip": {"type": "string"},
-                "chassis_name": {"type": "string"}},
-            "indexes": [["type", "ip"]]},
-        "Address_Set": {
-            "columns": {
-                "name": {"type": "string"},
-                "addresses": {"type": {"key": "string",
-                                       "min": 0,
-                                       "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Port_Group": {
-            "columns": {
-                "name": {"type": "string"},
-                "ports": {"type": {"key": "string",
-                                   "min": 0,
-                                   "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Logical_Flow": {
-            "columns": {
-                "logical_datapath": {"type": {"key": {"type": "uuid",
-                                                      "refTable": "Datapath_Binding"}}},
-                "pipeline": {"type": {"key": {"type": "string",
-                                      "enum": ["set", ["ingress",
-                                                       "egress"]]}}},
-                "table_id": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 23}}},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 65535}}},
-                "match": {"type": "string"},
-                "actions": {"type": "string"},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "Multicast_Group": {
-            "columns": {
-                "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding"}}},
-                "name": {"type": "string"},
-                "tunnel_key": {
-                    "type": {"key": {"type": "integer",
-                                     "minInteger": 32768,
-                                     "maxInteger": 65535}}},
-                "ports": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Port_Binding",
-                                           "refType": "weak"},
-                                   "min": 1, "max": "unlimited"}}},
-            "indexes": [["datapath", "tunnel_key"],
-                        ["datapath", "name"]],
-            "isRoot": true},
-        "Meter": {
-            "columns": {
-                "name": {"type": "string"},
-                "unit": {"type": {"key": {"type": "string",
-                                          "enum": ["set", ["kbps", "pktps"]]}}},
-                "bands": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Meter_Band",
-                                           "refType": "strong"},
-                                   "min": 1,
-                                   "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Meter_Band": {
-            "columns": {
-                "action": {"type": {"key": {"type": "string",
-                                            "enum": ["set", ["drop"]]}}},
-                "rate": {"type": {"key": {"type": "integer",
-                                          "minInteger": 1,
-                                          "maxInteger": 4294967295}}},
-                "burst_size": {"type": {"key": {"type": "integer",
-                                                "minInteger": 0,
-                                                "maxInteger": 4294967295}}}},
-            "isRoot": false},
-        "Datapath_Binding": {
-            "columns": {
-                "tunnel_key": {
-                     "type": {"key": {"type": "integer",
-                                      "minInteger": 1,
-                                      "maxInteger": 16777215}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["tunnel_key"]],
-            "isRoot": true},
-        "Port_Binding": {
-            "columns": {
-                "logical_port": {"type": "string"},
-                "type": {"type": "string"},
-                "gateway_chassis": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "Gateway_Chassis",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": "unlimited"}},
-                "ha_chassis_group": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "HA_Chassis_Group",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": 1}},
-                "options": {
-                     "type": {"key": "string",
-                              "value": "string",
-                              "min": 0,
-                              "max": "unlimited"}},
-                "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding"}}},
-                "tunnel_key": {
-                     "type": {"key": {"type": "integer",
-                                      "minInteger": 1,
-                                      "maxInteger": 32767}}},
-                "parent_port": {"type": {"key": "string", "min": 0, "max": 1}},
-                "tag": {
-                     "type": {"key": {"type": "integer",
-                                      "minInteger": 1,
-                                      "maxInteger": 4095},
-                              "min": 0, "max": 1}},
-                "chassis": {"type": {"key": {"type": "uuid",
-                                             "refTable": "Chassis",
-                                             "refType": "weak"},
-                                     "min": 0, "max": 1}},
-                "encap": {"type": {"key": {"type": "uuid",
-                                            "refTable": "Encap",
-                                             "refType": "weak"},
-                                    "min": 0, "max": 1}},
-                "mac": {"type": {"key": "string",
-                                 "min": 0,
-                                 "max": "unlimited"}},
-                "nat_addresses": {"type": {"key": "string",
-                                           "min": 0,
-                                           "max": "unlimited"}},
-                "external_ids": {"type": {"key": "string",
-                                 "value": "string",
-                                 "min": 0,
-                                 "max": "unlimited"}}},
-            "indexes": [["datapath", "tunnel_key"], ["logical_port"]],
-            "isRoot": true},
-        "MAC_Binding": {
-            "columns": {
-                "logical_port": {"type": "string"},
-                "ip": {"type": "string"},
-                "mac": {"type": "string"},
-                "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding"}}}},
-            "indexes": [["logical_port", "ip"]],
-            "isRoot": true},
-        "DHCP_Options": {
-            "columns": {
-                "name": {"type": "string"},
-                "code": {
-                    "type": {"key": {"type": "integer",
-                                     "minInteger": 0, "maxInteger": 254}}},
-                "type": {
-                    "type": {"key": {
-                        "type": "string",
-                        "enum": ["set", ["bool", "uint8", "uint16", "uint32",
-                                         "ipv4", "static_routes", "str"]]}}}},
-            "isRoot": true},
-        "DHCPv6_Options": {
-            "columns": {
-                "name": {"type": "string"},
-                "code": {
-                    "type": {"key": {"type": "integer",
-                                     "minInteger": 0, "maxInteger": 254}}},
-                "type": {
-                    "type": {"key": {
-                        "type": "string",
-                        "enum": ["set", ["ipv6", "str", "mac"]]}}}},
-            "isRoot": true},
-        "Connection": {
-            "columns": {
-                "target": {"type": "string"},
-                "max_backoff": {"type": {"key": {"type": "integer",
-                                         "minInteger": 1000},
-                                         "min": 0,
-                                         "max": 1}},
-                "inactivity_probe": {"type": {"key": "integer",
-                                              "min": 0,
-                                              "max": 1}},
-                "read_only": {"type": "boolean"},
-                "role": {"type": "string"},
-                "other_config": {"type": {"key": "string",
-                                          "value": "string",
-                                          "min": 0,
-                                          "max": "unlimited"}},
-                "external_ids": {"type": {"key": "string",
-                                 "value": "string",
-                                 "min": 0,
-                                 "max": "unlimited"}},
-                "is_connected": {"type": "boolean", "ephemeral": true},
-                "status": {"type": {"key": "string",
-                                    "value": "string",
-                                    "min": 0,
-                                    "max": "unlimited"},
-                                    "ephemeral": true}},
-            "indexes": [["target"]]},
-        "SSL": {
-            "columns": {
-                "private_key": {"type": "string"},
-                "certificate": {"type": "string"},
-                "ca_cert": {"type": "string"},
-                "bootstrap_ca_cert": {"type": "boolean"},
-                "ssl_protocols": {"type": "string"},
-                "ssl_ciphers": {"type": "string"},
-                "external_ids": {"type": {"key": "string",
-                                          "value": "string",
-                                          "min": 0,
-                                          "max": "unlimited"}}},
-            "maxRows": 1},
-        "DNS": {
-            "columns": {
-                "records": {"type": {"key": "string",
-                                            "value": "string",
-                                            "min": 0,
-                                            "max": "unlimited"}},
-                "datapaths": {"type": {"key": {"type": "uuid",
-                                               "refTable": "Datapath_Binding"},
-                                       "min": 1,
-                                       "max": "unlimited"}},
-                "external_ids": {"type": {"key": "string",
-                                          "value": "string",
-                                          "min": 0,
-                                          "max": "unlimited"}}},
-            "isRoot": true},
-        "RBAC_Role": {
-            "columns": {
-                "name": {"type": "string"},
-                "permissions": {
-                    "type": {"key": {"type": "string"},
-                             "value": {"type": "uuid",
-                                       "refTable": "RBAC_Permission",
-                                       "refType": "weak"},
-                                     "min": 0, "max": "unlimited"}}},
-            "isRoot": true},
-        "RBAC_Permission": {
-            "columns": {
-                "table": {"type": "string"},
-                "authorization": {"type": {"key": "string",
-                                           "min": 0,
-                                           "max": "unlimited"}},
-                "insert_delete": {"type": "boolean"},
-                "update" : {"type": {"key": "string",
-                                     "min": 0,
-                                     "max": "unlimited"}}},
-            "isRoot": true},
-        "Gateway_Chassis": {
-            "columns": {
-                "name": {"type": "string"},
-                "chassis": {"type": {"key": {"type": "uuid",
-                                             "refTable": "Chassis",
-                                             "refType": "weak"},
-                                     "min": 0, "max": 1}},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}},
-                "options": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": false},
-        "HA_Chassis": {
-            "columns": {
-                "chassis": {"type": {"key": {"type": "uuid",
-                                             "refTable": "Chassis",
-                                             "refType": "weak"},
-                                     "min": 0, "max": 1}},
-                "priority": {"type": {"key": {"type": "integer",
-                                              "minInteger": 0,
-                                              "maxInteger": 32767}}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "isRoot": false},
-        "HA_Chassis_Group": {
-            "columns": {
-                "name": {"type": "string"},
-                "ha_chassis": {
-                    "type": {"key": {"type": "uuid",
-                                     "refTable": "HA_Chassis",
-                                     "refType": "strong"},
-                             "min": 0,
-                             "max": "unlimited"}},
-                "ref_chassis": {"type": {"key": {"type": "uuid",
-                                                 "refTable": "Chassis",
-                                                 "refType": "weak"},
-                                         "min": 0, "max": "unlimited"}},
-                "external_ids": {
-                    "type": {"key": "string", "value": "string",
-                             "min": 0, "max": "unlimited"}}},
-            "indexes": [["name"]],
-            "isRoot": true},
-        "Controller_Event": {
-            "columns": {
-                "event_type": {"type": {"key": {"type": "string",
-                               "enum": ["set", ["empty_lb_backends"]]}}},
-                "event_info": {"type": {"key": "string", "value": "string",
-                               "min": 0, "max": "unlimited"}},
-                "chassis": {"type": {"key": {"type": "uuid",
-                                             "refTable": "Chassis",
-                                             "refType": "weak"},
-                                     "min": 0, "max": 1}},
-                "seq_num": {"type": {"key": "integer"}}
-            },
-            "isRoot": true},
-        "IP_Multicast": {
-            "columns": {
-                "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding",
-                                              "refType": "weak"}}},
-                "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "querier": {"type": {"key": "boolean", "min": 0, "max": 1}},
-                "eth_src": {"type": "string"},
-                "ip4_src": {"type": "string"},
-                "table_size": {"type": {"key": "integer",
-                                        "min": 0, "max": 1}},
-                "idle_timeout": {"type": {"key": "integer",
-                                          "min": 0, "max": 1}},
-                "query_interval": {"type": {"key": "integer",
-                                            "min": 0, "max": 1}},
-                "query_max_resp": {"type": {"key": "integer",
-                                            "min": 0, "max": 1}},
-                "seq_no": {"type": "integer"}},
-            "indexes": [["datapath"]],
-            "isRoot": true},
-        "IGMP_Group": {
-            "columns": {
-                "address": {"type": "string"},
-                "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding",
-                                              "refType": "weak"},
-                                      "min": 0,
-                                      "max": 1}},
-                "chassis": {"type": {"key": {"type": "uuid",
-                                             "refTable": "Chassis",
-                                             "refType": "weak"},
-                                     "min": 0,
-                                     "max": 1}},
-                "ports": {"type": {"key": {"type": "uuid",
-                                           "refTable": "Port_Binding",
-                                           "refType": "weak"},
-                                   "min": 0, "max": "unlimited"}}},
-            "indexes": [["address", "datapath", "chassis"]],
-            "isRoot": true}}}
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
deleted file mode 100644
index 544a071fa3af..000000000000
--- a/ovn/ovn-sb.xml
+++ /dev/null
@@ -1,3638 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<database name="ovn-sb" title="OVN Southbound Database">
-  <p>
-    This database holds logical and physical configuration and state for the
-    Open Virtual Network (OVN) system to support virtual network abstraction.
-    For an introduction to OVN, please see <code>ovn-architecture</code>(7).
-  </p>
-
-  <p>
-    The OVN Southbound database sits at the center of the OVN
-    architecture.  It is the one component that speaks both southbound
-    directly to all the hypervisors and gateways, via
-    <code>ovn-controller</code>/<code>ovn-controller-vtep</code>, and
-    northbound to the Cloud Management System, via <code>ovn-northd</code>:
-  </p>
-
-  <h2>Database Structure</h2>
-
-  <p>
-    The OVN Southbound database contains classes of data with
-    different properties, as described in the sections below.
-  </p>
-
-  <h3>Physical network</h3>
-
-  <p>
-    Physical network tables contain information about the chassis nodes in the
-    system.  This contains all the information necessary to wire the overlay,
-    such as IP addresses, supported tunnel types, and security keys.
-  </p>
-
-  <p>
-    The amount of physical network data is small (O(n) in the number of
-    chassis) and it changes infrequently, so it can be replicated to every
-    chassis.
-  </p>
-
-  <p>
-    The <ref table="Chassis"/> and <ref table="Encap"/> tables are the physical
-    network tables.
-  </p>
-
-  <h3>Logical Network</h3>
-
-  <p>
-    Logical network tables contain the topology of logical switches and
-    routers, ACLs, firewall rules, and everything needed to describe how
-    packets traverse a logical network, represented as logical datapath flows
-    (see Logical Datapath Flows, below).
-  </p>
-
-  <p>
-    Logical network data may be large (O(n) in the number of logical ports, ACL
-    rules, etc.).  Thus, to improve scaling, each chassis should receive only
-    data related to logical networks in which that chassis participates.
-  </p>
-
-  <p>
-    The logical network data is ultimately controlled by the cloud management
-    system (CMS) running northbound of OVN.  That CMS determines the entire OVN
-    logical configuration and therefore the logical network data at any given
-    time is a deterministic function of the CMS's configuration, although that
-    happens indirectly via the <ref db="OVN_Northbound"/> database and
-    <code>ovn-northd</code>.
-  </p>
-
-  <p>
-    Logical network data is likely to change more quickly than physical network
-    data.  This is especially true in a container environment where containers
-    are created and destroyed (and therefore added to and deleted from logical
-    switches) quickly.
-  </p>
-
-  <p>
-    The <ref table="Logical_Flow"/>, <ref table="Multicast_Group"/>, <ref
-    table="Address_Group"/>, <ref table="DHCP_Options"/>, <ref
-    table="DHCPv6_Options"/>, and <ref table="DNS"/> tables contain logical
-    network data.
-  </p>
-
-  <h3>Logical-physical bindings</h3>
-
-  <p>
-    These tables link logical and physical components.  They show the current
-    placement of logical components (such as VMs and VIFs) onto chassis, and
-    map logical entities to the values that represent them in tunnel
-    encapsulations.
-  </p>
-
-  <p>
-    These tables change frequently, at least every time a VM powers up or down
-    or migrates, and especially quickly in a container environment.  The
-    amount of data per VM (or VIF) is small.
-  </p>
-
-  <p>
-    Each chassis is authoritative about the VMs and VIFs that it hosts at any
-    given time and can efficiently flood that state to a central location, so
-    the consistency needs are minimal.
-  </p>
-
-  <p>
-    The <ref table="Port_Binding"/> and <ref table="Datapath_Binding"/> tables
-    contain binding data.
-  </p>
-
-  <h3>MAC bindings</h3>
-
-  <p>
-    The <ref table="MAC_Binding"/> table tracks the bindings from IP addresses
-    to Ethernet addresses that are dynamically discovered using ARP (for IPv4)
-    and neighbor discovery (for IPv6).  Usually, IP-to-MAC bindings for virtual
-    machines are statically populated into the <ref table="Port_Binding"/>
-    table, so <ref table="MAC_Binding"/> is primarily used to discover bindings
-    on physical networks.
-  </p>
-
-  <h2>Common Columns</h2>
-
-  <p>
-    Some tables contain a special column named <code>external_ids</code>.  This
-    column has the same form and purpose each place that it appears, so we
-    describe it here to save space later.
-  </p>
-
-  <dl>
-    <dt><code>external_ids</code>: map of string-string pairs</dt>
-    <dd>
-      Key-value pairs for use by the software that manages the OVN Southbound
-      database rather than by
-      <code>ovn-controller</code>/<code>ovn-controller-vtep</code>.  In
-      particular, <code>ovn-northd</code> can use key-value pairs in this
-      column to relate entities in the southbound database to higher-level
-      entities (such as entities in the OVN Northbound database).  Individual
-      key-value pairs in this column may be documented in some cases to aid
-      in understanding and troubleshooting, but the reader should not mistake
-      such documentation as comprehensive.
-    </dd>
-  </dl>
-
-  <table name="SB_Global" title="Southbound configuration">
-    <p>
-      Southbound configuration for an OVN system.  This table must have exactly
-      one row.
-    </p>
-
-    <group title="Status">
-      This column allow a client to track the overall configuration state of
-      the system.
-
-      <column name="nb_cfg">
-        Sequence number for the configuration.  When a CMS or
-        <code>ovn-nbctl</code> updates the northbound database, it increments
-        the <code>nb_cfg</code> column in the <code>NB_Global</code> table in
-        the northbound database.  In turn, when <code>ovn-northd</code> updates
-        the southbound database to bring it up to date with these changes, it
-        updates this column to the same value.
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-
-      <column name="options">
-      </column>
-    </group>
-
-    <group title="Common options">
-      <column name="options">
-        This column provides general key/value settings. The supported
-        options are described individually below.
-      </column>
-
-      <group title="Options for configuring BFD">
-        <p>
-          These options apply when <code>ovn-controller</code> configures
-          BFD on tunnels interfaces.
-        </p>
-
-        <column name="options" key="bfd-min-rx">
-          BFD option <code>min-rx</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-decay-min-rx">
-          BFD option <code>decay-min-rx</code> value to use when configuring
-          BFD on tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-min-tx">
-          BFD option <code>min-tx</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-
-        <column name="options" key="bfd-mult">
-          BFD option <code>mult</code> value to use when configuring BFD on
-          tunnel interfaces.
-        </column>
-      </group>
-    </group>
-
-    <group title="Connection Options">
-      <column name="connections">
-        Database clients to which the Open vSwitch database server should
-        connect or on which it should listen, along with options for how these
-        connections should be configured.  See the <ref table="Connection"/>
-        table for more information.
-      </column>
-      <column name="ssl">
-        Global SSL configuration.
-      </column>
-    </group>
-    <group title="Security Configurations">
-      <column name="ipsec">
-        Tunnel encryption configuration. If this column is set to be true, all
-        OVN tunnels will be encrypted with IPsec.
-      </column>
-    </group>
-  </table>
-
-  <table name="Chassis" title="Physical Network Hypervisor and Gateway Information">
-    <p>
-      Each row in this table represents a hypervisor or gateway (a chassis) in
-      the physical network.  Each chassis, via
-      <code>ovn-controller</code>/<code>ovn-controller-vtep</code>, adds
-      and updates its own row, and keeps a copy of the remaining rows to
-      determine how to reach other hypervisors.
-    </p>
-
-    <p>
-      When a chassis shuts down gracefully, it should remove its own row.
-      (This is not critical because resources hosted on the chassis are equally
-      unreachable regardless of whether the row is present.)  If a chassis
-      shuts down permanently without removing its row, some kind of manual or
-      automatic cleanup is eventually needed; we can devise a process for that
-      as necessary.
-    </p>
-
-    <column name="name">
-      OVN does not prescribe a particular format for chassis names.
-      ovn-controller populates this column using <ref key="system-id"
-      table="Open_vSwitch" column="external_ids" db="Open_vSwitch"/>
-      in the Open_vSwitch database's <ref table="Open_vSwitch"
-      db="Open_vSwitch"/> table.  ovn-controller-vtep populates this
-      column with <ref table="Physical_Switch" column="name"
-      db="hardware_vtep"/> in the hardware_vtep database's
-      <ref table="Physical_Switch" db="hardware_vtep"/> table.
-    </column>
-
-    <column name="hostname">
-      The hostname of the chassis, if applicable.  ovn-controller will populate
-      this column with the hostname of the host it is running on.
-      ovn-controller-vtep will leave this column empty.
-    </column>
-
-    <column name="nb_cfg">
-      Sequence number for the configuration.  When <code>ovn-controller</code>
-      updates the configuration of a chassis from the contents of the
-      southbound database, it copies <ref table="SB_Global" column="nb_cfg"/>
-      from the <ref table="SB_Global"/> table into this column.
-    </column>
-
-    <column name="external_ids" key="ovn-bridge-mappings">
-      <code>ovn-controller</code> populates this key with the set of bridge
-      mappings it has been configured to use.  Other applications should treat
-      this key as read-only.  See <code>ovn-controller</code>(8) for more
-      information.
-    </column>
-
-    <column name="external_ids" key="datapath-type">
-      <code>ovn-controller</code> populates this key with the datapath type
-      configured in the <ref table="Bridge" column="datapath_type"/> column of
-      the Open_vSwitch database's <ref table="Bridge" db="Open_vSwitch"/>
-      table.  Other applications should treat this key as read-only. See
-      <code>ovn-controller</code>(8) for more information.
-    </column>
-
-    <column name="external_ids" key="iface-types">
-      <code>ovn-controller</code> populates this key with the interface types
-      configured in the <ref table="Open_vSwitch" column="iface_types"/> column
-      of the Open_vSwitch database's <ref table="Open_vSwitch"
-      db="Open_vSwitch"/> table.  Other applications should treat this key as
-      read-only. See <code>ovn-controller</code>(8) for more information.
-    </column>
-
-    <column name="external_ids" key="ovn-cms-options">
-      <code>ovn-controller</code> populates this key with the set of options
-      configured in the <ref table="Open_vSwitch"
-      column="external_ids:ovn-cms-options"/> column of the Open_vSwitch
-      database's <ref table="Open_vSwitch" db="Open_vSwitch"/> table.
-      See <code>ovn-controller</code>(8) for more information.
-    </column>
-
-    <column name="transport_zones">
-      <code>ovn-controller</code> populates this key with the transport
-      zones configured in the <ref table="Open_vSwitch"
-      column="external_ids:ovn-transport-zones"/> column of the Open_vSwitch
-      database's <ref table="Open_vSwitch" db="Open_vSwitch"/> table.
-      See <code>ovn-controller</code>(8) for more information.
-    </column>
-
-    <column name="external_ids" key="ovn-chassis-mac-mappings">
-      <code>ovn-controller</code> populates this key with the set of options
-      configured in the <ref table="Open_vSwitch"
-      column="external_ids:ovn-chassis-mac-mappings"/> column of the
-      Open_vSwitch database's <ref table="Open_vSwitch" db="Open_vSwitch"/>
-      table. See <code>ovn-controller</code>(8) for more information.
-    </column>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-
-    <group title="Encapsulation Configuration">
-      <p>
-        OVN uses encapsulation to transmit logical dataplane packets
-        between chassis.
-      </p>
-
-      <column name="encaps">
-        Points to supported encapsulation configurations to transmit
-        logical dataplane packets to this chassis.  Each entry is a <ref
-        table="Encap"/> record that describes the configuration.
-      </column>
-    </group>
-
-    <group title="Gateway Configuration">
-      <p>
-        A <dfn>gateway</dfn> is a chassis that forwards traffic between the
-        OVN-managed part of a logical network and a physical VLAN, extending a
-        tunnel-based logical network into a physical network.  Gateways are
-        typically dedicated nodes that do not host VMs and will be controlled
-        by <code>ovn-controller-vtep</code>.
-      </p>
-
-      <column name="vtep_logical_switches">
-        Stores all VTEP logical switch names connected by this gateway
-        chassis.  The <ref table="Port_Binding"/> table entry with
-        <ref column="options" table="Port_Binding"/>:<code>vtep-physical-switch</code>
-        equal <ref table="Chassis"/> <ref column="name" table="Chassis"/>, and
-        <ref column="options" table="Port_Binding"/>:<code>vtep-logical-switch</code>
-        value in <ref table="Chassis"/>
-        <ref column="vtep_logical_switches" table="Chassis"/>, will be
-        associated with this <ref table="Chassis"/>.
-      </column>
-    </group>
-  </table>
-
-  <table name="Encap" title="Encapsulation Types">
-    <p>
-      The <ref column="encaps" table="Chassis"/> column in the <ref
-      table="Chassis"/> table refers to rows in this table to identify
-      how OVN may transmit logical dataplane packets to this chassis.
-      Each chassis, via <code>ovn-controller</code>(8) or
-      <code>ovn-controller-vtep</code>(8), adds and updates its own rows
-      and keeps a copy of the remaining rows to determine how to reach
-      other chassis.
-    </p>
-
-    <column name="type">
-      The encapsulation to use to transmit packets to this chassis.
-      Hypervisors must use either <code>geneve</code> or
-      <code>stt</code>.  Gateways may use <code>vxlan</code>,
-      <code>geneve</code>, or <code>stt</code>.
-    </column>
-
-    <column name="options">
-      Options for configuring the encapsulation, which may be <ref column="type"/> specific.
-    </column>
-
-    <column name="options" key="csum" type='{"type": "boolean"}'>
-      <p>
-        <code>csum</code> indicates whether this chassis can transmit and
-        receive packets that include checksums with reasonable performance.  It
-        hints
-        to senders transmitting data to this chassis that they should use
-        checksums to protect OVN metadata. <code>ovn-controller</code>
-        populates this key with the value defined in
-        <ref table="Open_vSwitch" column="external_ids:ovn-encap-csum"/> column
-        of the Open_vSwitch database's <ref table="Open_vSwitch"
-        db="Open_vSwitch"/> table.  Other applications should treat this key as
-        read-only. See <code>ovn-controller</code>(8) for more information.
-      </p>
-
-      <p>
-        In terms of performance, checksumming actually significantly increases
-        throughput in most common cases when running on Linux based hosts
-        without NICs supporting encapsulation hardware offload (around 60% for
-        bulk traffic). The reason is that generally all NICs are capable of
-        offloading transmitted and received TCP/UDP checksums (viewed as
-        ordinary data packets and not as tunnels). The benefit comes on the
-        receive side where the validated outer checksum can be used to
-        additionally validate an inner checksum (such as TCP), which in turn
-        allows aggregation of packets to be more efficiently handled by the
-        rest of the stack.
-      </p>
-
-      <p>
-        Not all devices see such a benefit. The most notable exception is
-        hardware VTEPs. These devices are designed to not buffer entire
-        packets in their switching engines and are therefore unable to
-        efficiently compute or validate full packet checksums. In addition
-        certain versions of the Linux kernel are not able to fully take
-        advantage of encapsulation NIC offloads in the presence of checksums.
-        (This is actually a pretty narrow corner case though: earlier
-        versions of Linux don't support encapsulation offloads at all and
-        later versions support both offloads and checksums well.)
-      </p>
-
-      <p>
-        <code>csum</code> defaults to <code>false</code> for hardware VTEPs and
-        <code>true</code> for all other cases.
-      </p>
-
-      <p>
-        This option applies to <code>geneve</code> and <code>vxlan</code>
-        encapsulations.
-      </p>
-    </column>
-
-    <column name="options" key="dst_port" type='{"type": "integer"}'>
-      <p>
-        If set, overrides the UDP (for <code>geneve</code> and
-        <code>vxlan</code>) or TCP (for <code>stt</code>) destination port.
-      </p>
-    </column>
-
-    <column name="ip">
-      The IPv4 address of the encapsulation tunnel endpoint.
-    </column>
-    <column name="chassis_name">
-      The name of the chassis that created this encap.
-    </column>
-  </table>
-
-  <table name="Address_Set" title="Address Sets">
-    <p>
-      This table contains address sets synced from the <ref table="Address_Set"
-      db="OVN_Northbound"/> table in the <ref db="OVN_Northbound"/> database
-      and address sets generated from the <ref table="Port_Group"
-      db="OVN_Northbound"/> table in the <ref db="OVN_Northbound"/> database.
-    </p>
-
-    <p>
-      See the documentation for the <ref table="Address_Set"
-      db="OVN_Northbound"/> table and <ref table="Port_Group"
-      db="OVN_Northbound"/> table in the <ref db="OVN_Northbound"/>
-      database for details.
-    </p>
-
-    <column name="name"/>
-    <column name="addresses"/>
-  </table>
-
-  <table name="Port_Group" title="Port Groups">
-    <p>
-      This table contains names for the logical switch ports in the
-      <ref db="OVN_Northbound"/> database that belongs to the same group
-      that is defined in <ref table="Port_Group" db="OVN_Northbound"/>
-      in the <ref db="OVN_Northbound"/> database.
-    </p>
-
-    <column name="name"/>
-    <column name="ports"/>
-  </table>
-
-  <table name="Logical_Flow" title="Logical Network Flows">
-    <p>
-      Each row in this table represents one logical flow.
-      <code>ovn-northd</code> populates this table with logical flows
-      that implement the L2 and L3 topologies specified in the
-      <ref db="OVN_Northbound"/> database.  Each hypervisor, via
-      <code>ovn-controller</code>, translates the logical flows into
-      OpenFlow flows specific to its hypervisor and installs them into
-      Open vSwitch.
-    </p>
-
-    <p>
-      Logical flows are expressed in an OVN-specific format, described here.  A
-      logical datapath flow is much like an OpenFlow flow, except that the
-      flows are written in terms of logical ports and logical datapaths instead
-      of physical ports and physical datapaths.  Translation between logical
-      and physical flows helps to ensure isolation between logical datapaths.
-      (The logical flow abstraction also allows the OVN centralized
-      components to do less work, since they do not have to separately
-      compute and push out physical flows to each chassis.)
-    </p>
-
-    <p>
-      The default action when no flow matches is to drop packets.
-    </p>
-
-    <p><em>Architectural Logical Life Cycle of a Packet</em></p>
-
-    <p>
-      This following description focuses on the life cycle of a packet through
-      a logical datapath, ignoring physical details of the implementation.
-      Please refer to <em>Architectural Physical Life Cycle of a Packet</em> in
-      <code>ovn-architecture</code>(7) for the physical information.
-    </p>
-
-    <p>
-      The description here is written as if OVN itself executes these steps,
-      but in fact OVN (that is, <code>ovn-controller</code>) programs Open
-      vSwitch, via OpenFlow and OVSDB, to execute them on its behalf.
-    </p>
-
-    <p>
-      At a high level, OVN passes each packet through the logical datapath's
-      logical ingress pipeline, which may output the packet to one or more
-      logical port or logical multicast groups.  For each such logical output
-      port, OVN passes the packet through the datapath's logical egress
-      pipeline, which may either drop the packet or deliver it to the
-      destination.  Between the two pipelines, outputs to logical multicast
-      groups are expanded into logical ports, so that the egress pipeline only
-      processes a single logical output port at a time.  Between the two
-      pipelines is also where, when necessary, OVN encapsulates a packet in a
-      tunnel (or tunnels) to transmit to remote hypervisors.
-    </p>
-
-    <p>
-      In more detail, to start, OVN searches the <ref table="Logical_Flow"/>
-      table for a row with correct <ref column="logical_datapath"/>, a <ref
-      column="pipeline"/> of <code>ingress</code>, a <ref column="table_id"/>
-      of 0, and a <ref column="match"/> that is true for the packet.  If none
-      is found, OVN drops the packet.  If OVN finds more than one, it chooses
-      the match with the highest <ref column="priority"/>.  Then OVN executes
-      each of the actions specified in the row's <ref table="actions"/> column,
-      in the order specified.  Some actions, such as those to modify packet
-      headers, require no further details.  The <code>next</code> and
-      <code>output</code> actions are special.
-    </p>
-
-    <p>
-      The <code>next</code> action causes the above process to be repeated
-      recursively, except that OVN searches for <ref column="table_id"/> of 1
-      instead of 0.  Similarly, any <code>next</code> action in a row found in
-      that table would cause a further search for a <ref column="table_id"/> of
-      2, and so on.  When recursive processing completes, flow control returns
-      to the action following <code>next</code>.
-    </p>
-
-    <p>
-      The <code>output</code> action also introduces recursion.  Its effect
-      depends on the current value of the <code>outport</code> field.  Suppose
-      <code>outport</code> designates a logical port.  First, OVN compares
-      <code>inport</code> to <code>outport</code>; if they are equal, it treats
-      the <code>output</code> as a no-op by default.  In the common
-      case, where they are different, the packet enters the egress
-      pipeline.  This transition to the egress pipeline discards
-      register data, e.g. <code>reg0</code> ...  <code>reg9</code> and
-      connection tracking state, to achieve uniform behavior regardless
-      of whether the egress pipeline is on a different hypervisor
-      (because registers aren't preserve across tunnel encapsulation).
-    </p>
-
-    <p>
-      To execute the egress pipeline, OVN again searches the <ref
-      table="Logical_Flow"/> table for a row with correct <ref
-      column="logical_datapath"/>, a <ref column="table_id"/> of 0, a <ref
-      column="match"/> that is true for the packet, but now looking for a <ref
-      column="pipeline"/> of <code>egress</code>.  If no matching row is found,
-      the output becomes a no-op.  Otherwise, OVN executes the actions for the
-      matching flow (which is chosen from multiple, if necessary, as already
-      described).
-    </p>
-
-    <p>
-      In the <code>egress</code> pipeline, the <code>next</code> action acts as
-      already described, except that it, of course, searches for
-      <code>egress</code> flows.  The <code>output</code> action, however, now
-      directly outputs the packet to the output port (which is now fixed,
-      because <code>outport</code> is read-only within the egress pipeline).
-    </p>
-
-    <p>
-      The description earlier assumed that <code>outport</code> referred to a
-      logical port.  If it instead designates a logical multicast group, then
-      the description above still applies, with the addition of fan-out from
-      the logical multicast group to each logical port in the group.  For each
-      member of the group, OVN executes the logical pipeline as described, with
-      the logical output port replaced by the group member.
-    </p>
-
-    <p><em>Pipeline Stages</em></p>
-
-    <p>
-      <code>ovn-northd</code> populates the <ref table="Logical_Flow"/> table
-      with the logical flows described in detail in <code>ovn-northd</code>(8).
-    </p>
-
-    <column name="logical_datapath">
-      The logical datapath to which the logical flow belongs.
-    </column>
-
-    <column name="pipeline">
-      <p>
-        The primary flows used for deciding on a packet's destination are the
-        <code>ingress</code> flows.  The <code>egress</code> flows implement
-        ACLs.  See <em>Logical Life Cycle of a Packet</em>, above, for details.
-      </p>
-    </column>
-
-    <column name="table_id">
-      The stage in the logical pipeline, analogous to an OpenFlow table number.
-    </column>
-
-    <column name="priority">
-      The flow's priority.  Flows with numerically higher priority take
-      precedence over those with lower.  If two logical datapath flows with the
-      same priority both match, then the one actually applied to the packet is
-      undefined.
-    </column>
-
-    <column name="match">
-      <p>
-        A matching expression.  OVN provides a superset of OpenFlow matching
-        capabilities, using a syntax similar to Boolean expressions in a
-        programming language.
-      </p>
-
-      <p>
-        The most important components of match expression are
-        <dfn>comparisons</dfn> between <dfn>symbols</dfn> and
-        <dfn>constants</dfn>, e.g. <code>ip4.dst == 192.168.0.1</code>,
-        <code>ip.proto == 6</code>, <code>arp.op == 1</code>, <code>eth.type ==
-        0x800</code>.  The logical AND operator <code>&amp;&amp;</code> and
-        logical OR operator <code>||</code> can combine comparisons into a
-        larger expression.
-      </p>
-
-      <p>
-        Matching expressions also support parentheses for grouping, the logical
-        NOT prefix operator <code>!</code>, and literals <code>0</code> and
-        <code>1</code> to express ``false'' or ``true,'' respectively.  The
-        latter is useful by itself as a catch-all expression that matches every
-        packet.
-      </p>
-
-      <p>
-        Match expressions also support a kind of function syntax.  The
-        following functions are supported:
-      </p>
-
-      <dl>
-        <dt><code>is_chassis_resident(<var>lport</var>)</code></dt>
-        <dd>
-          Evaluates to true on a chassis on which logical port <var>lport</var>
-          (a quoted string) resides, and to false elsewhere.  This function was
-          introduced in OVN 2.7.
-        </dd>
-      </dl>
-
-      <p><em>Symbols</em></p>
-
-      <p>
-        <em>Type</em>.  Symbols have <dfn>integer</dfn> or <dfn>string</dfn>
-        type.  Integer symbols have a <dfn>width</dfn> in bits.
-      </p>
-
-      <p>
-        <em>Kinds</em>.  There are three kinds of symbols:
-      </p>
-
-      <ul>
-        <li>
-          <p>
-            <dfn>Fields</dfn>.  A field symbol represents a packet header or
-            metadata field.  For example, a field
-            named <code>vlan.tci</code> might represent the VLAN TCI field in a
-            packet.
-          </p>
-
-          <p>
-            A field symbol can have integer or string type.  Integer fields can
-            be nominal or ordinal (see <em>Level of Measurement</em>,
-            below).
-          </p>
-        </li>
-
-        <li>
-          <p>
-            <dfn>Subfields</dfn>.  A subfield represents a subset of bits from
-            a larger field.  For example, a field <code>vlan.vid</code> might
-            be defined as an alias for <code>vlan.tci[0..11]</code>.  Subfields
-            are provided for syntactic convenience, because it is always
-            possible to instead refer to a subset of bits from a field
-            directly.
-          </p>
-
-          <p>
-            Only ordinal fields (see <em>Level of Measurement</em>,
-            below) may have subfields.  Subfields are always ordinal.
-          </p>
-        </li>
-
-        <li>
-          <p>
-            <dfn>Predicates</dfn>.  A predicate is shorthand for a Boolean
-            expression.  Predicates may be used much like 1-bit fields.  For
-            example, <code>ip4</code> might expand to <code>eth.type ==
-            0x800</code>.  Predicates are provided for syntactic convenience,
-            because it is always possible to instead specify the underlying
-            expression directly.
-          </p>
-
-          <p>
-            A predicate whose expansion refers to any nominal field or
-            predicate (see <em>Level of Measurement</em>, below) is nominal;
-            other predicates have Boolean level of measurement.
-          </p>
-        </li>
-      </ul>
-
-      <p>
-        <em>Level of Measurement</em>.  See
-        http://en.wikipedia.org/wiki/Level_of_measurement for the statistical
-        concept on which this classification is based.  There are three
-        levels:
-      </p>
-
-      <ul>
-        <li>
-          <p>
-            <dfn>Ordinal</dfn>.  In statistics, ordinal values can be ordered
-            on a scale.  OVN considers a field (or subfield) to be ordinal if
-            its bits can be examined individually.  This is true for the
-            OpenFlow fields that OpenFlow or Open vSwitch makes ``maskable.''
-          </p>
-
-          <p>
-            Any use of a ordinal field may specify a single bit or a range of
-            bits, e.g. <code>vlan.tci[13..15]</code> refers to the PCP field
-            within the VLAN TCI, and <code>eth.dst[40]</code> refers to the
-            multicast bit in the Ethernet destination address.
-          </p>
-
-          <p>
-            OVN supports all the usual arithmetic relations (<code>==</code>,
-            <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>,
-            <code>&gt;</code>, and <code>&gt;=</code>) on ordinal fields and
-            their subfields, because OVN can implement these in OpenFlow and
-            Open vSwitch as collections of bitwise tests.
-          </p>
-        </li>
-
-        <li>
-          <p>
-            <dfn>Nominal</dfn>.  In statistics, nominal values cannot be
-            usefully compared except for equality.  This is true of OpenFlow
-            port numbers, Ethernet types, and IP protocols are examples: all of
-            these are just identifiers assigned arbitrarily with no deeper
-            meaning.  In OpenFlow and Open vSwitch, bits in these fields
-            generally aren't individually addressable.
-          </p>
-
-          <p>
-            OVN only supports arithmetic tests for equality on nominal fields,
-            because OpenFlow and Open vSwitch provide no way for a flow to
-            efficiently implement other comparisons on them.  (A test for
-            inequality can be sort of built out of two flows with different
-            priorities, but OVN matching expressions always generate flows with
-            a single priority.)
-          </p>
-
-          <p>
-            String fields are always nominal.
-          </p>
-        </li>
-
-        <li>
-          <p>
-            <dfn>Boolean</dfn>.  A nominal field that has only two values, 0
-            and 1, is somewhat exceptional, since it is easy to support both
-            equality and inequality tests on such a field: either one can be
-            implemented as a test for 0 or 1.
-          </p>
-
-          <p>
-            Only predicates (see above) have a Boolean level of measurement.
-          </p>
-
-          <p>
-            This isn't a standard level of measurement.
-          </p>
-        </li>
-      </ul>
-
-      <p>
-        <em>Prerequisites</em>.  Any symbol can have prerequisites, which are
-        additional condition implied by the use of the symbol.  For example,
-        For example, <code>icmp4.type</code> symbol might have prerequisite
-        <code>icmp4</code>, which would cause an expression <code>icmp4.type ==
-        0</code> to be interpreted as <code>icmp4.type == 0 &amp;&amp;
-        icmp4</code>, which would in turn expand to <code>icmp4.type == 0
-        &amp;&amp; eth.type == 0x800 &amp;&amp; ip4.proto == 1</code> (assuming
-        <code>icmp4</code> is a predicate defined as suggested under
-        <em>Types</em> above).
-      </p>
-
-      <p><em>Relational operators</em></p>
-
-      <p>
-        All of the standard relational operators <code>==</code>,
-        <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>,
-        <code>&gt;</code>, and <code>&gt;=</code> are supported.  Nominal
-        fields support only <code>==</code> and <code>!=</code>, and only in a
-        positive sense when outer <code>!</code> are taken into account,
-        e.g. given string field <code>inport</code>, <code>inport ==
-        "eth0"</code> and <code>!(inport != "eth0")</code> are acceptable, but
-        not <code>inport != "eth0"</code>.
-      </p>
-
-      <p>
-        The implementation of <code>==</code> (or <code>!=</code> when it is
-        negated), is more efficient than that of the other relational
-        operators.
-      </p>
-
-      <p><em>Constants</em></p>
-
-      <p>
-        Integer constants may be expressed in decimal, hexadecimal prefixed by
-        <code>0x</code>, or as dotted-quad IPv4 addresses, IPv6 addresses in
-        their standard forms, or Ethernet addresses as colon-separated hex
-        digits.  A constant in any of these forms may be followed by a slash
-        and a second constant (the mask) in the same form, to form a masked
-        constant.  IPv4 and IPv6 masks may be given as integers, to express
-        CIDR prefixes.
-      </p>
-
-      <p>
-        String constants have the same syntax as quoted strings in JSON (thus,
-        they are Unicode strings).
-      </p>
-
-      <p>
-        Some operators support sets of constants written inside curly braces
-        <code>{</code> ... <code>}</code>.  Commas between elements of a set,
-        and after the last elements, are optional.  With <code>==</code>,
-        ``<code><var>field</var> == { <var>constant1</var>,
-        <var>constant2</var>,</code> ... <code>}</code>'' is syntactic sugar
-        for ``<code><var>field</var> == <var>constant1</var> ||
-        <var>field</var> == <var>constant2</var> || </code>...<code></code>.
-        Similarly, ``<code><var>field</var> != { <var>constant1</var>,
-        <var>constant2</var>, </code>...<code> }</code>'' is equivalent to
-        ``<code><var>field</var> != <var>constant1</var> &amp;&amp;
-        <var>field</var> != <var>constant2</var> &amp;&amp;
-        </code>...<code></code>''.
-      </p>
-
-      <p>
-        You may refer to a set of IPv4, IPv6, or MAC addresses stored in the
-        <ref table="Address_Set"/> table by its <ref column="name"
-        table="Address_Set"/>.  An <ref table="Address_Set"/> with a name
-        of <code>set1</code> can be referred to as
-        <code>$set1</code>.
-      </p>
-
-      <p>
-        You may refer to a group of logical switch ports stored in the
-        <ref table="Port_Group"/> table by its <ref column="name"
-        table="Port_Group"/>.  An <ref table="Port_Group"/> with a name
-        of <code>port_group1</code> can be referred to as
-        <code>@port_group1</code>.
-      </p>
-
-      <p>
-        Additionally, you may refer to the set of addresses belonging to a
-        group of logical switch ports stored in the <ref table="Port_Group"/>
-        table by its <ref column="name" table="Port_Group"/> followed by
-        a suffix '_ip4'/'_ip6'.  The IPv4 address set of a
-        <ref table="Port_Group"/> with a name of <code>port_group1</code>
-        can be referred to as <code>$port_group1_ip4</code>, and the IPv6
-        address set of the same <ref table="Port_Group"/> can be referred to
-        as <code>$port_group1_ip6</code>
-      </p>
-
-      <p><em>Miscellaneous</em></p>
-
-      <p>
-        Comparisons may name the symbol or the constant first,
-        e.g. <code>tcp.src == 80</code> and <code>80 == tcp.src</code> are both
-        acceptable.
-      </p>
-
-      <p>
-        Tests for a range may be expressed using a syntax like <code>1024 &lt;=
-        tcp.src &lt;= 49151</code>, which is equivalent to <code>1024 &lt;=
-        tcp.src &amp;&amp; tcp.src &lt;= 49151</code>.
-      </p>
-
-      <p>
-        For a one-bit field or predicate, a mention of its name is equivalent
-        to <code><var>symobl</var> == 1</code>, e.g. <code>vlan.present</code>
-        is equivalent to <code>vlan.present == 1</code>.  The same is true for
-        one-bit subfields, e.g. <code>vlan.tci[12]</code>.  There is no
-        technical limitation to implementing the same for ordinal fields of all
-        widths, but the implementation is expensive enough that the syntax
-        parser requires writing an explicit comparison against zero to make
-        mistakes less likely, e.g. in <code>tcp.src != 0</code> the comparison
-        against 0 is required.
-      </p>
-
-      <p>
-        <em>Operator precedence</em> is as shown below, from highest to lowest.
-        There are two exceptions where parentheses are required even though the
-        table would suggest that they are not: <code>&amp;&amp;</code> and
-        <code>||</code> require parentheses when used together, and
-        <code>!</code> requires parentheses when applied to a relational
-        expression.  Thus, in <code>(eth.type == 0x800 || eth.type == 0x86dd)
-        &amp;&amp; ip.proto == 6</code> or <code>!(arp.op == 1)</code>, the
-        parentheses are mandatory.
-      </p>
-
-      <ul>
-        <li><code>()</code></li>
-        <li><code>==   !=   &lt;   &lt;=   &gt;   &gt;=</code></li>
-        <li><code>!</code></li>
-        <li><code>&amp;&amp;   ||</code></li>
-      </ul>
-
-      <p>
-        <em>Comments</em> may be introduced by <code>//</code>, which extends
-        to the next new-line.  Comments within a line may be bracketed by
-        <code>/*</code> and <code>*/</code>.  Multiline comments are not
-        supported.
-      </p>
-
-      <p><em>Symbols</em></p>
-
-      <p>
-        Most of the symbols below have integer type.  Only <code>inport</code>
-        and <code>outport</code> have string type.  <code>inport</code> names a
-        logical port.  Thus, its value is a <ref column="logical_port"/> name
-        from the <ref table="Port_Binding"/> table.  <code>outport</code> may
-        name a logical port, as <code>inport</code>, or a logical multicast
-        group defined in the <ref table="Multicast_Group"/> table.  For both
-        symbols, only names within the flow's logical datapath may be used.
-      </p>
-
-      <p>
-        The <code>reg</code><var>X</var> symbols are 32-bit integers.
-        The <code>xxreg</code><var>X</var> symbols are 128-bit integers,
-        which overlay four of the 32-bit registers: <code>xxreg0</code>
-        overlays <code>reg0</code> through <code>reg3</code>, with
-        <code>reg0</code> supplying the most-significant bits of
-        <code>xxreg0</code> and <code>reg3</code> the least-signficant.
-        <code>xxreg1</code> similarly overlays <code>reg4</code> through
-        <code>reg7</code>.
-      </p>
-
-      <ul>
-        <li><code>reg0</code>...<code>reg9</code></li>
-        <li><code>xxreg0</code> <code>xxreg1</code></li>
-        <li><code>inport</code> <code>outport</code></li>
-        <li><code>flags.loopback</code></li>
-        <li><code>eth.src</code> <code>eth.dst</code> <code>eth.type</code></li>
-        <li><code>vlan.tci</code> <code>vlan.vid</code> <code>vlan.pcp</code> <code>vlan.present</code></li>
-        <li><code>ip.proto</code> <code>ip.dscp</code> <code>ip.ecn</code> <code>ip.ttl</code> <code>ip.frag</code></li>
-        <li><code>ip4.src</code> <code>ip4.dst</code></li>
-        <li><code>ip6.src</code> <code>ip6.dst</code> <code>ip6.label</code></li>
-        <li><code>arp.op</code> <code>arp.spa</code> <code>arp.tpa</code> <code>arp.sha</code> <code>arp.tha</code></li>
-        <li><code>tcp.src</code> <code>tcp.dst</code> <code>tcp.flags</code></li>
-        <li><code>udp.src</code> <code>udp.dst</code></li>
-        <li><code>sctp.src</code> <code>sctp.dst</code></li>
-        <li><code>icmp4.type</code> <code>icmp4.code</code></li>
-        <li><code>icmp6.type</code> <code>icmp6.code</code></li>
-        <li><code>nd.target</code> <code>nd.sll</code> <code>nd.tll</code></li>
-        <li><code>ct_mark</code> <code>ct_label</code></li>
-        <li>
-          <p>
-            <code>ct_state</code>, which has several Boolean subfields.  The
-            <code>ct_next</code> action initializes the following subfields:
-          </p>
-          <ul>
-            <li>
-              <code>ct.trk</code>: Always set to true by <code>ct_next</code>
-              to indicate that connection tracking has taken place.  All other
-              <code>ct</code> subfields have <code>ct.trk</code> as a
-              prerequisite.
-            </li>
-            <li><code>ct.new</code>: True for a new flow</li>
-            <li><code>ct.est</code>: True for an established flow</li>
-            <li><code>ct.rel</code>: True for a related flow</li>
-            <li><code>ct.rpl</code>: True for a reply flow</li>
-            <li><code>ct.inv</code>: True for a connection entry in a bad state</li>
-          </ul>
-          <p>
-            The <code>ct_dnat</code>, <code>ct_snat</code>, and
-            <code>ct_lb</code> actions initialize the following subfields:
-          </p>
-          <ul>
-            <li>
-              <code>ct.dnat</code>: True for a packet whose destination IP
-              address has been changed.
-            </li>
-            <li>
-              <code>ct.snat</code>: True for a packet whose source IP
-              address has been changed.
-            </li>
-          </ul>
-        </li>
-      </ul>
-
-      <p>
-        The following predicates are supported:
-      </p>
-
-      <ul>
-        <li><code>eth.bcast</code> expands to <code>eth.dst == ff:ff:ff:ff:ff:ff</code></li>
-        <li><code>eth.mcast</code> expands to <code>eth.dst[40]</code></li>
-        <li><code>vlan.present</code> expands to <code>vlan.tci[12]</code></li>
-        <li><code>ip4</code> expands to <code>eth.type == 0x800</code></li>
-        <li><code>ip4.mcast</code> expands to <code>ip4.dst[28..31] == 0xe</code></li>
-        <li><code>ip6</code> expands to <code>eth.type == 0x86dd</code></li>
-        <li><code>ip</code> expands to <code>ip4 || ip6</code></li>
-        <li><code>icmp4</code> expands to <code>ip4 &amp;&amp; ip.proto == 1</code></li>
-        <li><code>icmp6</code> expands to <code>ip6 &amp;&amp; ip.proto == 58</code></li>
-        <li><code>icmp</code> expands to <code>icmp4 || icmp6</code></li>
-        <li><code>ip.is_frag</code> expands to <code>ip.frag[0]</code></li>
-        <li><code>ip.later_frag</code> expands to <code>ip.frag[1]</code></li>
-        <li><code>ip.first_frag</code> expands to <code>ip.is_frag &amp;&amp; !ip.later_frag</code></li>
-        <li><code>arp</code> expands to <code>eth.type == 0x806</code></li>
-        <li><code>nd</code> expands to <code>icmp6.type == {135, 136} &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
-        <li><code>nd_ns</code> expands to <code>icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
-        <li><code>nd_na</code> expands to <code>icmp6.type == 136 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
-        <li><code>nd_rs</code> expands to <code>icmp6.type == 133 &amp;&amp;
-        icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
-        <li><code>nd_ra</code> expands to <code>icmp6.type == 134 &amp;&amp;
-        icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
-        <li><code>tcp</code> expands to <code>ip.proto == 6</code></li>
-        <li><code>udp</code> expands to <code>ip.proto == 17</code></li>
-        <li><code>sctp</code> expands to <code>ip.proto == 132</code></li>
-      </ul>
-    </column>
-
-    <column name="actions">
-      <p>
-        Logical datapath actions, to be executed when the logical flow
-        represented by this row is the highest-priority match.
-      </p>
-
-      <p>
-        Actions share lexical syntax with the <ref column="match"/> column.  An
-        empty set of actions (or one that contains just white space or
-        comments), or a set of actions that consists of just
-        <code>drop;</code>, causes the matched packets to be dropped.
-        Otherwise, the column should contain a sequence of actions, each
-        terminated by a semicolon.
-      </p>
-
-      <p>
-        The following actions are defined:
-      </p>
-
-      <dl>
-        <dt><code>output;</code></dt>
-        <dd>
-          <p>
-            In the ingress pipeline, this action executes the
-            <code>egress</code> pipeline as a subroutine.  If
-            <code>outport</code> names a logical port, the egress pipeline
-            executes once; if it is a multicast group, the egress pipeline runs
-            once for each logical port in the group.
-          </p>
-
-          <p>
-            In the egress pipeline, this action performs the actual
-            output to the <code>outport</code> logical port.  (In the egress
-            pipeline, <code>outport</code> never names a multicast group.)
-          </p>
-
-          <p>
-            By default, output to the input port is implicitly dropped,
-            that is, <code>output</code> becomes a no-op if
-            <code>outport</code> == <code>inport</code>.  Occasionally
-            it may be useful to override this behavior, e.g. to send an
-            ARP reply to an ARP request; to do so, use
-            <code>flags.loopback = 1</code> to allow the packet to
-            "hair-pin" back to the input port.
-          </p>
-        </dd>
-
-        <dt><code>next;</code></dt>
-        <dt><code>next(<var>table</var>);</code></dt>
-        <dt><code>next(pipeline=<var>pipeline</var>, table=<var>table</var>);</code></dt>
-        <dd>
-          Executes the given logical datapath <var>table</var> in
-          <var>pipeline</var> as a subroutine.  The default <var>table</var> is
-          just after the current one.  If <var>pipeline</var> is specified, it
-          may be <code>ingress</code> or <code>egress</code>; the default
-          <var>pipeline</var> is the one currently executing.  Actions in the
-          ingress pipeline may not use <code>next</code> to jump into the
-          egress pipeline (use the <code>output</code> instead), but
-          transitions in the opposite direction are allowed.
-        </dd>
-
-        <dt><code><var>field</var> = <var>constant</var>;</code></dt>
-        <dd>
-          <p>
-            Sets data or metadata field <var>field</var> to constant value
-            <var>constant</var>, e.g. <code>outport = "vif0";</code> to set the
-            logical output port.  To set only a subset of bits in a field,
-            specify a subfield for <var>field</var> or a masked
-            <var>constant</var>, e.g. one may use <code>vlan.pcp[2] = 1;</code>
-            or <code>vlan.pcp = 4/4;</code> to set the most sigificant bit of
-            the VLAN PCP.
-          </p>
-
-          <p>
-            Assigning to a field with prerequisites implicitly adds those
-            prerequisites to <ref column="match"/>; thus, for example, a flow
-            that sets <code>tcp.dst</code> applies only to TCP flows,
-            regardless of whether its <ref column="match"/> mentions any TCP
-            field.
-          </p>
-
-          <p>
-            Not all fields are modifiable (e.g. <code>eth.type</code> and
-            <code>ip.proto</code> are read-only), and not all modifiable fields
-            may be partially modified (e.g. <code>ip.ttl</code> must assigned
-            as a whole).  The <code>outport</code> field is modifiable in the
-            <code>ingress</code> pipeline but not in the <code>egress</code>
-            pipeline.
-          </p>
-        </dd>
-
-        <dt><code><var>ovn_field</var> = <var>constant</var>;</code></dt>
-        <dd>
-          <p>
-            Sets OVN field <var>ovn_field</var> to constant value
-            <var>constant</var>.
-          </p>
-
-          <p>
-            <code>OVN</code> supports setting the values of certain fields
-            which are not yet supported in OpenFlow to set or modify them.
-          </p>
-
-          <p>
-            Below are the supported <code>OVN fields</code>:
-          </p>
-
-          <ul>
-            <li>
-              <code>icmp4.frag_mtu</code>
-              <p>
-                This field sets the low-order 16 bits of the ICMP4 header field
-                that is labelled "unused" in the ICMP specification as defined
-                in the RFC 1191 with the value specified in
-                <var>constant</var>.
-              </p>
-
-              <p>
-                Eg. icmp4.frag_mtu = 1500;
-              </p>
-            </li>
-          </ul>
-        </dd>
-
-        <dt><code><var>field1</var> = <var>field2</var>;</code></dt>
-        <dd>
-          <p>
-            Sets data or metadata field <var>field1</var> to the value of data
-            or metadata field <var>field2</var>, e.g. <code>reg0 =
-            ip4.src;</code> copies <code>ip4.src</code> into <code>reg0</code>.
-            To modify only a subset of a field's bits, specify a subfield for
-            <var>field1</var> or <var>field2</var> or both, e.g. <code>vlan.pcp
-            = reg0[0..2];</code> copies the least-significant bits of
-            <code>reg0</code> into the VLAN PCP.
-          </p>
-
-          <p>
-            <var>field1</var> and <var>field2</var> must be the same type,
-            either both string or both integer fields.  If they are both
-            integer fields, they must have the same width.
-          </p>
-
-          <p>
-            If <var>field1</var> or <var>field2</var> has prerequisites, they
-            are added implicitly to <ref column="match"/>.  It is possible to
-            write an assignment with contradictory prerequisites, such as
-            <code>ip4.src = ip6.src[0..31];</code>, but the contradiction means
-            that a logical flow with such an assignment will never be matched.
-          </p>
-        </dd>
-
-        <dt><code><var>field1</var> &lt;-&gt; <var>field2</var>;</code></dt>
-        <dd>
-          <p>
-            Similar to <code><var>field1</var> = <var>field2</var>;</code>
-            except that the two values are exchanged instead of copied.  Both
-            <var>field1</var> and <var>field2</var> must modifiable.
-          </p>
-        </dd>
-
-        <dt><code>ip.ttl--;</code></dt>
-        <dd>
-          <p>
-            Decrements the IPv4 or IPv6 TTL.  If this would make the TTL zero
-            or negative, then processing of the packet halts; no further
-            actions are processed.  (To properly handle such cases, a
-            higher-priority flow should match on
-            <code>ip.ttl == {0, 1};</code>.)
-          </p>
-
-          <p><b>Prerequisite:</b> <code>ip</code></p>
-        </dd>
-
-        <dt><code>ct_next;</code></dt>
-        <dd>
-          <p>
-            Apply connection tracking to the flow, initializing
-            <code>ct_state</code> for matching in later tables.
-            Automatically moves on to the next table, as if followed by
-            <code>next</code>.
-          </p>
-
-          <p>
-            As a side effect, IP fragments will be reassembled for matching.
-            If a fragmented packet is output, then it will be sent with any
-            overlapping fragments squashed.  The connection tracking state is
-            scoped by the logical port when the action is used in a flow for
-            a logical switch, so overlapping addresses may be used.  To allow
-            traffic related to the matched flow, execute <code>ct_commit
-            </code>.  Connection tracking state is scoped by the logical
-            topology when the action is used in a flow for a router.
-          </p>
-
-          <p>
-            It is possible to have actions follow <code>ct_next</code>,
-            but they will not have access to any of its side-effects and
-            is not generally useful.
-          </p>
-        </dd>
-
-        <dt><code>ct_commit;</code></dt>
-        <dt><code>ct_commit(ct_mark=<var>value[/mask]</var>);</code></dt>
-        <dt><code>ct_commit(ct_label=<var>value[/mask]</var>);</code></dt>
-        <dt><code>ct_commit(ct_mark=<var>value[/mask]</var>, ct_label=<var>value[/mask]</var>);</code></dt>
-        <dd>
-          <p>
-            Commit the flow to the connection tracking entry associated with it
-            by a previous call to <code>ct_next</code>.  When
-            <code>ct_mark=<var>value[/mask]</var></code> and/or
-            <code>ct_label=<var>value[/mask]</var></code> are supplied,
-            <code>ct_mark</code> and/or <code>ct_label</code> will be set to the
-            values indicated by <var>value[/mask]</var> on the connection
-            tracking entry. <code>ct_mark</code> is a 32-bit field.
-            <code>ct_label</code> is a 128-bit field. The <var>value[/mask]</var>
-            should be specified in hex string if more than 64bits are to be used.
-          </p>
-
-          <p>
-            Note that if you want processing to continue in the next table,
-            you must execute the <code>next</code> action after
-            <code>ct_commit</code>.  You may also leave out <code>next</code>
-            which will commit connection tracking state, and then drop the
-            packet.  This could be useful for setting <code>ct_mark</code>
-            on a connection tracking entry before dropping a packet,
-            for example.
-          </p>
-        </dd>
-
-        <dt><code>ct_dnat;</code></dt>
-        <dt><code>ct_dnat(<var>IP</var>);</code></dt>
-        <dd>
-          <p>
-            <code>ct_dnat</code> sends the packet through the DNAT zone in
-            connection tracking table to unDNAT any packet that was DNATed in
-            the opposite direction.  The packet is then automatically sent to
-            to the next tables as if followed by <code>next;</code> action.
-            The next tables will see the changes in the packet caused by
-            the connection tracker.
-          </p>
-          <p>
-            <code>ct_dnat(<var>IP</var>)</code> sends the packet through the
-            DNAT zone to change the destination IP address of the packet to
-            the one provided inside the parentheses and commits the connection.
-            The packet is then automatically sent to the next tables as if
-            followed by <code>next;</code> action.  The next tables will see
-            the changes in the packet caused by the connection tracker.
-          </p>
-        </dd>
-
-        <dt><code>ct_snat;</code></dt>
-        <dt><code>ct_snat(<var>IP</var>);</code></dt>
-        <dd>
-          <p>
-            <code>ct_snat</code> sends the packet through the SNAT zone to
-            unSNAT any packet that was SNATed in the opposite direction.  The
-            packet is automatically sent to the next tables as if followed by
-            the <code>next;</code> action.   The next tables will see the
-            changes in the packet caused by the connection tracker.
-          </p>
-          <p>
-            <code>ct_snat(<var>IP</var>)</code> sends the packet through the
-            SNAT zone to change the source IP address of the packet to
-            the one provided inside the parenthesis and commits the connection.
-            The packet is then automatically sent to the next tables as if
-            followed by <code>next;</code> action.  The next tables will see the
-            changes in the packet caused by the connection tracker.
-          </p>
-        </dd>
-
-        <dt><code>ct_clear;</code></dt>
-        <dd>
-          Clears connection tracking state.
-        </dd>
-
-        <dt><code>clone { <var>action</var>; </code>...<code> };</code></dt>
-        <dd>
-          Makes a copy of the packet being processed and executes each
-          <code>action</code> on the copy.  Actions following the
-          <var>clone</var> action, if any, apply to the original, unmodified
-          packet.  This can be used as a way to ``save and restore'' the packet
-          around a set of actions that may modify it and should not persist.
-        </dd>
-
-        <dt><code>arp { <var>action</var>; </code>...<code> };</code></dt>
-        <dd>
-          <p>
-            Temporarily replaces the IPv4 packet being processed by an ARP
-            packet and executes each nested <var>action</var> on the ARP
-            packet.  Actions following the <var>arp</var> action, if any, apply
-            to the original, unmodified packet.
-          </p>
-
-          <p>
-            The ARP packet that this action operates on is initialized based on
-            the IPv4 packet being processed, as follows.  These are default
-            values that the nested actions will probably want to change:
-          </p>
-
-          <ul>
-            <li><code>eth.src</code> unchanged</li>
-            <li><code>eth.dst</code> unchanged</li>
-            <li><code>eth.type = 0x0806</code></li>
-            <li><code>arp.op = 1</code> (ARP request)</li>
-            <li><code>arp.sha</code> copied from <code>eth.src</code></li>
-            <li><code>arp.spa</code> copied from <code>ip4.src</code></li>
-            <li><code>arp.tha = 00:00:00:00:00:00</code></li>
-            <li><code>arp.tpa</code> copied from <code>ip4.dst</code></li>
-          </ul>
-
-          <p>
-            The ARP packet has the same VLAN header, if any, as the IP packet
-            it replaces.
-          </p>
-
-          <p><b>Prerequisite:</b> <code>ip4</code></p>
-        </dd>
-
-        <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
-            IP address field <var>A</var>.
-          </p>
-
-          <p>
-            Looks up <var>A</var> in <var>P</var>'s mac binding table.
-            If an entry is found, stores its Ethernet address in
-            <code>eth.dst</code>, otherwise stores
-            <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
-          </p>
-
-          <p><b>Example:</b> <code>get_arp(outport, ip4.dst);</code></p>
-        </dd>
-
-        <dt>
-          <code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
-            IP address field <var>A</var>, 48-bit Ethernet address field
-            <var>E</var>.
-          </p>
-
-          <p>
-            Adds or updates the entry for IP address <var>A</var> in
-            logical port <var>P</var>'s mac binding table, setting its
-            Ethernet address to <var>E</var>.
-          </p>
-
-          <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
-        </dd>
-
-        <dt><code>nd_ns { <var>action</var>; </code>...<code> };</code></dt>
-        <dd>
-          <p>
-            Temporarily replaces the IPv6 packet being processed by an IPv6
-            Neighbor Solicitation packet and executes each nested
-            <var>action</var> on the IPv6 NS packet.  Actions following the
-            <var>nd_ns</var> action, if any, apply to the original, unmodified
-            packet.
-          </p>
-
-          <p>
-            The IPv6 NS packet that this action operates on is initialized
-            based on the IPv6 packet being processed, as follows.  These are
-            default values that the nested actions will probably want to
-            change:
-          </p>
-
-          <ul>
-            <li><code>eth.src</code> unchanged</li>
-            <li><code>eth.dst</code> set to IPv6 multicast MAC address</li>
-            <li><code>eth.type = 0x86dd</code></li>
-            <li><code>ip6.src</code> copied from <code>ip6.src</code></li>
-            <li>
-              <code>ip6.dst</code> set to IPv6 Solicited-Node multicast address
-            </li>
-            <li><code>icmp6.type = 135</code> (Neighbor Solicitation)</li>
-            <li><code>nd.target</code> copied from <code>ip6.dst</code></li>
-          </ul>
-
-          <p>
-            The IPv6 NS packet has the same VLAN header, if any, as the IP
-            packet it replaces.
-          </p>
-
-          <p><b>Prerequisite:</b> <code>ip6</code></p>
-        </dd>
-
-        <dt>
-          <code>nd_na { <var>action</var>; </code>...<code> };</code>
-        </dt>
-
-        <dd>
-          <p>
-            Temporarily replaces the IPv6 neighbor solicitation packet
-            being processed by an IPv6 neighbor advertisement (NA)
-            packet and executes each nested <var>action</var> on the NA
-            packet.  Actions following the <code>nd_na</code> action,
-            if any, apply to the original, unmodified packet.
-          </p>
-
-          <p>
-            The NA packet that this action operates on is initialized based on
-            the IPv6 packet being processed, as follows. These are default
-            values that the nested actions will probably want to change:
-          </p>
-
-          <ul>
-            <li><code>eth.dst</code> exchanged with <code>eth.src</code></li>
-            <li><code>eth.type = 0x86dd</code></li>
-            <li><code>ip6.dst</code> copied from <code>ip6.src</code></li>
-            <li><code>ip6.src</code> copied from <code>nd.target</code></li>
-            <li><code>icmp6.type = 136</code> (Neighbor Advertisement)</li>
-            <li><code>nd.target</code> unchanged</li>
-            <li><code>nd.sll = 00:00:00:00:00:00</code></li>
-            <li><code>nd.tll</code> copied from <code>eth.dst</code></li>
-          </ul>
-
-          <p>
-            The ND packet has the same VLAN header, if any, as the IPv6 packet
-            it replaces.
-          </p>
-
-          <p>
-            <b>Prerequisite:</b> <code>nd_ns</code>
-          </p>
-        </dd>
-
-        <dt>
-          <code>nd_na_router { <var>action</var>; </code>...<code> };</code>
-        </dt>
-
-        <dd>
-          <p>
-            Temporarily replaces the IPv6 neighbor solicitation packet
-            being processed by an IPv6 neighbor advertisement (NA)
-            packet, sets ND_NSO_ROUTER in the RSO flags and executes each
-            nested <var>action</var> on the NA packet.  Actions following
-            the <code>nd_na_router</code> action, if any, apply to the
-            original, unmodified packet.
-          </p>
-
-          <p>
-            The NA packet that this action operates on is initialized based on
-            the IPv6 packet being processed, as follows. These are default
-            values that the nested actions will probably want to change:
-          </p>
-
-          <ul>
-            <li><code>eth.dst</code> exchanged with <code>eth.src</code></li>
-            <li><code>eth.type = 0x86dd</code></li>
-            <li><code>ip6.dst</code> copied from <code>ip6.src</code></li>
-            <li><code>ip6.src</code> copied from <code>nd.target</code></li>
-            <li><code>icmp6.type = 136</code> (Neighbor Advertisement)</li>
-            <li><code>nd.target</code> unchanged</li>
-            <li><code>nd.sll = 00:00:00:00:00:00</code></li>
-            <li><code>nd.tll</code> copied from <code>eth.dst</code></li>
-          </ul>
-
-          <p>
-            The ND packet has the same VLAN header, if any, as the IPv6 packet
-            it replaces.
-          </p>
-
-          <p>
-            <b>Prerequisite:</b> <code>nd_ns</code>
-          </p>
-        </dd>
-
-        <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: logical port string field <var>P</var>, 128-bit
-            IPv6 address field <var>A</var>.
-          </p>
-
-          <p>
-            Looks up <var>A</var> in <var>P</var>'s mac binding table.
-            If an entry is found, stores its Ethernet address in
-            <code>eth.dst</code>, otherwise stores
-            <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
-          </p>
-
-          <p><b>Example:</b> <code>get_nd(outport, ip6.dst);</code></p>
-        </dd>
-
-        <dt>
-          <code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: logical port string field <var>P</var>,
-            128-bit IPv6 address field <var>A</var>, 48-bit Ethernet
-            address field <var>E</var>.
-          </p>
-
-          <p>
-            Adds or updates the entry for IPv6 address <var>A</var> in
-            logical port <var>P</var>'s mac binding table, setting its
-            Ethernet address to <var>E</var>.
-          </p>
-
-          <p><b>Example:</b> <code>put_nd(inport, nd.target, nd.tll);</code></p>
-        </dd>
-
-        <dt>
-          <code><var>R</var> = put_dhcp_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: one or more DHCP option/value pairs, which must
-            include an <code>offerip</code> option (with code 0).
-          </p>
-
-          <p>
-            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
-          </p>
-
-          <p>
-            Valid only in the ingress pipeline.
-          </p>
-
-          <p>
-            When this action is applied to a DHCP request packet (DHCPDISCOVER
-            or DHCPREQUEST), it changes the packet into a DHCP reply (DHCPOFFER
-            or DHCPACK, respectively), replaces the options by those specified
-            as parameters, and stores 1 in <var>R</var>.
-          </p>
-
-          <p>
-            When this action is applied to a non-DHCP packet or a DHCP packet
-            that is not DHCPDISCOVER or DHCPREQUEST, it leaves the packet
-            unchanged and stores 0 in <var>R</var>.
-          </p>
-
-          <p>
-            The contents of the <ref table="DHCP_Option"/> table control the
-            DHCP option names and values that this action supports.
-          </p>
-
-          <p>
-            <b>Example:</b>
-            <code>
-              reg0[0] = put_dhcp_opts(offerip = 10.0.0.2, router = 10.0.0.1,
-              netmask = 255.255.255.0, dns_server = {8.8.8.8, 7.7.7.7});
-            </code>
-          </p>
-        </dd>
-
-        <dt>
-          <code><var>R</var> = put_dhcpv6_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: one or more DHCPv6 option/value pairs.
-          </p>
-
-          <p>
-            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
-          </p>
-
-          <p>
-            Valid only in the ingress pipeline.
-          </p>
-
-          <p>
-            When this action is applied to a DHCPv6 request packet, it changes
-            the packet into a DHCPv6 reply, replaces the options by those
-            specified as parameters, and stores 1 in <var>R</var>.
-          </p>
-
-          <p>
-            When this action is applied to a non-DHCPv6 packet or an invalid
-            DHCPv6 request packet , it leaves the packet unchanged and stores
-            0 in <var>R</var>.
-          </p>
-
-          <p>
-            The contents of the <ref table="DHCPv6_Options"/> table control the
-            DHCPv6 option names and values that this action supports.
-          </p>
-
-          <p>
-            <b>Example:</b>
-            <code>
-              reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02,
-              dns_server={ae70::1,ae70::2});
-            </code>
-          </p>
-        </dd>
-
-        <dt>
-          <code>set_queue(<var>queue_number</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: Queue number <var>queue_number</var>, in the range 0 to 61440.
-          </p>
-
-          <p>
-            This is a logical equivalent of the OpenFlow <code>set_queue</code>
-            action.  It affects packets that egress a hypervisor through a
-            physical interface.  For nonzero <var>queue_number</var>, it
-            configures packet queuing to match the settings configured for the
-            <ref table="Port_Binding"/> with
-            <code>options:qdisc_queue_id</code> matching
-            <var>queue_number</var>.  When <var>queue_number</var> is zero, it
-            resets queuing to the default strategy.
-          </p>
-
-          <p><b>Example:</b> <code>set_queue(10);</code></p>
-        </dd>
-
-        <dt><code>ct_lb;</code></dt>
-        <dt><code>ct_lb(</code><var>ip</var>[<code>:</code><var>port</var>]...<code>);</code></dt>
-        <dd>
-          <p>
-            With one or more arguments, <code>ct_lb</code> commits the packet
-            to the connection tracking table and DNATs the packet's destination
-            IP address (and port) to the IP address or addresses (and optional
-            ports) specified in the string.  If multiple comma-separated IP
-            addresses are specified, each is given equal weight for picking the
-            DNAT address.  Processing automatically moves on to the next table,
-            as if <code>next;</code> were specified, and later tables act on
-            the packet as modified by the connection tracker.  Connection
-            tracking state is scoped by the logical port when the action is
-            used in a flow for a logical switch, so overlapping
-            addresses may be used.  Connection tracking state is scoped by the
-            logical topology when the action is used in a flow for a router.
-          </p>
-          <p>
-            Without arguments, <code>ct_lb</code> sends the packet to the
-            connection tracking table to NAT the packets.  If the packet is
-            part of an established connection that was previously committed to
-            the connection tracker via <code>ct_lb(</code>...<code>)</code>, it
-            will automatically get DNATed to the same IP address as the first
-            packet in that connection.
-          </p>
-        </dd>
-
-        <dt>
-          <code><var>R</var> = dns_lookup();</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: No parameters.
-          </p>
-
-          <p>
-            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
-          </p>
-
-          <p>
-            Valid only in the ingress pipeline.
-          </p>
-
-          <p>
-            When this action is applied to a valid DNS request (a UDP packet
-            typically directed to port 53), it attempts to resolve the query
-            using the contents of the <ref table="DNS"/> table.  If it is
-            successful, it changes the packet into a DNS reply and stores 1 in
-            <var>R</var>.  If the action is applied to a non-DNS packet, an
-            invalid DNS request packet, or a valid DNS request for which the
-            <ref table="DNS"/> table does not supply an answer, it leaves the
-            packet unchanged and stores 0 in <var>R</var>.
-          </p>
-
-          <p>
-            Regardless of success, the action does not make any of the changes
-            to the flow that are necessary to direct the packet back to the
-            requester.  The logical pipeline can implement this behavior with
-            matches and actions in later tables.
-          </p>
-
-          <p>
-            <b>Example:</b>
-            <code>
-              reg0[3] = dns_lookup();
-            </code>
-          </p>
-
-          <p>
-            <b>Prerequisite:</b> <code>udp</code>
-          </p>
-        </dd>
-
-        <dt>
-          <code><var>R</var> = put_nd_ra_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            <b>Parameters</b>: The following IPv6 ND Router Advertisement
-               option/value pairs as defined in RFC 4861.
-
-            <ul>
-              <li>
-                <code>addr_mode</code>
-                <p>
-                  Mandatory parameter which specifies the address mode flag to
-                  be set in the RA flag options field. The value of this option
-                  is a string and the following values can be defined -
-                  "slaac", "dhcpv6_stateful" and "dhcpv6_stateless".
-                </p>
-              </li>
-
-              <li>
-                <code>slla</code>
-                <p>
-                  Mandatory parameter which specifies the link-layer address of
-                  the interface from which the Router Advertisement is sent.
-                </p>
-              </li>
-
-              <li>
-                <code>mtu</code>
-                <p>
-                  Optional parameter which specifies the MTU.
-                </p>
-              </li>
-
-              <li>
-                <code>prefix</code>
-                <p>
-                  Optional parameter which should be specified if the addr_mode
-                  is "slaac" or "dhcpv6_stateless". The value should be an IPv6
-                  prefix which will be used for stateless IPv6 address
-                  configuration. This option can be defined multiple times.
-                </p>
-              </li>
-            </ul>
-          </p>
-
-          <p>
-            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
-          </p>
-
-          <p>
-            Valid only in the ingress pipeline.
-          </p>
-
-          <p>
-            When this action is applied to an IPv6 Router solicitation request
-            packet, it changes the packet into an IPv6 Router Advertisement
-            reply and adds the options specified in the parameters, and stores
-            1 in <var>R</var>.
-          </p>
-
-          <p>
-            When this action is applied to a non-IPv6 Router solicitation
-            packet or an invalid IPv6 request packet , it leaves the packet
-            unchanged and stores 0 in <var>R</var>.
-          </p>
-
-          <p>
-            <b>Example:</b>
-            <code>
-              reg0[3] = put_nd_ra_opts(addr_mode = "slaac",
-              slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450);
-            </code>
-          </p>
-        </dd>
-
-        <dt><code>set_meter(<var>rate</var>);</code></dt>
-        <dt><code>set_meter(<var>rate</var>, <var>burst</var>);</code></dt>
-        <dd>
-          <p>
-            <b>Parameters</b>: rate limit int field <var>rate</var> in kbps,
-            burst rate limits int field <var>burst</var> in kbps.
-          </p>
-
-          <p>
-            This action sets the rate limit for a flow.
-          </p>
-
-          <p><b>Example:</b> <code>set_meter(100, 1000);</code></p>
-        </dd>
-
-        <dt><code><var>R</var> = check_pkt_larger(<var>L</var>)</code></dt>
-        <dd>
-          <p>
-            <b>Parameters</b>: packet length <var>L</var> to check for
-            in bytes.
-          </p>
-
-          <p>
-            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
-          </p>
-
-          <p>
-            This is a logical equivalent of the OpenFlow
-            <code>check_pkt_larger</code> action. If the packet is larger
-            than the length specified in <var>L</var>, it stores 1 in the
-            subfield <var>R</var>.
-          </p>
-
-          <p><b>Example: </b><code>reg0[6] = check_pkt_larger(1000);</code></p>
-        </dd>
-      </dl>
-
-      <dl>
-        <dt>
-          <code>log(<var>key</var>=<var>value</var>, </code>...<code>);</code>
-        </dt>
-
-        <dd>
-          <p>
-            Causes <code>ovn-controller</code> to log the packet on the chassis
-            that processes it.  Packet logging currently uses the same logging
-            mechanism as other Open vSwitch and OVN messages, which means that
-            whether and where log messages appear depends on the local logging
-            configuration that can be configured with <code>ovs-appctl</code>,
-            etc.
-          </p>
-          <p>
-            The <code>log</code> action takes zero or more of the following
-            key-value pair arguments that control what is logged:
-          </p>
-          <dl>
-            <dt><code>name=</code><var>string</var></dt>
-            <dd>
-              An optional name for the ACL.  The <var>string</var> is
-              currently limited to 64 bytes.
-            </dd>
-            <dt><code>severity=</code><var>level</var></dt>
-            <dd>
-              Indicates the severity of the event.  The <var>level</var> is one
-              of following (from more to less serious): <code>alert</code>,
-              <code>warning</code>, <code>notice</code>, <code>info</code>, or
-              <code>debug</code>.  If a severity is not provided, the default
-              is <code>info</code>.
-            </dd>
-            <dt><code>verdict=</code><var>value</var></dt>
-            <dd>
-              The verdict for packets matching the flow.  The value must be one
-              of <code>allow</code>, <code>deny</code>, or <code>reject</code>.
-            </dd>
-            <dt><code>meter=</code><var>string</var></dt>
-            <dd>
-              An optional rate-limiting meter to be applied to the logs.
-              The <var>string</var> should reference a
-              <ref column="name" table="Meter"/> entry from the
-              <ref table="Meter"/> table.  The only meter
-              <ref column="action" table="meter"/> that is appriopriate
-              is <code>drop</code>.
-            </dd>
-          </dl>
-        </dd>
-      </dl>
-
-      <dl>
-        <dt><code>icmp4 { <var>action</var>; </code>...<code> };</code></dt>
-        <dt>
-          <code>icmp4_error { <var>action</var>; </code>...<code> };</code>
-        </dt>
-        <dd>
-          <p>
-            Temporarily replaces the IPv4 packet being processed by an ICMPv4
-            packet and executes each nested <var>action</var> on the ICMPv4
-            packet.  Actions following these actions, if any,
-            apply to the original, unmodified packet.
-          </p>
-
-          <p>
-            The ICMPv4 packet that these actions operates on is initialized
-            based on the IPv4 packet being processed, as follows.  These are
-            default values that the nested actions will probably want to
-            change. Ethernet and IPv4 fields not listed here are not changed:
-          </p>
-
-          <ul>
-            <li><code>ip.proto = 1</code> (ICMPv4)</li>
-            <li><code>ip.frag = 0</code> (not a fragment)</li>
-            <li><code>ip.ttl = 255</code></li>
-            <li><code>icmp4.type = 3</code> (destination unreachable)</li>
-            <li><code>icmp4.code = 1</code> (host unreachable)</li>
-          </ul>
-
-          <p>
-              <code>icmp4_error</code> action is expected to be used to
-              generate an ICMPv4 packet in response to an error in original
-              IP packet. When this action generates the ICMPv4 packet, it
-              also copies the original IP datagram following the ICMPv4 header
-              as per RFC 1122: 3.2.2.
-          </p>
-          <p><b>Prerequisite:</b> <code>ip4</code></p>
-        </dd>
-
-        <dt><code>icmp6 { <var>action</var>; </code>...<code> };</code></dt>
-        <dd>
-          <p>
-            Temporarily replaces the IPv6 packet being processed by an ICMPv6
-            packet and executes each nested <var>action</var> on the ICMPv6
-            packet. Actions following the <var>icmp6</var> action, if any,
-            apply to the original, unmodified packet.
-          </p>
-
-          <p>
-            The ICMPv6 packet that this action operates on is initialized based
-            on the IPv6 packet being processed, as follows. These are default
-            values that the nested actions will probably want to change.
-            Ethernet and IPv6 fields not listed here are not changed:
-          </p>
-
-          <ul>
-            <li><code>ip.proto = 58</code> (ICMPv6)</li>
-            <li><code>ip.ttl = 255</code></li>
-            <li><code>icmp6.type = 1</code> (destination unreachable)</li>
-            <li><code>icmp6.code = 1</code> (administratively prohibited)</li>
-          </ul>
-
-          <p><b>Prerequisite:</b> <code>ip6</code></p>
-        </dd>
-
-        <dt><code>tcp_reset;</code></dt>
-        <dd>
-          <p>
-            This action transforms the current TCP packet according to the
-            following pseudocode:
-          </p>
-
-          <pre>
-if (tcp.ack) {
-        tcp.seq = tcp.ack;
-} else {
-        tcp.ack = tcp.seq + length(tcp.payload);
-        tcp.seq = 0;
-}
-tcp.flags = RST;
-</pre>
-
-          <p>
-            Then, the action drops all TCP options and payload data, and
-            updates the TCP checksum. IP ttl is set to 255.
-          </p>
-
-          <p><b>Prerequisite:</b> <code>tcp</code></p>
-        </dd>
-
-        <dt><code>trigger_event;</code></dt>
-        <dd>
-          <p>
-            This action is used to allow ovs-vswitchd to report CMS related
-            events writing them in <ref table="Controller_Event"/> table.
-            Supported event:
-          </p>
-
-          <ul>
-            <li>
-              <p>
-                <dfn>empty_lb_backends</dfn>. This event is raised if a
-                received packet is destined for a load balancer VIP that has
-                no configured backend destinations. For this event, the event
-                info includes the load balancer VIP, the load balancer UUID,
-                and the transport protocol.
-              </p>
-            </li>
-          </ul>
-        </dd>
-        <dt><code>igmp;</code></dt>
-        <dd>
-          <p>
-            This action sends the packet to <code>ovn-controller</code> for
-            multicast snooping.
-          </p>
-          <p><b>Prerequisite:</b> <code>igmp</code></p>
-        </dd>
-      </dl>
-    </column>
-
-    <column name="external_ids" key="stage-name">
-      Human-readable name for this flow's stage in the pipeline.
-    </column>
-
-    <column name="external_ids" key="stage-hint" type='{"type": "uuid"}'>
-      UUID of a <ref db="OVN_Northbound"/> record that caused this logical flow
-      to be created.  Currently used only for attribute of logical flows to
-      northbound <ref db="OVN_Northbound" table="ACL"/> records.
-    </column>
-
-    <column name="external_ids" key="source">
-      Source file and line number of the code that added this flow to the
-      pipeline.
-    </column>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-  </table>
-
-  <table name="Multicast_Group" title="Logical Port Multicast Groups">
-    <p>
-      The rows in this table define multicast groups of logical ports.
-      Multicast groups allow a single packet transmitted over a tunnel to a
-      hypervisor to be delivered to multiple VMs on that hypervisor, which
-      uses bandwidth more efficiently.
-    </p>
-
-    <p>
-      Each row in this table defines a logical multicast group numbered <ref
-      column="tunnel_key"/> within <ref column="datapath"/>, whose logical
-      ports are listed in the <ref column="ports"/> column.
-    </p>
-
-    <column name="datapath">
-      The logical datapath in which the multicast group resides.
-    </column>
-
-    <column name="tunnel_key">
-      The value used to designate this logical egress port in tunnel
-      encapsulations.  An index forces the key to be unique within the <ref
-      column="datapath"/>.  The unusual range ensures that multicast group IDs
-      do not overlap with logical port IDs.
-    </column>
-
-    <column name="name">
-      <p>
-        The logical multicast group's name.  An index forces the name to be
-        unique within the <ref column="datapath"/>.  Logical flows in the
-        ingress pipeline may output to the group just as for individual logical
-        ports, by assigning the group's name to <code>outport</code> and
-        executing an <code>output</code> action.
-      </p>
-
-      <p>
-        Multicast group names and logical port names share a single namespace
-        and thus should not overlap (but the database schema cannot enforce
-        this).  To try to avoid conflicts, <code>ovn-northd</code> uses names
-        that begin with <code>_MC_</code>.
-      </p>
-    </column>
-
-    <column name="ports">
-      The logical ports included in the multicast group.  All of these ports
-      must be in the <ref column="datapath"/> logical datapath (but the
-      database schema cannot enforce this).
-    </column>
-  </table>
-
-  <table name="Meter" title="Meter entry">
-    <p>
-      Each row in this table represents a meter that can be used for QoS or
-      rate-limiting.
-    </p>
-
-    <column name="name">
-      <p>
-        A name for this meter.
-      </p>
-
-      <p>
-        Names that begin with "__" (two underscores) are reserved for
-        OVN internal use and should not be added manually.
-      </p>
-    </column>
-
-    <column name="unit">
-      <p>
-        The unit for <ref column="rate" table="Meter_Band"/> and
-        <ref column="burst_rate" table="Meter_Band"/> parameters in
-        the <ref column="bands"/> entry.  <code>kbps</code> specifies
-        kilobits per second, and <code>pktps</code> specifies packets
-        per second.
-      </p>
-    </column>
-
-    <column name="bands">
-      <p>
-        The bands associated with this meter.  Each band specifies a
-        rate above which the band is to take the action
-        <code>action</code>.  If multiple bands' rates are exceeded,
-        then the band with the highest rate among the exceeded bands is
-        selected.
-      </p>
-    </column>
-  </table>
-
-  <table name="Meter_Band" title="Band for meter entries">
-    <p>
-      Each row in this table represents a meter band which specifies the
-      rate above which the configured action should be applied.  These bands
-      are referenced by the <ref column="bands" table="Meter"/> column in
-      the <ref table="Meter"/> table.
-    </p>
-
-    <column name="action">
-      <p>
-        The action to execute when this band matches.  The only supported
-        action is <code>drop</code>.
-      </p>
-    </column>
-
-    <column name="rate">
-      <p>
-        The rate limit for this band, in kilobits per second or bits per
-        second, depending on whether the parent <ref table="Meter"/>
-        entry's <ref column="unit" table="Meter"/> column specified
-        <code>kbps</code> or <code>pktps</code>.
-      </p>
-    </column>
-
-    <column name="burst_size">
-      <p>
-        The maximum burst allowed for the band in kilobits or packets,
-        depending on whether <code>kbps</code> or <code>pktps</code> was
-        selected in the parent <ref table="Meter"/> entry's
-        <ref column="unit" table="Meter"/> column.  If the size is zero,
-        the switch is free to select some reasonable value depending on
-        its configuration.
-      </p>
-    </column>
-  </table>
-
-  <table name="Datapath_Binding" title="Physical-Logical Datapath Bindings">
-    <p>
-      Each row in this table represents a logical datapath, which implements a
-      logical pipeline among the ports in the <ref table="Port_Binding"/> table
-      associated with it.  In practice, the pipeline in a given logical
-      datapath implements either a logical switch or a logical router.
-    </p>
-
-    <p>
-      The main purpose of a row in this table is provide a physical binding for
-      a logical datapath.  A logical datapath does not have a physical
-      location, so its physical binding information is limited: just <ref
-      column="tunnel_key"/>.  The rest of the data in this table does not
-      affect packet forwarding.
-    </p>
-
-    <column name="tunnel_key">
-      The tunnel key value to which the logical datapath is bound.
-      The <code>Tunnel Encapsulation</code> section in
-      <code>ovn-architecture</code>(7) describes how tunnel keys are
-      constructed for each supported encapsulation.
-    </column>
-
-    <group title="OVN_Northbound Relationship">
-      <p>
-        Each row in <ref table="Datapath_Binding"/> is associated with some
-        logical datapath.  <code>ovn-northd</code> uses these keys to track the
-        association of a logical datapath with concepts in the <ref
-        db="OVN_Northbound"/> database.
-      </p>
-
-      <column name="external_ids" key="logical-switch" type='{"type": "uuid"}'>
-        For a logical datapath that represents a logical switch,
-        <code>ovn-northd</code> stores in this key the UUID of the
-        corresponding <ref table="Logical_Switch" db="OVN_Northbound"/> row in
-        the <ref db="OVN_Northbound"/> database.
-      </column>
-
-      <column name="external_ids" key="logical-router" type='{"type": "uuid"}'>
-        For a logical datapath that represents a logical router,
-        <code>ovn-northd</code> stores in this key the UUID of the
-        corresponding <ref table="Logical_Router" db="OVN_Northbound"/> row in
-        the <ref db="OVN_Northbound"/> database.
-      </column>
-
-      <group title="Naming">
-        <p>
-          <code>ovn-northd</code> copies these from the name fields in the <ref
-          db="OVN_Northbound"/> database, either from <ref
-          table="Logical_Router" db="OVN_Northbound" column="name"/> and <ref
-          table="Logical_Router" db="OVN_Northbound" column="external_ids"
-          key="neutron:router_name"/> in the <ref table="Logical_Router"
-          db="OVN_Northbound"/> table or from <ref table="Logical_Switch"
-          db="OVN_Northbound" column="name"/> and <ref table="Logical_Switch"
-          db="OVN_Northbound" column="external_ids"
-          key="neutron:network_name"/> in the <ref table="Logical_Switch"
-          db="OVN_Northbound"/> table.
-        </p>
-
-        <column name="external_ids" key="name">
-          A name for the logical datapath.
-        </column>
-
-        <column name="external_ids" key="name2">
-          Another name for the logical datapath.
-        </column>
-      </group>
-    </group>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-  </table>
-
-  <table name="Port_Binding" title="Physical-Logical Port Bindings">
-    <p>
-      Each row in this table binds a logical port to a realization.  For most
-      logical ports, this means binding to some physical location, for example
-      by binding a logical port to a VIF that belongs to a VM running on a
-      particular hypervisor.  Other logical ports, such as logical patch ports,
-      can be realized without a specific physical location, but their bindings
-      are still expressed through rows in this table.
-    </p>
-
-    <p>
-      For every <code>Logical_Switch_Port</code> record in
-      <code>OVN_Northbound</code> database, <code>ovn-northd</code>
-      creates a record in this table.  <code>ovn-northd</code> populates
-      and maintains every column except the <code>chassis</code> column,
-      which it leaves empty in new records.
-    </p>
-
-    <p>
-      <code>ovn-controller</code>/<code>ovn-controller-vtep</code>
-      populates the <code>chassis</code> column for the records that
-      identify the logical ports that are located on its hypervisor/gateway,
-      which <code>ovn-controller</code>/<code>ovn-controller-vtep</code> in
-      turn finds out by monitoring the local hypervisor's Open_vSwitch
-      database, which identifies logical ports via the conventions described
-      in <code>IntegrationGuide.rst</code>.  (The exceptions are for
-      <code>Port_Binding</code> records with <code>type</code> of
-      <code>l3gateway</code>, whose locations are identified by
-      <code>ovn-northd</code> via the <code>options:l3gateway-chassis</code>
-      column in this table.  <code>ovn-controller</code> is still responsible
-      to populate the <code>chassis</code> column.)
-    </p>
-
-    <p>
-      When a chassis shuts down gracefully, it should clean up the
-      <code>chassis</code> column that it previously had populated.
-      (This is not critical because resources hosted on the chassis are equally
-      unreachable regardless of whether their rows are present.)  To handle the
-      case where a VM is shut down abruptly on one chassis, then brought up
-      again on a different one,
-      <code>ovn-controller</code>/<code>ovn-controller-vtep</code> must
-      overwrite the <code>chassis</code> column with new information.
-    </p>
-
-    <group title="Core Features">
-      <column name="datapath">
-        The logical datapath to which the logical port belongs.
-      </column>
-
-      <column name="logical_port">
-        A logical port, taken from <ref table="Logical_Switch_Port"
-        column="name" db="OVN_Northbound"/> in the OVN_Northbound
-        database's <ref table="Logical_Switch_Port" db="OVN_Northbound"/>
-        table.  OVN does not prescribe a particular format for the
-        logical port ID.
-      </column>
-
-      <column name="encap">
-        Points to supported encapsulation configurations to transmit
-        logical dataplane packets to this chassis.  Each entry is a <ref
-        table="Encap"/> record that describes the configuration.
-      </column>
-
-      <column name="chassis">
-        The meaning of this column depends on the value of the <ref column="type"/>
-        column.  This is the meaning for each <ref column="type"/>
-
-        <dl>
-          <dt>(empty string)</dt>
-          <dd>
-            The physical location of the logical port.  To successfully identify a
-            chassis, this column must be a <ref table="Chassis"/> record.  This is
-            populated by <code>ovn-controller</code>.
-          </dd>
-
-          <dt>vtep</dt>
-          <dd>
-            The physical location of the hardware_vtep gateway.  To successfully
-            identify a chassis, this column must be a <ref table="Chassis"/> record.
-            This is populated by <code>ovn-controller-vtep</code>.
-          </dd>
-
-          <dt>localnet</dt>
-          <dd>
-            Always empty.  A localnet port is realized on every chassis that has
-            connectivity to the corresponding physical network.
-          </dd>
-
-          <dt>localport</dt>
-          <dd>
-            Always empty.  A localport port is present on every chassis.
-          </dd>
-
-          <dt>l3gateway</dt>
-          <dd>
-            The physical location of the L3 gateway.  To successfully identify a
-            chassis, this column must be a <ref table="Chassis"/> record.  This is
-            populated by <code>ovn-controller</code> based on the value of
-            the <code>options:l3gateway-chassis</code> column in this table.
-          </dd>
-
-          <dt>l2gateway</dt>
-          <dd>
-            The physical location of this L2 gateway.  To successfully identify a
-            chassis, this column must be a <ref table="Chassis"/> record.
-            This is populated by <code>ovn-controller</code> based on the value
-            of the <code>options:l2gateway-chassis</code> column in this table.
-          </dd>
-        </dl>
-
-      </column>
-
-      <column name="gateway_chassis">
-        <p>
-          A list of <ref table="Gateway_Chassis"/>.
-        </p>
-        <p>
-          This should only be populated for ports with
-          <ref column="type"/> set to <code>chassisredirect</code>.
-          This column defines the list of chassis used as gateways where
-          traffic will be redirected through.
-        </p>
-      </column>
-
-      <column name="ha_chassis_group">
-        <p>
-          This should only be populated for ports with
-          <ref column="type"/> set to <code>chassisredirect</code>.
-          This column defines the HA chassis group with a list of
-          HA chassis used as gateways where traffic will be redirected
-          through.
-        </p>
-      </column>
-
-      <column name="tunnel_key">
-        <p>
-          A number that represents the logical port in the key (e.g. STT key or
-          Geneve TLV) field carried within tunnel protocol packets.
-        </p>
-
-        <p>
-          The tunnel ID must be unique within the scope of a logical datapath.
-        </p>
-      </column>
-
-      <column name="mac">
-        <p>
-          The Ethernet address or addresses used as a source address on the
-          logical port, each in the form
-          <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>.
-          The string <code>unknown</code> is also allowed to indicate that the
-          logical port has an unknown set of (additional) source addresses.
-        </p>
-
-        <p>
-          A VM interface would ordinarily have a single Ethernet address.  A
-          gateway port might initially only have <code>unknown</code>, and then
-          add MAC addresses to the set as it learns new source addresses.
-        </p>
-      </column>
-
-      <column name="type">
-        <p>
-          A type for this logical port.  Logical ports can be used to model other
-          types of connectivity into an OVN logical switch.  The following types
-          are defined:
-        </p>
-
-        <dl>
-          <dt>(empty string)</dt>
-          <dd>VM (or VIF) interface.</dd>
-
-          <dt><code>patch</code></dt>
-          <dd>
-            One of a pair of logical ports that act as if connected by a patch
-            cable.  Useful for connecting two logical datapaths, e.g. to connect
-            a logical router to a logical switch or to another logical router.
-          </dd>
-
-          <dt><code>l3gateway</code></dt>
-          <dd>
-            One of a pair of logical ports that act as if connected by a patch
-            cable across multiple chassis.  Useful for connecting a logical
-            switch with a Gateway router (which is only resident on a
-            particular chassis).
-          </dd>
-
-          <dt><code>localnet</code></dt>
-          <dd>
-            A connection to a locally accessible network from each
-            <code>ovn-controller</code> instance.  A logical switch can only
-            have a single <code>localnet</code> port attached.  This is used
-            to model direct connectivity to an existing network.
-          </dd>
-
-          <dt><code>localport</code></dt>
-          <dd>
-            A connection to a local VIF. Traffic that arrives on a
-            <code>localport</code> is never forwarded over a tunnel to another
-            chassis. These ports are present on every chassis and have the same
-            address in all of them. This is used to model connectivity to local
-            services that run on every hypervisor.
-          </dd>
-
-          <dt><code>l2gateway</code></dt>
-          <dd>
-            An L2 connection to a physical network.  The chassis this
-            <ref table="Port_Binding"/> is bound to will serve as
-            an L2 gateway to the network named by
-            <ref column="options" table="Port_Binding"/>:<code>network_name</code>.
-          </dd>
-
-          <dt><code>vtep</code></dt>
-          <dd>
-            A port to a logical switch on a VTEP gateway chassis.  In order to
-            get this port correctly recognized by the OVN controller, the <ref
-            column="options"
-            table="Port_Binding"/>:<code>vtep-physical-switch</code> and <ref
-            column="options"
-            table="Port_Binding"/>:<code>vtep-logical-switch</code> must also
-            be defined.
-          </dd>
-
-          <dt><code>chassisredirect</code></dt>
-          <dd>
-            A logical port that represents a particular instance, bound
-            to a specific chassis, of an otherwise distributed parent
-            port (e.g. of type <code>patch</code>).  A
-            <code>chassisredirect</code> port should never be used as an
-            <code>inport</code>.  When an ingress pipeline sets the
-            <code>outport</code>, it may set the value to a logical port
-            of type <code>chassisredirect</code>.  This will cause the
-            packet to be directed to a specific chassis to carry out the
-            egress pipeline.  At the beginning of the egress pipeline,
-            the <code>outport</code> will be reset to the value of the
-            distributed port.
-          </dd>
-        </dl>
-      </column>
-    </group>
-
-    <group title="Patch Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> of
-        <code>patch</code>.
-      </p>
-
-      <column name="options" key="peer">
-        The <ref column="logical_port"/> in the <ref table="Port_Binding"/>
-        record for the other side of the patch.  The named <ref
-        column="logical_port"/> must specify this <ref column="logical_port"/>
-        in its own <code>peer</code> option.  That is, the two patch logical
-        ports must have reversed <ref column="logical_port"/> and
-        <code>peer</code> values.
-      </column>
-
-      <column name="nat_addresses">
-        MAC address followed by a list of SNAT and DNAT external IP
-        addresses, followed by
-        <code>is_chassis_resident("<var>lport</var>")</code>, where
-        <var>lport</var> is the name of a logical port on the same chassis
-        where the corresponding NAT rules are applied.  This is used to
-        send gratuitous ARPs for SNAT and DNAT external IP addresses via
-        <code>localnet</code>, from the chassis where <var>lport</var>
-        resides.  Example: <code>80:fa:5b:06:72:b7 158.36.44.22
-        158.36.44.24 is_chassis_resident("foo1")</code>.  This would result
-        in generation of gratuitous ARPs for IP addresses 158.36.44.22 and
-        158.36.44.24 with a MAC address of 80:fa:5b:06:72:b7 from the chassis
-        where the logical port "foo1" resides.
-      </column>
-    </group>
-
-    <group title="L3 Gateway Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> of
-        <code>l3gateway</code>.
-      </p>
-
-      <column name="options" key="peer">
-        The <ref column="logical_port"/> in the <ref table="Port_Binding"/>
-        record for the other side of the 'l3gateway' port.  The named <ref
-        column="logical_port"/> must specify this <ref column="logical_port"/>
-        in its own <code>peer</code> option.  That is, the two 'l3gateway'
-        logical ports must have reversed <ref column="logical_port"/> and
-        <code>peer</code> values.
-      </column>
-
-      <column name="options" key="l3gateway-chassis">
-        The <code>chassis</code> in which the port resides.
-      </column>
-
-      <column name="options" key="nat-addresses">
-        MAC address of the <code>l3gateway</code> port followed by a list of
-        SNAT and DNAT external IP addresses.  This is used to send gratuitous
-        ARPs for SNAT and DNAT external IP addresses via <code>localnet</code>.
-        Example: <code>80:fa:5b:06:72:b7 158.36.44.22 158.36.44.24</code>.
-        This would result in generation of gratuitous ARPs for IP addresses
-        158.36.44.22 and 158.36.44.24 with a MAC address of 80:fa:5b:06:72:b7.
-        This is used in OVS versions prior to 2.8.
-      </column>
-
-      <column name="nat_addresses">
-        MAC address of the <code>l3gateway</code> port followed by a list of
-        SNAT and DNAT external IP addresses.  This is used to send gratuitous
-        ARPs for SNAT and DNAT external IP addresses via <code>localnet</code>.
-        Example: <code>80:fa:5b:06:72:b7 158.36.44.22 158.36.44.24</code>.
-        This would result in generation of gratuitous ARPs for IP addresses
-        158.36.44.22 and 158.36.44.24 with a MAC address of 80:fa:5b:06:72:b7.
-        This is used in OVS version 2.8 and later versions.
-      </column>
-    </group>
-
-    <group title="Localnet Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> of
-        <code>localnet</code>.
-      </p>
-
-      <column name="options" key="network_name">
-        Required.  <code>ovn-controller</code> uses the configuration entry
-        <code>ovn-bridge-mappings</code> to determine how to connect to this
-        network.  <code>ovn-bridge-mappings</code> is a list of network names
-        mapped to a local OVS bridge that provides access to that network.  An
-        example of configuring <code>ovn-bridge-mappings</code> would be:
-
-        <pre>$ ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1</pre>
-
-        <p>
-          When a logical switch has a <code>localnet</code> port attached,
-          every chassis that may have a local vif attached to that logical
-          switch must have a bridge mapping configured to reach that
-          <code>localnet</code>.  Traffic that arrives on a
-          <code>localnet</code> port is never forwarded over a tunnel to
-          another chassis.
-        </p>
-      </column>
-
-      <column name="tag">
-        If set, indicates that the port represents a connection to a specific
-        VLAN on a locally accessible network. The VLAN ID is used to match
-        incoming traffic and is also added to outgoing traffic.
-      </column>
-    </group>
-
-    <group title="L2 Gateway Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> of
-        <code>l2gateway</code>.
-      </p>
-
-      <column name="options" key="network_name">
-        Required.  <code>ovn-controller</code> uses the configuration entry
-        <code>ovn-bridge-mappings</code> to determine how to connect to this
-        network.  <code>ovn-bridge-mappings</code> is a list of network names
-        mapped to a local OVS bridge that provides access to that network.  An
-        example of configuring <code>ovn-bridge-mappings</code> would be:
-
-        <pre>$ ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1</pre>
-
-        <p>
-          When a logical switch has a <code>l2gateway</code> port attached,
-          the chassis that the <code>l2gateway</code> port is bound to
-          must have a bridge mapping configured to reach the network
-          identified by <code>network_name</code>.
-        </p>
-      </column>
-
-      <column name="options" key="l2gateway-chassis">
-        Required. The <code>chassis</code> in which the port resides.
-      </column>
-
-      <column name="tag">
-        If set, indicates that the gateway is connected to a specific
-        VLAN on the physical network. The VLAN ID is used to match
-        incoming traffic and is also added to outgoing traffic.
-      </column>
-    </group>
-
-    <group title="VTEP Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> of
-        <code>vtep</code>.
-      </p>
-
-      <column name="options" key="vtep-physical-switch">
-        Required. The name of the VTEP gateway.
-      </column>
-
-      <column name="options" key="vtep-logical-switch">
-        Required.  A logical switch name connected by the VTEP gateway.  Must
-        be set when <ref column="type"/> is <code>vtep</code>.
-      </column>
-    </group>
-
-    <group title="VMI (or VIF) Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/> having
-        (empty string)
-      </p>
-
-      <column name="options" key="requested-chassis">
-        If set, identifies a specific chassis (by name or hostname) that
-        is allowed to bind this port. Using this option will prevent
-        thrashing between two chassis trying to bind the same port during
-        a live migration. It can also prevent similar thrashing due to a
-        mis-configuration, if a port is accidentally created on more than
-        one chassis.
-      </column>
-
-      <column name="options" key="qos_max_rate">
-        If set, indicates the maximum rate for data sent from this interface,
-        in bit/s. The traffic will be shaped according to this limit.
-      </column>
-
-      <column name="options" key="qos_burst">
-        If set, indicates the maximum burst size for data sent from this
-        interface, in bits.
-      </column>
-
-      <column name="options" key="qdisc_queue_id"
-              type='{"type": "integer", "minInteger": 1, "maxInteger": 61440}'>
-        Indicates the queue number on the physical device. This is same as the
-        <code>queue_id</code> used in OpenFlow in <code>struct
-        ofp_action_enqueue</code>.
-      </column>
-    </group>
-
-    <group title="Chassis Redirect Options">
-      <p>
-        These options apply to logical ports with <ref column="type"/>
-        of <code>chassisredirect</code>.
-      </p>
-
-      <column name="options" key="distributed-port">
-        The name of the distributed port for which this
-        <code>chassisredirect</code> port represents a particular instance.
-      </column>
-
-      <column name="options" key="redirect-chassis">
-        The <code>chassis</code> that this <code>chassisredirect</code> port
-        is bound to.  This is taken from <ref table="Logical_Router_Port"
-        column="options" key="redirect-chassis" db="OVN_Northbound"/>
-        in the OVN_Northbound database's <ref table="Logical_Router_Port"
-        db="OVN_Northbound"/> table.
-      </column>
-    </group>
-
-    <group title="Nested Containers">
-      <p>
-        These columns support containers nested within a VM.  Specifically,
-        they are used when <ref column="type"/> is empty and <ref
-        column="logical_port"/> identifies the interface of a container spawned
-        inside a VM.  They are empty for containers or VMs that run directly on
-        a hypervisor.
-      </p>
-
-      <column name="parent_port">
-        This is taken from
-        <ref table="Logical_Switch_Port" column="parent_name"
-        db="OVN_Northbound"/> in the OVN_Northbound database's
-        <ref table="Logical_Switch_Port" db="OVN_Northbound"/> table.
-      </column>
-
-      <column name="tag">
-        <p>
-          Identifies the VLAN tag in the network traffic associated with that
-          container's network interface.
-        </p>
-
-        <p>
-          This column is used for a different purpose when <ref column="type"/>
-          is <code>localnet</code> (see <code>Localnet Options</code>, above)
-          or <code>l2gateway</code> (see <code>L2 Gateway Options</code>, above).
-        </p>
-      </column>
-    </group>
-
-    <group title="Naming">
-      <column name="external_ids" key="name">
-        <p>
-          For a logical switch port, <code>ovn-northd</code> copies this from
-          <ref table="Logical_Switch_Port" db="OVN_Northbound"
-          column="external_ids" key="neutron:port_name"/> in the <ref
-          table="Logical_Switch_Port" db="OVN_Northbound"/> table in the
-          OVN_Northbound database, if it is a nonempty string.
-        </p>
-
-        <p>
-          For a logical switch port, <code>ovn-northd</code> does not currently
-          set this key.
-        </p>
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        <p>
-          See <em>External IDs</em> at the beginning of this document.
-        </p>
-
-        <p>
-          The <code>ovn-northd</code> program populates this column with
-          all entries into the <ref column="external_ids"/> column of the
-          <ref table="Logical_Switch_Port"/> table of the
-          <ref db="OVN_Northbound"/> database.
-        </p>
-      </column>
-    </group>
-  </table>
-
-  <table name="MAC_Binding" title="IP to MAC bindings">
-    <p>
-      Each row in this table specifies a binding from an IP address to an
-      Ethernet address that has been discovered through ARP (for IPv4) or
-      neighbor discovery (for IPv6).  This table is primarily used to discover
-      bindings on physical networks, because IP-to-MAC bindings for virtual
-      machines are usually populated statically into the <ref
-      table="Port_Binding"/> table.
-    </p>
-
-    <p>
-      This table expresses a functional relationship: <ref
-      table="MAC_Binding"/>(<ref column="logical_port"/>, <ref column="ip"/>) =
-      <ref column="mac"/>.
-    </p>
-
-    <p>
-      In outline, the lifetime of a logical router's MAC binding looks like
-      this:
-    </p>
-
-    <ol>
-      <li>
-        On hypervisor 1, a logical router determines that a packet should be
-        forwarded to IP address <var>A</var> on one of its router ports.  It
-        uses its logical flow table to determine that <var>A</var> lacks a
-        static IP-to-MAC binding and the <code>get_arp</code> action to
-        determine that it lacks a dynamic IP-to-MAC binding.
-      </li>
-
-      <li>
-        Using an OVN logical <code>arp</code> action, the logical router
-        generates and sends a broadcast ARP request to the router port.  It
-        drops the IP packet.
-      </li>
-
-      <li>
-        The logical switch attached to the router port delivers the ARP request
-        to all of its ports.  (It might make sense to deliver it only to ports
-        that have no static IP-to-MAC bindings, but this could also be
-        surprising behavior.)
-      </li>
-
-      <li>
-        A host or VM on hypervisor 2 (which might be the same as hypervisor 1)
-        attached to the logical switch owns the IP address in question.  It
-        composes an ARP reply and unicasts it to the logical router port's
-        Ethernet address.
-      </li>
-
-      <li>
-        The logical switch delivers the ARP reply to the logical router port.
-      </li>
-
-      <li>
-        The logical router flow table executes a <code>put_arp</code> action.
-        To record the IP-to-MAC binding, <code>ovn-controller</code> adds a row
-        to the <ref table="MAC_Binding"/> table.
-      </li>
-
-      <li>
-        On hypervisor 1, <code>ovn-controller</code> receives the updated <ref
-        table="MAC_Binding"/> table from the OVN southbound database.  The next
-        packet destined to <var>A</var> through the logical router is sent
-        directly to the bound Ethernet address.
-      </li>
-    </ol>
-
-    <column name="logical_port">
-      The logical port on which the binding was discovered.
-    </column>
-
-    <column name="ip">
-      The bound IP address.
-    </column>
-
-    <column name="mac">
-      The Ethernet address to which the IP is bound.
-    </column>
-    <column name="datapath">
-      The logical datapath to which the logical port belongs.
-    </column>
-  </table>
-
-  <table name="DHCP_Options" title="DHCP Options supported by native OVN DHCP">
-    <p>
-      Each row in this table stores the DHCP Options supported by native OVN
-      DHCP. <code>ovn-northd</code> populates this table with the supported
-      DHCP options. <code>ovn-controller</code> looks up this table to get the
-      DHCP codes of the DHCP options defined in the "put_dhcp_opts" action.
-      Please refer to the RFC 2132 <code>"https://tools.ietf.org/html/rfc2132"</code>
-      for the possible list of DHCP options that can be defined here.
-    </p>
-
-    <column name="name">
-      <p>
-        Name of the DHCP option.
-      </p>
-
-      <p>
-        Example. name="router"
-      </p>
-    </column>
-
-    <column name="code">
-      <p>
-        DHCP option code for the DHCP option as defined in the RFC 2132.
-      </p>
-
-      <p>
-        Example. code=3
-      </p>
-    </column>
-
-    <column name="type">
-      <p>
-        Data type of the DHCP option code.
-      </p>
-
-      <dl>
-        <dt><code>value: bool</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is a bool.
-          </p>
-
-          <p>
-            Example. "name=ip_forward_enable", "code=19", "type=bool".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., ip_forward_enable = 1,...)
-          </p>
-        </dd>
-
-        <dt><code>value: uint8</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is an unsigned
-            int8 (8 bits)
-          </p>
-
-          <p>
-            Example. "name=default_ttl", "code=23", "type=uint8".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., default_ttl = 50,...)
-          </p>
-        </dd>
-
-        <dt><code>value: uint16</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is an unsigned
-            int16 (16 bits).
-          </p>
-
-          <p>
-            Example. "name=mtu", "code=26", "type=uint16".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., mtu = 1450,...)
-          </p>
-        </dd>
-
-        <dt><code>value: uint32</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is an unsigned
-            int32 (32 bits).
-          </p>
-
-          <p>
-            Example. "name=lease_time", "code=51", "type=uint32".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., lease_time = 86400,...)
-          </p>
-        </dd>
-
-        <dt><code>value: ipv4</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is an IPv4
-            address or addresses.
-          </p>
-
-          <p>
-            Example. "name=router", "code=3", "type=ipv4".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., router = 10.0.0.1,...)
-          </p>
-
-          <p>
-            Example. "name=dns_server", "code=6", "type=ipv4".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., dns_server = {8.8.8.8 7.7.7.7},...)
-          </p>
-        </dd>
-
-        <dt><code>value: static_routes</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option contains a pair of
-            IPv4 route and next hop addresses.
-          </p>
-
-          <p>
-            Example. "name=classless_static_route", "code=121", "type=static_routes".
-          </p>
-
-          <p>
-            put_dhcp_opts(..., classless_static_route = {30.0.0.0/24,10.0.0.4,0.0.0.0/0,10.0.0.1}...)
-          </p>
-        </dd>
-
-        <dt><code>value: str</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCP option is a string.
-          </p>
-
-          <p>
-            Example. "name=host_name", "code=12", "type=str".
-          </p>
-        </dd>
-      </dl>
-    </column>
-  </table>
-
-  <table name="DHCPv6_Options" title="DHCPv6 Options supported by native OVN DHCPv6">
-    <p>
-      Each row in this table stores the DHCPv6 Options supported by native OVN
-      DHCPv6. <code>ovn-northd</code> populates this table with the supported
-      DHCPv6 options. <code>ovn-controller</code> looks up this table to get
-      the DHCPv6 codes of the DHCPv6 options defined in the
-      <code>put_dhcpv6_opts</code> action. Please refer to RFC 3315 and RFC
-      3646 for the list of DHCPv6 options that can be defined here.
-    </p>
-
-    <column name="name">
-      <p>
-        Name of the DHCPv6 option.
-      </p>
-
-      <p>
-        Example. name="ia_addr"
-      </p>
-    </column>
-
-    <column name="code">
-      <p>
-        DHCPv6 option code for the DHCPv6 option as defined in the appropriate
-        RFC.
-      </p>
-
-      <p>
-        Example. code=3
-      </p>
-    </column>
-
-    <column name="type">
-      <p>
-        Data type of the DHCPv6 option code.
-      </p>
-
-      <dl>
-        <dt><code>value: ipv6</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCPv6 option is an IPv6
-            address(es).
-          </p>
-
-          <p>
-            Example. "name=ia_addr", "code=5", "type=ipv6".
-          </p>
-
-          <p>
-            put_dhcpv6_opts(..., ia_addr = ae70::4,...)
-          </p>
-        </dd>
-
-        <dt><code>value: str</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCPv6 option is a string.
-          </p>
-
-          <p>
-            Example. "name=domain_search", "code=24", "type=str".
-          </p>
-
-          <p>
-            put_dhcpv6_opts(..., domain_search = ovn.domain,...)
-          </p>
-        </dd>
-
-        <dt><code>value: mac</code></dt>
-        <dd>
-          <p>
-            This indicates that the value of the DHCPv6 option is a MAC address.
-          </p>
-
-          <p>
-            Example. "name=server_id", "code=2", "type=mac".
-          </p>
-
-          <p>
-            put_dhcpv6_opts(..., server_id = 01:02:03:04L05:06,...)
-          </p>
-        </dd>
-      </dl>
-    </column>
-  </table>
-  <table name="Connection" title="OVSDB client connections.">
-    <p>
-      Configuration for a database connection to an Open vSwitch database
-      (OVSDB) client.
-    </p>
-
-    <p>
-      This table primarily configures the Open vSwitch database server
-      (<code>ovsdb-server</code>).
-    </p>
-
-    <p>
-      The Open vSwitch database server can initiate and maintain active
-      connections to remote clients.  It can also listen for database
-      connections.
-    </p>
-
-    <group title="Core Features">
-      <column name="target">
-        <p>Connection methods for clients.</p>
-        <p>
-          The following connection methods are currently supported:
-        </p>
-        <dl>
-          <dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
-          <dd>
-            <p>
-              The specified SSL <var>port</var> on the given <var>host</var>,
-              which can either be a DNS name (if built with unbound library) or
-              an IP address.  A valid SSL configuration must be provided when
-              this form is used, this configuration can be specified via
-              command-line options or the <ref table="SSL"/> table.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-            <p>
-              SSL support is an optional feature that is not always
-              built as part of Open vSwitch.
-            </p>
-          </dd>
-
-          <dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
-          <dd>
-            <p>
-              The specified TCP <var>port</var> on the given <var>host</var>,
-              which can either be a DNS name (if built with unbound library) or
-              an IP address (IPv4 or IPv6).  If <var>host</var> is an IPv6
-              address, wrap it in square brackets, e.g. <code>tcp:[::1]:6640</code>.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-          </dd>
-          <dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
-          <dd>
-            <p>
-              Listens for SSL connections on the specified TCP <var>port</var>.
-              Specify 0 for <var>port</var> to have the kernel automatically
-              choose an available port.  If <var>host</var>, which can either
-              be a DNS name (if built with unbound library) or an IP address,
-              is specified, then connections are restricted to the resolved or
-              specified local IP address (either IPv4 or IPv6 address).  If
-              <var>host</var> is an IPv6 address, wrap in square brackets,
-              e.g. <code>pssl:6640:[::1]</code>.  If <var>host</var> is not
-              specified then it listens only on IPv4 (but not IPv6) addresses.
-              A valid SSL configuration must be provided when this form is used,
-              this can be specified either via command-line options or the
-              <ref table="SSL"/> table.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-            <p>
-              SSL support is an optional feature that is not always built as
-              part of Open vSwitch.
-            </p>
-          </dd>
-          <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
-          <dd>
-            <p>
-              Listens for connections on the specified TCP <var>port</var>.
-              Specify 0 for <var>port</var> to have the kernel automatically
-              choose an available port.  If <var>host</var>, which can either
-              be a DNS name (if built with unbound library) or an IP address,
-              is specified, then connections are restricted to the resolved or
-              specified local IP address (either IPv4 or IPv6 address).  If
-              <var>host</var> is an IPv6 address, wrap it in square brackets,
-              e.g. <code>ptcp:6640:[::1]</code>.  If <var>host</var> is not
-              specified then it listens only on IPv4 addresses.
-            </p>
-            <p>
-              If <var>port</var> is not specified, it defaults to 6640.
-            </p>
-          </dd>
-        </dl>
-        <p>When multiple clients are configured, the <ref column="target"/>
-        values must be unique.  Duplicate <ref column="target"/> values yield
-        unspecified results.</p>
-      </column>
-
-      <column name="read_only">
-        <code>true</code> to restrict these connections to read-only
-        transactions, <code>false</code> to allow them to modify the database.
-      </column>
-      <column name="role">
-        String containing role name for this connection entry.
-      </column>
-    </group>
-
-    <group title="Client 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 the client
-        before sending an inactivity probe message.  If Open vSwitch does not
-        communicate with the client 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.
-        A value of 0 disables inactivity probes.
-      </column>
-    </group>
-
-    <group title="Status">
-      <p>
-        Key-value pair of <ref column="is_connected"/> is always updated.
-        Other key-value pairs in the status columns may be updated depends
-        on the <ref column="target"/> type.
-      </p>
-
-      <p>
-        When <ref column="target"/> specifies a connection method that
-        listens for inbound connections (e.g. <code>ptcp:</code> or
-        <code>punix:</code>), both <ref column="n_connections"/> and
-        <ref column="is_connected"/> may also be updated while the
-        remaining key-value pairs are omitted.
-      </p>
-
-      <p>
-        On the other hand, when <ref column="target"/> specifies an
-        outbound connection, all key-value pairs may be updated, except
-        the above-mentioned two key-value pairs associated with inbound
-        connection targets. They are omitted.
-      </p>
-
-      <column name="is_connected">
-        <code>true</code> if currently connected to this client,
-        <code>false</code> otherwise.
-      </column>
-
-      <column name="status" key="last_error">
-        A human-readable description of the last error on the connection
-        to the manager; i.e. <code>strerror(errno)</code>.  This key
-        will exist only if an error has occurred.
-      </column>
-
-      <column name="status" key="state"
-              type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'>
-        <p>
-          The state of the connection to the manager:
-        </p>
-        <dl>
-          <dt><code>VOID</code></dt>
-          <dd>Connection is disabled.</dd>
-
-          <dt><code>BACKOFF</code></dt>
-          <dd>Attempting to reconnect at an increasing period.</dd>
-
-          <dt><code>CONNECTING</code></dt>
-          <dd>Attempting to connect.</dd>
-
-          <dt><code>ACTIVE</code></dt>
-          <dd>Connected, remote host responsive.</dd>
-
-          <dt><code>IDLE</code></dt>
-          <dd>Connection is idle.  Waiting for response to keep-alive.</dd>
-        </dl>
-        <p>
-          These values may change in the future.  They are provided only for
-          human consumption.
-        </p>
-      </column>
-
-      <column name="status" key="sec_since_connect"
-              type='{"type": "integer", "minInteger": 0}'>
-        The amount of time since this client last successfully connected
-        to the database (in seconds). Value is empty if client has never
-        successfully been connected.
-      </column>
-
-      <column name="status" key="sec_since_disconnect"
-              type='{"type": "integer", "minInteger": 0}'>
-        The amount of time since this client last disconnected from the
-        database (in seconds). Value is empty if client has never
-        disconnected.
-      </column>
-
-      <column name="status" key="locks_held">
-        Space-separated list of the names of OVSDB locks that the connection
-        holds.  Omitted if the connection does not hold any locks.
-      </column>
-
-      <column name="status" key="locks_waiting">
-        Space-separated list of the names of OVSDB locks that the connection is
-        currently waiting to acquire.  Omitted if the connection is not waiting
-        for any locks.
-      </column>
-
-      <column name="status" key="locks_lost">
-        Space-separated list of the names of OVSDB locks that the connection
-        has had stolen by another OVSDB client.  Omitted if no locks have been
-        stolen from this connection.
-      </column>
-
-      <column name="status" key="n_connections"
-              type='{"type": "integer", "minInteger": 2}'>
-        When <ref column="target"/> specifies a connection method that
-        listens for inbound connections (e.g. <code>ptcp:</code> or
-        <code>pssl:</code>) and more than one connection is actually active,
-        the value is the number of active connections.  Otherwise, this
-        key-value pair is omitted.
-      </column>
-
-      <column name="status" key="bound_port" type='{"type": "integer"}'>
-        When <ref column="target"/> is <code>ptcp:</code> or
-        <code>pssl:</code>, this is the TCP port on which the OVSDB server is
-        listening.  (This is particularly useful when <ref
-        column="target"/> specifies a port of 0, allowing the kernel to
-        choose any available port.)
-      </column>
-    </group>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-      <column name="other_config"/>
-    </group>
-  </table>
-  <table name="SSL">
-    SSL configuration for ovn-sb database access.
-
-    <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>
-
-    <column name="ssl_protocols">
-      List of SSL protocols to be enabled for SSL connections. The default
-      when this option is omitted is <code>TLSv1,TLSv1.1,TLSv1.2</code>.
-    </column>
-
-    <column name="ssl_ciphers">
-      List of ciphers (in OpenSSL cipher string format) to be supported
-      for SSL connections. The default when this option is omitted is
-      <code>HIGH:!aNULL:!MD5</code>.
-    </column>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-  </table>
-  <table name="DNS" title="Native DNS resolution">
-    <p>
-      Each row in this table stores the DNS records. The OVN action
-      <code>dns_lookup</code> uses this table for DNS resolution.
-    </p>
-
-    <column name="records">
-      Key-value pair of DNS records with <code>DNS query name</code> as the key
-      and a string of IP address(es) separated by comma or space as the
-      value.
-
-      <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p>
-    </column>
-
-    <column name="datapaths">
-      The DNS records defined in the column <ref column="records"/> will be
-      applied only to the DNS queries originating from the datapaths defined
-      in this column.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="RBAC_Role">
-    Role table for role-based access controls.
-
-    <column name="name">
-        The role name, corresponding to the <code>role</code>
-        column in the <code>Connection</code> table.
-    </column>
-
-    <column name="permissions">
-        A mapping of table names to rows in the
-        <code>RBAC_Permission</code> table.
-    </column>
-  </table>
-  <table name="RBAC_Permission">
-    Permissions table for role-based access controls.
-
-    <column name="table">
-      Name of table to which this row applies.
-    </column>
-
-    <column name="authorization">
-        Set of strings identifying columns and column:key pairs to be compared
-        with client ID. At least one match is required in order to be
-        authorized.  A zero-length string is treated as a special value
-        indicating all clients should be considered authorized.
-    </column>
-
-    <column name="insert_delete">
-        When "true", row insertions and authorized row
-        deletions are permitted.
-    </column>
-    <column name="update">
-        Set of strings identifying columns and column:key pairs that authorized
-        clients are allowed to modify.
-    </column>
-  </table>
-  <table name="Gateway_Chassis">
-    <p>
-      Association of <ref table="Port_Binding"/> rows of
-      <ref table="Port_Binding" column="type"/> <code>chassisredirect</code> to
-      a <ref table="Chassis"/>. The traffic going out through a specific
-      <code>chassisredirect</code> port will be redirected to a chassis,
-      or a set of them in high availability configurations.
-    </p>
-
-    <column name="name">
-      <p>
-        Name of the <ref table="Gateway_Chassis"/>.
-      </p>
-      <p>
-        A suggested, but not required naming convention is
-        <code>${port_name}_${chassis_name}</code>.
-      </p>
-    </column>
-
-    <column name="chassis">
-      The <ref table="Chassis"/> to which we send the traffic.
-    </column>
-
-    <column name="priority">
-      This is the priority the specific <ref table="Chassis"/> among all
-      Gateway_Chassis belonging to the same <ref table="Port_Binding"/>.
-    </column>
-
-    <column name="options">
-      Reserved for future use.
-    </column>
-
-    <group title="Common Columns">
-      The overall purpose of these columns is described under <code>Common
-      Columns</code> at the beginning of this document.
-
-      <column name="external_ids"/>
-    </group>
-  </table>
-
-  <table name="HA_Chassis">
-    <column name="chassis">
-      <p>
-        The <ref table="Chassis"/> which provides the HA functionality.
-        </p>
-    </column>
-
-    <column name="priority">
-      <p>
-        Priority of the HA chassis. Chassis with highest priority will be
-        the master in the HA chassis group.
-      </p>
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-
-  <table name="HA_Chassis_Group">
-    <p>
-      Table representing a group of chassis which can provide High availability
-      services. Each chassis in the group is represented by the table
-      <ref table="HA_Chassis"/>. The HA chassis with highest priority will
-      be the master of this group. If the master chassis failover is detected,
-      the HA chassis with the next higher priority takes over the
-      responsibility of providing the HA. If <ref db="OVN_Southbound"
-      table="Port_Binding" column="ha_chassis_group"/> column of the table
-      <ref db="OVN_Southbound" table="Port_Binding"/> references this table,
-      then this HA chassis group provides the gateway functionality and
-      redirects the gateway traffic to the master of this group.
-    </p>
-    <column name="name">
-      Name of the <ref table="HA_Chassis_Group"/>. Name should be unique.
-    </column>
-
-    <column name="ha_chassis">
-      A list of <ref table="HA_Chassis"/> which belongs to this group.
-    </column>
-
-    <column name="ref_chassis">
-      A list of <ref table="chassis"/> which references this HA chassis group.
-    </column>
-
-    <group title="Common Columns">
-      <column name="external_ids">
-        See <em>External IDs</em> at the beginning of this document.
-      </column>
-    </group>
-  </table>
-  <table name="Controller_Event" title="Controller Event table">
-    <p>
-      Database table used by <code>ovn-controller</code> to report CMS
-      related events. Please note there is no guarantee a given event is
-      written exactly once in the db. It is CMS responsibility to squash
-      duplicated lines or to filter out duplicated events
-    </p>
-    <column name="event_type">
-      Event type occurred
-    </column>
-    <column name="event_info">
-    <p>
-      Key-value pairs used to specify event info to the CMS.
-      Possible values are:
-    </p>
-      <ul>
-        <li>
-         <code>vip</code>: VIP reported for the <code>empty_lb_backends</code>
-         event
-        </li>
-        <li>
-          <code>protocol</code>: Transport protocol reported for the
-          <code>empty_lb_backends</code> event
-        </li>
-        <li>
-          <code>load_balancer</code>: UUID of the load balancer reported for
-          the <code>empty_lb_backends</code> event
-        </li>
-      </ul>
-    </column>
-    <column name="chassis">
-      This column is a <ref table="Chassis"/> record to identify the chassis
-      that has managed a given event.
-    </column>
-    <column name="seq_num">
-      Event sequence number. Global counter for controller generated events.
-      It can be used by the CMS to detect possible duplication of the same
-      event.
-    </column>
-  </table>
-  <table name="IP_Multicast">
-    <p>
-      IP Multicast configuration options. For now only applicable to IGMP.
-    </p>
-
-    <column name="datapath">
-      <ref table="Datapath_Binding"/> entry for which these configuration
-      options are defined.
-    </column>
-    <column name="enabled">
-      Enables/disables multicast snooping. Default: disabled.
-    </column>
-    <column name="querier">
-      Enables/disables multicast querying. If
-      <ref table="IP_Multicast" column="enabled"/> then multicast querying is
-      enabled by default.
-    </column>
-    <column name="table_size">
-      Limits the number of multicast groups that can be learned. Default:
-      2048 groups per datapath.
-    </column>
-    <column name="idle_timeout">
-      Configures the idle timeout (in seconds) for IP multicast groups if
-      multicast snooping is enabled. Default: 300 seconds.
-    </column>
-    <column name="query_interval">
-      Configures the interval (in seconds) for sending multicast queries if
-      snooping and querier are enabled.
-      Default: <ref table="IP_Multicast" column="idle_timeout"/>/2 seconds.
-    </column>
-    <column name="seq_no">
-      <code>ovn-controller</code> reads this value and flushes all learned
-      multicast groups when it detects that <code>seq_no</code> was changed.
-    </column>
-
-    <group title="Querier configuration options">
-      The <code>ovn-controller</code> process that runs on OVN hypervisor
-      nodes uses the following columns to determine field values in IGMP
-      queries that it originates:
-      <column name="eth_src">
-        Source Ethernet address.
-      </column>
-      <column name="ip4_src">
-        Source IPv4 address.
-      </column>
-      <column name="query_max_resp">
-        Value (in seconds) to be used as "max-response" field in multicast
-        queries. Default: 1 second.
-      </column>
-    </group>
-  </table>
-  <table name="IGMP_Group">
-    <p>
-      Contains learned IGMP groups indexed by address/datapath/chassis.
-    </p>
-
-    <column name="address">
-      Destination IPv4 address for the IGMP group.
-    </column>
-
-    <column name="datapath">
-      Datapath to which this IGMP group belongs.
-    </column>
-
-    <column name="chassis">
-      Chassis to which this IGMP group belongs.
-    </column>
-
-    <column name="ports">
-      The destination port bindings for this IGMP group.
-    </column>
-  </table>
-</database>
diff --git a/ovn/utilities/.gitignore b/ovn/utilities/.gitignore
deleted file mode 100644
index 1d01e0b2878f..000000000000
--- a/ovn/utilities/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-/ovn-ctl.8
-/ovn-nbctl
-/ovn-nbctl.8
-/ovn-sbctl
-/ovn-sbctl.8
-/ovn-trace
-/ovn-trace.8
-/ovn-detrace
-/ovn-detrace.1
-/ovn-docker-overlay-driver
-/ovn-docker-underlay-driver
diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk
deleted file mode 100644
index d2e2675c08a5..000000000000
--- a/ovn/utilities/automake.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-EXTRA_DIST += \
-    ovn/utilities/ovn-nbctl.8.xml \
-    ovn/utilities/ovn-sbctl.8.in
-
-CLEANFILES += \
-    ovn/utilities/ovn-nbctl.8 \
-    ovn/utilities/ovn-sbctl.8
-
-# ovn-nbctl
-bin_PROGRAMS += ovn/utilities/ovn-nbctl
-ovn_utilities_ovn_nbctl_SOURCES = ovn/utilities/ovn-nbctl.c
-ovn_utilities_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
-
-# ovn-sbctl
-bin_PROGRAMS += ovn/utilities/ovn-sbctl
-ovn_utilities_ovn_sbctl_SOURCES = ovn/utilities/ovn-sbctl.c
-ovn_utilities_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
deleted file mode 100644
index 41d50b6943c0..000000000000
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ /dev/null
@@ -1,1228 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-nbctl" section="8" title="ovn-nbctl">
-    <h1>Name</h1>
-    <p>ovn-nbctl -- Open Virtual Network northbound db management utility</p>
-
-    <h1>Synopsis</h1>
-    <p><code>ovn-nbctl</code> [<var>options</var>] <var>command</var> [<var>arg</var>...]</p>
-
-    <h1>Description</h1>
-    <p>This utility can be used to manage the OVN northbound database.</p>
-
-    <h1>General Commands</h1>
-
-    <dl>
-      <dt><code>init</code></dt>
-      <dd>
-        Initializes the database, if it is empty.  If the database has already
-        been initialized, this command has no effect.
-      </dd>
-
-      <dt><code>show [<var>switch</var> | <var>router</var>]</code></dt>
-      <dd>
-        Prints a brief overview of the database contents.  If
-        <var>switch</var> is provided, only records related to that
-        logical switch are shown. If
-        <var>router</var> is provided, only records related to that
-        logical router are shown.
-      </dd>
-    </dl>
-
-    <h1>Logical Switch Commands</h1>
-
-    <dl>
-      <dt><code>ls-add</code></dt>
-      <dd>
-        <p>
-          Creates a new, unnamed logical switch, which initially has no ports.
-          The switch does not have a name, other commands must refer to this
-          switch by its UUID.
-        </p>
-      </dd>
-
-      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>] <code>ls-add</code> <var>switch</var></dt>
-      <dd>
-        <p>
-          Creates a new logical switch named <var>switch</var>, which
-          initially has no ports.
-        </p>
-
-        <p>
-          The OVN northbound database schema does not require logical switch
-          names to be unique, but the whole point to the names is to provide an
-          easy way for humans to refer to the switches, making duplicate names
-          unhelpful.  Thus, without any options, this command regards it as an
-          error if <var>switch</var> is a duplicate name.  With
-          <code>--may-exist</code>, adding a duplicate name succeeds but does
-          not create a new logical switch.  With <code>--add-duplicate</code>,
-          the command really creates a new logical switch with a duplicate
-          name.  It is an error to specify both options.  If there are multiple
-          logical switches with a duplicate name, configure the logical switches
-          using the UUID instead of the <var>switch</var> name.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>ls-del</code> <var>switch</var></dt>
-      <dd>
-        Deletes <var>switch</var>.  It is an error if <var>switch</var> does
-        not exist, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>ls-list</code></dt>
-      <dd>
-        Lists all existing switches on standard output, one per line.
-      </dd>
-    </dl>
-
-    <h1>ACL Commands</h1>
-    <p>
-      These commands operates on ACL objects for a given <var>entity</var>.
-      The <var>entity</var> can be either a logical switch or a port group.
-      The <var>entity</var> can be specified as uuid or name.  The
-      <code>--type</code> option can be used to specify the type of the
-      <var>entity</var>, in case both a logical switch and a port groups exist
-      with the same name specified for <var>entity</var>.  <code>type</code>
-      must be either <code>switch</code> or <code>port-group</code>.
-    </p>
-    <dl>
-      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
-      <dd>
-        <p>
-          Adds the specified ACL to <var>entity</var>.  <var>direction</var>
-          must be either <code>from-lport</code> or <code>to-lport</code>.
-          <var>priority</var> must be between <code>0</code> and
-          <code>32767</code>, inclusive.  A full description of the fields are
-          in <code>ovn-nb</code>(5).  If <code>--may-exist</code> is specified,
-          adding a duplicated ACL succeeds but the ACL is not really created.
-          Without <code>--may-exist</code>, adding a duplicated ACL results in
-          error.
-        </p>
-
-        <p>
-          The <code>--log</code> option enables packet logging for the ACL.
-          The options <code>--severity</code> and <code>--name</code> specify a
-          severity and name, respectively, for log entries (and also enable
-          logging).  The severity must be one of <code>alert</code>,
-          <code>warning</code>, <code>notice</code>, <code>info</code>, or
-          <code>debug</code>.  If a severity is not specified, the default is
-          <code>info</code>.  The <code>--meter=<var>meter</var></code> option
-          is used to rate-limit packet logging.  The <var>meter</var> argument
-          names a meter configured by <code>meter-add</code>.
-        </p>
-      </dd>
-
-      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] <code>acl-del</code> <var>entity</var> [<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
-      <dd>
-        Deletes ACLs from <var>entity</var>.  If only <var>entity</var> is
-        supplied, all the ACLs from the <var>entity</var> are deleted.  If
-        <var>direction</var> is also specified, then all the flows in that
-        direction will be deleted from the <var>entity</var>.  If all the
-        fields are given, then a single flow that matches all the fields will
-        be deleted.
-      </dd>
-
-      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] <code>acl-list</code> <var>entity</var> </dt>
-      <dd>
-        Lists the ACLs on <var>entity</var>.
-      </dd>
-    </dl>
-
-    <h1>Logical Switch QoS Rule Commands</h1>
-    <dl>
-      <dt>[<code>--may-exist</code>] <code>qos-add</code> <var>switch</var> <var>direction</var> <var>priority</var> <var>match</var> [<code>dscp=</code><var>dscp</var>] [<code>rate=</code><var>rate</var> [<code>burst=</code><var>burst</var>]]</dt>
-      <dd>
-        <p>
-          Adds QoS marking and metering rules to <var>switch</var>.
-          <var>direction</var> must be either <code>from-lport</code> or
-          <code>to-lport</code>.  <var>priority</var> must be between
-          <code>0</code> and <code>32767</code>, inclusive.
-        </p>
-
-        <p>
-          If <code>dscp=</code><var>dscp</var> is specified, then
-          matching packets will have DSCP marking applied.
-          <var>dscp</var> must be between <code>0</code> and
-          <code>63</code>, inclusive.  If <code>rate=</code><var>rate</var>
-          is specified then matching packets will have metering applied
-          at <var>rate</var> kbps.  If metering is configured, then
-          <code>burst=</code><var>burst</var> specifies the burst rate
-          limit in kilobits.  <code>dscp</code> and/or <code>rate</code>
-          are required arguments.
-        </p>
-
-        <p>
-          If <code>--may-exist</code> is specified, adding a duplicated
-          QoS rule succeeds but the QoS rule is not really created.
-          Without <code>--may-exist</code>, adding a duplicated QoS rule
-          results in error.
-        </p>
-      </dd>
-
-      <dt><code>qos-del</code> <var>switch</var> [<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
-      <dd>
-        Deletes QoS rules from <var>switch</var>.  If only
-        <var>switch</var> is supplied, all the QoS rules from the logical
-        switch are deleted.  If <var>direction</var> is also specified,
-        then all the flows in that direction will be deleted from the
-        logical switch.  If all the fields are supplied, then a single
-        flow that matches the given fields will be deleted.
-      </dd>
-
-      <dt><code>qos-list</code> <var>switch</var></dt>
-      <dd>
-        Lists the QoS rules on <var>switch</var>.
-      </dd>
-    </dl>
-
-    <h1>Meter Commands</h1>
-    <dl>
-        <dt><code>meter-add</code> <var>name</var> <var>action</var> <var>rate</var> <var>unit</var> [<var>burst</var>]</dt>
-      <dd>
-        <p>
-          Adds the specified meter.  <var>name</var> must be a unique
-          name to identify this meter.  The <var>action</var> argument
-          specifies what should happen when this meter is exceeded.
-          The only supported action is <code>drop</code>.
-        </p>
-
-        <p>
-          The <var>unit</var> specifies the unit for the <var>rate</var>
-          argument; valid values are <code>kbps</code> and
-          <code>pktps</code> for kilobits per second and packets per
-          second, respectively.  The <var>burst</var> option
-          configures the maximum burst allowed for the band in kilobits
-          or packets depending on whether the <var>unit</var> chosen was
-          <code>kbps</code> or <code>pktps</code>, respectively.  If a
-          burst is not supplied, the switch is free to select some
-          reasonable value depending on its configuration.
-        </p>
-
-        <p>
-          <code>ovn-nbctl</code> only supports adding a meter with a
-          single band, but the other commands support meters with
-          multiple bands.
-        </p>
-
-        <p>
-          Names that start with "__" (two underscores) are reserved for
-          internal use by OVN, so <code>ovn-nbctl</code> does not allow
-          adding them.
-        </p>
-      </dd>
-
-      <dt><code>meter-del</code> [<var>name</var>]</dt>
-      <dd>
-        <p>
-          Deletes meters.  By default, all meters are deleted.  If
-          <var>name</var> is supplied, only the meter with that name
-          will be deleted.
-      </p>
-      </dd>
-
-      <dt><code>meter-list</code></dt>
-      <dd>
-        <p>
-          Lists all meters.
-        </p>
-      </dd>
-    </dl>
-
-    <h1>Logical Switch Port Commands</h1>
-    <dl>
-      <dt>[<code>--may-exist</code>] <code>lsp-add</code> <var>switch</var> <var>port</var></dt>
-      <dd>
-        <p>
-          Creates on <var>lswitch</var> a new logical switch port named
-          <var>port</var>.
-        </p>
-
-        <p>
-          It is an error if a logical port named <var>port</var> already
-          exists, unless <code>--may-exist</code> is specified.  Regardless of
-          <code>--may-exist</code>, it is an error if the existing port is in
-          some logical switch other than <var>switch</var> or if it has a
-          parent port.
-        </p>
-      </dd>
-
-      <dt>[<code>--may-exist</code>] <code>lsp-add</code> <var>switch</var> <var>port</var> <var>parent</var> <var>tag_request</var></dt>
-      <dd>
-        <p>
-          Creates on <var>switch</var> a logical switch port named
-          <var>port</var> that is a child of <var>parent</var> that is
-          identified with VLAN ID <var>tag_request</var>,
-          which must be between <code>0</code> and
-          <code>4095</code>, inclusive. If
-          <var>tag_request</var> is <code>0</code>, <code>ovn-northd</code>
-          generates a tag that is unique in the scope of <var>parent</var>.
-          This is useful in cases such as virtualized container environments
-          where Open vSwitch does not have a direct connection to the
-          container's port and it must be shared with the virtual machine's
-          port.
-        </p>
-
-        <p>
-          It is an error if a logical port named <var>port</var> already
-          exists, unless <code>--may-exist</code> is specified.  Regardless of
-          <code>--may-exist</code>, it is an error if the existing port is not
-          in <var>switch</var> or if it does not have the specified
-          <var>parent</var> and <var>tag_request</var>.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lsp-del</code> <var>port</var></dt>
-      <dd>
-        Deletes <var>port</var>.  It is an error if <var>port</var> does
-        not exist, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>lsp-list</code> <var>switch</var></dt>
-      <dd>
-        Lists all the logical switch ports within <var>switch</var> on
-        standard output, one per line.
-      </dd>
-
-      <dt><code>lsp-get-parent</code> <var>port</var></dt>
-      <dd>
-        If set, get the parent port of <var>port</var>.  If not set, print
-        nothing.
-      </dd>
-
-      <dt><code>lsp-get-tag</code> <var>port</var></dt>
-      <dd>
-        If set, get the tag for <var>port</var> traffic.  If not set, print
-        nothing.
-      </dd>
-
-      <dt><code>lsp-set-addresses</code> <var>port</var> [<var>address</var>]...</dt>
-      <dd>
-        <p>
-          Sets the addresses associated with <var>port</var> to
-          <var>address</var>.  Each <var>address</var> should be one of the
-          following:
-        </p>
-
-        <dl>
-          <dt>an Ethernet address, optionally followed by a space and one or more IP addresses</dt>
-          <dd>
-            OVN delivers packets for the Ethernet address to this port.
-          </dd>
-
-          <dt><code>unknown</code></dt>
-          <dd>
-            OVN delivers unicast Ethernet packets whose destination MAC address
-            is not in any logical port's addresses column to ports with address
-            <code>unknown</code>.
-          </dd>
-
-          <dt><code>dynamic</code></dt>
-          <dd>
-            Use this keyword to make <code>ovn-northd</code> generate a
-            globally unique MAC address and choose an unused IPv4 address with
-            the logical port's subnet and store them in the port's
-            <code>dynamic_addresses</code> column.
-          </dd>
-
-          <dt><code>router</code></dt>
-          <dd>
-            Accepted only when the <code>type</code> of the logical switch
-            port is <code>router</code>.  This indicates that the Ethernet,
-            IPv4, and IPv6 addresses for this logical switch port should be
-            obtained from the connected logical router port, as specified by
-            <code>router-port</code> in <code>lsp-set-options</code>.
-          </dd>
-        </dl>
-
-        <p>
-          Multiple addresses may be set.  If no <var>address</var> argument is
-          given, <var>port</var> will have no addresses associated with it.
-        </p>
-      </dd>
-
-      <dt><code>lsp-get-addresses</code> <var>port</var></dt>
-      <dd>
-        Lists all the addresses associated with <var>port</var> on standard
-        output, one per line.
-      </dd>
-
-      <dt><code>lsp-set-port-security</code> <var>port</var> [<var>addrs</var>]...</dt>
-      <dd>
-        <p>
-          Sets the port security addresses associated with <var>port</var> to
-          <var>addrs</var>.  Multiple sets of addresses may be set by using
-          multiple <var>addrs</var> arguments.  If no <var>addrs</var> argument
-          is given, <var>port</var> will not have port security enabled.
-        </p>
-
-        <p>
-          Port security limits the addresses from which a logical port may send
-          packets and to which it may receive packets.  See the
-          <code>ovn-nb</code>(5) documentation for the <ref
-          column="port_security" table="Logical_Switch_Port"/> column in
-          the <ref table="Logical_Switch_Port"/> table for details.
-        </p>
-      </dd>
-
-      <dt><code>lsp-get-port-security</code> <var>port</var></dt>
-      <dd>
-        Lists all the port security addresses associated with <var>port</var>
-        on standard output, one per line.
-      </dd>
-
-      <dt><code>lsp-get-up</code> <var>port</var></dt>
-      <dd>
-        Prints the state of <var>port</var>, either <code>up</code> or
-        <code>down</code>.
-      </dd>
-
-      <dt><code>lsp-set-enabled</code> <var>port</var> <var>state</var></dt>
-      <dd>
-        Set the administrative state of <var>port</var>, either <code>enabled</code>
-        or <code>disabled</code>.  When a port is disabled, no traffic is allowed into
-        or out of the port.
-      </dd>
-
-      <dt><code>lsp-get-enabled</code> <var>port</var></dt>
-      <dd>
-        Prints the administrative state of <var>port</var>, either <code>enabled</code>
-        or <code>disabled</code>.
-      </dd>
-
-      <dt><code>lsp-set-type</code> <var>port</var> <var>type</var></dt>
-      <dd>
-        <p>
-          Set the type for the logical port.  The type must be one of the following:
-        </p>
-
-        <dl>
-          <dt><code>(empty string)</code></dt>
-          <dd>
-            A VM (or VIF) interface.
-          </dd>
-
-          <dt><code>router</code></dt>
-          <dd>
-            A connection to a logical router.
-          </dd>
-
-          <dt><code>localnet</code></dt>
-          <dd>
-            A connection to a locally accessible network from each ovn-controller
-            instance. A logical switch can only have a single localnet port
-            attached. This is used to model direct connectivity to an existing
-            network.
-          </dd>
-
-          <dt><code>localport</code></dt>
-          <dd>
-            A connection to a local VIF. Traffic that arrives on a localport is
-            never forwarded over a tunnel to another chassis. These ports are
-            present on every chassis and have the same address in all of them.
-            This is used to model connectivity to local services that run on
-            every hypervisor.
-          </dd>
-
-          <dt><code>l2gateway</code></dt>
-          <dd>
-            A connection to a physical network.
-          </dd>
-
-          <dt><code>vtep</code></dt>
-          <dd>
-            A port to a logical switch on a VTEP gateway.
-          </dd>
-        </dl>
-
-      </dd>
-
-      <dt><code>lsp-get-type</code> <var>port</var></dt>
-      <dd>
-        Get the type for the logical port.
-      </dd>
-
-      <dt><code>lsp-set-options</code> <var>port</var> [<var>key=value</var>]...</dt>
-      <dd>
-        Set type-specific key-value options for the logical port.
-      </dd>
-
-      <dt><code>lsp-get-options</code> <var>port</var></dt>
-      <dd>
-        Get the type-specific options for the logical port.
-      </dd>
-
-      <dt><code>lsp-set-dhcpv4-options</code> <var>port</var>
-          <var>dhcp_options</var></dt>
-      <dd>
-        Set the DHCPv4 options for the logical port.  The
-        <var>dhcp_options</var> is a UUID referring to a set of DHCP options in
-        the <ref table="DHCP_Options" /> table.
-      </dd>
-
-      <dt><code>lsp-get-dhcpv4-options</code> <var>port</var></dt>
-      <dd>
-        Get the configured DHCPv4 options for the logical port.
-      </dd>
-
-      <dt><code>lsp-set-dhcpv6-options</code> <var>port</var>
-          <var>dhcp_options</var></dt>
-      <dd>
-          Set the DHCPv6 options for the logical port.  The
-          <var>dhcp_options</var> is a UUID referring to a set of DHCP options
-          in the <ref table="DHCP_Options" /> table.
-      </dd>
-
-      <dt><code>lsp-get-dhcpv6-options</code> <var>port</var></dt>
-      <dd>
-        Get the configured DHCPv6 options for the logical port.
-      </dd>
-
-      <dt><code>lsp-get-ls</code> <var>port</var></dt>
-      <dd>
-        Get the logical switch which the <var>port</var> belongs to.
-      </dd>
-
-    </dl>
-
-    <h1>Logical Router Commands</h1>
-
-    <dl>
-      <dt><code>lr-add</code></dt>
-      <dd>
-        <p>
-          Creates a new, unnamed logical router, which initially has no ports.
-          The router does not have a name, other commands must refer to this
-          router by its UUID.
-        </p>
-      </dd>
-
-      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>] <code>lr-add</code> <var>router</var></dt>
-      <dd>
-        <p>
-          Creates a new logical router named <var>router</var>, which
-          initially has no ports.
-        </p>
-
-        <p>
-          The OVN northbound database schema does not require logical router
-          names to be unique, but the whole point to the names is to provide an
-          easy way for humans to refer to the routers, making duplicate names
-          unhelpful.  Thus, without any options, this command regards it as an
-          error if <var>router</var> is a duplicate name.  With
-          <code>--may-exist</code>, adding a duplicate name succeeds but does
-          not create a new logical router.  With <code>--add-duplicate</code>,
-          the command really creates a new logical router with a duplicate
-          name.  It is an error to specify both options.  If there are multiple
-          logical routers with a duplicate name, configure the logical routers
-          using the UUID instead of the <var>router</var> name.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lr-del</code> <var>router</var></dt>
-      <dd>
-        Deletes <var>router</var>.  It is an error if <var>router</var> does
-        not exist, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>lr-list</code></dt>
-      <dd>
-        Lists all existing routers on standard output, one per line.
-      </dd>
-    </dl>
-
-    <h1>Logical Router Port Commands</h1>
-
-    <dl>
-      <dt>[<code>--may-exist</code>] <code>lrp-add</code> <var>router</var> <var>port</var> <var>mac</var> <var>network</var>... [<code>peer=</code><var>peer</var>]</dt>
-      <dd>
-        <p>
-          Creates on <var>router</var> a new logical router port named
-          <var>port</var> with Ethernet address <var>mac</var> and one
-          or more IP address/netmask for each <var>network</var>.
-        </p>
-
-        <p>
-          The optional argument <code>peer</code> identifies a logical
-          router port that connects to this one.  The following example
-          adds a router port with an IPv4 and IPv6 address with peer
-          <code>lr1</code>:
-        </p>
-
-        <p>
-          <code>lrp-add lr0 lrp0 00:11:22:33:44:55 192.168.0.1/24 2001:db8::1/64 peer=lr1</code>
-        </p>
-
-        <p>
-          It is an error if a logical router port named <var>port</var>
-          already exists, unless <code>--may-exist</code> is specified.
-          Regardless of <code>--may-exist</code>, it is an error if the
-          existing router port is in some logical router other than
-          <var>router</var>.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lrp-del</code> <var>port</var></dt>
-      <dd>
-        Deletes <var>port</var>.  It is an error if <var>port</var> does
-        not exist, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>lrp-list</code> <var>router</var></dt>
-      <dd>
-        Lists all the logical router ports within <var>router</var> on
-        standard output, one per line.
-      </dd>
-
-      <dt><code>lrp-set-enabled</code> <var>port</var> <var>state</var></dt>
-      <dd>
-        Set the administrative state of <var>port</var>, either
-        <code>enabled</code> or <code>disabled</code>.  When a port is
-        disabled, no traffic is allowed into or out of the port.
-      </dd>
-
-      <dt><code>lrp-get-enabled</code> <var>port</var></dt>
-      <dd>
-        Prints the administrative state of <var>port</var>, either
-        <code>enabled</code> or <code>disabled</code>.
-      </dd>
-
-      <dt><code>lrp-set-gateway-chassis</code> <var>port</var>
-          <var>chassis</var> [<var>priority</var>]</dt>
-      <dd>
-        Set gateway chassis for <var>port</var>. <var>chassis</var>
-        is the name of the chassis. This creates a gateway chassis entry
-        in Gateway_Chassis table. It won't check if chassis really exists
-        in OVN_Southbound database. Priority will be set to 0
-        if <var>priority</var> is not provided by user. <var>priority</var>
-        must be between <code>0</code> and <code>32767</code>, inclusive.
-      </dd>
-      <dt><code>lrp-del-gateway-chassis</code> <var>port</var>
-          <var>chassis</var></dt>
-      <dd>
-        Deletes gateway chassis from <var>port</var>.  It is an error if
-        gateway chassis with <var>chassis</var> for <var>port</var> does
-        not exist.
-      </dd>
-      <dt><code>lrp-get-gateway-chassis</code> <var>port</var></dt>
-      <dd>
-        Lists all the gateway chassis with priority within <var>port</var> on
-        standard output, one per line, ordered based on priority.
-      </dd>
-    </dl>
-
-    <h1>Logical Router Static Route Commands</h1>
-
-    <dl>
-      <dt>[<code>--may-exist</code>] [<code>--policy</code>=<var>POLICY</var>] <code>lr-route-add</code> <var>router</var> <var>prefix</var> <var>nexthop</var> [<var>port</var>]</dt>
-      <dd>
-        <p>
-          Adds the specified route to <var>router</var>.
-          <var>prefix</var> describes an IPv4 or IPv6 prefix for this
-          route, such as <code>192.168.100.0/24</code>.
-          <var>nexthop</var> specifies the gateway to use for this
-          route, which should be the IP address of one of
-          <var>router</var> logical router ports or the IP address of a
-          logical port.  If <var>port</var> is specified, packets that
-          match this route will be sent out that port.  When
-          <var>port</var> is omitted, OVN infers the output port based
-          on <var>nexthop</var>.
-        </p>
-
-        <p>
-          <code>--policy</code> describes the policy used to make routing
-          decisions.  This should be one of "dst-ip" or "src-ip".  If not
-          specified, the default is "dst-ip".
-        </p>
-
-        <p>
-          It is an error if a route with <var>prefix</var> already exists,
-          unless <code>--may-exist</code> is specified.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lr-route-del</code> <var>router</var> [<var>prefix</var>]</dt>
-      <dd>
-        <p>
-          Deletes routes from <var>router</var>.  If only <var>router</var>
-          is supplied, all the routes from the logical router are
-          deleted.  If <var>prefix</var> is also specified, then all the
-          routes that match the prefix will be deleted from the logical
-          router.
-        </p>
-
-        <p>
-          It is an error if <var>prefix</var> is specified and there
-          is no matching route entry, unless <code>--if-exists</code> is
-          specified.
-        </p>
-      </dd>
-
-      <dt><code>lr-route-list</code> <var>router</var></dt>
-      <dd>
-        Lists the routes on <var>router</var>.
-      </dd>
-    </dl>
-
-    <h1>NAT Commands</h1>
-
-    <dl>
-      <dt>[<code>--may-exist</code>] <code>lr-nat-add</code> <var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
-      <dd>
-        <p>
-          Adds the specified NAT to <var>router</var>.
-          The <var>type</var> must be one of <code>snat</code>,
-          <code>dnat</code>, or <code>dnat_and_snat</code>.
-          The <var>external_ip</var> is an IPv4 address.
-          The <var>logical_ip</var> is an IPv4 network (e.g 192.168.1.0/24)
-          or an IPv4 address.
-          The <var>logical_port</var> and <var>external_mac</var> are only
-          accepted when <var>router</var> is a distributed router (rather
-          than a gateway router) and <var>type</var> is
-          <code>dnat_and_snat</code>.
-          The <var>logical_port</var> is the name of an existing logical
-          switch port where the <var>logical_ip</var> resides.
-          The <var>external_mac</var> is an Ethernet address.
-        </p>
-        <p>
-          When <var>type</var> is <code>dnat</code>, the externally
-          visible IP address <var>external_ip</var> is DNATted to the
-          IP address <var>logical_ip</var> in the logical space.
-        </p>
-        <p>
-          When <var>type</var> is <code>snat</code>, IP packets with their
-          source IP address that either matches the IP address in
-          <var>logical_ip</var> or is in the network provided by
-          <var>logical_ip</var> is SNATed into the IP address in
-          <var>external_ip</var>.
-        </p>
-        <p>
-          When <var>type</var> is <code>dnat_and_snat</code>,
-          the externally visible IP address <var>external_ip</var>
-          is DNATted to the IP address <var>logical_ip</var> in
-          the logical space.  In addition, IP packets with the source
-          IP address that matches <var>logical_ip</var> is SNATed into
-          the IP address in <var>external_ip</var>.
-        </p>
-        <p>
-          When the <var>logical_port</var> and <var>external_mac</var>
-          are specified, the NAT rule will be programmed on the chassis
-          where the <var>logical_port</var> resides.  This includes
-          ARP replies for the <var>external_ip</var>, which return the
-          value of <var>external_mac</var>.  All packets transmitted
-          with source IP address equal to <var>external_ip</var> will
-          be sent using the <var>external_mac</var>.
-        </p>
-        <p>
-          It is an error if a NAT already exists with the same values
-          of <var>router</var>, <var>type</var>, <var>external_ip</var>,
-          and <var>logical_ip</var>, unless <code>--may-exist</code> is
-          specified.  When <code>--may-exist</code>,
-          <var>logical_port</var>, and <var>external_mac</var> are all
-          specified, the existing values of <var>logical_port</var> and
-          <var>external_mac</var> are overwritten.
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>]]</dt>
-      <dd>
-        <p>
-          Deletes NATs from <var>router</var>.  If only <var>router</var>
-          is supplied, all the NATs from the logical router are
-          deleted.  If <var>type</var> is also specified, then all the
-          NATs that match the <var>type</var> will be deleted from the logical
-          router.  If all the fields are given, then a single NAT rule
-          that matches all the fields will be deleted.  When <var>type</var>
-          is <code>snat</code>, the <var>ip</var> should be logical_ip.
-          When <var>type</var> is <code>dnat</code> or
-          <code>dnat_and_snat</code>, the <var>ip</var> shoud be external_ip.
-        </p>
-
-        <p>
-          It is an error if <var>ip</var> is specified and there
-          is no matching NAT entry, unless <code>--if-exists</code> is
-          specified.
-        </p>
-      </dd>
-
-      <dt><code>lr-nat-list</code> <var>router</var></dt>
-      <dd>
-        Lists the NATs on <var>router</var>.
-      </dd>
-    </dl>
-
-    <h1>Load Balancer Commands</h1>
-    <dl>
-      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>] <code>lb-add</code> <var>lb</var> <var>vip</var> <var>ips</var> [<var>protocol</var>]</dt>
-      <dd>
-        <p>
-         Creates a new load balancer named <var>lb</var> with the provided
-         <var>vip</var> and <var>ips</var> or adds the <var>vip</var> to
-         an existing <var>lb</var>.  <var>vip</var> should be a
-         virtual IP address (or an IP address and a port number with
-         <code>:</code> as a separator).  Examples for <var>vip</var> are
-         <code>192.168.1.4</code>, <code>fd0f::1</code>, and
-         <code>192.168.1.5:8080</code>. <var>ips</var> should be comma
-         separated IP endpoints (or comma separated IP addresses and port
-         numbers with <code>:</code> as a separator).  <var>ips</var> must
-         be the same address family as <var>vip</var>.  Examples for
-         <var>ips</var> are <code>10.0.0.1,10.0.0.2</code>or
-         <code>[fdef::1]:8800,[fdef::2]:8800</code>.
-        </p>
-
-        <p>
-         The optional argument <var>protocol</var> must be either
-         <code>tcp</code> or <code>udp</code>.  This argument is useful when
-         a port number is provided as part of the <var>vip</var>.  If the
-         <var>protocol</var> is unspecified and a port number is provided as
-         part of the <var>vip</var>, OVN assumes the <var>protocol</var> to
-         be <code>tcp</code>.
-        </p>
-
-        <p>
-         It is an error if the <var>vip</var> already exists in the load
-         balancer named <var>lb</var>, unless <code>--may-exist</code> is
-         specified.  With <code>--add-duplicate</code>, the command really
-         creates a new load balancer with a duplicate name.
-        </p>
-
-        <p>
-         The following example adds a load balancer.
-        </p>
-
-        <p>
-         <code>lb-add lb0 30.0.0.10:80
-         192.168.10.10:80,192.168.10.20:80,192.168.10.30:80 udp</code>
-        </p>
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lb-del</code> <var>lb</var> [<var>vip</var>]</dt>
-      <dd>
-        Deletes <var>lb</var> or the <var>vip</var> from <var>lb</var>.
-        If <var>vip</var> is supplied, only the <var>vip</var> will be
-        deleted from the <var>lb</var>.  If only the <var>lb</var> is supplied,
-        the <var>lb</var> will be deleted.  It is an error if <var>vip</var>
-        does not already exist in <var>lb</var>, unless
-        <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>lb-list</code> [<var>lb</var>]</dt>
-      <dd>
-        Lists the LBs.  If <var>lb</var> is also specified, then only the
-        specified <var>lb</var> will be listed.
-      </dd>
-
-      <dt>[<code>--may-exist</code>] <code>ls-lb-add</code> <var>switch</var> <var>lb</var></dt>
-      <dd>
-        Adds the specified <var>lb</var> to <var>switch</var>.
-        It is an error if a load balancer named <var>lb</var> already exists
-        in the <var>switch</var>, unless <code>--may-exist</code> is specified.
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>ls-lb-del</code> <var>switch</var> [<var>lb</var>]</dt>
-      <dd>
-        Removes <var>lb</var> from <var>switch</var>.  If only
-        <var>switch</var> is supplied, all the LBs from the logical switch are
-        removed.  If <var>lb</var> is also specified, then only the
-        <var>lb</var> will be removed from the logical switch.
-        It is an error if <var>lb</var> does not exist in the
-        <var>switch</var>, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>ls-lb-list</code> <var>switch</var></dt>
-      <dd>
-        Lists the LBs for the given <var>switch</var>.
-      </dd>
-
-      <dt>[<code>--may-exist</code>] <code>lr-lb-add</code> <var>router</var> <var>lb</var></dt>
-      <dd>
-        Adds the specified <var>lb</var> to <var>router</var>.
-        It is an error if a load balancer named <var>lb</var> already exists
-        in the <var>router</var>, unless <code>--may-exist</code> is specified.
-      </dd>
-
-      <dt>[<code>--if-exists</code>] <code>lr-lb-del</code> <var>router</var> [<var>lb</var>]</dt>
-      <dd>
-        Removes <var>lb</var> from <var>router</var>.  If only
-        <var>router</var> is supplied, all the LBs from the logical router are
-        removed.  If <var>lb</var> is also specified, then only the
-        <var>lb</var> will be removed from the logical router.
-        It is an error if <var>lb</var> does not exist in the
-        <var>router</var>, unless <code>--if-exists</code> is specified.
-      </dd>
-
-      <dt><code>lr-lb-list</code> <var>router</var></dt>
-      <dd>
-        Lists the LBs for the given <var>router</var>.
-      </dd>
-    </dl>
-
-
-    <h1>DHCP Options commands</h1>
-
-    <dl>
-      <dt><code>dhcp-options-create</code> <var>cidr</var> [<var>key=value</var>]</dt>
-      <dd>
-        Creates a new DHCP Options entry in the <code>DHCP_Options</code> table
-        with the specified <code>cidr</code> and optional <code>external-ids</code>.
-      </dd>
-
-      <dt><code>dhcp-options-list</code></dt>
-      <dd>
-        Lists the DHCP Options entries.
-      </dd>
-
-      <dt><code>dhcp-options-del</code> <var>dhcp-option</var></dt>
-      <dd>
-        Deletes the DHCP Options entry referred by <var>dhcp-option</var> UUID.
-      </dd>
-
-      <dt><code>dhcp-options-set-options</code> <var>dhcp-option</var> [<var>key=value</var>]...</dt>
-      <dd>
-        Set the DHCP Options for the <var>dhcp-option</var> UUID.
-      </dd>
-
-      <dt><code>dhcp-options-get-options</code> <var>dhcp-option</var></dt>
-      <dd>
-        Lists the DHCP Options for the <var>dhcp-option</var> UUID.
-      </dd>
-    </dl>
-
-    <h1>Port Group commands</h1>
-
-    <dl>
-      <dt><code>pg-add</code> <var>group</var> [<var>port</var>]...</dt>
-      <dd>
-        Creates a new port group in the <code>Port_Group</code> table named
-        <code>group</code> with optional <code>ports</code> added to the group.
-      </dd>
-
-      <dt><code>pg-set-ports</code> <var>group</var> <var>port</var>...</dt>
-      <dd>
-        Sets <code>ports</code> on the port group named <code>group</code>. It
-        is an error if <code>group</code> does not exist.
-      </dd>
-
-      <dt><code>pg-del</code> <var>group</var></dt>
-      <dd>
-        Deletes port group <code>group</code>. It is an error if
-        <code>group</code> does not exist.
-      </dd>
-    </dl>
-
-    <h1> HA Chassis Group commands</h1>
-
-    <dl>
-      <dt><code>ha-chassis-group-add</code> <var>group</var></dt>
-      <dd>
-        Creates a new HA chassis group in the <code>HA_Chassis_Group</code>
-        table named <code>group</code>.
-      </dd>
-
-      <dt><code>ha-chassis-group-del</code> <var>group</var></dt>
-      <dd>
-        Deletes the HA chassis group <code>group</code>. It is an error if
-        <code>group</code> does not exist.
-      </dd>
-
-      <dt><code>ha-chassis-group-list</code></dt>
-      <dd>
-        Lists the HA chassis group <code>group</code> along with the
-        <code>HA chassis</code> if any associated with it.
-      </dd>
-
-      <dt><code>ha-chassis-group-add-chassis</code> <var>group</var>
-      <var>chassis</var> <var>priority</var></dt>
-      <dd>
-        Adds a new HA chassis <code>chassis</code> to the
-        HA Chassis group <code>group</code> with the specified priority.
-        If the <code>chassis</code> already exists, then the
-        <code>priority</code> is updated.
-        The <code>chassis</code> should be the name of the chassis in the
-        <code>OVN_Southbound</code>.
-      </dd>
-
-      <dt><code>ha-chassis-group-remove-chassis</code> <var>group</var>
-      <var>chassis</var></dt>
-      <dd>
-        Removes the HA chassis <code>chassis</code> from the HA chassis
-        group <code>group</code>. It is an error if <code>chassis</code> does
-        not exist.
-      </dd>
-    </dl>
-
-    <h1>Database Commands</h1>
-    <p>These commands query and modify the contents of <code>ovsdb</code> tables.
-    They are a slight abstraction of the <code>ovsdb</code> interface and
-    as such they operate at a lower level than other <code>ovn-nbctl</code> commands.</p>
-    <p><var>Identifying Tables, Records, and Columns</var></p>
-    <p>Each of these commands has a <var>table</var> parameter to identify a table
-    within the database.  Many of them also take a <var>record</var> parameter
-    that identifies a particular record within a table.  The <var>record</var>
-    parameter may be the UUID for a record, which may be abbreviated to its
-    first 4 (or more) hex digits, as long as that is unique.  Many tables offer
-    additional ways to identify records.  Some commands also take
-    <var>column</var> parameters that identify a particular field within the
-    records in a table.</p>
-
-    <p>
-      For a list of tables and their columns, see <code>ovn-nb</code>(5) or
-      see the table listing from the <code>--help</code> option.
-    </p>
-
-    <p>
-      Record names must be specified in full and with correct capitalization,
-      except that UUIDs may be abbreviated to their first 4 (or more) hex
-      digits, as long as that is unique within the table.  Names of tables and
-      columns are not case-sensitive, and <code>-</code> and <code>_</code> are
-      treated interchangeably.  Unique abbreviations of table and column names
-      are acceptable, e.g. <code>d</code> or <code>dhcp</code> is sufficient
-      to identify the <code>DHCP_Options</code> table.
-    </p>
-
-    <xi:include href="lib/db-ctl-base.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h1>Synchronization Commands</h1>
-
-    <dl>
-      <dt>sync</dt>
-      <dd>
-        Ordinarily, <code>--wait=sb</code> or <code>--wait=hv</code> only waits
-        for changes by the current <code>ovn-nbctl</code> invocation to take
-        effect.  This means that, if none of the commands supplied to
-        <code>ovn-nbctl</code> change the database, then the command does not
-        wait at all.  With the <code>sync</code> command, however,
-        <code>ovn-nbctl</code> waits even for earlier changes to the database
-        to propagate down to the southbound database or all of the OVN chassis,
-        according to the argument to <code>--wait</code>.
-      </dd>
-    </dl>
-
-    <h1>Remote Connectivity Commands</h1>
-    <dl>
-      <dt><code>get-connection</code></dt>
-      <dd>
-      Prints the configured connection(s).
-      </dd>
-
-      <dt><code>del-connection</code></dt>
-      <dd>
-      Deletes the configured connection(s).
-      </dd>
-
-      <dt>[<code>--inactivity-probe=</code><var>msecs</var>] <code>set-connection</code> <var>target</var>...</dt>
-      <dd>
-        Sets the configured manager target or targets.  Use
-        <code>--inactivity-probe=</code><var>msecs</var> to override the default
-        idle connection inactivity probe time.  Use 0 to disable inactivity probes.
-      </dd>
-    </dl>
-
-    <h1>SSL Configuration Commands</h1>
-    <dl>
-      <dt><code>get-ssl</code></dt>
-      <dd>
-      Prints the SSL configuration.
-      </dd>
-
-      <dt><code>del-ssl</code></dt>
-      <dd>
-      Deletes the current SSL configuration.
-      </dd>
-
-      <dt>[<code>--bootstrap</code>] <code>set-ssl</code>
-         <var>private-key</var> <var>certificate</var> <var>ca-cert</var>
-         [<var>ssl-protocol-list</var> [<var>ssl-cipher-list</var>]]</dt>
-      <dd>
-      Sets the SSL configuration.
-      </dd>
-    </dl>
-
-    <h1>Daemon Mode</h1>
-
-    <p>
-      When it is invoked in the most ordinary way, <code>ovn-nbctl</code>
-      connects to an OVSDB server that hosts the northbound database, retrieves
-      a partial copy of the database that is complete enough to do its work,
-      sends a transaction request to the server, and receives and processes the
-      server's reply.  In common interactive use, this is fine, but if the
-      database is large, the step in which <code>ovn-nbctl</code> retrieves a
-      partial copy of the database can take a long time, which yields poor
-      performance overall.
-    </p>
-
-    <p>
-      To improve performance in such a case, <code>ovn-nbctl</code> offers a
-      "daemon mode," in which the user first starts <code>ovn-nbctl</code>
-      running in the background and afterward uses the daemon to execute
-      operations.  Over several <code>ovn-nbctl</code> command invocations,
-      this performs better overall because it retrieves a copy of the database
-      only once at the beginning, not once per program run.
-    </p>
-
-    <p>
-      Use the <code>--detach</code> option to start an <code>ovn-nbctl</code>
-      daemon.  With this option, <code>ovn-nbctl</code> prints the name of a
-      control socket to stdout.  The client should save this name in
-      environment variable <env>OVN_NB_DAEMON</env>.  Under the Bourne shell
-      this might be done like this:
-    </p>
-
-    <pre fixed="yes">
-      export OVN_NB_DAEMON=$(ovn-nbctl --pidfile --detach)
-    </pre>
-
-    <p>
-      When <env>OVN_NB_DAEMON</env> is set, <code>ovn-nbctl</code>
-      automatically and transparently uses the daemon to execute its commands.
-    </p>
-
-    <p>
-      When the daemon is no longer needed, kill it and unset the environment
-      variable, e.g.:
-    </p>
-
-    <pre fixed="yes">
-      kill $(cat /var/run/ovn-nbctl.pid)
-      unset OVN_NB_DAEMON
-    </pre>
-
-    <p>
-      Daemon mode is experimental.
-    </p>
-
-    <h2>Daemon Commands</h2>
-
-    <p>
-      Daemon mode is internally implemented using the same mechanism used by
-      <code>ovs-appctl</code>.  One may also use <code>ovs-appctl</code>
-      directly with the following commands:
-    </p>
-
-    <dl>
-      <dt>
-        <code>run</code> [<var>options</var>] <var>command</var>
-        [<var>arg</var>...] [<code>--</code> [<var>options</var>]
-        <var>command</var> [<var>arg</var>...] ...]
-      </dt>
-      <dd>
-        Instructs the daemon process to run one or more <code>ovn-nbctl</code>
-        commands described above and reply with the results of running these
-        commands. Accepts the <code>--no-wait</code>, <code>--wait</code>,
-        <code>--timeout</code>, <code>--dry-run</code>, <code>--oneline</code>,
-        and the options described under <code>Table Formatting Options</code>
-        in addition to the the command-specific options.
-      </dd>
-
-      <dt><code>exit</code></dt>
-      <dd>Causes <code>ovn-nbctl</code> to gracefully terminate.</dd>
-    </dl>
-
-    <h1>Options</h1>
-
-    <dl>
-      <dt><code>--no-wait</code> | <code>--wait=none</code></dt>
-      <dt><code>--wait=sb</code></dt>
-      <dt><code>--wait=hv</code></dt>
-
-      <dd>
-        <p>
-          These options control whether and how <code>ovn-nbctl</code> waits
-          for the OVN system to become up-to-date with changes made in an
-          <code>ovn-nbctl</code> invocation.
-        </p>
-
-        <p>
-          By default, or if <code>--no-wait</code> or <code>--wait=none</code>,
-          <code>ovn-nbctl</code> exits immediately after confirming that
-          changes have been committed to the northbound database, without
-          waiting.
-        </p>
-
-        <p>
-          With <code>--wait=sb</code>, before <code>ovn-nbctl</code> exits, it
-          waits for <code>ovn-northd</code> to bring the southbound database
-          up-to-date with the northbound database updates.
-        </p>
-
-        <p>
-          With <code>--wait=hv</code>, before <code>ovn-nbctl</code> exits, it
-          additionally waits for all OVN chassis (hypervisors and gateways) to
-          become up-to-date with the northbound database updates.  (This can
-          become an indefinite wait if any chassis is malfunctioning.)
-        </p>
-
-        <p>
-          Ordinarily, <code>--wait=sb</code> or <code>--wait=hv</code> only
-          waits for changes by the current <code>ovn-nbctl</code> invocation to
-          take effect.  This means that, if none of the commands supplied to
-          <code>ovn-nbctl</code> change the database, then the command does not
-          wait at all.  Use the <code>sync</code> command to override this
-          behavior.
-        </p>
-      </dd>
-
-    <dt><code>--db</code> <var>database</var></dt>
-    <dd>
-      The OVSDB database remote to contact.  If the <env>OVN_NB_DB</env>
-      environment variable is set, its value is used as the default.
-      Otherwise, the default is <code>unix:@RUNDIR@/ovnnb_db.sock</code>, but this
-      default is unlikely to be useful outside of single-machine OVN test
-      environments.
-    </dd>
-
-    <dt><code>--leader-only</code></dt>
-    <dt><code>--no-leader-only</code></dt>
-    <dd>
-      By default, or with <code>--leader-only</code>, when the database server
-      is a clustered database, <code>ovn-nbctl</code> will avoid servers other
-      than the cluster leader.  This ensures that any data that
-      <code>ovn-nbctl</code> reads and reports is up-to-date.  With
-      <code>--no-leader-only</code>, <code>ovn-nbctl</code> will use any server
-      in the cluster, which means that for read-only transactions it can report
-      and act on stale data (transactions that modify the database are always
-      serialized even with <code>--no-leader-only</code>).  Refer to
-      <code>Understanding Cluster Consistency</code> in <code>ovsdb</code>(7)
-      for more information.
-    </dd>
-
-    <dt><code>--shuffle-remotes</code></dt>
-    <dt><code>--no-shuffle-remotes</code></dt>
-    <dd>
-      By default, or with <code>--shuffle-remotes</code>, when there are
-      multiple remotes specified in the OVSDB connection string specified by
-      <code>--db</code> or the <env>OVN_NB_DB</env> environment variable,
-      the order of the remotes will be shuffled before the client tries to
-      connect.  The remotes will be shuffled only once to a new order before
-      the first connection attempt.  The following retries, if any, will
-      follow the same new order.  The default behavior is to make sure
-      clients of a clustered database can distribute evenly to all memembers
-      of the cluster.  With <code>--no-shuffle-remotes</code>,
-      <code>ovn-nbctl</code> will use the original order specified in the
-      connection string to connect.  This allows user to specify the
-      preferred order, which is particularly useful for testing.
-    </dd>
-    </dl>
-
-    <h2>Daemon Options</h2>
-    <xi:include href="lib/daemon.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h1>Logging options</h1>
-    <xi:include href="lib/vlog.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h1>Table Formatting Options</h1>
-    These options control the format of output from the <code>list</code> and
-    <code>find</code> commands.
-    <xi:include href="lib/table.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h2>PKI Options</h2>
-    <p>
-      PKI configuration is required to use SSL for the connection to the
-      database.
-    </p>
-    <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-    <xi:include href="lib/ssl-bootstrap.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h2>Other Options</h2>
-
-    <xi:include href="lib/common.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-</manpage>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
deleted file mode 100644
index 98a8faa0b153..000000000000
--- a/ovn/utilities/ovn-nbctl.c
+++ /dev/null
@@ -1,6061 +0,0 @@
-/*
- * 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.
- */
-
-#include <config.h>
-
-#include <getopt.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "command-line.h"
-#include "daemon.h"
-#include "db-ctl-base.h"
-#include "dirs.h"
-#include "fatal-signal.h"
-#include "jsonrpc.h"
-#include "openvswitch/json.h"
-#include "ovn/lib/acl-log.h"
-#include "ovn/lib/ovn-nb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "packets.h"
-#include "openvswitch/poll-loop.h"
-#include "process.h"
-#include "smap.h"
-#include "sset.h"
-#include "stream.h"
-#include "stream-ssl.h"
-#include "svec.h"
-#include "table.h"
-#include "timeval.h"
-#include "timer.h"
-#include "unixctl.h"
-#include "util.h"
-#include "openvswitch/vlog.h"
-
-VLOG_DEFINE_THIS_MODULE(nbctl);
-
-/* --db: The database server to contact. */
-static const char *db;
-
-/* --oneline: Write each command's output as a single line? */
-static bool oneline;
-
-/* --dry-run: Do not commit any changes. */
-static bool dry_run;
-
-/* --wait=TYPE: Wait for configuration change to take effect? */
-enum nbctl_wait_type {
-    NBCTL_WAIT_NONE,            /* Do not wait. */
-    NBCTL_WAIT_SB,              /* Wait for southbound database updates. */
-    NBCTL_WAIT_HV               /* Wait for hypervisors to catch up. */
-};
-static enum nbctl_wait_type wait_type = NBCTL_WAIT_NONE;
-
-/* Should we wait (if specified by 'wait_type') even if the commands don't
- * change the database at all? */
-static bool force_wait = false;
-
-/* --timeout: Time to wait for a connection to 'db'. */
-static unsigned int timeout;
-
-/* Format for table output. */
-static struct table_style table_style = TABLE_STYLE_DEFAULT;
-
-/* The IDL we're using and the current transaction, if any.
- * This is for use by nbctl_exit() only, to allow it to clean up.
- * Other code should use its context arguments. */
-static struct ovsdb_idl *the_idl;
-static struct ovsdb_idl_txn *the_idl_txn;
-OVS_NO_RETURN static void nbctl_exit(int status);
-
-/* --leader-only, --no-leader-only: Only accept the leader in a cluster. */
-static int leader_only = true;
-
-/* --shuffle-remotes, --no-shuffle-remotes: Shuffle the order of remotes that
- * are specified in the connetion method string. */
-static int shuffle_remotes = true;
-
-/* --unixctl-path: Path to use for unixctl server, for "monitor" and "snoop"
-     commands. */
-static char *unixctl_path;
-
-static unixctl_cb_func server_cmd_exit;
-static unixctl_cb_func server_cmd_run;
-
-static void nbctl_cmd_init(void);
-OVS_NO_RETURN static void usage(void);
-static struct option *get_all_options(void);
-static bool has_option(const struct ovs_cmdl_parsed_option *, size_t n,
-                       int option);
-static void nbctl_client(const char *socket_name,
-                         const struct ovs_cmdl_parsed_option *, size_t n,
-                         int argc, char *argv[]);
-static bool will_detach(const struct ovs_cmdl_parsed_option *, size_t n);
-static void apply_options_direct(const struct ovs_cmdl_parsed_option *,
-                                 size_t n, struct shash *local_options);
-static char * OVS_WARN_UNUSED_RESULT run_prerequisites(struct ctl_command[],
-                                                       size_t n_commands,
-                                                       struct ovsdb_idl *);
-static char * OVS_WARN_UNUSED_RESULT do_nbctl(const char *args,
-                                              struct ctl_command *, size_t n,
-                                              struct ovsdb_idl *,
-                                              const struct timer *,
-                                              bool *retry);
-static char * OVS_WARN_UNUSED_RESULT dhcp_options_get(
-    struct ctl_context *ctx, const char *id, bool must_exist,
-    const struct nbrec_dhcp_options **);
-static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args,
-                                               struct ctl_command *commands,
-                                               size_t n_commands,
-                                               struct ovsdb_idl *idl,
-                                               const struct timer *);
-static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]);
-
-int
-main(int argc, char *argv[])
-{
-    struct ovsdb_idl *idl;
-    struct shash local_options;
-
-    set_program_name(argv[0]);
-    fatal_ignore_sigpipe();
-    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
-    vlog_set_levels_from_string_assert("reconnect:warn");
-
-    nbctl_cmd_init();
-
-    /* ovn-nbctl has three operation modes:
-     *
-     *    - Direct: Executes commands by contacting ovsdb-server directly.
-     *
-     *    - Server: Runs in the background as a daemon waiting for requests
-     *      from ovn-nbctl running in client mode.
-     *
-     *    - Client: Executes commands by passing them to an ovn-nbctl running
-     *      in the server mode.
-     *
-     * At this point we don't know what mode we're running in.  The mode partly
-     * depends on the command line.  So, for now we transform the command line
-     * into a parsed form, and figure out what to do with it later.
-     */
-    char *args = process_escape_args(argv);
-    struct ovs_cmdl_parsed_option *parsed_options;
-    size_t n_parsed_options;
-    char *error_s = ovs_cmdl_parse_all(argc, argv, get_all_options(),
-                                       &parsed_options, &n_parsed_options);
-    if (error_s) {
-        free(args);
-        ctl_fatal("%s", error_s);
-    }
-
-    /* Now figure out the operation mode:
-     *
-     *    - A --detach option implies server mode.
-     *
-     *    - An OVN_NB_DAEMON environment variable implies client mode.
-     *
-     *    - Otherwise, we're in direct mode. */
-    char *socket_name = getenv("OVN_NB_DAEMON");
-    if (socket_name && socket_name[0]
-        && !will_detach(parsed_options, n_parsed_options)) {
-        nbctl_client(socket_name, parsed_options, n_parsed_options,
-                     argc, argv);
-    }
-
-    /* Parse command line. */
-    shash_init(&local_options);
-    apply_options_direct(parsed_options, n_parsed_options, &local_options);
-    free(parsed_options);
-
-    bool daemon_mode = false;
-    if (get_detach()) {
-        if (argc != optind) {
-            free(args);
-            ctl_fatal("non-option arguments not supported with --detach "
-                      "(use --help for help)");
-        }
-        daemon_mode = true;
-    }
-    /* Initialize IDL. */
-    idl = the_idl = ovsdb_idl_create_unconnected(&nbrec_idl_class, true);
-    ovsdb_idl_set_shuffle_remotes(idl, shuffle_remotes);
-    /* "retry" is true iff in daemon mode. */
-    ovsdb_idl_set_remote(idl, db, daemon_mode);
-    ovsdb_idl_set_leader_only(idl, leader_only);
-
-    if (daemon_mode) {
-        server_loop(idl, argc, argv);
-    } else {
-        struct ctl_command *commands;
-        size_t n_commands;
-        char *error;
-
-        error = ctl_parse_commands(argc - optind, argv + optind,
-                                   &local_options, &commands, &n_commands);
-        if (error) {
-            free(args);
-            ctl_fatal("%s", error);
-        }
-        VLOG(ctl_might_write_to_db(commands, n_commands) ? VLL_INFO : VLL_DBG,
-             "Called as %s", args);
-
-        ctl_timeout_setup(timeout);
-
-        error = run_prerequisites(commands, n_commands, idl);
-        if (error) {
-            free(args);
-            ctl_fatal("%s", error);
-        }
-
-        error = main_loop(args, commands, n_commands, idl, NULL);
-        if (error) {
-            free(args);
-            ctl_fatal("%s", error);
-        }
-
-        struct ctl_command *c;
-        for (c = commands; c < &commands[n_commands]; c++) {
-            ds_destroy(&c->output);
-            table_destroy(c->table);
-            free(c->table);
-            shash_destroy_free_data(&c->options);
-        }
-        free(commands);
-    }
-
-    ovsdb_idl_destroy(idl);
-    idl = the_idl = NULL;
-
-    free(args);
-    exit(EXIT_SUCCESS);
-}
-
-static char *
-main_loop(const char *args, struct ctl_command *commands, size_t n_commands,
-          struct ovsdb_idl *idl, const struct timer *wait_timeout)
-{
-    unsigned int seqno;
-    bool idl_ready;
-
-    /* Execute the commands.
-     *
-     * 'seqno' is the database sequence number for which we last tried to
-     * execute our transaction.  There's no point in trying to commit more than
-     * once for any given sequence number, because if the transaction fails
-     * it's because the database changed and we need to obtain an up-to-date
-     * view of the database before we try the transaction again. */
-    seqno = ovsdb_idl_get_seqno(idl);
-
-    /* IDL might have already obtained the database copy during previous
-     * invocation. If so, we can't expect the sequence number to change before
-     * we issue any new requests. */
-    idl_ready = ovsdb_idl_has_ever_connected(idl);
-    for (;;) {
-        ovsdb_idl_run(idl);
-        if (!ovsdb_idl_is_alive(idl)) {
-            int retval = ovsdb_idl_get_last_error(idl);
-            ctl_fatal("%s: database connection failed (%s)",
-                      db, ovs_retval_to_string(retval));
-        }
-
-        if (idl_ready || seqno != ovsdb_idl_get_seqno(idl)) {
-            idl_ready = false;
-            seqno = ovsdb_idl_get_seqno(idl);
-
-            bool retry;
-            char *error = do_nbctl(args, commands, n_commands, idl,
-                                   wait_timeout, &retry);
-            if (error) {
-                return error;
-            }
-            if (!retry) {
-                return NULL;
-            }
-        }
-
-        if (seqno == ovsdb_idl_get_seqno(idl)) {
-            ovsdb_idl_wait(idl);
-            poll_block();
-        }
-    }
-
-    return NULL;
-}
-
-/* All options that affect the main loop and are not external. */
-#define MAIN_LOOP_OPTION_ENUMS                  \
-        OPT_NO_WAIT,                            \
-        OPT_WAIT,                               \
-        OPT_DRY_RUN,                            \
-        OPT_ONELINE
-
-#define MAIN_LOOP_LONG_OPTIONS                           \
-        {"no-wait", no_argument, NULL, OPT_NO_WAIT},     \
-        {"wait", required_argument, NULL, OPT_WAIT},     \
-        {"dry-run", no_argument, NULL, OPT_DRY_RUN},     \
-        {"oneline", no_argument, NULL, OPT_ONELINE},     \
-        {"timeout", required_argument, NULL, 't'}
-
-enum {
-    OPT_DB = UCHAR_MAX + 1,
-    OPT_NO_SYSLOG,
-    OPT_LOCAL,
-    OPT_COMMANDS,
-    OPT_OPTIONS,
-    OPT_LEADER_ONLY,
-    OPT_NO_LEADER_ONLY,
-    OPT_SHUFFLE_REMOTES,
-    OPT_NO_SHUFFLE_REMOTES,
-    OPT_BOOTSTRAP_CA_CERT,
-    MAIN_LOOP_OPTION_ENUMS,
-    DAEMON_OPTION_ENUMS,
-    VLOG_OPTION_ENUMS,
-    TABLE_OPTION_ENUMS,
-    SSL_OPTION_ENUMS,
-};
-
-static char * OVS_WARN_UNUSED_RESULT
-handle_main_loop_option(int opt, const char *arg, bool *handled)
-{
-    ovs_assert(handled);
-    *handled = true;
-
-    switch (opt) {
-    case OPT_ONELINE:
-        oneline = true;
-        break;
-
-    case OPT_NO_WAIT:
-        wait_type = NBCTL_WAIT_NONE;
-        break;
-
-    case OPT_WAIT:
-        if (!strcmp(arg, "none")) {
-            wait_type = NBCTL_WAIT_NONE;
-        } else if (!strcmp(arg, "sb")) {
-            wait_type = NBCTL_WAIT_SB;
-        } else if (!strcmp(arg, "hv")) {
-            wait_type = NBCTL_WAIT_HV;
-        } else {
-            return xstrdup("argument to --wait must be "
-                           "\"none\", \"sb\", or \"hv\"");
-        }
-        break;
-
-    case OPT_DRY_RUN:
-        dry_run = true;
-        break;
-
-    case 't':
-        if (!str_to_uint(arg, 10, &timeout) || !timeout) {
-            return xasprintf("value %s on -t or --timeout is invalid", arg);
-        }
-        break;
-
-    default:
-        *handled = false;
-        break;
-    }
-
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-build_short_options(const struct option *long_options, bool print_errors)
-{
-    char *tmp, *short_options;
-
-    tmp = ovs_cmdl_long_options_to_short_options(long_options);
-    short_options = xasprintf("+%s%s", print_errors ? "" : ":", tmp);
-    free(tmp);
-
-    return short_options;
-}
-
-static struct option * OVS_WARN_UNUSED_RESULT
-append_command_options(const struct option *options, int opt_val)
-{
-    struct option *o;
-    size_t n_allocated;
-    size_t n_existing;
-    int i;
-
-    for (i = 0; options[i].name; i++) {
-        ;
-    }
-    n_allocated = i + 1;
-    n_existing = i;
-
-    /* We want to parse both global and command-specific options here, but
-     * getopt_long() isn't too convenient for the job.  We copy our global
-     * options into a dynamic array, then append all of the command-specific
-     * options. */
-    o = xmemdup(options, n_allocated * sizeof *options);
-    ctl_add_cmd_options(&o, &n_existing, &n_allocated, opt_val);
-
-    return o;
-}
-
-static struct option *
-get_all_options(void)
-{
-    static const struct option global_long_options[] = {
-        {"db", required_argument, NULL, OPT_DB},
-        {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
-        {"help", no_argument, NULL, 'h'},
-        {"commands", no_argument, NULL, OPT_COMMANDS},
-        {"options", no_argument, NULL, OPT_OPTIONS},
-        {"leader-only", no_argument, NULL, OPT_LEADER_ONLY},
-        {"no-leader-only", no_argument, NULL, OPT_NO_LEADER_ONLY},
-        {"shuffle-remotes", no_argument, NULL, OPT_SHUFFLE_REMOTES},
-        {"no-shuffle-remotes", no_argument, NULL, OPT_NO_SHUFFLE_REMOTES},
-        {"version", no_argument, NULL, 'V'},
-        MAIN_LOOP_LONG_OPTIONS,
-        DAEMON_LONG_OPTIONS,
-        VLOG_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
-        TABLE_LONG_OPTIONS,
-        {NULL, 0, NULL, 0},
-    };
-
-    static struct option *options;
-    if (!options) {
-        options = append_command_options(global_long_options, OPT_LOCAL);
-    }
-
-    return options;
-}
-
-static bool
-has_option(const struct ovs_cmdl_parsed_option *parsed_options, size_t n,
-           int option)
-{
-    for (const struct ovs_cmdl_parsed_option *po = parsed_options;
-         po < &parsed_options[n]; po++) {
-        if (po->o->val == option) {
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool
-will_detach(const struct ovs_cmdl_parsed_option *parsed_options, size_t n)
-{
-    return has_option(parsed_options, n, OPT_DETACH);
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-add_local_option(const char *name, const char *arg,
-                 struct shash *local_options)
-{
-    char *full_name = xasprintf("--%s", name);
-    if (shash_find(local_options, full_name)) {
-        char *error = xasprintf("'%s' option specified multiple times",
-                                full_name);
-        free(full_name);
-        return error;
-    }
-    shash_add_nocopy(local_options, full_name, nullable_xstrdup(arg));
-    return NULL;
-}
-
-static void
-apply_options_direct(const struct ovs_cmdl_parsed_option *parsed_options,
-                     size_t n, struct shash *local_options)
-{
-    for (const struct ovs_cmdl_parsed_option *po = parsed_options;
-         po < &parsed_options[n]; po++) {
-        bool handled;
-        char *error = handle_main_loop_option(po->o->val, po->arg, &handled);
-        if (error) {
-            ctl_fatal("%s", error);
-        }
-        if (handled) {
-            continue;
-        }
-
-        optarg = po->arg;
-        switch (po->o->val) {
-        case OPT_DB:
-            db = po->arg;
-            break;
-
-        case OPT_NO_SYSLOG:
-            vlog_set_levels(&this_module, VLF_SYSLOG, VLL_WARN);
-            break;
-
-        case OPT_LOCAL:
-            error = add_local_option(po->o->name, po->arg, local_options);
-            if (error) {
-                ctl_fatal("%s", error);
-            }
-            break;
-
-        case 'h':
-            usage();
-            exit(EXIT_SUCCESS);
-
-        case OPT_COMMANDS:
-            ctl_print_commands();
-            /* fall through */
-
-        case OPT_OPTIONS:
-            ctl_print_options(get_all_options());
-            /* fall through */
-
-        case OPT_LEADER_ONLY:
-            leader_only = true;
-            break;
-
-        case OPT_NO_LEADER_ONLY:
-            leader_only = false;
-            break;
-
-        case OPT_SHUFFLE_REMOTES:
-            shuffle_remotes = true;
-            break;
-
-        case OPT_NO_SHUFFLE_REMOTES:
-            shuffle_remotes = false;
-            break;
-
-        case 'V':
-            ovs_print_version(0, 0);
-            printf("DB Schema %s\n", nbrec_get_db_version());
-            exit(EXIT_SUCCESS);
-
-        DAEMON_OPTION_HANDLERS
-        VLOG_OPTION_HANDLERS
-        TABLE_OPTION_HANDLERS(&table_style)
-        STREAM_SSL_OPTION_HANDLERS
-
-        case OPT_BOOTSTRAP_CA_CERT:
-            stream_ssl_set_ca_cert_file(po->arg, true);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            abort();
-
-        case 0:
-            break;
-        }
-    }
-
-    if (!db) {
-        db = default_nb_db();
-    }
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN northbound DB management utility\n\
-usage: %s [OPTIONS] COMMAND [ARG...]\n\
-\n\
-General commands:\n\
-  init                      initialize the database\n\
-  show                      print overview of database contents\n\
-  show SWITCH               print overview of database contents for SWITCH\n\
-  show ROUTER               print overview of database contents for ROUTER\n\
-\n\
-Logical switch commands:\n\
-  ls-add [SWITCH]           create a logical switch named SWITCH\n\
-  ls-del SWITCH             delete SWITCH and all its ports\n\
-  ls-list                   print the names of all logical switches\n\
-\n\
-ACL commands:\n\
-  [--type={switch | port-group}] [--log] [--severity=SEVERITY] [--name=NAME] [--may-exist]\n\
-  acl-add {SWITCH | PORTGROUP} DIRECTION PRIORITY MATCH ACTION\n\
-                            add an ACL to SWITCH/PORTGROUP\n\
-  [--type={switch | port-group}]\n\
-  acl-del {SWITCH | PORTGROUP} [DIRECTION [PRIORITY MATCH]]\n\
-                            remove ACLs from SWITCH/PORTGROUP\n\
-  [--type={switch | port-group}]\n\
-  acl-list {SWITCH | PORTGROUP}\n\
-                            print ACLs for SWITCH\n\
-\n\
-QoS commands:\n\
-  qos-add SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]\n\
-                            add an QoS rule to SWITCH\n\
-  qos-del SWITCH [DIRECTION [PRIORITY MATCH]]\n\
-                            remove QoS rules from SWITCH\n\
-  qos-list SWITCH           print QoS rules for SWITCH\n\
-\n\
-Meter commands:\n\
-  meter-add NAME ACTION RATE UNIT [BURST]\n\
-                            add a meter\n\
-  meter-del [NAME]          remove meters\n\
-  meter-list                print meters\n\
-\n\
-Logical switch port commands:\n\
-  lsp-add SWITCH PORT       add logical port PORT on SWITCH\n\
-  lsp-add SWITCH PORT PARENT TAG\n\
-                            add logical port PORT on SWITCH with PARENT\n\
-                            on TAG\n\
-  lsp-del PORT              delete PORT from its attached switch\n\
-  lsp-list SWITCH           print the names of all logical ports on SWITCH\n\
-  lsp-get-parent PORT       get the parent of PORT if set\n\
-  lsp-get-tag PORT          get the PORT's tag if set\n\
-  lsp-set-addresses PORT [ADDRESS]...\n\
-                            set MAC or MAC+IP addresses for PORT.\n\
-  lsp-get-addresses PORT    get a list of MAC or MAC+IP addresses on PORT\n\
-  lsp-set-port-security PORT [ADDRS]...\n\
-                            set port security addresses for PORT.\n\
-  lsp-get-port-security PORT    get PORT's port security addresses\n\
-  lsp-get-up PORT           get state of PORT ('up' or 'down')\n\
-  lsp-set-enabled PORT STATE\n\
-                            set administrative state PORT\n\
-                            ('enabled' or 'disabled')\n\
-  lsp-get-enabled PORT      get administrative state PORT\n\
-                            ('enabled' or 'disabled')\n\
-  lsp-set-type PORT TYPE    set the type for PORT\n\
-  lsp-get-type PORT         get the type for PORT\n\
-  lsp-set-options PORT KEY=VALUE [KEY=VALUE]...\n\
-                            set options related to the type of PORT\n\
-  lsp-get-options PORT      get the type specific options for PORT\n\
-  lsp-set-dhcpv4-options PORT [DHCP_OPTIONS_UUID]\n\
-                            set dhcpv4 options for PORT\n\
-  lsp-get-dhcpv4-options PORT  get the dhcpv4 options for PORT\n\
-  lsp-set-dhcpv6-options PORT [DHCP_OPTIONS_UUID]\n\
-                            set dhcpv6 options for PORT\n\
-  lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
-  lsp-get-ls PORT           get the logical switch which the port belongs to\n\
-\n\
-Logical router commands:\n\
-  lr-add [ROUTER]           create a logical router named ROUTER\n\
-  lr-del ROUTER             delete ROUTER and all its ports\n\
-  lr-list                   print the names of all logical routers\n\
-\n\
-Logical router port commands:\n\
-  lrp-add ROUTER PORT MAC NETWORK... [peer=PEER]\n\
-                            add logical port PORT on ROUTER\n\
-  lrp-set-gateway-chassis PORT CHASSIS [PRIORITY]\n\
-                            set gateway chassis for port PORT\n\
-  lrp-del-gateway-chassis PORT CHASSIS\n\
-                            delete gateway chassis from port PORT\n\
-  lrp-get-gateway-chassis PORT\n\
-                            print the names of all gateway chassis on PORT\n\
-                            with PRIORITY\n\
-  lrp-del PORT              delete PORT from its attached router\n\
-  lrp-list ROUTER           print the names of all ports on ROUTER\n\
-  lrp-set-enabled PORT STATE\n\
-                            set administrative state PORT\n\
-                            ('enabled' or 'disabled')\n\
-  lrp-get-enabled PORT      get administrative state PORT\n\
-                            ('enabled' or 'disabled')\n\
-\n\
-Route commands:\n\
-  [--policy=POLICY] lr-route-add ROUTER PREFIX NEXTHOP [PORT]\n\
-                            add a route to ROUTER\n\
-  lr-route-del ROUTER [PREFIX]\n\
-                            remove routes from ROUTER\n\
-  lr-route-list ROUTER      print routes for ROUTER\n\
-\n\
-Policy commands:\n\
-  lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP]\n\
-                            add a policy to router\n\
-  lr-policy-del ROUTER [PRIORITY [MATCH]]\n\
-                            remove policies from ROUTER\n\
-  lr-policy-list ROUTER     print policies for ROUTER\n\
-\n\
-NAT commands:\n\
-  lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\
-                            add a NAT to ROUTER\n\
-  lr-nat-del ROUTER [TYPE [IP]]\n\
-                            remove NATs from ROUTER\n\
-  lr-nat-list ROUTER        print NATs for ROUTER\n\
-\n\
-LB commands:\n\
-  lb-add LB VIP[:PORT] IP[:PORT]... [PROTOCOL]\n\
-                            create a load-balancer or add a VIP to an\n\
-                            existing load balancer\n\
-  lb-del LB [VIP]           remove a load-balancer or just the VIP from\n\
-                            the load balancer\n\
-  lb-list [LB]              print load-balancers\n\
-  lr-lb-add ROUTER LB       add a load-balancer to ROUTER\n\
-  lr-lb-del ROUTER [LB]     remove load-balancers from ROUTER\n\
-  lr-lb-list ROUTER         print load-balancers\n\
-  ls-lb-add SWITCH LB       add a load-balancer to SWITCH\n\
-  ls-lb-del SWITCH [LB]     remove load-balancers from SWITCH\n\
-  ls-lb-list SWITCH         print load-balancers\n\
-\n\
-DHCP Options commands:\n\
-  dhcp-options-create CIDR [EXTERNAL_IDS]\n\
-                           create a DHCP options row with CIDR\n\
-  dhcp-options-del DHCP_OPTIONS_UUID\n\
-                           delete DHCP_OPTIONS_UUID\n\
-  dhcp-options-list        \n\
-                           lists the DHCP_Options rows\n\
-  dhcp-options-set-options DHCP_OPTIONS_UUID  KEY=VALUE [KEY=VALUE]...\n\
-                           set DHCP options for DHCP_OPTIONS_UUID\n\
-  dhcp-options-get-options DHCO_OPTIONS_UUID \n\
-                           displays the DHCP options for DHCP_OPTIONS_UUID\n\
-\n\
-Connection commands:\n\
-  get-connection             print the connections\n\
-  del-connection             delete the connections\n\
-  [--inactivity-probe=MSECS]\n\
-  set-connection TARGET...   set the list of connections to TARGET...\n\
-\n\
-SSL commands:\n\
-  get-ssl                     print the SSL configuration\n\
-  del-ssl                     delete the SSL configuration\n\
-  set-ssl PRIV-KEY CERT CA-CERT [SSL-PROTOS [SSL-CIPHERS]] \
-set the SSL configuration\n\
-Port group commands:\n\
-  pg-add PG [PORTS]           Create port group PG with optional PORTS\n\
-  pg-set-ports PG PORTS       Set PORTS on port group PG\n\
-  pg-del PG                   Delete port group PG\n\n",
-            program_name, program_name);
-    printf("\
-HA chassis group commands:\n\
-  ha-chassis-group-add GRP  Create an HA chassis group GRP\n\
-  ha-chassis-group-del GRP  Delete the HA chassis group GRP\n\
-  ha-chassis-group-list     List the HA chassis groups\n\
-  ha-chassis-group-add-chassis GRP CHASSIS [PRIORITY] Adds an HA\
-chassis with optional PRIORITY to the HA chassis group GRP\n\
-  ha-chassis-group-del-chassis GRP CHASSIS Deletes the HA chassis\
-CHASSIS from the HA chassis group GRP\n\
-\n\
-%s\
-%s\
-\n\
-Synchronization command (use with --wait=sb|hv):\n\
-  sync                     wait even for earlier changes to take effect\n\
-\n\
-Options:\n\
-  --db=DATABASE               connect to DATABASE\n\
-                              (default: %s)\n\
-  --no-wait, --wait=none      do not wait for OVN reconfiguration (default)\n\
-  --no-leader-only            accept any cluster member, not just the leader\n\
-  --no-shuffle-remotes        do not shuffle the order of remotes\n\
-  --wait=sb                   wait for southbound database update\n\
-  --wait=hv                   wait for all chassis to catch up\n\
-  -t, --timeout=SECS          wait at most SECS seconds\n\
-  --dry-run                   do not commit changes to database\n\
-  --oneline                   print exactly one line of output per command\n",
-           ctl_get_db_cmd_usage(),
-           ctl_list_db_tables_usage(), default_nb_db());
-    table_usage();
-    daemon_usage();
-    vlog_usage();
-    printf("\
-  --no-syslog             equivalent to --verbose=nbctl:syslog:warn\n");
-    printf("\n\
-Other options:\n\
-  -h, --help                  display this help message\n\
-  -V, --version               display version information\n");
-    stream_usage("database", true, true, true);
-    exit(EXIT_SUCCESS);
-}
-
-/* One should not use ctl_fatal() within commands because it will kill the
- * daemon if we're in daemon mode.  Use ctl_error() instead and return
- * gracefully.  */
-#define ctl_fatal dont_use_ctl_fatal_use_ctl_error_and_return
-
-/* Find a logical router given its id. */
-static char * OVS_WARN_UNUSED_RESULT
-lr_by_name_or_uuid(struct ctl_context *ctx, const char *id,
-                   bool must_exist, const struct nbrec_logical_router **lr_p)
-{
-    const struct nbrec_logical_router *lr = NULL;
-    bool is_uuid = false;
-    struct uuid lr_uuid;
-
-    *lr_p = NULL;
-    if (uuid_from_string(&lr_uuid, id)) {
-        is_uuid = true;
-        lr = nbrec_logical_router_get_for_uuid(ctx->idl, &lr_uuid);
-    }
-
-    if (!lr) {
-        const struct nbrec_logical_router *iter;
-
-        NBREC_LOGICAL_ROUTER_FOR_EACH(iter, ctx->idl) {
-            if (strcmp(iter->name, id)) {
-                continue;
-            }
-            if (lr) {
-                return xasprintf("Multiple logical routers named '%s'.  "
-                                 "Use a UUID.", id);
-            }
-            lr = iter;
-        }
-    }
-
-    if (!lr && must_exist) {
-        return xasprintf("%s: router %s not found",
-                         id, is_uuid ? "UUID" : "name");
-    }
-
-    *lr_p = lr;
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-ls_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-                   const struct nbrec_logical_switch **ls_p)
-{
-    const struct nbrec_logical_switch *ls = NULL;
-    *ls_p = NULL;
-
-    struct uuid ls_uuid;
-    bool is_uuid = uuid_from_string(&ls_uuid, id);
-    if (is_uuid) {
-        ls = nbrec_logical_switch_get_for_uuid(ctx->idl, &ls_uuid);
-    }
-
-    if (!ls) {
-        const struct nbrec_logical_switch *iter;
-
-        NBREC_LOGICAL_SWITCH_FOR_EACH(iter, ctx->idl) {
-            if (strcmp(iter->name, id)) {
-                continue;
-            }
-            if (ls) {
-                return xasprintf("Multiple logical switches named '%s'.  "
-                                 "Use a UUID.", id);
-            }
-            ls = iter;
-        }
-    }
-
-    if (!ls && must_exist) {
-        return xasprintf("%s: switch %s not found",
-                         id, is_uuid ? "UUID" : "name");
-    }
-
-    *ls_p = ls;
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-lb_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-                   const struct nbrec_load_balancer **lb_p)
-{
-    const struct nbrec_load_balancer *lb = NULL;
-
-    struct uuid lb_uuid;
-    bool is_uuid = uuid_from_string(&lb_uuid, id);
-    if (is_uuid) {
-        lb = nbrec_load_balancer_get_for_uuid(ctx->idl, &lb_uuid);
-    }
-
-    if (!lb) {
-        const struct nbrec_load_balancer *iter;
-
-        NBREC_LOAD_BALANCER_FOR_EACH(iter, ctx->idl) {
-            if (strcmp(iter->name, id)) {
-                continue;
-            }
-            if (lb) {
-                return xasprintf("Multiple load balancers named '%s'.  "
-                                 "Use a UUID.", id);
-            }
-            lb = iter;
-        }
-    }
-
-    if (!lb && must_exist) {
-        return xasprintf("%s: load balancer %s not found", id,
-                         is_uuid ? "UUID" : "name");
-    }
-
-    if (lb_p) {
-        *lb_p = lb;
-    }
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-pg_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-                   const struct nbrec_port_group **pg_p)
-{
-    const struct nbrec_port_group *pg = NULL;
-    *pg_p = NULL;
-
-    struct uuid pg_uuid;
-    bool is_uuid = uuid_from_string(&pg_uuid, id);
-    if (is_uuid) {
-        pg = nbrec_port_group_get_for_uuid(ctx->idl, &pg_uuid);
-    }
-
-    if (!pg) {
-        const struct nbrec_port_group *iter;
-
-        NBREC_PORT_GROUP_FOR_EACH (iter, ctx->idl) {
-            if (!strcmp(iter->name, id)) {
-                pg = iter;
-                break;
-            }
-        }
-    }
-
-    if (!pg && must_exist) {
-        return xasprintf("%s: port group %s not found", id,
-                         is_uuid ? "UUID" : "name");
-    }
-
-    *pg_p = pg;
-    return NULL;
-}
-
-static void
-print_alias(const struct smap *external_ids, const char *key, struct ds *s)
-{
-    const char *alias = smap_get(external_ids, key);
-    if (alias && alias[0]) {
-        ds_put_format(s, " (aka %s)", alias);
-    }
-}
-
-/* gateway_chassis ordering
- *  */
-static int
-compare_chassis_prio_(const void *gc1_, const void *gc2_)
-{
-    const struct nbrec_gateway_chassis *const *gc1p = gc1_;
-    const struct nbrec_gateway_chassis *const *gc2p = gc2_;
-    const struct nbrec_gateway_chassis *gc1 = *gc1p;
-    const struct nbrec_gateway_chassis *gc2 = *gc2p;
-
-    int prio_diff = gc2->priority - gc1->priority;
-    if (!prio_diff) {
-        return strcmp(gc2->name, gc1->name);
-    }
-    return prio_diff;
-}
-
-static const struct nbrec_gateway_chassis **
-get_ordered_gw_chassis_prio_list(const struct nbrec_logical_router_port *lrp)
-{
-    const struct nbrec_gateway_chassis **gcs;
-    int i;
-
-    gcs = xmalloc(sizeof *gcs * lrp->n_gateway_chassis);
-    for (i = 0; i < lrp->n_gateway_chassis; i++) {
-        gcs[i] = lrp->gateway_chassis[i];
-    }
-
-    qsort(gcs, lrp->n_gateway_chassis, sizeof *gcs, compare_chassis_prio_);
-    return gcs;
-}
-
-/* Given pointer to logical router, this routine prints the router
- * information.  */
-static void
-print_lr(const struct nbrec_logical_router *lr, struct ds *s)
-{
-    ds_put_format(s, "router "UUID_FMT" (%s)",
-                  UUID_ARGS(&lr->header_.uuid), lr->name);
-    print_alias(&lr->external_ids, "neutron:router_name", s);
-    ds_put_char(s, '\n');
-
-    for (size_t i = 0; i < lr->n_ports; i++) {
-        const struct nbrec_logical_router_port *lrp = lr->ports[i];
-        ds_put_format(s, "    port %s\n", lrp->name);
-        if (lrp->mac) {
-            ds_put_cstr(s, "        mac: ");
-            ds_put_format(s, "\"%s\"\n", lrp->mac);
-        }
-        if (lrp->n_networks) {
-            ds_put_cstr(s, "        networks: [");
-            for (size_t j = 0; j < lrp->n_networks; j++) {
-                ds_put_format(s, "%s\"%s\"",
-                        j == 0 ? "" : ", ",
-                        lrp->networks[j]);
-            }
-            ds_put_cstr(s, "]\n");
-        }
-
-        if (lrp->n_gateway_chassis) {
-            const struct nbrec_gateway_chassis **gcs;
-
-            gcs = get_ordered_gw_chassis_prio_list(lrp);
-            ds_put_cstr(s, "        gateway chassis: [");
-            for (size_t j = 0; j < lrp->n_gateway_chassis; j++) {
-                const struct nbrec_gateway_chassis *gc = gcs[j];
-                ds_put_format(s, "%s ", gc->chassis_name);
-            }
-            ds_chomp(s, ' ');
-            ds_put_cstr(s, "]\n");
-            free(gcs);
-        }
-    }
-
-    for (size_t i = 0; i < lr->n_nat; i++) {
-        const struct nbrec_nat *nat = lr->nat[i];
-        ds_put_format(s, "    nat "UUID_FMT"\n",
-                  UUID_ARGS(&nat->header_.uuid));
-        ds_put_cstr(s, "        external ip: ");
-        ds_put_format(s, "\"%s\"\n", nat->external_ip);
-        ds_put_cstr(s, "        logical ip: ");
-        ds_put_format(s, "\"%s\"\n", nat->logical_ip);
-        ds_put_cstr(s, "        type: ");
-        ds_put_format(s, "\"%s\"\n", nat->type);
-    }
-}
-
-static void
-print_ls(const struct nbrec_logical_switch *ls, struct ds *s)
-{
-    ds_put_format(s, "switch "UUID_FMT" (%s)",
-                  UUID_ARGS(&ls->header_.uuid), ls->name);
-    print_alias(&ls->external_ids, "neutron:network_name", s);
-    ds_put_char(s, '\n');
-
-    for (size_t i = 0; i < ls->n_ports; i++) {
-        const struct nbrec_logical_switch_port *lsp = ls->ports[i];
-
-        ds_put_format(s, "    port %s", lsp->name);
-        print_alias(&lsp->external_ids, "neutron:port_name", s);
-        ds_put_char(s, '\n');
-
-        if (lsp->type[0]) {
-            ds_put_format(s, "        type: %s\n", lsp->type);
-        }
-        if (lsp->parent_name) {
-            ds_put_format(s, "        parent: %s\n", lsp->parent_name);
-        }
-        if (lsp->n_tag) {
-            ds_put_format(s, "        tag: %"PRIu64"\n", lsp->tag[0]);
-        }
-
-        /* Print the addresses, but not if there's just a single "router"
-         * address because that's just clutter. */
-        if (lsp->n_addresses
-            && !(lsp->n_addresses == 1
-                 && !strcmp(lsp->addresses[0], "router"))) {
-            ds_put_cstr(s, "        addresses: [");
-            for (size_t j = 0; j < lsp->n_addresses; j++) {
-                ds_put_format(s, "%s\"%s\"",
-                        j == 0 ? "" : ", ",
-                        lsp->addresses[j]);
-            }
-            ds_put_cstr(s, "]\n");
-        }
-
-        const char *router_port = smap_get(&lsp->options, "router-port");
-        if (router_port) {
-            ds_put_format(s, "        router-port: %s\n", router_port);
-        }
-    }
-}
-
-static void
-nbctl_init(struct ctl_context *ctx OVS_UNUSED)
-{
-}
-
-static void
-nbctl_pre_sync(struct ctl_context *ctx OVS_UNUSED)
-{
-    if (wait_type != NBCTL_WAIT_NONE) {
-        force_wait = true;
-    } else {
-        VLOG_INFO("\"sync\" command has no effect without --wait");
-    }
-}
-
-static void
-nbctl_sync(struct ctl_context *ctx OVS_UNUSED)
-{
-}
-
-static void
-nbctl_show(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-
-    if (ctx->argc == 2) {
-        char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], false, &ls);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-        if (ls) {
-            print_ls(ls, &ctx->output);
-        }
-    } else {
-        NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
-            print_ls(ls, &ctx->output);
-        }
-    }
-    const struct nbrec_logical_router *lr;
-
-    if (ctx->argc == 2) {
-        char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], false, &lr);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-        if (lr) {
-            print_lr(lr, &ctx->output);
-        }
-    } else {
-        NBREC_LOGICAL_ROUTER_FOR_EACH(lr, ctx->idl) {
-            print_lr(lr, &ctx->output);
-        }
-    }
-}
-
-static void
-nbctl_ls_add(struct ctl_context *ctx)
-{
-    const char *ls_name = ctx->argc == 2 ? ctx->argv[1] : NULL;
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
-    if (may_exist && add_duplicate) {
-        ctl_error(ctx, "--may-exist and --add-duplicate may not be used "
-                  "together");
-        return;
-    }
-
-    if (ls_name) {
-        if (!add_duplicate) {
-            const struct nbrec_logical_switch *ls;
-            NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
-                if (!strcmp(ls->name, ls_name)) {
-                    if (may_exist) {
-                        return;
-                    }
-                    ctl_error(ctx, "%s: a switch with this name already "
-                              "exists", ls_name);
-                    return;
-                }
-            }
-        }
-    } else if (may_exist) {
-        ctl_error(ctx, "--may-exist requires specifying a name");
-        return;
-    } else if (add_duplicate) {
-        ctl_error(ctx, "--add-duplicate requires specifying a name");
-        return;
-    }
-
-    struct nbrec_logical_switch *ls;
-    ls = nbrec_logical_switch_insert(ctx->txn);
-    if (ls_name) {
-        nbrec_logical_switch_set_name(ls, ls_name);
-    }
-}
-
-static void
-nbctl_ls_del(struct ctl_context *ctx)
-{
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch *ls = NULL;
-
-    char *error = ls_by_name_or_uuid(ctx, id, must_exist, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!ls) {
-        return;
-    }
-
-    nbrec_logical_switch_delete(ls);
-}
-
-static void
-nbctl_ls_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-    struct smap switches;
-
-    smap_init(&switches);
-    NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
-        smap_add_format(&switches, ls->name, UUID_FMT " (%s)",
-                        UUID_ARGS(&ls->header_.uuid), ls->name);
-    }
-    const struct smap_node **nodes = smap_sort(&switches);
-    for (size_t i = 0; i < smap_count(&switches); i++) {
-        const struct smap_node *node = nodes[i];
-        ds_put_format(&ctx->output, "%s\n", node->value);
-    }
-    smap_destroy(&switches);
-    free(nodes);
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id,
-                    bool must_exist,
-                    const struct nbrec_logical_switch_port **lsp_p)
-{
-    const struct nbrec_logical_switch_port *lsp = NULL;
-    *lsp_p = NULL;
-
-    struct uuid lsp_uuid;
-    bool is_uuid = uuid_from_string(&lsp_uuid, id);
-    if (is_uuid) {
-        lsp = nbrec_logical_switch_port_get_for_uuid(ctx->idl, &lsp_uuid);
-    }
-
-    if (!lsp) {
-        NBREC_LOGICAL_SWITCH_PORT_FOR_EACH(lsp, ctx->idl) {
-            if (!strcmp(lsp->name, id)) {
-                break;
-            }
-        }
-    }
-
-    if (!lsp && must_exist) {
-        return xasprintf("%s: port %s not found",
-                         id, is_uuid ? "UUID" : "name");
-    }
-
-    *lsp_p = lsp;
-    return NULL;
-}
-
-/* Returns the logical switch that contains 'lsp'. */
-static char * OVS_WARN_UNUSED_RESULT
-lsp_to_ls(const struct ovsdb_idl *idl,
-          const struct nbrec_logical_switch_port *lsp,
-          const struct nbrec_logical_switch **ls_p)
-{
-    const struct nbrec_logical_switch *ls;
-    *ls_p = NULL;
-
-    NBREC_LOGICAL_SWITCH_FOR_EACH (ls, idl) {
-        for (size_t i = 0; i < ls->n_ports; i++) {
-            if (ls->ports[i] == lsp) {
-                *ls_p = ls;
-                return NULL;
-            }
-        }
-    }
-
-    /* Can't happen because of the database schema */
-    return xasprintf("logical port %s is not part of any logical switch",
-                     lsp->name);
-}
-
-static const char *
-ls_get_name(const struct nbrec_logical_switch *ls,
-                 char uuid_s[UUID_LEN + 1], size_t uuid_s_size)
-{
-    if (ls->name[0]) {
-        return ls->name;
-    }
-    snprintf(uuid_s, uuid_s_size, UUID_FMT, UUID_ARGS(&ls->header_.uuid));
-    return uuid_s;
-}
-
-static void
-nbctl_lsp_add(struct ctl_context *ctx)
-{
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-
-    const struct nbrec_logical_switch *ls = NULL;
-    char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const char *parent_name;
-    int64_t tag;
-    if (ctx->argc == 3) {
-        parent_name = NULL;
-        tag = -1;
-    } else if (ctx->argc == 5) {
-        /* Validate tag. */
-        parent_name = ctx->argv[3];
-        if (!ovs_scan(ctx->argv[4], "%"SCNd64, &tag)
-            || tag < 0 || tag > 4095) {
-            ctl_error(ctx, "%s: invalid tag (must be in range 0 to 4095)",
-                      ctx->argv[4]);
-            return;
-        }
-    } else {
-        ctl_error(ctx, "lsp-add with parent must also specify a tag");
-        return;
-    }
-
-    const char *lsp_name = ctx->argv[2];
-    const struct nbrec_logical_switch_port *lsp;
-    error = lsp_by_name_or_uuid(ctx, lsp_name, false, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lsp) {
-        if (!may_exist) {
-            ctl_error(ctx, "%s: a port with this name already exists",
-                      lsp_name);
-            return;
-        }
-
-        const struct nbrec_logical_switch *lsw;
-        error = lsp_to_ls(ctx->idl, lsp, &lsw);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-        if (lsw != ls) {
-            char uuid_s[UUID_LEN + 1];
-            ctl_error(ctx, "%s: port already exists but in switch %s",
-                      lsp_name, ls_get_name(lsw, uuid_s, sizeof uuid_s));
-            return;
-        }
-
-        if (parent_name) {
-            if (!lsp->parent_name) {
-                ctl_error(ctx, "%s: port already exists but has no parent",
-                          lsp_name);
-                return;
-            } else if (strcmp(parent_name, lsp->parent_name)) {
-                ctl_error(ctx, "%s: port already exists with different parent "
-                          "%s", lsp_name, lsp->parent_name);
-                return;
-            }
-
-            if (!lsp->n_tag_request) {
-                ctl_error(ctx, "%s: port already exists but has no "
-                          "tag_request", lsp_name);
-                return;
-            } else if (lsp->tag_request[0] != tag) {
-                ctl_error(ctx, "%s: port already exists with different "
-                          "tag_request %"PRId64, lsp_name,
-                          lsp->tag_request[0]);
-                return;
-            }
-        } else {
-            if (lsp->parent_name) {
-                ctl_error(ctx, "%s: port already exists but has parent %s",
-                          lsp_name, lsp->parent_name);
-                return;
-            }
-        }
-
-        return;
-    }
-
-    /* Create the logical port. */
-    lsp = nbrec_logical_switch_port_insert(ctx->txn);
-    nbrec_logical_switch_port_set_name(lsp, lsp_name);
-    if (tag >= 0) {
-        nbrec_logical_switch_port_set_parent_name(lsp, parent_name);
-        nbrec_logical_switch_port_set_tag_request(lsp, &tag, 1);
-    }
-
-    /* Insert the logical port into the logical switch. */
-    nbrec_logical_switch_verify_ports(ls);
-    struct nbrec_logical_switch_port **new_ports = xmalloc(sizeof *new_ports *
-                                                    (ls->n_ports + 1));
-    nullable_memcpy(new_ports, ls->ports, sizeof *new_ports * ls->n_ports);
-    new_ports[ls->n_ports] = CONST_CAST(struct nbrec_logical_switch_port *,
-                                             lsp);
-    nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1);
-    free(new_ports);
-}
-
-/* Removes logical switch port 'ls->ports[idx]'. */
-static void
-remove_lsp(const struct nbrec_logical_switch *ls, size_t idx)
-{
-    const struct nbrec_logical_switch_port *lsp = ls->ports[idx];
-
-    /* First remove 'lsp' from the array of ports.  This is what will
-     * actually cause the logical port to be deleted when the transaction is
-     * sent to the database server (due to garbage collection). */
-    struct nbrec_logical_switch_port **new_ports
-        = xmemdup(ls->ports, sizeof *new_ports * ls->n_ports);
-    new_ports[idx] = new_ports[ls->n_ports - 1];
-    nbrec_logical_switch_verify_ports(ls);
-    nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports - 1);
-    free(new_ports);
-
-    /* Delete 'lsp' from the IDL.  This won't have a real effect on the
-     * database server (the IDL will suppress it in fact) but it means that it
-     * won't show up when we iterate with NBREC_LOGICAL_SWITCH_PORT_FOR_EACH
-     * later. */
-    nbrec_logical_switch_port_delete(lsp);
-}
-
-static void
-nbctl_lsp_del(struct ctl_context *ctx)
-{
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, ctx->argv[1], must_exist, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lsp) {
-        return;
-    }
-
-    /* Find the switch that contains 'lsp', then delete it. */
-    const struct nbrec_logical_switch *ls;
-    NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
-        for (size_t i = 0; i < ls->n_ports; i++) {
-            if (ls->ports[i] == lsp) {
-                remove_lsp(ls, i);
-                return;
-            }
-        }
-    }
-
-    /* Can't happen because of the database schema. */
-    ctl_error(ctx, "logical port %s is not part of any logical switch",
-              ctx->argv[1]);
-}
-
-static void
-nbctl_lsp_list(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch *ls;
-    struct smap lsps;
-    size_t i;
-
-    char *error = ls_by_name_or_uuid(ctx, id, true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    smap_init(&lsps);
-    for (i = 0; i < ls->n_ports; i++) {
-        const struct nbrec_logical_switch_port *lsp = ls->ports[i];
-        smap_add_format(&lsps, lsp->name, UUID_FMT " (%s)",
-                        UUID_ARGS(&lsp->header_.uuid), lsp->name);
-    }
-    const struct smap_node **nodes = smap_sort(&lsps);
-    for (i = 0; i < smap_count(&lsps); i++) {
-        const struct smap_node *node = nodes[i];
-        ds_put_format(&ctx->output, "%s\n", node->value);
-    }
-    smap_destroy(&lsps);
-    free(nodes);
-}
-
-static void
-nbctl_lsp_get_parent(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, ctx->argv[1], true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lsp->parent_name) {
-        ds_put_format(&ctx->output, "%s\n", lsp->parent_name);
-    }
-}
-
-static void
-nbctl_lsp_get_tag(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, ctx->argv[1], true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lsp->n_tag > 0) {
-        ds_put_format(&ctx->output, "%"PRId64"\n", lsp->tag[0]);
-    }
-}
-
-static char *
-lsp_contains_duplicate_ip(struct lport_addresses *laddrs1,
-                          struct lport_addresses *laddrs2)
-{
-    for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) {
-        for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) {
-            if (laddrs1->ipv4_addrs[i].addr == laddrs2->ipv4_addrs[j].addr) {
-                return xasprintf("duplicate IPv4 address %s",
-                                 laddrs1->ipv4_addrs[i].addr_s);
-            }
-        }
-    }
-
-    for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) {
-        for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) {
-            if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr,
-                                   &laddrs2->ipv6_addrs[j].addr)) {
-                return xasprintf("duplicate IPv6 address %s",
-                                 laddrs1->ipv6_addrs[i].addr_s);
-            }
-        }
-    }
-
-    return NULL;
-}
-
-static char *
-lsp_contains_duplicates(const struct nbrec_logical_switch *ls,
-                        const struct nbrec_logical_switch_port *lsp,
-                        const char *address)
-{
-    struct lport_addresses laddrs;
-    if (!extract_lsp_addresses(address, &laddrs)) {
-        return NULL;
-    }
-
-    char *sub_error = NULL;
-    for (size_t i = 0; i < ls->n_ports; i++) {
-        struct nbrec_logical_switch_port *lsp_test = ls->ports[i];
-        if (lsp_test == lsp) {
-            continue;
-        }
-        for (size_t j = 0; j < lsp_test->n_addresses; j++) {
-            struct lport_addresses laddrs_test;
-            char *addr = lsp_test->addresses[j];
-            if (is_dynamic_lsp_address(addr) && lsp_test->dynamic_addresses) {
-                addr = lsp_test->dynamic_addresses;
-            }
-            if (extract_lsp_addresses(addr, &laddrs_test)) {
-                sub_error = lsp_contains_duplicate_ip(&laddrs, &laddrs_test);
-                destroy_lport_addresses(&laddrs_test);
-                if (sub_error) {
-                    goto err_out;
-                }
-            }
-        }
-    }
-
-err_out: ;
-    char *error = NULL;
-    if (sub_error) {
-        error = xasprintf("Error on switch %s: %s", ls->name, sub_error);
-        free(sub_error);
-    }
-    destroy_lport_addresses(&laddrs);
-    return error;
-}
-
-static void
-nbctl_lsp_set_addresses(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const struct nbrec_logical_switch *ls;
-    error = lsp_to_ls(ctx->idl, lsp, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    int i;
-    for (i = 2; i < ctx->argc; i++) {
-        char ipv6_s[IPV6_SCAN_LEN + 1];
-        struct eth_addr ea;
-        ovs_be32 ip;
-
-        if (strcmp(ctx->argv[i], "unknown") && strcmp(ctx->argv[i], "dynamic")
-            && strcmp(ctx->argv[i], "router")
-            && !ovs_scan(ctx->argv[i], ETH_ADDR_SCAN_FMT,
-                         ETH_ADDR_SCAN_ARGS(ea))
-            && !ovs_scan(ctx->argv[i], "dynamic "IPV6_SCAN_FMT, ipv6_s)
-            && !ovs_scan(ctx->argv[i], "dynamic "IP_SCAN_FMT,
-                         IP_SCAN_ARGS(&ip))) {
-            ctl_error(ctx, "%s: Invalid address format. See ovn-nb(5). "
-                      "Hint: An Ethernet address must be "
-                      "listed before an IP address, together as a single "
-                      "argument.", ctx->argv[i]);
-            return;
-        }
-
-        error = lsp_contains_duplicates(ls, lsp, ctx->argv[i]);
-        if (error) {
-            ctl_error(ctx, "%s", error);
-            return;
-        }
-    }
-
-    nbrec_logical_switch_port_set_addresses(lsp,
-            (const char **) ctx->argv + 2, ctx->argc - 2);
-}
-
-static void
-nbctl_lsp_get_addresses(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-    struct svec addresses;
-    const char *mac;
-    size_t i;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    svec_init(&addresses);
-    for (i = 0; i < lsp->n_addresses; i++) {
-        svec_add(&addresses, lsp->addresses[i]);
-    }
-    svec_sort(&addresses);
-    SVEC_FOR_EACH(i, mac, &addresses) {
-        ds_put_format(&ctx->output, "%s\n", mac);
-    }
-    svec_destroy(&addresses);
-}
-
-static void
-nbctl_lsp_set_port_security(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    nbrec_logical_switch_port_set_port_security(lsp,
-            (const char **) ctx->argv + 2, ctx->argc - 2);
-}
-
-static void
-nbctl_lsp_get_port_security(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-    struct svec addrs;
-    const char *addr;
-    size_t i;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    svec_init(&addrs);
-    for (i = 0; i < lsp->n_port_security; i++) {
-        svec_add(&addrs, lsp->port_security[i]);
-    }
-    svec_sort(&addrs);
-    SVEC_FOR_EACH(i, addr, &addrs) {
-        ds_put_format(&ctx->output, "%s\n", addr);
-    }
-    svec_destroy(&addrs);
-}
-
-static void
-nbctl_lsp_get_up(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    ds_put_format(&ctx->output,
-                  "%s\n", (lsp->up && *lsp->up) ? "up" : "down");
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-parse_enabled(const char *state, bool *enabled_p)
-{
-    ovs_assert(enabled_p);
-
-    if (!strcasecmp(state, "enabled")) {
-        *enabled_p = true;
-    } else if (!strcasecmp(state, "disabled")) {
-        *enabled_p = false;
-    } else {
-        return xasprintf("%s: state must be \"enabled\" or \"disabled\"",
-                         state);
-    }
-    return NULL;
-}
-
-static void
-nbctl_lsp_set_enabled(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const char *state = ctx->argv[2];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    bool enabled;
-    error = parse_enabled(state, &enabled);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    nbrec_logical_switch_port_set_enabled(lsp, &enabled, 1);
-}
-
-static void
-nbctl_lsp_get_enabled(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    ds_put_format(&ctx->output, "%s\n",
-                  !lsp->enabled || *lsp->enabled ? "enabled" : "disabled");
-}
-
-static void
-nbctl_lsp_set_type(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const char *type = ctx->argv[2];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (ovn_is_known_nb_lsp_type(type)) {
-        nbrec_logical_switch_port_set_type(lsp, type);
-    } else {
-        ctl_error(ctx, "Logical switch port type '%s' is unrecognized. "
-                  "Not setting type.", type);
-        return;
-    }
-}
-
-static void
-nbctl_lsp_get_type(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    ds_put_format(&ctx->output, "%s\n", lsp->type);
-}
-
-static void
-nbctl_lsp_set_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-    size_t i;
-    struct smap options = SMAP_INITIALIZER(&options);
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    for (i = 2; i < ctx->argc; i++) {
-        char *key, *value;
-        value = xstrdup(ctx->argv[i]);
-        key = strsep(&value, "=");
-        if (value) {
-            smap_add(&options, key, value);
-        }
-        free(key);
-    }
-
-    nbrec_logical_switch_port_set_options(lsp, &options);
-
-    smap_destroy(&options);
-}
-
-static void
-nbctl_lsp_get_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-    struct smap_node *node;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    SMAP_FOR_EACH(node, &lsp->options) {
-        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
-    }
-}
-
-static void
-nbctl_lsp_set_dhcpv4_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    const struct nbrec_dhcp_options *dhcp_opt = NULL;
-    if (ctx->argc == 3 ) {
-        error = dhcp_options_get(ctx, ctx->argv[2], true, &dhcp_opt);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-    }
-
-    if (dhcp_opt) {
-        ovs_be32 ip;
-        unsigned int plen;
-        error = ip_parse_cidr(dhcp_opt->cidr, &ip, &plen);
-        if (error){
-            free(error);
-            ctl_error(ctx, "DHCP options cidr '%s' is not IPv4",
-                      dhcp_opt->cidr);
-            return;
-        }
-    }
-    nbrec_logical_switch_port_set_dhcpv4_options(lsp, dhcp_opt);
-}
-
-static void
-nbctl_lsp_set_dhcpv6_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    const struct nbrec_dhcp_options *dhcp_opt = NULL;
-    if (ctx->argc == 3) {
-        error = dhcp_options_get(ctx, ctx->argv[2], true, &dhcp_opt);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-    }
-
-    if (dhcp_opt) {
-        struct in6_addr ip;
-        unsigned int plen;
-        error = ipv6_parse_cidr(dhcp_opt->cidr, &ip, &plen);
-        if (error) {
-            free(error);
-            ctl_error(ctx, "DHCP options cidr '%s' is not IPv6",
-                      dhcp_opt->cidr);
-            return;
-        }
-    }
-    nbrec_logical_switch_port_set_dhcpv6_options(lsp, dhcp_opt);
-}
-
-static void
-nbctl_lsp_get_dhcpv4_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lsp->dhcpv4_options) {
-        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
-                      UUID_ARGS(&lsp->dhcpv4_options->header_.uuid),
-                      lsp->dhcpv4_options->cidr);
-    }
-}
-
-static void
-nbctl_lsp_get_dhcpv6_options(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lsp->dhcpv6_options) {
-        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
-                      UUID_ARGS(&lsp->dhcpv6_options->header_.uuid),
-                      lsp->dhcpv6_options->cidr);
-    }
-}
-
-static void
-nbctl_lsp_get_ls(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_switch_port *lsp = NULL;
-
-    char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const struct nbrec_logical_switch *ls;
-    NBREC_LOGICAL_SWITCH_FOR_EACH(ls, ctx->idl) {
-        for (size_t i = 0; i < ls->n_ports; i++) {
-            if (ls->ports[i] == lsp) {
-                ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
-                      UUID_ARGS(&ls->header_.uuid), ls->name);
-                break;
-            }
-        }
-    }
-}
-
-enum {
-    DIR_FROM_LPORT,
-    DIR_TO_LPORT
-};
-
-static int
-dir_encode(const char *dir)
-{
-    if (!strcmp(dir, "from-lport")) {
-        return DIR_FROM_LPORT;
-    } else if (!strcmp(dir, "to-lport")) {
-        return DIR_TO_LPORT;
-    }
-
-    OVS_NOT_REACHED();
-}
-
-static int
-acl_cmp(const void *acl1_, const void *acl2_)
-{
-    const struct nbrec_acl *const *acl1p = acl1_;
-    const struct nbrec_acl *const *acl2p = acl2_;
-    const struct nbrec_acl *acl1 = *acl1p;
-    const struct nbrec_acl *acl2 = *acl2p;
-
-    int dir1 = dir_encode(acl1->direction);
-    int dir2 = dir_encode(acl2->direction);
-
-    if (dir1 != dir2) {
-        return dir1 < dir2 ? -1 : 1;
-    } else if (acl1->priority != acl2->priority) {
-        return acl1->priority > acl2->priority ? -1 : 1;
-    } else {
-        return strcmp(acl1->match, acl2->match);
-    }
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-acl_cmd_get_pg_or_ls(struct ctl_context *ctx,
-                     const struct nbrec_logical_switch **ls,
-                     const struct nbrec_port_group **pg)
-{
-    const char *opt_type = shash_find_data(&ctx->options, "--type");
-    char *error;
-
-    if (!opt_type) {
-        error = pg_by_name_or_uuid(ctx, ctx->argv[1], false, pg);
-        if (error) {
-            return error;
-        }
-        error = ls_by_name_or_uuid(ctx, ctx->argv[1], false, ls);
-        if (error) {
-            return error;
-        }
-        if (*pg && *ls) {
-            return xasprintf("Same name '%s' exists in both port-groups and "
-                             "logical switches. Specify --type=port-group or "
-                             "switch, or use a UUID.", ctx->argv[1]);
-        }
-        if (!*pg && !*ls) {
-            return xasprintf("'%s' is not found for port-group or switch.",
-                             ctx->argv[1]);
-        }
-    } else if (!strcmp(opt_type, "port-group")) {
-        error = pg_by_name_or_uuid(ctx, ctx->argv[1], true, pg);
-        if (error) {
-            return error;
-        }
-        *ls = NULL;
-    } else if (!strcmp(opt_type, "switch")) {
-        error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, ls);
-        if (error) {
-            return error;
-        }
-        *pg = NULL;
-    } else {
-        return xasprintf("Invalid value '%s' for option --type", opt_type);
-    }
-
-    return NULL;
-}
-
-static void
-nbctl_acl_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls = NULL;
-    const struct nbrec_port_group *pg = NULL;
-    const struct nbrec_acl **acls;
-    size_t i;
-
-    char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    size_t n_acls = pg ? pg->n_acls : ls->n_acls;
-    struct nbrec_acl **nb_acls = pg ? pg->acls : ls->acls;
-
-    acls = xmalloc(sizeof *acls * n_acls);
-    for (i = 0; i < n_acls; i++) {
-        acls[i] = nb_acls[i];
-    }
-
-    qsort(acls, n_acls, sizeof *acls, acl_cmp);
-
-    for (i = 0; i < n_acls; i++) {
-        const struct nbrec_acl *acl = acls[i];
-        ds_put_format(&ctx->output, "%10s %5"PRId64" (%s) %s",
-                      acl->direction, acl->priority, acl->match,
-                      acl->action);
-        if (acl->log) {
-            ds_put_cstr(&ctx->output, " log(");
-            if (acl->name) {
-                ds_put_format(&ctx->output, "name=%s,", acl->name);
-            }
-            if (acl->severity) {
-                ds_put_format(&ctx->output, "severity=%s,", acl->severity);
-            }
-            if (acl->meter) {
-                ds_put_format(&ctx->output, "meter=\"%s\",", acl->meter);
-            }
-            ds_chomp(&ctx->output, ',');
-            ds_put_cstr(&ctx->output, ")");
-        }
-        ds_put_cstr(&ctx->output, "\n");
-    }
-
-    free(acls);
-}
-
-static int
-qos_cmp(const void *qos1_, const void *qos2_)
-{
-    const struct nbrec_qos *const *qos1p = qos1_;
-    const struct nbrec_qos *const *qos2p = qos2_;
-    const struct nbrec_qos *qos1 = *qos1p;
-    const struct nbrec_qos *qos2 = *qos2p;
-
-    int dir1 = dir_encode(qos1->direction);
-    int dir2 = dir_encode(qos2->direction);
-
-    if (dir1 != dir2) {
-        return dir1 < dir2 ? -1 : 1;
-    } else if (qos1->priority != qos2->priority) {
-        return qos1->priority > qos2->priority ? -1 : 1;
-    } else {
-        return strcmp(qos1->match, qos2->match);
-    }
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-parse_direction(const char *arg, const char **direction_p)
-{
-    /* Validate direction.  Only require the first letter. */
-    if (arg[0] == 't') {
-        *direction_p = "to-lport";
-    } else if (arg[0] == 'f') {
-        *direction_p = "from-lport";
-    } else {
-        *direction_p = NULL;
-        return xasprintf("%s: direction must be \"to-lport\" or "
-                         "\"from-lport\"", arg);
-    }
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-parse_priority(const char *arg, int64_t *priority_p)
-{
-    /* Validate priority. */
-    int64_t priority;
-    if (!ovs_scan(arg, "%"SCNd64, &priority)
-        || priority < 0 || priority > 32767) {
-        /* Priority_p could be uninitialized as no valid priority was
-         * input, initialize it to a valid value of 0 before returning */
-        *priority_p = 0;
-        return xasprintf("%s: priority must in range 0...32767", arg);
-    }
-    *priority_p = priority;
-    return NULL;
-}
-
-static void
-nbctl_acl_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls = NULL;
-    const struct nbrec_port_group *pg = NULL;
-    const char *action = ctx->argv[5];
-
-    char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const char *direction;
-    error = parse_direction(ctx->argv[2], &direction);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    int64_t priority;
-    error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    /* Validate action. */
-    if (strcmp(action, "allow") && strcmp(action, "allow-related")
-        && strcmp(action, "drop") && strcmp(action, "reject")) {
-        ctl_error(ctx, "%s: action must be one of \"allow\", "
-                  "\"allow-related\", \"drop\", and \"reject\"", action);
-        return;
-    }
-
-    /* Create the acl. */
-    struct nbrec_acl *acl = nbrec_acl_insert(ctx->txn);
-    nbrec_acl_set_priority(acl, priority);
-    nbrec_acl_set_direction(acl, direction);
-    nbrec_acl_set_match(acl, ctx->argv[4]);
-    nbrec_acl_set_action(acl, action);
-
-    /* Logging options. */
-    bool log = shash_find(&ctx->options, "--log") != NULL;
-    const char *severity = shash_find_data(&ctx->options, "--severity");
-    const char *name = shash_find_data(&ctx->options, "--name");
-    const char *meter = shash_find_data(&ctx->options, "--meter");
-    if (log || severity || name || meter) {
-        nbrec_acl_set_log(acl, true);
-    }
-    if (severity) {
-        if (log_severity_from_string(severity) == UINT8_MAX) {
-            ctl_error(ctx, "bad severity: %s", severity);
-            return;
-        }
-        nbrec_acl_set_severity(acl, severity);
-    }
-    if (name) {
-        nbrec_acl_set_name(acl, name);
-    }
-    if (meter) {
-        nbrec_acl_set_meter(acl, meter);
-    }
-
-    /* Check if same acl already exists for the ls/portgroup */
-    size_t n_acls = pg ? pg->n_acls : ls->n_acls;
-    struct nbrec_acl **acls = pg ? pg->acls : ls->acls;
-    for (size_t i = 0; i < n_acls; i++) {
-        if (!acl_cmp(&acls[i], &acl)) {
-            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-            if (!may_exist) {
-                ctl_error(ctx, "Same ACL already existed on the ls %s.",
-                          ctx->argv[1]);
-                return;
-            }
-            return;
-        }
-    }
-
-    /* Insert the acl into the logical switch/port group. */
-    struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * (n_acls + 1));
-    nullable_memcpy(new_acls, acls, sizeof *new_acls * n_acls);
-    new_acls[n_acls] = acl;
-    if (pg) {
-        nbrec_port_group_verify_acls(pg);
-        nbrec_port_group_set_acls(pg, new_acls, n_acls + 1);
-    } else {
-        nbrec_logical_switch_verify_acls(ls);
-        nbrec_logical_switch_set_acls(ls, new_acls, n_acls + 1);
-    }
-    free(new_acls);
-}
-
-static void
-nbctl_acl_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls = NULL;
-    const struct nbrec_port_group *pg = NULL;
-
-    char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If direction, priority, and match are not specified, delete
-         * all ACLs. */
-        if (pg) {
-            nbrec_port_group_verify_acls(pg);
-            nbrec_port_group_set_acls(pg, NULL, 0);
-        } else {
-            nbrec_logical_switch_verify_acls(ls);
-            nbrec_logical_switch_set_acls(ls, NULL, 0);
-        }
-        return;
-    }
-
-    const char *direction;
-    error = parse_direction(ctx->argv[2], &direction);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    size_t n_acls = pg ? pg->n_acls : ls->n_acls;
-    struct nbrec_acl **acls = pg ? pg->acls : ls->acls;
-    /* If priority and match are not specified, delete all ACLs with the
-     * specified direction. */
-    if (ctx->argc == 3) {
-        struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * n_acls);
-
-        int n_new_acls = 0;
-        for (size_t i = 0; i < n_acls; i++) {
-            if (strcmp(direction, acls[i]->direction)) {
-                new_acls[n_new_acls++] = acls[i];
-            }
-        }
-
-        if (pg) {
-            nbrec_port_group_verify_acls(pg);
-            nbrec_port_group_set_acls(pg, new_acls, n_new_acls);
-        } else {
-            nbrec_logical_switch_verify_acls(ls);
-            nbrec_logical_switch_set_acls(ls, new_acls, n_new_acls);
-        }
-        free(new_acls);
-        return;
-    }
-
-    int64_t priority;
-    error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 4) {
-        ctl_error(ctx, "cannot specify priority without match");
-        return;
-    }
-
-    /* Remove the matching rule. */
-    for (size_t i = 0; i < n_acls; i++) {
-        struct nbrec_acl *acl = acls[i];
-
-        if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) &&
-             !strcmp(direction, acl->direction)) {
-            struct nbrec_acl **new_acls
-                = xmemdup(acls, sizeof *new_acls * n_acls);
-            new_acls[i] = acls[n_acls - 1];
-            if (pg) {
-                nbrec_port_group_verify_acls(pg);
-                nbrec_port_group_set_acls(pg, new_acls,
-                                          n_acls - 1);
-            } else {
-                nbrec_logical_switch_verify_acls(ls);
-                nbrec_logical_switch_set_acls(ls, new_acls,
-                                              n_acls - 1);
-            }
-            free(new_acls);
-            return;
-        }
-    }
-}
-
-static void
-nbctl_qos_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-    const struct nbrec_qos **qos_rules;
-    size_t i;
-
-    char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    qos_rules = xmalloc(sizeof *qos_rules * ls->n_qos_rules);
-    for (i = 0; i < ls->n_qos_rules; i++) {
-        qos_rules[i] = ls->qos_rules[i];
-    }
-
-    qsort(qos_rules, ls->n_qos_rules, sizeof *qos_rules, qos_cmp);
-
-    for (i = 0; i < ls->n_qos_rules; i++) {
-        const struct nbrec_qos *qos_rule = qos_rules[i];
-        ds_put_format(&ctx->output, "%10s %5"PRId64" (%s)",
-                      qos_rule->direction, qos_rule->priority,
-                      qos_rule->match);
-        for (size_t j = 0; j < qos_rule->n_bandwidth; j++) {
-            if (!strcmp(qos_rule->key_bandwidth[j], "rate")) {
-                ds_put_format(&ctx->output, " rate=%"PRId64"",
-                              qos_rule->value_bandwidth[j]);
-            }
-        }
-        for (size_t j = 0; j < qos_rule->n_bandwidth; j++) {
-            if (!strcmp(qos_rule->key_bandwidth[j], "burst")) {
-                ds_put_format(&ctx->output, " burst=%"PRId64"",
-                              qos_rule->value_bandwidth[j]);
-            }
-        }
-        for (size_t j = 0; j < qos_rule->n_action; j++) {
-            if (!strcmp(qos_rule->key_action[j], "dscp")) {
-                ds_put_format(&ctx->output, " dscp=%"PRId64"",
-                              qos_rule->value_action[j]);
-            }
-        }
-        ds_put_cstr(&ctx->output, "\n");
-    }
-
-    free(qos_rules);
-}
-
-static void
-nbctl_qos_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-    const char *direction;
-    int64_t priority;
-    int64_t dscp = -1;
-    int64_t rate = 0;
-    int64_t burst = 0;
-    char *error;
-
-    error = parse_direction(ctx->argv[2], &direction);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    for (int i = 5; i < ctx->argc; i++) {
-        if (!strncmp(ctx->argv[i], "dscp=", 5)) {
-            if (!ovs_scan(ctx->argv[i] + 5, "%"SCNd64, &dscp)
-                || dscp < 0 || dscp > 63) {
-                ctl_error(ctx, "%s: dscp must be in the range 0...63",
-                          ctx->argv[i] + 5);
-                return;
-            }
-        }
-        else if (!strncmp(ctx->argv[i], "rate=", 5)) {
-            if (!ovs_scan(ctx->argv[i] + 5, "%"SCNd64, &rate)
-                || rate < 1 || rate > UINT32_MAX) {
-                ctl_error(ctx, "%s: rate must be in the range 1...4294967295",
-                          ctx->argv[i] + 5);
-                return;
-            }
-        }
-        else if (!strncmp(ctx->argv[i], "burst=", 6)) {
-            if (!ovs_scan(ctx->argv[i] + 6, "%"SCNd64, &burst)
-                || burst < 1 || burst > UINT32_MAX) {
-                ctl_error(ctx, "%s: burst must be in the range 1...4294967295",
-                          ctx->argv[i] + 6);
-                return;
-            }
-        } else {
-            ctl_error(ctx, "%s: supported arguments are \"dscp=\", \"rate=\", "
-                      "and \"burst=\"", ctx->argv[i]);
-            return;
-        }
-    }
-
-    /* Validate rate and dscp. */
-    if (-1 == dscp && !rate) {
-        ctl_error(ctx, "Either \"rate\" and/or \"dscp\" must be specified");
-        return;
-    }
-
-    /* Create the qos. */
-    struct nbrec_qos *qos = nbrec_qos_insert(ctx->txn);
-    nbrec_qos_set_priority(qos, priority);
-    nbrec_qos_set_direction(qos, direction);
-    nbrec_qos_set_match(qos, ctx->argv[4]);
-    if (-1 != dscp) {
-        const char *dscp_key = "dscp";
-        nbrec_qos_set_action(qos, &dscp_key, &dscp, 1);
-    }
-    if (rate) {
-        const char *bandwidth_key[2] = {"rate", "burst"};
-        const int64_t bandwidth_value[2] = {rate, burst};
-        size_t n_bandwidth = 1;
-        if (burst) {
-            n_bandwidth = 2;
-        }
-        nbrec_qos_set_bandwidth(qos, bandwidth_key, bandwidth_value,
-                                n_bandwidth);
-    }
-
-    /* Check if same qos rule already exists for the ls */
-    for (size_t i = 0; i < ls->n_qos_rules; i++) {
-        if (!qos_cmp(&ls->qos_rules[i], &qos)) {
-            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-            if (!may_exist) {
-                ctl_error(ctx, "Same qos already existed on the ls %s.",
-                          ctx->argv[1]);
-                return;
-            }
-            return;
-        }
-    }
-
-    /* Insert the qos rule the logical switch. */
-    nbrec_logical_switch_verify_qos_rules(ls);
-    struct nbrec_qos **new_qos_rules
-        = xmalloc(sizeof *new_qos_rules * (ls->n_qos_rules + 1));
-    nullable_memcpy(new_qos_rules,
-                    ls->qos_rules, sizeof *new_qos_rules * ls->n_qos_rules);
-    new_qos_rules[ls->n_qos_rules] = qos;
-    nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
-                                       ls->n_qos_rules + 1);
-    free(new_qos_rules);
-}
-
-static void
-nbctl_qos_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-    char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If direction, priority, and match are not specified, delete
-         * all QoS rules. */
-        nbrec_logical_switch_verify_qos_rules(ls);
-        nbrec_logical_switch_set_qos_rules(ls, NULL, 0);
-        return;
-    }
-
-    const char *direction;
-    error = parse_direction(ctx->argv[2], &direction);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    /* If priority and match are not specified, delete all qos_rules with the
-     * specified direction. */
-    if (ctx->argc == 3) {
-        struct nbrec_qos **new_qos_rules
-            = xmalloc(sizeof *new_qos_rules * ls->n_qos_rules);
-
-        int n_qos_rules = 0;
-        for (size_t i = 0; i < ls->n_qos_rules; i++) {
-            if (strcmp(direction, ls->qos_rules[i]->direction)) {
-                new_qos_rules[n_qos_rules++] = ls->qos_rules[i];
-            }
-        }
-
-        nbrec_logical_switch_verify_qos_rules(ls);
-        nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, n_qos_rules);
-        free(new_qos_rules);
-        return;
-    }
-
-    int64_t priority;
-    error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 4) {
-        ctl_error(ctx, "cannot specify priority without match");
-        return;
-    }
-
-    /* Remove the matching rule. */
-    for (size_t i = 0; i < ls->n_qos_rules; i++) {
-        struct nbrec_qos *qos = ls->qos_rules[i];
-
-        if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) &&
-             !strcmp(direction, qos->direction)) {
-            struct nbrec_qos **new_qos_rules
-                = xmemdup(ls->qos_rules,
-                          sizeof *new_qos_rules * ls->n_qos_rules);
-            new_qos_rules[i] = ls->qos_rules[ls->n_qos_rules - 1];
-            nbrec_logical_switch_verify_qos_rules(ls);
-            nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
-                                          ls->n_qos_rules - 1);
-            free(new_qos_rules);
-            return;
-        }
-    }
-}
-
-static int
-meter_cmp(const void *meter1_, const void *meter2_)
-{
-    struct nbrec_meter *const *meter1p = meter1_;
-    struct nbrec_meter *const *meter2p = meter2_;
-    const struct nbrec_meter *meter1 = *meter1p;
-    const struct nbrec_meter *meter2 = *meter2p;
-
-    return strcmp(meter1->name, meter2->name);
-}
-
-static void
-nbctl_meter_list(struct ctl_context *ctx)
-{
-    const struct nbrec_meter **meters = NULL;
-    const struct nbrec_meter *meter;
-    size_t n_capacity = 0;
-    size_t n_meters = 0;
-
-    NBREC_METER_FOR_EACH (meter, ctx->idl) {
-        if (n_meters == n_capacity) {
-            meters = x2nrealloc(meters, &n_capacity, sizeof *meters);
-        }
-
-        meters[n_meters] = meter;
-        n_meters++;
-    }
-
-    if (n_meters) {
-        qsort(meters, n_meters, sizeof *meters, meter_cmp);
-    }
-
-    for (size_t i = 0; i < n_meters; i++) {
-        meter = meters[i];
-        ds_put_format(&ctx->output, "%s: bands:\n", meter->name);
-
-        for (size_t j = 0; j < meter->n_bands; j++) {
-            const struct nbrec_meter_band *band = meter->bands[j];
-
-            ds_put_format(&ctx->output, "  %s: %"PRId64" %s",
-                          band->action, band->rate, meter->unit);
-            if (band->burst_size) {
-                ds_put_format(&ctx->output, ", %"PRId64" %s burst",
-                              band->burst_size,
-                              !strcmp(meter->unit, "kbps") ? "kb" : "packet" );
-            }
-        }
-
-        ds_put_cstr(&ctx->output, "\n");
-    }
-
-    free(meters);
-}
-
-static void
-nbctl_meter_add(struct ctl_context *ctx)
-{
-    const struct nbrec_meter *meter;
-
-    const char *name = ctx->argv[1];
-    NBREC_METER_FOR_EACH (meter, ctx->idl) {
-        if (!strcmp(meter->name, name)) {
-            ctl_error(ctx, "meter with name \"%s\" already exists", name);
-            return;
-        }
-    }
-
-    if (!strncmp(name, "__", 2)) {
-        ctl_error(ctx, "meter names that begin with \"__\" are reserved");
-        return;
-    }
-
-    const char *action = ctx->argv[2];
-    if (strcmp(action, "drop")) {
-        ctl_error(ctx, "action must be \"drop\"");
-        return;
-    }
-
-    int64_t rate;
-    if (!ovs_scan(ctx->argv[3], "%"SCNd64, &rate)
-        || rate < 1 || rate > UINT32_MAX) {
-        ctl_error(ctx, "rate must be in the range 1...4294967295");
-        return;
-    }
-
-    const char *unit = ctx->argv[4];
-    if (strcmp(unit, "kbps") && strcmp(unit, "pktps")) {
-        ctl_error(ctx, "unit must be \"kbps\" or \"pktps\"");
-        return;
-    }
-
-    int64_t burst = 0;
-    if (ctx->argc > 5) {
-        if (!ovs_scan(ctx->argv[5], "%"SCNd64, &burst)
-            || burst < 0 || burst > UINT32_MAX) {
-            ctl_error(ctx, "burst must be in the range 0...4294967295");
-            return;
-        }
-    }
-
-    /* Create the band.  We only support adding a single band. */
-    struct nbrec_meter_band *band = nbrec_meter_band_insert(ctx->txn);
-    nbrec_meter_band_set_action(band, action);
-    nbrec_meter_band_set_rate(band, rate);
-    nbrec_meter_band_set_burst_size(band, burst);
-
-    /* Create the meter. */
-    meter = nbrec_meter_insert(ctx->txn);
-    nbrec_meter_set_name(meter, name);
-    nbrec_meter_set_unit(meter, unit);
-    nbrec_meter_set_bands(meter, &band, 1);
-}
-
-static void
-nbctl_meter_del(struct ctl_context *ctx)
-{
-    const struct nbrec_meter *meter, *next;
-
-    /* If a name is not specified, delete all meters. */
-    if (ctx->argc == 1) {
-        NBREC_METER_FOR_EACH_SAFE (meter, next, ctx->idl) {
-            nbrec_meter_delete(meter);
-        }
-        return;
-    }
-
-    /* Remove the matching meter. */
-    NBREC_METER_FOR_EACH (meter, ctx->idl) {
-        if (strcmp(ctx->argv[1], meter->name)) {
-            continue;
-        }
-
-        nbrec_meter_delete(meter);
-        return;
-    }
-}
-
-static void
-nbctl_lb_add(struct ctl_context *ctx)
-{
-    const char *lb_name = ctx->argv[1];
-    const char *lb_vip = ctx->argv[2];
-    char *lb_ips = ctx->argv[3];
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
-
-    const char *lb_proto;
-    bool is_update_proto = false;
-
-    if (ctx->argc == 4) {
-        /* Default protocol. */
-        lb_proto = "tcp";
-    } else {
-        /* Validate protocol. */
-        lb_proto = ctx->argv[4];
-        is_update_proto = true;
-        if (strcmp(lb_proto, "tcp") && strcmp(lb_proto, "udp")) {
-            ctl_error(ctx, "%s: protocol must be one of \"tcp\", \"udp\".",
-                      lb_proto);
-            return;
-        }
-    }
-
-    struct sockaddr_storage ss_vip;
-    if (!inet_parse_active(lb_vip, 0, &ss_vip, false)) {
-        ctl_error(ctx, "%s: should be an IP address (or an IP address "
-                  "and a port number with : as a separator).", lb_vip);
-        return;
-    }
-
-    struct ds lb_vip_normalized_ds = DS_EMPTY_INITIALIZER;
-    uint16_t lb_vip_port = ss_get_port(&ss_vip);
-    if (lb_vip_port) {
-        ss_format_address(&ss_vip, &lb_vip_normalized_ds);
-        ds_put_format(&lb_vip_normalized_ds, ":%d", lb_vip_port);
-    } else {
-        ss_format_address_nobracks(&ss_vip, &lb_vip_normalized_ds);
-    }
-    const char *lb_vip_normalized = ds_cstr(&lb_vip_normalized_ds);
-
-    if (!lb_vip_port && is_update_proto) {
-        ds_destroy(&lb_vip_normalized_ds);
-        ctl_error(ctx, "Protocol is unnecessary when no port of vip "
-                  "is given.");
-        return;
-    }
-
-    char *token = NULL, *save_ptr = NULL;
-    struct ds lb_ips_new = DS_EMPTY_INITIALIZER;
-    for (token = strtok_r(lb_ips, ",", &save_ptr);
-            token != NULL; token = strtok_r(NULL, ",", &save_ptr)) {
-        struct sockaddr_storage ss_dst;
-
-        if (lb_vip_port) {
-            if (!inet_parse_active(token, -1, &ss_dst, false)) {
-                ctl_error(ctx, "%s: should be an IP address and a port "
-                          "number with : as a separator.", token);
-                goto out;
-            }
-        } else {
-            if (!inet_parse_address(token, &ss_dst)) {
-                ctl_error(ctx, "%s: should be an IP address.", token);
-                goto out;
-            }
-        }
-
-        if (ss_vip.ss_family != ss_dst.ss_family) {
-            ctl_error(ctx, "%s: IP address family is different from VIP %s.",
-                      token, lb_vip_normalized);
-            goto out;
-        }
-        ds_put_format(&lb_ips_new, "%s%s",
-                lb_ips_new.length ? "," : "", token);
-    }
-
-    const struct nbrec_load_balancer *lb = NULL;
-    if (!add_duplicate) {
-        char *error = lb_by_name_or_uuid(ctx, lb_name, false, &lb);
-        if (error) {
-            ctx->error = error;
-            goto out;
-        }
-        if (lb) {
-            if (smap_get(&lb->vips, lb_vip_normalized)) {
-                if (!may_exist) {
-                    ctl_error(ctx, "%s: a load balancer with this vip (%s) "
-                              "already exists", lb_name, lb_vip_normalized);
-                    goto out;
-                }
-                /* Update the vips. */
-                smap_replace(CONST_CAST(struct smap *, &lb->vips),
-                        lb_vip_normalized, ds_cstr(&lb_ips_new));
-            } else {
-                /* Add the new vips. */
-                smap_add(CONST_CAST(struct smap *, &lb->vips),
-                        lb_vip_normalized, ds_cstr(&lb_ips_new));
-            }
-
-            /* Update the load balancer. */
-            if (is_update_proto) {
-                nbrec_load_balancer_verify_protocol(lb);
-                nbrec_load_balancer_set_protocol(lb, lb_proto);
-            }
-            nbrec_load_balancer_verify_vips(lb);
-            nbrec_load_balancer_set_vips(lb, &lb->vips);
-            goto out;
-        }
-    }
-
-    /* Create the load balancer. */
-    lb = nbrec_load_balancer_insert(ctx->txn);
-    nbrec_load_balancer_set_name(lb, lb_name);
-    nbrec_load_balancer_set_protocol(lb, lb_proto);
-    smap_add(CONST_CAST(struct smap *, &lb->vips),
-            lb_vip_normalized, ds_cstr(&lb_ips_new));
-    nbrec_load_balancer_set_vips(lb, &lb->vips);
-out:
-    ds_destroy(&lb_ips_new);
-
-    ds_destroy(&lb_vip_normalized_ds);
-}
-
-static void
-nbctl_lb_del(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_load_balancer *lb = NULL;
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-
-    char *error = lb_by_name_or_uuid(ctx, id, false, &lb);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lb) {
-        return;
-    }
-
-    if (ctx->argc == 3) {
-        const char *lb_vip = ctx->argv[2];
-        if (smap_get(&lb->vips, lb_vip)) {
-            smap_remove(CONST_CAST(struct smap *, &lb->vips), lb_vip);
-            if (smap_is_empty(&lb->vips)) {
-                nbrec_load_balancer_delete(lb);
-                return;
-            }
-
-            /* Delete the vip of the load balancer. */
-            nbrec_load_balancer_verify_vips(lb);
-            nbrec_load_balancer_set_vips(lb, &lb->vips);
-            return;
-        }
-        if (must_exist) {
-            ctl_error(ctx, "vip %s is not part of the load balancer.",
-                      lb_vip);
-            return;
-        }
-        return;
-    }
-    nbrec_load_balancer_delete(lb);
-}
-
-static void
-lb_info_add_smap(const struct nbrec_load_balancer *lb,
-                 struct smap *lbs, int vip_width)
-{
-    const struct smap_node **nodes = smap_sort(&lb->vips);
-    if (nodes) {
-        struct ds val = DS_EMPTY_INITIALIZER;
-        for (int i = 0; i < smap_count(&lb->vips); i++) {
-            const struct smap_node *node = nodes[i];
-
-            struct sockaddr_storage ss;
-            if (!inet_parse_active(node->key, 0, &ss, false)) {
-                continue;
-            }
-
-            char *protocol = ss_get_port(&ss) ? lb->protocol : "tcp/udp";
-            i == 0 ? ds_put_format(&val,
-                        UUID_FMT "    %-20.16s%-11.7s%-*.*s%s",
-                        UUID_ARGS(&lb->header_.uuid),
-                        lb->name, protocol,
-                        vip_width + 4, vip_width,
-                        node->key, node->value)
-                   : ds_put_format(&val, "\n%60s%-11.7s%-*.*s%s",
-                        "", protocol,
-                        vip_width + 4, vip_width,
-                        node->key, node->value);
-        }
-
-        smap_add_nocopy(lbs, xasprintf("%-20.16s", lb->name),
-                        ds_steal_cstr(&val));
-        free(nodes);
-    }
-}
-
-static void
-lb_info_print(struct ctl_context *ctx, struct smap *lbs, int vip_width)
-{
-    const struct smap_node **nodes = smap_sort(lbs);
-    if (nodes) {
-        ds_put_format(&ctx->output, "%-40.36s%-20.16s%-11.7s%-*.*s%s\n",
-                "UUID", "LB", "PROTO", vip_width + 4, vip_width, "VIP", "IPs");
-        for (size_t i = 0; i < smap_count(lbs); i++) {
-            const struct smap_node *node = nodes[i];
-            ds_put_format(&ctx->output, "%s\n", node->value);
-        }
-
-        free(nodes);
-    }
-}
-
-static int
-lb_get_max_vip_length(const struct nbrec_load_balancer *lb, int vip_width)
-{
-    const struct smap_node *node;
-    int max_length = vip_width;
-
-    SMAP_FOR_EACH (node, &lb->vips) {
-        size_t keylen = strlen(node->key);
-        if (max_length < keylen) {
-            max_length = keylen;
-        }
-    }
-
-    return max_length;
-}
-
-static void
-lb_info_list_all(struct ctl_context *ctx,
-                 const char *lb_name, bool lb_check)
-{
-    const struct nbrec_load_balancer *lb;
-    struct smap lbs = SMAP_INITIALIZER(&lbs);
-    int vip_width = 0;
-
-    NBREC_LOAD_BALANCER_FOR_EACH (lb, ctx->idl) {
-        if (lb_check && strcmp(lb->name, lb_name)) {
-            continue;
-        }
-        vip_width = lb_get_max_vip_length(lb, vip_width);
-    }
-
-    NBREC_LOAD_BALANCER_FOR_EACH(lb, ctx->idl) {
-        if (lb_check && strcmp(lb->name, lb_name)) {
-            continue;
-        }
-        lb_info_add_smap(lb, &lbs, vip_width);
-    }
-
-    lb_info_print(ctx, &lbs, vip_width);
-    smap_destroy(&lbs);
-}
-
-static void
-nbctl_lb_list(struct ctl_context *ctx)
-{
-    if (ctx->argc == 1) {
-        lb_info_list_all(ctx, NULL, false);
-    } else if (ctx->argc == 2) {
-        lb_info_list_all(ctx, ctx->argv[1], true);
-    }
-}
-
-static void
-nbctl_lr_lb_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr = NULL;
-    const struct nbrec_load_balancer *new_lb;
-
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    error = lb_by_name_or_uuid(ctx, ctx->argv[2], true, &new_lb);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    for (int i = 0; i < lr->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = lr->load_balancer[i];
-
-        if (uuid_equals(&new_lb->header_.uuid, &lb->header_.uuid)) {
-            if (may_exist) {
-                return;
-            }
-            ctl_error(ctx, UUID_FMT " : a load balancer with this UUID "
-                      "already exists", UUID_ARGS(&lb->header_.uuid));
-            return;
-        }
-    }
-
-    /* Insert the load balancer into the logical router. */
-    nbrec_logical_router_verify_load_balancer(lr);
-    struct nbrec_load_balancer **new_lbs
-        = xmalloc(sizeof *new_lbs * (lr->n_load_balancer + 1));
-
-    nullable_memcpy(new_lbs, lr->load_balancer,
-                    sizeof *new_lbs * lr->n_load_balancer);
-    new_lbs[lr->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *,
-            new_lb);
-    nbrec_logical_router_set_load_balancer(lr, new_lbs,
-            lr->n_load_balancer + 1);
-    free(new_lbs);
-}
-
-static void
-nbctl_lr_lb_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    const struct nbrec_load_balancer *del_lb;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If load-balancer is not specified, remove
-         * all load-balancers from the logical router. */
-        nbrec_logical_router_verify_load_balancer(lr);
-        nbrec_logical_router_set_load_balancer(lr, NULL, 0);
-        return;
-    }
-
-    error = lb_by_name_or_uuid(ctx, ctx->argv[2], true, &del_lb);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    for (size_t i = 0; i < lr->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = lr->load_balancer[i];
-
-        if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
-            /* Remove the matching rule. */
-            nbrec_logical_router_verify_load_balancer(lr);
-
-            struct nbrec_load_balancer **new_lbs
-                = xmemdup(lr->load_balancer,
-                    sizeof *new_lbs * lr->n_load_balancer);
-            new_lbs[i] = lr->load_balancer[lr->n_load_balancer - 1];
-            nbrec_logical_router_set_load_balancer(lr, new_lbs,
-                                          lr->n_load_balancer - 1);
-            free(new_lbs);
-            return;
-        }
-    }
-
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    if (must_exist) {
-        ctl_error(ctx, "load balancer %s is not part of any logical router.",
-                  del_lb->name);
-        return;
-    }
-}
-
-static void
-nbctl_lr_lb_list(struct ctl_context *ctx)
-{
-    const char *lr_name = ctx->argv[1];
-    const struct nbrec_logical_router *lr;
-    struct smap lbs = SMAP_INITIALIZER(&lbs);
-    int vip_width = 0;
-
-    char *error = lr_by_name_or_uuid(ctx, lr_name, true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    for (int i = 0; i < lr->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = lr->load_balancer[i];
-        vip_width = lb_get_max_vip_length(lb, vip_width);
-    }
-    for (int i = 0; i < lr->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = lr->load_balancer[i];
-        lb_info_add_smap(lb, &lbs, vip_width);
-    }
-
-    lb_info_print(ctx, &lbs, vip_width);
-    smap_destroy(&lbs);
-}
-
-static void
-nbctl_ls_lb_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls = NULL;
-    const struct nbrec_load_balancer *new_lb;
-
-    char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    error = lb_by_name_or_uuid(ctx, ctx->argv[2], true, &new_lb);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    for (int i = 0; i < ls->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = ls->load_balancer[i];
-
-        if (uuid_equals(&new_lb->header_.uuid, &lb->header_.uuid)) {
-            if (may_exist) {
-                return;
-            }
-            ctl_error(ctx, UUID_FMT " : a load balancer with this UUID "
-                      "already exists", UUID_ARGS(&lb->header_.uuid));
-            return;
-        }
-    }
-
-    /* Insert the load balancer into the logical switch. */
-    nbrec_logical_switch_verify_load_balancer(ls);
-    struct nbrec_load_balancer **new_lbs
-        = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1));
-
-    nullable_memcpy(new_lbs, ls->load_balancer,
-                    sizeof *new_lbs * ls->n_load_balancer);
-    new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *,
-            new_lb);
-    nbrec_logical_switch_set_load_balancer(ls, new_lbs,
-            ls->n_load_balancer + 1);
-    free(new_lbs);
-}
-
-static void
-nbctl_ls_lb_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_switch *ls;
-    const struct nbrec_load_balancer *del_lb;
-    char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If load-balancer is not specified, remove
-         * all load-balancers from the logical switch. */
-        nbrec_logical_switch_verify_load_balancer(ls);
-        nbrec_logical_switch_set_load_balancer(ls, NULL, 0);
-        return;
-    }
-
-    error = lb_by_name_or_uuid(ctx, ctx->argv[2], true, &del_lb);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    for (size_t i = 0; i < ls->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = ls->load_balancer[i];
-
-        if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
-            /* Remove the matching rule. */
-            nbrec_logical_switch_verify_load_balancer(ls);
-
-            struct nbrec_load_balancer **new_lbs
-                = xmemdup(ls->load_balancer,
-                        sizeof *new_lbs * ls->n_load_balancer);
-            new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1];
-            nbrec_logical_switch_set_load_balancer(ls, new_lbs,
-                                          ls->n_load_balancer - 1);
-            free(new_lbs);
-            return;
-        }
-    }
-
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    if (must_exist) {
-        ctl_error(ctx, "load balancer %s is not part of any logical switch.",
-                  del_lb->name);
-        return;
-    }
-}
-
-static void
-nbctl_ls_lb_list(struct ctl_context *ctx)
-{
-    const char *ls_name = ctx->argv[1];
-    const struct nbrec_logical_switch *ls;
-    struct smap lbs = SMAP_INITIALIZER(&lbs);
-    int vip_width = 0;
-
-    char *error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    for (int i = 0; i < ls->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = ls->load_balancer[i];
-        vip_width = lb_get_max_vip_length(lb, vip_width);
-    }
-    for (int i = 0; i < ls->n_load_balancer; i++) {
-        const struct nbrec_load_balancer *lb
-            = ls->load_balancer[i];
-        lb_info_add_smap(lb, &lbs, vip_width);
-    }
-
-    lb_info_print(ctx, &lbs, vip_width);
-    smap_destroy(&lbs);
-}
-
-static void
-nbctl_lr_add(struct ctl_context *ctx)
-{
-    const char *lr_name = ctx->argc == 2 ? ctx->argv[1] : NULL;
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
-    if (may_exist && add_duplicate) {
-        ctl_error(ctx, "--may-exist and --add-duplicate may not be used "
-                  "together");
-        return;
-    }
-
-    if (lr_name) {
-        if (!add_duplicate) {
-            const struct nbrec_logical_router *lr;
-            NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) {
-                if (!strcmp(lr->name, lr_name)) {
-                    if (may_exist) {
-                        return;
-                    }
-                    ctl_error(ctx, "%s: a router with this name already "
-                              "exists", lr_name);
-                    return;
-                }
-            }
-        }
-    } else if (may_exist) {
-        ctl_error(ctx, "--may-exist requires specifying a name");
-        return;
-    } else if (add_duplicate) {
-        ctl_error(ctx, "--add-duplicate requires specifying a name");
-        return;
-    }
-
-    struct nbrec_logical_router *lr;
-    lr = nbrec_logical_router_insert(ctx->txn);
-    if (lr_name) {
-        nbrec_logical_router_set_name(lr, lr_name);
-    }
-}
-
-static void
-nbctl_lr_del(struct ctl_context *ctx)
-{
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_router *lr = NULL;
-
-    char *error = lr_by_name_or_uuid(ctx, id, must_exist, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lr) {
-        return;
-    }
-
-    nbrec_logical_router_delete(lr);
-}
-
-static void
-nbctl_lr_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    struct smap lrs;
-
-    smap_init(&lrs);
-    NBREC_LOGICAL_ROUTER_FOR_EACH(lr, ctx->idl) {
-        smap_add_format(&lrs, lr->name, UUID_FMT " (%s)",
-                        UUID_ARGS(&lr->header_.uuid), lr->name);
-    }
-    const struct smap_node **nodes = smap_sort(&lrs);
-    for (size_t i = 0; i < smap_count(&lrs); i++) {
-        const struct smap_node *node = nodes[i];
-        ds_put_format(&ctx->output, "%s\n", node->value);
-    }
-    smap_destroy(&lrs);
-    free(nodes);
-}
-
-static char *
-dhcp_options_get(struct ctl_context *ctx, const char *id, bool must_exist,
-                 const struct nbrec_dhcp_options **dhcp_opts_p)
-{
-    struct uuid dhcp_opts_uuid;
-    const struct nbrec_dhcp_options *dhcp_opts = NULL;
-    if (uuid_from_string(&dhcp_opts_uuid, id)) {
-        dhcp_opts = nbrec_dhcp_options_get_for_uuid(ctx->idl, &dhcp_opts_uuid);
-    }
-
-    *dhcp_opts_p = dhcp_opts;
-    if (!dhcp_opts && must_exist) {
-        return xasprintf("%s: dhcp options UUID not found", id);
-    }
-
-    return NULL;
-}
-
-static void
-nbctl_dhcp_options_create(struct ctl_context *ctx)
-{
-    /* Validate the cidr */
-    ovs_be32 ip;
-    unsigned int plen;
-    char *error = ip_parse_cidr(ctx->argv[1], &ip, &plen);
-    if (error){
-        /* check if its IPv6 cidr */
-        free(error);
-        struct in6_addr ipv6;
-        error = ipv6_parse_cidr(ctx->argv[1], &ipv6, &plen);
-        if (error) {
-            free(error);
-            ctl_error(ctx, "Invalid cidr format '%s'", ctx->argv[1]);
-            return;
-        }
-    }
-
-    struct nbrec_dhcp_options *dhcp_opts = nbrec_dhcp_options_insert(ctx->txn);
-    nbrec_dhcp_options_set_cidr(dhcp_opts, ctx->argv[1]);
-
-    struct smap ext_ids = SMAP_INITIALIZER(&ext_ids);
-    for (size_t i = 2; i < ctx->argc; i++) {
-        char *key, *value;
-        value = xstrdup(ctx->argv[i]);
-        key = strsep(&value, "=");
-        if (value) {
-            smap_add(&ext_ids, key, value);
-        }
-        free(key);
-    }
-
-    nbrec_dhcp_options_set_external_ids(dhcp_opts, &ext_ids);
-    smap_destroy(&ext_ids);
-}
-
-static void
-nbctl_dhcp_options_set_options(struct ctl_context *ctx)
-{
-    const struct nbrec_dhcp_options *dhcp_opts;
-    char *error = dhcp_options_get(ctx, ctx->argv[1], true, &dhcp_opts);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
-    for (size_t i = 2; i < ctx->argc; i++) {
-        char *key, *value;
-        value = xstrdup(ctx->argv[i]);
-        key = strsep(&value, "=");
-        if (value) {
-            smap_add(&dhcp_options, key, value);
-        }
-        free(key);
-    }
-
-    nbrec_dhcp_options_set_options(dhcp_opts, &dhcp_options);
-    smap_destroy(&dhcp_options);
-}
-
-static void
-nbctl_dhcp_options_get_options(struct ctl_context *ctx)
-{
-    const struct nbrec_dhcp_options *dhcp_opts;
-    char *error = dhcp_options_get(ctx, ctx->argv[1], true, &dhcp_opts);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    struct smap_node *node;
-    SMAP_FOR_EACH(node, &dhcp_opts->options) {
-        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
-    }
-}
-
-static void
-nbctl_dhcp_options_del(struct ctl_context *ctx)
-{
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const char *id = ctx->argv[1];
-    const struct nbrec_dhcp_options *dhcp_opts;
-
-    char *error = dhcp_options_get(ctx, id, must_exist, &dhcp_opts);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!dhcp_opts) {
-        return;
-    }
-
-    nbrec_dhcp_options_delete(dhcp_opts);
-}
-
-static void
-nbctl_dhcp_options_list(struct ctl_context *ctx)
-{
-    const struct nbrec_dhcp_options *dhcp_opts;
-    struct smap dhcp_options;
-
-    smap_init(&dhcp_options);
-    NBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opts, ctx->idl) {
-        smap_add_format(&dhcp_options, dhcp_opts->cidr, UUID_FMT,
-                        UUID_ARGS(&dhcp_opts->header_.uuid));
-    }
-    const struct smap_node **nodes = smap_sort(&dhcp_options);
-    for (size_t i = 0; i < smap_count(&dhcp_options); i++) {
-        const struct smap_node *node = nodes[i];
-        ds_put_format(&ctx->output, "%s\n", node->value);
-    }
-    smap_destroy(&dhcp_options);
-    free(nodes);
-}
-
-/* The caller must free the returned string. */
-static char *
-normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen)
-{
-    ovs_be32 network = ipv4 & be32_prefix_mask(plen);
-    if (plen == 32) {
-        return xasprintf(IP_FMT, IP_ARGS(network));
-    } else {
-        return xasprintf(IP_FMT"/%d", IP_ARGS(network), plen);
-    }
-}
-
-/* The caller must free the returned string. */
-static char *
-normalize_ipv6_prefix(struct in6_addr ipv6, unsigned int plen)
-{
-    char network_s[INET6_ADDRSTRLEN];
-
-    struct in6_addr mask = ipv6_create_mask(plen);
-    struct in6_addr network = ipv6_addr_bitand(&ipv6, &mask);
-
-    inet_ntop(AF_INET6, &network, network_s, INET6_ADDRSTRLEN);
-    if (plen == 128) {
-        return xasprintf("%s", network_s);
-    } else {
-        return xasprintf("%s/%d", network_s, plen);
-    }
-}
-
-/* The caller must free the returned string. */
-static char *
-normalize_prefix_str(const char *orig_prefix)
-{
-    unsigned int plen;
-    ovs_be32 ipv4;
-    char *error;
-
-    error = ip_parse_cidr(orig_prefix, &ipv4, &plen);
-    if (!error) {
-        return normalize_ipv4_prefix(ipv4, plen);
-    } else {
-        struct in6_addr ipv6;
-        free(error);
-
-        error = ipv6_parse_cidr(orig_prefix, &ipv6, &plen);
-        if (error) {
-            free(error);
-            return NULL;
-        }
-        return normalize_ipv6_prefix(ipv6, plen);
-    }
-}
-
-static void
-nbctl_lr_policy_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    int64_t priority = 0;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    error = parse_priority(ctx->argv[2], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    const char *action = ctx->argv[4];
-    char *next_hop = NULL;
-
-    /* Validate action. */
-    if (strcmp(action, "allow") && strcmp(action, "drop")
-        && strcmp(action, "reroute")) {
-        ctl_error(ctx, "%s: action must be one of \"allow\", \"drop\", "
-                  "and \"reroute\"", action);
-    }
-    if (!strcmp(action, "reroute")) {
-        if (ctx->argc < 6) {
-            ctl_error(ctx, "Nexthop is required when action is reroute.");
-        }
-    }
-
-    /* Check if same routing policy already exists.
-     * A policy is uniquely identified by priority and match */
-    for (int i = 0; i < lr->n_policies; i++) {
-        const struct nbrec_logical_router_policy *policy = lr->policies[i];
-        if (policy->priority == priority &&
-            !strcmp(policy->match, ctx->argv[3])) {
-            ctl_error(ctx, "Same routing policy already existed on the "
-                      "logical router %s.", ctx->argv[1]);
-        }
-    }
-    if (ctx->argc == 6) {
-        next_hop = normalize_prefix_str(ctx->argv[5]);
-        if (!next_hop) {
-            ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]);
-        }
-    }
-
-    struct nbrec_logical_router_policy *policy;
-    policy = nbrec_logical_router_policy_insert(ctx->txn);
-    nbrec_logical_router_policy_set_priority(policy, priority);
-    nbrec_logical_router_policy_set_match(policy, ctx->argv[3]);
-    nbrec_logical_router_policy_set_action(policy, action);
-    if (ctx->argc == 6) {
-        nbrec_logical_router_policy_set_nexthop(policy, next_hop);
-    }
-    nbrec_logical_router_verify_policies(lr);
-    struct nbrec_logical_router_policy **new_policies
-        = xmalloc(sizeof *new_policies * (lr->n_policies + 1));
-    memcpy(new_policies, lr->policies,
-           sizeof *new_policies * lr->n_policies);
-    new_policies[lr->n_policies] = policy;
-    nbrec_logical_router_set_policies(lr, new_policies,
-                                      lr->n_policies + 1);
-    free(new_policies);
-    if (next_hop != NULL) {
-        free(next_hop);
-    }
-}
-
-static void
-nbctl_lr_policy_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    int64_t priority = 0;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If a priority is not specified, delete all policies. */
-        nbrec_logical_router_set_policies(lr, NULL, 0);
-        return;
-    }
-
-    error = parse_priority(ctx->argv[2], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    /* If match is not specified, delete all routing policies with the
-     * specified priority. */
-    if (ctx->argc == 3) {
-        struct nbrec_logical_router_policy **new_policies
-            = xmemdup(lr->policies,
-                      sizeof *new_policies * lr->n_policies);
-        int n_policies = 0;
-        for (int i = 0; i < lr->n_policies; i++) {
-            if (priority != lr->policies[i]->priority) {
-                new_policies[n_policies++] = lr->policies[i];
-            }
-        }
-        nbrec_logical_router_verify_policies(lr);
-        nbrec_logical_router_set_policies(lr, new_policies, n_policies);
-        free(new_policies);
-        return;
-    }
-
-    /* Delete policy that has the same priority and match string */
-    for (int i = 0; i < lr->n_policies; i++) {
-        struct nbrec_logical_router_policy *routing_policy = lr->policies[i];
-        if (priority == routing_policy->priority &&
-            !strcmp(ctx->argv[3], routing_policy->match)) {
-            struct nbrec_logical_router_policy **new_policies
-                = xmemdup(lr->policies,
-                          sizeof *new_policies * lr->n_policies);
-            new_policies[i] = lr->policies[lr->n_policies - 1];
-            nbrec_logical_router_verify_policies(lr);
-            nbrec_logical_router_set_policies(lr, new_policies,
-                                              lr->n_policies - 1);
-            free(new_policies);
-            return;
-        }
-    }
-}
-
- struct routing_policy {
-    int priority;
-    char *match;
-    const struct nbrec_logical_router_policy *policy;
-};
-
-static int
-routing_policy_cmp(const void *policy1_, const void *policy2_)
-{
-    const struct routing_policy *policy1p = policy1_;
-    const struct routing_policy *policy2p = policy2_;
-    if (policy1p->priority != policy2p->priority) {
-        return policy1p->priority > policy2p->priority ? -1 : 1;
-    } else {
-        return strcmp(policy1p->match, policy2p->match);
-    }
-}
-
-static void
-print_routing_policy(const struct nbrec_logical_router_policy *policy,
-                     struct ds *s)
-{
-    if (policy->nexthop != NULL) {
-        char *next_hop = normalize_prefix_str(policy->nexthop);
-        ds_put_format(s, "%10"PRId64" %50s %15s %25s", policy->priority,
-                      policy->match, policy->action, next_hop);
-        free(next_hop);
-    } else {
-        ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority,
-                      policy->match, policy->action);
-    }
-    ds_put_char(s, '\n');
-}
-
-static void
-nbctl_lr_policy_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    struct routing_policy *policies;
-    size_t n_policies = 0;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    policies = xmalloc(sizeof *policies * lr->n_policies);
-     for (int i = 0; i < lr->n_policies; i++) {
-        const struct nbrec_logical_router_policy *policy
-            = lr->policies[i];
-        policies[n_policies].priority = policy->priority;
-        policies[n_policies].match = policy->match;
-        policies[n_policies].policy = policy;
-        n_policies++;
-    }
-    qsort(policies, n_policies, sizeof *policies, routing_policy_cmp);
-    if (n_policies) {
-        ds_put_cstr(&ctx->output, "Routing Policies\n");
-    }
-    for (int i = 0; i < n_policies; i++) {
-        print_routing_policy(policies[i].policy, &ctx->output);
-    }
-    free(policies);
-}
-
-static void
-nbctl_lr_route_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr = NULL;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    char *prefix, *next_hop;
-
-    const char *policy = shash_find_data(&ctx->options, "--policy");
-    if (policy && strcmp(policy, "src-ip") && strcmp(policy, "dst-ip")) {
-        ctl_error(ctx, "bad policy: %s", policy);
-        return;
-    }
-
-    prefix = normalize_prefix_str(ctx->argv[2]);
-    if (!prefix) {
-        ctl_error(ctx, "bad prefix argument: %s", ctx->argv[2]);
-        return;
-    }
-
-    next_hop = normalize_prefix_str(ctx->argv[3]);
-    if (!next_hop) {
-        free(prefix);
-        ctl_error(ctx, "bad next hop argument: %s", ctx->argv[3]);
-        return;
-    }
-
-    if (strchr(prefix, '.')) {
-        ovs_be32 hop_ipv4;
-        if (!ip_parse(ctx->argv[3], &hop_ipv4)) {
-            free(prefix);
-            free(next_hop);
-            ctl_error(ctx, "bad IPv4 nexthop argument: %s", ctx->argv[3]);
-            return;
-        }
-    } else {
-        struct in6_addr hop_ipv6;
-        if (!ipv6_parse(ctx->argv[3], &hop_ipv6)) {
-            free(prefix);
-            free(next_hop);
-            ctl_error(ctx, "bad IPv6 nexthop argument: %s", ctx->argv[3]);
-            return;
-        }
-    }
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    for (int i = 0; i < lr->n_static_routes; i++) {
-        const struct nbrec_logical_router_static_route *route
-            = lr->static_routes[i];
-        char *rt_prefix;
-
-        rt_prefix = normalize_prefix_str(lr->static_routes[i]->ip_prefix);
-        if (!rt_prefix) {
-            /* Ignore existing prefix we couldn't parse. */
-            continue;
-        }
-
-        if (strcmp(rt_prefix, prefix)) {
-            free(rt_prefix);
-            continue;
-        }
-
-        if (!may_exist) {
-            ctl_error(ctx, "duplicate prefix: %s", prefix);
-            free(next_hop);
-            free(rt_prefix);
-            free(prefix);
-            return;
-        }
-
-        /* Update the next hop for an existing route. */
-        nbrec_logical_router_verify_static_routes(lr);
-        nbrec_logical_router_static_route_verify_ip_prefix(route);
-        nbrec_logical_router_static_route_verify_nexthop(route);
-        nbrec_logical_router_static_route_set_ip_prefix(route, prefix);
-        nbrec_logical_router_static_route_set_nexthop(route, next_hop);
-        if (ctx->argc == 5) {
-            nbrec_logical_router_static_route_set_output_port(route,
-                                                              ctx->argv[4]);
-        }
-        if (policy) {
-             nbrec_logical_router_static_route_set_policy(route, policy);
-        }
-        free(rt_prefix);
-        free(next_hop);
-        free(prefix);
-        return;
-    }
-
-    struct nbrec_logical_router_static_route *route;
-    route = nbrec_logical_router_static_route_insert(ctx->txn);
-    nbrec_logical_router_static_route_set_ip_prefix(route, prefix);
-    nbrec_logical_router_static_route_set_nexthop(route, next_hop);
-    if (ctx->argc == 5) {
-        nbrec_logical_router_static_route_set_output_port(route, ctx->argv[4]);
-    }
-    if (policy) {
-        nbrec_logical_router_static_route_set_policy(route, policy);
-    }
-
-    nbrec_logical_router_verify_static_routes(lr);
-    struct nbrec_logical_router_static_route **new_routes
-        = xmalloc(sizeof *new_routes * (lr->n_static_routes + 1));
-    nullable_memcpy(new_routes, lr->static_routes,
-               sizeof *new_routes * lr->n_static_routes);
-    new_routes[lr->n_static_routes] = route;
-    nbrec_logical_router_set_static_routes(lr, new_routes,
-                                           lr->n_static_routes + 1);
-    free(new_routes);
-    free(next_hop);
-    free(prefix);
-}
-
-static void
-nbctl_lr_route_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If a prefix is not specified, delete all routes. */
-        nbrec_logical_router_set_static_routes(lr, NULL, 0);
-        return;
-    }
-
-    char *prefix = normalize_prefix_str(ctx->argv[2]);
-    if (!prefix) {
-        ctl_error(ctx, "bad prefix argument: %s", ctx->argv[2]);
-        return;
-    }
-
-    for (int i = 0; i < lr->n_static_routes; i++) {
-        char *rt_prefix = normalize_prefix_str(lr->static_routes[i]->ip_prefix);
-        if (!rt_prefix) {
-            /* Ignore existing prefix we couldn't parse. */
-            continue;
-        }
-
-        if (!strcmp(prefix, rt_prefix)) {
-            struct nbrec_logical_router_static_route **new_routes
-                = xmemdup(lr->static_routes,
-                          sizeof *new_routes * lr->n_static_routes);
-
-            new_routes[i] = lr->static_routes[lr->n_static_routes - 1];
-            nbrec_logical_router_verify_static_routes(lr);
-            nbrec_logical_router_set_static_routes(lr, new_routes,
-                                                 lr->n_static_routes - 1);
-            free(new_routes);
-            free(rt_prefix);
-            free(prefix);
-            return;
-        }
-        free(rt_prefix);
-    }
-
-    if (!shash_find(&ctx->options, "--if-exists")) {
-        ctl_error(ctx, "no matching prefix: %s", prefix);
-    }
-    free(prefix);
-}
-
-static void
-nbctl_lr_nat_add(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr = NULL;
-    const char *nat_type = ctx->argv[2];
-    const char *external_ip = ctx->argv[3];
-    const char *logical_ip = ctx->argv[4];
-    char *new_logical_ip = NULL;
-
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (strcmp(nat_type, "dnat") && strcmp(nat_type, "snat")
-            && strcmp(nat_type, "dnat_and_snat")) {
-        ctl_error(ctx, "%s: type must be one of \"dnat\", \"snat\" and "
-                  "\"dnat_and_snat\".", nat_type);
-        return;
-    }
-
-    ovs_be32 ipv4 = 0;
-    unsigned int plen;
-    if (!ip_parse(external_ip, &ipv4)) {
-        ctl_error(ctx, "%s: should be an IPv4 address.", external_ip);
-        return;
-    }
-
-    if (strcmp("snat", nat_type)) {
-        if (!ip_parse(logical_ip, &ipv4)) {
-            ctl_error(ctx, "%s: should be an IPv4 address.", logical_ip);
-            return;
-        }
-        new_logical_ip = xstrdup(logical_ip);
-    } else {
-        error = ip_parse_cidr(logical_ip, &ipv4, &plen);
-        if (error) {
-            free(error);
-            ctl_error(ctx, "%s: should be an IPv4 address or network.",
-                      logical_ip);
-            return;
-        }
-        new_logical_ip = normalize_ipv4_prefix(ipv4, plen);
-    }
-
-    const char *logical_port;
-    const char *external_mac;
-    if (ctx->argc == 6) {
-        ctl_error(ctx, "lr-nat-add with logical_port "
-                  "must also specify external_mac.");
-        free(new_logical_ip);
-        return;
-    } else if (ctx->argc == 7) {
-        if (strcmp(nat_type, "dnat_and_snat")) {
-            ctl_error(ctx, "logical_port and external_mac are only valid when "
-                      "type is \"dnat_and_snat\".");
-            free(new_logical_ip);
-            return;
-        }
-
-        logical_port = ctx->argv[5];
-        const struct nbrec_logical_switch_port *lsp;
-        error = lsp_by_name_or_uuid(ctx, logical_port, true, &lsp);
-        if (error) {
-            ctx->error = error;
-            free(new_logical_ip);
-            return;
-        }
-
-        external_mac = ctx->argv[6];
-        struct eth_addr ea;
-        if (!eth_addr_from_string(external_mac, &ea)) {
-            ctl_error(ctx, "invalid mac address %s.", external_mac);
-            free(new_logical_ip);
-            return;
-        }
-    } else {
-        logical_port = NULL;
-        external_mac = NULL;
-    }
-
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    int is_snat = !strcmp("snat", nat_type);
-    for (size_t i = 0; i < lr->n_nat; i++) {
-        const struct nbrec_nat *nat = lr->nat[i];
-        if (!strcmp(nat_type, nat->type)) {
-            if (!strcmp(is_snat ? new_logical_ip : external_ip,
-                        is_snat ? nat->logical_ip : nat->external_ip)) {
-                if (!strcmp(is_snat ? external_ip : new_logical_ip,
-                            is_snat ? nat->external_ip : nat->logical_ip)) {
-                        if (may_exist) {
-                            nbrec_nat_verify_logical_port(nat);
-                            nbrec_nat_verify_external_mac(nat);
-                            nbrec_nat_set_logical_port(nat, logical_port);
-                            nbrec_nat_set_external_mac(nat, external_mac);
-                            free(new_logical_ip);
-                            return;
-                        }
-                        ctl_error(ctx, "%s, %s: a NAT with this external_ip "
-                                  "and logical_ip already exists",
-                                  external_ip, new_logical_ip);
-                        free(new_logical_ip);
-                        return;
-                } else {
-                    ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
-                              "already exists",
-                              nat_type,
-                              is_snat ? "logical_ip" : "external_ip",
-                              is_snat ? new_logical_ip : external_ip);
-                    free(new_logical_ip);
-                    return;
-                }
-            }
-        }
-    }
-
-    /* Create the NAT. */
-    struct nbrec_nat *nat = nbrec_nat_insert(ctx->txn);
-    nbrec_nat_set_type(nat, nat_type);
-    nbrec_nat_set_external_ip(nat, external_ip);
-    nbrec_nat_set_logical_ip(nat, new_logical_ip);
-    if (logical_port && external_mac) {
-        nbrec_nat_set_logical_port(nat, logical_port);
-        nbrec_nat_set_external_mac(nat, external_mac);
-    }
-    free(new_logical_ip);
-
-    /* Insert the NAT into the logical router. */
-    nbrec_logical_router_verify_nat(lr);
-    struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * (lr->n_nat + 1));
-    nullable_memcpy(new_nats, lr->nat, sizeof *new_nats * lr->n_nat);
-    new_nats[lr->n_nat] = nat;
-    nbrec_logical_router_set_nat(lr, new_nats, lr->n_nat + 1);
-    free(new_nats);
-}
-
-static void
-nbctl_lr_nat_del(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr = NULL;
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        /* If type, external_ip and logical_ip are not specified, delete
-         * all NATs. */
-        nbrec_logical_router_verify_nat(lr);
-        nbrec_logical_router_set_nat(lr, NULL, 0);
-        return;
-    }
-
-    const char *nat_type = ctx->argv[2];
-    if (strcmp(nat_type, "dnat") && strcmp(nat_type, "snat")
-            && strcmp(nat_type, "dnat_and_snat")) {
-        ctl_error(ctx, "%s: type must be one of \"dnat\", \"snat\" and "
-                  "\"dnat_and_snat\".", nat_type);
-        return;
-    }
-
-    if (ctx->argc == 3) {
-        /*Deletes all NATs with the specified type. */
-        struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * lr->n_nat);
-        int n_nat = 0;
-        for (size_t i = 0; i < lr->n_nat; i++) {
-            if (strcmp(nat_type, lr->nat[i]->type)) {
-                new_nats[n_nat++] = lr->nat[i];
-            }
-        }
-
-        nbrec_logical_router_verify_nat(lr);
-        nbrec_logical_router_set_nat(lr, new_nats, n_nat);
-        free(new_nats);
-        return;
-    }
-
-    const char *nat_ip = ctx->argv[3];
-    int is_snat = !strcmp("snat", nat_type);
-    /* Remove the matching NAT. */
-    for (size_t i = 0; i < lr->n_nat; i++) {
-        struct nbrec_nat *nat = lr->nat[i];
-        if (!strcmp(nat_type, nat->type) &&
-             !strcmp(nat_ip, is_snat ? nat->logical_ip : nat->external_ip)) {
-            struct nbrec_nat **new_nats
-                = xmemdup(lr->nat, sizeof *new_nats * lr->n_nat);
-            new_nats[i] = lr->nat[lr->n_nat - 1];
-            nbrec_logical_router_verify_nat(lr);
-            nbrec_logical_router_set_nat(lr, new_nats,
-                                          lr->n_nat - 1);
-            free(new_nats);
-            return;
-        }
-    }
-
-    if (must_exist) {
-        ctl_error(ctx, "no matching NAT with the type (%s) and %s (%s)",
-                  nat_type, is_snat ? "logical_ip" : "external_ip", nat_ip);
-        return;
-    }
-}
-
-static void
-nbctl_lr_nat_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    struct smap lr_nats = SMAP_INITIALIZER(&lr_nats);
-    for (size_t i = 0; i < lr->n_nat; i++) {
-        const struct nbrec_nat *nat = lr->nat[i];
-        char *key = xasprintf("%-17.13s%s", nat->type, nat->external_ip);
-        if (nat->external_mac && nat->logical_port) {
-            smap_add_format(&lr_nats, key, "%-22.18s%-21.17s%s",
-                            nat->logical_ip, nat->external_mac,
-                            nat->logical_port);
-        } else {
-            smap_add_format(&lr_nats, key, "%s", nat->logical_ip);
-        }
-        free(key);
-    }
-
-    const struct smap_node **nodes = smap_sort(&lr_nats);
-    if (nodes) {
-        ds_put_format(&ctx->output, "%-17.13s%-19.15s%-22.18s%-21.17s%s\n",
-                "TYPE", "EXTERNAL_IP", "LOGICAL_IP", "EXTERNAL_MAC",
-                "LOGICAL_PORT");
-        for (size_t i = 0; i < smap_count(&lr_nats); i++) {
-            const struct smap_node *node = nodes[i];
-            ds_put_format(&ctx->output, "%-36.32s%s\n",
-                    node->key, node->value);
-        }
-        free(nodes);
-    }
-    smap_destroy(&lr_nats);
-}
-
-
-static char * OVS_WARN_UNUSED_RESULT
-lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-                    const struct nbrec_logical_router_port **lrp_p)
-{
-    const struct nbrec_logical_router_port *lrp = NULL;
-    *lrp_p = NULL;
-
-    struct uuid lrp_uuid;
-    bool is_uuid = uuid_from_string(&lrp_uuid, id);
-    if (is_uuid) {
-        lrp = nbrec_logical_router_port_get_for_uuid(ctx->idl, &lrp_uuid);
-    }
-
-    if (!lrp) {
-        NBREC_LOGICAL_ROUTER_PORT_FOR_EACH(lrp, ctx->idl) {
-            if (!strcmp(lrp->name, id)) {
-                break;
-            }
-        }
-    }
-
-    if (!lrp && must_exist) {
-        return xasprintf("%s: port %s not found",
-                         id, is_uuid ? "UUID" : "name");
-    }
-
-    *lrp_p = lrp;
-    return NULL;
-}
-
-/* Returns the logical router that contains 'lrp'. */
-static char * OVS_WARN_UNUSED_RESULT
-lrp_to_lr(const struct ovsdb_idl *idl,
-          const struct nbrec_logical_router_port *lrp,
-          const struct nbrec_logical_router **lr_p)
-{
-    const struct nbrec_logical_router *lr;
-    *lr_p = NULL;
-
-    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, idl) {
-        for (size_t i = 0; i < lr->n_ports; i++) {
-            if (lr->ports[i] == lrp) {
-                *lr_p = lr;
-                return NULL;
-            }
-        }
-    }
-
-    /* Can't happen because of the database schema */
-    return xasprintf("port %s is not part of any logical router",
-                     lrp->name);
-}
-
-static const char *
-lr_get_name(const struct nbrec_logical_router *lr, char uuid_s[UUID_LEN + 1],
-            size_t uuid_s_size)
-{
-    if (lr->name[0]) {
-        return lr->name;
-    }
-    snprintf(uuid_s, uuid_s_size, UUID_FMT, UUID_ARGS(&lr->header_.uuid));
-    return uuid_s;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-gc_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-                   const struct nbrec_gateway_chassis **gc_p)
-{
-    const struct nbrec_gateway_chassis *gc = NULL;
-    *gc_p = NULL;
-
-    struct uuid gc_uuid;
-    bool is_uuid = uuid_from_string(&gc_uuid, id);
-    if (is_uuid) {
-        gc = nbrec_gateway_chassis_get_for_uuid(ctx->idl, &gc_uuid);
-    }
-
-    if (!gc) {
-        NBREC_GATEWAY_CHASSIS_FOR_EACH (gc, ctx->idl) {
-            if (!strcmp(gc->name, id)) {
-                break;
-            }
-        }
-    }
-
-    if (!gc && must_exist) {
-        return xasprintf("%s: gateway chassis %s not found", id,
-                         is_uuid ? "UUID" : "name");
-    }
-
-    *gc_p = gc;
-    return NULL;
-}
-
-static void
-nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx)
-{
-    char *gc_name;
-    int64_t priority = 0;
-    const char *lrp_name = ctx->argv[1];
-    const struct nbrec_logical_router_port *lrp = NULL;
-    char *error = lrp_by_name_or_uuid(ctx, lrp_name, true, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lrp) {
-        ctl_error(ctx, "router port %s is required", lrp_name);
-        return;
-    }
-
-    const char *chassis_name = ctx->argv[2];
-    if (ctx->argv[3]) {
-        error = parse_priority(ctx->argv[3], &priority);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-    }
-
-    gc_name = xasprintf("%s-%s", lrp_name, chassis_name);
-    const struct nbrec_gateway_chassis *existing_gc;
-    error = gc_by_name_or_uuid(ctx, gc_name, false, &existing_gc);
-    if (error) {
-        ctx->error = error;
-        free(gc_name);
-        return;
-    }
-    if (existing_gc) {
-        nbrec_gateway_chassis_set_priority(existing_gc, priority);
-        free(gc_name);
-        return;
-    }
-
-    /* Create the logical gateway chassis. */
-    struct nbrec_gateway_chassis *gc
-        = nbrec_gateway_chassis_insert(ctx->txn);
-    nbrec_gateway_chassis_set_name(gc, gc_name);
-    nbrec_gateway_chassis_set_chassis_name(gc, chassis_name);
-    nbrec_gateway_chassis_set_priority(gc, priority);
-
-    /* Insert the logical gateway chassis into the logical router port. */
-    nbrec_logical_router_port_verify_gateway_chassis(lrp);
-    struct nbrec_gateway_chassis **new_gc = xmalloc(
-        sizeof *new_gc * (lrp->n_gateway_chassis + 1));
-    nullable_memcpy(new_gc, lrp->gateway_chassis,
-                    sizeof *new_gc * lrp->n_gateway_chassis);
-    new_gc[lrp->n_gateway_chassis] = gc;
-    nbrec_logical_router_port_set_gateway_chassis(
-        lrp, new_gc, lrp->n_gateway_chassis + 1);
-    free(new_gc);
-    free(gc_name);
-}
-
-/* Removes logical router port 'lrp->gateway_chassis[idx]'. */
-static void
-remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx)
-{
-    const struct nbrec_gateway_chassis *gc = lrp->gateway_chassis[idx];
-
-    if (lrp->n_gateway_chassis == 1) {
-        nbrec_logical_router_port_set_gateway_chassis(lrp, NULL, 0);
-    } else {
-        /* First remove 'gc' from the array of gateway_chassis.  This is what
-         * will actually cause the gateway chassis to be deleted when the
-         * transaction is sent to the database server (due to garbage
-         * collection). */
-        struct nbrec_gateway_chassis **new_gc
-            = xmemdup(lrp->gateway_chassis,
-                      sizeof *new_gc * lrp->n_gateway_chassis);
-        new_gc[idx] = new_gc[lrp->n_gateway_chassis - 1];
-        nbrec_logical_router_port_verify_gateway_chassis(lrp);
-        nbrec_logical_router_port_set_gateway_chassis(
-            lrp, new_gc, lrp->n_gateway_chassis - 1);
-        free(new_gc);
-    }
-
-    /* Delete 'gc' from the IDL.  This won't have a real effect on
-     * the database server (the IDL will suppress it in fact) but it
-     * means that it won't show up when we iterate with
-     * NBREC_GATEWAY_CHASSIS_FOR_EACH later. */
-    nbrec_gateway_chassis_delete(gc);
-}
-
-static void
-nbctl_lrp_del_gateway_chassis(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router_port *lrp = NULL;
-    char *error = lrp_by_name_or_uuid(ctx, ctx->argv[1], true, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lrp) {
-        return;
-    }
-    /* Find the lrp that contains 'gc', then delete it. */
-    const char *chassis_name = ctx->argv[2];
-    for (size_t i = 0; i < lrp->n_gateway_chassis; i++) {
-        if (!strncmp(lrp->gateway_chassis[i]->chassis_name,
-                    chassis_name,
-                    strlen(lrp->gateway_chassis[i]->chassis_name))) {
-            remove_gc(lrp, i);
-            return;
-        }
-    }
-
-    /* Can't happen because of the database schema. */
-    ctl_error(ctx, "chassis %s is not added to logical port %s",
-              chassis_name, ctx->argv[1]);
-}
-
-/* Print a list of gateway chassis. */
-static void
-nbctl_lrp_get_gateway_chassis(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_router_port *lrp = NULL;
-    const struct nbrec_gateway_chassis **gcs;
-    size_t i;
-
-    char *error = lrp_by_name_or_uuid(ctx, id, true, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    gcs = get_ordered_gw_chassis_prio_list(lrp);
-
-    for (i = 0; i < lrp->n_gateway_chassis; i++) {
-        const struct nbrec_gateway_chassis *gc = gcs[i];
-        ds_put_format(&ctx->output, "%s %5"PRId64"\n",
-                      gc->name, gc->priority);
-    }
-
-    free(gcs);
-}
-
-static void
-nbctl_lrp_add(struct ctl_context *ctx)
-{
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-
-    const struct nbrec_logical_router *lr = NULL;
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const char *lrp_name = ctx->argv[2];
-    const char *mac = ctx->argv[3];
-    const char **networks = (const char **) &ctx->argv[4];
-
-    int n_networks = ctx->argc - 4;
-    for (int i = 4; i < ctx->argc; i++) {
-        if (strchr(ctx->argv[i], '=')) {
-            n_networks = i - 4;
-            break;
-        }
-    }
-
-    if (!n_networks) {
-        ctl_error(ctx, "%s: router port requires specifying a network",
-                  lrp_name);
-        return;
-    }
-
-    char **settings = (char **) &ctx->argv[n_networks + 4];
-    int n_settings = ctx->argc - 4 - n_networks;
-
-    const struct nbrec_logical_router_port *lrp;
-    error = lrp_by_name_or_uuid(ctx, lrp_name, false, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (lrp) {
-        if (!may_exist) {
-            ctl_error(ctx, "%s: a port with this name already exists",
-                      lrp_name);
-            return;
-        }
-
-        const struct nbrec_logical_router *bound_lr;
-        error = lrp_to_lr(ctx->idl, lrp, &bound_lr);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-        if (bound_lr != lr) {
-            char uuid_s[UUID_LEN + 1];
-            ctl_error(ctx, "%s: port already exists but in router %s",
-                      lrp_name, lr_get_name(bound_lr, uuid_s, sizeof uuid_s));
-            return;
-        }
-
-        if (strcmp(mac, lrp->mac)) {
-            ctl_error(ctx, "%s: port already exists with mac %s", lrp_name,
-                      lrp->mac);
-            return;
-        }
-
-        struct sset new_networks = SSET_INITIALIZER(&new_networks);
-        for (int i = 0; i < n_networks; i++) {
-            sset_add(&new_networks, networks[i]);
-        }
-
-        struct sset orig_networks = SSET_INITIALIZER(&orig_networks);
-        sset_add_array(&orig_networks, lrp->networks, lrp->n_networks);
-
-        bool same_networks = sset_equals(&orig_networks, &new_networks);
-        sset_destroy(&orig_networks);
-        sset_destroy(&new_networks);
-        if (!same_networks) {
-            ctl_error(ctx, "%s: port already exists with different network",
-                      lrp_name);
-            return;
-        }
-
-        /* Special-case sanity-check of peer ports. */
-        const char *peer = NULL;
-        for (int i = 0; i < n_settings; i++) {
-            if (!strncmp(settings[i], "peer=", 5)) {
-                peer = settings[i] + 5;
-                break;
-            }
-        }
-
-        if ((!peer != !lrp->peer) ||
-                (lrp->peer && strcmp(peer, lrp->peer))) {
-            ctl_error(ctx, "%s: port already exists with mismatching peer",
-                      lrp_name);
-            return;
-        }
-
-        return;
-    }
-
-    struct eth_addr ea;
-    if (!eth_addr_from_string(mac, &ea)) {
-        ctl_error(ctx, "%s: invalid mac address %s", lrp_name, mac);
-        return;
-    }
-
-    for (int i = 0; i < n_networks; i++) {
-        ovs_be32 ipv4;
-        unsigned int plen;
-        error = ip_parse_cidr(networks[i], &ipv4, &plen);
-        if (error) {
-            free(error);
-            struct in6_addr ipv6;
-            error = ipv6_parse_cidr(networks[i], &ipv6, &plen);
-            if (error) {
-                free(error);
-                ctl_error(ctx, "%s: invalid network address: %s", lrp_name,
-                          networks[i]);
-                return;
-            }
-        }
-    }
-
-    /* Create the logical port. */
-    lrp = nbrec_logical_router_port_insert(ctx->txn);
-    nbrec_logical_router_port_set_name(lrp, lrp_name);
-    nbrec_logical_router_port_set_mac(lrp, mac);
-    nbrec_logical_router_port_set_networks(lrp, networks, n_networks);
-
-    for (int i = 0; i < n_settings; i++) {
-        error = ctl_set_column("Logical_Router_Port", &lrp->header_,
-                               settings[i], ctx->symtab);
-        if (error) {
-            ctx->error = error;
-            return;
-        }
-    }
-
-    /* Insert the logical port into the logical router. */
-    nbrec_logical_router_verify_ports(lr);
-    struct nbrec_logical_router_port **new_ports = xmalloc(sizeof *new_ports *
-                                                        (lr->n_ports + 1));
-    nullable_memcpy(new_ports, lr->ports, sizeof *new_ports * lr->n_ports);
-    new_ports[lr->n_ports] = CONST_CAST(struct nbrec_logical_router_port *,
-                                             lrp);
-    nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1);
-    free(new_ports);
-}
-
-/* Removes logical router port 'lr->ports[idx]'. */
-static void
-remove_lrp(const struct nbrec_logical_router *lr, size_t idx)
-{
-    const struct nbrec_logical_router_port *lrp = lr->ports[idx];
-
-    /* First remove 'lrp' from the array of ports.  This is what will
-     * actually cause the logical port to be deleted when the transaction is
-     * sent to the database server (due to garbage collection). */
-    struct nbrec_logical_router_port **new_ports
-        = xmemdup(lr->ports, sizeof *new_ports * lr->n_ports);
-    new_ports[idx] = new_ports[lr->n_ports - 1];
-    nbrec_logical_router_verify_ports(lr);
-    nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports - 1);
-    free(new_ports);
-
-    /* Delete 'lrp' from the IDL.  This won't have a real effect on
-     * the database server (the IDL will suppress it in fact) but it
-     * means that it won't show up when we iterate with
-     * NBREC_LOGICAL_ROUTER_PORT_FOR_EACH later. */
-    nbrec_logical_router_port_delete(lrp);
-}
-
-static void
-nbctl_lrp_del(struct ctl_context *ctx)
-{
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    const struct nbrec_logical_router_port *lrp = NULL;
-
-    char *error = lrp_by_name_or_uuid(ctx, ctx->argv[1], must_exist, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lrp) {
-        return;
-    }
-
-    /* Find the router that contains 'lrp', then delete it. */
-    const struct nbrec_logical_router *lr;
-    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) {
-        for (size_t i = 0; i < lr->n_ports; i++) {
-            if (lr->ports[i] == lrp) {
-                remove_lrp(lr, i);
-                return;
-            }
-        }
-    }
-
-    /* Can't happen because of the database schema. */
-    ctl_error(ctx, "logical port %s is not part of any logical router",
-              ctx->argv[1]);
-}
-
-/* Print a list of logical router ports. */
-static void
-nbctl_lrp_list(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_router *lr;
-    struct smap lrps;
-    size_t i;
-
-    char *error = lr_by_name_or_uuid(ctx, id, true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    smap_init(&lrps);
-    for (i = 0; i < lr->n_ports; i++) {
-        const struct nbrec_logical_router_port *lrp = lr->ports[i];
-        smap_add_format(&lrps, lrp->name, UUID_FMT " (%s)",
-                        UUID_ARGS(&lrp->header_.uuid), lrp->name);
-    }
-    const struct smap_node **nodes = smap_sort(&lrps);
-    for (i = 0; i < smap_count(&lrps); i++) {
-        const struct smap_node *node = nodes[i];
-        ds_put_format(&ctx->output, "%s\n", node->value);
-    }
-    smap_destroy(&lrps);
-    free(nodes);
-}
-
-/* Set the logical router port admin-enabled state. */
-static void
-nbctl_lrp_set_enabled(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const char *state = ctx->argv[2];
-    const struct nbrec_logical_router_port *lrp = NULL;
-
-    char *error = lrp_by_name_or_uuid(ctx, id, true, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lrp) {
-        return;
-    }
-
-    bool enabled;
-    error = parse_enabled(state, &enabled);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    nbrec_logical_router_port_set_enabled(lrp, &enabled, 1);
-}
-
-/* Print admin-enabled state for logical router port. */
-static void
-nbctl_lrp_get_enabled(struct ctl_context *ctx)
-{
-    const char *id = ctx->argv[1];
-    const struct nbrec_logical_router_port *lrp = NULL;
-
-    char *error = lrp_by_name_or_uuid(ctx, id, true, &lrp);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-    if (!lrp) {
-        return;
-    }
-
-    ds_put_format(&ctx->output, "%s\n",
-                  !lrp->enabled ||
-                  *lrp->enabled ? "enabled" : "disabled");
-}
-
-struct ipv4_route {
-    int priority;
-    ovs_be32 addr;
-    const struct nbrec_logical_router_static_route *route;
-};
-
-static int
-ipv4_route_cmp(const void *route1_, const void *route2_)
-{
-    const struct ipv4_route *route1p = route1_;
-    const struct ipv4_route *route2p = route2_;
-
-    if (route1p->priority != route2p->priority) {
-        return route1p->priority > route2p->priority ? -1 : 1;
-    } else if (route1p->addr != route2p->addr) {
-        return ntohl(route1p->addr) < ntohl(route2p->addr) ? -1 : 1;
-    } else {
-        return 0;
-    }
-}
-
-struct ipv6_route {
-    int priority;
-    struct in6_addr addr;
-    const struct nbrec_logical_router_static_route *route;
-};
-
-static int
-ipv6_route_cmp(const void *route1_, const void *route2_)
-{
-    const struct ipv6_route *route1p = route1_;
-    const struct ipv6_route *route2p = route2_;
-
-    if (route1p->priority != route2p->priority) {
-        return route1p->priority > route2p->priority ? -1 : 1;
-    }
-    return memcmp(&route1p->addr, &route2p->addr, sizeof(route1p->addr));
-}
-
-static void
-print_route(const struct nbrec_logical_router_static_route *route, struct ds *s)
-{
-
-    char *prefix = normalize_prefix_str(route->ip_prefix);
-    char *next_hop = normalize_prefix_str(route->nexthop);
-    ds_put_format(s, "%25s %25s", prefix, next_hop);
-    free(prefix);
-    free(next_hop);
-
-    if (route->policy) {
-        ds_put_format(s, " %s", route->policy);
-    } else {
-        ds_put_format(s, " %s", "dst-ip");
-    }
-
-    if (route->output_port) {
-        ds_put_format(s, " %s", route->output_port);
-    }
-    ds_put_char(s, '\n');
-}
-
-static void
-nbctl_lr_route_list(struct ctl_context *ctx)
-{
-    const struct nbrec_logical_router *lr;
-    struct ipv4_route *ipv4_routes;
-    struct ipv6_route *ipv6_routes;
-    size_t n_ipv4_routes = 0;
-    size_t n_ipv6_routes = 0;
-
-    char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    ipv4_routes = xmalloc(sizeof *ipv4_routes * lr->n_static_routes);
-    ipv6_routes = xmalloc(sizeof *ipv6_routes * lr->n_static_routes);
-
-    for (int i = 0; i < lr->n_static_routes; i++) {
-        const struct nbrec_logical_router_static_route *route
-            = lr->static_routes[i];
-        unsigned int plen;
-        ovs_be32 ipv4;
-        const char *policy = route->policy ? route->policy : "dst-ip";
-        error = ip_parse_cidr(route->ip_prefix, &ipv4, &plen);
-        if (!error) {
-            ipv4_routes[n_ipv4_routes].priority = !strcmp(policy, "dst-ip")
-                                                    ? (2 * plen) + 1
-                                                    : 2 * plen;
-            ipv4_routes[n_ipv4_routes].addr = ipv4;
-            ipv4_routes[n_ipv4_routes].route = route;
-            n_ipv4_routes++;
-        } else {
-            free(error);
-
-            struct in6_addr ipv6;
-            error = ipv6_parse_cidr(route->ip_prefix, &ipv6, &plen);
-            if (!error) {
-                ipv6_routes[n_ipv6_routes].priority = !strcmp(policy, "dst-ip")
-                                                        ? (2 * plen) + 1
-                                                        : 2 * plen;
-                ipv6_routes[n_ipv6_routes].addr = ipv6;
-                ipv6_routes[n_ipv6_routes].route = route;
-                n_ipv6_routes++;
-            } else {
-                /* Invalid prefix. */
-                VLOG_WARN("router "UUID_FMT" (%s) has invalid prefix: %s",
-                          UUID_ARGS(&lr->header_.uuid), lr->name,
-                          route->ip_prefix);
-                free(error);
-                continue;
-            }
-        }
-    }
-
-    qsort(ipv4_routes, n_ipv4_routes, sizeof *ipv4_routes, ipv4_route_cmp);
-    qsort(ipv6_routes, n_ipv6_routes, sizeof *ipv6_routes, ipv6_route_cmp);
-
-    if (n_ipv4_routes) {
-        ds_put_cstr(&ctx->output, "IPv4 Routes\n");
-    }
-    for (int i = 0; i < n_ipv4_routes; i++) {
-        print_route(ipv4_routes[i].route, &ctx->output);
-    }
-
-    if (n_ipv6_routes) {
-        ds_put_format(&ctx->output, "%sIPv6 Routes\n",
-                      n_ipv4_routes ?  "\n" : "");
-    }
-    for (int i = 0; i < n_ipv6_routes; i++) {
-        print_route(ipv6_routes[i].route, &ctx->output);
-    }
-
-    free(ipv4_routes);
-    free(ipv6_routes);
-}
-
-static void
-verify_connections(struct ctl_context *ctx)
-{
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    const struct nbrec_connection *conn;
-
-    nbrec_nb_global_verify_connections(nb_global);
-
-    NBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
-        nbrec_connection_verify_target(conn);
-    }
-}
-
-static void
-pre_connection(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &nbrec_nb_global_col_connections);
-    ovsdb_idl_add_column(ctx->idl, &nbrec_connection_col_target);
-    ovsdb_idl_add_column(ctx->idl, &nbrec_connection_col_inactivity_probe);
-}
-
-static void
-cmd_get_connection(struct ctl_context *ctx)
-{
-    const struct nbrec_connection *conn;
-    struct svec targets;
-    size_t i;
-
-    verify_connections(ctx);
-
-    /* Print the targets in sorted order for reproducibility. */
-    svec_init(&targets);
-
-    NBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
-        svec_add(&targets, conn->target);
-    }
-
-    svec_sort_unique(&targets);
-    for (i = 0; i < targets.n; i++) {
-        ds_put_format(&ctx->output, "%s\n", targets.names[i]);
-    }
-    svec_destroy(&targets);
-}
-
-static void
-delete_connections(struct ctl_context *ctx)
-{
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    const struct nbrec_connection *conn, *next;
-
-    /* Delete Manager rows pointed to by 'connection_options' column. */
-    NBREC_CONNECTION_FOR_EACH_SAFE(conn, next, ctx->idl) {
-        nbrec_connection_delete(conn);
-    }
-
-    /* Delete 'Manager' row refs in 'manager_options' column. */
-    nbrec_nb_global_set_connections(nb_global, NULL, 0);
-}
-
-static void
-cmd_del_connection(struct ctl_context *ctx)
-{
-    verify_connections(ctx);
-    delete_connections(ctx);
-}
-
-static void
-insert_connections(struct ctl_context *ctx, char *targets[], size_t n)
-{
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    struct nbrec_connection **connections;
-    size_t i, conns=0;
-    const char *inactivity_probe = shash_find_data(&ctx->options,
-                                                   "--inactivity-probe");
-
-    /* Insert each connection in a new row in Connection table. */
-    connections = xmalloc(n * sizeof *connections);
-    for (i = 0; i < n; i++) {
-        if (stream_verify_name(targets[i]) &&
-                   pstream_verify_name(targets[i])) {
-            VLOG_WARN("target type \"%s\" is possibly erroneous", targets[i]);
-        }
-
-        connections[conns] = nbrec_connection_insert(ctx->txn);
-        nbrec_connection_set_target(connections[conns], targets[i]);
-        if (inactivity_probe) {
-            int64_t msecs = atoll(inactivity_probe);
-            nbrec_connection_set_inactivity_probe(connections[conns],
-                                                  &msecs, 1);
-        }
-        conns++;
-    }
-
-    /* Store uuids of new connection rows in 'connection' column. */
-    nbrec_nb_global_set_connections(nb_global, connections, conns);
-    free(connections);
-}
-
-static void
-cmd_set_connection(struct ctl_context *ctx)
-{
-    const size_t n = ctx->argc - 1;
-
-    verify_connections(ctx);
-    delete_connections(ctx);
-    insert_connections(ctx, &ctx->argv[1], n);
-}
-
-static void
-pre_cmd_get_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &nbrec_nb_global_col_ssl);
-
-    ovsdb_idl_add_column(ctx->idl, &nbrec_ssl_col_private_key);
-    ovsdb_idl_add_column(ctx->idl, &nbrec_ssl_col_certificate);
-    ovsdb_idl_add_column(ctx->idl, &nbrec_ssl_col_ca_cert);
-    ovsdb_idl_add_column(ctx->idl, &nbrec_ssl_col_bootstrap_ca_cert);
-}
-
-static void
-cmd_get_ssl(struct ctl_context *ctx)
-{
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    const struct nbrec_ssl *ssl = nbrec_ssl_first(ctx->idl);
-
-    nbrec_nb_global_verify_ssl(nb_global);
-    if (ssl) {
-        nbrec_ssl_verify_private_key(ssl);
-        nbrec_ssl_verify_certificate(ssl);
-        nbrec_ssl_verify_ca_cert(ssl);
-        nbrec_ssl_verify_bootstrap_ca_cert(ssl);
-
-        ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
-        ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
-        ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
-        ds_put_format(&ctx->output, "Bootstrap: %s\n",
-                ssl->bootstrap_ca_cert ? "true" : "false");
-    }
-}
-
-static void
-pre_cmd_del_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &nbrec_nb_global_col_ssl);
-}
-
-static void
-cmd_del_ssl(struct ctl_context *ctx)
-{
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    const struct nbrec_ssl *ssl = nbrec_ssl_first(ctx->idl);
-
-    if (ssl) {
-        nbrec_nb_global_verify_ssl(nb_global);
-        nbrec_ssl_delete(ssl);
-        nbrec_nb_global_set_ssl(nb_global, NULL);
-    }
-}
-
-static void
-pre_cmd_set_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &nbrec_nb_global_col_ssl);
-}
-
-static void
-cmd_set_ssl(struct ctl_context *ctx)
-{
-    bool bootstrap = shash_find(&ctx->options, "--bootstrap");
-    const struct nbrec_nb_global *nb_global = nbrec_nb_global_first(ctx->idl);
-    const struct nbrec_ssl *ssl = nbrec_ssl_first(ctx->idl);
-
-    nbrec_nb_global_verify_ssl(nb_global);
-    if (ssl) {
-        nbrec_ssl_delete(ssl);
-    }
-    ssl = nbrec_ssl_insert(ctx->txn);
-
-    nbrec_ssl_set_private_key(ssl, ctx->argv[1]);
-    nbrec_ssl_set_certificate(ssl, ctx->argv[2]);
-    nbrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
-
-    nbrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
-
-    if (ctx->argc == 5) {
-        nbrec_ssl_set_ssl_protocols(ssl, ctx->argv[4]);
-    } else if (ctx->argc == 6) {
-        nbrec_ssl_set_ssl_protocols(ssl, ctx->argv[4]);
-        nbrec_ssl_set_ssl_ciphers(ssl, ctx->argv[5]);
-    }
-
-    nbrec_nb_global_set_ssl(nb_global, ssl);
-}
-
-static char *
-set_ports_on_pg(struct ctl_context *ctx, const struct nbrec_port_group *pg,
-                char **new_ports, size_t num_new_ports)
-{
-    struct nbrec_logical_switch_port **lports;
-    lports = xmalloc(sizeof *lports * num_new_ports);
-
-    size_t i;
-    char *error = NULL;
-    for (i = 0; i < num_new_ports; i++) {
-        const struct nbrec_logical_switch_port *lsp;
-        error = lsp_by_name_or_uuid(ctx, new_ports[i], true, &lsp);
-        if (error) {
-            goto out;
-        }
-        lports[i] = (struct nbrec_logical_switch_port *) lsp;
-    }
-
-    nbrec_port_group_set_ports(pg, lports, num_new_ports);
-
-out:
-    free(lports);
-    return error;
-}
-
-static void
-cmd_pg_add(struct ctl_context *ctx)
-{
-    const struct nbrec_port_group *pg;
-
-    pg = nbrec_port_group_insert(ctx->txn);
-    nbrec_port_group_set_name(pg, ctx->argv[1]);
-    if (ctx->argc > 2) {
-        ctx->error = set_ports_on_pg(ctx, pg, ctx->argv + 2, ctx->argc - 2);
-    }
-}
-
-static void
-cmd_pg_set_ports(struct ctl_context *ctx)
-{
-    const struct nbrec_port_group *pg;
-
-    char *error;
-    error = pg_by_name_or_uuid(ctx, ctx->argv[1], true, &pg);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    ctx->error = set_ports_on_pg(ctx, pg, ctx->argv + 2, ctx->argc - 2);
-}
-
-static void
-cmd_pg_del(struct ctl_context *ctx)
-{
-    const struct nbrec_port_group *pg;
-
-    char *error;
-    error = pg_by_name_or_uuid(ctx, ctx->argv[1], true, &pg);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    nbrec_port_group_delete(pg);
-}
-
-static const struct nbrec_ha_chassis_group*
-ha_chassis_group_by_name_or_uuid(struct ctl_context *ctx, const char *id,
-                                 bool must_exist)
-{
-    struct uuid ch_grp_uuid;
-    const struct nbrec_ha_chassis_group *ha_ch_grp = NULL;
-    bool is_uuid = uuid_from_string(&ch_grp_uuid, id);
-    if (is_uuid) {
-        ha_ch_grp = nbrec_ha_chassis_group_get_for_uuid(ctx->idl,
-                                                        &ch_grp_uuid);
-    }
-
-    if (!ha_ch_grp) {
-        const struct nbrec_ha_chassis_group *iter;
-        NBREC_HA_CHASSIS_GROUP_FOR_EACH (iter, ctx->idl) {
-            if (!strcmp(iter->name, id)) {
-                ha_ch_grp = iter;
-                break;
-            }
-        }
-    }
-
-    if (!ha_ch_grp && must_exist) {
-        ctx->error = xasprintf("%s: ha_chassi_group %s not found",
-                               id, is_uuid ? "UUID" : "name");
-    }
-
-    return ha_ch_grp;
-}
-
-static void
-cmd_ha_ch_grp_add(struct ctl_context *ctx)
-{
-    const char *name = ctx->argv[1];
-    struct nbrec_ha_chassis_group *ha_ch_grp =
-        nbrec_ha_chassis_group_insert(ctx->txn);
-    nbrec_ha_chassis_group_set_name(ha_ch_grp, name);
-}
-
-static void
-cmd_ha_ch_grp_del(struct ctl_context *ctx)
-{
-    const char *name_or_id = ctx->argv[1];
-
-    const struct nbrec_ha_chassis_group *ha_ch_grp =
-        ha_chassis_group_by_name_or_uuid(ctx, name_or_id, true);
-
-    if (ha_ch_grp) {
-        nbrec_ha_chassis_group_delete(ha_ch_grp);
-    }
-}
-
-static void
-cmd_ha_ch_grp_list(struct ctl_context *ctx)
-{
-    const struct nbrec_ha_chassis_group *ha_ch_grp;
-
-    NBREC_HA_CHASSIS_GROUP_FOR_EACH (ha_ch_grp, ctx->idl) {
-        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
-                      UUID_ARGS(&ha_ch_grp->header_.uuid), ha_ch_grp->name);
-        const struct nbrec_ha_chassis *ha_ch;
-        for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
-            ha_ch = ha_ch_grp->ha_chassis[i];
-            ds_put_format(&ctx->output,
-                          "    "UUID_FMT " (%s)\n"
-                          "    priority %"PRId64"\n\n",
-                          UUID_ARGS(&ha_ch->header_.uuid), ha_ch->chassis_name,
-                          ha_ch->priority);
-        }
-        ds_put_cstr(&ctx->output, "\n");
-    }
-}
-
-static void
-cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx)
-{
-    const struct nbrec_ha_chassis_group *ha_ch_grp =
-        ha_chassis_group_by_name_or_uuid(ctx, ctx->argv[1], true);
-
-    if (!ha_ch_grp) {
-        return;
-    }
-
-    const char *chassis_name = ctx->argv[2];
-    int64_t priority;
-    char *error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    struct nbrec_ha_chassis *ha_chassis = NULL;
-    for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
-        if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) {
-            ha_chassis = ha_ch_grp->ha_chassis[i];
-            break;
-        }
-    }
-
-    if (ha_chassis) {
-        nbrec_ha_chassis_set_priority(ha_chassis, priority);
-        return;
-    }
-
-    ha_chassis = nbrec_ha_chassis_insert(ctx->txn);
-    nbrec_ha_chassis_set_chassis_name(ha_chassis, chassis_name);
-    nbrec_ha_chassis_set_priority(ha_chassis, priority);
-
-    nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp);
-
-    struct nbrec_ha_chassis **new_ha_chs =
-        xmalloc(sizeof *new_ha_chs * (ha_ch_grp->n_ha_chassis + 1));
-    nullable_memcpy(new_ha_chs, ha_ch_grp->ha_chassis,
-                    sizeof *new_ha_chs * ha_ch_grp->n_ha_chassis);
-    new_ha_chs[ha_ch_grp->n_ha_chassis] =
-        CONST_CAST(struct nbrec_ha_chassis *, ha_chassis);
-    nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_chs,
-                                          ha_ch_grp->n_ha_chassis + 1);
-    free(new_ha_chs);
-}
-
-static void
-cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx)
-{
-    const struct nbrec_ha_chassis_group *ha_ch_grp =
-        ha_chassis_group_by_name_or_uuid(ctx, ctx->argv[1], true);
-
-    if (!ha_ch_grp) {
-        return;
-    }
-
-    const char *chassis_name = ctx->argv[2];
-    struct nbrec_ha_chassis *ha_chassis = NULL;
-    size_t idx = 0;
-    for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
-        if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) {
-            ha_chassis = ha_ch_grp->ha_chassis[i];
-            idx = i;
-            break;
-        }
-    }
-
-    if (!ha_chassis) {
-        ctx->error = xasprintf("%s: ha chassis not found in %s ha "
-                               "chassis group", chassis_name, ctx->argv[1]);
-        return;
-    }
-
-    struct nbrec_ha_chassis **new_ha_ch
-        = xmemdup(ha_ch_grp->ha_chassis,
-                  sizeof *new_ha_ch * ha_ch_grp->n_ha_chassis);
-    new_ha_ch[idx] = new_ha_ch[ha_ch_grp->n_ha_chassis - 1];
-    nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp);
-    nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_ch,
-                                          ha_ch_grp->n_ha_chassis - 1);
-    free(new_ha_ch);
-    nbrec_ha_chassis_delete(ha_chassis);
-}
-
-static void
-cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
-{
-    const struct nbrec_ha_chassis_group *ha_ch_grp =
-        ha_chassis_group_by_name_or_uuid(ctx, ctx->argv[1], true);
-
-    if (!ha_ch_grp) {
-        return;
-    }
-
-    int64_t priority;
-    char *error = parse_priority(ctx->argv[3], &priority);
-    if (error) {
-        ctx->error = error;
-        return;
-    }
-
-    const char *chassis_name = ctx->argv[2];
-    struct nbrec_ha_chassis *ha_chassis = NULL;
-
-    for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
-        if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) {
-            ha_chassis = ha_ch_grp->ha_chassis[i];
-            break;
-        }
-    }
-
-    if (!ha_chassis) {
-        ctx->error = xasprintf("%s: ha chassis not found in %s ha "
-                               "chassis group", chassis_name, ctx->argv[1]);
-        return;
-    }
-
-    nbrec_ha_chassis_set_priority(ha_chassis, priority);
-}
-
-static const struct ctl_table_class tables[NBREC_N_TABLES] = {
-    [NBREC_TABLE_DHCP_OPTIONS].row_ids
-    = {{&nbrec_logical_switch_port_col_name, NULL,
-        &nbrec_logical_switch_port_col_dhcpv4_options},
-       {&nbrec_logical_switch_port_col_external_ids,
-        "neutron:port_name", &nbrec_logical_switch_port_col_dhcpv4_options},
-       {&nbrec_logical_switch_port_col_name, NULL,
-        &nbrec_logical_switch_port_col_dhcpv6_options},
-       {&nbrec_logical_switch_port_col_external_ids,
-        "neutron:port_name", &nbrec_logical_switch_port_col_dhcpv6_options}},
-
-    [NBREC_TABLE_LOGICAL_SWITCH].row_ids
-    = {{&nbrec_logical_switch_col_name, NULL, NULL},
-       {&nbrec_logical_switch_col_external_ids, "neutron:network_name", NULL}},
-
-    [NBREC_TABLE_LOGICAL_SWITCH_PORT].row_ids
-    = {{&nbrec_logical_switch_port_col_name, NULL, NULL},
-       {&nbrec_logical_switch_port_col_external_ids,
-        "neutron:port_name", NULL}},
-
-    [NBREC_TABLE_LOGICAL_ROUTER].row_ids
-    = {{&nbrec_logical_router_col_name, NULL, NULL},
-       {&nbrec_logical_router_col_external_ids, "neutron:router_name", NULL}},
-
-    [NBREC_TABLE_LOGICAL_ROUTER_PORT].row_ids[0]
-    = {&nbrec_logical_router_port_col_name, NULL, NULL},
-
-    [NBREC_TABLE_ADDRESS_SET].row_ids[0]
-    = {&nbrec_address_set_col_name, NULL, NULL},
-
-    [NBREC_TABLE_PORT_GROUP].row_ids[0]
-    = {&nbrec_port_group_col_name, NULL, NULL},
-
-    [NBREC_TABLE_ACL].row_ids[0] = {&nbrec_acl_col_name, NULL, NULL},
-
-    [NBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
-    = {&nbrec_ha_chassis_group_col_name, NULL, NULL},
-};
-
-static char *
-run_prerequisites(struct ctl_command *commands, size_t n_commands,
-                  struct ovsdb_idl *idl)
-{
-    ovsdb_idl_add_table(idl, &nbrec_table_nb_global);
-    if (wait_type == NBCTL_WAIT_SB) {
-        ovsdb_idl_add_column(idl, &nbrec_nb_global_col_sb_cfg);
-    } else if (wait_type == NBCTL_WAIT_HV) {
-        ovsdb_idl_add_column(idl, &nbrec_nb_global_col_hv_cfg);
-    }
-
-    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
-        if (c->syntax->prerequisites) {
-            struct ctl_context ctx;
-
-            ds_init(&c->output);
-            c->table = NULL;
-
-            ctl_context_init(&ctx, c, idl, NULL, NULL, NULL);
-            (c->syntax->prerequisites)(&ctx);
-            if (ctx.error) {
-                char *error = xstrdup(ctx.error);
-                ctl_context_done(&ctx, c);
-                return error;
-            }
-            ctl_context_done(&ctx, c);
-
-            ovs_assert(!c->output.string);
-            ovs_assert(!c->table);
-        }
-    }
-
-    return NULL;
-}
-
-static void
-oneline_format(struct ds *lines, struct ds *s)
-{
-    size_t j;
-
-    ds_chomp(lines, '\n');
-    for (j = 0; j < lines->length; j++) {
-        int ch = lines->string[j];
-        switch (ch) {
-        case '\n':
-            ds_put_cstr(s, "\\n");
-            break;
-
-        case '\\':
-            ds_put_cstr(s, "\\\\");
-            break;
-
-        default:
-            ds_put_char(s, ch);
-        }
-    }
-    ds_put_char(s, '\n');
-}
-
-static void
-oneline_print(struct ds *lines)
-{
-    struct ds s = DS_EMPTY_INITIALIZER;
-    oneline_format(lines, &s);
-    fputs(ds_cstr(&s), stdout);
-    ds_destroy(&s);
-}
-
-static char *
-do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
-         struct ovsdb_idl *idl, const struct timer *wait_timeout, bool *retry)
-{
-    struct ovsdb_idl_txn *txn;
-    enum ovsdb_idl_txn_status status;
-    struct ovsdb_symbol_table *symtab;
-    struct ctl_context ctx;
-    struct ctl_command *c;
-    struct shash_node *node;
-    int64_t next_cfg = 0;
-    char *error = NULL;
-
-    ovs_assert(retry);
-
-    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
-    if (dry_run) {
-        ovsdb_idl_txn_set_dry_run(txn);
-    }
-
-    ovsdb_idl_txn_add_comment(txn, "ovs-nbctl: %s", args);
-
-    const struct nbrec_nb_global *nb = nbrec_nb_global_first(idl);
-    if (!nb) {
-        /* XXX add verification that table is empty */
-        nb = nbrec_nb_global_insert(txn);
-    }
-
-    if (wait_type != NBCTL_WAIT_NONE) {
-        ovsdb_idl_txn_increment(txn, &nb->header_, &nbrec_nb_global_col_nb_cfg,
-                                force_wait);
-    }
-
-    symtab = ovsdb_symbol_table_create();
-    for (c = commands; c < &commands[n_commands]; c++) {
-        ds_init(&c->output);
-        c->table = NULL;
-    }
-    ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL);
-    for (c = commands; c < &commands[n_commands]; c++) {
-        ctl_context_init_command(&ctx, c);
-        if (c->syntax->run) {
-            (c->syntax->run)(&ctx);
-        }
-        if (ctx.error) {
-            error = xstrdup(ctx.error);
-            ctl_context_done(&ctx, c);
-            goto out_error;
-        }
-        ctl_context_done_command(&ctx, c);
-
-        if (ctx.try_again) {
-            ctl_context_done(&ctx, NULL);
-            goto try_again;
-        }
-    }
-    ctl_context_done(&ctx, NULL);
-
-    SHASH_FOR_EACH (node, &symtab->sh) {
-        struct ovsdb_symbol *symbol = node->data;
-        if (!symbol->created) {
-            error = xasprintf("row id \"%s\" is referenced but never created "
-                              "(e.g. with \"-- --id=%s create ...\")",
-                              node->name, node->name);
-            goto out_error;
-        }
-        if (!symbol->strong_ref) {
-            if (!symbol->weak_ref) {
-                VLOG_WARN("row id \"%s\" was created but no reference to it "
-                          "was inserted, so it will not actually appear in "
-                          "the database", node->name);
-            } else {
-                VLOG_WARN("row id \"%s\" was created but only a weak "
-                          "reference to it was inserted, so it will not "
-                          "actually appear in the database", node->name);
-            }
-        }
-    }
-
-    status = ovsdb_idl_txn_commit_block(txn);
-    if (wait_type != NBCTL_WAIT_NONE && status == TXN_SUCCESS) {
-        next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
-    }
-    if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
-        for (c = commands; c < &commands[n_commands]; c++) {
-            if (c->syntax->postprocess) {
-                ctl_context_init(&ctx, c, idl, txn, symtab, NULL);
-                (c->syntax->postprocess)(&ctx);
-                if (ctx.error) {
-                    error = xstrdup(ctx.error);
-                    ctl_context_done(&ctx, c);
-                    goto out_error;
-                }
-                ctl_context_done(&ctx, c);
-            }
-        }
-    }
-
-    switch (status) {
-    case TXN_UNCOMMITTED:
-    case TXN_INCOMPLETE:
-        OVS_NOT_REACHED();
-
-    case TXN_ABORTED:
-        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
-        error = xstrdup("transaction aborted");
-        goto out_error;
-
-    case TXN_UNCHANGED:
-    case TXN_SUCCESS:
-        break;
-
-    case TXN_TRY_AGAIN:
-        goto try_again;
-
-    case TXN_ERROR:
-        error = xasprintf("transaction error: %s",
-                          ovsdb_idl_txn_get_error(txn));
-        goto out_error;
-
-    case TXN_NOT_LOCKED:
-        /* Should not happen--we never call ovsdb_idl_set_lock(). */
-        error = xstrdup("database not locked");
-        goto out_error;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-
-    for (c = commands; c < &commands[n_commands]; c++) {
-        struct ds *ds = &c->output;
-
-        if (c->table) {
-            table_print(c->table, &table_style);
-        } else if (oneline) {
-            oneline_print(ds);
-        } else {
-            fputs(ds_cstr(ds), stdout);
-        }
-    }
-
-    if (wait_type != NBCTL_WAIT_NONE && status != TXN_UNCHANGED) {
-        ovsdb_idl_enable_reconnect(idl);
-        for (;;) {
-            ovsdb_idl_run(idl);
-            NBREC_NB_GLOBAL_FOR_EACH (nb, idl) {
-                int64_t cur_cfg = (wait_type == NBCTL_WAIT_SB
-                                   ? nb->sb_cfg
-                                   : nb->hv_cfg);
-                if (cur_cfg >= next_cfg) {
-                    goto done;
-                }
-            }
-            ovsdb_idl_wait(idl);
-            if (wait_timeout) {
-                timer_wait(wait_timeout);
-            }
-            poll_block();
-            if (wait_timeout && timer_expired(wait_timeout)) {
-                error = xstrdup("timeout expired");
-                goto out_error;
-            }
-        }
-    done: ;
-    }
-
-    ovsdb_symbol_table_destroy(symtab);
-    ovsdb_idl_txn_destroy(txn);
-    the_idl_txn = NULL;
-
-    *retry = false;
-    return NULL;
-
-try_again:
-    /* Our transaction needs to be rerun, or a prerequisite was not met.  Free
-     * resources and return so that the caller can try again. */
-    *retry = true;
-
-out_error:
-    ovsdb_idl_txn_abort(txn);
-    ovsdb_idl_txn_destroy(txn);
-    the_idl_txn = NULL;
-
-    ovsdb_symbol_table_destroy(symtab);
-    for (c = commands; c < &commands[n_commands]; c++) {
-        ds_destroy(&c->output);
-        table_destroy(c->table);
-        free(c->table);
-    }
-
-    return error;
-}
-
-/* Frees the current transaction and the underlying IDL and then calls
- * exit(status).
- *
- * Freeing the transaction and the IDL is not strictly necessary, but it makes
- * for a clean memory leak report from valgrind in the normal case.  That makes
- * it easier to notice real memory leaks. */
-static void
-nbctl_exit(int status)
-{
-    if (the_idl_txn) {
-        ovsdb_idl_txn_abort(the_idl_txn);
-        ovsdb_idl_txn_destroy(the_idl_txn);
-    }
-    ovsdb_idl_destroy(the_idl);
-    exit(status);
-}
-
-static const struct ctl_command_syntax nbctl_commands[] = {
-    { "init", 0, 0, "", NULL, nbctl_init, NULL, "", RW },
-    { "sync", 0, 0, "", nbctl_pre_sync, nbctl_sync, NULL, "", RO },
-    { "show", 0, 1, "[SWITCH]", NULL, nbctl_show, NULL, "", RO },
-
-    /* logical switch commands. */
-    { "ls-add", 0, 1, "[SWITCH]", NULL, nbctl_ls_add, NULL,
-      "--may-exist,--add-duplicate", RW },
-    { "ls-del", 1, 1, "SWITCH", NULL, nbctl_ls_del, NULL, "--if-exists", RW },
-    { "ls-list", 0, 0, "", NULL, nbctl_ls_list, NULL, "", RO },
-
-    /* acl commands. */
-    { "acl-add", 5, 6, "{SWITCH | PORTGROUP} DIRECTION PRIORITY MATCH ACTION",
-      NULL, nbctl_acl_add, NULL,
-      "--log,--may-exist,--type=,--name=,--severity=,--meter=", RW },
-    { "acl-del", 1, 4, "{SWITCH | PORTGROUP} [DIRECTION [PRIORITY MATCH]]",
-      NULL, nbctl_acl_del, NULL, "--type=", RW },
-    { "acl-list", 1, 1, "{SWITCH | PORTGROUP}",
-      NULL, nbctl_acl_list, NULL, "--type=", RO },
-
-    /* qos commands. */
-    { "qos-add", 5, 7,
-      "SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]",
-      NULL, nbctl_qos_add, NULL, "--may-exist", RW },
-    { "qos-del", 1, 4, "SWITCH [DIRECTION [PRIORITY MATCH]]", NULL,
-      nbctl_qos_del, NULL, "", RW },
-    { "qos-list", 1, 1, "SWITCH", NULL, nbctl_qos_list, NULL, "", RO },
-
-    /* meter commands. */
-    { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", NULL,
-      nbctl_meter_add, NULL, "", RW },
-    { "meter-del", 0, 1, "[NAME]", NULL, nbctl_meter_del, NULL, "", RW },
-    { "meter-list", 0, 0, "", NULL, nbctl_meter_list, NULL, "", RO },
-
-    /* logical switch port commands. */
-    { "lsp-add", 2, 4, "SWITCH PORT [PARENT] [TAG]", NULL, nbctl_lsp_add,
-      NULL, "--may-exist", RW },
-    { "lsp-del", 1, 1, "PORT", NULL, nbctl_lsp_del, NULL, "--if-exists", RW },
-    { "lsp-list", 1, 1, "SWITCH", NULL, nbctl_lsp_list, NULL, "", RO },
-    { "lsp-get-parent", 1, 1, "PORT", NULL, nbctl_lsp_get_parent, NULL,
-      "", RO },
-    { "lsp-get-tag", 1, 1, "PORT", NULL, nbctl_lsp_get_tag, NULL, "", RO },
-    { "lsp-set-addresses", 1, INT_MAX, "PORT [ADDRESS]...", NULL,
-      nbctl_lsp_set_addresses, NULL, "", RW },
-    { "lsp-get-addresses", 1, 1, "PORT", NULL, nbctl_lsp_get_addresses, NULL,
-      "", RO },
-    { "lsp-set-port-security", 0, INT_MAX, "PORT [ADDRS]...", NULL,
-      nbctl_lsp_set_port_security, NULL, "", RW },
-    { "lsp-get-port-security", 1, 1, "PORT", NULL,
-      nbctl_lsp_get_port_security, NULL, "", RO },
-    { "lsp-get-up", 1, 1, "PORT", NULL, nbctl_lsp_get_up, NULL, "", RO },
-    { "lsp-set-enabled", 2, 2, "PORT STATE", NULL, nbctl_lsp_set_enabled,
-      NULL, "", RW },
-    { "lsp-get-enabled", 1, 1, "PORT", NULL, nbctl_lsp_get_enabled, NULL,
-      "", RO },
-    { "lsp-set-type", 2, 2, "PORT TYPE", NULL, nbctl_lsp_set_type, NULL,
-      "", RW },
-    { "lsp-get-type", 1, 1, "PORT", NULL, nbctl_lsp_get_type, NULL, "", RO },
-    { "lsp-set-options", 1, INT_MAX, "PORT KEY=VALUE [KEY=VALUE]...", NULL,
-      nbctl_lsp_set_options, NULL, "", RW },
-    { "lsp-get-options", 1, 1, "PORT", NULL, nbctl_lsp_get_options, NULL,
-      "", RO },
-    { "lsp-set-dhcpv4-options", 1, 2, "PORT [DHCP_OPT_UUID]", NULL,
-      nbctl_lsp_set_dhcpv4_options, NULL, "", RW },
-    { "lsp-get-dhcpv4-options", 1, 1, "PORT", NULL,
-      nbctl_lsp_get_dhcpv4_options, NULL, "", RO },
-    { "lsp-set-dhcpv6-options", 1, 2, "PORT [DHCP_OPT_UUID]", NULL,
-      nbctl_lsp_set_dhcpv6_options, NULL, "", RW },
-    { "lsp-get-dhcpv6-options", 1, 1, "PORT", NULL,
-      nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
-    { "lsp-get-ls", 1, 1, "PORT", NULL, nbctl_lsp_get_ls, NULL, "", RO },
-
-    /* logical router commands. */
-    { "lr-add", 0, 1, "[ROUTER]", NULL, nbctl_lr_add, NULL,
-      "--may-exist,--add-duplicate", RW },
-    { "lr-del", 1, 1, "ROUTER", NULL, nbctl_lr_del, NULL, "--if-exists", RW },
-    { "lr-list", 0, 0, "", NULL, nbctl_lr_list, NULL, "", RO },
-
-    /* logical router port commands. */
-    { "lrp-add", 4, INT_MAX,
-      "ROUTER PORT MAC NETWORK... [COLUMN[:KEY]=VALUE]...",
-      NULL, nbctl_lrp_add, NULL, "--may-exist", RW },
-    { "lrp-set-gateway-chassis", 2, 3,
-      "PORT CHASSIS [PRIORITY]",
-      NULL, nbctl_lrp_set_gateway_chassis, NULL, "--may-exist", RW },
-    { "lrp-del-gateway-chassis", 2, 2, "PORT CHASSIS", NULL,
-      nbctl_lrp_del_gateway_chassis, NULL, "", RW },
-    { "lrp-get-gateway-chassis", 1, 1, "PORT", NULL,
-      nbctl_lrp_get_gateway_chassis, NULL, "", RO },
-    { "lrp-del", 1, 1, "PORT", NULL, nbctl_lrp_del, NULL, "--if-exists", RW },
-    { "lrp-list", 1, 1, "ROUTER", NULL, nbctl_lrp_list, NULL, "", RO },
-    { "lrp-set-enabled", 2, 2, "PORT STATE", NULL, nbctl_lrp_set_enabled,
-      NULL, "", RW },
-    { "lrp-get-enabled", 1, 1, "PORT", NULL, nbctl_lrp_get_enabled,
-      NULL, "", RO },
-
-    /* logical router route commands. */
-    { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL,
-      nbctl_lr_route_add, NULL, "--may-exist,--policy=", RW },
-    { "lr-route-del", 1, 2, "ROUTER [PREFIX]", NULL, nbctl_lr_route_del,
-      NULL, "--if-exists", RW },
-    { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL,
-      "", RO },
-
-    /* Policy commands */
-    { "lr-policy-add", 4, 5, "ROUTER PRIORITY MATCH ACTION [NEXTHOP]", NULL,
-        nbctl_lr_policy_add, NULL, "", RW },
-    { "lr-policy-del", 1, 3, "ROUTER [PRIORITY [MATCH]]", NULL,
-        nbctl_lr_policy_del, NULL, "", RW },
-    { "lr-policy-list", 1, 1, "ROUTER", NULL, nbctl_lr_policy_list, NULL,
-       "", RO },
-
-    /* NAT commands. */
-    { "lr-nat-add", 4, 6,
-      "ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]", NULL,
-      nbctl_lr_nat_add, NULL, "--may-exist", RW },
-    { "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]", NULL,
-        nbctl_lr_nat_del, NULL, "--if-exists", RW },
-    { "lr-nat-list", 1, 1, "ROUTER", NULL, nbctl_lr_nat_list, NULL, "", RO },
-
-    /* load balancer commands. */
-    { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL,
-      nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW },
-    { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL,
-        "--if-exists", RW },
-    { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO },
-    { "lr-lb-add", 2, 2, "ROUTER LB", NULL, nbctl_lr_lb_add, NULL,
-        "--may-exist", RW },
-    { "lr-lb-del", 1, 2, "ROUTER [LB]", NULL, nbctl_lr_lb_del, NULL,
-        "--if-exists", RW },
-    { "lr-lb-list", 1, 1, "ROUTER", NULL, nbctl_lr_lb_list, NULL,
-        "", RO },
-    { "ls-lb-add", 2, 2, "SWITCH LB", NULL, nbctl_ls_lb_add, NULL,
-        "--may-exist", RW },
-    { "ls-lb-del", 1, 2, "SWITCH [LB]", NULL, nbctl_ls_lb_del, NULL,
-        "--if-exists", RW },
-    { "ls-lb-list", 1, 1, "SWITCH", NULL, nbctl_ls_lb_list, NULL,
-        "", RO },
-
-    /* DHCP_Options commands */
-    {"dhcp-options-create", 1, INT_MAX, "CIDR [EXTERNAL:IDS]", NULL,
-     nbctl_dhcp_options_create, NULL, "", RW },
-    {"dhcp-options-del", 1, 1, "DHCP_OPT_UUID", NULL,
-     nbctl_dhcp_options_del, NULL, "", RW},
-    {"dhcp-options-list", 0, 0, "", NULL, nbctl_dhcp_options_list, NULL, "", RO},
-    {"dhcp-options-set-options", 1, INT_MAX, "DHCP_OPT_UUID KEY=VALUE [KEY=VALUE]...",
-    NULL, nbctl_dhcp_options_set_options, NULL, "", RW },
-    {"dhcp-options-get-options", 1, 1, "DHCP_OPT_UUID", NULL,
-     nbctl_dhcp_options_get_options, NULL, "", RO },
-
-    /* Connection commands. */
-    {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "", RO},
-    {"del-connection", 0, 0, "", pre_connection, cmd_del_connection, NULL, "", RW},
-    {"set-connection", 1, INT_MAX, "TARGET...", pre_connection, cmd_set_connection,
-     NULL, "--inactivity-probe=", RW},
-
-    /* SSL commands. */
-    {"get-ssl", 0, 0, "", pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO},
-    {"del-ssl", 0, 0, "", pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW},
-    {"set-ssl", 3, 5,
-        "PRIVATE-KEY CERTIFICATE CA-CERT [SSL-PROTOS [SSL-CIPHERS]]",
-        pre_cmd_set_ssl, cmd_set_ssl, NULL, "--bootstrap", RW},
-
-    /* Port Group Commands */
-    {"pg-add", 1, INT_MAX, "", NULL, cmd_pg_add, NULL, "", RW },
-    {"pg-set-ports", 2, INT_MAX, "", NULL, cmd_pg_set_ports, NULL, "", RW },
-    {"pg-del", 1, 1, "", NULL, cmd_pg_del, NULL, "", RW },
-
-    /* HA chassis group commands. */
-    {"ha-chassis-group-add", 1, 1, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_add, NULL, "", RW },
-    {"ha-chassis-group-del", 1, 1, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_del, NULL, "", RW },
-    {"ha-chassis-group-list", 0, 0, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_list, NULL, "", RO },
-    {"ha-chassis-group-add-chassis", 3, 3, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_add_chassis, NULL, "", RW },
-    {"ha-chassis-group-remove-chassis", 2, 2, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_remove_chassis, NULL, "", RW },
-    {"ha-chassis-group-set-chassis-prio", 3, 3, "[CHASSIS GROUP]", NULL,
-      cmd_ha_ch_grp_set_chassis_prio, NULL, "", RW },
-
-    {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
-};
-
-/* Registers nbctl and common db commands. */
-static void
-nbctl_cmd_init(void)
-{
-    ctl_init(&nbrec_idl_class, nbrec_table_classes, tables, NULL, nbctl_exit);
-    ctl_register_commands(nbctl_commands);
-}
-
-/* Server implementation. */
-
-#undef ctl_fatal
-
-static const struct option *
-find_option_by_value(const struct option *options, int value)
-{
-    const struct option *o;
-
-    for (o = options; o->name; o++) {
-        if (o->val == value) {
-            return o;
-        }
-    }
-    return NULL;
-}
-
-static char * OVS_WARN_UNUSED_RESULT
-server_parse_options(int argc, char *argv[], struct shash *local_options,
-                     int *n_options_p)
-{
-    static const struct option global_long_options[] = {
-        VLOG_LONG_OPTIONS,
-        MAIN_LOOP_LONG_OPTIONS,
-        TABLE_LONG_OPTIONS,
-        {NULL, 0, NULL, 0},
-    };
-    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
-    char *short_options;
-    struct option *options;
-    char *error = NULL;
-
-    ovs_assert(n_options_p);
-
-    short_options = build_short_options(global_long_options, false);
-    options = append_command_options(global_long_options, OPT_LOCAL);
-
-    optind = 0;
-    opterr = 0;
-    for (;;) {
-        int idx;
-        int c;
-
-        c = getopt_long(argc, argv, short_options, options, &idx);
-        if (c == -1) {
-            break;
-        }
-
-        bool handled;
-        error = handle_main_loop_option(c, optarg, &handled);
-        if (error) {
-            goto out;
-        }
-        if (handled) {
-            continue;
-        }
-
-        switch (c) {
-        case OPT_LOCAL:
-            error = add_local_option(options[idx].name, optarg, local_options);
-            if (error) {
-                goto out;
-            }
-            break;
-
-        VLOG_OPTION_HANDLERS
-        TABLE_OPTION_HANDLERS(&table_style)
-
-        case '?':
-            if (find_option_by_value(options, optopt)) {
-                error = xasprintf("option '%s' doesn't allow an argument",
-                                  argv[optind-1]);
-            } else if (optopt) {
-                error = xasprintf("unrecognized option '%c'", optopt);
-            } else {
-                error = xasprintf("unrecognized option '%s'", argv[optind-1]);
-            }
-            goto out;
-            break;
-
-        case ':':
-            error = xasprintf("option '%s' requires an argument",
-                              argv[optind-1]);
-            goto out;
-            break;
-
-        case 0:
-            break;
-
-        default:
-            error = xasprintf("unhandled option '%c'", c);
-            goto out;
-            break;
-        }
-    }
-    *n_options_p = optind;
-
-out:
-    for (int i = n_global_long_options; options[i].name; i++) {
-        free(CONST_CAST(char *, options[i].name));
-    }
-    free(options);
-    free(short_options);
-
-    return error;
-}
-
-static void
-server_cmd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                const char *argv[] OVS_UNUSED, void *exiting_)
-{
-    bool *exiting = exiting_;
-    *exiting = true;
-    unixctl_command_reply(conn, NULL);
-}
-
-static void
-server_cmd_run(struct unixctl_conn *conn, int argc, const char **argv_,
-               void *idl_)
-{
-    struct ovsdb_idl *idl = idl_;
-    struct ctl_command *commands = NULL;
-    struct shash local_options;
-    size_t n_commands = 0;
-    int n_options = 0;
-    char *error = NULL;
-
-    /* Copy args so that getopt() can permute them. Leave last entry NULL. */
-    char **argv = xcalloc(argc + 1, sizeof *argv);
-    for (int i = 0; i < argc; i++) {
-        argv[i] = xstrdup(argv_[i]);
-    }
-
-    /* Reset global state. */
-    oneline = false;
-    dry_run = false;
-    wait_type = NBCTL_WAIT_NONE;
-    force_wait = false;
-    timeout = 0;
-    table_style = table_style_default;
-
-    /* Parse commands & options. */
-    char *args = process_escape_args(argv);
-    shash_init(&local_options);
-    error = server_parse_options(argc, argv, &local_options, &n_options);
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        goto out;
-    }
-    error = ctl_parse_commands(argc - n_options, argv + n_options,
-                               &local_options, &commands, &n_commands);
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        goto out;
-    }
-    VLOG(ctl_might_write_to_db(commands, n_commands) ? VLL_INFO : VLL_DBG,
-         "Running command %s", args);
-
-    struct timer *wait_timeout = NULL;
-    struct timer wait_timeout_;
-    if (timeout) {
-        wait_timeout = &wait_timeout_;
-        timer_set_duration(wait_timeout, timeout * 1000);
-    }
-
-    error = run_prerequisites(commands, n_commands, idl);
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        goto out;
-    }
-    error = main_loop(args, commands, n_commands, idl, wait_timeout);
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        goto out;
-    }
-
-    struct ds output = DS_EMPTY_INITIALIZER;
-    table_format_reset();
-    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
-        if (c->table) {
-            table_format(c->table, &table_style, &output);
-        } else if (oneline) {
-            oneline_format(&c->output, &output);
-        } else {
-            ds_put_cstr(&output, ds_cstr_ro(&c->output));
-        }
-
-        ds_destroy(&c->output);
-        table_destroy(c->table);
-        free(c->table);
-    }
-    unixctl_command_reply(conn, ds_cstr_ro(&output));
-    ds_destroy(&output);
-
-out:
-    free(error);
-    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
-        shash_destroy_free_data(&c->options);
-    }
-    free(commands);
-    shash_destroy_free_data(&local_options);
-    free(args);
-    for (int i = 0; i < argc; i++) {
-        free(argv[i]);
-    }
-    free(argv);
-}
-
-static void
-server_cmd_init(struct ovsdb_idl *idl, bool *exiting)
-{
-    unixctl_command_register("exit", "", 0, 0, server_cmd_exit, exiting);
-    unixctl_command_register("run", "", 0, INT_MAX, server_cmd_run, idl);
-}
-
-static void
-server_loop(struct ovsdb_idl *idl, int argc, char *argv[])
-{
-    struct unixctl_server *server = NULL;
-    bool exiting = false;
-
-    service_start(&argc, &argv);
-    daemonize_start(false);
-    int error = unixctl_server_create(unixctl_path, &server);
-    if (error) {
-        ctl_fatal("failed to create unixctl server (%s)",
-                  ovs_retval_to_string(error));
-    }
-    puts(unixctl_server_get_path(server));
-    fflush(stdout);
-    server_cmd_init(idl, &exiting);
-
-    for (;;) {
-        ovsdb_idl_run(idl);
-        if (!ovsdb_idl_is_alive(idl)) {
-            int retval = ovsdb_idl_get_last_error(idl);
-            ctl_fatal("%s: database connection failed (%s)",
-                      db, ovs_retval_to_string(retval));
-        }
-
-        if (ovsdb_idl_has_ever_connected(idl)) {
-            daemonize_complete();
-            unixctl_server_run(server);
-        }
-        if (exiting) {
-            break;
-        }
-
-        ovsdb_idl_wait(idl);
-        unixctl_server_wait(server);
-        poll_block();
-    }
-
-    unixctl_server_destroy(server);
-}
-
-static void
-nbctl_client(const char *socket_name,
-             const struct ovs_cmdl_parsed_option *parsed_options, size_t n,
-             int argc, char *argv[])
-{
-    struct svec args = SVEC_EMPTY_INITIALIZER;
-
-    for (const struct ovs_cmdl_parsed_option *po = parsed_options;
-         po < &parsed_options[n]; po++) {
-        optarg = po->arg;
-        switch (po->o->val) {
-        case OPT_DB:
-            VLOG_WARN("not using ovn-nbctl daemon because of %s option",
-                      po->o->name);
-            svec_destroy(&args);
-            return;
-
-        case OPT_NO_SYSLOG:
-            vlog_set_levels(&this_module, VLF_SYSLOG, VLL_WARN);
-            break;
-
-        case 'h':
-            usage();
-            exit(EXIT_SUCCESS);
-
-        case OPT_COMMANDS:
-            ctl_print_commands();
-            /* fall through */
-
-        case OPT_OPTIONS:
-            ctl_print_options(get_all_options());
-            /* fall through */
-
-        case OPT_LEADER_ONLY:
-        case OPT_NO_LEADER_ONLY:
-        case OPT_SHUFFLE_REMOTES:
-        case OPT_NO_SHUFFLE_REMOTES:
-        case OPT_BOOTSTRAP_CA_CERT:
-        STREAM_SSL_CASES
-        DAEMON_OPTION_CASES
-            VLOG_INFO("using ovn-nbctl daemon, ignoring %s option",
-                      po->o->name);
-            break;
-
-        case 'V':
-            ovs_print_version(0, 0);
-            printf("DB Schema %s\n", nbrec_get_db_version());
-            exit(EXIT_SUCCESS);
-
-        case 't':
-            if (!str_to_uint(po->arg, 10, &timeout) || !timeout) {
-                ctl_fatal("value %s on -t or --timeout is invalid", po->arg);
-            }
-            break;
-
-        VLOG_OPTION_HANDLERS
-
-        case OPT_LOCAL:
-        default:
-            if (po->arg) {
-                svec_add_nocopy(&args,
-                                xasprintf("--%s=%s", po->o->name, po->arg));
-            } else {
-                svec_add_nocopy(&args, xasprintf("--%s", po->o->name));
-            }
-            break;
-        }
-    }
-    svec_add(&args, "--");
-    for (int i = optind; i < argc; i++) {
-        svec_add(&args, argv[i]);
-    }
-
-    ctl_timeout_setup(timeout);
-
-    struct jsonrpc *client;
-    int error = unixctl_client_create(socket_name, &client);
-    if (error) {
-        ctl_fatal("%s: could not connect to ovn-nb daemon (%s); "
-                  "unset OVN_NB_DAEMON to avoid using daemon",
-                  socket_name, ovs_strerror(error));
-    }
-
-    char *cmd_result;
-    char *cmd_error;
-    error = unixctl_client_transact(client, "run",
-                                    args.n, args.names,
-                                    &cmd_result, &cmd_error);
-    if (error) {
-        ctl_fatal("%s: transaction error (%s)",
-                  socket_name, ovs_strerror(error));
-    }
-    svec_destroy(&args);
-
-    int exit_status;
-    if (cmd_error) {
-        exit_status = EXIT_FAILURE;
-        fprintf(stderr, "%s: %s", program_name, cmd_error);
-    } else {
-        exit_status = EXIT_SUCCESS;
-        fputs(cmd_result, stdout);
-    }
-    free(cmd_result);
-    free(cmd_error);
-    jsonrpc_close(client);
-    exit(exit_status);
-}
diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
deleted file mode 100644
index 2aaa457e826b..000000000000
--- a/ovn/utilities/ovn-sbctl.8.in
+++ /dev/null
@@ -1,303 +0,0 @@
-.\" -*- nroff -*-
-.so lib/ovs.tmac
-.TH ovn\-sbctl 8 "@VERSION@" "Open vSwitch" "Open vSwitch Manual"
-.\" This program's name:
-.ds PN ovn\-sbctl
-.
-.SH NAME
-ovn\-sbctl \- utility for querying and configuring \fBOVN_Southbound\fR database
-.
-.SH SYNOPSIS
-\fBovn\-sbctl\fR [\fIoptions\fR] \fB\-\-\fR [\fIoptions\fR] \fIcommand
-\fR[\fIargs\fR] [\fB\-\-\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR]]...
-.
-.SH DESCRIPTION
-The \fBovn\-sbctl\fR program configures the \fBOVN_Southbound\fR database
-by providing a high\-level interface to its configuration database.  See
-\fBovn\-sb\fR(5) for comprehensive documentation of the database schema.
-.PP
-\fBovn\-sbctl\fR connects to an \fBovsdb\-server\fR process that
-maintains an OVN_Southbound configuration database.  Using this
-connection, it queries and possibly applies changes to the database,
-depending on the supplied commands.
-.PP
-\fBovn\-sbctl\fR can perform any number of commands in a single run,
-implemented as a single atomic transaction against the database.
-.PP
-The \fBovn\-sbctl\fR command line begins with global options (see
-\fBOPTIONS\fR below for details).  The global options are followed by
-one or more commands.  Each command should begin with \fB\-\-\fR by
-itself as a command-line argument, to separate it from the following
-commands.  (The \fB\-\-\fR before the first command is optional.)  The
-command
-itself starts with command-specific options, if any, followed by the
-command name and any arguments.
-.
-.SH OPTIONS
-.
-The following options affect the behavior of \fBovn\-sbctl\fR as a
-whole.  Some individual commands also accept their own options, which
-are given just before the command name.  If the first command on the
-command line has options, then those options must be separated from
-the global options by \fB\-\-\fR.
-.
-.IP "\fB\-\-db=\fIserver\fR"
-The OVSDB database remote to contact.  If the \fBOVN_SB_DB\fR
-environment variable is set, its value is used as the default.
-Otherwise, the default is \fBunix:@RUNDIR@/ovnsb_db.sock\fR, but this
-default is unlikely to be useful outside of single-machine OVN test
-environments.
-.IP
-\fIserver\fR may be an OVSDB active or passive connection method,
-e.g. \fBssl:192.168.10.5:6640\fR, as described in \fBovsdb\fR(7).
-.
-.IP "\fB\-\-leader\-only\fR"
-.IQ "\fB\-\-no\-leader\-only\fR"
-By default, or with \fB\-\-leader\-only\fR, when the database server
-is a clustered database, \fBovn\-sbctl\fR will avoid servers other
-than the cluster leader.  This ensures that any data that
-\fBovn\-sbctl\fR reads and reports is up-to-date.  With
-\fB\-\-no\-leader\-only\fR, \fBovn\-sbctl\fR will use any server in
-the cluster, which means that for read-only transactions it can report
-and act on stale data (transactions that modify the database are
-always serialized even with \fB\-\-no\-leader\-only\fR).  Refer to
-\fBUnderstanding Cluster Consistency\fR in \fBovsdb\fR(7) for more
-information.
-.
-.IP "\fB\-\-no\-syslog\fR"
-By default, \fBovn\-sbctl\fR logs its arguments and the details of any
-changes that it makes to the system log.  This option disables this
-logging.
-.IP
-This option is equivalent to \fB\-\-verbose=sbctl:syslog:warn\fR.
-.
-.IP "\fB\-\-oneline\fR"
-Modifies the output format so that the output for each command is printed
-on a single line.  New-line characters that would otherwise separate
-lines are printed as \fB\\n\fR, and any instances of \fB\\\fR that
-would otherwise appear in the output are doubled.
-Prints a blank line for each command that has no output.
-This option does not affect the formatting of output from the
-\fBlist\fR or \fBfind\fR commands; see \fBTable Formatting Options\fR
-below.
-.
-.IP "\fB\-\-dry\-run\fR"
-Prevents \fBovn\-sbctl\fR from actually modifying the database.
-.
-.IP "\fB\-t \fIsecs\fR"
-.IQ "\fB\-\-timeout=\fIsecs\fR"
-By default, or with a \fIsecs\fR of \fB0\fR, \fBovn\-sbctl\fR waits
-forever for a response from the database.  This option limits runtime
-to approximately \fIsecs\fR seconds.  If the timeout expires,
-\fBovn\-sbctl\fR will exit with a \fBSIGALRM\fR signal.  (A timeout
-would normally happen only if the database cannot be contacted, or if
-the system is overloaded.)
-.
-.so lib/vlog.man
-.so lib/common.man
-.
-.SS "Table Formatting Options"
-These options control the format of output from the \fBlist\fR and
-\fBfind\fR commands.
-.so lib/table.man
-.
-.SS "Public Key Infrastructure Options"
-.so lib/ssl-bootstrap.man
-.so lib/ssl.man
-.
-.SH COMMANDS
-The commands implemented by \fBovn\-sbctl\fR are described in the
-sections below.
-.SS "OVN_Southbound Commands"
-These commands work with an \fBOVN_Southbound\fR database as a whole.
-.
-.IP "\fBinit\fR"
-Initializes the database, if it is empty.  If the database has already
-been initialized, this command has no effect.
-.
-.IP "\fBshow\fR"
-Prints a brief overview of the database contents.
-.
-.SS "Chassis Commands"
-These commands manipulate \fBOVN_Southbound\fR chassis.
-.
-.IP "[\fB\-\-may\-exist\fR] \fBchassis\-add \fIchassis\fR \fIencap-type\fR \fIencap-ip\fR"
-Creates a new chassis named \fIchassis\fR.  \fIencap-type\fR is a
-comma-separated list of tunnel types.  The chassis will have
-one encap entry for each specified tunnel type with \fIencap-ip\fR
-as the destination IP for each.
-.IP
-Without \fB\-\-may\-exist\fR, attempting to create a chassis that
-exists is an error.  With \fB\-\-may\-exist\fR, this command does
-nothing if \fIchassis\fR already exists.
-.
-.IP "[\fB\-\-if\-exists\fR] \fBchassis\-del \fIchassis\fR"
-Deletes \fIchassis\fR and its \fIencaps\fR and \fIgateway_ports\fR.
-.IP
-Without \fB\-\-if\-exists\fR, attempting to delete a chassis that does
-not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
-delete a chassis that does not exist has no effect.
-.
-.SS "Port binding Commands"
-.
-These commands manipulate \fBOVN_Southbound\fR port bindings.
-.
-.IP "[\fB\-\-may\-exist\fR] \fBlsp\-bind \fIlogical-port\fR \fIchassis\fR"
-Binds the logical port named \fIlogical-port\fR to \fIchassis\fR.
-.IP
-Without \fB\-\-may\-exist\fR, attempting to bind a logical port that
-has already been bound is an error.  With \fB\-\-may\-exist\fR, this
-command does nothing if \fIlogical-port\fR has already been bound to
-a chassis.
-.
-.IP "[\fB\-\-if\-exists\fR] \fBlsp\-unbind\fR \fIlogical-port\fR"
-Resets the binding of \fIlogical-port\fR to \fINULL\fR.
-.IP
-Without \fB\-\-if\-exists\fR, attempting to unbind a logical port
-that is not bound is an error.  With \fB\-\-if\-exists\fR, attempting
-to unbind logical port that is not bound has no effect.
-.
-.SS "Logical Flow Commands"
-.
-.IP "[\fB\-\-uuid\fR] [\fB\-\-ovs\fR[\fB=\fIremote\fR]] [\fB\-\-stats\fR] \fBlflow\-list\fR [\fIlogical-datapath\fR] [\fIlflow\fR...]"
-List logical flows.  If \fIlogical-datapath\fR is specified, only list
-flows for that logical datapath.  The \fIlogical-datapath\fR may be
-given as a UUID or as a datapath name (reporting an error if multiple
-datapaths have the same name).
-.IP
-If at least one \fIlflow\fR is given, only matching logical flows, if
-any, are listed.  Each \fIlflow\fR may be specified as a UUID or the
-first few characters of a UUID, optionally prefixed by \fB0x\fR.
-(Because \fBovn\-controller\fR sets OpenFlow flow cookies to the first
-32 bits of the corresponding logical flow's UUID, this makes it easy
-to look up the logical flow that generated a particular OpenFlow
-flow.)
-.IP
-If \fB\-\-uuid\fR is specified, the output includes the first 32 bits
-of each logical flow's UUID.  This makes it easier to find the
-OpenFlow flows that correspond to a given logical flow.
-.IP
-If \fB\-\-ovs\fR is included, \fBovn\-sbctl\fR attempts to obtain and
-display the OpenFlow flows that correspond to each OVN logical flow.
-To do so, \fBovn\-sbctl\fR connects to \fIremote\fR (by default,
-\fBunix:@RUNDIR@/br-int.mgmt\fR) over OpenFlow and retrieves the
-flows.  If \fIremote\fR is specified, it must be an active OpenFlow
-connection method described in \fBovsdb\fR(7).  Please see the
-discussion of the similar \fB\-\-ovs\fR option in \fBovn-trace\fR(8)
-for more information about the OpenFlow flow output.
-.IP
-By default, OpenFlow flow output includes only match and actions.  Add
-\fB\-\-stats\fR to include all OpenFlow information, such as packet
-and byte counters, duration, and timeouts.
-.
-.IP "[\fB\-\-uuid\fR] \fBdump\-flows\fR [\fIlogical-datapath\fR]"
-Alias for \fBlflow\-list\fB.
-.
-.SS "Remote Connectivity Commands"
-.
-These commands manipulate the \fBconnections\fR column in the \fBSB_Global\fR
-table and rows in the \fBConnection\fR table.  When \fBovsdb\-server\fR
-is configured to use the \fBconnections\fR column for OVSDB connections,
-this allows the administrator to use \fBovn\-sbctl\fR to configure database
-connections.
-.
-.IP "\fBget\-connection\fR"
-Prints the configured connection(s).
-.
-.IP "\fBdel\-connection\fR"
-Deletes the configured connection(s).
-.
-.IP "\fBset\-connection\fR [\fIaccess\-specifier\fR] \fItarget\fR\&..."
-Sets the configured manager target or targets.  Each \fItarget\fR may
-may be an OVSDB active or passive connection method,
-e.g. \fBpssl:6640\fR, as described in \fBovsdb\fR(7),
-optionally preceded by an optional access-specifier (\fBread\-only\fR or
-\fBread\-write\fR).
-If provided, the effect of the access specifier persists for subsequent
-targets until changed by another access specifier.
-.
-.SS "SSL Configuration"
-When \fBovsdb\-server\fR is configured to connect using SSL, the
-following parameters are required:
-.TP
-\fIprivate-key\fR
-Specifies a PEM file containing the private key used for SSL connections.
-.TP
-\fIcertificate\fR
-Specifies a PEM file containing a certificate, signed by the
-certificate authority (CA) used by the connection peers, that
-certifies the private key, identifying a trustworthy peer.
-.TP
-\fIca-cert\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the connection peers are trustworthy.
-.PP
-These SSL settings apply to all SSL connections made by the southbound
-database server.
-.
-.IP "\fBget\-ssl\fR"
-Prints the SSL configuration.
-.
-.IP "\fBdel\-ssl\fR"
-Deletes the current SSL configuration.
-.
-.IP "[\fB\-\-bootstrap\fR] \fBset\-ssl\fR \fIprivate-key\fR \fIcertificate\fR \fIca-cert\fR [\fIssl-protocol-list\fR [\fIssl-cipher-list\fR]]"
-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
-before SSL connectivity can be used.  However, if the \fIca-cert\fR file
-does not exist and the \fB\-\-bootstrap\fR
-option is given, then \fBovsdb\-server\fR will attempt to obtain the
-CA certificate from the target 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.
-.PP
-\fBThis option exposes the SSL connection to a man-in-the-middle
-attack obtaining the initial CA certificate\fR, but it may be useful
-for bootstrapping.
-.PP
-This option is only useful if the SSL peer sends its CA certificate
-as part of the SSL certificate chain.  The SSL protocol does not
-require the controller to send the CA certificate.
-.
-.SS "Database Commands"
-.
-These commands query and modify the contents of \fBovsdb\fR tables.
-They are a slight abstraction of the \fBovsdb\fR interface and as such
-they operate at a lower level than other \fBovs\-sbctl\fR commands.
-.PP
-.ST "Identifying Tables, Records, and Columns"
-.PP
-Each of these commands has a \fItable\fR parameter to identify a table
-within the database.  Many of them also take a \fIrecord\fR parameter
-that identifies a particular record within a table.  The \fIrecord\fR
-parameter may be the UUID for a record, and many tables offer
-additional ways to identify records.  Some commands also take
-\fIcolumn\fR parameters that identify a particular field within the
-records in a table.
-.PP
-For a list of tables and their columns, see \fBovn\-sb\fR(5) or
-see the table listing from the \fB--help\fR option.
-.PP
-Record names must be specified in full and with correct
-capitalization, except that UUIDs may be abbreviated to their first 4
-(or more) hex digits, as long as that is unique within the table.
-Names of tables and columns are not case-sensitive, and \fB\-\fR and
-\fB_\fR are treated interchangeably.  Unique abbreviations of table
-and column names are acceptable, e.g. \fBaddr\fR or \fBa\fR is
-sufficient to identify the \fBAddress_Set\fR table.
-.
-.so lib/db-ctl-base.man
-.SH "EXIT STATUS"
-.IP "0"
-Successful program execution.
-.IP "1"
-Usage, syntax, or configuration file error.
-.SH "SEE ALSO"
-.
-.BR ovn\-sb (5).
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
deleted file mode 100644
index dd6585a3eeb0..000000000000
--- a/ovn/utilities/ovn-sbctl.c
+++ /dev/null
@@ -1,1541 +0,0 @@
-/*
- * Copyright (c) 2015, 2016, 2017 Nicira, Inc.
- *
- * 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.
- */
-
-#include <config.h>
-
-#include <ctype.h>
-#include <errno.h>
-#include <float.h>
-#include <getopt.h>
-#include <inttypes.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "colors.h"
-#include "command-line.h"
-#include "compiler.h"
-#include "db-ctl-base.h"
-#include "dirs.h"
-#include "fatal-signal.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofp-flow.h"
-#include "openvswitch/ofp-print.h"
-#include "openvswitch/shash.h"
-#include "openvswitch/vconn.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "ovsdb-data.h"
-#include "ovsdb-idl.h"
-#include "openvswitch/poll-loop.h"
-#include "process.h"
-#include "sset.h"
-#include "stream-ssl.h"
-#include "stream.h"
-#include "table.h"
-#include "timeval.h"
-#include "util.h"
-#include "svec.h"
-
-VLOG_DEFINE_THIS_MODULE(sbctl);
-
-struct sbctl_context;
-
-/* --db: The database server to contact. */
-static const char *db;
-
-/* --oneline: Write each command's output as a single line? */
-static bool oneline;
-
-/* --dry-run: Do not commit any changes. */
-static bool dry_run;
-
-/* --timeout: Time to wait for a connection to 'db'. */
-static unsigned int timeout;
-
-/* Format for table output. */
-static struct table_style table_style = TABLE_STYLE_DEFAULT;
-
-/* The IDL we're using and the current transaction, if any.
- * This is for use by sbctl_exit() only, to allow it to clean up.
- * Other code should use its context arguments. */
-static struct ovsdb_idl *the_idl;
-static struct ovsdb_idl_txn *the_idl_txn;
-OVS_NO_RETURN static void sbctl_exit(int status);
-
-/* --leader-only, --no-leader-only: Only accept the leader in a cluster. */
-static int leader_only = true;
-
-static void sbctl_cmd_init(void);
-OVS_NO_RETURN static void usage(void);
-static void parse_options(int argc, char *argv[], struct shash *local_options);
-static void run_prerequisites(struct ctl_command[], size_t n_commands,
-                              struct ovsdb_idl *);
-static bool do_sbctl(const char *args, struct ctl_command *, size_t n,
-                     struct ovsdb_idl *);
-
-int
-main(int argc, char *argv[])
-{
-    struct ovsdb_idl *idl;
-    struct ctl_command *commands;
-    struct shash local_options;
-    unsigned int seqno;
-    size_t n_commands;
-
-    set_program_name(argv[0]);
-    fatal_ignore_sigpipe();
-    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
-    vlog_set_levels_from_string_assert("reconnect:warn");
-
-    sbctl_cmd_init();
-
-    /* Parse command line. */
-    char *args = process_escape_args(argv);
-    shash_init(&local_options);
-    parse_options(argc, argv, &local_options);
-    char *error = ctl_parse_commands(argc - optind, argv + optind,
-                                     &local_options, &commands, &n_commands);
-    if (error) {
-        ctl_fatal("%s", error);
-    }
-    VLOG(ctl_might_write_to_db(commands, n_commands) ? VLL_INFO : VLL_DBG,
-         "Called as %s", args);
-
-    ctl_timeout_setup(timeout);
-
-    /* Initialize IDL. */
-    idl = the_idl = ovsdb_idl_create(db, &sbrec_idl_class, false, true);
-    ovsdb_idl_set_leader_only(idl, leader_only);
-    run_prerequisites(commands, n_commands, idl);
-
-    /* Execute the commands.
-     *
-     * 'seqno' is the database sequence number for which we last tried to
-     * execute our transaction.  There's no point in trying to commit more than
-     * once for any given sequence number, because if the transaction fails
-     * it's because the database changed and we need to obtain an up-to-date
-     * view of the database before we try the transaction again. */
-    seqno = ovsdb_idl_get_seqno(idl);
-    for (;;) {
-        ovsdb_idl_run(idl);
-        if (!ovsdb_idl_is_alive(idl)) {
-            int retval = ovsdb_idl_get_last_error(idl);
-            ctl_fatal("%s: database connection failed (%s)",
-                        db, ovs_retval_to_string(retval));
-        }
-
-        if (seqno != ovsdb_idl_get_seqno(idl)) {
-            seqno = ovsdb_idl_get_seqno(idl);
-            if (do_sbctl(args, commands, n_commands, idl)) {
-                free(args);
-                exit(EXIT_SUCCESS);
-            }
-        }
-
-        if (seqno == ovsdb_idl_get_seqno(idl)) {
-            ovsdb_idl_wait(idl);
-            poll_block();
-        }
-    }
-}
-
-static void
-parse_options(int argc, char *argv[], struct shash *local_options)
-{
-    enum {
-        OPT_DB = UCHAR_MAX + 1,
-        OPT_ONELINE,
-        OPT_NO_SYSLOG,
-        OPT_DRY_RUN,
-        OPT_LOCAL,
-        OPT_COMMANDS,
-        OPT_OPTIONS,
-        OPT_BOOTSTRAP_CA_CERT,
-        VLOG_OPTION_ENUMS,
-        TABLE_OPTION_ENUMS,
-        SSL_OPTION_ENUMS,
-    };
-    static const struct option global_long_options[] = {
-        {"db", required_argument, NULL, OPT_DB},
-        {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
-        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
-        {"oneline", no_argument, NULL, OPT_ONELINE},
-        {"timeout", required_argument, NULL, 't'},
-        {"help", no_argument, NULL, 'h'},
-        {"commands", no_argument, NULL, OPT_COMMANDS},
-        {"options", no_argument, NULL, OPT_OPTIONS},
-        {"leader-only", no_argument, &leader_only, true},
-        {"no-leader-only", no_argument, &leader_only, false},
-        {"version", no_argument, NULL, 'V'},
-        VLOG_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
-        TABLE_LONG_OPTIONS,
-        {NULL, 0, NULL, 0},
-    };
-    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
-    char *tmp, *short_options;
-
-    struct option *options;
-    size_t allocated_options;
-    size_t n_options;
-    size_t i;
-
-    tmp = ovs_cmdl_long_options_to_short_options(global_long_options);
-    short_options = xasprintf("+%s", tmp);
-    free(tmp);
-
-    /* We want to parse both global and command-specific options here, but
-     * getopt_long() isn't too convenient for the job.  We copy our global
-     * options into a dynamic array, then append all of the command-specific
-     * options. */
-    options = xmemdup(global_long_options, sizeof global_long_options);
-    allocated_options = ARRAY_SIZE(global_long_options);
-    n_options = n_global_long_options;
-    ctl_add_cmd_options(&options, &n_options, &allocated_options, OPT_LOCAL);
-
-    for (;;) {
-        int idx;
-        int c;
-
-        c = getopt_long(argc, argv, short_options, options, &idx);
-        if (c == -1) {
-            break;
-        }
-
-        switch (c) {
-        case OPT_DB:
-            db = optarg;
-            break;
-
-        case OPT_ONELINE:
-            oneline = true;
-            break;
-
-        case OPT_NO_SYSLOG:
-            vlog_set_levels(&this_module, VLF_SYSLOG, VLL_WARN);
-            break;
-
-        case OPT_DRY_RUN:
-            dry_run = true;
-            break;
-
-        case OPT_LOCAL:
-            if (shash_find(local_options, options[idx].name)) {
-                ctl_fatal("'%s' option specified multiple times",
-                            options[idx].name);
-            }
-            shash_add_nocopy(local_options,
-                             xasprintf("--%s", options[idx].name),
-                             nullable_xstrdup(optarg));
-            break;
-
-        case 'h':
-            usage();
-
-        case OPT_COMMANDS:
-            ctl_print_commands();
-            /* fall through */
-
-        case OPT_OPTIONS:
-            ctl_print_options(global_long_options);
-            /* fall through */
-
-        case 'V':
-            ovs_print_version(0, 0);
-            printf("DB Schema %s\n", sbrec_get_db_version());
-            exit(EXIT_SUCCESS);
-
-        case 't':
-            if (!str_to_uint(optarg, 10, &timeout) || !timeout) {
-                ctl_fatal("value %s on -t or --timeout is invalid", optarg);
-            }
-            break;
-
-        VLOG_OPTION_HANDLERS
-        TABLE_OPTION_HANDLERS(&table_style)
-        STREAM_SSL_OPTION_HANDLERS
-
-        case OPT_BOOTSTRAP_CA_CERT:
-            stream_ssl_set_ca_cert_file(optarg, true);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            abort();
-
-        case 0:
-            break;
-        }
-    }
-    free(short_options);
-
-    if (!db) {
-        db = default_sb_db();
-    }
-
-    for (i = n_global_long_options; options[i].name; i++) {
-        free(CONST_CAST(char *, options[i].name));
-    }
-    free(options);
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN southbound DB management utility\n\
-\n\
-usage: %s [OPTIONS] COMMAND [ARG...]\n\
-\n\
-General commands:\n\
-  show                        print overview of database contents\n\
-\n\
-Chassis commands:\n\
-  chassis-add CHASSIS ENCAP-TYPE ENCAP-IP  create a new chassis named\n\
-                                           CHASSIS with ENCAP-TYPE tunnels\n\
-                                           and ENCAP-IP\n\
-  chassis-del CHASSIS         delete CHASSIS and all of its encaps\n\
-                              and gateway_ports\n\
-\n\
-Port binding commands:\n\
-  lsp-bind PORT CHASSIS       bind logical port PORT to CHASSIS\n\
-  lsp-unbind PORT             reset the port binding of logical port PORT\n\
-\n\
-Logical flow commands:\n\
-  lflow-list [DATAPATH] [LFLOW...] List logical flows for DATAPATH\n\
-  dump-flows [DATAPATH] [LFLOW...] Alias for lflow-list\n\
-\n\
-Connection commands:\n\
-  get-connection             print the connections\n\
-  del-connection             delete the connections\n\
-  [--inactivity-probe=MSECS]\n\
-  set-connection TARGET...   set the list of connections to TARGET...\n\
-\n\
-SSL commands:\n\
-  get-ssl                     print the SSL configuration\n\
-  del-ssl                     delete the SSL configuration\n\
-  set-ssl PRIV-KEY CERT CA-CERT [SSL-PROTOS [SSL-CIPHERS]] \
-set the SSL configuration\n\
-\n\
-%s\
-%s\
-\n\
-Options:\n\
-  --db=DATABASE               connect to DATABASE\n\
-                              (default: %s)\n\
-  --no-leader-only            accept any cluster member, not just the leader\n\
-  -t, --timeout=SECS          wait at most SECS seconds\n\
-  --dry-run                   do not commit changes to database\n\
-  --oneline                   print exactly one line of output per command\n",
-           program_name, program_name, ctl_get_db_cmd_usage(),
-           ctl_list_db_tables_usage(), default_sb_db());
-    table_usage();
-    vlog_usage();
-    printf("\
-  --no-syslog             equivalent to --verbose=sbctl:syslog:warn\n");
-    printf("\n\
-Other options:\n\
-  -h, --help                  display this help message\n\
-  -V, --version               display version information\n");
-    stream_usage("database", true, true, true);
-    exit(EXIT_SUCCESS);
-}
-
-
-/* ovs-sbctl specific context.  Inherits the 'struct ctl_context' as base. */
-struct sbctl_context {
-    struct ctl_context base;
-
-    /* A cache of the contents of the database.
-     *
-     * A command that needs to use any of this information must first call
-     * sbctl_context_populate_cache().  A command that changes anything that
-     * could invalidate the cache must either call
-     * sbctl_context_invalidate_cache() or manually update the cache to
-     * maintain its correctness. */
-    bool cache_valid;
-    /* Maps from chassis name to struct sbctl_chassis. */
-    struct shash chassis;
-    /* Maps from lport name to struct sbctl_port_binding. */
-    struct shash port_bindings;
-};
-
-/* Casts 'base' into 'struct sbctl_context'. */
-static struct sbctl_context *
-sbctl_context_cast(struct ctl_context *base)
-{
-    return CONTAINER_OF(base, struct sbctl_context, base);
-}
-
-struct sbctl_chassis {
-    const struct sbrec_chassis *ch_cfg;
-};
-
-struct sbctl_port_binding {
-    const struct sbrec_port_binding *bd_cfg;
-};
-
-static void
-sbctl_context_invalidate_cache(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-
-    if (!sbctl_ctx->cache_valid) {
-        return;
-    }
-    sbctl_ctx->cache_valid = false;
-    shash_destroy_free_data(&sbctl_ctx->chassis);
-    shash_destroy_free_data(&sbctl_ctx->port_bindings);
-}
-
-static void
-sbctl_context_populate_cache(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-    const struct sbrec_chassis *chassis_rec;
-    const struct sbrec_port_binding *port_binding_rec;
-    struct sset chassis, port_bindings;
-
-    if (sbctl_ctx->cache_valid) {
-        /* Cache is already populated. */
-        return;
-    }
-    sbctl_ctx->cache_valid = true;
-    shash_init(&sbctl_ctx->chassis);
-    shash_init(&sbctl_ctx->port_bindings);
-    sset_init(&chassis);
-    SBREC_CHASSIS_FOR_EACH(chassis_rec, ctx->idl) {
-        struct sbctl_chassis *ch;
-
-        if (!sset_add(&chassis, chassis_rec->name)) {
-            VLOG_WARN("database contains duplicate chassis name (%s)",
-                      chassis_rec->name);
-            continue;
-        }
-
-        ch = xmalloc(sizeof *ch);
-        ch->ch_cfg = chassis_rec;
-        shash_add(&sbctl_ctx->chassis, chassis_rec->name, ch);
-    }
-    sset_destroy(&chassis);
-
-    sset_init(&port_bindings);
-    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->idl) {
-        struct sbctl_port_binding *bd;
-
-        if (!sset_add(&port_bindings, port_binding_rec->logical_port)) {
-            VLOG_WARN("database contains duplicate port binding for logical "
-                      "port (%s)",
-                      port_binding_rec->logical_port);
-            continue;
-        }
-
-        bd = xmalloc(sizeof *bd);
-        bd->bd_cfg = port_binding_rec;
-        shash_add(&sbctl_ctx->port_bindings, port_binding_rec->logical_port,
-                  bd);
-    }
-    sset_destroy(&port_bindings);
-}
-
-static void
-check_conflicts(struct sbctl_context *sbctl_ctx, const char *name,
-                char *msg)
-{
-    if (shash_find(&sbctl_ctx->chassis, name)) {
-        ctl_fatal("%s because a chassis named %s already exists",
-                    msg, name);
-    }
-    free(msg);
-}
-
-static struct sbctl_chassis *
-find_chassis(struct sbctl_context *sbctl_ctx, const char *name,
-             bool must_exist)
-{
-    struct sbctl_chassis *sbctl_ch;
-
-    ovs_assert(sbctl_ctx->cache_valid);
-
-    sbctl_ch = shash_find_data(&sbctl_ctx->chassis, name);
-    if (must_exist && !sbctl_ch) {
-        ctl_fatal("no chassis named %s", name);
-    }
-
-    return sbctl_ch;
-}
-
-static struct sbctl_port_binding *
-find_port_binding(struct sbctl_context *sbctl_ctx, const char *name,
-                  bool must_exist)
-{
-    struct sbctl_port_binding *bd;
-
-    ovs_assert(sbctl_ctx->cache_valid);
-
-    bd = shash_find_data(&sbctl_ctx->port_bindings, name);
-    if (must_exist && !bd) {
-        ctl_fatal("no port named %s", name);
-    }
-
-    return bd;
-}
-
-static void
-pre_get_info(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &sbrec_chassis_col_name);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_chassis_col_encaps);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_type);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_encap_col_ip);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_logical_port);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_pipeline);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_actions);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_priority);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_table_id);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_match);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_external_ids);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_datapath_binding_col_external_ids);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ip_multicast_col_datapath);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ip_multicast_col_seq_no);
-}
-
-static struct cmd_show_table cmd_show_tables[] = {
-    {&sbrec_table_chassis,
-     &sbrec_chassis_col_name,
-     {&sbrec_chassis_col_hostname,
-      &sbrec_chassis_col_encaps,
-      NULL},
-     {&sbrec_table_port_binding,
-      &sbrec_port_binding_col_logical_port,
-      &sbrec_port_binding_col_chassis}},
-
-    {&sbrec_table_encap,
-     &sbrec_encap_col_type,
-     {&sbrec_encap_col_ip,
-      &sbrec_encap_col_options,
-      NULL},
-     {NULL, NULL, NULL}},
-
-    {NULL, NULL, {NULL, NULL, NULL}, {NULL, NULL, NULL}},
-};
-
-static void
-sbctl_init(struct ctl_context *ctx OVS_UNUSED)
-{
-}
-
-static void
-cmd_chassis_add(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    const char *ch_name, *encap_types, *encap_ip;
-
-    ch_name = ctx->argv[1];
-    encap_types = ctx->argv[2];
-    encap_ip = ctx->argv[3];
-
-    sbctl_context_populate_cache(ctx);
-    if (may_exist) {
-        struct sbctl_chassis *sbctl_ch;
-
-        sbctl_ch = find_chassis(sbctl_ctx, ch_name, false);
-        if (sbctl_ch) {
-            return;
-        }
-    }
-    check_conflicts(sbctl_ctx, ch_name,
-                    xasprintf("cannot create a chassis named %s", ch_name));
-
-    struct sset encap_set;
-    sset_from_delimited_string(&encap_set, encap_types, ",");
-
-    size_t n_encaps = sset_count(&encap_set);
-    struct sbrec_encap **encaps = xmalloc(n_encaps * sizeof *encaps);
-    const struct smap options = SMAP_CONST1(&options, "csum", "true");
-    const char *encap_type;
-    int i = 0;
-    SSET_FOR_EACH (encap_type, &encap_set){
-        encaps[i] = sbrec_encap_insert(ctx->txn);
-
-        sbrec_encap_set_type(encaps[i], encap_type);
-        sbrec_encap_set_ip(encaps[i], encap_ip);
-        sbrec_encap_set_options(encaps[i], &options);
-        sbrec_encap_set_chassis_name(encaps[i], ch_name);
-        i++;
-    }
-    sset_destroy(&encap_set);
-
-    struct sbrec_chassis *ch = sbrec_chassis_insert(ctx->txn);
-    sbrec_chassis_set_name(ch, ch_name);
-    sbrec_chassis_set_encaps(ch, encaps, n_encaps);
-    free(encaps);
-
-    sbctl_context_invalidate_cache(ctx);
-}
-
-static void
-cmd_chassis_del(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    struct sbctl_chassis *sbctl_ch;
-
-    sbctl_context_populate_cache(ctx);
-    sbctl_ch = find_chassis(sbctl_ctx, ctx->argv[1], must_exist);
-    if (sbctl_ch) {
-        if (sbctl_ch->ch_cfg) {
-            size_t i;
-
-            for (i = 0; i < sbctl_ch->ch_cfg->n_encaps; i++) {
-                sbrec_encap_delete(sbctl_ch->ch_cfg->encaps[i]);
-            }
-            sbrec_chassis_delete(sbctl_ch->ch_cfg);
-        }
-        shash_find_and_delete(&sbctl_ctx->chassis, ctx->argv[1]);
-        free(sbctl_ch);
-    }
-}
-
-static void
-cmd_lsp_bind(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-    struct sbctl_chassis *sbctl_ch;
-    struct sbctl_port_binding *sbctl_bd;
-    char *lport_name, *ch_name;
-
-    /* port_binding must exist, chassis must exist! */
-    lport_name = ctx->argv[1];
-    ch_name = ctx->argv[2];
-
-    sbctl_context_populate_cache(ctx);
-    sbctl_bd = find_port_binding(sbctl_ctx, lport_name, true);
-    sbctl_ch = find_chassis(sbctl_ctx, ch_name, true);
-
-    if (sbctl_bd->bd_cfg->chassis) {
-        if (may_exist && sbctl_bd->bd_cfg->chassis == sbctl_ch->ch_cfg) {
-            return;
-        } else {
-            ctl_fatal("lport (%s) has already been binded to chassis (%s)",
-                      lport_name, sbctl_bd->bd_cfg->chassis->name);
-        }
-    }
-    sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg);
-    sbctl_context_invalidate_cache(ctx);
-}
-
-static void
-cmd_lsp_unbind(struct ctl_context *ctx)
-{
-    struct sbctl_context *sbctl_ctx = sbctl_context_cast(ctx);
-    bool must_exist = !shash_find(&ctx->options, "--if-exists");
-    struct sbctl_port_binding *sbctl_bd;
-    char *lport_name;
-
-    lport_name = ctx->argv[1];
-    sbctl_context_populate_cache(ctx);
-    sbctl_bd = find_port_binding(sbctl_ctx, lport_name, must_exist);
-    if (sbctl_bd) {
-        sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL);
-    }
-}
-
-enum {
-    PL_INGRESS,
-    PL_EGRESS,
-};
-
-/* Help ensure we catch any future pipeline values */
-static int
-pipeline_encode(const char *pl)
-{
-    if (!strcmp(pl, "ingress")) {
-        return PL_INGRESS;
-    } else if (!strcmp(pl, "egress")) {
-        return PL_EGRESS;
-    }
-
-    OVS_NOT_REACHED();
-}
-
-static int
-lflow_cmp(const void *lf1_, const void *lf2_)
-{
-    const struct sbrec_logical_flow *const *lf1p = lf1_;
-    const struct sbrec_logical_flow *const *lf2p = lf2_;
-    const struct sbrec_logical_flow *lf1 = *lf1p;
-    const struct sbrec_logical_flow *lf2 = *lf2p;
-
-    int pl1 = pipeline_encode(lf1->pipeline);
-    int pl2 = pipeline_encode(lf2->pipeline);
-
-#define CMP(expr) \
-    do { \
-        int res; \
-        res = (expr); \
-        if (res) { \
-            return res; \
-        } \
-    } while (0)
-
-    CMP(uuid_compare_3way(&lf1->logical_datapath->header_.uuid,
-                          &lf2->logical_datapath->header_.uuid));
-    CMP(pl1 - pl2);
-    CMP(lf1->table_id > lf2->table_id ? 1 :
-            (lf1->table_id < lf2->table_id ? -1 : 0));
-    CMP(lf1->priority > lf2->priority ? -1 :
-            (lf1->priority < lf2->priority ? 1 : 0));
-    CMP(strcmp(lf1->match, lf2->match));
-
-#undef CMP
-
-    return 0;
-}
-
-static char *
-parse_partial_uuid(char *s)
-{
-    /* Accept a full or partial UUID. */
-    if (uuid_is_partial_string(s)) {
-        return s;
-    }
-
-    /* Accept a full or partial UUID prefixed by 0x, since "ovs-ofctl
-     * dump-flows" prints cookies prefixed by 0x. */
-    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
-        && uuid_is_partial_string(s + 2)) {
-        return s + 2;
-    }
-
-    /* Not a (partial) UUID. */
-    return NULL;
-}
-
-static const char *
-strip_leading_zero(const char *s)
-{
-    return s + strspn(s, "0");
-}
-
-static bool
-is_partial_uuid_match(const struct uuid *uuid, const char *match)
-{
-    char uuid_s[UUID_LEN + 1];
-    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
-
-    /* We strip leading zeros because we want to accept cookie values derived
-     * from UUIDs, and cookie values are printed without leading zeros because
-     * they're just numbers. */
-    const char *s1 = strip_leading_zero(uuid_s);
-    const char *s2 = strip_leading_zero(match);
-
-    return !strncmp(s1, s2, strlen(s2));
-}
-
-static char *
-default_ovs(void)
-{
-    return xasprintf("unix:%s/br-int.mgmt", ovs_rundir());
-}
-
-static struct vconn *
-sbctl_open_vconn(struct shash *options)
-{
-    struct shash_node *ovs = shash_find(options, "--ovs");
-    if (!ovs) {
-        return NULL;
-    }
-
-    char *remote = ovs->data ? xstrdup(ovs->data) : default_ovs();
-    struct vconn *vconn;
-    int retval = vconn_open_block(remote, 1 << OFP13_VERSION, 0, -1, &vconn);
-    if (retval) {
-        VLOG_WARN("%s: connection failed (%s)", remote, ovs_strerror(retval));
-    }
-    free(remote);
-    return vconn;
-}
-
-static void
-sbctl_dump_openflow(struct vconn *vconn, const struct uuid *uuid, bool stats)
-{
-    struct ofputil_flow_stats_request fsr = {
-        .cookie = htonll(uuid->parts[0]),
-        .cookie_mask = OVS_BE64_MAX,
-        .out_port = OFPP_ANY,
-        .out_group = OFPG_ANY,
-        .table_id = OFPTT_ALL,
-    };
-
-    struct ofputil_flow_stats *fses;
-    size_t n_fses;
-    int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM,
-                                 &fses, &n_fses);
-    if (error) {
-        VLOG_WARN("%s: error obtaining flow stats (%s)",
-                  vconn_get_name(vconn), ovs_strerror(error));
-        return;
-    }
-
-    if (n_fses) {
-        struct ds s = DS_EMPTY_INITIALIZER;
-        for (size_t i = 0; i < n_fses; i++) {
-            const struct ofputil_flow_stats *fs = &fses[i];
-
-            ds_clear(&s);
-            if (stats) {
-                ofputil_flow_stats_format(&s, fs, NULL, NULL, true);
-            } else {
-                ds_put_format(&s, "%stable=%s%"PRIu8" ",
-                              colors.special, colors.end, fs->table_id);
-                match_format(&fs->match, NULL, &s, OFP_DEFAULT_PRIORITY);
-                if (ds_last(&s) != ' ') {
-                    ds_put_char(&s, ' ');
-                }
-
-                ds_put_format(&s, "%sactions=%s", colors.actions, colors.end);
-                struct ofpact_format_params fp = { .s = &s };
-                ofpacts_format(fs->ofpacts, fs->ofpacts_len, &fp);
-            }
-            printf("    %s\n", ds_cstr(&s));
-        }
-        ds_destroy(&s);
-    }
-
-    for (size_t i = 0; i < n_fses; i++) {
-        free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
-    }
-    free(fses);
-}
-
-static void
-cmd_lflow_list(struct ctl_context *ctx)
-{
-    const struct sbrec_datapath_binding *datapath = NULL;
-    if (ctx->argc > 1) {
-        const struct ovsdb_idl_row *row;
-        char *error = ctl_get_row(ctx, &sbrec_table_datapath_binding,
-                                  ctx->argv[1], false, &row);
-        if (error) {
-            ctl_fatal("%s", error);
-        }
-
-        datapath = (const struct sbrec_datapath_binding *)row;
-        if (datapath) {
-            ctx->argc--;
-            ctx->argv++;
-        }
-    }
-
-    for (size_t i = 1; i < ctx->argc; i++) {
-        char *s = parse_partial_uuid(ctx->argv[i]);
-        if (!s) {
-            ctl_fatal("%s is not a UUID or the beginning of a UUID",
-                      ctx->argv[i]);
-        }
-        ctx->argv[i] = s;
-    }
-
-    struct vconn *vconn = sbctl_open_vconn(&ctx->options);
-    bool stats = shash_find(&ctx->options, "--stats") != NULL;
-
-    const struct sbrec_logical_flow **lflows = NULL;
-    size_t n_flows = 0;
-    size_t n_capacity = 0;
-    const struct sbrec_logical_flow *lflow;
-    SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) {
-        if (datapath && lflow->logical_datapath != datapath) {
-            continue;
-        }
-
-        if (n_flows == n_capacity) {
-            lflows = x2nrealloc(lflows, &n_capacity, sizeof *lflows);
-        }
-        lflows[n_flows] = lflow;
-        n_flows++;
-    }
-
-    if (n_flows) {
-        qsort(lflows, n_flows, sizeof *lflows, lflow_cmp);
-    }
-
-    bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL;
-
-    const struct sbrec_logical_flow *prev = NULL;
-    for (size_t i = 0; i < n_flows; i++) {
-        lflow = lflows[i];
-
-        /* Figure out whether to print this particular flow.  By default, we
-         * print all flows, but if any UUIDs were listed on the command line
-         * then we only print the matching ones. */
-        bool include;
-        if (ctx->argc > 1) {
-            include = false;
-            for (size_t j = 1; j < ctx->argc; j++) {
-                if (is_partial_uuid_match(&lflow->header_.uuid,
-                                          ctx->argv[j])) {
-                    include = true;
-                    break;
-                }
-            }
-        } else {
-            include = true;
-        }
-        if (!include) {
-            continue;
-        }
-
-        /* Print a header line for this datapath or pipeline, if we haven't
-         * already done so. */
-        if (!prev
-            || prev->logical_datapath != lflow->logical_datapath
-            || strcmp(prev->pipeline, lflow->pipeline)) {
-            printf("Datapath:");
-
-            const struct smap *ids = &lflow->logical_datapath->external_ids;
-            const char *name = smap_get(ids, "name");
-            const char *name2 = smap_get(ids, "name2");
-            if (name && name2) {
-                printf(" \"%s\" aka \"%s\"", name, name2);
-            } else if (name || name2) {
-                printf(" \"%s\"", name ? name : name2);
-            }
-            printf(" ("UUID_FMT")  Pipeline: %s\n",
-                   UUID_ARGS(&lflow->logical_datapath->header_.uuid),
-                   lflow->pipeline);
-        }
-
-        /* Print the flow. */
-        printf("  ");
-        if (print_uuid) {
-            printf("uuid=0x%08"PRIx32", ", lflow->header_.uuid.parts[0]);
-        }
-        printf("table=%-2"PRId64"(%-19s), priority=%-5"PRId64
-               ", match=(%s), action=(%s)\n",
-               lflow->table_id,
-               smap_get_def(&lflow->external_ids, "stage-name", ""),
-               lflow->priority, lflow->match, lflow->actions);
-        if (vconn) {
-            sbctl_dump_openflow(vconn, &lflow->header_.uuid, stats);
-        }
-        prev = lflow;
-    }
-
-    vconn_close(vconn);
-    free(lflows);
-}
-
-static void
-sbctl_ip_mcast_flush_switch(struct ctl_context *ctx,
-                            const struct sbrec_datapath_binding *dp)
-{
-    const struct sbrec_ip_multicast *ip_mcast;
-
-    /* Lookup the corresponding IP_Multicast entry. */
-    SBREC_IP_MULTICAST_FOR_EACH (ip_mcast, ctx->idl) {
-        if (ip_mcast->datapath != dp) {
-            continue;
-        }
-
-        sbrec_ip_multicast_set_seq_no(ip_mcast, ip_mcast->seq_no + 1);
-    }
-}
-
-static void
-sbctl_ip_mcast_flush(struct ctl_context *ctx)
-{
-    const struct sbrec_datapath_binding *dp;
-
-    if (ctx->argc > 2) {
-        return;
-    }
-
-    if (ctx->argc == 2) {
-        const struct ovsdb_idl_row *row;
-        char *error = ctl_get_row(ctx, &sbrec_table_datapath_binding,
-                                  ctx->argv[1], false, &row);
-        if (error) {
-            ctl_fatal("%s", error);
-        }
-
-        dp = (const struct sbrec_datapath_binding *)row;
-        if (!dp) {
-            ctl_fatal("%s is not a valid datapath", ctx->argv[1]);
-        }
-
-        sbctl_ip_mcast_flush_switch(ctx, dp);
-    } else {
-        SBREC_DATAPATH_BINDING_FOR_EACH (dp, ctx->idl) {
-            sbctl_ip_mcast_flush_switch(ctx, dp);
-        }
-    }
-}
-
-static void
-verify_connections(struct ctl_context *ctx)
-{
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    const struct sbrec_connection *conn;
-
-    sbrec_sb_global_verify_connections(sb_global);
-
-    SBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
-        sbrec_connection_verify_target(conn);
-    }
-}
-
-static void
-pre_connection(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_connections);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_target);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_read_only);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_role);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_inactivity_probe);
-}
-
-static void
-cmd_get_connection(struct ctl_context *ctx)
-{
-    const struct sbrec_connection *conn;
-    struct svec targets;
-    size_t i;
-
-    verify_connections(ctx);
-
-    /* Print the targets in sorted order for reproducibility. */
-    svec_init(&targets);
-
-    SBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
-        char *s;
-
-        s = xasprintf("%s role=\"%s\" %s",
-                      conn->read_only ? "read-only" : "read-write",
-                      conn->role,
-                      conn->target);
-        svec_add(&targets, s);
-        free(s);
-    }
-
-    svec_sort_unique(&targets);
-    for (i = 0; i < targets.n; i++) {
-        ds_put_format(&ctx->output, "%s\n", targets.names[i]);
-    }
-    svec_destroy(&targets);
-}
-
-static void
-delete_connections(struct ctl_context *ctx)
-{
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    const struct sbrec_connection *conn, *next;
-
-    /* Delete Manager rows pointed to by 'connection_options' column. */
-    SBREC_CONNECTION_FOR_EACH_SAFE(conn, next, ctx->idl) {
-        sbrec_connection_delete(conn);
-    }
-
-    /* Delete 'Manager' row refs in 'manager_options' column. */
-    sbrec_sb_global_set_connections(sb_global, NULL, 0);
-}
-
-static void
-cmd_del_connection(struct ctl_context *ctx)
-{
-    verify_connections(ctx);
-    delete_connections(ctx);
-}
-
-static void
-insert_connections(struct ctl_context *ctx, char *targets[], size_t n)
-{
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    struct sbrec_connection **connections;
-    size_t i, conns=0;
-    bool read_only = false;
-    char *role = "";
-    const char *inactivity_probe = shash_find_data(&ctx->options,
-                                                   "--inactivity-probe");
-
-    /* Insert each connection in a new row in Connection table. */
-    connections = xmalloc(n * sizeof *connections);
-    for (i = 0; i < n; i++) {
-        if (!strcmp(targets[i], "read-only")) {
-            read_only = true;
-            continue;
-        } else if (!strcmp(targets[i], "read-write")) {
-            read_only = false;
-            continue;
-        } else if (!strncmp(targets[i], "role=", 5)) {
-            role = targets[i] + 5;
-            continue;
-        } else if (stream_verify_name(targets[i]) &&
-                   pstream_verify_name(targets[i])) {
-            VLOG_WARN("target type \"%s\" is possibly erroneous", targets[i]);
-        }
-
-        connections[conns] = sbrec_connection_insert(ctx->txn);
-        sbrec_connection_set_target(connections[conns], targets[i]);
-        sbrec_connection_set_read_only(connections[conns], read_only);
-        sbrec_connection_set_role(connections[conns], role);
-        if (inactivity_probe) {
-            int64_t msecs = atoll(inactivity_probe);
-            sbrec_connection_set_inactivity_probe(connections[conns],
-                                                  &msecs, 1);
-        }
-        conns++;
-    }
-
-    /* Store uuids of new connection rows in 'connection' column. */
-    sbrec_sb_global_set_connections(sb_global, connections, conns);
-    free(connections);
-}
-
-static void
-cmd_set_connection(struct ctl_context *ctx)
-{
-    const size_t n = ctx->argc - 1;
-
-    verify_connections(ctx);
-    delete_connections(ctx);
-    insert_connections(ctx, &ctx->argv[1], n);
-}
-
-static void
-pre_cmd_get_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
-
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_private_key);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_certificate);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_ca_cert);
-    ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_bootstrap_ca_cert);
-}
-
-static void
-cmd_get_ssl(struct ctl_context *ctx)
-{
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
-
-    sbrec_sb_global_verify_ssl(sb_global);
-    if (ssl) {
-        sbrec_ssl_verify_private_key(ssl);
-        sbrec_ssl_verify_certificate(ssl);
-        sbrec_ssl_verify_ca_cert(ssl);
-        sbrec_ssl_verify_bootstrap_ca_cert(ssl);
-
-        ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
-        ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
-        ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
-        ds_put_format(&ctx->output, "Bootstrap: %s\n",
-                ssl->bootstrap_ca_cert ? "true" : "false");
-    }
-}
-
-static void
-pre_cmd_del_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
-}
-
-static void
-cmd_del_ssl(struct ctl_context *ctx)
-{
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
-
-    if (ssl) {
-        sbrec_sb_global_verify_ssl(sb_global);
-        sbrec_ssl_delete(ssl);
-        sbrec_sb_global_set_ssl(sb_global, NULL);
-    }
-}
-
-static void
-pre_cmd_set_ssl(struct ctl_context *ctx)
-{
-    ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
-}
-
-static void
-cmd_set_ssl(struct ctl_context *ctx)
-{
-    bool bootstrap = shash_find(&ctx->options, "--bootstrap");
-    const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
-    const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
-
-    sbrec_sb_global_verify_ssl(sb_global);
-    if (ssl) {
-        sbrec_ssl_delete(ssl);
-    }
-    ssl = sbrec_ssl_insert(ctx->txn);
-
-    sbrec_ssl_set_private_key(ssl, ctx->argv[1]);
-    sbrec_ssl_set_certificate(ssl, ctx->argv[2]);
-    sbrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
-
-    sbrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
-
-    if (ctx->argc == 5) {
-        sbrec_ssl_set_ssl_protocols(ssl, ctx->argv[4]);
-    } else if (ctx->argc == 6) {
-        sbrec_ssl_set_ssl_protocols(ssl, ctx->argv[4]);
-        sbrec_ssl_set_ssl_ciphers(ssl, ctx->argv[5]);
-    }
-
-    sbrec_sb_global_set_ssl(sb_global, ssl);
-}
-
-
-static const struct ctl_table_class tables[SBREC_N_TABLES] = {
-    [SBREC_TABLE_CHASSIS].row_ids[0] = {&sbrec_chassis_col_name, NULL, NULL},
-
-    [SBREC_TABLE_DATAPATH_BINDING].row_ids
-     = {{&sbrec_datapath_binding_col_external_ids, "name", NULL},
-        {&sbrec_datapath_binding_col_external_ids, "name2", NULL},
-        {&sbrec_datapath_binding_col_external_ids, "logical-switch", NULL},
-        {&sbrec_datapath_binding_col_external_ids, "logical-router", NULL}},
-
-    [SBREC_TABLE_PORT_BINDING].row_ids
-     = {{&sbrec_port_binding_col_logical_port, NULL, NULL},
-        {&sbrec_port_binding_col_external_ids, "name", NULL}},
-
-    [SBREC_TABLE_MAC_BINDING].row_ids[0] =
-    {&sbrec_mac_binding_col_logical_port, NULL, NULL},
-
-    [SBREC_TABLE_ADDRESS_SET].row_ids[0]
-    = {&sbrec_address_set_col_name, NULL, NULL},
-
-    [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
-    = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
-
-    [SBREC_TABLE_HA_CHASSIS].row_ids[0]
-    = {&sbrec_ha_chassis_col_chassis, NULL, NULL},
-};
-
-
-static void
-sbctl_context_init_command(struct sbctl_context *sbctl_ctx,
-                           struct ctl_command *command)
-{
-    ctl_context_init_command(&sbctl_ctx->base, command);
-}
-
-static void
-sbctl_context_init(struct sbctl_context *sbctl_ctx,
-                   struct ctl_command *command, struct ovsdb_idl *idl,
-                   struct ovsdb_idl_txn *txn,
-                   struct ovsdb_symbol_table *symtab)
-{
-    ctl_context_init(&sbctl_ctx->base, command, idl, txn, symtab,
-                     sbctl_context_invalidate_cache);
-    sbctl_ctx->cache_valid = false;
-}
-
-static void
-sbctl_context_done_command(struct sbctl_context *sbctl_ctx,
-                           struct ctl_command *command)
-{
-    ctl_context_done_command(&sbctl_ctx->base, command);
-}
-
-static void
-sbctl_context_done(struct sbctl_context *sbctl_ctx,
-                   struct ctl_command *command)
-{
-    ctl_context_done(&sbctl_ctx->base, command);
-}
-
-static void
-run_prerequisites(struct ctl_command *commands, size_t n_commands,
-                  struct ovsdb_idl *idl)
-{
-    ovsdb_idl_add_table(idl, &sbrec_table_sb_global);
-
-    for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) {
-        if (c->syntax->prerequisites) {
-            struct sbctl_context sbctl_ctx;
-
-            ds_init(&c->output);
-            c->table = NULL;
-
-            sbctl_context_init(&sbctl_ctx, c, idl, NULL, NULL);
-            (c->syntax->prerequisites)(&sbctl_ctx.base);
-            if (sbctl_ctx.base.error) {
-                ctl_fatal("%s", sbctl_ctx.base.error);
-            }
-            sbctl_context_done(&sbctl_ctx, c);
-
-            ovs_assert(!c->output.string);
-            ovs_assert(!c->table);
-        }
-    }
-}
-
-static bool
-do_sbctl(const char *args, struct ctl_command *commands, size_t n_commands,
-         struct ovsdb_idl *idl)
-{
-    struct ovsdb_idl_txn *txn;
-    enum ovsdb_idl_txn_status status;
-    struct ovsdb_symbol_table *symtab;
-    struct sbctl_context sbctl_ctx;
-    struct ctl_command *c;
-    struct shash_node *node;
-
-    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
-    if (dry_run) {
-        ovsdb_idl_txn_set_dry_run(txn);
-    }
-
-    ovsdb_idl_txn_add_comment(txn, "ovs-sbctl: %s", args);
-
-    const struct sbrec_sb_global *sb = sbrec_sb_global_first(idl);
-    if (!sb) {
-        /* XXX add verification that table is empty */
-        sbrec_sb_global_insert(txn);
-    }
-
-    symtab = ovsdb_symbol_table_create();
-    for (c = commands; c < &commands[n_commands]; c++) {
-        ds_init(&c->output);
-        c->table = NULL;
-    }
-    sbctl_context_init(&sbctl_ctx, NULL, idl, txn, symtab);
-    for (c = commands; c < &commands[n_commands]; c++) {
-        sbctl_context_init_command(&sbctl_ctx, c);
-        if (c->syntax->run) {
-            (c->syntax->run)(&sbctl_ctx.base);
-        }
-        if (sbctl_ctx.base.error) {
-            ctl_fatal("%s", sbctl_ctx.base.error);
-        }
-        sbctl_context_done_command(&sbctl_ctx, c);
-
-        if (sbctl_ctx.base.try_again) {
-            sbctl_context_done(&sbctl_ctx, NULL);
-            goto try_again;
-        }
-    }
-    sbctl_context_done(&sbctl_ctx, NULL);
-
-    SHASH_FOR_EACH (node, &symtab->sh) {
-        struct ovsdb_symbol *symbol = node->data;
-        if (!symbol->created) {
-            ctl_fatal("row id \"%s\" is referenced but never created (e.g. "
-                      "with \"-- --id=%s create ...\")",
-                      node->name, node->name);
-        }
-        if (!symbol->strong_ref) {
-            if (!symbol->weak_ref) {
-                VLOG_WARN("row id \"%s\" was created but no reference to it "
-                          "was inserted, so it will not actually appear in "
-                          "the database", node->name);
-            } else {
-                VLOG_WARN("row id \"%s\" was created but only a weak "
-                          "reference to it was inserted, so it will not "
-                          "actually appear in the database", node->name);
-            }
-        }
-    }
-
-    status = ovsdb_idl_txn_commit_block(txn);
-    if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
-        for (c = commands; c < &commands[n_commands]; c++) {
-            if (c->syntax->postprocess) {
-                sbctl_context_init(&sbctl_ctx, c, idl, txn, symtab);
-                (c->syntax->postprocess)(&sbctl_ctx.base);
-                if (sbctl_ctx.base.error) {
-                    ctl_fatal("%s", sbctl_ctx.base.error);
-                }
-                sbctl_context_done(&sbctl_ctx, c);
-            }
-        }
-    }
-
-    switch (status) {
-    case TXN_UNCOMMITTED:
-    case TXN_INCOMPLETE:
-        OVS_NOT_REACHED();
-
-    case TXN_ABORTED:
-        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
-        ctl_fatal("transaction aborted");
-
-    case TXN_UNCHANGED:
-    case TXN_SUCCESS:
-        break;
-
-    case TXN_TRY_AGAIN:
-        goto try_again;
-
-    case TXN_ERROR:
-        ctl_fatal("transaction error: %s", ovsdb_idl_txn_get_error(txn));
-
-    case TXN_NOT_LOCKED:
-        /* Should not happen--we never call ovsdb_idl_set_lock(). */
-        ctl_fatal("database not locked");
-
-    default:
-        OVS_NOT_REACHED();
-    }
-
-    ovsdb_symbol_table_destroy(symtab);
-
-    for (c = commands; c < &commands[n_commands]; c++) {
-        struct ds *ds = &c->output;
-
-        if (c->table) {
-            table_print(c->table, &table_style);
-        } else if (oneline) {
-            size_t j;
-
-            ds_chomp(ds, '\n');
-            for (j = 0; j < ds->length; j++) {
-                int ch = ds->string[j];
-                switch (ch) {
-                case '\n':
-                    fputs("\\n", stdout);
-                    break;
-
-                case '\\':
-                    fputs("\\\\", stdout);
-                    break;
-
-                default:
-                    putchar(ch);
-                }
-            }
-            putchar('\n');
-        } else {
-            fputs(ds_cstr(ds), stdout);
-        }
-        ds_destroy(&c->output);
-        table_destroy(c->table);
-        free(c->table);
-
-        shash_destroy_free_data(&c->options);
-    }
-    free(commands);
-    ovsdb_idl_txn_destroy(txn);
-    ovsdb_idl_destroy(idl);
-
-    return true;
-
-try_again:
-    /* Our transaction needs to be rerun, or a prerequisite was not met.  Free
-     * resources and return so that the caller can try again. */
-    ovsdb_idl_txn_abort(txn);
-    ovsdb_idl_txn_destroy(txn);
-    the_idl_txn = NULL;
-
-    ovsdb_symbol_table_destroy(symtab);
-    for (c = commands; c < &commands[n_commands]; c++) {
-        ds_destroy(&c->output);
-        table_destroy(c->table);
-        free(c->table);
-    }
-    return false;
-}
-
-/* Frees the current transaction and the underlying IDL and then calls
- * exit(status).
- *
- * Freeing the transaction and the IDL is not strictly necessary, but it makes
- * for a clean memory leak report from valgrind in the normal case.  That makes
- * it easier to notice real memory leaks. */
-static void
-sbctl_exit(int status)
-{
-    if (the_idl_txn) {
-        ovsdb_idl_txn_abort(the_idl_txn);
-        ovsdb_idl_txn_destroy(the_idl_txn);
-    }
-    ovsdb_idl_destroy(the_idl);
-    exit(status);
-}
-
-static const struct ctl_command_syntax sbctl_commands[] = {
-    { "init", 0, 0, "", NULL, sbctl_init, NULL, "", RW },
-
-    /* Chassis commands. */
-    {"chassis-add", 3, 3, "CHASSIS ENCAP-TYPE ENCAP-IP", pre_get_info,
-     cmd_chassis_add, NULL, "--may-exist", RW},
-    {"chassis-del", 1, 1, "CHASSIS", pre_get_info, cmd_chassis_del, NULL,
-     "--if-exists", RW},
-
-    /* Port binding commands. */
-    {"lsp-bind", 2, 2, "PORT CHASSIS", pre_get_info, cmd_lsp_bind, NULL,
-     "--may-exist", RW},
-    {"lsp-unbind", 1, 1, "PORT", pre_get_info, cmd_lsp_unbind, NULL,
-     "--if-exists", RW},
-
-    /* Logical flow commands */
-    {"lflow-list", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
-     pre_get_info, cmd_lflow_list, NULL,
-     "--uuid,--ovs?,--stats", RO},
-    {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
-     pre_get_info, cmd_lflow_list, NULL,
-     "--uuid,--ovs?,--stats", RO}, /* Friendly alias for lflow-list */
-
-    /* IP multicast commands. */
-    {"ip-multicast-flush", 0, 1, "SWITCH",
-     pre_get_info, sbctl_ip_mcast_flush, NULL, "", RW },
-
-    /* Connection commands. */
-    {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "", RO},
-    {"del-connection", 0, 0, "", pre_connection, cmd_del_connection, NULL, "", RW},
-    {"set-connection", 1, INT_MAX, "TARGET...", pre_connection, cmd_set_connection,
-     NULL, "--inactivity-probe=", RW},
-
-    /* SSL commands. */
-    {"get-ssl", 0, 0, "", pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO},
-    {"del-ssl", 0, 0, "", pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW},
-    {"set-ssl", 3, 5,
-        "PRIVATE-KEY CERTIFICATE CA-CERT [SSL-PROTOS [SSL-CIPHERS]]",
-        pre_cmd_set_ssl, cmd_set_ssl, NULL, "--bootstrap", RW},
-
-    {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
-};
-
-/* Registers sbctl and common db commands. */
-static void
-sbctl_cmd_init(void)
-{
-    ctl_init(&sbrec_idl_class, sbrec_table_classes, tables,
-             cmd_show_tables, sbctl_exit);
-    ctl_register_commands(sbctl_commands);
-}
diff --git a/rhel/.gitignore b/rhel/.gitignore
index e6f72859f32e..a9c047f83e25 100644
--- a/rhel/.gitignore
+++ b/rhel/.gitignore
@@ -4,5 +4,4 @@ kmod-openvswitch-rhel6.spec
 openvswitch-kmod-fedora.spec
 openvswitch.spec
 openvswitch-fedora.spec
-ovn-fedora.spec
 usr_lib_systemd_system_ovs-vswitchd.service
diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
index fc113c9f1642..229bb22148ec 100644
--- a/rhel/openvswitch-fedora.spec.in
+++ b/rhel/openvswitch-fedora.spec.in
@@ -272,17 +272,6 @@ rm -f $RPM_BUILD_ROOT%{_bindir}/ovs-parse-backtrace \
         $RPM_BUILD_ROOT%{_sbindir}/ovs-vlan-bug-workaround \
         $RPM_BUILD_ROOT%{_mandir}/man8/ovs-vlan-bug-workaround.8
 
-# remove ovn unpackages files
-rm -f $RPM_BUILD_ROOT%{_bindir}/ovn*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovn*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovn*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovn*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovn*
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/ovn*
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovn*
-rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
-rm -f $RPM_BUILD_ROOT%{_libdir}/libovn*
-
 %check
 %if %{with check}
     touch resolv.conf
diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
index 7f27fedd6886..27935f81ed92 100644
--- a/rhel/openvswitch.spec.in
+++ b/rhel/openvswitch.spec.in
@@ -101,11 +101,7 @@ rm \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-test.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-l3ping.8 \
     $RPM_BUILD_ROOT/usr/sbin/ovs-vlan-bug-workaround \
-    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-vlan-bug-workaround.8 \
-    $RPM_BUILD_ROOT/usr/bin/ovn-* \
-    $RPM_BUILD_ROOT/usr/share/man/man?/ovn-* \
-    $RPM_BUILD_ROOT/usr/share/openvswitch/ovn-* \
-    $RPM_BUILD_ROOT/usr/share/openvswitch/scripts/ovn*
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-vlan-bug-workaround.8
 (cd "$RPM_BUILD_ROOT" && rm -rf usr/%{_lib}/*.la)
 (cd "$RPM_BUILD_ROOT" && rm -rf usr/include)
 
diff --git a/tests/automake.mk b/tests/automake.mk
index 0442334ecb78..9dd07a2d9b17 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -187,7 +187,7 @@ SYSTEM_DPDK_TESTSUITE = $(srcdir)/tests/system-dpdk-testsuite
 OVSDB_CLUSTER_TESTSUITE = $(srcdir)/tests/ovsdb-cluster-testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 
-AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):ovn/utilities
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR)
 
 check-local:
 	set $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH); \
@@ -228,8 +228,6 @@ check-lcov: all $(check_DATA) clean-lcov
 # valgrind support
 
 valgrind_wrappers = \
-	tests/valgrind/ovn-nbctl \
-	tests/valgrind/ovn-sbctl \
 	tests/valgrind/ovs-appctl \
 	tests/valgrind/ovs-ofctl \
 	tests/valgrind/ovs-vsctl \
