[ovs-dev] [PATCH v4] Remove OVN.

Mark Michelson mmichels at redhat.com
Mon Jul 29 19:38:57 UTC 2019


OVN is separated into its own repo. This commit removes the OVN source,
OVN tests, and OVN documentation. It also removes mentions of OVN from
most documentation. The only place where OVN has been left is in
changelogs/NEWS, since we shouldn't mess with the history of the
project.

Signed-off-by: Mark Michelson <mmichels at redhat.com>
---
v3 -> v4 Fixed 0-day Robot problems with NEWS and documentation
v2 -> v3 Added NEWS entry for removal of OVN
v1 -> v2 Rebase
---
 Documentation/automake.mk                          |    12 -
 Documentation/faq/index.rst                        |     1 -
 Documentation/faq/ovn.rst                          |    90 -
 Documentation/howto/docker.rst                     |   326 -
 Documentation/howto/firewalld.rst                  |   107 -
 Documentation/howto/index.rst                      |     9 -
 Documentation/howto/openstack-containers.rst       |   135 -
 Documentation/index.rst                            |    20 +-
 Documentation/intro/install/fedora.rst             |    12 -
 Documentation/intro/install/index.rst              |     8 -
 Documentation/intro/install/ovn-upgrades.rst       |   115 -
 Documentation/ref/ovs-sim.1.rst                    |   100 -
 Documentation/topics/high-availability.rst         |   440 -
 Documentation/topics/index.rst                     |    18 +-
 Documentation/topics/ovn-news-2.8.rst              |   278 -
 Documentation/topics/role-based-access-control.rst |   101 -
 Documentation/tutorials/index.rst                  |     7 +-
 Documentation/tutorials/ovn-ipsec.rst              |   146 -
 Documentation/tutorials/ovn-openstack.rst          |  1922 ---
 Documentation/tutorials/ovn-rbac.rst               |   134 -
 Documentation/tutorials/ovn-sandbox.rst            |   177 -
 Makefile.am                                        |     1 -
 NEWS                                               |     5 +-
 configure.ac                                       |     3 -
 debian/.gitignore                                  |     5 -
 debian/automake.mk                                 |    22 -
 debian/control                                     |    78 -
 debian/ovn-central.dirs                            |     1 -
 debian/ovn-central.init                            |    60 -
 debian/ovn-central.install                         |     3 -
 debian/ovn-central.manpages                        |     1 -
 debian/ovn-central.postinst                        |    49 -
 debian/ovn-central.postrm                          |    48 -
 debian/ovn-central.template                        |     5 -
 debian/ovn-common.install                          |     7 -
 debian/ovn-common.manpages                         |     8 -
 debian/ovn-common.postinst                         |    24 -
 debian/ovn-common.postrm                           |    23 -
 debian/ovn-controller-vtep.init                    |    54 -
 debian/ovn-controller-vtep.install                 |     1 -
 debian/ovn-controller-vtep.manpages                |     1 -
 debian/ovn-docker.install                          |     2 -
 debian/ovn-host.dirs                               |     1 -
 debian/ovn-host.init                               |    54 -
 debian/ovn-host.install                            |     1 -
 debian/ovn-host.manpages                           |     1 -
 debian/ovn-host.postinst                           |    49 -
 debian/ovn-host.postrm                             |    44 -
 debian/ovn-host.template                           |     5 -
 debian/rules                                       |     6 -
 include/automake.mk                                |     1 -
 include/ovn/actions.h                              |   622 -
 include/ovn/automake.mk                            |     6 -
 include/ovn/expr.h                                 |   518 -
 include/ovn/lex.h                                  |   152 -
 include/ovn/logical-fields.h                       |   130 -
 lib/db-ctl-base.xml                                |    12 +-
 manpages.mk                                        |    28 -
 ovn/.gitignore                                     |     8 -
 ovn/TODO.rst                                       |   147 -
 ovn/automake.mk                                    |    92 -
 ovn/controller-vtep/.gitignore                     |     2 -
 ovn/controller-vtep/automake.mk                    |    14 -
 ovn/controller-vtep/binding.c                      |   274 -
 ovn/controller-vtep/binding.h                      |    27 -
 ovn/controller-vtep/gateway.c                      |   230 -
 ovn/controller-vtep/gateway.h                      |    26 -
 ovn/controller-vtep/ovn-controller-vtep.8.xml      |    80 -
 ovn/controller-vtep/ovn-controller-vtep.c          |   272 -
 ovn/controller-vtep/ovn-controller-vtep.h          |    51 -
 ovn/controller-vtep/vtep.c                         |   600 -
 ovn/controller-vtep/vtep.h                         |    27 -
 ovn/controller/.gitignore                          |     2 -
 ovn/controller/automake.mk                         |    32 -
 ovn/controller/bfd.c                               |   268 -
 ovn/controller/bfd.h                               |    41 -
 ovn/controller/binding.c                           |   764 -
 ovn/controller/binding.h                           |    57 -
 ovn/controller/chassis.c                           |   671 -
 ovn/controller/chassis.h                           |    46 -
 ovn/controller/encaps.c                            |   409 -
 ovn/controller/encaps.h                            |    48 -
 ovn/controller/ha-chassis.c                        |   203 -
 ovn/controller/ha-chassis.h                        |    50 -
 ovn/controller/ip-mcast.c                          |   164 -
 ovn/controller/ip-mcast.h                          |    52 -
 ovn/controller/lflow.c                             |   898 --
 ovn/controller/lflow.h                             |   184 -
 ovn/controller/lport.c                             |   102 -
 ovn/controller/lport.h                             |    52 -
 ovn/controller/ofctrl.c                            |  1393 --
 ovn/controller/ofctrl.h                            |    87 -
 ovn/controller/ovn-controller.8.xml                |   456 -
 ovn/controller/ovn-controller.c                    |  2366 ---
 ovn/controller/ovn-controller.h                    |    85 -
 ovn/controller/patch.c                             |   273 -
 ovn/controller/patch.h                             |    42 -
 ovn/controller/physical.c                          |  1459 --
 ovn/controller/physical.h                          |    74 -
 ovn/controller/pinctrl.c                           |  4343 ------
 ovn/controller/pinctrl.h                           |    51 -
 ovn/lib/.gitignore                                 |     7 -
 ovn/lib/acl-log.c                                  |   105 -
 ovn/lib/acl-log.h                                  |    54 -
 ovn/lib/actions.c                                  |  2902 ----
 ovn/lib/automake.mk                                |    57 -
 ovn/lib/chassis-index.c                            |    67 -
 ovn/lib/chassis-index.h                            |    30 -
 ovn/lib/expr.c                                     |  3450 -----
 ovn/lib/extend-table.c                             |   208 -
 ovn/lib/extend-table.h                             |    82 -
 ovn/lib/inc-proc-eng.c                             |   201 -
 ovn/lib/inc-proc-eng.h                             |   234 -
 ovn/lib/ip-mcast-index.c                           |    40 -
 ovn/lib/ip-mcast-index.h                           |    36 -
 ovn/lib/lex.c                                      |  1023 --
 ovn/lib/libovn.sym.in                              |     4 -
 ovn/lib/logical-fields.c                           |   261 -
 ovn/lib/mcast-group-index.c                        |    43 -
 ovn/lib/mcast-group-index.h                        |    32 -
 ovn/lib/ovn-l7.h                                   |   322 -
 ovn/lib/ovn-nb-idl.ann                             |     9 -
 ovn/lib/ovn-sb-idl.ann                             |    29 -
 ovn/lib/ovn-util.c                                 |   373 -
 ovn/lib/ovn-util.h                                 |    84 -
 ovn/northd/.gitignore                              |     2 -
 ovn/northd/automake.mk                             |    10 -
 ovn/northd/ovn-northd.8.xml                        |  2544 ----
 ovn/northd/ovn-northd.c                            |  9447 ------------
 ovn/ovn-architecture.7.xml                         |  2074 ---
 ovn/ovn-nb.ovsschema                               |   449 -
 ovn/ovn-nb.xml                                     |  2917 ----
 ovn/ovn-sb.ovsschema                               |   404 -
 ovn/ovn-sb.xml                                     |  3638 -----
 ovn/utilities/.gitignore                           |    11 -
 ovn/utilities/automake.mk                          |    57 -
 ovn/utilities/bugtool/automake.mk                  |     9 -
 ovn/utilities/bugtool/ovn-bugtool-nbctl-show       |    19 -
 ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list |    19 -
 ovn/utilities/bugtool/ovn-bugtool-sbctl-show       |    19 -
 .../bugtool/plugins/network-status/ovn.xml         |    23 -
 ovn/utilities/ovn-ctl                              |   822 --
 ovn/utilities/ovn-ctl.8.xml                        |   215 -
 ovn/utilities/ovn-detrace.1.in                     |    38 -
 ovn/utilities/ovn-detrace.in                       |   215 -
 ovn/utilities/ovn-docker-overlay-driver.in         |   442 -
 ovn/utilities/ovn-docker-underlay-driver.in        |   677 -
 ovn/utilities/ovn-nbctl.8.xml                      |  1228 --
 ovn/utilities/ovn-nbctl.c                          |  6061 --------
 ovn/utilities/ovn-sbctl.8.in                       |   303 -
 ovn/utilities/ovn-sbctl.c                          |  1541 --
 ovn/utilities/ovn-trace.8.xml                      |   485 -
 ovn/utilities/ovn-trace.c                          |  2373 ---
 ovn/utilities/ovndb-servers.ocf                    |   642 -
 ovsdb/ovsdb-tool.1.in                              |    23 +-
 rhel/automake.mk                                   |    16 +-
 rhel/ovn-fedora.spec.in                            |   432 -
 ...walld_services_ovn-central-firewall-service.xml |     7 -
 ...irewalld_services_ovn-host-firewall-service.xml |     6 -
 ..._lib_systemd_system_ovn-controller-vtep.service |    50 -
 rhel/usr_lib_systemd_system_ovn-controller.service |    34 -
 rhel/usr_lib_systemd_system_ovn-northd.service     |    35 -
 tests/atlocal.in                                   |     4 -
 tests/automake.mk                                  |    46 +-
 tests/ofproto-macros.at                            |     2 +-
 tests/oss-fuzz/automake.mk                         |    10 -
 tests/oss-fuzz/config/expr.dict                    |   120 -
 tests/oss-fuzz/config/expr_parse_target.options    |     3 -
 tests/oss-fuzz/expr_parse_target.c                 |   464 -
 tests/ovn-controller-vtep.at                       |   467 -
 tests/ovn-controller.at                            |   294 -
 tests/ovn-macros.at                                |   180 -
 tests/ovn-nbctl.at                                 |  1660 ---
 tests/ovn-northd.at                                |   900 --
 tests/ovn-performance.at                           |   424 -
 tests/ovn-sbctl.at                                 |   150 -
 tests/ovn.at                                       | 14702 -------------------
 tests/ovsdb-cluster-testsuite.at                   |    10 -
 tests/ovsdb-cluster.at                             |   450 -
 tests/system-kmod-testsuite.at                     |     2 -
 tests/system-ovn.at                                |  1663 ---
 tests/system-userspace-testsuite.at                |     2 -
 tests/test-ovn.c                                   |  1584 --
 tests/testsuite.at                                 |     8 -
 tutorial/automake.mk                               |     3 +-
 tutorial/ovn-setup.sh                              |    37 -
 tutorial/ovs-sandbox                               |   261 -
 utilities/bugtool/automake.mk                      |     9 +-
 utilities/ovs-sim.in                               |   237 +-
 xenserver/openvswitch-xen.spec.in                  |     7 -
 190 files changed, 43 insertions(+), 93307 deletions(-)
 delete mode 100644 Documentation/faq/ovn.rst
 delete mode 100644 Documentation/howto/docker.rst
 delete mode 100644 Documentation/howto/firewalld.rst
 delete mode 100644 Documentation/howto/openstack-containers.rst
 delete mode 100644 Documentation/intro/install/ovn-upgrades.rst
 delete mode 100644 Documentation/topics/high-availability.rst
 delete mode 100644 Documentation/topics/ovn-news-2.8.rst
 delete mode 100644 Documentation/topics/role-based-access-control.rst
 delete mode 100644 Documentation/tutorials/ovn-ipsec.rst
 delete mode 100644 Documentation/tutorials/ovn-openstack.rst
 delete mode 100644 Documentation/tutorials/ovn-rbac.rst
 delete mode 100644 Documentation/tutorials/ovn-sandbox.rst
 delete mode 100644 debian/ovn-central.dirs
 delete mode 100755 debian/ovn-central.init
 delete mode 100644 debian/ovn-central.install
 delete mode 100644 debian/ovn-central.manpages
 delete mode 100755 debian/ovn-central.postinst
 delete mode 100755 debian/ovn-central.postrm
 delete mode 100644 debian/ovn-central.template
 delete mode 100644 debian/ovn-common.install
 delete mode 100644 debian/ovn-common.manpages
 delete mode 100644 debian/ovn-common.postinst
 delete mode 100644 debian/ovn-common.postrm
 delete mode 100755 debian/ovn-controller-vtep.init
 delete mode 100644 debian/ovn-controller-vtep.install
 delete mode 100644 debian/ovn-controller-vtep.manpages
 delete mode 100644 debian/ovn-docker.install
 delete mode 100644 debian/ovn-host.dirs
 delete mode 100755 debian/ovn-host.init
 delete mode 100644 debian/ovn-host.install
 delete mode 100644 debian/ovn-host.manpages
 delete mode 100755 debian/ovn-host.postinst
 delete mode 100755 debian/ovn-host.postrm
 delete mode 100644 debian/ovn-host.template
 delete mode 100644 include/ovn/actions.h
 delete mode 100644 include/ovn/automake.mk
 delete mode 100644 include/ovn/expr.h
 delete mode 100644 include/ovn/lex.h
 delete mode 100644 include/ovn/logical-fields.h
 delete mode 100644 ovn/.gitignore
 delete mode 100644 ovn/TODO.rst
 delete mode 100644 ovn/automake.mk
 delete mode 100644 ovn/controller-vtep/.gitignore
 delete mode 100644 ovn/controller-vtep/automake.mk
 delete mode 100644 ovn/controller-vtep/binding.c
 delete mode 100644 ovn/controller-vtep/binding.h
 delete mode 100644 ovn/controller-vtep/gateway.c
 delete mode 100644 ovn/controller-vtep/gateway.h
 delete mode 100644 ovn/controller-vtep/ovn-controller-vtep.8.xml
 delete mode 100644 ovn/controller-vtep/ovn-controller-vtep.c
 delete mode 100644 ovn/controller-vtep/ovn-controller-vtep.h
 delete mode 100644 ovn/controller-vtep/vtep.c
 delete mode 100644 ovn/controller-vtep/vtep.h
 delete mode 100644 ovn/controller/.gitignore
 delete mode 100644 ovn/controller/automake.mk
 delete mode 100644 ovn/controller/bfd.c
 delete mode 100644 ovn/controller/bfd.h
 delete mode 100644 ovn/controller/binding.c
 delete mode 100644 ovn/controller/binding.h
 delete mode 100644 ovn/controller/chassis.c
 delete mode 100644 ovn/controller/chassis.h
 delete mode 100644 ovn/controller/encaps.c
 delete mode 100644 ovn/controller/encaps.h
 delete mode 100644 ovn/controller/ha-chassis.c
 delete mode 100644 ovn/controller/ha-chassis.h
 delete mode 100644 ovn/controller/ip-mcast.c
 delete mode 100644 ovn/controller/ip-mcast.h
 delete mode 100644 ovn/controller/lflow.c
 delete mode 100644 ovn/controller/lflow.h
 delete mode 100644 ovn/controller/lport.c
 delete mode 100644 ovn/controller/lport.h
 delete mode 100644 ovn/controller/ofctrl.c
 delete mode 100644 ovn/controller/ofctrl.h
 delete mode 100644 ovn/controller/ovn-controller.8.xml
 delete mode 100644 ovn/controller/ovn-controller.c
 delete mode 100644 ovn/controller/ovn-controller.h
 delete mode 100644 ovn/controller/patch.c
 delete mode 100644 ovn/controller/patch.h
 delete mode 100644 ovn/controller/physical.c
 delete mode 100644 ovn/controller/physical.h
 delete mode 100644 ovn/controller/pinctrl.c
 delete mode 100644 ovn/controller/pinctrl.h
 delete mode 100644 ovn/lib/.gitignore
 delete mode 100644 ovn/lib/acl-log.c
 delete mode 100644 ovn/lib/acl-log.h
 delete mode 100644 ovn/lib/actions.c
 delete mode 100644 ovn/lib/automake.mk
 delete mode 100644 ovn/lib/chassis-index.c
 delete mode 100644 ovn/lib/chassis-index.h
 delete mode 100644 ovn/lib/expr.c
 delete mode 100644 ovn/lib/extend-table.c
 delete mode 100644 ovn/lib/extend-table.h
 delete mode 100644 ovn/lib/inc-proc-eng.c
 delete mode 100644 ovn/lib/inc-proc-eng.h
 delete mode 100644 ovn/lib/ip-mcast-index.c
 delete mode 100644 ovn/lib/ip-mcast-index.h
 delete mode 100644 ovn/lib/lex.c
 delete mode 100644 ovn/lib/libovn.sym.in
 delete mode 100644 ovn/lib/logical-fields.c
 delete mode 100644 ovn/lib/mcast-group-index.c
 delete mode 100644 ovn/lib/mcast-group-index.h
 delete mode 100644 ovn/lib/ovn-l7.h
 delete mode 100644 ovn/lib/ovn-nb-idl.ann
 delete mode 100644 ovn/lib/ovn-sb-idl.ann
 delete mode 100644 ovn/lib/ovn-util.c
 delete mode 100644 ovn/lib/ovn-util.h
 delete mode 100644 ovn/northd/.gitignore
 delete mode 100644 ovn/northd/automake.mk
 delete mode 100644 ovn/northd/ovn-northd.8.xml
 delete mode 100644 ovn/northd/ovn-northd.c
 delete mode 100644 ovn/ovn-architecture.7.xml
 delete mode 100644 ovn/ovn-nb.ovsschema
 delete mode 100644 ovn/ovn-nb.xml
 delete mode 100644 ovn/ovn-sb.ovsschema
 delete mode 100644 ovn/ovn-sb.xml
 delete mode 100644 ovn/utilities/.gitignore
 delete mode 100644 ovn/utilities/automake.mk
 delete mode 100644 ovn/utilities/bugtool/automake.mk
 delete mode 100644 ovn/utilities/bugtool/ovn-bugtool-nbctl-show
 delete mode 100644 ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list
 delete mode 100644 ovn/utilities/bugtool/ovn-bugtool-sbctl-show
 delete mode 100644 ovn/utilities/bugtool/plugins/network-status/ovn.xml
 delete mode 100755 ovn/utilities/ovn-ctl
 delete mode 100644 ovn/utilities/ovn-ctl.8.xml
 delete mode 100644 ovn/utilities/ovn-detrace.1.in
 delete mode 100755 ovn/utilities/ovn-detrace.in
 delete mode 100755 ovn/utilities/ovn-docker-overlay-driver.in
 delete mode 100755 ovn/utilities/ovn-docker-underlay-driver.in
 delete mode 100644 ovn/utilities/ovn-nbctl.8.xml
 delete mode 100644 ovn/utilities/ovn-nbctl.c
 delete mode 100644 ovn/utilities/ovn-sbctl.8.in
 delete mode 100644 ovn/utilities/ovn-sbctl.c
 delete mode 100644 ovn/utilities/ovn-trace.8.xml
 delete mode 100644 ovn/utilities/ovn-trace.c
 delete mode 100755 ovn/utilities/ovndb-servers.ocf
 delete mode 100644 rhel/ovn-fedora.spec.in
 delete mode 100644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml
 delete mode 100644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml
 delete mode 100644 rhel/usr_lib_systemd_system_ovn-controller-vtep.service
 delete mode 100644 rhel/usr_lib_systemd_system_ovn-controller.service
 delete mode 100644 rhel/usr_lib_systemd_system_ovn-northd.service
 delete mode 100644 tests/oss-fuzz/config/expr.dict
 delete mode 100644 tests/oss-fuzz/config/expr_parse_target.options
 delete mode 100644 tests/oss-fuzz/expr_parse_target.c
 delete mode 100644 tests/ovn-controller-vtep.at
 delete mode 100644 tests/ovn-controller.at
 delete mode 100644 tests/ovn-macros.at
 delete mode 100644 tests/ovn-nbctl.at
 delete mode 100644 tests/ovn-northd.at
 delete mode 100644 tests/ovn-performance.at
 delete mode 100644 tests/ovn-sbctl.at
 delete mode 100644 tests/ovn.at
 delete mode 100644 tests/ovsdb-cluster-testsuite.at
 delete mode 100644 tests/ovsdb-cluster.at
 delete mode 100644 tests/system-ovn.at
 delete mode 100644 tests/test-ovn.c
 delete mode 100755 tutorial/ovn-setup.sh

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 2a3214a3c..cd68f3b15 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -18,7 +18,6 @@ DOC_SOURCE = \
 	Documentation/intro/install/fedora.rst \
 	Documentation/intro/install/general.rst \
 	Documentation/intro/install/netbsd.rst \
-	Documentation/intro/install/ovn-upgrades.rst \
 	Documentation/intro/install/rhel.rst \
 	Documentation/intro/install/userspace.rst \
 	Documentation/intro/install/windows.rst \
@@ -26,12 +25,8 @@ DOC_SOURCE = \
 	Documentation/tutorials/index.rst \
 	Documentation/tutorials/faucet.rst \
 	Documentation/tutorials/ovs-advanced.rst \
-	Documentation/tutorials/ovn-openstack.rst \
-	Documentation/tutorials/ovn-sandbox.rst \
 	Documentation/tutorials/ovs-conntrack.rst \
 	Documentation/tutorials/ipsec.rst \
-	Documentation/tutorials/ovn-ipsec.rst \
-	Documentation/tutorials/ovn-rbac.rst \
 	Documentation/topics/index.rst \
 	Documentation/topics/bonding.rst \
 	Documentation/topics/idl-compound-indexes.rst \
@@ -54,28 +49,22 @@ DOC_SOURCE = \
 	Documentation/topics/fuzzing/ovs-fuzzers.rst \
 	Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
 	Documentation/topics/testing.rst \
-	Documentation/topics/high-availability.rst \
 	Documentation/topics/integration.rst \
 	Documentation/topics/language-bindings.rst \
 	Documentation/topics/networking-namespaces.rst \
 	Documentation/topics/openflow.rst \
-	Documentation/topics/ovn-news-2.8.rst \
 	Documentation/topics/ovsdb-replication.rst \
 	Documentation/topics/porting.rst \
-	Documentation/topics/role-based-access-control.rst \
 	Documentation/topics/tracing.rst \
 	Documentation/topics/windows.rst \
 	Documentation/howto/index.rst \
-	Documentation/howto/docker.rst \
 	Documentation/howto/dpdk.rst \
-	Documentation/howto/firewalld.rst \
 	Documentation/howto/ipsec.rst \
 	Documentation/howto/kvm.rst \
 	Documentation/howto/libvirt.rst \
 	Documentation/howto/selinux.rst \
 	Documentation/howto/ssl.rst \
 	Documentation/howto/lisp.rst \
-	Documentation/howto/openstack-containers.rst \
 	Documentation/howto/qos.png \
 	Documentation/howto/qos.rst \
 	Documentation/howto/sflow.png \
@@ -94,7 +83,6 @@ DOC_SOURCE = \
 	Documentation/faq/general.rst \
 	Documentation/faq/issues.rst \
 	Documentation/faq/openflow.rst \
-	Documentation/faq/ovn.rst \
 	Documentation/faq/qos.rst \
 	Documentation/faq/releases.rst \
 	Documentation/faq/terminology.rst \
diff --git a/Documentation/faq/index.rst b/Documentation/faq/index.rst
index ad3cc2b6f..334b828b2 100644
--- a/Documentation/faq/index.rst
+++ b/Documentation/faq/index.rst
@@ -41,4 +41,3 @@ Open vSwitch FAQ
    terminology
    vlan
    vxlan
-   ovn
diff --git a/Documentation/faq/ovn.rst b/Documentation/faq/ovn.rst
deleted file mode 100644
index 4d96b4aa5..000000000
--- a/Documentation/faq/ovn.rst
+++ /dev/null
@@ -1,90 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-===
-OVN
-===
-
-Q: Why does OVN use STT and Geneve instead of VLANs or VXLAN (or GRE)?
-
-    A: OVN implements a fairly sophisticated packet processing pipeline in
-    "logical datapaths" that can implement switching or routing functionality.
-    A logical datapath has an ingress pipeline and an egress pipeline, and each
-    of these pipelines can include logic based on packet fields as well as
-    packet metadata such as the logical ingress and egress ports (the latter
-    only in the egress pipeline).
-
-    The processing for a logical datapath can be split across hypervisors.  In
-    particular, when a logical ingress pipeline executes an "output" action,
-    OVN passes the packet to the egress pipeline on the hypervisor (or, in the
-    case of output to a logical multicast group, hypervisors) on which the
-    logical egress port is located.  If this hypervisor is not the same as the
-    ingress hypervisor, then the packet has to be transmitted across a physical
-    network.
-
-    This situation is where tunneling comes in.  To send the packet to another
-    hypervisor, OVN encapsulates it with a tunnel protocol and sends the
-    encapsulated packet across the physical network.  When the remote
-    hypervisor receives the tunnel packet, it decapsulates it and passes it
-    through the logical egress pipeline.  To do so, it also needs the metadata,
-    that is, the logical ingress and egress ports.
-
-    Thus, to implement OVN logical packet processing, at least the following
-    metadata must pass across the physical network:
-
-    * Logical datapath ID, a 24-bit identifier.  In Geneve, OVN uses the VNI to
-      hold the logical datapath ID; in STT, OVN uses 24 bits of STT's 64-bit
-      context ID.
-
-    * Logical ingress port, a 15-bit identifier.  In Geneve, OVN uses an option
-      to hold the logical ingress port; in STT, 15 bits of the context ID.
-
-    * Logical egress port, a 16-bit identifier.  In Geneve, OVN uses an option
-      to hold the logical egress port; in STT, 16 bits of the context ID.
-
-    See ``ovn-architecture(7)``, under "Tunnel Encapsulations", for details.
-
-    Together, these metadata require 24 + 15 + 16 = 55 bits.  GRE provides 32
-    bits, VXLAN provides 24, and VLAN only provides 12.  Most notably, if
-    logical egress pipelines do not match on the logical ingress port, thereby
-    restricting the class of ACLs available to users, then this eliminates 15
-    bits, bringing the requirement down to 40 bits.  At this point, one can
-    choose to limit the size of the OVN logical network in various ways, e.g.:
-
-    * 16 bits of logical datapaths + 16 bits of logical egress ports.  This
-      combination fits within a 32-bit GRE tunnel key.
-
-    * 12 bits of logical datapaths + 12 bits of logical egress ports.  This
-      combination fits within a 24-bit VXLAN VNI.
-
-    * It's difficult to identify an acceptable compromise for a VLAN-based
-      deployment.
-
-    These compromises wouldn't suit every site, since some deployments
-    may need to allocate more bits to the datapath or egress port
-    identifiers.
-
-    As a side note, OVN does support VXLAN for use with ASIC-based top of rack
-    switches, using ``ovn-controller-vtep(8)`` and the OVSDB VTEP schema
-    described in ``vtep(5)``, but this limits the features available from OVN
-    to the subset available from the VTEP schema.
diff --git a/Documentation/howto/docker.rst b/Documentation/howto/docker.rst
deleted file mode 100644
index a68b02fdb..000000000
--- a/Documentation/howto/docker.rst
+++ /dev/null
@@ -1,326 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-===================================
-Open Virtual Networking With Docker
-===================================
-
-This document describes how to use Open Virtual Networking with Docker 1.9.0
-or later.
-
-.. important::
-
-  Requires Docker version 1.9.0 or later. Only Docker 1.9.0+ comes with support
-  for multi-host networking. Consult www.docker.com for instructions on how to
-  install Docker.
-
-.. note::
-
-  You must build and install Open vSwitch before proceeding with the below
-  guide. Refer to :doc:`/intro/install/index` for more information.
-
-Setup
------
-
-For multi-host networking with OVN and Docker, Docker has to be started with a
-distributed key-value store. For example, if you decide to use consul as your
-distributed key-value store and your host IP address is ``$HOST_IP``, start
-your Docker daemon with::
-
-    $ docker daemon --cluster-store=consul://127.0.0.1:8500 \
-        --cluster-advertise=$HOST_IP:0
-
-OVN provides network virtualization to containers. OVN's integration with
-Docker currently works in two modes - the "underlay" mode or the "overlay"
-mode.
-
-In the "underlay" mode, OVN requires a OpenStack setup to provide container
-networking. In this mode, one can create logical networks and can have
-containers running inside VMs, standalone VMs (without having any containers
-running inside them) and physical machines connected to the same logical
-network. This is a multi-tenant, multi-host solution.
-
-In the "overlay" mode, OVN can create a logical network amongst containers
-running on multiple hosts. This is a single-tenant (extendable to multi-tenants
-depending on the security characteristics of the workloads), multi-host
-solution. In this mode, you do not need a pre-created OpenStack setup.
-
-For both the modes to work, a user has to install and start Open vSwitch in
-each VM/host that they plan to run their containers on.
-
-.. _docker-overlay:
-
-The "overlay" mode
-------------------
-
-.. note::
-
-  OVN in "overlay" mode needs a minimum Open vSwitch version of 2.5.
-
-1. Start the central components.
-
-  OVN architecture has a central component which stores your networking intent
-  in a database. On one of your machines, with an IP Address of
-  ``$CENTRAL_IP``, where you have installed and started Open vSwitch, you will
-  need to start some central components.
-
-  Start ovn-northd daemon. This daemon translates networking intent from Docker
-  stored in the OVN\_Northbound database to logical flows in ``OVN_Southbound``
-  database. For example::
-
-      $ /usr/share/openvswitch/scripts/ovn-ctl start_northd
-
-  With Open vSwitch version of 2.7 or greater, you need to run the following
-  additional commands (Please read the manpages of ovn-nb for more control
-  on the types of connection allowed.) ::
-
-      $ ovn-nbctl set-connection ptcp:6641
-      $ ovn-sbctl set-connection ptcp:6642
-
-2. One time setup
-
-   On each host, where you plan to spawn your containers, you will need to run
-   the below command once. You may need to run it again if your OVS database
-   gets cleared. It is harmless to run it again in any case::
-
-       $ ovs-vsctl set Open_vSwitch . \
-           external_ids:ovn-remote="tcp:$CENTRAL_IP:6642" \
-           external_ids:ovn-nb="tcp:$CENTRAL_IP:6641" \
-           external_ids:ovn-encap-ip=$LOCAL_IP \
-           external_ids:ovn-encap-type="$ENCAP_TYPE"
-
-   where:
-
-   ``$LOCAL_IP``
-     is the IP address via which other hosts can reach this host.  This acts as
-     your local tunnel endpoint.
-
-   ``$ENCAP_TYPE``
-     is the type of tunnel that you would like to use for overlay networking.
-     The options are ``geneve`` or ``stt``. Your kernel must have support for
-     your chosen ``$ENCAP_TYPE``. Both ``geneve`` and ``stt`` are part of the
-     Open vSwitch kernel module that is compiled from this repo. If you use the
-     Open vSwitch kernel module from upstream Linux, you will need a minimum
-     kernel version of 3.18 for ``geneve``. There is no ``stt`` support in
-     upstream Linux. You can verify whether you have the support in your kernel
-     as follows::
-
-         $ lsmod | grep $ENCAP_TYPE
-
-   In addition, each Open vSwitch instance in an OVN deployment needs a unique,
-   persistent identifier, called the ``system-id``.  If you install OVS from
-   distribution packaging for Open vSwitch (e.g. .deb or .rpm packages), or if
-   you use the ovs-ctl utility included with Open vSwitch, it automatically
-   configures a system-id.  If you start Open vSwitch manually, you should set
-   one up yourself. For example::
-
-       $ id_file=/etc/openvswitch/system-id.conf
-       $ test -e $id_file || uuidgen > $id_file
-       $ ovs-vsctl set Open_vSwitch . external_ids:system-id=$(cat $id_file)
-
-3. Start the ``ovn-controller``.
-
-   You need to run the below command on every boot::
-
-       $ /usr/share/openvswitch/scripts/ovn-ctl start_controller
-
-4. Start the Open vSwitch network driver.
-
-   By default Docker uses Linux bridge for networking. But it has support for
-   external drivers. To use Open vSwitch instead of the Linux bridge, you will
-   need to start the Open vSwitch driver.
-
-   The Open vSwitch driver uses the Python's flask module to listen to Docker's
-   networking api calls. So, if your host does not have Python's flask module,
-   install it::
-
-       $ sudo pip install Flask
-
-   Start the Open vSwitch driver on every host where you plan to create your
-   containers. Refer to the note on ``$OVS_PYTHON_LIBS_PATH`` that is used below
-   at the end of this document::
-
-       $ PYTHONPATH=$OVS_PYTHON_LIBS_PATH ovn-docker-overlay-driver --detach
-
-   .. note::
-
-     The ``$OVS_PYTHON_LIBS_PATH`` variable should point to the directory where
-     Open vSwitch Python modules are installed. If you installed Open vSwitch
-     Python modules via the Debian package of ``python-openvswitch`` or via pip
-     by running ``pip install ovs``, you do not need to specify the PATH. If
-     you installed it by following the instructions in
-     :doc:`/intro/install/general`, then you should specify the PATH. In this
-     case, the PATH depends on the options passed to ``./configure``. It is
-     usually either ``/usr/share/openvswitch/python`` or
-     ``/usr/local/share/openvswitch/python``
-
-Docker has inbuilt primitives that closely match OVN's logical switches and
-logical port concepts. Consult Docker's documentation for all the possible
-commands. Here are some examples.
-
-Create a logical switch
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To create a logical switch with name 'foo', on subnet '192.168.1.0/24', run::
-
-    $ NID=`docker network create -d openvswitch --subnet=192.168.1.0/24 foo`
-
-List all logical switches
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    $ docker network ls
-
-You can also look at this logical switch in OVN's northbound database by
-running the following command::
-
-    $ ovn-nbctl --db=tcp:$CENTRAL_IP:6640 ls-list
-
-Delete a logical switch
-~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    $ docker network rm bar
-
-
-Create a logical port
-~~~~~~~~~~~~~~~~~~~~~
-
-Docker creates your logical port and attaches it to the logical network in a
-single step. For example, to attach a logical port to network ``foo`` inside
-container busybox, run::
-
-    $ docker run -itd --net=foo --name=busybox busybox
-
-List all logical ports
-~~~~~~~~~~~~~~~~~~~~~~
-
-Docker does not currently have a CLI command to list all logical ports but you
-can look at them in the OVN database by running::
-
-    $ ovn-nbctl --db=tcp:$CENTRAL_IP:6640 lsp-list $NID
-
-Create and attach a logical port to a running container
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    $ docker network create -d openvswitch --subnet=192.168.2.0/24 bar
-    $ docker network connect bar busybox
-
-Detach and delete a logical port from a running container
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can delete your logical port and detach it from a running container
-by running:
-
-::
-
-    $ docker network disconnect bar busybox
-
-.. _docker-underlay:
-
-The "underlay" mode
--------------------
-
-.. note::
-
-  This mode requires that you have a OpenStack setup pre-installed with
-  OVN providing the underlay networking.
-
-1. One time setup
-
-   A OpenStack tenant creates a VM with a single network interface (or multiple)
-   that belongs to management logical networks. The tenant needs to fetch the
-   port-id associated with the interface via which he plans to send the container
-   traffic inside the spawned VM. This can be obtained by running the below
-   command to fetch the 'id' associated with the VM::
-
-       $ nova list
-
-   and then by running::
-
-       $ neutron port-list --device_id=$id
-
-   Inside the VM, download the OpenStack RC file that contains the tenant
-   information (henceforth referred to as ``openrc.sh``). Edit the file and add the
-   previously obtained port-id information to the file by appending the following
-   line::
-
-       $ export OS_VIF_ID=$port_id
-
-   After this edit, the file will look something like::
-
-       #!/bin/bash
-       export OS_AUTH_URL=http://10.33.75.122:5000/v2.0
-       export OS_TENANT_ID=fab106b215d943c3bad519492278443d
-       export OS_TENANT_NAME="demo"
-       export OS_USERNAME="demo"
-       export OS_VIF_ID=e798c371-85f4-4f2d-ad65-d09dd1d3c1c9
-
-2. Create the Open vSwitch bridge
-
-   If your VM has one ethernet interface (e.g.: 'eth0'), you will need to add
-   that device as a port to an Open vSwitch bridge 'breth0' and move its IP
-   address and route related information to that bridge. (If it has multiple
-   network interfaces, you will need to create and attach an Open vSwitch
-   bridge for the interface via which you plan to send your container
-   traffic.)
-
-   If you use DHCP to obtain an IP address, then you should kill the DHCP
-   client that was listening on the physical Ethernet interface (e.g. eth0) and
-   start one listening on the Open vSwitch bridge (e.g. breth0).
-
-   Depending on your VM, you can make the above step persistent across reboots.
-   For example, if your VM is Debian/Ubuntu-based, read
-   `openvswitch-switch.README.Debian` found in `debian` folder. If your VM is
-   RHEL-based, refer to :doc:`/intro/install/rhel`.
-
-3. Start the Open vSwitch network driver
-
-   The Open vSwitch driver uses the Python's flask module to listen to Docker's
-   networking api calls. The driver also uses OpenStack's
-   ``python-neutronclient`` libraries. If your host does not have Python's
-   ``flask`` module or ``python-neutronclient`` you must install them. For
-   example::
-
-       $ pip install python-neutronclient
-       $ pip install Flask
-
-   Once installed, source the ``openrc`` file::
-
-       $ . ./openrc.sh
-
-   Start the network driver and provide your OpenStack tenant password when
-   prompted::
-
-       $ PYTHONPATH=$OVS_PYTHON_LIBS_PATH ovn-docker-underlay-driver \
-           --bridge breth0 --detach
-
-From here-on you can use the same Docker commands as described in
-`docker-overlay`_.
-
-Refer to the ovs-architecture man pages (``man ovn-architecture``) to
-understand OVN's architecture in detail.
diff --git a/Documentation/howto/firewalld.rst b/Documentation/howto/firewalld.rst
deleted file mode 100644
index 0dc455ea8..000000000
--- a/Documentation/howto/firewalld.rst
+++ /dev/null
@@ -1,107 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-===================================
-Open Virtual Network With firewalld
-===================================
-
-firewalld is a service that allows for easy administration of firewalls. OVN
-ships with a set of service files that can be used with firewalld to allow
-for remote connections to the northbound and southbound databases.
-
-This guide will describe how you can use these files with your existing
-firewalld setup. Setup and administration of firewalld is outside the scope
-of this document.
-
-Installation
-------------
-
-If you have installed OVN from an RPM, then the service files for firewalld
-will automatically be installed in ``/usr/lib/firewalld/services``.
-Installation from RPM includes installation from the yum or dnf package
-managers.
-
-If you have installed OVN from source, then from the top level source
-directory, issue the following commands to copy the firewalld service files:
-
-::
-
-    $ cp rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
-    /etc/firewalld/services/
-    $ cp rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
-    /etc/firewalld/services/
-
-
-Activation
-----------
-
-Assuming you are already running firewalld, you can issue the following
-commands to enable the OVN services.
-
-On the central server (the one running ``ovn-northd``), issue the following::
-
-$ firewall-cmd --zone=public --add-service=ovn-central-firewall-service
-
-This will open TCP ports 6641 and 6642, allowing for remote connections to the
-northbound and southbound databases.
-
-On the OVN hosts (the ones running ``ovn-controller``), issue the following::
-
-$ firewall-cmd --zone=public --add-service=ovn-host-firewall-service
-
-This will open UDP port 6081, allowing for geneve traffic to flow between the
-controllers.
-
-Variations
-----------
-
-When installing the XML service files, you have the choice of copying them to
-``/etc/firewalld/services`` or ``/usr/lib/firewalld/services``. The former is
-recommend since the latter can be overwritten if firewalld is upgraded.
-
-The above commands assumed your underlay network interfaces are in the
-"public" firewalld zone. If your underlay network interfaces are in a separate
-zone, then adjust the above commands accordingly.
-
-The ``--permanent`` option may be passed to the above firewall-cmd invocations
-in order for the services to be permanently added to the firewalld
-configuration. This way it is not necessary to re-issue the commands each
-time the firewalld service restarts.
-
-The ovn-host-firewall-service only opens port 6081. This is because the
-default protocol for OVN tunnels is geneve. If you are using a different
-encapsulation protocol, you will need to modify the XML service file to open
-the appropriate port(s). For VXLAN, open port 4789. For STT, open port 7471.
-
-Recommendations
----------------
-
-The firewalld service files included with the OVS repo are meant as a
-convenience for firewalld users. All that the service files do is to open
-the common ports used by OVN. No additional security is provided. To ensure a
-more secure environment, it is a good idea to do the following
-
-* Use tools such as iptables or nftables to restrict access to known hosts.
-* Use SSL for all remote connections to OVN databases.
-* Use role-based access control for connections to the OVN southbound
-  database.
diff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst
index 9a3487be3..60fb8a717 100644
--- a/Documentation/howto/index.rst
+++ b/Documentation/howto/index.rst
@@ -50,12 +50,3 @@ OVS
    sflow
    dpdk
 
-OVN
----
-
-.. toctree::
-   :maxdepth: 1
-
-   docker
-   openstack-containers
-   firewalld
diff --git a/Documentation/howto/openstack-containers.rst b/Documentation/howto/openstack-containers.rst
deleted file mode 100644
index 692fe25e5..000000000
--- a/Documentation/howto/openstack-containers.rst
+++ /dev/null
@@ -1,135 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-================================================
-Integration of Containers with OVN and OpenStack
-================================================
-
-Isolation between containers is weaker than isolation between VMs, so some
-environments deploy containers for different tenants in separate VMs as an
-additional security measure.  This document describes creation of containers
-inside VMs and how they can be made part of the logical networks securely.  The
-created logical network can include VMs, containers and physical machines as
-endpoints.  To better understand the proposed integration of containers with
-OVN and OpenStack, this document describes the end to end workflow with an
-example.
-
-* A OpenStack tenant creates a VM (say VM-A) with a single network interface
-  that belongs to a management logical network.  The VM is meant to host
-  containers.  OpenStack Nova chooses the hypervisor on which VM-A is created.
-
-* A Neutron port may have been created in advance and passed in to Nova with
-  the request to create a new VM.  If not, Nova will issue a request to Neutron
-  to create a new port.  The ID of the logical port from Neutron will also be
-  used as the vif-id for the virtual network interface (VIF) of VM-A.
-
-* When VM-A is created on a hypervisor, its VIF gets added to the Open vSwitch
-  integration bridge.  This creates a row in the Interface table of the
-  ``Open_vSwitch`` database.  As explained in the :doc:`integration guide
-  </topics/integration>`, the vif-id associated with the VM network interface
-  gets added in the ``external_ids:iface-id`` column of the newly created row
-  in the Interface table.
-
-* Since VM-A belongs to a logical network, it gets an IP address.  This IP
-  address is used to spawn containers (either manually or through container
-  orchestration systems) inside that VM and to monitor the health of the
-  created containers.
-
-* The vif-id associated with the VM's network interface can be obtained by
-  making a call to Neutron using tenant credentials.
-
-* This flow assumes a component called a "container network plugin".  If you
-  take Docker as an example for containers, you could envision the plugin to be
-  either a wrapper around Docker or a feature of Docker itself that understands
-  how to perform part of this workflow to get a container connected to a
-  logical network managed by Neutron.  The rest of the flow refers to this
-  logical component that does not yet exist as the "container network plugin".
-
-* All the calls to Neutron will need tenant credentials.  These calls can
-  either be made from inside the tenant VM as part of a container network
-  plugin or from outside the tenant VM (if the tenant is not comfortable using
-  temporary Keystone tokens from inside the tenant VMs).  For simplicity, this
-  document explains the work flow using the former method.
-
-* The container hosting VM will need Open vSwitch installed in it.  The only
-  work for Open vSwitch inside the VM is to tag network traffic coming from
-  containers.
-
-* When a container needs to be created inside the VM with a container network
-  interface that is expected to be attached to a particular logical switch, the
-  network plugin in that VM chooses any unused VLAN (This VLAN tag only needs
-  to be unique inside that VM.  This limits the number of container interfaces
-  to 4096 inside a single VM).  This VLAN tag is stripped out in the hypervisor
-  by OVN and is only useful as a context (or metadata) for OVN.
-
-* The container network plugin then makes a call to Neutron to create a logical
-  port.  In addition to all the inputs that a call to create a port in Neutron
-  that are currently needed, it sends the vif-id and the VLAN tag as inputs.
-
-* Neutron in turn will verify that the vif-id belongs to the tenant in question
-  and then uses the OVN specific plugin to create a new row in the
-  Logical_Switch_Port table of the OVN Northbound Database.  Neutron responds
-  back with an IP address and MAC address for that network interface.  So
-  Neutron becomes the IPAM system and provides unique IP and MAC addresses
-  across VMs and containers in the same logical network.
-
-* The Neutron API call above to create a logical port for the container could
-  add a relatively significant amount of time to container creation.  However,
-  an optimization is possible here.  Logical ports could be created in advance
-  and reused by the container system doing container orchestration.  Additional
-  Neutron API calls would only be needed if the port needs to be attached to a
-  different logical network.
-
-* When a container is eventually deleted, the network plugin in that VM may
-  make a call to Neutron to delete that port.  Neutron in turn will delete the
-  entry in the ``Logical_Switch_Port`` table of the OVN Northbound Database.
-
-As an example, consider Docker containers.  Since Docker currently does not
-have a network plugin feature, this example uses a hypothetical wrapper around
-Docker to make calls to Neutron.
-
-* Create a Logical switch::
-
-      $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f create network LS1
-
-  The above command will make a call to Neutron with the credentials to create
-  a logical switch.  The above is optional if the logical switch has already
-  been created from outside the VM.
-
-* List networks available to the tenant::
-
-      $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f list networks
-
-* Create a container and attach a interface to the previously created switch as
-  a logical port::
-
-      $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f --vif-id=$VIF_ID \
-          --network=LS1 run -d --net=none ubuntu:14.04 /bin/sh -c \
-          "while true; do echo hello world; sleep 1; done"
-
-  The above command will make a call to Neutron with all the inputs it
-  currently needs to create a logical port.  In addition, it passes the $VIF_ID
-  and a unused VLAN.  Neutron will add that information in OVN and return back
-  a MAC address and IP address for that interface.  ovn-docker will then create
-  a veth pair, insert one end inside the container as 'eth0' and the other end
-  as a port of a local OVS bridge as an access port of the chosen VLAN.
diff --git a/Documentation/index.rst b/Documentation/index.rst
index bace34dbf..f18f8df1c 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -33,22 +33,20 @@ How the Documentation is Organised
 The Open vSwitch documentation is organised into multiple sections:
 
 - :doc:`Installation guides </intro/install/index>` guide you through
-  installing Open vSwitch (OVS) and Open Virtual Network (OVN) on a variety of
-  different platforms
+  installing Open vSwitch (OVS) on a variety of different platforms
 - :doc:`Tutorials </tutorials/index>` take you through a series of steps to
-  configure OVS and OVN in sandboxed environments
-- :doc:`Topic guides </topics/index>` provide a high level overview of OVS and
-  OVN internals and operation
-- :doc:`How-to guides </howto/index>` are recipes or use-cases for OVS and OVN.
+  configure OVS in sandboxed environments
+- :doc:`Topic guides </topics/index>` provide a high level overview of OVS
+  internals and operation
+- :doc:`How-to guides </howto/index>` are recipes or use-cases for OVS.
   They are more advanced than the tutorials.
 - :doc:`Frequently Asked Questions </faq/index>` provide general insight into
-  a variety of topics related to configuration and operation of OVS and OVN.
+  a variety of topics related to configuration and operation of OVS.
 
 First Steps
 -----------
 
-Getting started with Open vSwitch (OVS) or Open Virtual Network (OVN) for Open
-vSwitch? Start here.
+Getting started with Open vSwitch (OVS)? Start here.
 
 - **Overview:** :doc:`intro/what-is-ovs` |
   :doc:`intro/why-ovs`
@@ -64,12 +62,8 @@ vSwitch? Start here.
 
 - **Tutorials:** :doc:`tutorials/faucet` |
   :doc:`tutorials/ovs-advanced` |
-  :doc:`tutorials/ovn-sandbox` |
-  :doc:`tutorials/ovn-openstack` |
   :doc:`tutorials/ovs-conntrack` |
   :doc:`tutorials/ipsec` |
-  :doc:`tutorials/ovn-ipsec` |
-  :doc:`tutorials/ovn-rbac`
 
 Deeper Dive
 -----------
diff --git a/Documentation/intro/install/fedora.rst b/Documentation/intro/install/fedora.rst
index 4e1a99766..f11d05a01 100644
--- a/Documentation/intro/install/fedora.rst
+++ b/Documentation/intro/install/fedora.rst
@@ -119,16 +119,6 @@ tests.  This can take several minutes.
 
     $ make rpm-fedora RPMBUILD_OPT="--with check"
 
-To build OVN RPMs, execute the following from the directory in which
-`./configure` was executed:
-
-::
-
-    $ make rpm-fedora-ovn
-
-This will create the RPMs `ovn`, `ovn-common`, `ovn-central`, `ovn-host`,
-`ovn-docker` and `ovn-vtep`.
-
 
 Kernel OVS Tree Datapath RPM
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -165,8 +155,6 @@ In most cases only the `openvswitch` RPM will need to be installed. The
 `openvswitch-debuginfo` RPMs are optional unless required for a specific
 purpose.
 
-The `ovn-*` packages are only needed when using OVN.
-
 Refer to the `RHEL README`__ for additional usage and configuration
 information.
 
diff --git a/Documentation/intro/install/index.rst b/Documentation/intro/install/index.rst
index c27a9c9d1..586ced95f 100644
--- a/Documentation/intro/install/index.rst
+++ b/Documentation/intro/install/index.rst
@@ -62,14 +62,6 @@ provided below.
    fedora
    rhel
 
-Upgrades
---------
-
-.. toctree::
-   :maxdepth: 2
-
-   ovn-upgrades
-
 Others
 ------
 
diff --git a/Documentation/intro/install/ovn-upgrades.rst b/Documentation/intro/install/ovn-upgrades.rst
deleted file mode 100644
index 3e6cd984e..000000000
--- a/Documentation/intro/install/ovn-upgrades.rst
+++ /dev/null
@@ -1,115 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-============
-OVN Upgrades
-============
-
-Since OVN is a distributed system, special consideration must be given to
-the process used to upgrade OVN across a deployment.  This document discusses
-the recommended upgrade process.
-
-Release Notes
--------------
-
-You should always check the OVS and OVN release notes (NEWS file) for any
-release specific notes on upgrades.
-
-OVS
----
-
-OVN depends on and is included with OVS.  It's expected that OVS and OVN are
-upgraded together, partly for convenience.  OVN is included in OVS releases
-so it's easiest to upgrade them together.  OVN may also make use of new
-features of OVS only available in that release.
-
-Upgrade ovn-controller
-----------------------
-
-You should start by upgrading ovn-controller on each host it's running on.
-First, you upgrade the OVS and OVN packages.  Then, restart the
-ovn-controller service.  You can restart with ovn-ctl::
-
-    $ sudo /usr/share/openvswitch/scripts/ovn-ctl restart_controller
-
-or with systemd::
-
-    $ sudo systemd restart ovn-controller
-
-Upgrade OVN Databases and ovn-northd
-------------------------------------
-
-The OVN databases and ovn-northd should be upgraded next.  Since ovn-controller
-has already been upgraded, it will be ready to operate on any new functionality
-specified by the database or logical flows created by ovn-northd.
-
-Upgrading the OVN packages installs everything needed for an upgrade.  The only
-step required after upgrading the packages is to restart ovn-northd, which
-automatically restarts the databases and upgrades the database schema, as well.
-
-You may perform this restart using the ovn-ctl script::
-
-    $ sudo /usr/share/openvswitch/scripts/ovn-ctl restart_northd
-
-or if you're using a Linux distribution with systemd::
-
-    $ sudo systemctl restart ovn-northd
-
-Schema Change
-^^^^^^^^^^^^^
-
-During database upgrading, if there is schema change, the DB file will be
-converted to the new schema automatically, if the schema change is backward
-compatible.  OVN tries the best to keep the DB schemas backward compatible.
-
-However, there can be situations that an incompatible change is reasonble.  An
-example of such case is to add constraints in the table to ensure correctness.
-If there were already data that violates the new constraints got added somehow,
-it will result in DB upgrade failures.  In this case, user should manually
-correct data using ovn-nbctl (for north-bound DB) or ovn-sbctl (for south-
-bound DB), and then upgrade again following previous steps.  Below is a list
-of known impactible schema changes and how to fix when error encountered.
-
-#. Release 2.11: index [type, ip] added for Encap table of south-bound DB to
-   prevent duplicated IPs being used for same tunnel type.  If there are
-   duplicated data added already (e.g. due to improper chassis management),
-   a convenient way to fix is to find the chassis that is using the IP
-   with command::
-
-    $ ovn-sbctl show
-
-   Then delete the chassis with command::
-
-    $ ovn-sbctl chassis-del <chassis>
-
-
-Upgrading OVN Integration
--------------------------
-
-Lastly, you may also want to upgrade integration with OVN that you may be
-using.  For example, this could be the OpenStack Neutron driver or
-ovn-kubernetes.
-
-OVN's northbound database schema is a backwards compatible interface, so
-you should be able to safely complete an OVN upgrade before upgrading
-any integration in use.
diff --git a/Documentation/ref/ovs-sim.1.rst b/Documentation/ref/ovs-sim.1.rst
index 4382598e1..f59cd7af7 100644
--- a/Documentation/ref/ovs-sim.1.rst
+++ b/Documentation/ref/ovs-sim.1.rst
@@ -142,103 +142,3 @@ with ``main`` directly.
     must already have been created by a previous invocation of
     ``net_add``. The default sandbox must not be ``main``.
 
-OVN Commands
-------------
-
-These commands interact with OVN, the Open Virtual Network.
-
-``ovn_start`` [*options*]
-    Creates and initializes the central OVN databases (both
-    ``ovn-sb(5)`` and ``ovn-nb(5)``) and starts an instance of
-    ``ovsdb-server`` for each one.  Also starts an instance of
-    ``ovn-northd``.
-
-    The following options are available:
-
-       ``--nbdb-model`` *model*
-           Uses the given database model for the northbound database.
-           The *model* may be ``standalone`` (the default), ``backup``,
-           or ``clustered``.
-
-       ``--nbdb-servers`` *n*
-           For a clustered northbound database, the number of servers in
-           the cluster.  The default is 3.
-
-       ``--sbdb-model`` *model*
-           Uses the given database model for the southbound database.
-           The *model* may be ``standalone`` (the default), ``backup``,
-           or ``clustered``.
-
-       ``--sbdb-servers`` *n*
-           For a clustered southbound database, the number of servers in
-           the cluster.  The default is 3.
-
-``ovn_attach`` *network* *bridge* *ip* [*masklen*]
-    First, this command attaches bridge to interconnection network
-    network, just like ``net_attach`` *network* *bridge*.  Second, it
-    configures (simulated) IP address *ip* (with network mask length
-    *masklen*, which defaults to 24) on *bridge*. Finally, it
-    configures the Open vSwitch database to work with OVN and starts
-    ``ovn-controller``.
-
-Examples
-========
-
-The following creates a pair of Open vSwitch instances ``hv0`` and
-``hv1``, adds a port named ``vif0`` or ``vif1``, respectively, to each
-one, and then connects the two through an interconnection network
-``n1``::
-
-    net_add n1
-    for i in 0 1; do
-        sim_add hv$i
-        as hv$i ovs-vsctl add-br br0 -- add-port br0 vif$i
-        as hv$i net_attach n1 br0
-    done
-
-Here’s an extended version that also starts OVN::
-
-    ovn_start
-    ovn-nbctl ls-add lsw0
-    net_add n1
-    for i in 0 1; do
-        sim_add hv$i
-        as hv$i
-        ovs-vsctl add-br br-phys
-        ovn_attach n1 br-phys 192.168.0.`expr $i + 1`
-        ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lp$i
-        ovn-nbctl lsp-add lsw0 lp$i
-        ovn-nbctl lsp-set-addresses lp$i f0:00:00:00:00:0$i
-    done
-
-Here’s a primitive OVN "scale test" (adjust the scale by changing
-``n`` in the first line::
-
-    n=200; export n
-    ovn_start --sbdb-model=clustered
-    net_add n1
-    ovn-nbctl ls-add br0
-    for i in `seq $n`; do
-        (sim_add hv$i
-        as hv$i
-        ovs-vsctl add-br br-phys
-        y=$(expr $i / 256)
-        x=$(expr $i % 256)
-        ovn_attach n1 br-phys 192.168.$y.$x
-        ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lp$i) &
-        case $i in
-            *50|*00) echo $i; wait ;;
-        esac
-    done
-    wait
-    for i in `seq $n`; do
-        yy=$(printf %02x $(expr $i / 256))
-        xx=$(printf $02x $(expr $i % 256))
-        ovn-nbctl lsp-add br0 lp$i
-        ovn-nbctl lsp-set-addresses lp$i f0:00:00:00:$yy:$xx
-    done
-
-When the scale test has finished initializing, you can watch the
-logical ports come up with a command like this::
-
-    watch 'for i in `seq $n`; do if test `ovn-nbctl lsp-get-up lp$i` != up; then echo $i; fi; done'
diff --git a/Documentation/topics/high-availability.rst b/Documentation/topics/high-availability.rst
deleted file mode 100644
index a5cb76383..000000000
--- a/Documentation/topics/high-availability.rst
+++ /dev/null
@@ -1,440 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-==================================
-OVN Gateway High Availability Plan
-==================================
-
-::
-
-    OVN Gateway
-
-         +---------------------------+
-         |                           |
-         |     External Network      |
-         |                           |
-         +-------------^-------------+
-                       |
-                       |
-                 +-----------+
-                 |           |
-                 |  Gateway  |
-                 |           |
-                 +-----------+
-                       ^
-                       |
-                       |
-         +-------------v-------------+
-         |                           |
-         |    OVN Virtual Network    |
-         |                           |
-         +---------------------------+
-
-The OVN gateway is responsible for shuffling traffic between the tunneled
-overlay network (governed by ovn-northd), and the legacy physical network.  In
-a naive implementation, the gateway is a single x86 server, or hardware VTEP.
-For most deployments, a single system has enough forwarding capacity to service
-the entire virtualized network, however, it introduces a single point of
-failure.  If this system dies, the entire OVN deployment becomes unavailable.
-To mitigate this risk, an HA solution is critical -- by spreading
-responsibility across multiple systems, no single server failure can take down
-the network.
-
-An HA solution is both critical to the manageability of the system, and
-extremely difficult to get right.  The purpose of this document, is to propose
-a plan for OVN Gateway High Availability which takes into account our past
-experience building similar systems.  It should be considered a fluid changing
-proposal, not a set-in-stone decree.
-
-.. note::
-    This document describes a range of options OVN could take to provide
-    high availability for gateways.  The current implementation provides L3
-    gateway high availability by the "Router Specific Active/Backup"
-    approach described in this document.
-
-Basic Architecture
-------------------
-
-In an OVN deployment, the set of hypervisors and network elements operating
-under the guidance of ovn-northd are in what's called "logical space".  These
-servers use VXLAN, STT, or Geneve to communicate, oblivious to the details of
-the underlying physical network.  When these systems need to communicate with
-legacy networks, traffic must be routed through a Gateway which translates from
-OVN controlled tunnel traffic, to raw physical network traffic.
-
-Since the gateway is typically the only system with a connection to the
-physical network all traffic between logical space and the WAN must travel
-through it.  This makes it a critical single point of failure -- if the gateway
-dies, communication with the WAN ceases for all systems in logical space.
-
-To mitigate this risk, multiple gateways should be run in a "High Availability
-Cluster" or "HA Cluster".  The HA cluster will be responsible for performing
-the duties of a gateways,  while being able to recover gracefully from
-individual member failures.
-
-::
-
-    OVN Gateway HA Cluster
-
-             +---------------------------+
-             |                           |
-             |     External Network      |
-             |                           |
-             +-------------^-------------+
-                           |
-                           |
-    +----------------------v----------------------+
-    |                                             |
-    |          High Availability Cluster          |
-    |                                             |
-    | +-----------+  +-----------+  +-----------+ |
-    | |           |  |           |  |           | |
-    | |  Gateway  |  |  Gateway  |  |  Gateway  | |
-    | |           |  |           |  |           | |
-    | +-----------+  +-----------+  +-----------+ |
-    +----------------------^----------------------+
-                           |
-                           |
-             +-------------v-------------+
-             |                           |
-             |    OVN Virtual Network    |
-             |                           |
-             +---------------------------+
-
-L2 vs L3 High Availability
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In order to achieve this goal, there are two broad approaches one can take.
-The HA cluster can appear to the network like a giant Layer 2 Ethernet Switch,
-or like a giant IP Router. These approaches are called L2HA, and L3HA
-respectively.  L2HA allows ethernet broadcast domains to extend into logical
-space, a significant advantage, but this comes at a cost.  The need to avoid
-transient L2 loops during failover significantly complicates their design.  On
-the other hand, L3HA works for most use cases, is simpler, and fails more
-gracefully.  For these reasons, it is suggested that OVN supports an L3HA
-model, leaving L2HA for future work (or third party VTEP providers).  Both
-models are discussed further below.
-
-L3HA
-----
-
-In this section, we'll work through a basic simple L3HA implementation, on top
-of which we'll gradually build more sophisticated features explaining their
-motivations and implementations as we go.
-
-Naive active-backup
-~~~~~~~~~~~~~~~~~~~
-
-Let's assume that there are a collection of logical routers which a tenant has
-asked for, our task is to schedule these logical routers on one of N gateways,
-and gracefully redistribute the routers on gateways which have failed.  The
-absolute simplest way to achieve this is what we'll call "naive-active-backup".
-
-::
-
-    Naive Active Backup HA Implementation
-
-    +----------------+   +----------------+
-    | Leader         |   | Backup         |
-    |                |   |                |
-    |      A B C     |   |                |
-    |                |   |                |
-    +----+-+-+-+----++   +-+--------------+
-         ^ ^ ^ ^    |      |
-         | | | |    |      |
-         | | | |  +-+------+---+
-         + + + +  | ovn-northd |
-         Traffic  +------------+
-
-In a naive active-backup, one of the Gateways is chosen (arbitrarily) as a
-leader.  All logical routers (A, B, C in the figure), are scheduled on this
-leader gateway and all traffic flows through it.  ovn-northd monitors this
-gateway via OpenFlow echo requests (or some equivalent), and if the gateway
-dies, it recreates the routers on one of the backups.
-
-This approach basically works in most cases and should likely be the starting
-point for OVN -- it's strictly better than no HA solution and is a good
-foundation for more sophisticated solutions.  That said, it's not without it's
-limitations. Specifically, this approach doesn't coordinate with the physical
-network to minimize disruption during failures, and it tightly couples failover
-to ovn-northd (we'll discuss why this is bad in a bit), and wastes resources by
-leaving backup gateways completely unutilized.
-
-Router Failover
-+++++++++++++++
-
-When ovn-northd notices the leader has died and decides to migrate routers to a
-backup gateway, the physical network has to be notified to direct traffic to
-the new gateway.  Otherwise, traffic could be blackholed for longer than
-necessary making failovers worse than they need to be.
-
-For now, let's assume that OVN requires all gateways to be on the same IP
-subnet on the physical network.  If this isn't the case, gateways would need to
-participate in routing protocols to orchestrate failovers, something which is
-difficult and out of scope of this document.
-
-Since all gateways are on the same IP subnet, we simply need to worry about
-updating the MAC learning tables of the Ethernet switches on that subnet.
-Presumably, they all have entries for each logical router pointing to the old
-leader.  If these entries aren't updated, all traffic will be sent to the (now
-defunct) old leader, instead of the new one.
-
-In order to mitigate this issue, it's recommended that the new gateway sends a
-Reverse ARP (RARP) onto the physical network for each logical router it now
-controls.  A Reverse ARP is a benign protocol used by many hypervisors when
-virtual machines migrate to update L2 forwarding tables.  In this case, the
-ethernet source address of the RARP is that of the logical router it
-corresponds to, and its destination is the broadcast address.  This causes the
-RARP to travel to every L2 switch in the broadcast domain, updating forwarding
-tables accordingly.  This strategy is recommended in all failover mechanisms
-discussed in this document -- when a router newly boots on a new leader, it
-should RARP its MAC address.
-
-Controller Independent Active-backup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    Controller Independent Active-Backup Implementation
-
-    +----------------+   +----------------+
-    | Leader         |   | Backup         |
-    |                |   |                |
-    |      A B C     |   |                |
-    |                |   |                |
-    +----------------+   +----------------+
-         ^ ^ ^ ^
-         | | | |
-         | | | |
-         + + + +
-         Traffic
-
-The fundamental problem with naive active-backup, is it tightly couples the
-failover solution to ovn-northd.  This can significantly increase downtime in
-the event of a failover as the (often already busy) ovn-northd controller has
-to recompute state for the new leader. Worse, if ovn-northd goes down, we can't
-perform gateway failover at all.  This violates the principle that control
-plane outages should have no impact on dataplane functionality.
-
-In a controller independent active-backup configuration, ovn-northd is
-responsible for initial configuration while the HA cluster is responsible for
-monitoring the leader, and failing over to a backup if necessary.  ovn-northd
-sets HA policy, but doesn't actively participate when failovers occur.
-
-Of course, in this model, ovn-northd is not without some responsibility.  Its
-role is to pre-plan what should happen in the event of a failure, leaving it to
-the individual switches to execute this plan.  It does this by assigning each
-gateway a unique leadership priority.  Once assigned, it communicates this
-priority to each node it controls.  Nodes use the leadership priority to
-determine which gateway in the cluster is the active leader by using a simple
-metric: the leader is the gateway that is healthy, with the highest priority.
-If that gateway goes down, leadership falls to the next highest priority, and
-conversely, if a new gateway comes up with a higher priority, it takes over
-leadership.
-
-Thus, in this model, leadership of the HA cluster is determined simply by the
-status of its members.  Therefore if we can communicate the status of each
-gateway to each transport node, they can individually figure out which is the
-leader, and direct traffic accordingly.
-
-Tunnel Monitoring
-+++++++++++++++++
-
-Since in this model leadership is determined exclusively by the health status
-of member gateways, a key problem is how do we communicate this information to
-the relevant transport nodes.  Luckily, we can do this fairly cheaply using
-tunnel monitoring protocols like BFD.
-
-The basic idea is pretty straightforward.  Each transport node maintains a
-tunnel to every gateway in the HA cluster (not just the leader).  These tunnels
-are monitored using the BFD protocol to see which are alive.  Given this
-information, hypervisors can trivially compute the highest priority live
-gateway, and thus the leader.
-
-In practice, this leadership computation can be performed trivially using the
-bundle or group action.  Rather than using OpenFlow to simply output to the
-leader, all gateways could be listed in an active-backup bundle action ordered
-by their priority.  The bundle action will automatically take into account the
-tunnel monitoring status to output the packet to the highest priority live
-gateway.
-
-Inter-Gateway Monitoring
-++++++++++++++++++++++++
-
-One somewhat subtle aspect of this model, is that failovers are not globally
-atomic.  When a failover occurs, it will take some time for all hypervisors to
-notice and adjust accordingly.  Similarly, if a new high priority Gateway comes
-up, it may take some time for all hypervisors to switch over to the new leader.
-In order to avoid confusing the physical network, under these circumstances
-it's important for the backup gateways to drop traffic they've received
-erroneously.  In order to do this, each Gateway must know whether or not it is,
-in fact active.  This can be achieved by creating a mesh of tunnels between
-gateways.  Each gateway monitors the other gateways its cluster to determine
-which are alive, and therefore whether or not that gateway happens to be the
-leader.  If leading, the gateway forwards traffic normally, otherwise it drops
-all traffic.
-
-We should note that this method works well under the assumption that there
-are no inter-gateway connectivity failures, in such case this method would fail
-to elect a single master. The simplest example is two gateways which stop seeing
-each other but can still reach the hypervisors. Protocols like VRRP or CARP
-have the same issue. A mitigation for this type of failure mode could be
-achieved by having all network elements (hypervisors and gateways) periodically
-share their link status to other endpoints.
-
-Gateway Leadership Resignation
-++++++++++++++++++++++++++++++
-
-Sometimes a gateway may be healthy, but still may not be suitable to lead the
-HA cluster.  This could happen for several reasons including:
-
-* The physical network is unreachable
-
-* BFD (or ping) has detected the next hop router is unreachable
-
-* The Gateway recently booted and isn't fully configured
-
-In this case, the Gateway should resign leadership by holding its tunnels down
-using the ``other_config:cpath_down`` flag.  This indicates to participating
-hypervisors and Gateways that this gateway should be treated as if it's down,
-even though its tunnels are still healthy.
-
-Router Specific Active-Backup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    Router Specific Active-Backup
-
-    +----------------+ +----------------+
-    |                | |                |
-    |      A C       | |     B D E      |
-    |                | |                |
-    +----------------+ +----------------+
-                  ^ ^   ^ ^
-                  | |   | |
-                  | |   | |
-                  + +   + +
-                   Traffic
-
-Controller independent active-backup is a great advance over naive
-active-backup, but it still has one glaring problem -- it under-utilizes the
-backup gateways.  In ideal scenario, all traffic would split evenly among the
-live set of gateways.  Getting all the way there is somewhat tricky, but as a
-step in the direction, one could use the "Router Specific Active-Backup"
-algorithm.  This algorithm looks a lot like active-backup on a per logical
-router basis, with one twist.  It chooses a different active Gateway for each
-logical router.  Thus, in situations where there are several logical routers,
-all with somewhat balanced load, this algorithm performs better.
-
-Implementation of this strategy is quite straightforward if built on top of
-basic controller independent active-backup.  On a per logical router basis, the
-algorithm is the same, leadership is determined by the liveness of the
-gateways.  The key difference here is that the gateways must have a different
-leadership priority for each logical router.  These leadership priorities can
-be computed by ovn-northd just as they had been in the controller independent
-active-backup model.
-
-Once we have these per logical router priorities, they simply need be
-communicated to the members of the gateway cluster and the hypervisors.  The
-hypervisors in particular, need simply have an active-backup bundle action (or
-group action) per logical router listing the gateways in priority order for
-*that router*, rather than having a single bundle action shared for all the
-routers.
-
-Additionally, the gateways need to be updated to take into account individual
-router priorities.  Specifically, each gateway should drop traffic of backup
-routers it's running, and forward traffic of active gateways, instead of simply
-dropping or forwarding everything.  This should likely be done by having
-ovn-controller recompute OpenFlow for the gateway, though other options exist.
-
-The final complication is that ovn-northd's logic must be updated to choose
-these per logical router leadership priorities in a more sophisticated manner.
-It doesn't matter much exactly what algorithm it chooses to do this, beyond
-that it should provide good balancing in the common case.  I.E. each logical
-routers priorities should be different enough that routers balance to different
-gateways even when failures occur.
-
-Preemption
-++++++++++
-
-In an active-backup setup, one issue that users will run into is that of
-gateway leader preemption.  If a new Gateway is added to a cluster, or for some
-reason an existing gateway is rebooted, we could end up in a situation where
-the newly activated gateway has higher priority than any other in the HA
-cluster.  In this case, as soon as that gateway appears, it will preempt
-leadership from the currently active leader causing an unnecessary failover.
-Since failover can be quite expensive, this preemption may be undesirable.
-
-The controller can optionally avoid preemption by cleverly tweaking the
-leadership priorities.  For each router, new gateways should be assigned
-priorities that put them second in line or later when they eventually come up.
-Furthermore, if a gateway goes down for a significant period of time, its old
-leadership priorities should be revoked and new ones should be assigned as if
-it's a brand new gateway.  Note that this should only happen if a gateway has
-been down for a while (several minutes), otherwise a flapping gateway could
-have wide ranging, unpredictable, consequences.
-
-Note that preemption avoidance should be optional depending on the deployment.
-One necessarily sacrifices optimal load balancing to satisfy these requirements
-as new gateways will get no traffic on boot.  Thus, this feature represents a
-trade-off which must be made on a per installation basis.
-
-Fully Active-Active HA
-~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
-    Fully Active-Active HA
-
-    +----------------+ +----------------+
-    |                | |                |
-    |   A B C D E    | |    A B C D E   |
-    |                | |                |
-    +----------------+ +----------------+
-                  ^ ^   ^ ^
-                  | |   | |
-                  | |   | |
-                  + +   + +
-                   Traffic
-
-The final step in L3HA is to have true active-active HA.  In this scenario each
-router has an instance on each Gateway, and a mechanism similar to ECMP is used
-to distribute traffic evenly among all instances.  This mechanism would require
-Gateways to participate in routing protocols with the physical network to
-attract traffic and alert of failures.  It is out of scope of this document,
-but may eventually be necessary.
-
-L2HA
-----
-
-L2HA is very difficult to get right.  Unlike L3HA, where the consequences of
-problems are minor, in L2HA if two gateways are both transiently active, an L2
-loop triggers and a broadcast storm results.  In practice to get around this,
-gateways end up implementing an overly conservative "when in doubt drop all
-traffic" policy, or they implement something like MLAG.
-
-MLAG has multiple gateways work together to pretend to be a single L2 switch
-with a large LACP bond.  In principle, it's the right solution to the problem
-as it solves the broadcast storm problem, and has been deployed successfully in
-other contexts.  That said, it's difficult to get right and not recommended.
diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
index 057649dd7..fcb741637 100644
--- a/Documentation/topics/index.rst
+++ b/Documentation/topics/index.rst
@@ -27,7 +27,7 @@
 Deep Dive
 =========
 
-How Open vSwitch and OVN are implemented and, where necessary, why it was
+How Open vSwitch is implemented and, where necessary, why it was
 implemented that way.
 
 OVS
@@ -52,19 +52,3 @@ OVS
    tracing
    idl-compound-indexes
 
-OVN
----
-
-.. toctree::
-   :maxdepth: 2
-
-   high-availability
-   role-based-access-control
-   ovn-news-2.8
-
-.. list-table::
-
-   * - ovn-architecture(7)
-     - `(pdf) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.pdf>`__
-     - `(html) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.html>`__
-     - `(plain text) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.txt>`__
diff --git a/Documentation/topics/ovn-news-2.8.rst b/Documentation/topics/ovn-news-2.8.rst
deleted file mode 100644
index fae0a4278..000000000
--- a/Documentation/topics/ovn-news-2.8.rst
+++ /dev/null
@@ -1,278 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-===============================
-What's New with OVS and OVN 2.8
-===============================
-
-This document is about what was added in Open vSwitch 2.8, which was released
-at the end of August 2017, concentrating on the new features in OVN.  It also
-covers some of what is coming up in Open vSwitch and OVN 2.9, which is due to
-be released in February 2018.  OVN has many features, and this document does
-not cover every new or enhanced feature (but contributions are welcome).
-
-This document assumes a basic familiarity with Open vSwitch, OVN, and their
-associated tools.  For more information, please refer to the Open vSwitch and
-OVN documentation, such as the ``ovn-architecture``\(7) manpage.
-
-Debugging and Troubleshooting
------------------------------
-
-Before version 2.8, Open vSwitch command-line tools were far more painful to
-use than they needed to be.  This section covers the improvements made to the
-CLI in the 2.8 release.
-
-User-Hostile UUIDs
-~~~~~~~~~~~~~~~~~~
-
-The OVN CLI, through ``ovn-nbctl``, ``ovn-nbctl``, and ``ovn-trace``, used
-full-length UUIDs almost everywhere.  It didn't even provide any assistance
-with completion, etc., which in practice meant always cutting and pasting UUIDs
-from one command or window to another.  This problem wasn't limited to the
-places where one would expect to have to see or use a UUID, either.  In many
-places where one would expect to be able to use a network, router, or port
-name, a UUID was required instead.  In many places where one would want to see
-a name, the UUID was displayed instead.  More than anything else, these
-shortcomings made the CLI user-hostile.
-
-There was an underlying problem that the southbound database didn't actually
-contain all the information needed to provide a decent user interface.  In some
-cases, for example, the human-friendly names that one would want to use for
-entities simply weren't part of the database.  These names weren't necessary
-for correctness, only for usability.
-
-OVN 2.8 eased many of these problems.  Most parts of the CLI now allow the user
-to abbreviate UUIDs, as long as the abbreviations are unique within the
-database.  Some parts of the CLI where full-length UUIDs make output hard to
-read now abbreviate them themselves.  Perhaps more importantly, in many places
-the OVN CLI now displays and accepts human-friendly names for networks,
-routers, ports, and other entities.  In the places where the names were not
-previously available, OVN (through ``ovn-northd``) now copies the names into
-the southbound database.
-
-The CLIs for layers below OVN, at the OpenFlow and datapath layers with
-``ovs-ofctl`` and ``ovs-dpctl``, respectively, had some similar problems in
-which numbers were used for entities that had human-friendly names.  Open
-vSwitch 2.8 also solves some of those problems.  Other than that, the most
-notable enhancement in this area was the ``--no-stats`` option to ``ovs-ofctl
-dump-flows``, which made that command's output more readable for the cases
-where per-flow statistics were not interesting to the reader.
-
-Connections Between Levels
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-OVN and Open vSwitch work almost like a stack of compilers: the OVN Neutron
-plugin translates Neutron configuration into OVN northbound configuration,
-which ``ovn-northd`` translates into logical flows, which ``ovn-controller``
-translates into OpenFlow flows, which ``ovs-vswitchd`` translates into datapath
-flows.  For debugging and troubleshooting it is often necessary to understand
-exactly how these translations work.  The relationship from a logical flow to
-its OpenFlow flows, or in the other direction, from an OpenFlow flow back to
-the logical flow that produced it, was often of particular interest, but OVN
-didn't provide good tools for the job.
-
-OVN 2.8 added some new features that ease these jobs.  ``ovn-sbctl lflow-list``
-has a new option ``--ovs`` that lists the OpenFlow flows on a particular
-chassis that were generated from the logical flows that it lists.
-``ovn-trace`` also added a similar ``--ovs`` option that applies to the logical
-flows it traces.
-
-In the other direction, OVN 2.8 added a new utility ``ovn-detrace`` that, given
-an Open vSwitch trace of OpenFlow flows, annotates it with the logical flows
-that yielded those OpenFlow flows.
-
-Distributed Firewall
-~~~~~~~~~~~~~~~~~~~~
-
-OVN supports a distributed firewall with stateful connection tracking to ensure
-that only packets for established connections, or those that the configuration
-explicitly allows, can ingress a given VM or container.  Neutron uses this
-feature by default.  Most packets in an OpenStack environment pass through it
-twice, once after egress from the packet's source VM and once before ingress
-into its destination VM.  Before OVN 2.8, the ``ovn-trace`` program, which
-shows the path of a packet through an OVN logical network, did not support the
-logical firewall, which in practice made it almost useless for Neutron.
-
-In OVN 2.8, ``ovn-trace`` adds support for the logical firewall.  By default it
-assumes that packets are part of an established connection, which is usually
-what the user wants as part of the trace.  It also accepts command-line options
-to override that assumption, which allows the user to discover the treatment of
-packets that the firewall should drop.
-
-At the next level deeper, prior to Open vSwitch 2.8, the OpenFlow tracing
-command ``ofproto/trace`` also supported neither the connection tracking
-feature underlying the OVN distributed firewall nor the "recirculation" feature
-that accompanied it.  This meant that, even if the user tried to look deeper
-into the distributed firewall mechanism, he or she would encounter a further
-roadblock.  Open vSwitch 2.8 added support for both of these features as well.
-
-Summary Display
-~~~~~~~~~~~~~~~
-
-``ovn-nbctl show`` and ``ovn-sbctl show``, for showing an overview of the OVN
-configuration, didn't show a lot of important information.  OVN adds some more
-useful information here.
-
-DNS, and IPAM
--------------
-
-OVN 2.8 adds a built-in DNS server designed for assigning names to VMs and
-containers within an OVN logical network.  DNS names are assigned using records
-in the OVN northbound database and, like other OVN features, translated into
-logical flows at the OVN southbound layer.  DNS requests directed to the OVN
-DNS server never leave the hypervisor from which the request is sent; instead,
-OVN processes and replies to the request from its ``ovn-controller`` local
-agent.  The OVN DNS server is not a general-purpose DNS server and cannot be
-used for that purpose.
-
-OVN includes simple built-in support for IP address management (IPAM), in which
-OVN assigns IP addresses to VMs or containers from a pool or pools of IP
-addresses delegated to it by the administrator.  Before OVN 2.8, OVN IPAM only
-supported IPv4 addresses; OVN 2.8 adds support for IPv6.  OVN 2.8 also enhances
-the address pool support to allow specific addresses to be excluded.  Neutron
-assigns IP addresses itself and does not use OVN IPAM.
-
-High Availability
------------------
-
-As a distributed system, in OVN a lot can go wrong.  As OVN advances, it adds
-redundancy in places where currently a single failure could disrupt the
-functioning of the system as a whole.  OVN 2.8 adds two new kinds of high
-availability.
-
-ovn-northd HA
-~~~~~~~~~~~~~
-
-The ``ovn-northd`` program sits between the OVN northbound and southbound
-databases and translates from a logical network configuration into logical
-flows.  If ``ovn-northd`` itself or the host on which it runs fails, then
-updates to the OVN northbound configuration will not propagate to the
-hypervisors and the OVN configuration freezes in place until ``ovn-northd``
-restarts.
-
-OVN 2.8 adds support for active-backup HA to ``ovn-northd``.  When more than
-one ``ovn-northd`` instance runs, it uses an OVSDB locking feature to
-automatically choose a single active instance.  When that instance dies or
-becomes nonresponsive, the OVSDB server automatically choose one of the
-remaining instance(s) to take over.
-
-L3 Gateway HA
-~~~~~~~~~~~~~
-
-In OVN 2.8, multiple chassis may now be specified for L3 gateways.  When more
-than one chassis is specified, OVN manages high availability for that gateway.
-Each hypervisor uses the BFD protocol to keep track of the gateway nodes that
-are currently up.  At any given time, a hypervisor uses the highest-priority
-gateway node that is currently up.
-
-OVSDB
------
-
-The OVN architecture relies heavily on OVSDB, the Open vSwitch database, for
-hosting the northbound and southbound databases.  OVSDB was originally selected
-for this purpose because it was already used in Open vSwitch for configuring
-OVS itself and, thus, it was well integrated with OVS and well supported in C
-and Python, the two languages that are used in Open vSwitch.
-
-OVSDB was well designed for its original purpose of configuring Open vSwitch.
-It supports ACID transactions, has a small, efficient server, a flexible schema
-system, and good support for troubleshooting and debugging.  However, it lacked
-several features that are important for OVN but not for Open vSwitch.  As OVN
-advances, these missing features have become more and more of a problem.  One
-option would be to switch to a different database that already has many of
-these features, but despite a careful search, no ideal existing database was
-identified, so the project chose instead to improve OVSDB where necessary to
-bring it up to speed.  The following sections talk more about recent and future
-improvements.
-
-High Availability
-~~~~~~~~~~~~~~~~~
-
-When ``ovsdb-server`` was only used for OVS configuration, high availability
-was not important.  ``ovsdb-server`` was capable of restarting itself
-automatically if it crashed, and if the whole system went down then Open
-vSwitch itself was dead too, so the database server's failure was not
-important.
-
-In contrast, the northbound and southbound databases are centralized components
-of a distributed system, so it is important that they not be a single point of
-failure for the system as a whole.  In released versions of OVN,
-``ovsdb-server`` supports only "active-backup replication" across a pair of
-servers.  This means that if one server goes down, the other can pick it back
-up approximately where the other one left off.  The servers do not have
-built-in support for deciding at any given time which is the active and which
-the backup, so the administrator must configure an external agent to do this
-management.
-
-Active-backup replication is not entirely satisfactory, for multiple reasons.
-Replication is only approximate.  Configuring the external agent requires extra
-work.  There is no benefit from the backup server except when the active server
-fails.  At most two servers can be used.
-
-A new form of high availability for OVSDB is under development for the OVN 2.9
-release, based on the Raft algorithm for distributed consensus.  Whereas
-replication uses two servers, clustering using Raft requires three or more
-(typically an odd number) and continues functioning as long as more than half
-of the servers are up.  The clustering implementation is built into
-``ovsdb-server`` and does not require an external agent.  Clustering preserves
-the ACID properties of the database, so that a transaction that commits is
-guaranteed to persist.  Finally, reads (which are the bulk of the OVN workload)
-scale with the size of the cluster, so that adding more servers should improve
-performance as the number of hypervisors in an OVN deployment increases.  As of
-this writing, OVSDB support for clustering is undergoing development and early
-deployment testing.
-
-RBAC security
-~~~~~~~~~~~~~
-
-Until Open vSwitch 2.8, ``ovsdb-server`` had little support for access control
-within a database.  If an OVSDB client could modify the database at all, it
-could make arbitrary changes.  This was sufficient for most uses case to that
-point.
-
-Hypervisors in an OVN deployment need access to the OVN southbound database.
-Most of their access is reads, to find out about the OVN configuration.
-Hypervisors do need some write access to the southbound database, primarily to
-let the other hypervisors know what VMs and containers they are running and how
-to reach them.  Thus, OVN gives all of the hypervisors in the OVN deployment
-write access to the OVN southbound database.  This is fine when all is well,
-but if any of the hypervisors were compromised then they could disrupt the
-entire OVN deployment by corrupting the database.
-
-The OVN developers considered a few ways to solve this problem.  One way would
-be to introduce a new central service (perhaps in ``ovn-northd``) that provided
-only the kinds of writes that the hypervisors legitimately need, and then grant
-hypervisors direct access to the southbound database only for reads.  But
-ultimately the developers decided to introduce a new form of more access
-control for OVSDB, called the OVSDB RBAC (role-based access control) feature.
-OVSDB RBAC allows for granular enough control over access that hypervisors can
-be granted only the ability to add, modify, and delete the records that relate
-to themselves, preventing them from corrupting the database as a whole.
-
-Further Directions
-------------------
-
-For more information about new features in OVN and Open vSwitch, please refer
-to the NEWS file distributed with the source tree.  If you have questions about
-Open vSwitch or OVN features, please feel free to write to the Open vSwitch
-discussion mailing list at ovs-discuss at openvswitch.org.
diff --git a/Documentation/topics/role-based-access-control.rst b/Documentation/topics/role-based-access-control.rst
deleted file mode 100644
index 8f2a3a998..000000000
--- a/Documentation/topics/role-based-access-control.rst
+++ /dev/null
@@ -1,101 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-=========================
-Role Based Access Control
-=========================
-
-Where SSL provides authentication when connecting to an OVS database, role
-based access control (RBAC) provides authorization to operations performed
-by clients connecting to an OVS database. RBAC allows for administrators to
-restrict the database operations a client may perform and thus enhance the
-security already provided by SSL.
-
-In theory, any OVS database could define RBAC roles and permissions, but at
-present only the OVN southbound database has the appropriate tables defined to
-facilitate RBAC.
-
-Mechanics
----------
-RBAC is intended to supplement SSL. In order to enable RBAC, the connection to
-the database must use SSL. Some permissions in RBAC are granted based on the
-certificate common name (CN) of the connecting client.
-
-RBAC is controlled with two database tables, RBAC_Role and RBAC_Permission.
-The RBAC_Permission table contains records that describe a set of permissions
-for a given table in the database.
-
-The RBAC_Permission table contains the following columns:
-
-table
-  The table in the database for which permissions are being described.
-insert_delete
-  Describes whether insertion and deletion of records is allowed.
-update
-  A list of columns that are allowed to be updated.
-authorization
-  A list of column names. One of the listed columns must match the SSL
-  certificate CN in order for the attempted operation on the table to
-  succeed. If a key-value pair is provided, then the key is the column name,
-  and the value is the name of a key in that column. An empty string gives
-  permission to all clients to perform operations.
-
-The RBAC_Role table contains the following columns:
-
-name
-  The name of the role being defined
-permissions
-  A list of key-value pairs. The key is the name of a table in the database,
-  and the value is a UUID of a record in the RBAC_Permission table that
-  describes the permissions the role has for that table.
-
-.. note::
-
-   All tables not explicitly referenced in an RBAC_Role record are read-only
-
-In order to enable RBAC, specify the role name as an argument to the
-set-connection command for the database. As an example, to enable the
-"ovn-controller" role on the OVN southbound database, use the following
-command:
-
-::
-
-   $ ovn-sbctl set-connection role=ovn-controller ssl:192.168.0.1:6642
-
-Pre-defined Roles
------------------
-This section describes roles that have been defined internally by OVS/OVN.
-
-ovn-controller
-~~~~~~~~~~~~~~
-The ovn-controller role is specified in the OVN southbound database and is
-intended for use by hypervisors running the ovn-controller daemon.
-ovn-controller connects to the OVN southbound database mostly to read
-information, but there are a few cases where ovn-controller also needs to
-write. The ovn-controller role was designed to allow for ovn-controllers
-to write to the southbound database only in places where it makes sense to do
-so. This way, if an intruder were to take over a hypervisor running
-ovn-controller, it is more difficult to compromise the entire overlay network.
-
-It is strongly recommended to set the ovn-controller role for the OVN
-southbound database to enhance security.
diff --git a/Documentation/tutorials/index.rst b/Documentation/tutorials/index.rst
index 35340ee56..5ec62beab 100644
--- a/Documentation/tutorials/index.rst
+++ b/Documentation/tutorials/index.rst
@@ -27,8 +27,7 @@
 Tutorials
 =========
 
-Getting started with Open vSwitch (OVS) and Open Virtual Network (OVN) for Open
-vSwitch.
+Getting started with Open vSwitch (OVS).
 
 .. TODO(stephenfin): We could really do with a few basic tutorials, along with
    some more specialized ones (DPDK, XenServer, Windows). The latter could
@@ -42,8 +41,4 @@ vSwitch.
    faucet
    ipsec
    ovs-advanced
-   ovn-sandbox
-   ovn-openstack
-   ovn-rbac
-   ovn-ipsec
    ovs-conntrack
diff --git a/Documentation/tutorials/ovn-ipsec.rst b/Documentation/tutorials/ovn-ipsec.rst
deleted file mode 100644
index feb695ea3..000000000
--- a/Documentation/tutorials/ovn-ipsec.rst
+++ /dev/null
@@ -1,146 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-==================
-OVN IPsec Tutorial
-==================
-
-This document provides a step-by-step guide for encrypting tunnel traffic with
-IPsec in Open Virtual Network (OVN). OVN tunnel traffic is transported by
-physical routers and switches. These physical devices could be untrusted
-(devices in public network) or might be compromised.  Enabling IPsec encryption
-for the tunnel traffic can prevent the traffic data from being monitored and
-manipulated. More details about the OVN IPsec design can be found in
-``ovn-architecture``\(7) manpage.
-
-This document assumes OVN is installed in your system and runs normally. Also,
-you need to install OVS IPsec packages in each chassis (refer to
-:ref:`install-ovs-ipsec`).
-
-Generating Certificates and Keys
---------------------------------
-
-OVN chassis uses CA-signed certificate to authenticate peer chassis for
-building IPsec tunnel. If you have enabled Role-Based Access Control (RBAC) in
-OVN, you can use the RBAC SSL certificates and keys to set up OVN IPsec. Or you
-can generate separate certificates and keys with ``ovs-pki`` (refer to
-:ref:`gen-certs-keys`).
-
-.. note::
-
-   OVN IPsec requires x.509 version 3 certificate with the subjectAltName DNS
-   field setting the same string as the common name (CN) field. CN should be
-   set as the chassis name.  ``ovs-pki`` in Open vSwitch 2.10.90 and later
-   generates such certificates.  Please generate compatible certificates if you
-   use another PKI tool, or an older version of ``ovs-pki``, to manage
-   certificates.
-
-Configuring OVN IPsec
----------------------
-
-You need to install the CA certificate, chassis certificate and private key in
-each chassis. Use the following command::
-
-    $ ovs-vsctl set Open_vSwitch . \
-            other_config:certificate=/path/to/chassis-cert.pem \
-            other_config:private_key=/path/to/chassis-privkey.pem \
-            other_config:ca_cert=/path/to/cacert.pem
-
-Enabling OVN IPsec
-------------------
-
-To enable OVN IPsec, set ``ipsec`` column in ``NB_Global`` table of the
-northbound database to true::
-
-    $ ovn-nbctl set nb_global . ipsec=true
-
-With OVN IPsec enabled, all tunnel traffic in OVN will be encrypted with IPsec.
-To disable it, set ``ipsec`` column in ``NB_Global`` table of the northbound
-database to false::
-
-    $ ovn-nbctl set nb_global . ipsec=false
-
-Troubleshooting
----------------
-
-The ``ovs-monitor-ipsec`` daemon in each chassis manages and monitors the IPsec
-tunnel state. Use the following ``ovs-appctl`` command to view
-``ovs-monitor-ipsec`` internal representation of tunnel configuration::
-
-    $ ovs-appctl -t ovs-monitor-ipsec tunnels/show
-
-If there is a misconfiguration, then ``ovs-appctl`` should indicate why.
-For example::
-
-   Interface name: ovn-host_2-0 v1 (CONFIGURED) <--- Should be set
-                                             to CONFIGURED. Otherwise,
-                                             error message will be
-                                             provided
-   Tunnel Type:    geneve
-   Remote IP:      2.2.2.2
-   SKB mark:       None
-   Local cert:     /path/to/chassis-cert.pem
-   Local name:     host_1
-   Local key:      /path/to/chassis-privkey.pem
-   Remote cert:    None
-   Remote name:    host_2
-   CA cert:        /path/to/cacert.pem
-   PSK:            None
-   Ofport:         2          <--- Whether ovs-vswitchd has assigned Ofport
-                                   number to this Tunnel Port
-   CFM state:      Disabled     <--- Whether CFM declared this tunnel healthy
-   Kernel policies installed:
-   ...                          <--- IPsec policies for this OVS tunnel in
-                                     Linux Kernel installed by strongSwan
-   Kernel security associations installed:
-   ...                          <--- IPsec security associations for this OVS
-                                     tunnel in Linux Kernel installed by
-                                     strongswan
-   IPsec connections that are active:
-   ...                          <--- IPsec "connections" for this OVS
-                                     tunnel
-
-If you don't see any active connections, try to run the following command to
-refresh the ``ovs-monitor-ipsec`` daemon::
-
-    $ ovs-appctl -t ovs-monitor-ipsec refresh
-
-You can also check the logs of the ``ovs-monitor-ipsec`` daemon and the IKE
-daemon to locate issues.  ``ovs-monitor-ipsec`` outputs log messages to
-``/var/log/openvswitch/ovs-monitor-ipsec.log``.
-
-Bug Reporting
--------------
-
-If you think you may have found a bug with security implications, like
-
-1. IPsec protected tunnel accepted packets that came unencrypted; OR
-2. IPsec protected tunnel allowed packets to leave unencrypted;
-
-Then report such bugs according to :doc:`/internals/security`.
-
-If bug does not have security implications, then report it according to
-instructions in :doc:`/internals/bugs`.
-
-If you have suggestions to improve this tutorial, please send a email to
-ovs-discuss at openvswitch.org.
diff --git a/Documentation/tutorials/ovn-openstack.rst b/Documentation/tutorials/ovn-openstack.rst
deleted file mode 100644
index c6dff5e55..000000000
--- a/Documentation/tutorials/ovn-openstack.rst
+++ /dev/null
@@ -1,1922 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-======================
-OVN OpenStack Tutorial
-======================
-
-This tutorial demonstrates how OVN works in an OpenStack "DevStack"
-environment.  It was tested with the "master" branches of DevStack and
-Open vSwitch near the beginning of May 2017.  Anyone using an earlier
-version is likely to encounter some differences.  In particular, we
-noticed some shortcomings in OVN utilities while writing the tutorial
-and pushed out some improvements, so it's best to use recent Open
-vSwitch at least from that point of view.
-
-The goal of this tutorial is to demonstrate OVN in an end-to-end way,
-that is, to show how it works from the cloud management system at the
-top (in this case, OpenStack and specifically its Neutron networking
-subsystem), through the OVN northbound and southbound databases, to
-the bottom at the OVN local controller and Open vSwitch data plane.
-We hope that this demonstration makes it easier for users and
-potential users to understand how OVN works and how to debug and
-troubleshoot it.
-
-In addition to new material, this tutorial incorporates content from
-``testing.rst`` in OpenStack networking-ovn, by Russell Bryant and
-others.  Without that example, this tutorial could not have been
-written.
-
-We provide enough details in the tutorial that you should be able to
-fully follow along, by creating a DevStack VM and cloning DevStack and
-so on.  If you want to do this, start out from `Setting Up DevStack`_
-below.
-
-Setting Up DevStack
--------------------
-
-This section explains how to install DevStack, a kind of OpenStack
-packaging for developers, in a way that allows you to follow along
-with the tutorial in full.
-
-Unless you have a spare computer laying about, it's easiest to install
-DevStacck in a virtual machine.  This tutorial was built using a VM
-implemented by KVM and managed by virt-manager.  I recommend
-configuring the VM configured for the x86-64 architecture, 4 GB RAM, 2
-VCPUs, and a 20 GB virtual disk.
-
-.. note::
-
-   If you happen to run your Linux-based host with 32-bit userspace,
-   then you will have some special issues, even if you use a 64-bit
-   kernel:
-
-   * You may find that you can get 32-bit DevStack VMs to work to some
-     extent, but I personally got tired of finding workarounds.  I
-     recommend running your VMs in 64-bit mode.  To get this to work,
-     I had to go to the CPUs tab for the VM configuration in
-     virt-manager and change the CPU model from the one originally
-     listed to "Hypervisor Default' (it is curious that this is not
-     the default!).
-
-   * On a host with 32-bit userspace, KVM supports VMs with at most
-     2047 MB RAM.  This is adequate, barely, to start DevStack, but it
-     is not enough to run multiple (nested) VMs.  To prevent
-     out-of-memory failures, set up extra swap space in the guest.
-     For example, to add 2 GB swap::
-
-       $ sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
-       $ sudo mkswap /swapfile
-       $ sudo swapon /swapfile
-
-     and then add a line like this to ``/etc/fstab`` to add the new
-     swap automatically upon reboot::
-
-       /swapfile swap swap defaults 0 0
-
-Here are step-by-step instructions to get started:
-
-1. Install a VM.
-
-   I tested these instructions with Centos 7.3.  Download the "minimal
-   install" ISO and booted it.  The install is straightforward.  Be
-   sure to enable networking, and set a host name, such as
-   "ovn-devstack-1".  Add a regular (non-root) user, and check the box
-   "Make this user administrator".  Also, set your time zone.
-
-2. You can SSH into the DevStack VM, instead of running from a
-   console.  I recommend it because it's easier to cut and paste
-   commands into a terminal than a VM console.  You might also
-   consider using a very wide terminal, perhaps 160 columns, to keep
-   tables from wrapping.
-
-   To improve convenience further, you can make it easier to log in
-   with the following steps, which are optional:
-
-   a. On your host, edit your ``~/.ssh/config``, adding lines like
-      the following::
-
-        Host ovn-devstack-1
-              Hostname VMIP
-              User VMUSER
-
-      where VMIP is the VM's IP address and VMUSER is your username
-      inside the VM.  (You can omit the ``User`` line if your
-      username is the same in the host and the VM.)  After you do
-      this, you can SSH to the VM by name, e.g. ``ssh
-      ovn-devstack-1``, and if command-line completion is set up in
-      your host shell, you can shorten that to something like ``ssh
-      ovn`` followed by hitting the Tab key.
-
-   b. If you have SSH public key authentication set up, with an SSH
-      agent, run on your host::
-
-        $ ssh-copy-id ovn-devstack-1
-
-      and type your password once.  Afterward, you can log in without
-      typing your password again.
-
-      (If you don't already use SSH public key authentication and an
-      agent, consider looking into it--it will save you time in the
-      long run.)
-
-   c. Optionally, inside the VM, append the following to your
-      ``~/.bash_profile``::
-
-        . $HOME/devstack/openrc admin
-
-      It will save you running it by hand each time you log in.  But
-      it also prints garbage to the console, which can screw up
-      services like ``ssh-copy-id``, so be careful.
-
-2. Boot into the installed system and log in as the regular user, then
-   install Git::
-
-     $ sudo yum install git
-
-   .. note::
-
-      If you installed a 32-bit i386 guest (against the advice above),
-      install a non-PAE kernel and reboot into it at this point::
-
-           $ sudo yum install kernel-core kernel-devel
-           $ sudo reboot
-
-      Be sure to select the non-PAE kernel from the list at boot.
-      Without this step, DevStack will fail to install properly later.
-
-3. Get copies of DevStack and OVN and set them up::
-
-     $ git clone http://git.openstack.org/openstack-dev/devstack.git
-     $ git clone http://git.openstack.org/openstack/networking-ovn.git
-     $ cd devstack
-     $ cp ../networking-ovn/devstack/local.conf.sample local.conf
-
-   .. note::
-
-      If you installed a 32-bit i386 guest (against the advice above),
-      at this point edit ``local.conf`` to add the following line::
-
-        CIRROS_ARCH=i386
-
-4. Initialize DevStack::
-
-     $ ./stack.sh
-
-   This will spew many screenfuls of text, and the first time you run
-   it, it will download lots of software from the Internet.  The
-   output should eventually end with something like this::
-
-     This is your host IP address: 172.16.189.6
-     This is your host IPv6 address: ::1
-     Horizon is now available at http://172.16.189.6/dashboard
-     Keystone is serving at http://172.16.189.6/identity/
-     The default users are: admin and demo
-     The password: password
-     2017-03-09 15:10:54.117 | stack.sh completed in 2110 seconds.
-
-   If there's some kind of failure, you can restart by running
-   ``./stack.sh`` again.  It won't restart exactly where it left off,
-   but steps up to the one where it failed will skip the download
-   steps.  (Sometimes blindly restarting after a failure will allow it
-   to succeed.)  If you reboot your VM, you need to rerun this
-   command.  (If you run into trouble with ``stack.sh`` after
-   rebooting your VM, try running ``./unstack.sh``.)
-
-   At this point you can navigate a web browser on your host to the
-   Horizon dashboard URL.  Many OpenStack operations can be initiated
-   from this UI.  Feel free to explore, but this tutorial focuses on
-   the alternative command-line interfaces because they are easier to
-   explain and to cut and paste.
-
-5. As of this writing, you need to run the following to fix a problem
-   with using VM consoles from the OpenStack web instance::
-
-     $ (cd /opt/stack/noVNC && git checkout v0.6.0)
-
-   See
-   https://serenity-networks.com/how-to-fix-setkeycodes-00-and-unknown-key-pressed-console-errors-on-openstack/
-   for more details.
-
-6. The firewall in the VM by default allows SSH access but not HTTP.
-   You will probably want HTTP access to use the OpenStack web
-   interface.  The following command enables that.  (It also enables
-   every other kind of network access, so if you're concerned about
-   security then you might want to find a more targeted approach.)
-
-   ::
-
-      $ sudo iptables -F
-
-   (You need to re-run this if you reboot the VM.)
-
-7. To use OpenStack command line utilities in the tutorial, run::
-
-     $ . ~/devstack/openrc admin
-
-   This needs to be re-run each time you log in (but see the following
-   section).
-
-DevStack preliminaries
-----------------------
-
-Before we really jump in, let's set up a couple of things in DevStack.
-This is the first real test that DevStack is working, so if you get
-errors from any of these commands, it's a sign that ``stack.sh``
-didn't finish properly, or perhaps that you didn't run the ``openrc
-admin`` command at the end of the previous instructions.
-
-If you stop and restart DevStack via ``unstack.sh`` followed by
-``stack.sh``, you have to rerun these steps.
-
-1. For SSH access to the VMs we're going to create, we'll need a SSH
-   keypair.  Later on, we'll get OpenStack to install this keypair
-   into VMs.  Create one with::
-
-     $ openstack keypair create demo > ~/id_rsa_demo
-     $ chmod 600 ~/id_rsa_demo
-
-2. By default, DevStack security groups drop incoming traffic, but to
-   test networking in a reasonable way we need to enable it.  You only
-   need to actually edit one particular security group, but DevStack
-   creates multiple and it's somewhat difficult to figure out which
-   one is important because all of them are named "default".  So, the
-   following adds rules to allow SSH and ICMP traffic into **every**
-   security group::
-
-     $ for group in $(openstack security group list -f value -c ID); do \
-     openstack security group rule create --ingress --ethertype IPv4 --dst-port 22 --protocol tcp $group; \
-     openstack security group rule create --ingress --ethertype IPv4 --protocol ICMP $group; \
-     done
-
-3. Later on, we're going to create some VMs and we'll need an
-   operating system image to install.  DevStack comes with a very
-   simple image built-in, called "cirros", which works fine.  We need
-   to get the UUID for this image.  Our later commands assume shell
-   variable ``IMAGE_ID`` holds this UUID.  You can set this by hand,
-   e.g.::
-
-     $ openstack image list
-     +--------------------------------------+--------------------------+--------+
-     | ID                                   | Name                     | Status |
-     +--------------------------------------+--------------------------+--------+
-     | 77f37d2c-3d6b-4e99-a01b-1fa5d78d1fa1 | cirros-0.3.5-x86_64-disk | active |
-     +--------------------------------------+--------------------------+--------+
-     $ IMAGE_ID=73ca34f3-63c4-4c10-a62f-4540afc24eaa
-
-   or by parsing CLI output::
-
-     $ IMAGE_ID=$(openstack image list -f value -c ID)
-
-   .. note::
-
-      Your image ID will differ from the one above, as will every UUID
-      in this tutorial.  They will also change every time you run
-      ``stack.sh``.  The UUIDs are generated randomly.
-
-Shortening UUIDs
-----------------
-
-OpenStack, OVN, and Open vSwitch all really like UUIDs.  These are
-great for uniqueness, but 36-character strings are terrible for
-readability.  Statistically, just the first few characters are enough
-for uniqueness in small environments, so let's define a helper to make
-things more readable::
-
-  $ abbrev() { a='[0-9a-fA-F]' b=$a$a c=$b$b; sed "s/$b-$c-$c-$c-$c$c$c//g"; }
-
-You can use this as a filter to abbreviate UUIDs.  For example, use it
-to abbreviate the above image list::
-
-  $ openstack image list -f yaml | abbrev
-  - ID: 77f37d
-    Name: cirros-0.3.5-x86_64-disk
-    Status: active
-
-The command above also adds ``-f yaml`` to switch to YAML output
-format, because abbreviating UUIDs screws up the default table-based
-formatting and because YAML output doesn't produce wrap columns across
-lines and therefore is easier to cut and paste.
-
-Overview
---------
-
-Now that DevStack is ready, with OVN set up as the networking
-back-end, here's an overview of what we're going to do in the
-remainder of the demo, all via OpenStack:
-
-1. Switching: Create an OpenStack network ``n1`` and VMs ``a`` and
-   ``b`` attached to it.
-
-   An OpenStack network is a virtual switch; it corresponds to an OVN
-   logical switch.
-
-2. Routing: Create a second OpenStack network ``n2`` and VM ``c``
-   attached to it, then connect it to network ``n1`` by creating an
-   OpenStack router and attaching ``n1`` and ``n2`` to it.
-
-3. Gateways: Make VMs ``a`` and ``b`` available via an external network.
-
-4. IPv6: Add IPv6 addresses to our VMs to demonstrate OVN support for
-   IPv6 routing.
-
-5. ACLs: Add and modify OpenStack stateless and stateful rules in
-   security groups.
-
-6. DHCP: How it works in OVN.
-
-7. Further directions: Adding more compute nodes.
-
-At each step, we will take a look at how the features in question work
-from OpenStack's Neutron networking layer at the top to the data plane
-layer at the bottom.  From the highest to lowest level, these layers
-and the software components that connect them are:
-
-* OpenStack Neutron, which as the top level in the system is the
-  authoritative source of the virtual network configuration.
-
-  We will use OpenStack's ``openstack`` utility to observe and modify
-  Neutron and other OpenStack configuration.
-
-* networking-ovn, the Neutron driver that interfaces with OVN and
-  translates the internal Neutron representation of the virtual
-  network into OVN's representation and pushes that representation
-  down the OVN northbound database.
-
-  In this tutorial it's rarely worth distinguishing Neutron from
-  networking-ovn, so we usually don't break out this layer separately.
-
-* The OVN Northbound database, aka NB DB.  This is an instance of
-  OVSDB, a simple general-purpose database that is used for multiple
-  purposes in Open vSwitch and OVN.  The NB DB's schema is in terms of
-  networking concepts such as switches and routers.  The NB DB serves
-  the purpose that in other systems might be filled by some kind of
-  API; for example, in place of calling an API to create or delete a
-  logical switch, networking-ovn performs these operations by
-  inserting or deleting a row in the NB DB's Logical_Switch table.
-
-  We will use OVN's ``ovn-nbctl`` utility to observe the NB DB.  (We
-  won't directly modify data at this layer or below.  Because
-  configuration trickles down from Neutron through the stack, the
-  right way to make changes is to use the ``openstack`` utility or
-  another OpenStack interface and then wait for them to percolate
-  through to lower layers.)
-
-* The ovn-northd daemon, a program that runs centrally and translates
-  the NB DB's network representation into the lower-level
-  representation used by the OVN Southbound database in the next
-  layer.  The details of this daemon are usually not of interest,
-  although without it OVN will not work, so this tutorial does not
-  often mention it.
-
-* The OVN Southbound database, aka SB DB, which is also an OVSDB
-  database.  Its schema is very different from the NB DB.  Instead of
-  familiar networking concepts, the SB DB defines the network in terms
-  of collections of match-action rules called "logical flows", which
-  while similar in concept to OpenFlow flows use logical concepts, such
-  as virtual machine instances, in place of physical concepts like
-  physical Ethernet ports.
-
-  We will use OVN's ``ovn-sbctl`` utility to observe the SB DB.
-
-* The ovn-controller daemon.  A copy of ovn-controller runs on each
-  hypervisor.  It reads logical flows from the SB DB, translates them
-  into OpenFlow flows, and sends them to Open vSwitch's ovs-vswitchd
-  daemon.  Like ovn-northd, usually the details of what this daemon
-  are not of interest, even though it's important to the operation of
-  the system.
-
-* ovs-vswitchd.  This program runs on each hypervisor.  It is the core
-  of Open vSwitch, which processes packets according to the OpenFlow
-  flows set up by ovn-controller.
-
-* Open vSwitch datapath.  This is essentially a cache designed to
-  accelerate packet processing.  Open vSwitch includes a few different
-  datapaths but OVN installations typically use one based on the Open
-  vSwitch Linux kernel module.
-
-Switching
----------
-
-Switching is the basis of networking in the real world and in virtual
-networking as well.  OpenStack calls its concept of a virtual switch a
-"network", and OVN calls its corresponding concept a "logical switch".
-
-In this step, we'll create an OpenStack network ``n1``, then create
-VMs ``a`` and ``b`` and attach them to ``n1``.
-
-Creating network ``n1``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Let's start by creating the network::
-
-  $ openstack network create --project admin --provider-network-type geneve n1
-
-OpenStack needs to know the subnets that a network serves.  We inform
-it by creating subnet objects.  To keep it simple, let's give our
-network a single subnet for the 10.1.1.0/24 network.  We have to give
-it a name, in this case ``n1subnet``::
-
-  $ openstack subnet create --subnet-range 10.1.1.0/24 --network n1 n1subnet
-
-If you ask Neutron to show us the available networks, we see ``n1`` as
-well as the two networks that DevStack creates by default::
-
-  $ openstack network list -f yaml | abbrev
-  - ID: 5b6baf
-    Name: n1
-    Subnets: 5e67e7
-  - ID: c02c4d
-    Name: private
-    Subnets: d88a34, fd87f9
-  - ID: d1ac28
-    Name: public
-    Subnets: 0b1e79, c87dc1
-
-Neutron pushes this network setup down to the OVN northbound
-database.  We can use ``ovn-nbctl show`` to see an overview of what's
-in the NB DB::
-
-  $ ovn-nbctl show | abbrev
-  switch 5b3d5f (neutron-c02c4d) (aka private)
-      port b256dd
-          type: router
-          router-port: lrp-b256dd
-      port f264e7
-          type: router
-          router-port: lrp-f264e7
-  switch 2579f4 (neutron-d1ac28) (aka public)
-      port provnet-d1ac28
-          type: localnet
-          addresses: ["unknown"]
-      port ae9b52
-          type: router
-          router-port: lrp-ae9b52
-  switch 3eb263 (neutron-5b6baf) (aka n1)
-  router c59ad2 (neutron-9b057f) (aka router1)
-      port lrp-ae9b52
-          mac: "fa:16:3e:b2:d2:67"
-          networks: ["172.24.4.9/24", "2001:db8::b/64"]
-      port lrp-b256dd
-          mac: "fa:16:3e:35:33:db"
-          networks: ["fdb0:5860:4ba8::1/64"]
-      port lrp-f264e7
-          mac: "fa:16:3e:fc:c8:da"
-          networks: ["10.0.0.1/26"]
-      nat 80914c
-          external ip: "172.24.4.9"
-          logical ip: "10.0.0.0/26"
-          type: "snat"
-
-This output shows that OVN has three logical switches, each of which
-corresponds to a Neutron network, and a logical router that
-corresponds to the Neutron router that DevStack creates by default.
-The logical switch that corresponds to our new network ``n1`` has no
-ports yet, because we haven't added any.  The ``public`` and
-``private`` networks that DevStack creates by default have router
-ports that connect to the logical router.
-
-Using ovn-northd, OVN translates the NB DB's high-level switch and
-router concepts into lower-level concepts of "logical datapaths" and
-logical flows.  There's one logical datapath for each logical switch
-or router::
-
-  $ ovn-sbctl list datapath_binding | abbrev
-  _uuid               : 0ad69d
-  external_ids        : {logical-switch="5b3d5f", name="neutron-c02c4d", "name2"=private}
-  tunnel_key          : 1
-
-  _uuid               : a8a758
-  external_ids        : {logical-switch="3eb263", name="neutron-5b6baf", "name2"="n1"}
-  tunnel_key          : 4
-
-  _uuid               : 191256
-  external_ids        : {logical-switch="2579f4", name="neutron-d1ac28", "name2"=public}
-  tunnel_key          : 3
-
-  _uuid               : b87bec
-  external_ids        : {logical-router="c59ad2", name="neutron-9b057f", "name2"="router1"}
-  tunnel_key          : 2
-
-This output lists the NB DB UUIDs in external_ids:logical-switch and
-Neutron UUIDs in externals_ids:uuid.  We can dive in deeper by viewing
-the OVN logical flows that implement a logical switch.  Our new
-logical switch is a simple and almost pathological example given that
-it doesn't yet have any ports attached to it.  We'll look at the
-details a bit later::
-
-  $ ovn-sbctl lflow-list n1 | abbrev
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: ingress
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(eth.src[40]), action=(drop;)
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(vlan.present), action=(drop;)
-  ...
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: egress
-    table=0 (ls_out_pre_lb      ), priority=0    , match=(1), action=(next;)
-    table=1 (ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
-  ...
-
-We have one hypervisor (aka "compute node", in OpenStack parlance),
-which is the one where we're running all these commands.  On this
-hypervisor, ovn-controller is translating OVN logical flows into
-OpenFlow flows ("physical flows").  It makes sense to go deeper, to
-see the OpenFlow flows that get generated from this datapath.  By
-adding ``--ovs`` to the ``ovn-sbctl`` command, we can see OpenFlow
-flows listed just below their logical flows.  We also need to use
-``sudo`` because connecting to Open vSwitch is privileged.  Go ahead
-and try it::
-
-  $ sudo ovn-sbctl --ovs lflow-list n1 | abbrev
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: ingress
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(eth.src[40]), action=(drop;)
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(vlan.present), action=(drop;)
-  ...
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: egress
-    table=0 (ls_out_pre_lb      ), priority=0    , match=(1), action=(next;)
-    table=1 (ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
-  ...
-
-You were probably disappointed: the output didn't change, and no
-OpenFlow flows were printed.  That's because no OpenFlow flows are
-installed for this logical datapath, which in turn is because there
-are no VIFs for this logical datapath on the local hypervisor.  For a
-better example, you can try ``ovn-sbctl --ovs`` on one of the other
-logical datapaths.
-
-Attaching VMs
-~~~~~~~~~~~~~
-
-A switch without any ports is not very interesting.  Let's create a
-couple of VMs and attach them to the switch.  Run the following
-commands, which create VMs named ``a`` and ``b`` and attaches them to
-our network ``n1`` with IP addresses 10.1.1.5 and 10.1.1.6,
-respectively.  It is not actually necessary to manually assign IP
-address assignments, since OpenStack is perfectly happy to assign them
-itself from the subnet's IP address range, but predictable addresses
-are useful for our discussion::
-
-  $ openstack server create --nic net-id=n1,v4-fixed-ip=10.1.1.5 --flavor m1.nano --image $IMAGE_ID --key-name demo a
-  $ openstack server create --nic net-id=n1,v4-fixed-ip=10.1.1.6 --flavor m1.nano --image $IMAGE_ID --key-name demo b
-
-These commands return before the VMs are really finished being built.
-You can run ``openstack server list`` a few times until each of them
-is shown in the state ACTIVE, which means that they're not just built
-but already running on the local hypervisor.
-
-These operations had the side effect of creating separate "port"
-objects, but without giving those ports any easy-to-read names.  It'll
-be easier to deal with them later if we can refer to them by name, so
-let's name ``a``'s port ``ap`` and ``b``'s port ``bp``::
-
-  $ openstack port set --name ap $(openstack port list --server a -f value -c ID)
-  $ openstack port set --name bp $(openstack port list --server b -f value -c ID)
-
-We'll need to refer to these ports' MAC addresses a few times, so
-let's put them in variables::
-
-  $ AP_MAC=$(openstack port show -f value -c mac_address ap)
-  $ BP_MAC=$(openstack port show -f value -c mac_address bp)
-
-At this point you can log into the consoles of the VMs if you like.
-You can do that from the OpenStack web interface or get a direct URL
-to paste into a web browser using a command like::
-
-  $ openstack console url show -f yaml a
-
-(The option ``-f yaml`` keeps the URL in the output from being broken
-into noncontiguous pieces on a 80-column console.)
-
-The VMs don't have many tools in them but ``ping`` and ``ssh`` from
-one to the other should work fine.  The VMs do not have any external
-network access or DNS configuration.
-
-Let's chase down what's changed in OVN.  Start with the NB DB at the
-top of the system.  It's clear that our logical switch now has the two
-logical ports attached to it::
-
-  $ ovn-nbctl show | abbrev
-  ...
-  switch 3eb263 (neutron-5b6baf) (aka n1)
-      port c29d41 (aka bp)
-          addresses: ["fa:16:3e:99:7a:17 10.1.1.6"]
-      port 820c08 (aka ap)
-          addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5"]
-  ...
-
-We can get some more details on each of these by looking at their NB
-DB records in the Logical_Switch_Port table.  Each port has addressing
-information, port security enabled, and a pointer to DHCP
-configuration (which we'll look at much later in `DHCP`_)::
-
-  $ ovn-nbctl list logical_switch_port ap bp | abbrev
-  _uuid               : ef17e5
-  addresses           : ["fa:16:3e:a9:4c:c7 10.1.1.5"]
-  dhcpv4_options      : 165974
-  dhcpv6_options      : []
-  dynamic_addresses   : []
-  enabled             : true
-  external_ids        : {"neutron:port_name"=ap}
-  name                : "820c08"
-  options             : {}
-  parent_name         : []
-  port_security       : ["fa:16:3e:a9:4c:c7 10.1.1.5"]
-  tag                 : []
-  tag_request         : []
-  type                : ""
-  up                  : true
-
-  _uuid               : e8af12
-  addresses           : ["fa:16:3e:99:7a:17 10.1.1.6"]
-  dhcpv4_options      : 165974
-  dhcpv6_options      : []
-  dynamic_addresses   : []
-  enabled             : true
-  external_ids        : {"neutron:port_name"=bp}
-  name                : "c29d41"
-  options             : {}
-  parent_name         : []
-  port_security       : ["fa:16:3e:99:7a:17 10.1.1.6"]
-  tag                 : []
-  tag_request         : []
-  type                : ""
-  up                  : true
-
-Now that the logical switch is less pathological, it's worth taking
-another look at the SB DB logical flow table.  Try a command like
-this::
-
-  $ ovn-sbctl lflow-list n1 | abbrev | less -S
-
-and then glance through the flows.  Packets that egress a VM into the
-logical switch travel through the flow table's ingress pipeline
-starting from table 0.  At each table, the switch finds the
-highest-priority logical flow that matches and executes its actions,
-or if there's no matching flow then the packet is dropped.  The
-``ovn-sb``\(5) manpage gives all the details, but with a little
-thought it's possible to guess a lot without reading the manpage.  For
-example, consider the flows in ingress pipeline table 0, which are the
-first flows encountered by a packet traversing the switch::
-
-  table=0 (ls_in_port_sec_l2  ), priority=100  , match=(eth.src[40]), action=(drop;)
-  table=0 (ls_in_port_sec_l2  ), priority=100  , match=(vlan.present), action=(drop;)
-  table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;)
-  table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "c29d41" && eth.src == {fa:16:3e:99:7a:17}), action=(next;)
-
-The first two flows, with priority 100, immediately drop two kinds of
-invalid packets: those with a multicast or broadcast Ethernet source
-address (since multicast is only for packet destinations) and those
-with a VLAN tag (because OVN doesn't yet support VLAN tags inside
-logical networks).  The next two flows implement L2 port security:
-they advance to the next table for packets with the correct Ethernet
-source addresses for their ingress ports.  A packet that does not
-match any flow is implicitly dropped, so there's no need for flows to
-deal with mismatches.
-
-The logical flow table includes many other flows, some of which we
-will look at later.  For now, it's most worth looking at ingress table
-13::
-
-  table=13(ls_in_l2_lkup      ), priority=100  , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
-  table=13(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == fa:16:3e:99:7a:17), action=(outport = "c29d41"; output;)
-  table=13(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == fa:16:3e:a9:4c:c7), action=(outport = "820c08"; output;)
-
-The first flow in table 13 checks whether the packet is an Ethernet
-multicast or broadcast and, if so, outputs it to a special port that
-egresses to every logical port (other than the ingress port).
-Otherwise the packet is output to the port corresponding to its
-Ethernet destination address.  Packets addressed to any other Ethernet
-destination are implicitly dropped.
-
-(It's common for an OVN logical switch to know all the MAC addresses
-supported by its logical ports, like this one.  That's why there's no
-logic here for MAC learning or flooding packets to unknown MAC
-addresses.  OVN does support unknown MAC handling but that's not in
-play in our example.)
-
-.. note::
-
-  If you're interested in the details for the multicast group, you can
-  run a command like the following and then look at the row for the
-  correct datapath::
-
-    $ ovn-sbctl find multicast_group name=_MC_flood | abbrev
-
-Now if you want to look at the OpenFlow flows, you can actually see
-them.  For example, here's the beginning of the output that lists the
-first four logical flows, which we already looked at above, and their
-corresponding OpenFlow flows.  If you want to know more about the
-syntax, the ``ovs-fields``\(7) manpage explains OpenFlow matches and
-``ovs-ofctl``\(8) explains OpenFlow actions::
-
-  $ sudo ovn-sbctl --ovs lflow-list n1 | abbrev
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: ingress
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(eth.src[40]), action=(drop;)
-      table=8 metadata=0x4,dl_src=01:00:00:00:00:00/01:00:00:00:00:00 actions=drop
-    table=0 (ls_in_port_sec_l2  ), priority=100  , match=(vlan.present), action=(drop;)
-      table=8 metadata=0x4,vlan_tci=0x1000/0x1000 actions=drop
-    table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;)
-      table=8 reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7 actions=resubmit(,9)
-    table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "c29d41" && eth.src == {fa:16:3e:99:7a:17}), action=(next;)
-      table=8 reg14=0x2,metadata=0x4,dl_src=fa:16:3e:99:7a:17 actions=resubmit(,9)
-  ...
-
-Logical 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 OVN, logically, and Open
-vSwitch, physically.
-
-Let's use OVN's ovn-trace utility to see what happens to packets from
-a logical point of view.  The ``ovn-trace``\(8) manpage has a lot of
-detail on how to do that, but let's just start by building up from a
-simple example.  You can start with a command that just specifies the
-logical datapath, an input port, and nothing else; unspecified fields
-default to all-zeros.  This doesn't do much::
-
-  $ ovn-trace n1 'inport == "ap"'
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2: no match (implicit drop)
-
-We see that the packet was dropped in logical table 0,
-"ls_in_port_sec_l2", the L2 port security stage (as we discussed
-earlier).  That's because we didn't use the right Ethernet source
-address for ``a``.  Let's see what happens if we do::
-
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a
-      next;
-  13. ls_in_l2_lkup: no match (implicit drop)
-
-Now the packet passes through L2 port security and skips through
-several other tables until it gets dropped in the L2 lookup stage
-(because the destination is unknown).  Let's add the Ethernet
-destination for ``b``::
-
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$BP_MAC
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:99:7a:17, priority 50, uuid 57a4c46f
-      outport = "bp";
-      output;
-
-  egress(dp="n1", inport="ap", outport="bp")
-  ------------------------------------------
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "bp" && eth.dst == {fa:16:3e:99:7a:17}, priority 50, uuid 8aa6426d
-      output;
-      /* output to "bp", type "" */
-
-You can see that in this case the packet gets properly switched from
-``a`` to ``b``.
-
-Physical Tracing for Hypothetical Packets
-+++++++++++++++++++++++++++++++++++++++++
-
-ovn-trace showed us how a hypothetical packet would travel through the
-system in a logical fashion, that is, without regard to how VMs are
-distributed across the physical network.  This is a convenient
-representation for understanding how OVN is **supposed** to work
-abstractly, but sometimes we might want to know more about how it
-actually works in the real systems where it is running.  For this, we
-can use the tracing tool that Open vSwitch provides, which traces
-a hypothetical packet through the OpenFlow tables.
-
-We can actually get two levels of detail.  Let's start with the
-version that's easier to interpret, by physically tracing a packet
-that looks like the one we logically traced before.  One obstacle is
-that we need to know the OpenFlow port number of the input port.  One
-way to do that is to look for a port whose "attached-mac" is the one
-we expect and print its ofport number::
-
-  $ AP_PORT=$(ovs-vsctl --bare --columns=ofport find  interface external-ids:attached-mac=\"$AP_MAC\")
-  $ echo $AP_PORT
-  3
-
-(You could also just do a plain ``ovs-vsctl list interface`` and then
-look through for the right row and pick its ``ofport`` value.)
-
-Now we can feed this input port number into ``ovs-appctl
-ofproto/trace`` along with the correct Ethernet source and
-destination addresses and get a physical trace::
-
-  $ sudo ovs-appctl ofproto/trace br-int in_port=$AP_PORT,dl_src=$AP_MAC,dl_dst=$BP_MAC
-  Flow: in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000
-
-  bridge("br-int")
-  ----------------
-   0. in_port=3, priority 100
-      set_field:0x8->reg13
-      set_field:0x9->reg11
-      set_field:0xa->reg12
-      set_field:0x4->metadata
-      set_field:0x1->reg14
-      resubmit(,8)
-   8. reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7, priority 50, cookie 0x6dcc418a
-      resubmit(,9)
-   9. metadata=0x4, priority 0, cookie 0x8fe8689e
-      resubmit(,10)
-  10. metadata=0x4, priority 0, cookie 0x719549d1
-      resubmit(,11)
-  11. metadata=0x4, priority 0, cookie 0x39c99e6f
-      resubmit(,12)
-  12. metadata=0x4, priority 0, cookie 0x838152a3
-      resubmit(,13)
-  13. metadata=0x4, priority 0, cookie 0x918259e3
-      resubmit(,14)
-  14. metadata=0x4, priority 0, cookie 0xcad14db2
-      resubmit(,15)
-  15. metadata=0x4, priority 0, cookie 0x7834d912
-      resubmit(,16)
-  16. metadata=0x4, priority 0, cookie 0x87745210
-      resubmit(,17)
-  17. metadata=0x4, priority 0, cookie 0x34951929
-      resubmit(,18)
-  18. metadata=0x4, priority 0, cookie 0xd7a8c9fb
-      resubmit(,19)
-  19. metadata=0x4, priority 0, cookie 0xd02e9578
-      resubmit(,20)
-  20. metadata=0x4, priority 0, cookie 0x42d35507
-      resubmit(,21)
-  21. metadata=0x4,dl_dst=fa:16:3e:99:7a:17, priority 50, cookie 0x57a4c46f
-      set_field:0x2->reg15
-      resubmit(,32)
-  32. priority 0
-      resubmit(,33)
-  33. reg15=0x2,metadata=0x4, priority 100
-      set_field:0xb->reg13
-      set_field:0x9->reg11
-      set_field:0xa->reg12
-      resubmit(,34)
-  34. priority 0
-      set_field:0->reg0
-      set_field:0->reg1
-      set_field:0->reg2
-      set_field:0->reg3
-      set_field:0->reg4
-      set_field:0->reg5
-      set_field:0->reg6
-      set_field:0->reg7
-      set_field:0->reg8
-      set_field:0->reg9
-      resubmit(,40)
-  40. metadata=0x4, priority 0, cookie 0xde9f3899
-      resubmit(,41)
-  41. metadata=0x4, priority 0, cookie 0x74074eff
-      resubmit(,42)
-  42. metadata=0x4, priority 0, cookie 0x7789c8b1
-      resubmit(,43)
-  43. metadata=0x4, priority 0, cookie 0xa6b002c0
-      resubmit(,44)
-  44. metadata=0x4, priority 0, cookie 0xaeab2b45
-      resubmit(,45)
-  45. metadata=0x4, priority 0, cookie 0x290cc4d4
-      resubmit(,46)
-  46. metadata=0x4, priority 0, cookie 0xa3223b88
-      resubmit(,47)
-  47. metadata=0x4, priority 0, cookie 0x7ac2132e
-      resubmit(,48)
-  48. reg15=0x2,metadata=0x4,dl_dst=fa:16:3e:99:7a:17, priority 50, cookie 0x8aa6426d
-      resubmit(,64)
-  64. priority 0
-      resubmit(,65)
-  65. reg15=0x2,metadata=0x4, priority 100
-      output:4
-
-  Final flow: reg11=0x9,reg12=0xa,reg13=0xb,reg14=0x1,reg15=0x2,metadata=0x4,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000
-  Megaflow: recirc_id=0,ct_state=-new-est-rel-rpl-inv-trk,ct_label=0/0x1,in_port=3,vlan_tci=0x0000/0x1000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000
-  Datapath actions: 4
-
-There's a lot there, which you can read through if you like, but the
-important part is::
-
-  65. reg15=0x2,metadata=0x4, priority 100
-      output:4
-
-which means that the packet is ultimately being output to OpenFlow
-port 4.  That's port ``b``, which you can confirm with::
-
-  $ sudo ovs-vsctl find interface ofport=4
-  _uuid               : 840a5aca-ea8d-4c16-a11b-a94e0f408091
-  admin_state         : up
-  bfd                 : {}
-  bfd_status          : {}
-  cfm_fault           : []
-  cfm_fault_status    : []
-  cfm_flap_count      : []
-  cfm_health          : []
-  cfm_mpid            : []
-  cfm_remote_mpids    : []
-  cfm_remote_opstate  : []
-  duplex              : full
-  error               : []
-  external_ids        : {attached-mac="fa:16:3e:99:7a:17", iface-id="c29d4120-20a4-4c44-bd83-8d91f5f447fd", iface-status=active, vm-id="2db969ca-ca2a-4d9a-b49e-f287d39c5645"}
-  ifindex             : 9
-  ingress_policing_burst: 0
-  ingress_policing_rate: 0
-  lacp_current        : []
-  link_resets         : 1
-  link_speed          : 10000000
-  link_state          : up
-  lldp                : {}
-  mac                 : []
-  mac_in_use          : "fe:16:3e:99:7a:17"
-  mtu                 : 1500
-  mtu_request         : []
-  name                : "tapc29d4120-20"
-  ofport              : 4
-  ofport_request      : []
-  options             : {}
-  other_config        : {}
-  statistics          : {collisions=0, rx_bytes=4254, rx_crc_err=0, rx_dropped=0, rx_errors=0, rx_frame_err=0, rx_over_err=0, rx_packets=39, tx_bytes=4188, tx_dropped=0, tx_errors=0, tx_packets=39}
-  status              : {driver_name=tun, driver_version="1.6", firmware_version=""}
-  type                : ""
-
-or::
-
-  $ BP_PORT=$(ovs-vsctl --bare --columns=ofport find  interface external-ids:attached-mac=\"$BP_MAC\")
-  $ echo $BP_PORT
-  4
-
-Physical Tracing for Real Packets
-+++++++++++++++++++++++++++++++++
-
-In the previous sections we traced a hypothetical L2 packet, one
-that's honestly not very realistic: we didn't even supply an Ethernet
-type, so it defaulted to zero, which isn't anything one would see on a
-real network.  We could refine our packet so that it becomes a more
-realistic TCP or UDP or ICMP, etc. packet, but let's try a different
-approach: working from a real packet.
-
-Pull up a console for VM ``a`` and start ``ping 10.1.1.6``, then leave
-it running for the rest of our experiment.
-
-Now go back to your DevStack session and run::
-
-  $ sudo watch ovs-dpctl dump-flows
-
-We're working with a new program.  ovn-dpctl is an interface to Open
-vSwitch datapaths, in this case to the Linux kernel datapath.  Its
-``dump-flows`` command displays the contents of the in-kernel flow
-cache, and by running it under the ``watch`` program we see a new
-snapshot of the flow table every 2 seconds.
-
-Look through the output for a flow that begins with ``recirc_id(0)``
-and matches the Ethernet source address for ``a``.  There is one flow
-per line, but the lines are very long, so it's easier to read if you
-make the window very wide.  This flow's packet counter should be
-increasing at a rate of 1 packet per second.  It looks something like
-this::
-
-  recirc_id(0),in_port(3),eth(src=fa:16:3e:f5:2a:90),eth_type(0x0800),ipv4(src=10.1.1.5,frag=no), packets:388, bytes:38024, used:0.977s, actions:ct(zone=8),recirc(0x18)
-
-We can hand the first part of this (everything up to the first space)
-to ``ofproto/trace``, and it will tell us what happens::
-
-  $ sudo ovs-appctl ofproto/trace 'recirc_id(0),in_port(3),eth(src=fa:16:3e:a9:4c:c7),eth_type(0x0800),ipv4(src=10.1.1.5,dst=10.1.0.0/255.255.0.0,frag=no)'
-  Flow: ip,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=00:00:00:00:00:00,nw_src=10.1.1.5,nw_dst=10.1.0.0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
-
-  bridge("br-int")
-  ----------------
-   0. in_port=3, priority 100
-      set_field:0x8->reg13
-      set_field:0x9->reg11
-      set_field:0xa->reg12
-      set_field:0x4->metadata
-      set_field:0x1->reg14
-      resubmit(,8)
-   8. reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7, priority 50, cookie 0x6dcc418a
-      resubmit(,9)
-   9. ip,reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7,nw_src=10.1.1.5, priority 90, cookie 0x343af48c
-      resubmit(,10)
-  10. metadata=0x4, priority 0, cookie 0x719549d1
-      resubmit(,11)
-  11. ip,metadata=0x4, priority 100, cookie 0x46c089e6
-      load:0x1->NXM_NX_XXREG0[96]
-      resubmit(,12)
-  12. metadata=0x4, priority 0, cookie 0x838152a3
-      resubmit(,13)
-  13. ip,reg0=0x1/0x1,metadata=0x4, priority 100, cookie 0xd1941634
-      ct(table=22,zone=NXM_NX_REG13[0..15])
-      drop
-
-  Final flow: ip,reg0=0x1,reg11=0x9,reg12=0xa,reg13=0x8,reg14=0x1,metadata=0x4,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=00:00:00:00:00:00,nw_src=10.1.1.5,nw_dst=10.1.0.0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
-  Megaflow: recirc_id=0,ip,in_port=3,vlan_tci=0x0000/0x1000,dl_src=fa:16:3e:a9:4c:c7,nw_src=10.1.1.5,nw_dst=10.1.0.0/16,nw_frag=no
-  Datapath actions: ct(zone=8),recirc(0xb)
-
-.. note::
-   Be careful cutting and pasting ``ovs-dpctl dump-flows`` output into
-   ``ofproto/trace`` because the latter has terrible error reporting.
-   If you add an extra line break, etc., it will likely give you a
-   useless error message.
-
-There's no ``output`` action in the output, but there are ``ct`` and
-``recirc`` actions (which you can see in the ``Datapath actions`` at
-the end).  The ``ct`` action tells the kernel to pass the packet
-through the kernel connection tracking for firewalling purposes and
-the ``recirc`` says to go back to the flow cache for another pass
-based on the firewall results.  The ``0xb`` value inside the
-``recirc`` gives us a hint to look at the kernel flows for a cached
-flow with ``recirc_id(0xb)``.  Indeed, there is one::
-
-  recirc_id(0xb),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.4/255.255.255.252,frag=no), packets:171, bytes:16758, used:0.271s, actions:ct(zone=11),recirc(0xc)
-
-We can then repeat our command with the match part of this kernel
-flow::
-
-  $ sudo ovs-appctl ofproto/trace 'recirc_id(0xb),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.4/255.255.255.252,frag=no)'
-  ...
-  Datapath actions: ct(zone=11),recirc(0xc)
-
-In other words, the flow passes through the connection tracker a
-second time.  The first time was for ``a``'s outgoing firewall; this
-second time is for ``b``'s incoming firewall.  Again, we continue
-tracing with ``recirc_id(0xc)``::
-
-  $ sudo ovs-appctl ofproto/trace 'recirc_id(0xc),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.6,proto=1,frag=no)'
-  ...
-  Datapath actions: 4
-
-It was took multiple hops, but we finally came to the end of the line
-where the packet was output to ``b`` after passing through both
-firewalls.  The port number here is a datapath port number, which is
-usually different from an OpenFlow port number.  To check that it is
-``b``'s port, we first list the datapath ports to get the name
-corresponding to the port number::
-
-  $ sudo ovs-dpctl show
-  system at ovs-system:
-          lookups: hit:1994 missed:56 lost:0
-          flows: 6
-          masks: hit:2340 total:4 hit/pkt:1.14
-          port 0: ovs-system (internal)
-          port 1: br-int (internal)
-          port 2: br-ex (internal)
-          port 3: tap820c0888-13
-          port 4: tapc29d4120-20
-
-and then confirm that this is the port we think it is with a command
-like this::
-
-  $ ovs-vsctl --columns=external-ids list interface tapc29d4120-20
-  external_ids        : {attached-mac="fa:16:3e:99:7a:17", iface-id="c29d4120-20a4-4c44-bd83-8d91f5f447fd", iface-status=active, vm-id="2db969ca-ca2a-4d9a-b49e-f287d39c5645"}
-
-Finally, we can relate the OpenFlow flows from our traces back to OVN
-logical flows.  For individual flows, cut and paste a "cookie" value
-from ``ofproto/trace`` output into ``ovn-sbctl lflow-list``, e.g.::
-
-  $ ovn-sbctl lflow-list 0x6dcc418a|abbrev
-  Datapath: "neutron-5b6baf" aka "n1" (a8a758)  Pipeline: ingress
-    table=0 (ls_in_port_sec_l2  ), priority=50   , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;)
-
-Or, you can pipe ``ofproto/trace`` output through ``ovn-detrace`` to
-annotate every flow::
-
-  $ sudo ovs-appctl ofproto/trace 'recirc_id(0xc),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.6,proto=1,frag=no)' | ovn-detrace
-  ...
-
-Routing
--------
-
-Previously we set up a pair of VMs ``a`` and ``b`` on a network ``n1``
-and demonstrated how packets make their way between them.  In this
-step, we'll set up a second network ``n2`` with a new VM ``c``,
-connect a router ``r`` to both networks, and demonstrate how routing
-works in OVN.
-
-There's nothing really new for the network and the VM so let's just go
-ahead and create them::
-
-  $ openstack network create --project admin --provider-network-type geneve n2
-  $ openstack subnet create --subnet-range 10.1.2.0/24 --network n2 n2subnet
-  $ openstack server create --nic net-id=n2,v4-fixed-ip=10.1.2.7 --flavor m1.nano --image $IMAGE_ID --key-name demo c
-  $ openstack port set --name cp $(openstack port list --server c -f value -c ID)
-  $ CP_MAC=$(openstack port show -f value -c mac_address cp)
-
-The new network ``n2`` is not yet connected to ``n1`` in any way.  You
-can try tracing a broadcast packet from ``a`` to see, for example,
-that it doesn't make it to ``c``::
-
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$CP_MAC
-  ...
-
-Now create an OpenStack router and connect it to ``n1`` and ``n2``::
-
-  $ openstack router create r
-  $ openstack router add subnet r n1subnet
-  $ openstack router add subnet r n2subnet
-
-Now ``a``, ``b``, and ``c`` should all be able to reach other.  You
-can get some verification that routing is taking place by running you
-``ping`` between ``c`` and one of the other VMs: the reported TTL
-should be one less than between ``a`` and ``b`` (63 instead of 64).
-
-Observe via ``ovn-nbctl`` the new OVN logical switch and router and
-then ports that connect them together::
-
-  $ ovn-nbctl show|abbrev
-  ...
-  switch f51234 (neutron-332346) (aka n2)
-      port 82b983
-          type: router
-          router-port: lrp-82b983
-      port 2e585f (aka cp)
-          addresses: ["fa:16:3e:89:f2:36 10.1.2.7"]
-  switch 3eb263 (neutron-5b6baf) (aka n1)
-      port c29d41 (aka bp)
-          addresses: ["fa:16:3e:99:7a:17 10.1.1.6"]
-      port 820c08 (aka ap)
-          addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5"]
-      port 17d870
-          type: router
-          router-port: lrp-17d870
-  ...
-  router dde06c (neutron-f88ebc) (aka r)
-      port lrp-82b983
-          mac: "fa:16:3e:19:9f:46"
-          networks: ["10.1.2.1/24"]
-      port lrp-17d870
-          mac: "fa:16:3e:f6:e2:8f"
-          networks: ["10.1.1.1/24"]
-
-We have not yet looked at the logical flows for an OVN logical router.
-You might find it of interest to look at them on your own::
-
-  $ ovn-sbctl lflow-list r | abbrev | less -S
-  ...
-
-Let's grab the ``n1subnet`` router porter MAC address to simplify
-later commands::
-
-  $ N1SUBNET_MAC=$(ovn-nbctl --bare --columns=mac find logical_router_port networks=10.1.1.1/24)
-
-Let's see what happens at the logical flow level for an ICMP packet
-from ``a`` to ``c``.  This generates a long trace but an interesting
-one, so we'll look at it bit by bit.  The first three stanzas in the
-output show the packet's ingress into ``n1`` and processing through
-the firewall on that side (via the "ct_next" connection-tracking
-action), and then the selection of the port that leads to router ``r``
-as the output port::
-
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && icmp4.type == 8'
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a
-      next;
-   1. ls_in_port_sec_ip (ovn-northd.c:2364): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == {10.1.1.5}, priority 90, uuid 343af48c
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6
-      reg0[0] = 1;
-      next;
-   5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip4), priority 2002, uuid a12b39f0
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:f6:e2:8f, priority 50, uuid c43ead31
-      outport = "17d870";
-      output;
-
-  egress(dp="n1", inport="ap", outport="17d870")
-  ----------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2626): ip && outport == "17d870", priority 110, uuid 60395450
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "17d870", priority 50, uuid 91b5cab0
-      output;
-      /* output to "17d870", type "patch" */
-
-The next two stanzas represent processing through logical router
-``r``.  The processing in table 5 is the core of the routing
-implementation: it recognizes that the packet is destined for an
-attached subnet, decrements the TTL and updates the Ethernet source
-address.  Table 6 then selects the Ethernet destination address based
-on the IP destination.  The packet then passes to switch ``n2`` via an
-OVN "logical patch port"::
-
-  ingress(dp="r", inport="lrp-17d870")
-  ------------------------------------
-   0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:f6:e2:8f && inport == "lrp-17d870", priority 50, uuid fa5270b0
-      next;
-   5. lr_in_ip_routing (ovn-northd.c:3782): ip4.dst == 10.1.2.0/24, priority 49, uuid 5f9d469f
-      ip.ttl--;
-      reg0 = ip4.dst;
-      reg1 = 10.1.2.1;
-      eth.src = fa:16:3e:19:9f:46;
-      outport = "lrp-82b983";
-      flags.loopback = 1;
-      next;
-   6. lr_in_arp_resolve (ovn-northd.c:5088): outport == "lrp-82b983" && reg0 == 10.1.2.7, priority 100, uuid 03d506d3
-      eth.dst = fa:16:3e:89:f2:36;
-      next;
-   8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid 6dacdd82
-      output;
-
-  egress(dp="r", inport="lrp-17d870", outport="lrp-82b983")
-  ---------------------------------------------------------
-   3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-82b983", priority 100, uuid 00bea4f2
-      output;
-      /* output to "lrp-82b983", type "patch" */
-
-Finally the logical switch for ``n2`` runs through the same logic as
-``n1`` and the packet is delivered to VM ``c``::
-
-  ingress(dp="n2", inport="82b983")
-  ---------------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "82b983", priority 50, uuid 9a789e06
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "82b983", priority 110, uuid ab52f21a
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:89:f2:36, priority 50, uuid dcafb3e9
-      outport = "cp";
-      output;
-
-  egress(dp="n2", inport="82b983", outport="cp")
-  ----------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid cd9cfa74
-      reg0[0] = 1;
-      next;
-   2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 9e8e22c5
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "cp" && ip4 && ip4.src == $as_ip4_0fc1b6cf_f925_49e6_8f00_6dd13beca9dc), priority 2002, uuid a746fa0d
-      next;
-   7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "cp" && eth.dst == fa:16:3e:89:f2:36 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.1.2.7}, priority 90, uuid 4d9862b5
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "cp" && eth.dst == {fa:16:3e:89:f2:36}, priority 50, uuid 0242cdc3
-      output;
-      /* output to "cp", type "" */
-
-Physical Tracing
-~~~~~~~~~~~~~~~~
-
-It's possible to use ``ofproto/trace``, just as before, to trace a
-packet through OpenFlow tables, either for a hypothetical packet or
-one that you get from a real test case using ``ovs-dpctl``.  The
-process is just the same as before and the output is almost the same,
-too.  Using a router doesn't actually introduce any interesting new
-wrinkles, so we'll skip over this for this case and for the remainder
-of the tutorial, but you can follow the steps on your own if you like.
-
-Adding a Gateway
-----------------
-
-The VMs that we've created can access each other but they are isolated
-from the physical world.  In OpenStack, the dominant way to connect a
-VM to external networks is by creating what is called a "floating IP
-address", which uses network address translation to connect an
-external address to an internal one.
-
-DevStack created a pair of networks named "private" and "public".  To
-use a floating IP address from a VM, we first add a port to the VM
-with an IP address from the "private" network, then we create a
-floating IP address on the "public" network, then we associate the
-port with the floating IP address.
-
-Let's add a new VM ``d`` with a floating IP::
-
-  $ openstack server create --nic net-id=private --flavor m1.nano --image $IMAGE_ID --key-name demo d
-  $ openstack port set --name dp $(openstack port list --server d -f value -c ID)
-  $ DP_MAC=$(openstack port show -f value -c mac_address dp)
-  $ openstack floating ip create --floating-ip-address 172.24.4.8 public
-  $ openstack server add floating ip d 172.24.4.8
-
-(We specified a particular floating IP address to make the examples
-easier to follow, but without that OpenStack will automatically
-allocate one.)
-
-It's also necessary to configure the "public" network because DevStack
-does not do it automatically::
-
-  $ sudo ip link set br-ex up
-  $ sudo ip route add 172.24.4.0/24 dev br-ex
-  $ sudo ip addr add 172.24.4.1/24 dev br-ex
-
-Now you should be able to "ping" VM ``d`` from the OpenStack host::
-
-  $ ping 172.24.4.8
-  PING 172.24.4.8 (172.24.4.8) 56(84) bytes of data.
-  64 bytes from 172.24.4.8: icmp_seq=1 ttl=63 time=56.0 ms
-  64 bytes from 172.24.4.8: icmp_seq=2 ttl=63 time=1.44 ms
-  64 bytes from 172.24.4.8: icmp_seq=3 ttl=63 time=1.04 ms
-  64 bytes from 172.24.4.8: icmp_seq=4 ttl=63 time=0.403 ms
-  ^C
-  --- 172.24.4.8 ping statistics ---
-  4 packets transmitted, 4 received, 0% packet loss, time 3003ms
-  rtt min/avg/max/mdev = 0.403/14.731/56.028/23.845 ms
-
-You can also SSH in with the key that we created during setup::
-
-  $ ssh -i ~/id_rsa_demo cirros at 172.24.4.8
-
-Let's dive in and see how this gets implemented in OVN.  First, the
-relevant parts of the NB DB for the "public" and "private" networks
-and the router between them::
-
-  $ ovn-nbctl show | abbrev
-  switch 2579f4 (neutron-d1ac28) (aka public)
-      port provnet-d1ac28
-          type: localnet
-          addresses: ["unknown"]
-      port ae9b52
-          type: router
-          router-port: lrp-ae9b52
-  switch 5b3d5f (neutron-c02c4d) (aka private)
-      port b256dd
-          type: router
-          router-port: lrp-b256dd
-      port f264e7
-          type: router
-          router-port: lrp-f264e7
-      port cae25b (aka dp)
-          addresses: ["fa:16:3e:c1:f5:a2 10.0.0.6 fdb0:5860:4ba8:0:f816:3eff:fec1:f5a2"]
-  ...
-  router c59ad2 (neutron-9b057f) (aka router1)
-      port lrp-ae9b52
-          mac: "fa:16:3e:b2:d2:67"
-          networks: ["172.24.4.9/24", "2001:db8::b/64"]
-      port lrp-b256dd
-          mac: "fa:16:3e:35:33:db"
-          networks: ["fdb0:5860:4ba8::1/64"]
-      port lrp-f264e7
-          mac: "fa:16:3e:fc:c8:da"
-          networks: ["10.0.0.1/26"]
-      nat 788c6d
-          external ip: "172.24.4.8"
-          logical ip: "10.0.0.6"
-          type: "dnat_and_snat"
-      nat 80914c
-          external ip: "172.24.4.9"
-          logical ip: "10.0.0.0/26"
-          type: "snat"
-  ...
-
-What we see is:
-
-* VM ``d`` is on the "private" switch under its private IP address
-  10.0.0.8.  The "private" switch is connected to "router1" via two
-  router ports (one for IPv4, one for IPv6).
-
-* The "public" switch is connected to "router1" and to the physical
-  network via a "localnet" port.
-
-* "router1" is in the middle between "private" and "public".  In
-  addition to the router ports that connect to these switches, it has
-  "nat" entries that direct network address translation.  The
-  translation between floating IP address 172.24.4.8 and private
-  address 10.0.0.8 makes perfect sense.
-
-When the NB DB gets translated into logical flows at the southbound
-layer, the "nat" entries get translated into IP matches that then
-invoke "ct_snat" and "ct_dnat" actions.  The details are intricate,
-but you can get some of the idea by just looking for relevant flows::
-
-  $ ovn-sbctl lflow-list router1 | abbrev | grep nat | grep -E '172.24.4.8|10.0.0.8'
-    table=3 (lr_in_unsnat       ), priority=100  , match=(ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_snat;)
-    table=3 (lr_in_unsnat       ), priority=50   , match=(ip && ip4.dst == 172.24.4.8), action=(reg9[0] = 1; next;)
-    table=4 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_dnat(10.0.0.6);)
-    table=4 (lr_in_dnat         ), priority=50   , match=(ip && ip4.dst == 172.24.4.8), action=(reg9[0] = 1; next;)
-    table=1 (lr_out_snat        ), priority=33   , match=(ip && ip4.src == 10.0.0.6 && outport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_snat(172.24.4.8);)
-
-Let's take a look at how a packet passes through this whole gauntlet.
-The first two stanzas just show the packet traveling through the
-"public" network and being forwarded to the "router1" network::
-
-  $ ovn-trace public 'inport == "provnet-d1ac2896-18a7-4bca-8f46-b21e2370e5b1" && eth.src == 00:01:02:03:04:05 && eth.dst == fa:16:3e:b2:d2:67 && ip4.src == 172.24.4.1 && ip4.dst == 172.24.4.8 && ip.ttl == 64 && icmp4.type==8'
-  ...
-  ingress(dp="public", inport="provnet-d1ac28")
-  ---------------------------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "provnet-d1ac28", priority 50, uuid 8d86fb06
-      next;
-  10. ls_in_arp_rsp (ovn-northd.c:3266): inport == "provnet-d1ac28", priority 100, uuid 21313eff
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3571): eth.dst == fa:16:3e:b2:d2:67 && is_chassis_resident("cr-lrp-ae9b52"), priority 50, uuid 7f28f51f
-      outport = "ae9b52";
-      output;
-
-  egress(dp="public", inport="provnet-d1ac28", outport="ae9b52")
-  --------------------------------------------------------------
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ae9b52", priority 50, uuid 72fea396
-      output;
-      /* output to "ae9b52", type "patch" */
-
-In "router1", first the ``ct_snat`` action without an argument
-attempts to "un-SNAT" the packet.  ovn-trace treats this as a no-op,
-because it doesn't have any state for tracking connections.  As an
-alternative, it invokes ``ct_dnat(10.0.0.8)`` to NAT the destination
-IP::
-
-  ingress(dp="router1", inport="lrp-ae9b52")
-  ------------------------------------------
-   0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:b2:d2:67 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 50, uuid 8c6945c2
-      next;
-   3. lr_in_unsnat (ovn-northd.c:4591): ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 100, uuid e922f541
-      ct_snat;
-
-  ct_snat /* assuming no un-snat entry, so no change */
-  -----------------------------------------------------
-   4. lr_in_dnat (ovn-northd.c:4649): ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 100, uuid 02f41b79
-      ct_dnat(10.0.0.6);
-
-Still in "router1", the routing and output steps transmit the packet
-to the "private" network::
-
-  ct_dnat(ip4.dst=10.0.0.6)
-  -------------------------
-   5. lr_in_ip_routing (ovn-northd.c:3782): ip4.dst == 10.0.0.0/26, priority 53, uuid 86e005b0
-      ip.ttl--;
-      reg0 = ip4.dst;
-      reg1 = 10.0.0.1;
-      eth.src = fa:16:3e:fc:c8:da;
-      outport = "lrp-f264e7";
-      flags.loopback = 1;
-      next;
-   6. lr_in_arp_resolve (ovn-northd.c:5088): outport == "lrp-f264e7" && reg0 == 10.0.0.6, priority 100, uuid 2963d67c
-      eth.dst = fa:16:3e:c1:f5:a2;
-      next;
-   8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid eea419b7
-      output;
-
-  egress(dp="router1", inport="lrp-ae9b52", outport="lrp-f264e7")
-  ---------------------------------------------------------------
-   3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-f264e7", priority 100, uuid 42dadc23
-      output;
-      /* output to "lrp-f264e7", type "patch" */
-
-In the "private" network, the packet passes through VM ``d``'s
-firewall and is output to ``d``::
-
-  ingress(dp="private", inport="f264e7")
-  --------------------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "f264e7", priority 50, uuid 5b721214
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "f264e7", priority 110, uuid 5bdc3209
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:c1:f5:a2, priority 50, uuid 7957f80f
-      outport = "dp";
-      output;
-
-  egress(dp="private", inport="f264e7", outport="dp")
-  ---------------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid 4981c79d
-      reg0[0] = 1;
-      next;
-   2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 247e02eb
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "dp" && ip4 && ip4.src == 0.0.0.0/0 && icmp4), priority 2002, uuid b860fc9f
-      next;
-   7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "dp" && eth.dst == fa:16:3e:c1:f5:a2 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.0.0.6}, priority 90, uuid 15655a98
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "dp" && eth.dst == {fa:16:3e:c1:f5:a2}, priority 50, uuid 5916f94b
-      output;
-      /* output to "dp", type "" */
-
-IPv6
-----
-
-OVN supports IPv6 logical routing.  Let's try it out.
-
-The first step is to add an IPv6 subnet to networks ``n1`` and ``n2``,
-then attach those subnets to our router ``r``.  As usual, though
-OpenStack can assign addresses itself, we use fixed ones to make the
-discussion easier::
-
-  $ openstack subnet create --ip-version 6 --subnet-range fc11::/64 --network n1 n1subnet6
-  $ openstack subnet create --ip-version 6 --subnet-range fc22::/64 --network n2 n2subnet6
-  $ openstack router add subnet r n1subnet6
-  $ openstack router add subnet r n2subnet6
-
-Then we add an IPv6 address to each of our VMs::
-
-  $ A_PORT_ID=$(openstack port list --server a -f value -c ID)
-  $ openstack port set --fixed-ip subnet=n1subnet6,ip-address=fc11::5 $A_PORT_ID
-  $ B_PORT_ID=$(openstack port list --server b -f value -c ID)
-  $ openstack port set --fixed-ip subnet=n1subnet6,ip-address=fc11::6 $B_PORT_ID
-  $ C_PORT_ID=$(openstack port list --server c -f value -c ID)
-  $ openstack port set --fixed-ip subnet=n2subnet6,ip-address=fc22::7 $C_PORT_ID
-
-At least for me, the new IPv6 addresses didn't automatically get
-propagated into the VMs.  To do it by hand, pull up the console for
-``a`` and run::
-
-  $ sudo ip addr add fc11::5/64 dev eth0
-  $ sudo ip route add via fc11::1
-
-Then in ``b``::
-
-  $ sudo ip addr add fc11::6/64 dev eth0
-  $ sudo ip route add via fc11::1
-
-Finally in ``c``::
-
-  $ sudo ip addr add fc22::7/64 dev eth0
-  $ sudo ip route add via fc22::1
-
-Now you should have working IPv6 routing through router ``r``.  The
-relevant parts of the NB DB look like the following.  The interesting
-parts are the new ``fc11::`` and ``fc22::`` addresses on the ports in
-``n1`` and ``n2`` and the new IPv6 router ports in ``r``::
-
-  $ ovn-nbctl show | abbrev
-  ...
-  switch f51234 (neutron-332346) (aka n2)
-      port 1a8162
-          type: router
-          router-port: lrp-1a8162
-      port 82b983
-          type: router
-          router-port: lrp-82b983
-      port 2e585f (aka cp)
-          addresses: ["fa:16:3e:89:f2:36 10.1.2.7 fc22::7"]
-  switch 3eb263 (neutron-5b6baf) (aka n1)
-      port ad952e
-          type: router
-          router-port: lrp-ad952e
-      port c29d41 (aka bp)
-          addresses: ["fa:16:3e:99:7a:17 10.1.1.6 fc11::6"]
-      port 820c08 (aka ap)
-          addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"]
-      port 17d870
-          type: router
-          router-port: lrp-17d870
-  ...
-  router dde06c (neutron-f88ebc) (aka r)
-      port lrp-1a8162
-          mac: "fa:16:3e:06:de:ad"
-          networks: ["fc22::1/64"]
-      port lrp-82b983
-          mac: "fa:16:3e:19:9f:46"
-          networks: ["10.1.2.1/24"]
-      port lrp-ad952e
-          mac: "fa:16:3e:ef:2f:8b"
-          networks: ["fc11::1/64"]
-      port lrp-17d870
-          mac: "fa:16:3e:f6:e2:8f"
-          networks: ["10.1.1.1/24"]
-
-Try tracing a packet from ``a`` to ``c``.  The results correspond
-closely to those for IPv4 which we already discussed back under
-`Routing`_::
-
-  $ N1SUBNET6_MAC=$(ovn-nbctl --bare --columns=mac find logical_router_port networks=\"fc11::1/64\")
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip6.src == fc11::5 && ip6.dst == fc22::7 && ip.ttl == 64 && icmp6.type == 8'
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a
-      next;
-   1. ls_in_port_sec_ip (ovn-northd.c:2390): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip6.src == {fe80::f816:3eff:fea9:4cc7, fc11::5}, priority 90, uuid 604810ea
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6
-      reg0[0] = 1;
-      next;
-   5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip6), priority 2002, uuid 7fdd607e
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:ef:2f:8b, priority 50, uuid e1d87fc5
-      outport = "ad952e";
-      output;
-
-  egress(dp="n1", inport="ap", outport="ad952e")
-  ----------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2626): ip && outport == "ad952e", priority 110, uuid 88f68988
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ad952e", priority 50, uuid 5935755e
-      output;
-      /* output to "ad952e", type "patch" */
-
-  ingress(dp="r", inport="lrp-ad952e")
-  ------------------------------------
-   0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:ef:2f:8b && inport == "lrp-ad952e", priority 50, uuid ddfeb712
-      next;
-   5. lr_in_ip_routing (ovn-northd.c:3782): ip6.dst == fc22::/64, priority 129, uuid cc2130ec
-      ip.ttl--;
-      xxreg0 = ip6.dst;
-      xxreg1 = fc22::1;
-      eth.src = fa:16:3e:06:de:ad;
-      outport = "lrp-1a8162";
-      flags.loopback = 1;
-      next;
-   6. lr_in_arp_resolve (ovn-northd.c:5122): outport == "lrp-1a8162" && xxreg0 == fc22::7, priority 100, uuid bcf75288
-      eth.dst = fa:16:3e:89:f2:36;
-      next;
-   8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid 6dacdd82
-      output;
-
-  egress(dp="r", inport="lrp-ad952e", outport="lrp-1a8162")
-  ---------------------------------------------------------
-   3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-1a8162", priority 100, uuid 5260dfc5
-      output;
-      /* output to "lrp-1a8162", type "patch" */
-
-  ingress(dp="n2", inport="1a8162")
-  ---------------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "1a8162", priority 50, uuid 10957d1b
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "1a8162", priority 110, uuid a27ebd00
-      next;
-  13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:89:f2:36, priority 50, uuid dcafb3e9
-      outport = "cp";
-      output;
-
-  egress(dp="n2", inport="1a8162", outport="cp")
-  ----------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid cd9cfa74
-      reg0[0] = 1;
-      next;
-   2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 9e8e22c5
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "cp" && ip6 && ip6.src == $as_ip6_0fc1b6cf_f925_49e6_8f00_6dd13beca9dc), priority 2002, uuid 12fc96f9
-      next;
-   7. ls_out_port_sec_ip (ovn-northd.c:2390): outport == "cp" && eth.dst == fa:16:3e:89:f2:36 && ip6.dst == {fe80::f816:3eff:fe89:f236, ff00::/8, fc22::7}, priority 90, uuid c622596a
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "cp" && eth.dst == {fa:16:3e:89:f2:36}, priority 50, uuid 0242cdc3
-      output;
-      /* output to "cp", type "" */
-
-ACLs
-----
-
-Let's explore how ACLs work in OpenStack and OVN.  In OpenStack, ACL
-rules are part of "security groups", which are "default deny", that
-is, packets are not allowed by default and the rules added to security
-groups serve to allow different classes of packets.  The default group
-(named "default") that is assigned to each of our VMs so far allows
-all traffic from our other VMs, which isn't very interesting for
-testing.  So, let's create a new security group, which we'll name
-"custom", add rules to it that allow incoming SSH and ICMP traffic,
-and apply this security group to VM ``c``::
-
-  $ openstack security group create custom
-  $ openstack security group rule create --dst-port 22 custom
-  $ openstack security group rule create --protocol icmp custom
-  $ openstack server remove security group c default
-  $ openstack server add security group c custom
-
-Now we can do some experiments to test security groups.  From the
-console on ``a`` or ``b``, it should now be possible to "ping" ``c``
-or to SSH to it, but attempts to initiate connections on other ports
-should be blocked.  (You can try to connect on another port with
-``ssh -p PORT IP`` or ``nc PORT IP``.)  Connection attempts should
-time out rather than receive the "connection refused" or "connection
-reset" error that you would see between ``a`` and ``b``.
-
-It's also possible to test ACLs via ovn-trace, with one new wrinkle.
-ovn-trace can't simulate connection tracking state in the network, so
-by default it assumes that every packet represents an established
-connection.  That's good enough for what we've been doing so far, but
-for checking properties of security groups we want to look at more
-detail.
-
-If you look back at the VM-to-VM traces we've done until now, you can
-see that they execute two ``ct_next`` actions:
-
-* The first of these is for the packet passing outward through the
-  source VM's firewall.  We can tell ovn-trace to treat the packet as
-  starting a new connection or adding to an established connection by
-  adding a ``--ct`` option: ``--ct new`` or ``--ct est``,
-  respectively.  The latter is the default and therefore what we've
-  been using so far.  We can also use ``--ct est,rpl``, which in
-  addition to ``--ct est`` means that the connection was initiated by
-  the destination VM rather than by the VM sending this packet.
-
-* The second is for the packet passing inward through the destination
-  VM's firewall.  For this one, it makes sense to tell ovn-trace that
-  the packet is starting a new connection, with ``--ct new``, or that
-  it is a packet sent in reply to a connection established by the
-  destination VM, with ``--ct est,rpl``.
-
-ovn-trace uses the ``--ct`` options in order, so if we want to
-override the second ``ct_next`` behavior we have to specify two
-options.
-
-Another useful ovn-trace option for this testing is ``--minimal``,
-which reduces the amount of output.  In this case we're really just
-interested in finding out whether the packet reaches the destination
-VM, that is, whether there's an eventual ``output`` action to ``c``,
-so ``--minimal`` works fine and the output is easier to read.
-
-Try a few traces.  For example:
-
-* VM ``a`` initiates a new SSH connection to ``c``::
-
-    $ ovn-trace --ct new --ct new --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 22'
-    ...
-    ct_next(ct_state=new|trk) {
-        ip.ttl--;
-        eth.src = fa:16:3e:19:9f:46;
-        eth.dst = fa:16:3e:89:f2:36;
-        ct_next(ct_state=new|trk) {
-            output("cp");
-        };
-    };
-
-  This succeeds, as you can see since there is an ``output`` action.
-
-* VM ``a`` initiates a new Telnet connection to ``c``::
-
-    $ ovn-trace --ct new --ct new --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 23'
-    ct_next(ct_state=new|trk) {
-        ip.ttl--;
-        eth.src = fa:16:3e:19:9f:46;
-        eth.dst = fa:16:3e:89:f2:36;
-        ct_next(ct_state=new|trk);
-    };
-
-  This fails, as you can see from the lack of an ``output`` action.
-
-* VM ``a`` replies to a packet that is part of a Telnet connection
-  originally initiated by ``c``::
-
-    $ ovn-trace --ct est,rpl --ct est,rpl --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 23'
-    ...
-    ct_next(ct_state=est|rpl|trk) {
-        ip.ttl--;
-        eth.src = fa:16:3e:19:9f:46;
-        eth.dst = fa:16:3e:89:f2:36;
-        ct_next(ct_state=est|rpl|trk) {
-            output("cp");
-        };
-    };
-
-  This succeeds, as you can see from the ``output`` action, since
-  traffic received in reply to an outgoing connection is always
-  allowed.
-
-DHCP
-----
-
-As a final demonstration of the OVN architecture, let's examine the
-DHCP implementation.  Like switching, routing, and NAT, the OVN
-implementation of DHCP involves configuration in the NB DB and logical
-flows in the SB DB.
-
-Let's look at the DHCP support for ``a``'s port ``ap``.  The port's
-Logical_Switch_Port record shows that ``ap`` has DHCPv4 options::
-
-  $ ovn-nbctl list logical_switch_port ap | abbrev
-  _uuid               : ef17e5
-  addresses           : ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"]
-  dhcpv4_options      : 165974
-  dhcpv6_options      : 26f7cd
-  dynamic_addresses   : []
-  enabled             : true
-  external_ids        : {"neutron:port_name"=ap}
-  name                : "820c08"
-  options             : {}
-  parent_name         : []
-  port_security       : ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"]
-  tag                 : []
-  tag_request         : []
-  type                : ""
-  up                  : true
-
-We can then list them either by UUID or, more easily, by port name::
-
-  $ ovn-nbctl list dhcp_options ap | abbrev
-  _uuid               : 165974
-  cidr                : "10.1.1.0/24"
-  external_ids        : {subnet_id="5e67e7"}
-  options             : {lease_time="43200", mtu="1442", router="10.1.1.1", server_id="10.1.1.1", server_mac="fa:16:3e:bb:94:72"}
-
-These options show the basic DHCP configuration for the subnet.  They
-do not include the IP address itself, which comes from the
-Logical_Switch_Port record.  This allows a whole Neutron subnet to
-share a single DHCP_Options record.  You can see this sharing in
-action, if you like, by listing the record for port ``bp``, which is
-on the same subnet as ``ap``, and see that it is the same record as before::
-
-  $ ovn-nbctl list dhcp_options bp | abbrev
-  _uuid               : 165974
-  cidr                : "10.1.1.0/24"
-  external_ids        : {subnet_id="5e67e7"}
-  options             : {lease_time="43200", mtu="1442", router="10.1.1.1", server_id="10.1.1.1", server_mac="fa:16:3e:bb:94:72"}
-
-You can take another look at the southbound flow table if you like,
-but the best demonstration is to trace a DHCP packet.  The following
-is a trace of a DHCP request inbound from ``ap``.  The first part is
-just the usual travel through the firewall::
-
-  $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == ff:ff:ff:ff:ff:ff && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && ip.ttl == 1'
-  ...
-  ingress(dp="n1", inport="ap")
-  -----------------------------
-   0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a
-      next;
-   1. ls_in_port_sec_ip (ovn-northd.c:2325): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67, priority 90, uuid e46bed6f
-      next;
-   3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6
-      reg0[0] = 1;
-      next;
-   5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634
-      ct_next;
-
-The next part is the new part.  First, an ACL in table 6 allows a DHCP
-request to pass through.  In table 11, the special ``put_dhcp_opts``
-action replaces a DHCPDISCOVER or DHCPREQUEST packet by a
-reply.  Table 12 flips the packet's source and destination and sends
-it back the way it came in::
-
-   6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip4 && ip4.dst == {255.255.255.255, 10.1.1.0/24} && udp && udp.src == 68 && udp.dst == 67), priority 2002, uuid 9c90245d
-      next;
-  11. ls_in_dhcp_options (ovn-northd.c:3409): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67, priority 100, uuid 8d63f29c
-      reg0[3] = put_dhcp_opts(offerip = 10.1.1.5, lease_time = 43200, mtu = 1442, netmask = 255.255.255.0, router = 10.1.1.1, server_id = 10.1.1.1);
-      /* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */
-      next;
-  12. ls_in_dhcp_response (ovn-northd.c:3438): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4 && udp.src == 68 && udp.dst == 67 && reg0[3], priority 100, uuid 995eeaa9
-      eth.dst = eth.src;
-      eth.src = fa:16:3e:bb:94:72;
-      ip4.dst = 10.1.1.5;
-      ip4.src = 10.1.1.1;
-      udp.src = 67;
-      udp.dst = 68;
-      outport = inport;
-      flags.loopback = 1;
-      output;
-
-Then the last part is just traveling back through the firewall to VM
-``a``::
-
-  egress(dp="n1", inport="ap", outport="ap")
-  ------------------------------------------
-   1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid 3752b746
-      reg0[0] = 1;
-      next;
-   2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 0c066ea1
-      ct_next;
-
-  ct_next(ct_state=est|trk /* default (use --ct to customize) */)
-  ---------------------------------------------------------------
-   4. ls_out_acl (ovn-northd.c:3008): outport == "ap" && eth.src == fa:16:3e:bb:94:72 && ip4.src == 10.1.1.1 && udp && udp.src == 67 && udp.dst == 68, priority 34000, uuid 0b383e77
-      ct_commit;
-      next;
-   7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "ap" && eth.dst == fa:16:3e:a9:4c:c7 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.1.1.5}, priority 90, uuid 7b8cbcd5
-      next;
-   8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ap" && eth.dst == {fa:16:3e:a9:4c:c7}, priority 50, uuid b874ece8
-      output;
-      /* output to "ap", type "" */
-
-Further Directions
-------------------
-
-We've looked at a fair bit of how OVN works and how it interacts with
-OpenStack.  If you still have some interest, then you might want to
-explore some of these directions:
-
-* Adding more than one hypervisor ("compute node", in OpenStack
-  parlance).  OVN connects compute nodes by tunneling packets with the
-  STT or Geneve protocols.  OVN scales to 1000 compute nodes or more,
-  but two compute nodes demonstrate the principle.  All of the tools
-  and techniques we demonstrated also work with multiple compute
-  nodes.
-
-* Container support.  OVN supports seamlessly connecting VMs to
-  containers, whether the containers are hosted on "bare metal" or
-  nested inside VMs.  OpenStack support for containers, however, is
-  still evolving, and too difficult to incorporate into the tutorial
-  at this point.
-
-* Other kinds of gateways.  In addition to floating IPs with NAT, OVN
-  supports directly attaching VMs to a physical network and connecting
-  logical switches to VTEP hardware.
diff --git a/Documentation/tutorials/ovn-rbac.rst b/Documentation/tutorials/ovn-rbac.rst
deleted file mode 100644
index ec163e2df..000000000
--- a/Documentation/tutorials/ovn-rbac.rst
+++ /dev/null
@@ -1,134 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-=============================================
-OVN Role-Based Access Control (RBAC) Tutorial
-=============================================
-
-This document provides a step-by-step guide for setting up role-based access
-control (RBAC) in OVN. In OVN, hypervisors (chassis) read and write the
-southbound database to do configuration. Without restricting hypervisor's
-access to the southbound database, a compromised hypervisor might disrupt the
-entire OVN deployment by corrupting the database. RBAC ensures that each
-hypervisor can only modify its own data and thus improves the security of OVN.
-More details about the RBAC design can be found in ``ovn-architecture``\(7)
-manpage.
-
-This document assumes OVN is installed in your system and runs normally.
-
-.. _gen-certs-keys:
-
-Generating Certificates and Keys
---------------------------------
-
-In the OVN RBAC deployment, ovn-controller connects to the southbound database
-via SSL connection. The southbound database uses CA-signed certificate to
-authenticate ovn-controller.
-
-Suppose there are three machines in your deployment. `machine_1` runs
-`chassis_1` and has IP address `machine_1-ip`. `machine_2` runs `chassis_2` and
-has IP address `machine_2-ip`. `machine_3` hosts southbound database and has IP
-address `machine_3-ip`. `machine_3` also hosts public key infrastructure (PKI).
-
-1. Initiate PKI.
-
-   In `machine_3`::
-
-      $ ovs-pki init
-
-2. Generate southbound database's certificate request. Sign the certificate
-   request with the CA key.
-
-   In `machine_3`::
-
-      $ ovs-pki req -u sbdb
-      $ ovs-pki sign sbdb switch
-
-3. Generate chassis certificate requests. Copy the certificate requests to
-   `machine_3`.
-
-   In `machine_1`::
-
-      $ ovs-pki req -u chassis_1
-      $ scp chassis_1-req.pem \
-                    machine_3 at machine_3-ip:/path/to/chassis_1-req.pem
-
-   In `machine_2`::
-
-      $ ovs-pki req -u chassis_2
-      $ scp chassis_2-req.pem \
-                    machine_3 at machine_3-ip:/path/to/chassis_2-req.pem
-
-   .. note::
-
-     chassis_1 must be the same string as ``external_ids:system-id`` in the
-     Open_vSwitch table (the chassis name) of machine_1. Same applies for
-     chassis_2.
-
-4. Sign the chassis certificate requests with the CA key. Copy `chassis_1`'s
-   signed certificate and the CA certificate to `machine_1`. Copy `chassis_2`'s
-   signed certificate and the CA certificate to `machine_2`.
-
-   In `machine_3`::
-
-      $ ovs-pki sign chassis_1 switch
-      $ ovs-pki sign chassis_2 switch
-      $ scp chassis_1-cert.pem \
-                    machine_1 at machine_1-ip:/path/to/chassis_1-cert.pem
-      $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \
-                    machine_1 at machine_1-ip:/path/to/cacert.pem
-      $ scp chassis_2-cert.pem \
-                    machine_2 at machine_2-ip:/path/to/chassis_2-cert.pem
-      $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \
-                    machine_2 at machine_2-ip:/path/to/cacert.pem
-
-Configuring RBAC
-----------------
-
-1. Set certificate, private key, and CA certificate for the southbound
-   database. Configure the southbound database to listen on SSL connection and
-   enforce role-based access control.
-
-   In `machine_3`::
-
-      $ ovn-sbctl set-ssl /path/to/sbdb-privkey.pem \
-                          /path/to/sbdb-cert.pem /path/to/cacert.pem
-      $ ovn-sbctl set-connection role=ovn-controller pssl:6642
-
-2. Set certificate, private key, and CA certificate for `chassis_1` and
-   `chassis_2`. Configure `chassis_1` and `chassis_2` to connect southbound
-   database via SSL.
-
-   In `machine_1`::
-
-      $ ovs-vsctl set-ssl /path/to/chassis_1-privkey.pem \
-                    /path/to/chassis_1-cert.pem /path/to/cacert.pem
-      $ ovs-vsctl set open_vswitch . \
-                    external_ids:ovn-remote=ssl:machine_3-ip:6642
-
-   In `machine_2`::
-
-      $ ovs-vsctl set-ssl /path/to/chassis_2-privkey.pem \
-                    /path/to/chassis_2-cert.pem /path/to/cacert.pem
-      $ ovs-vsctl set open_vswitch . \
-                    external_ids:ovn-remote=ssl:machine_3-ip:6642
diff --git a/Documentation/tutorials/ovn-sandbox.rst b/Documentation/tutorials/ovn-sandbox.rst
deleted file mode 100644
index b906b799d..000000000
--- a/Documentation/tutorials/ovn-sandbox.rst
+++ /dev/null
@@ -1,177 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-===========
-OVN Sandbox
-===========
-
-This tutorial shows you how to explore features using ``ovs-sandbox`` as a
-simulated test environment.  It's assumed that you have an understanding of OVS
-before going through this tutorial. Detail about OVN is covered in
-ovn-architecture_, but this tutorial lets you quickly see it in action.
-
-Getting Started
----------------
-
-For some general information about ``ovs-sandbox``, see the "Getting Started"
-section of :doc:`ovs-advanced`.
-
-``ovs-sandbox`` does not include OVN support by default.  To enable OVN, you
-must pass the ``--ovn`` flag.  For example, if running it straight from the OVS
-git tree you would run::
-
-    $ make sandbox SANDBOXFLAGS="--ovn"
-
-Running the sandbox with OVN enabled does the following additional steps to the
-environment:
-
-1. Creates the ``OVN_Northbound`` and ``OVN_Southbound`` databases as described in
-   `ovn-nb(5)`_ and `ovn-sb(5)`_.
-
-2. Creates a backup server for ``OVN_Southbond`` database. Sandbox launch
-   screen provides the instructions on accessing the backup database.  However
-   access to the backup server is not required to go through the tutorial.
-
-3. Creates the ``hardware_vtep`` database as described in `vtep(5)`_.
-
-4. Runs the `ovn-northd(8)`_, `ovn-controller(8)`_, and
-   `ovn-controller-vtep(8)`_ daemons.
-
-5. Makes OVN and VTEP utilities available for use in the environment, including
-   `vtep-ctl(8)`_, `ovn-nbctl(8)`_, and `ovn-sbctl(8)`_.
-
-Using GDB
----------
-
-GDB support is not required to go through the tutorial. See the "Using GDB"
-section of :doc:`ovs-advanced` for more info. Additional flags exist for
-launching the debugger for the OVN programs::
-
-    --gdb-ovn-northd
-    --gdb-ovn-controller
-    --gdb-ovn-controller-vtep
-
-Creating OVN Resources
-----------------------
-
-Once you have ``ovs-sandbox`` running with OVN enabled, you can start using OVN
-utilities to create resources in OVN.  As an example, we will create an
-environment that has two logical switches connected by a logical router.
-
-Create the first logical switch with one port::
-
-    $ ovn-nbctl ls-add sw0
-    $ ovn-nbctl lsp-add sw0 sw0-port1
-    $ ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2"
-
-Create the second logical switch with one port::
-
-    $ ovn-nbctl ls-add sw1
-    $ ovn-nbctl lsp-add sw1 sw1-port1
-    $ ovn-nbctl lsp-set-addresses sw1-port1 "50:54:00:00:00:03 11.0.0.2"
-
-Create the logical router and attach both logical switches::
-
-    $ ovn-nbctl lr-add lr0
-    $ ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 192.168.0.1/24
-    $ ovn-nbctl lsp-add sw0 lrp0-attachment
-    $ ovn-nbctl lsp-set-type lrp0-attachment router
-    $ ovn-nbctl lsp-set-addresses lrp0-attachment 00:00:00:00:ff:01
-    $ ovn-nbctl lsp-set-options lrp0-attachment router-port=lrp0
-    $ ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 11.0.0.1/24
-    $ ovn-nbctl lsp-add sw1 lrp1-attachment
-    $ ovn-nbctl lsp-set-type lrp1-attachment router
-    $ ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02
-    $ ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
-
-View a summary of OVN's current logical configuration::
-
-    $ ovn-nbctl show
-        switch 1396cf55-d176-4082-9a55-1c06cef626e4 (sw1)
-            port lrp1-attachment
-                addresses: ["00:00:00:00:ff:02"]
-            port sw1-port1
-                addresses: ["50:54:00:00:00:03 11.0.0.2"]
-        switch 2c9d6d03-09fc-4e32-8da6-305f129b0d53 (sw0)
-            port lrp0-attachment
-                addresses: ["00:00:00:00:ff:01"]
-            port sw0-port1
-                addresses: ["50:54:00:00:00:01 192.168.0.2"]
-        router f8377e8c-f75e-4fc8-8751-f3ea03c6dd98 (lr0)
-            port lrp0
-                mac: "00:00:00:00:ff:01"
-                networks: ["192.168.0.1/24"]
-            port lrp1
-                mac: "00:00:00:00:ff:02"
-                networks: ["11.0.0.1/24"]
-
-The ``tutorial`` directory of the OVS source tree includes a script
-that runs all of the commands for you::
-
-    $ ./ovn-setup.sh
-
-Using ovn-trace
----------------
-
-Once you have configured resources in OVN, try using ``ovn-trace`` to see
-how OVN would process a sample packet through its logical pipeline.
-
-For example, we can trace an IP packet from ``sw0-port1`` to ``sw1-port1``.
-The ``--minimal`` output shows each visible action performed on the packet,
-which includes:
-
-#. The logical router will decrement the IP TTL field.
-#. The logical router will change the source and destination
-   MAC addresses to reflect the next hop.
-#. The packet will be output to ``sw1-port1``.
-
-::
-
-    $ ovn-trace --minimal sw0 'inport == "sw0-port1" \
-    > && eth.src == 50:54:00:00:00:01 && ip4.src == 192.168.0.2 \
-    > && eth.dst == 00:00:00:00:ff:01 && ip4.dst == 11.0.0.2 \
-    > && ip.ttl == 64'
-
-    # ip,reg14=0x1,vlan_tci=0x0000,dl_src=50:54:00:00:00:01,dl_dst=00:00:00:00:ff:01,nw_src=192.168.0.2,nw_dst=11.0.0.2,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=64
-    ip.ttl--;
-    eth.src = 00:00:00:00:ff:02;
-    eth.dst = 50:54:00:00:00:03;
-    output("sw1-port1");
-
-The ``ovn-trace`` utility can also provide much more detail on how the packet
-would be processed through OVN's logical pipeline, as well as correlate that
-to OpenFlow flows programmed by ``ovn-controller``.  See the `ovn-trace(8)`_
-man page for more detail.
-
-
-.. _ovn-architecture: http://openvswitch.org/support/dist-docs/ovn-architecture.7.html
-.. _ovn-nb(5): http://openvswitch.org/support/dist-docs/ovn-nb.5.html
-.. _ovn-sb(5): http://openvswitch.org/support/dist-docs/ovn-sb.5.html
-.. _vtep(5): http://openvswitch.org/support/dist-docs/vtep.5.html
-.. _ovn-northd(8): http://openvswitch.org/support/dist-docs/ovn-northd.8.html
-.. _ovn-controller(8): http://openvswitch.org/support/dist-docs/ovn-controller.8.html
-.. _ovn-controller-vtep(8): http://openvswitch.org/support/dist-docs/ovn-controller-vtep.8.html
-.. _vtep-ctl(8): http://openvswitch.org/support/dist-docs/vtep-ctl.8.html
-.. _ovn-nbctl(8): http://openvswitch.org/support/dist-docs/ovn-nbctl.8.html
-.. _ovn-sbctl(8): http://openvswitch.org/support/dist-docs/ovn-sbctl.8.html
-.. _ovn-trace(8): http://openvswitch.org/support/dist-docs/ovn-trace.8.html
diff --git a/Makefile.am b/Makefile.am
index ff1f94b48..a03a0351a 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/NEWS b/NEWS
index c5caa13d6..289d1e4f9 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,9 @@
 Post-v2.12.0
 ---------------------
-
+   - OVN:
+     * OVN has been removed from this repository. It now exists as a
+       separate project. You can find it at
+       https://github.com/ovn-org/ovn.git
 
 v2.12.0 - xx xxx xxxx
 ---------------------
diff --git a/configure.ac b/configure.ac
index e3603926b..64ce4cef0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -148,7 +148,6 @@ AC_CONFIG_FILES([
     ofproto/libofproto.sym
     lib/libsflow.sym
     lib/libopenvswitch.sym
-    ovn/lib/libovn.sym
     vtep/libvtep.sym])
 
 OVS_ENABLE_OPTION([-Wall])
@@ -210,8 +209,6 @@ dnl This makes sure that include/openflow gets created in the build directory.
 AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])
 
 AC_CONFIG_COMMANDS([utilities/bugtool/dummy], [:])
-AC_CONFIG_COMMANDS([ovn/dummy], [:])
-AC_CONFIG_COMMANDS([ovn/utilities/dummy], [:])
 AC_CONFIG_COMMANDS([ipsec/dummy], [:])
 
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
diff --git a/debian/.gitignore b/debian/.gitignore
index 9ec70eb9c..441a8ffb7 100644
--- a/debian/.gitignore
+++ b/debian/.gitignore
@@ -22,10 +22,5 @@
 /openvswitch-test
 /openvswitch-testcontroller
 /openvswitch-vtep
-/ovn-common
-/ovn-controller-vtep
-/ovn-host
-/ovn-central
-/ovn-docker
 /python-openvswitch
 /tmp
diff --git a/debian/automake.mk b/debian/automake.mk
index 8a8d43c9f..03a1d68c2 100644
--- a/debian/automake.mk
+++ b/debian/automake.mk
@@ -52,28 +52,6 @@ EXTRA_DIST += \
 	debian/openvswitch-vtep.init \
 	debian/openvswitch-vtep.install \
 	debian/openvswitch-vtep.manpages \
-	debian/ovn-central.dirs \
-	debian/ovn-central.init \
-	debian/ovn-central.install \
-	debian/ovn-central.manpages \
-	debian/ovn-central.postinst \
-	debian/ovn-central.postrm \
-	debian/ovn-central.template \
-	debian/ovn-controller-vtep.init \
-	debian/ovn-controller-vtep.install \
-	debian/ovn-controller-vtep.manpages \
-	debian/ovn-common.install \
-	debian/ovn-common.manpages \
-	debian/ovn-common.postinst \
-	debian/ovn-common.postrm \
-	debian/ovn-docker.install \
-	debian/ovn-host.dirs \
-	debian/ovn-host.init \
-	debian/ovn-host.install \
-	debian/ovn-host.manpages \
-	debian/ovn-host.postinst \
-	debian/ovn-host.postrm \
-	debian/ovn-host.template \
 	debian/python-openvswitch.dirs \
 	debian/python-openvswitch.install \
 	debian/rules \
diff --git a/debian/control b/debian/control
index b97e99b92..2ad35f2ce 100644
--- a/debian/control
+++ b/debian/control
@@ -119,84 +119,6 @@ Description: Open vSwitch switch implementations
  openvswitch-switch provides the userspace components and utilities for
  the Open vSwitch kernel-based switch.
 
-Package: ovn-common
-Architecture: linux-any
-Depends: openvswitch-common (= ${binary:Version}),
-         ${misc:Depends},
-         ${shlibs:Depends}
-Description: OVN common components
- OVN, the Open Virtual Network, is a system to support virtual network
- abstraction.  OVN complements the existing capabilities of OVS to add
- native support for virtual network abstractions, such as virtual L2 and L3
- overlays and security groups.
- .
- ovn-common provides components required by other OVN packages.
-
-Package: ovn-controller-vtep
-Architecture: linux-any
-Depends: ovn-common (= ${binary:Version}),
-         ${misc:Depends},
-         ${shlibs:Depends}
-Description: OVN vtep controller
- ovn-controller-vtep is the local controller daemon in
- OVN, the Open Virtual Network, for VTEP enabled physical switches.
- It connects up to the OVN Southbound database over the OVSDB protocol,
- and down to the VTEP database over the OVSDB protocol.
- .
- ovn-controller-vtep provides the ovn-controller-vtep binary for controlling
- vtep gateways.
-
-
-Package: ovn-host
-Architecture: linux-any
-Depends: openvswitch-switch (= ${binary:Version}),
-         openvswitch-common (= ${binary:Version}),
-         ovn-common (= ${binary:Version}),
-         ${misc:Depends},
-         ${shlibs:Depends}
-Description: OVN host components
- OVN, the Open Virtual Network, is a system to support virtual network
- abstraction.  OVN complements the existing capabilities of OVS to add
- native support for virtual network abstractions, such as virtual L2 and L3
- overlays and security groups.
- .
- ovn-host provides the userspace components and utilities for
- OVN that can be run on every host/hypervisor.
-
-Package: ovn-central
-Architecture: linux-any
-Depends: openvswitch-switch (= ${binary:Version}),
-         openvswitch-common (= ${binary:Version}),
-         ovn-common (= ${binary:Version}),
-         ${misc:Depends},
-         ${shlibs:Depends}
-Description: OVN central components
- OVN, the Open Virtual Network, is a system to support virtual network
- abstraction.  OVN complements the existing capabilities of OVS to add
- native support for virtual network abstractions, such as virtual L2 and L3
- overlays and security groups.
- .
- ovn-central provides the userspace daemons, utilities and
- databases for OVN that is run at a central location.
-
-Package: ovn-docker
-Architecture: linux-any
-Depends: openvswitch-switch (= ${binary:Version}),
-         openvswitch-common (= ${binary:Version}),
-         python (>= 2.7),
-         python-openvswitch (= ${source:Version}),
-         ovn-common (= ${binary:Version}),
-         ${misc:Depends},
-         ${python:Depends},
-         ${shlibs:Depends}
-Description: OVN Docker drivers
- OVN, the Open Virtual Network, is a system to support virtual network
- abstraction.  OVN complements the existing capabilities of OVS to add
- native support for virtual network abstractions, such as virtual L2 and L3
- overlays and security groups.
- .
- ovn-docker provides the docker drivers for OVN.
-
 Package: openvswitch-pki
 Architecture: all
 Depends: openvswitch-common (<< ${source:Version}.1~),
diff --git a/debian/ovn-central.dirs b/debian/ovn-central.dirs
deleted file mode 100644
index 6394883ce..000000000
--- a/debian/ovn-central.dirs
+++ /dev/null
@@ -1 +0,0 @@
-/usr/share/ovn/central
diff --git a/debian/ovn-central.init b/debian/ovn-central.init
deleted file mode 100755
index 60cee95a3..000000000
--- a/debian/ovn-central.init
+++ /dev/null
@@ -1,60 +0,0 @@
-#! /bin/sh
-#
-### BEGIN INIT INFO
-# Provides:          ovn-central
-# Required-Start:    openvswitch-switch $remote_fs $syslog
-# Required-Stop:     $remote_fs
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: OVN central components
-# Description:       ovn-central provides the userspace daemons,
-#                    utilities and databases for OVN that is run at a central
-#                    location.
-### END INIT INFO
-
-test -x /usr/bin/ovn-northd  || exit 0
-test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0
-
-_SYSTEMCTL_SKIP_REDIRECT=yes
-SYSTEMCTL_SKIP_REDIRECT=yes
-
-. /usr/share/openvswitch/scripts/ovs-lib
-if [ -e /etc/default/ovn-central ]; then
-    . /etc/default/ovn-central
-fi
-
-start () {
-    set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_northd}
-    set "$@" $OVN_CTL_OPTS
-    "$@" || exit $?
-}
-
-stop_northd () {
-    set /usr/share/openvswitch/scripts/ovn-ctl ${1-stop_northd}
-    set "$@" $OVN_CTL_OPTS
-    "$@" || exit $?
-}
-
-case $1 in
-    start)
-        start
-        ;;
-    stop)
-        stop_northd
-        ;;
-    restart)
-        start restart_northd
-        ;;
-    reload | force-reload)
-        ;;
-    status)
-        /usr/share/openvswitch/scripts/ovn-ctl status_northd
-        exit $?
-        ;;
-    *)
-        echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2
-        exit 1
-        ;;
-esac
-
-exit 0
diff --git a/debian/ovn-central.install b/debian/ovn-central.install
deleted file mode 100644
index 733d3fc5e..000000000
--- a/debian/ovn-central.install
+++ /dev/null
@@ -1,3 +0,0 @@
-usr/bin/ovn-northd
-usr/share/openvswitch/ovn-nb.ovsschema
-usr/share/openvswitch/ovn-sb.ovsschema
diff --git a/debian/ovn-central.manpages b/debian/ovn-central.manpages
deleted file mode 100644
index 2ddb43784..000000000
--- a/debian/ovn-central.manpages
+++ /dev/null
@@ -1 +0,0 @@
-ovn/northd/ovn-northd.8
diff --git a/debian/ovn-central.postinst b/debian/ovn-central.postinst
deleted file mode 100755
index 10e07ece4..000000000
--- a/debian/ovn-central.postinst
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/sh
-# postinst script for ovn-central
-#
-# see: dh_installdeb(1)
-
-set -e
-
-# summary of how this script can be called:
-#        * <postinst> `configure' <most-recently-configured-version>
-#        * <old-postinst> `abort-upgrade' <new version>
-#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
-#          <new-version>
-#        * <postinst> `abort-remove'
-#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
-#          <failed-install-package> <version> `removing'
-#          <conflicting-package> <version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
-case "$1" in
-    configure)
-        DEFAULT=/etc/default/ovn-central
-        TEMPLATE=/usr/share/ovn/central/default.template
-        if ! test -e $DEFAULT; then
-            cp $TEMPLATE $DEFAULT
-        else
-            for var in $(awk -F'[ :]' '/^# [_A-Z0-9]+:/{print $2}' $TEMPLATE)
-            do
-                if ! grep $var $DEFAULT >/dev/null 2>&1; then
-                    echo >> $DEFAULT
-                    sed -n "/$var:/,/$var=/p" $TEMPLATE >> $DEFAULT
-                fi
-            done
-        fi
-        ;;
-
-    abort-upgrade|abort-remove|abort-deconfigure)
-        ;;
-
-    *)
-        echo "postinst called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/ovn-central.postrm b/debian/ovn-central.postrm
deleted file mode 100755
index 0e654a37a..000000000
--- a/debian/ovn-central.postrm
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/sh
-# postrm script for ovn-central
-#
-# see: dh_installdeb(1)
-
-set -e
-
-# summary of how this script can be called:
-#        * <postrm> `remove'
-#        * <postrm> `purge'
-#        * <old-postrm> `upgrade' <new-version>
-#        * <new-postrm> `failed-upgrade' <old-version>
-#        * <new-postrm> `abort-install'
-#        * <new-postrm> `abort-install' <old-version>
-#        * <new-postrm> `abort-upgrade' <old-version>
-#        * <disappearer's-postrm> `disappear' <overwriter>
-#          <overwriter-version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
-case "$1" in
-    purge)
-        rm -f /etc/default/ovn-central
-        rm -f /etc/openvswitch/ovnnb.db*
-        rm -f /etc/openvswitch/.ovnnb.db.~lock~
-        rm -f /etc/openvswitch/ovnsb.db*
-        rm -f /etc/openvswitch/.ovnsb.db.~lock~
-        rm -f /var/log/openvswitch/ovn-northd.log* || true
-        ;;
-
-    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-        ;;
-
-    *)
-        echo "postrm called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-# dh_installdeb will replace this with shell code automatically
-# generated by other debhelper scripts.
-
-#DEBHELPER#
-
-exit 0
-
-
diff --git a/debian/ovn-central.template b/debian/ovn-central.template
deleted file mode 100644
index 7cea13e50..000000000
--- a/debian/ovn-central.template
+++ /dev/null
@@ -1,5 +0,0 @@
-# This is a POSIX shell fragment                -*- sh -*-
-
-# OVN_CTL_OPTS: Extra options to pass to ovs-ctl.  This is, for example,
-# a suitable place to specify --ovn-northd-wrapper=valgrind.
-# OVN_CTL_OPTS=
diff --git a/debian/ovn-common.install b/debian/ovn-common.install
deleted file mode 100644
index 90484d2fe..000000000
--- a/debian/ovn-common.install
+++ /dev/null
@@ -1,7 +0,0 @@
-usr/bin/ovn-nbctl
-usr/bin/ovn-sbctl
-usr/bin/ovn-trace
-usr/bin/ovn-detrace
-usr/share/openvswitch/scripts/ovn-ctl
-usr/share/openvswitch/scripts/ovndb-servers.ocf
-usr/lib/*/libovn*.so.*
diff --git a/debian/ovn-common.manpages b/debian/ovn-common.manpages
deleted file mode 100644
index 249349ed8..000000000
--- a/debian/ovn-common.manpages
+++ /dev/null
@@ -1,8 +0,0 @@
-ovn/ovn-architecture.7
-ovn/ovn-nb.5
-ovn/ovn-sb.5
-ovn/utilities/ovn-ctl.8
-ovn/utilities/ovn-nbctl.8
-ovn/utilities/ovn-sbctl.8
-ovn/utilities/ovn-trace.8
-ovn/utilities/ovn-detrace.1
diff --git a/debian/ovn-common.postinst b/debian/ovn-common.postinst
deleted file mode 100644
index 588044fbc..000000000
--- a/debian/ovn-common.postinst
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-# postinst script for ovn-common
-#
-# see: dh_installdeb(1)
-
-set -e
-
-case "$1" in
-    configure)
-        mkdir -p /usr/lib/ocf/resource.d/ovn
-        ln -sf /usr/share/openvswitch/scripts/ovndb-servers.ocf /usr/lib/ocf/resource.d/ovn/ovndb-servers
-        ;;
-    abort-upgrade|abort-remove|abort-deconfigure)
-        ;;
-
-    *)
-        echo "postinst called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/ovn-common.postrm b/debian/ovn-common.postrm
deleted file mode 100644
index 9face726b..000000000
--- a/debian/ovn-common.postrm
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh
-# postrm script for openvswitch-testcontroller
-#
-# see: dh_installdeb(1)
-
-set -e
-
-case "$1" in
-    purge|remove)
-        rm -rf /usr/lib/ocf/resource.d/ovn
-        ;;
-    upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-        ;;
-
-    *)
-        echo "postrm called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/ovn-controller-vtep.init b/debian/ovn-controller-vtep.init
deleted file mode 100755
index be0a24358..000000000
--- a/debian/ovn-controller-vtep.init
+++ /dev/null
@@ -1,54 +0,0 @@
-#! /bin/sh
-#
-### BEGIN INIT INFO
-# Provides:          ovn-controller-vtep
-# Required-Start:    openvswitch-switch $remote_fs $syslog
-# Required-Stop:     $remote_fs
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: OVN Controller for VTEP enabled devices
-# Description:       ovn-controller-vtep provides the userspace
-#                    components and utilities for OVN that can be run on
-#                    hosts taht connect to VTEP enabled devices.
-### END INIT INFO
-
-test -x /usr/bin/ovn-controller-vtep  || exit 0
-test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0
-
-_SYSTEMCTL_SKIP_REDIRECT=yes
-SYSTEMCTL_SKIP_REDIRECT=yes
-
-. /usr/share/openvswitch/scripts/ovs-lib
-if [ -e /etc/default/ovn-controller-vtep ]; then
-    . /etc/default/ovn-controller-vtep
-fi
-
-start () {
-    set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_controller_vtep}
-    set "$@" $OVN_CTL_OPTS
-    "$@" || exit $?
-}
-
-case $1 in
-    start)
-        start
-        ;;
-    stop | force-stop)
-        /usr/share/openvswitch/scripts/ovn-ctl stop_controller_vtep
-        ;;
-    restart)
-        start restart_controller_vtep
-        ;;
-    status)
-        /usr/share/openvswitch/scripts/ovn-ctl status_controller_vtep
-        exit $?
-        ;;
-    reload | force-reload)
-        ;;
-    *)
-        echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2
-        exit 1
-        ;;
-esac
-
-exit 0
diff --git a/debian/ovn-controller-vtep.install b/debian/ovn-controller-vtep.install
deleted file mode 100644
index 1d208f37e..000000000
--- a/debian/ovn-controller-vtep.install
+++ /dev/null
@@ -1 +0,0 @@
-usr/bin/ovn-controller-vtep
diff --git a/debian/ovn-controller-vtep.manpages b/debian/ovn-controller-vtep.manpages
deleted file mode 100644
index 787301704..000000000
--- a/debian/ovn-controller-vtep.manpages
+++ /dev/null
@@ -1 +0,0 @@
-ovn/controller-vtep/ovn-controller-vtep.8
diff --git a/debian/ovn-docker.install b/debian/ovn-docker.install
deleted file mode 100644
index 583306732..000000000
--- a/debian/ovn-docker.install
+++ /dev/null
@@ -1,2 +0,0 @@
-usr/bin/ovn-docker-overlay-driver
-usr/bin/ovn-docker-underlay-driver
diff --git a/debian/ovn-host.dirs b/debian/ovn-host.dirs
deleted file mode 100644
index 7d3c761e1..000000000
--- a/debian/ovn-host.dirs
+++ /dev/null
@@ -1 +0,0 @@
-/usr/share/ovn/host
diff --git a/debian/ovn-host.init b/debian/ovn-host.init
deleted file mode 100755
index 39c3bcf16..000000000
--- a/debian/ovn-host.init
+++ /dev/null
@@ -1,54 +0,0 @@
-#! /bin/sh
-#
-### BEGIN INIT INFO
-# Provides:          ovn-host
-# Required-Start:    openvswitch-switch $remote_fs $syslog
-# Required-Stop:     $remote_fs
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: OVN host components
-# Description:       ovn-host provides the userspace
-#                    components and utilities for OVN that can be run on
-#                    every host/hypervisor.
-### END INIT INFO
-
-test -x /usr/bin/ovn-controller  || exit 0
-test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0
-
-_SYSTEMCTL_SKIP_REDIRECT=yes
-SYSTEMCTL_SKIP_REDIRECT=yes
-
-. /usr/share/openvswitch/scripts/ovs-lib
-if [ -e /etc/default/ovn-host ]; then
-    . /etc/default/ovn-host
-fi
-
-start () {
-    set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_controller}
-    set "$@" $OVN_CTL_OPTS
-    "$@" || exit $?
-}
-
-case $1 in
-    start)
-        start
-        ;;
-    stop | force-stop)
-        /usr/share/openvswitch/scripts/ovn-ctl stop_controller
-        ;;
-    restart)
-        start restart_controller
-        ;;
-    status)
-        /usr/share/openvswitch/scripts/ovn-ctl status_controller
-        exit $?
-        ;;
-    reload | force-reload)
-        ;;
-    *)
-        echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2
-        exit 1
-        ;;
-esac
-
-exit 0
diff --git a/debian/ovn-host.install b/debian/ovn-host.install
deleted file mode 100644
index d2de82fd9..000000000
--- a/debian/ovn-host.install
+++ /dev/null
@@ -1 +0,0 @@
-usr/bin/ovn-controller
diff --git a/debian/ovn-host.manpages b/debian/ovn-host.manpages
deleted file mode 100644
index 4f9e7bc90..000000000
--- a/debian/ovn-host.manpages
+++ /dev/null
@@ -1 +0,0 @@
-ovn/controller/ovn-controller.8
diff --git a/debian/ovn-host.postinst b/debian/ovn-host.postinst
deleted file mode 100755
index 4b3edeb75..000000000
--- a/debian/ovn-host.postinst
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/sh
-# postinst script for ovn-host
-#
-# see: dh_installdeb(1)
-
-set -e
-
-# summary of how this script can be called:
-#        * <postinst> `configure' <most-recently-configured-version>
-#        * <old-postinst> `abort-upgrade' <new version>
-#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
-#          <new-version>
-#        * <postinst> `abort-remove'
-#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
-#          <failed-install-package> <version> `removing'
-#          <conflicting-package> <version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
-case "$1" in
-    configure)
-        DEFAULT=/etc/default/ovn-host
-        TEMPLATE=/usr/share/ovn/host/default.template
-        if ! test -e $DEFAULT; then
-            cp $TEMPLATE $DEFAULT
-        else
-            for var in $(awk -F'[ :]' '/^# [_A-Z0-9]+:/{print $2}' $TEMPLATE)
-            do
-                if ! grep $var $DEFAULT >/dev/null 2>&1; then
-                    echo >> $DEFAULT
-                    sed -n "/$var:/,/$var=/p" $TEMPLATE >> $DEFAULT
-                fi
-            done
-        fi
-        ;;
-
-    abort-upgrade|abort-remove|abort-deconfigure)
-        ;;
-
-    *)
-        echo "postinst called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/ovn-host.postrm b/debian/ovn-host.postrm
deleted file mode 100755
index 4cceb9087..000000000
--- a/debian/ovn-host.postrm
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-# postrm script for ovn-host
-#
-# see: dh_installdeb(1)
-
-set -e
-
-# summary of how this script can be called:
-#        * <postrm> `remove'
-#        * <postrm> `purge'
-#        * <old-postrm> `upgrade' <new-version>
-#        * <new-postrm> `failed-upgrade' <old-version>
-#        * <new-postrm> `abort-install'
-#        * <new-postrm> `abort-install' <old-version>
-#        * <new-postrm> `abort-upgrade' <old-version>
-#        * <disappearer's-postrm> `disappear' <overwriter>
-#          <overwriter-version>
-# for details, see http://www.debian.org/doc/debian-policy/ or
-# the debian-policy package
-
-
-case "$1" in
-    purge)
-        rm -f /etc/default/ovn-host
-        rm -f /var/log/openvswitch/ovn-controller.log* || true
-        ;;
-
-    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-        ;;
-
-    *)
-        echo "postrm called with unknown argument \`$1'" >&2
-        exit 1
-        ;;
-esac
-
-# dh_installdeb will replace this with shell code automatically
-# generated by other debhelper scripts.
-
-#DEBHELPER#
-
-exit 0
-
-
diff --git a/debian/ovn-host.template b/debian/ovn-host.template
deleted file mode 100644
index 7fd54efda..000000000
--- a/debian/ovn-host.template
+++ /dev/null
@@ -1,5 +0,0 @@
-# This is a POSIX shell fragment                -*- sh -*-
-
-# OVN_CTL_OPTS: Extra options to pass to ovs-ctl.  This is, for example,
-# a suitable place to specify --ovn-controller-wrapper=valgrind.
-# OVN_CTL_OPTS=
diff --git a/debian/rules b/debian/rules
index 9d0a81f1a..77f3a1984 100755
--- a/debian/rules
+++ b/debian/rules
@@ -53,12 +53,6 @@ override_dh_install-arch:
 	# openvswitch-switch
 	cp debian/openvswitch-switch.template debian/openvswitch-switch/usr/share/openvswitch/switch/default.template
 
-	# ovn-host
-	cp debian/ovn-host.template debian/ovn-host/usr/share/ovn/host/default.template
-
-	# ovn-central
-	cp debian/ovn-central.template debian/ovn-central/usr/share/ovn/central/default.template
-
 override_dh_install-indep:
 	dh_install
 
diff --git a/include/automake.mk b/include/automake.mk
index 01031e88d..e982da87d 100644
--- a/include/automake.mk
+++ b/include/automake.mk
@@ -11,7 +11,6 @@ include/odp-netlink-macros.h: include/odp-netlink.h \
 EXTRA_DIST += build-aux/extract-odp-netlink-h build-aux/extract-odp-netlink-macros-h
 CLEANFILES += include/odp-netlink.h include/odp-netlink-macros.h
 
-include include/ovn/automake.mk
 include include/openflow/automake.mk
 include include/openvswitch/automake.mk
 include include/sparse/automake.mk
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
deleted file mode 100644
index 63d3907d8..000000000
--- a/include/ovn/actions.h
+++ /dev/null
@@ -1,622 +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.
- */
-
-#ifndef OVN_ACTIONS_H
-#define OVN_ACTIONS_H 1
-
-#include <stdbool.h>
-#include <stdint.h>
-#include "compiler.h"
-#include "expr.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/uuid.h"
-#include "util.h"
-
-struct expr;
-struct lexer;
-struct ofpbuf;
-struct shash;
-struct simap;
-struct ovn_extend_table;
-
-/* List of OVN logical actions.
- *
- * This macro is used directly only internally by this header and its
- * corresponding .c file, but the list is still of interest to developers.
- *
- * Each OVNACT invocation has the following parameters:
- *
- * 1. <ENUM>, used below in the enum definition of OVNACT_<ENUM>, and
- *    elsewhere.
- *
- * 2. <STRUCT> corresponding to a structure "struct <STRUCT>", that must be a
- *    defined below.  This structure must be an abstract definition of the
- *    action.  Its first member must have type "struct ovnact" and name
- *    "ovnact".  The structure must have a fixed length, that is, it may not
- *    end with a flexible array member.
- */
-#define OVNACTS                                       \
-    OVNACT(OUTPUT,            ovnact_null)            \
-    OVNACT(NEXT,              ovnact_next)            \
-    OVNACT(LOAD,              ovnact_load)            \
-    OVNACT(MOVE,              ovnact_move)            \
-    OVNACT(EXCHANGE,          ovnact_move)            \
-    OVNACT(DEC_TTL,           ovnact_null)            \
-    OVNACT(CT_NEXT,           ovnact_ct_next)         \
-    OVNACT(CT_COMMIT,         ovnact_ct_commit)       \
-    OVNACT(CT_DNAT,           ovnact_ct_nat)          \
-    OVNACT(CT_SNAT,           ovnact_ct_nat)          \
-    OVNACT(CT_LB,             ovnact_ct_lb)           \
-    OVNACT(CT_CLEAR,          ovnact_null)            \
-    OVNACT(CLONE,             ovnact_nest)            \
-    OVNACT(ARP,               ovnact_nest)            \
-    OVNACT(ICMP4,             ovnact_nest)            \
-    OVNACT(ICMP4_ERROR,       ovnact_nest)            \
-    OVNACT(ICMP6,             ovnact_nest)            \
-    OVNACT(IGMP,              ovnact_null)            \
-    OVNACT(TCP_RESET,         ovnact_nest)            \
-    OVNACT(ND_NA,             ovnact_nest)            \
-    OVNACT(ND_NA_ROUTER,      ovnact_nest)            \
-    OVNACT(GET_ARP,           ovnact_get_mac_bind)    \
-    OVNACT(PUT_ARP,           ovnact_put_mac_bind)    \
-    OVNACT(GET_ND,            ovnact_get_mac_bind)    \
-    OVNACT(PUT_ND,            ovnact_put_mac_bind)    \
-    OVNACT(PUT_DHCPV4_OPTS,   ovnact_put_opts)        \
-    OVNACT(PUT_DHCPV6_OPTS,   ovnact_put_opts)        \
-    OVNACT(SET_QUEUE,         ovnact_set_queue)       \
-    OVNACT(DNS_LOOKUP,        ovnact_dns_lookup)      \
-    OVNACT(LOG,               ovnact_log)             \
-    OVNACT(PUT_ND_RA_OPTS,    ovnact_put_opts)        \
-    OVNACT(ND_NS,             ovnact_nest)            \
-    OVNACT(SET_METER,         ovnact_set_meter)       \
-    OVNACT(OVNFIELD_LOAD,     ovnact_load)            \
-    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
-    OVNACT(TRIGGER_EVENT,     ovnact_controller_event)
-
-/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
-enum OVS_PACKED_ENUM ovnact_type {
-#define OVNACT(ENUM, STRUCT) OVNACT_##ENUM,
-    OVNACTS
-#undef OVNACT
-};
-
-/* Define N_OVNACTS to the number of types of ovnacts. */
-enum {
-#define OVNACT(ENUM, STRUCT) + 1
-    N_OVNACTS = OVNACTS
-#undef OVNACT
-};
-
-/* Header for an action.
- *
- * Each action is a structure "struct ovnact_*" that begins with "struct
- * ovnact", usually followed by other data that describes the action.  Actions
- * are padded out to a multiple of OVNACT_ALIGNTO bytes in length.
- */
-struct ovnact {
-    /* We want the space advantage of an 8-bit type here on every
-     * implementation, without giving up the advantage of having a useful type
-     * on implementations that support packed enums. */
-#ifdef HAVE_PACKED_ENUM
-    enum ovnact_type type;      /* OVNACT_*. */
-#else
-    uint8_t type;               /* OVNACT_* */
-#endif
-    uint8_t pad;                /* Pad to multiple of 16 bits. */
-
-    uint16_t len;               /* Length of the action, in bytes, including
-                                 * struct ovnact, excluding padding. */
-};
-BUILD_ASSERT_DECL(sizeof(struct ovnact) == 4);
-
-/* Alignment. */
-#define OVNACT_ALIGNTO 8
-#define OVNACT_ALIGN(SIZE) ROUND_UP(SIZE, OVNACT_ALIGNTO)
-
-/* Returns the ovnact following 'ovnact'. */
-static inline struct ovnact *
-ovnact_next(const struct ovnact *ovnact)
-{
-    return (void *) ((uint8_t *) ovnact + OVNACT_ALIGN(ovnact->len));
-}
-
-struct ovnact *ovnact_next_flattened(const struct ovnact *);
-
-static inline struct ovnact *
-ovnact_end(const struct ovnact *ovnacts, size_t ovnacts_len)
-{
-    return (void *) ((uint8_t *) ovnacts + ovnacts_len);
-}
-
-/* Assigns POS to each ovnact, in turn, in the OVNACTS_LEN bytes of ovnacts
- * starting at OVNACTS. */
-#define OVNACT_FOR_EACH(POS, OVNACTS, OVNACTS_LEN)                      \
-    for ((POS) = (OVNACTS); (POS) < ovnact_end(OVNACTS, OVNACTS_LEN);  \
-         (POS) = ovnact_next(POS))
-
-static inline int
-ovnacts_count(const struct ovnact *ovnacts, size_t ovnacts_len)
-{
-    uint8_t n_ovnacts = 0;
-    if (ovnacts) {
-        const struct ovnact *a;
-
-        OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-            n_ovnacts++;
-        }
-    }
-    return n_ovnacts;
-}
-
-/* Action structure for each OVNACT_*. */
-
-/* Action structure for actions that do not have any extra data beyond the
- * action type. */
-struct ovnact_null {
-    struct ovnact ovnact;
-};
-
-/* Logical pipeline in which a set of actions is executed. */
-enum ovnact_pipeline {
-    OVNACT_P_INGRESS,
-    OVNACT_P_EGRESS,
-};
-
-/* OVNACT_NEXT. */
-struct ovnact_next {
-    struct ovnact ovnact;
-
-    /* Arguments. */
-    uint8_t ltable;                /* Logical table ID of next table. */
-    enum ovnact_pipeline pipeline; /* Pipeline of next table. */
-
-    /* Information about the flow that the action is in.  This does not affect
-     * behavior, since the implementation of "next" doesn't depend on the
-     * source table or pipeline.  It does affect how ovnacts_format() prints
-     * the action. */
-    uint8_t src_ltable;                /* Logical table ID of source table. */
-    enum ovnact_pipeline src_pipeline; /* Pipeline of source table. */
-};
-
-/* OVNACT_LOAD. */
-struct ovnact_load {
-    struct ovnact ovnact;
-    struct expr_field dst;
-    union expr_constant imm;
-};
-
-/* OVNACT_MOVE, OVNACT_EXCHANGE. */
-struct ovnact_move {
-    struct ovnact ovnact;
-    struct expr_field lhs;
-    struct expr_field rhs;
-};
-
-/* OVNACT_CT_NEXT. */
-struct ovnact_ct_next {
-    struct ovnact ovnact;
-    uint8_t ltable;                /* Logical table ID of next table. */
-};
-
-/* OVNACT_CT_COMMIT. */
-struct ovnact_ct_commit {
-    struct ovnact ovnact;
-    uint32_t ct_mark, ct_mark_mask;
-    ovs_be128 ct_label, ct_label_mask;
-};
-
-/* OVNACT_CT_DNAT, OVNACT_CT_SNAT. */
-struct ovnact_ct_nat {
-    struct ovnact ovnact;
-    ovs_be32 ip;
-    uint8_t ltable;             /* Logical table ID of next table. */
-};
-
-struct ovnact_ct_lb_dst {
-    int family;
-    union {
-        struct in6_addr ipv6;
-        ovs_be32 ipv4;
-    };
-    uint16_t port;
-};
-
-/* OVNACT_CT_LB. */
-struct ovnact_ct_lb {
-    struct ovnact ovnact;
-    struct ovnact_ct_lb_dst *dsts;
-    size_t n_dsts;
-    uint8_t ltable;             /* Logical table ID of next table. */
-};
-
-/* OVNACT_ARP, OVNACT_ND_NA, OVNACT_CLONE. */
-struct ovnact_nest {
-    struct ovnact ovnact;
-    struct ovnact *nested;
-    size_t nested_len;
-};
-
-/* OVNACT_GET_ARP, OVNACT_GET_ND. */
-struct ovnact_get_mac_bind {
-    struct ovnact ovnact;
-    struct expr_field port;     /* Logical port name. */
-    struct expr_field ip;       /* 32-bit or 128-bit IP address. */
-};
-
-/* OVNACT_PUT_ARP, ONVACT_PUT_ND. */
-struct ovnact_put_mac_bind {
-    struct ovnact ovnact;
-    struct expr_field port;     /* Logical port name. */
-    struct expr_field ip;       /* 32-bit or 128-bit IP address. */
-    struct expr_field mac;      /* 48-bit Ethernet address. */
-};
-
-struct ovnact_gen_option {
-    const struct gen_opts_map *option;
-    struct expr_constant_set value;
-};
-
-/* OVNACT_PUT_DHCPV4_OPTS, OVNACT_PUT_DHCPV6_OPTS. */
-struct ovnact_put_opts {
-    struct ovnact ovnact;
-    struct expr_field dst;      /* 1-bit destination field. */
-    struct ovnact_gen_option *options;
-    size_t n_options;
-};
-
-/* Valid arguments to SET_QUEUE action.
- *
- * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should
- * start at QDISC_MIN_QUEUE_ID+1. */
-#define QDISC_MIN_QUEUE_ID  0
-#define QDISC_MAX_QUEUE_ID  0xf000
-
-/* OVNACT_SET_QUEUE. */
-struct ovnact_set_queue {
-    struct ovnact ovnact;
-    uint16_t queue_id;
-};
-
-/* OVNACT_DNS_LOOKUP. */
-struct ovnact_dns_lookup {
-    struct ovnact ovnact;
-    struct expr_field dst;      /* 1-bit destination field. */
-};
-
-/* OVNACT_LOG. */
-struct ovnact_log {
-    struct ovnact ovnact;
-    uint8_t verdict;            /* One of LOG_VERDICT_*. */
-    uint8_t severity;           /* One of LOG_SEVERITY_*. */
-    char *name;
-    char *meter;
-};
-
-/* OVNACT_SET_METER. */
-struct ovnact_set_meter {
-    struct ovnact ovnact;
-    uint64_t rate;                   /* rate field, in kbps. */
-    uint64_t burst;                  /* burst rate field, in kbps. */
-};
-
-/* OVNACT_CHECK_IP_PKT_LARGER. */
-struct ovnact_check_pkt_larger {
-    struct ovnact ovnact;
-    uint16_t pkt_len;
-    struct expr_field dst;      /* 1-bit destination field. */
-};
-
-/* OVNACT_EVENT. */
-struct ovnact_controller_event {
-    struct ovnact ovnact;
-    int event_type;   /* controller event type */
-    struct ovnact_gen_option *options;
-    size_t n_options;
-};
-
-/* Internal use by the helpers below. */
-void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
-void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
-
-/* For each OVNACT_<ENUM> with a corresponding struct <STRUCT>, this defines
- * the following commonly useful functions:
- *
- *   struct <STRUCT> *ovnact_put_<ENUM>(struct ofpbuf *ovnacts);
- *
- *     Appends a new 'ovnact', of length OVNACT_<ENUM>_SIZE, to 'ovnacts',
- *     initializes it with ovnact_init_<ENUM>(), and returns it.  Also sets
- *     'ovnacts->header' to the returned action.
- *
- *   struct <STRUCT> *ovnact_get_<ENUM>(const struct ovnact *ovnact);
- *
- *     Returns 'ovnact' cast to "struct <STRUCT> *".  'ovnact->type' must be
- *     OVNACT_<ENUM>.
- *
- * as well as the following more rarely useful definitions:
- *
- *   void ovnact_init_<ENUM>(struct <STRUCT> *ovnact);
- *
- *     Initializes the parts of 'ovnact' that identify it as having type
- *     OVNACT_<ENUM> and length OVNACT_<ENUM>_SIZE and zeros the rest.
- *
- *   <ENUM>_SIZE
- *
- *     The size of the action structure, that is, sizeof(struct <STRUCT>)
- *     rounded up to a multiple of OVNACT_ALIGNTO.
- */
-#define OVNACT(ENUM, STRUCT)                                            \
-    BUILD_ASSERT_DECL(offsetof(struct STRUCT, ovnact) == 0);            \
-                                                                        \
-    enum { OVNACT_##ENUM##_SIZE = OVNACT_ALIGN(sizeof(struct STRUCT)) }; \
-                                                                        \
-    static inline struct STRUCT *                                       \
-    ovnact_get_##ENUM(const struct ovnact *ovnact)                      \
-    {                                                                   \
-        ovs_assert(ovnact->type == OVNACT_##ENUM);                      \
-        return ALIGNED_CAST(struct STRUCT *, ovnact);                   \
-    }                                                                   \
-                                                                        \
-    static inline struct STRUCT *                                       \
-    ovnact_put_##ENUM(struct ofpbuf *ovnacts)                           \
-    {                                                                   \
-        return ovnact_put(ovnacts, OVNACT_##ENUM,                       \
-                          OVNACT_##ENUM##_SIZE);                        \
-    }                                                                   \
-                                                                        \
-    static inline void                                                  \
-    ovnact_init_##ENUM(struct STRUCT *ovnact)                           \
-    {                                                                   \
-        ovnact_init(&ovnact->ovnact, OVNACT_##ENUM,                     \
-                    OVNACT_##ENUM##_SIZE);                              \
-    }
-OVNACTS
-#undef OVNACT
-
-enum action_opcode {
-    /* "arp { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_ARP,
-
-    /* "put_arp(port, ip, mac)"
-     *
-     * Arguments are passed through the packet metadata and data, as follows:
-     *
-     *     MFF_REG0 = ip
-     *     MFF_LOG_INPORT = port
-     *     MFF_ETH_SRC = mac
-     */
-    ACTION_OPCODE_PUT_ARP,
-
-    /* "result = put_dhcp_opts(offer_ip, option, ...)".
-     *
-     * Arguments follow the action_header, in this format:
-     *   - A 32-bit or 64-bit OXM header designating the result field.
-     *   - A 32-bit integer specifying a bit offset within the result field.
-     *   - The 32-bit DHCP offer IP.
-     *   - Any number of DHCP options.
-     */
-    ACTION_OPCODE_PUT_DHCP_OPTS,
-
-    /* "nd_na { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_ND_NA,
-
-    /* "put_nd(port, ip6, mac)"
-     *
-     * Arguments are passed through the packet metadata and data, as follows:
-     *
-     *     MFF_XXREG0 = ip6
-     *     MFF_LOG_INPORT = port
-     *     MFF_ETH_SRC = mac
-     */
-    ACTION_OPCODE_PUT_ND,
-
-    /* "result = put_dhcpv6_opts(option, ...)".
-     *
-     * Arguments follow the action_header, in this format:
-     *   - A 32-bit or 64-bit OXM header designating the result field.
-     *   - A 32-bit integer specifying a bit offset within the result field.
-     *   - Any number of DHCPv6 options.
-     */
-    ACTION_OPCODE_PUT_DHCPV6_OPTS,
-
-    /* "result = dns_lookup()".
-     * Arguments follow the action_header, in this format:
-     *   - A 32-bit or 64-bit OXM header designating the result field.
-     *   - A 32-bit integer specifying a bit offset within the result field.
-     *
-     */
-    ACTION_OPCODE_DNS_LOOKUP,
-
-    /* "log(arguments)".
-     *
-     * Arguments are as follows:
-     *   - An 8-bit verdict.
-     *   - An 8-bit severity.
-     *   - A variable length string containing the name.
-     */
-    ACTION_OPCODE_LOG,
-
-    /* "result = put_nd_ra_opts(option, ...)".
-     * Arguments follow the action_header, in this format:
-     *   - A 32-bit or 64-bit OXM header designating the result field.
-     *   - A 32-bit integer specifying a bit offset within the result field.
-     *   - Any number of ICMPv6 options.
-     */
-    ACTION_OPCODE_PUT_ND_RA_OPTS,
-
-    /* "nd_ns { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_ND_NS,
-
-    /* "icmp4 { ...actions... } and icmp6 { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_ICMP,
-
-    /* "tcp_reset { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_TCP_RESET,
-
-    /* "nd_na_router { ...actions... }" with rso flag 'ND_RSO_ROUTER' set.
-        *
-        * The actions, in OpenFlow 1.3 format, follow the action_header.
-        */
-    ACTION_OPCODE_ND_NA_ROUTER,
-
-     /* MTU value (to put in the icmp4 header field - frag_mtu) follow the
-     * action header. */
-    ACTION_OPCODE_PUT_ICMP4_FRAG_MTU,
-
-    /* "icmp4_error { ...actions... }".
-     *
-     * The actions, in OpenFlow 1.3 format, follow the action_header.
-     */
-    ACTION_OPCODE_ICMP4_ERROR,
-
-    /* "trigger_event (event_type)" */
-    ACTION_OPCODE_EVENT,
-
-    /* "igmp".
-     *
-     * Snoop IGMP, learn the multicast participants
-     */
-    ACTION_OPCODE_IGMP,
-};
-
-/* Header. */
-struct action_header {
-    ovs_be32 opcode;            /* One of ACTION_OPCODE_* */
-    uint8_t pad[4];
-};
-BUILD_ASSERT_DECL(sizeof(struct action_header) == 8);
-
-OVS_PACKED(
-struct ovnfield_act_header {
-    ovs_be16 id; /* one of enum ovnfield_id. */
-    ovs_be16 len; /* Length of the ovnfield data. */
-});
-
-struct ovnact_parse_params {
-    /* A table of "struct expr_symbol"s to support (as one would provide to
-     * expr_parse()). */
-    const struct shash *symtab;
-
-    /* hmap of 'struct gen_opts_map' to support 'put_dhcp_opts' action */
-    const struct hmap *dhcp_opts;
-
-    /* hmap of 'struct gen_opts_map'  to support 'put_dhcpv6_opts' action */
-    const struct hmap *dhcpv6_opts;
-
-    /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */
-    const struct hmap *nd_ra_opts;
-
-    /* Array of hmap of 'struct gen_opts_map' to support 'trigger_event'
-     * action */
-    const struct controller_event_options *controller_event_opts;
-
-    /* Each OVN flow exists in a logical table within a logical pipeline.
-     * These parameters express this context for a set of OVN actions being
-     * parsed:
-     *
-     *     - 'n_tables' is the number of tables in the logical ingress and
-     *        egress pipelines, that is, "next" may specify a table less than
-     *        or equal to 'n_tables'.  If 'n_tables' is 0 then "next" is
-     *        disallowed entirely.
-     *
-     *     - 'cur_ltable' is the logical table of the current flow, within
-     *       'pipeline'.  If cur_ltable + 1 < n_tables, then this defines the
-     *       default table that "next" will jump to.
-     *
-     *     - 'pipeline' is the logical pipeline.  It is the default pipeline to
-     *       which 'next' will jump.  If 'pipeline' is OVNACT_P_EGRESS, then
-     *       'next' will also be able to jump into the ingress pipeline, but
-     *       the reverse is not true. */
-    enum ovnact_pipeline pipeline; /* Logical pipeline. */
-    uint8_t n_tables;              /* Number of logical flow tables. */
-    uint8_t cur_ltable;            /* 0 <= cur_ltable < n_tables. */
-};
-
-bool ovnacts_parse(struct lexer *, const struct ovnact_parse_params *,
-                    struct ofpbuf *ovnacts, struct expr **prereqsp);
-char *ovnacts_parse_string(const char *s, const struct ovnact_parse_params *,
-                           struct ofpbuf *ovnacts, struct expr **prereqsp)
-    OVS_WARN_UNUSED_RESULT;
-
-void ovnacts_format(const struct ovnact[], size_t ovnacts_len, struct ds *);
-
-struct ovnact_encode_params {
-    /* Looks up logical port 'port_name'.  If found, stores its port number in
-     * '*portp' and returns true; otherwise, returns false. */
-    bool (*lookup_port)(const void *aux, const char *port_name,
-                        unsigned int *portp);
-    const void *aux;
-
-    /* 'true' if the flow is for a switch. */
-    bool is_switch;
-
-    /* A struct to figure out the group_id for group actions. */
-    struct ovn_extend_table *group_table;
-
-    /* A struct to figure out the meter_id for meter actions. */
-    struct ovn_extend_table *meter_table;
-
-    /* The logical flow uuid that drove this action. */
-    struct uuid lflow_uuid;
-
-    /* OVN maps each logical flow table (ltable), one-to-one, onto a physical
-     * OpenFlow flow table (ptable).  A number of parameters describe this
-     * mapping and data related to flow tables:
-     *
-     *     - 'pipeline' is the logical pipeline in which the actions are
-     *       executing.
-     *
-     *     - 'ingress_ptable' is the OpenFlow table that corresponds to OVN
-     *       ingress table 0.
-     *
-     *     - 'egress_ptable' is the OpenFlow table that corresponds to OVN
-     *       egress table 0.
-     *
-     *     - 'output_ptable' should be the OpenFlow table to which the logical
-     *       "output" action will resubmit.
-     *
-     *     - 'mac_bind_ptable' should be the OpenFlow table used to track MAC
-     *       bindings. */
-    enum ovnact_pipeline pipeline; /* Logical pipeline. */
-    uint8_t ingress_ptable;     /* First OpenFlow ingress table. */
-    uint8_t egress_ptable;      /* First OpenFlow egress table. */
-    uint8_t output_ptable;      /* OpenFlow table for 'output' to resubmit. */
-    uint8_t mac_bind_ptable;    /* OpenFlow table for 'get_arp'/'get_nd' to
-                                   resubmit. */
-};
-
-void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
-                    const struct ovnact_encode_params *,
-                    struct ofpbuf *ofpacts);
-
-void ovnacts_free(struct ovnact[], size_t ovnacts_len);
-
-#endif /* ovn/actions.h */
diff --git a/include/ovn/automake.mk b/include/ovn/automake.mk
deleted file mode 100644
index 54b0e2c0e..000000000
--- a/include/ovn/automake.mk
+++ /dev/null
@@ -1,6 +0,0 @@
-ovnincludedir = $(includedir)/ovn
-ovninclude_HEADERS = \
-	include/ovn/actions.h \
-	include/ovn/expr.h \
-	include/ovn/lex.h  \
-	include/ovn/logical-fields.h
diff --git a/include/ovn/expr.h b/include/ovn/expr.h
deleted file mode 100644
index 22f633e57..000000000
--- a/include/ovn/expr.h
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright (c) 2015, 2016 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 OVN_EXPR_H
-#define OVN_EXPR_H 1
-
-/* OVN matching expression tree
- * ============================
- *
- * The data structures here form an abstract expression tree for matching
- * expressions in OVN.
- *
- * The abstract syntax tree representation of a matching expression is one of:
- *
- *    - A Boolean literal ("true" or "false").
- *
- *    - A comparison of a field (or part of a field) against a constant
- *      with one of the operators == != < <= > >=.
- *
- *    - The logical AND or OR of two or more matching expressions.
- *
- * Literals and comparisons are called "terminal" nodes, logical AND and OR
- * nodes are "nonterminal" nodes.
- *
- * The syntax for expressions includes a few other concepts that are not part
- * of the abstract syntax tree.  In these examples, x is a field, a, b, and c
- * are constants, and e1 and e2 are arbitrary expressions:
- *
- *    - Logical NOT.  The parser implements NOT by inverting the sense of the
- *      operand: !(x == a) becomes x != a, !(e1 && e2) becomes !e1 || !e2, and
- *      so on.
- *
- *    - Set membership.  The parser translates x == {a, b, c} into
- *      x == a || x == b || x == c.
- *
- *    - Reversed comparisons.  The parser translates a < x into x > a.
- *
- *    - Range expressions.  The parser translates a < x < b into
- *      x > a && x < b.
- */
-
-#include "classifier.h"
-#include "lex.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/list.h"
-#include "openvswitch/match.h"
-#include "openvswitch/meta-flow.h"
-#include "logical-fields.h"
-
-struct ds;
-struct expr;
-struct flow;
-struct ofpbuf;
-struct shash;
-struct simap;
-struct sset;
-
-/* "Measurement level" of a field.  See "Level of Measurement" in the large
- * comment on struct expr_symbol below for more information. */
-enum expr_level {
-    EXPR_L_NOMINAL,
-
-    /* Boolean values are nominal, however because of their simple nature OVN
-     * can allow both equality and inequality tests on them. */
-    EXPR_L_BOOLEAN,
-
-    /* Ordinal values can at least be ordered on a scale.  OVN allows equality
-     * and inequality and relational tests on ordinal values.  These are the
-     * fields on which OVS allows bitwise matching. */
-    EXPR_L_ORDINAL
-};
-
-const char *expr_level_to_string(enum expr_level);
-
-/* A symbol.
- *
- *
- * Name
- * ====
- *
- * Every symbol must have a name.  To be useful, the name must satisfy the
- * lexer's syntax for an identifier.
- *
- *
- * Width
- * =====
- *
- * Every symbol has a width.  For integer symbols, this is the number of bits
- * in the value; for string symbols, this is 0.
- *
- *
- * Types
- * =====
- *
- * There are three kinds of symbols:
- *
- *   Fields:
- *
- *     One might, for example, define a field named "vlan.tci" to refer to
- *     MFF_VLAN_TCI.  'field' specifies the field.
- *
- *     'parent' and 'predicate' are NULL, and 'parent_ofs' is 0.
- *
- *     Integer fields can be nominal or ordinal (see below).  String fields are
- *     always nominal.
- *
- *   Subfields:
- *
- *     'parent' specifies the field (which may itself be a subfield,
- *     recursively) in which the subfield is embedded, and 'parent_ofs' a
- *     bitwise offset from the least-significant bit of the parent.  The
- *     subfield can contain a subset of the bits of the parent or all of them
- *     (in the latter case the subfield is really just a synonym for the
- *     parent).
- *
- *     'field' and 'predicate' are NULL.
- *
- *     Only ordinal fields (see below) may have subfields, and subfields are
- *     always ordinal.
- *
- *   Predicates:
- *
- *     A predicate is an arbitrary Boolean expression that can be used in an
- *     expression much like a 1-bit field.  'predicate' specifies the Boolean
- *     expression, e.g. "ip4" might expand to "eth.type == 0x800".  The
- *     epxression might refer to other predicates, e.g. "icmp4" might expand to
- *     "ip4 && ip4.proto == 1".
- *
- *     'field' and 'parent' are NULL, and 'parent_ofs' is 0.
- *
- *     A predicate that refers to any nominal field or predicate (see below) is
- *     nominal; other predicates have Boolean level of measurement.
- *
- *
- * Level of Measurement
- * ====================
- *
- * See http://en.wikipedia.org/wiki/Level_of_measurement for the statistical
- * concept on which this classification is based.  There are three levels:
- *
- *   Ordinal:
- *
- *     In statistics, ordinal values can be ordered on a scale.  Here, we
- *     consider 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".
- *
- *     OVN supports all the usual arithmetic relations (== != < <= > >=) on
- *     ordinal fields and their subfields, because all of these can be
- *     implemented as collections of bitwise tests.
- *
- *   Nominal:
- *
- *     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.
- *
- *     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.)
- *
- *     String fields are always nominal.
- *
- *   Boolean:
- *
- *     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.
- *
- *     Only predicates (see above) have a Boolean level of measurement.
- *
- *     This isn't a standard level of measurement.
- *
- *
- * Prerequisites
- * =============
- *
- * Any symbol can have prerequisites, which are specified as a string giving an
- * additional expression that must be true whenever the symbol is referenced.
- * For example, the "icmp4.type" symbol might have prerequisite "icmp4", which
- * would cause an expression "icmp4.type == 0" to be interpreted as "icmp4.type
- * == 0 && icmp4", which would in turn expand to "icmp4.type == 0 && eth.type
- * == 0x800 && ip4.proto == 1" (assuming "icmp4" is a predicate defined as
- * suggested under "Types" above).
- *
- *
- * Crossproducting
- * ===============
- *
- * Ordinarily OVN is willing to consider using any field as a dimension in the
- * Open vSwitch "conjunctive match" extension (see ovs-ofctl(8)).  However,
- * some fields can't actually be used that way because they are necessary as
- * prerequisites.  For example, from an expression like "tcp.src == {1,2,3}
- * && tcp.dst == {4,5,6}", OVN might naturally generate flows like this:
- *
- *     conj_id=1,actions=...
- *     ip,actions=conjunction(1,1/3)
- *     ip6,actions=conjunction(1,1/3)
- *     tp_src=1,actions=conjunction(1,2/3)
- *     tp_src=2,actions=conjunction(1,2/3)
- *     tp_src=3,actions=conjunction(1,2/3)
- *     tp_dst=4,actions=conjunction(1,3/3)
- *     tp_dst=5,actions=conjunction(1,3/3)
- *     tp_dst=6,actions=conjunction(1,3/3)
- *
- * but that's not valid because any flow that matches on tp_src or tp_dst must
- * also match on either ip or ip6.  Thus, one would mark eth.type as "must
- * crossproduct", to force generating flows like this:
- *
- *     conj_id=1,actions=...
- *     ip,tp_src=1,actions=conjunction(1,1/2)
- *     ip,tp_src=2,actions=conjunction(1,1/2)
- *     ip,tp_src=3,actions=conjunction(1,1/2)
- *     ip6,tp_src=1,actions=conjunction(1,1/2)
- *     ip6,tp_src=2,actions=conjunction(1,1/2)
- *     ip6,tp_src=3,actions=conjunction(1,1/2)
- *     ip,tp_dst=4,actions=conjunction(1,2/2)
- *     ip,tp_dst=5,actions=conjunction(1,2/2)
- *     ip,tp_dst=6,actions=conjunction(1,2/2)
- *     ip6,tp_dst=4,actions=conjunction(1,2/2)
- *     ip6,tp_dst=5,actions=conjunction(1,2/2)
- *     ip6,tp_dst=6,actions=conjunction(1,2/2)
- *
- * which are acceptable.
- */
-struct expr_symbol {
-    char *name;
-    int width;
-
-    const struct mf_field *field;     /* Fields only, otherwise NULL. */
-    const struct ovn_field *ovn_field;  /* OVN Fields only, otherwise NULL. */
-    const struct expr_symbol *parent; /* Subfields only, otherwise NULL. */
-    int parent_ofs;                   /* Subfields only, otherwise 0. */
-    char *predicate;                  /* Predicates only, otherwise NULL. */
-
-    enum expr_level level;
-
-    char *prereqs;
-    bool must_crossproduct;
-    bool rw;
-};
-
-void expr_symbol_format(const struct expr_symbol *, struct ds *);
-
-/* A reference to a symbol or a subfield of a symbol.
- *
- * For string fields, ofs and n_bits are 0. */
-struct expr_field {
-    const struct expr_symbol *symbol; /* The symbol. */
-    int ofs;                          /* Starting bit offset. */
-    int n_bits;                       /* Number of bits. */
-};
-
-bool expr_field_parse(struct lexer *, const struct shash *symtab,
-                      struct expr_field *, struct expr **prereqsp);
-void expr_field_format(const struct expr_field *, struct ds *);
-
-struct expr_symbol *expr_symtab_add_field(struct shash *symtab,
-                                          const char *name, enum mf_field_id,
-                                          const char *prereqs,
-                                          bool must_crossproduct);
-struct expr_symbol *expr_symtab_add_subfield(struct shash *symtab,
-                                             const char *name,
-                                             const char *prereqs,
-                                             const char *subfield);
-struct expr_symbol *expr_symtab_add_string(struct shash *symtab,
-                                           const char *name, enum mf_field_id,
-                                           const char *prereqs);
-struct expr_symbol *expr_symtab_add_predicate(struct shash *symtab,
-                                              const char *name,
-                                              const char *expansion);
-struct expr_symbol *expr_symtab_add_ovn_field(struct shash *symtab,
-                                              const char *name,
-                                              enum ovn_field_id id);
-void expr_symtab_destroy(struct shash *symtab);
-
-/* Expression type. */
-enum expr_type {
-    EXPR_T_CMP,                 /* Compare symbol with constant. */
-    EXPR_T_AND,                 /* Logical AND of 2 or more subexpressions. */
-    EXPR_T_OR,                  /* Logical OR of 2 or more subexpressions. */
-    EXPR_T_BOOLEAN,             /* True or false constant. */
-    EXPR_T_CONDITION,           /* Conditional to be evaluated in the
-                                 * controller during expr_simplify(),
-                                 * prior to constructing OpenFlow matches. */
-};
-
-/* Expression condition type. */
-enum expr_cond_type {
-    EXPR_COND_CHASSIS_RESIDENT, /* Check if specified logical port name is
-                                 * resident on the controller chassis. */
-};
-
-/* Relational operator. */
-enum expr_relop {
-    EXPR_R_EQ,                  /* == */
-    EXPR_R_NE,                  /* != */
-    EXPR_R_LT,                  /* < */
-    EXPR_R_LE,                  /* <= */
-    EXPR_R_GT,                  /* > */
-    EXPR_R_GE,                  /* >= */
-};
-const char *expr_relop_to_string(enum expr_relop);
-bool expr_relop_from_token(enum lex_type type, enum expr_relop *relop);
-
-/* An abstract syntax tree for a matching expression.
- *
- * The expression code maintains and relies on a few important invariants:
- *
- *     - An EXPR_T_AND or EXPR_T_OR node never has a child of the same type.
- *       (Any such children could be merged into their parent.)  A node may
- *       have grandchildren of its own type.
- *
- *       As a consequence, every nonterminal node at the same distance from the
- *       root has the same type.
- *
- *     - EXPR_T_AND and EXPR_T_OR nodes must have at least two children.
- *
- *     - An EXPR_T_CMP node always has a nonzero mask, and never has a 1-bit
- *       in its value in a position where the mask is a 0-bit.
- *
- * The expr_honors_invariants() function can check invariants. */
-struct expr {
-    struct ovs_list node;       /* In parent EXPR_T_AND or EXPR_T_OR if any. */
-    enum expr_type type;        /* Expression type. */
-
-    union {
-        /* EXPR_T_CMP.
-         *
-         * The symbol is on the left, e.g. "field < constant". */
-        struct {
-            const struct expr_symbol *symbol;
-            enum expr_relop relop;
-
-            union {
-                char *string;
-                struct {
-                    union mf_subvalue value;
-                    union mf_subvalue mask;
-                };
-            };
-        } cmp;
-
-        /* EXPR_T_AND, EXPR_T_OR. */
-        struct ovs_list andor;
-
-        /* EXPR_T_BOOLEAN. */
-        bool boolean;
-
-        /* EXPR_T_CONDITION. */
-        struct {
-            enum expr_cond_type type;
-            bool not;
-            /* XXX Should arguments for conditions be generic? */
-            char *string;
-        } cond;
-    };
-};
-
-struct expr *expr_create_boolean(bool b);
-struct expr *expr_create_andor(enum expr_type);
-struct expr *expr_combine(enum expr_type, struct expr *a, struct expr *b);
-
-static inline struct expr *
-expr_from_node(const struct ovs_list *node)
-{
-    return CONTAINER_OF(node, struct expr, node);
-}
-
-void expr_format(const struct expr *, struct ds *);
-void expr_print(const struct expr *);
-struct expr *expr_parse(struct lexer *, const struct shash *symtab,
-                        const struct shash *addr_sets,
-                        const struct shash *port_groups,
-                        struct sset *addr_sets_ref);
-struct expr *expr_parse_string(const char *, const struct shash *symtab,
-                               const struct shash *addr_sets,
-                               const struct shash *port_groups,
-                               struct sset *addr_sets_ref,
-                               char **errorp);
-
-struct expr *expr_clone(struct expr *);
-void expr_destroy(struct expr *);
-
-struct expr *expr_annotate(struct expr *, const struct shash *symtab,
-                           char **errorp);
-struct expr *expr_simplify(struct expr *,
-                           bool (*is_chassis_resident)(const void *c_aux,
-                                                       const char *port_name),
-                           const void *c_aux);
-struct expr *expr_normalize(struct expr *);
-
-bool expr_honors_invariants(const struct expr *);
-bool expr_is_simplified(const struct expr *);
-bool expr_is_normalized(const struct expr *);
-
-char *expr_parse_microflow(const char *, const struct shash *symtab,
-                           const struct shash *addr_sets,
-                           const struct shash *port_groups,
-                           bool (*lookup_port)(const void *aux,
-                                               const char *port_name,
-                                               unsigned int *portp),
-                           const void *aux, struct flow *uflow)
-    OVS_WARN_UNUSED_RESULT;
-
-bool expr_evaluate(const struct expr *, const struct flow *uflow,
-                   bool (*lookup_port)(const void *aux, const char *port_name,
-                                       unsigned int *portp),
-                   const void *aux);
-
-/* Converting expressions to OpenFlow flows. */
-
-/* An OpenFlow match generated from a Boolean expression.  See
- * expr_to_matches() for more information. */
-struct expr_match {
-    struct hmap_node hmap_node;
-    struct match match;
-    struct cls_conjunction *conjunctions;
-    size_t n, allocated;
-};
-
-uint32_t expr_to_matches(const struct expr *,
-                         bool (*lookup_port)(const void *aux,
-                                             const char *port_name,
-                                             unsigned int *portp),
-                         const void *aux,
-                         struct hmap *matches);
-void expr_matches_destroy(struct hmap *matches);
-void expr_matches_print(const struct hmap *matches, FILE *);
-
-/* Action parsing helper. */
-
-char *expr_type_check(const struct expr_field *, int n_bits, bool rw)
-    OVS_WARN_UNUSED_RESULT;
-struct mf_subfield expr_resolve_field(const struct expr_field *);
-
-/* Type of a "union expr_constant" or "struct expr_constant_set". */
-enum expr_constant_type {
-    EXPR_C_INTEGER,
-    EXPR_C_STRING
-};
-
-/* A string or integer constant (one must know which from context). */
-union expr_constant {
-    /* Integer constant.
-     *
-     * The width of a constant isn't always clear, e.g. if you write "1",
-     * there's no way to tell whether you mean for that to be a 1-bit constant
-     * or a 128-bit constant or somewhere in between. */
-    struct {
-        union mf_subvalue value;
-        union mf_subvalue mask; /* Only initialized if 'masked'. */
-        bool masked;
-
-        enum lex_format format; /* From the constant's lex_token. */
-    };
-
-    /* Null-terminated string constant. */
-    char *string;
-};
-
-bool expr_constant_parse(struct lexer *, const struct expr_field *,
-                         union expr_constant *);
-void expr_constant_format(const union expr_constant *,
-                          enum expr_constant_type, struct ds *);
-void expr_constant_destroy(const union expr_constant *,
-                           enum expr_constant_type);
-
-/* A collection of "union expr_constant"s of the same type. */
-struct expr_constant_set {
-    union expr_constant *values;  /* Constants. */
-    size_t n_values;              /* Number of constants. */
-    enum expr_constant_type type; /* Type of the constants. */
-    bool in_curlies;              /* Whether the constants were in {}. */
-};
-
-bool expr_constant_set_parse(struct lexer *, struct expr_constant_set *);
-void expr_constant_set_format(const struct expr_constant_set *, struct ds *);
-void expr_constant_set_destroy(struct expr_constant_set *cs);
-
-
-/* Constant sets.
- *
- * For example, instead of referring to a set of IP addresses as:
- *    {addr1, addr2, ..., addrN}
- * You can register a set of values and refer to them as:
- *    $name
- *
- * If convert_to_integer is true, the set must contain
- * integer/masked-integer values. The values that don't qualify
- * are ignored.
- */
-
-void expr_const_sets_add(struct shash *const_sets, const char *name,
-                         const char * const *values, size_t n_values,
-                         bool convert_to_integer);
-void expr_const_sets_remove(struct shash *const_sets, const char *name);
-void expr_const_sets_destroy(struct shash *const_sets);
-
-#endif /* ovn/expr.h */
diff --git a/include/ovn/lex.h b/include/ovn/lex.h
deleted file mode 100644
index 8d5585766..000000000
--- a/include/ovn/lex.h
+++ /dev/null
@@ -1,152 +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.
- */
-
-#ifndef OVN_LEX_H
-#define OVN_LEX_H 1
-
-/* OVN lexical analyzer
- * ====================
- *
- * This is a simple lexical analyzer (or tokenizer) for OVN match expressions
- * and ACLs. */
-
-#include "openvswitch/meta-flow.h"
-
-struct ds;
-
-/* Token type. */
-enum lex_type {
-    LEX_T_END,                  /* end of input */
-
-    /* Tokens with auxiliary data. */
-    LEX_T_ID,                   /* foo */
-    LEX_T_STRING,               /* "foo" */
-    LEX_T_INTEGER,              /* 12345 or 1.2.3.4 or ::1 or 01:02:03:04:05 */
-    LEX_T_MASKED_INTEGER,       /* 12345/10 or 1.2.0.0/16 or ::2/127 or... */
-    LEX_T_MACRO,                /* $NAME */
-    LEX_T_PORT_GROUP,            /* @NAME */
-    LEX_T_ERROR,                /* invalid input */
-
-    /* Bare tokens. */
-    LEX_T_LPAREN,               /* ( */
-    LEX_T_RPAREN,               /* ) */
-    LEX_T_LCURLY,               /* { */
-    LEX_T_RCURLY,               /* } */
-    LEX_T_LSQUARE,              /* [ */
-    LEX_T_RSQUARE,              /* ] */
-    LEX_T_EQ,                   /* == */
-    LEX_T_NE,                   /* != */
-    LEX_T_LT,                   /* < */
-    LEX_T_LE,                   /* <= */
-    LEX_T_GT,                   /* > */
-    LEX_T_GE,                   /* >= */
-    LEX_T_LOG_NOT,              /* ! */
-    LEX_T_LOG_AND,              /* && */
-    LEX_T_LOG_OR,               /* || */
-    LEX_T_ELLIPSIS,             /* .. */
-    LEX_T_COMMA,                /* , */
-    LEX_T_SEMICOLON,            /* ; */
-    LEX_T_EQUALS,               /* = */
-    LEX_T_EXCHANGE,             /* <-> */
-    LEX_T_DECREMENT,            /* -- */
-    LEX_T_COLON,                /* : */
-};
-
-/* Subtype for LEX_T_INTEGER and LEX_T_MASKED_INTEGER tokens.
- *
- * These do not change the semantics of a token; instead, they determine the
- * format used when a token is serialized back to a text form.  That's
- * important because 3232268289 is meaningless to a human whereas 192.168.128.1
- * has some actual significance. */
-enum lex_format {
-    LEX_F_DECIMAL,
-    LEX_F_HEXADECIMAL,
-    LEX_F_IPV4,
-    LEX_F_IPV6,
-    LEX_F_ETHERNET,
-};
-const char *lex_format_to_string(enum lex_format);
-
-/* A token. */
-struct lex_token {
-    /* One of LEX_*. */
-    enum lex_type type;
-
-    /* Meaningful for LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO only.
-     * For these token types, 's' may point to 'buffer'; otherwise, it points
-     * to malloc()ed memory owned by the token.
-     *
-     * Must be NULL for other token types.
-     *
-     * For LEX_T_MACRO, 's' does not include the leading $. */
-    char *s;
-
-    /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER only. */
-    enum lex_format format;
-
-    union {
-        /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER only. */
-        struct {
-            union mf_subvalue value; /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER. */
-            union mf_subvalue mask;  /* LEX_T_MASKED_INTEGER only. */
-        };
-
-        /* LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO only. */
-        char buffer[256];
-    };
-};
-
-void lex_token_init(struct lex_token *);
-void lex_token_destroy(struct lex_token *);
-void lex_token_swap(struct lex_token *, struct lex_token *);
-void lex_token_strcpy(struct lex_token *, const char *s, size_t length);
-void lex_token_strset(struct lex_token *, char *s);
-void lex_token_vsprintf(struct lex_token *, const char *format, va_list args);
-
-void lex_token_format(const struct lex_token *, struct ds *);
-const char *lex_token_parse(struct lex_token *, const char *input,
-                            const char **startp);
-
-/* A lexical analyzer. */
-struct lexer {
-    const char *input;          /* Remaining input (not owned by lexer). */
-    const char *start;          /* Start of current token in 'input'. */
-    struct lex_token token;     /* Current token (owned by lexer). */
-    char *error;                /* Error message, if any (owned by lexer). */
-};
-
-void lexer_init(struct lexer *, const char *input);
-void lexer_destroy(struct lexer *);
-
-enum lex_type lexer_get(struct lexer *);
-enum lex_type lexer_lookahead(const struct lexer *);
-bool lexer_match(struct lexer *, enum lex_type);
-bool lexer_force_match(struct lexer *, enum lex_type);
-bool lexer_match_id(struct lexer *, const char *id);
-bool lexer_is_int(const struct lexer *);
-bool lexer_get_int(struct lexer *, int *value);
-bool lexer_force_int(struct lexer *, int *value);
-
-bool lexer_force_end(struct lexer *);
-
-void lexer_error(struct lexer *, const char *message, ...)
-    OVS_PRINTF_FORMAT(2, 3);
-void lexer_syntax_error(struct lexer *, const char *message, ...)
-    OVS_PRINTF_FORMAT(2, 3);
-
-char *lexer_steal_error(struct lexer *);
-
-#endif /* ovn/lex.h */
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
deleted file mode 100644
index 9bac8e027..000000000
--- a/include/ovn/logical-fields.h
+++ /dev/null
@@ -1,130 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_LOGICAL_FIELDS_H
-#define OVN_LOGICAL_FIELDS_H 1
-
-#include "openvswitch/meta-flow.h"
-
-struct shash;
-
-enum ovn_controller_event {
-    OVN_EVENT_EMPTY_LB_BACKENDS = 0,
-    OVN_EVENT_MAX,
-};
-
-/* Logical fields.
- *
- * These values are documented in ovn-architecture(7), please update the
- * documentation if you change any of them. */
-#define MFF_LOG_DATAPATH MFF_METADATA /* Logical datapath (64 bits). */
-#define MFF_LOG_FLAGS      MFF_REG10  /* One of MLF_* (32 bits). */
-#define MFF_LOG_DNAT_ZONE  MFF_REG11  /* conntrack dnat zone for gateway router
-                                       * (32 bits). */
-#define MFF_LOG_SNAT_ZONE  MFF_REG12  /* conntrack snat zone for gateway router
-                                       * (32 bits). */
-#define MFF_LOG_CT_ZONE    MFF_REG13  /* Logical conntrack zone for lports
-                                       * (32 bits). */
-#define MFF_LOG_INPORT     MFF_REG14  /* Logical input port (32 bits). */
-#define MFF_LOG_OUTPORT    MFF_REG15  /* Logical output port (32 bits). */
-
-/* Logical registers.
- *
- * Make sure these don't overlap with the logical fields! */
-#define MFF_LOG_REG0 MFF_REG0
-#define MFF_N_LOG_REGS 10
-
-void ovn_init_symtab(struct shash *symtab);
-
-/* MFF_LOG_FLAGS_REG bit assignments */
-enum mff_log_flags_bits {
-    MLF_ALLOW_LOOPBACK_BIT = 0,
-    MLF_RCV_FROM_VXLAN_BIT = 1,
-    MLF_FORCE_SNAT_FOR_DNAT_BIT = 2,
-    MLF_FORCE_SNAT_FOR_LB_BIT = 3,
-    MLF_LOCAL_ONLY_BIT = 4,
-    MLF_NESTED_CONTAINER_BIT = 5,
-};
-
-/* MFF_LOG_FLAGS_REG flag assignments */
-enum mff_log_flags {
-    /* Allow outputting back to inport. */
-    MLF_ALLOW_LOOPBACK = (1 << MLF_ALLOW_LOOPBACK_BIT),
-
-    /* Indicate that a packet was received from a VXLAN tunnel to
-     * compensate for the lack of egress port information available in
-     * VXLAN encapsulation.  Egress port information is available for
-     * Geneve and STT tunnel types. */
-    MLF_RCV_FROM_VXLAN = (1 << MLF_RCV_FROM_VXLAN_BIT),
-
-    /* Indicate that a packet needs a force SNAT in the gateway router when
-     * DNAT has taken place. */
-    MLF_FORCE_SNAT_FOR_DNAT = (1 << MLF_FORCE_SNAT_FOR_DNAT_BIT),
-
-    /* Indicate that a packet needs a force SNAT in the gateway router when
-     * load-balancing has taken place. */
-    MLF_FORCE_SNAT_FOR_LB = (1 << MLF_FORCE_SNAT_FOR_LB_BIT),
-
-    /* Indicate that a packet that should be distributed across multiple
-     * hypervisors should instead only be output to local targets
-     */
-    MLF_LOCAL_ONLY = (1 << MLF_LOCAL_ONLY_BIT),
-
-    /* Indicate that a packet was received from a nested container. */
-    MLF_NESTED_CONTAINER = (1 << MLF_NESTED_CONTAINER_BIT),
-};
-
-/* OVN logical fields
- * ===================
- * These are the fields which OVN supports modifying which gets translated
- * to OFFlow controller action.
- *
- * OpenvSwitch doesn't support modifying these fields yet. If a field is
- * supported later by OpenvSwitch, it can be deleted from here.
- */
-
-enum ovn_field_id {
-    /*
-     * Name: "icmp4.frag_mtu" -
-     * Type: be16
-     * Description: Sets the low-order 16 bits of the ICMP4 header field
-     * (that is labelled "unused" in the ICMP specification) of the ICMP4
-     * packet as per the RFC 1191.
-     */
-    OVN_ICMP4_FRAG_MTU,
-
-    OVN_FIELD_N_IDS
-};
-
-struct ovn_field {
-    enum ovn_field_id id;
-    const char *name;
-    unsigned int n_bytes;       /* Width of the field in bytes. */
-    unsigned int n_bits;        /* Number of significant bits in field. */
-};
-
-static inline const struct ovn_field *
-ovn_field_from_id(enum ovn_field_id id)
-{
-    extern const struct ovn_field ovn_fields[OVN_FIELD_N_IDS];
-    ovs_assert((unsigned int) id < OVN_FIELD_N_IDS);
-    return &ovn_fields[id];
-}
-
-const char *event_to_string(enum ovn_controller_event event);
-int string_to_event(const char *s);
-const struct ovn_field *ovn_field_from_name(const char *name);
-void ovn_destroy_ovnfields(void);
-#endif /* ovn/lib/logical-fields.h */
diff --git a/lib/db-ctl-base.xml b/lib/db-ctl-base.xml
index a5fcc901c..10124c3ad 100644
--- a/lib/db-ctl-base.xml
+++ b/lib/db-ctl-base.xml
@@ -40,8 +40,8 @@
     <dd>
       Either a universally unique identifier in the style of RFC 4122,
       e.g. <code>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</code>, or an <code>@</code><var>name</var>
-      defined by a <code>get</code> or <code>create</code> command within the same <code>ovn-nbctl</code>
-      invocation.
+      defined by a <code>get</code> or <code>create</code> command within the
+      same <code>ovs-vsctl</code> invocation.
     </dd>
 
   </dl>
@@ -177,7 +177,7 @@
       </p>
 
       <p>
-        The UUIDs shown for rows created in the same <code>ovn-nbctl</code>
+        The UUIDs shown for rows created in the same <code>ovs-vsctl</code>
         invocation will be wrong.
       </p>
 
@@ -199,7 +199,7 @@
       </p>
       <p>
         If <code>@</code><var>name</var> is specified, then the UUID for <var>record</var> may be
-        referred to by that name later in the same <code>ovn-nbctl</code>
+        referred to by that name later in the same <code>ovs-vsctl</code>
         invocation in contexts where a UUID is expected.
       </p>
       <p>
@@ -379,8 +379,8 @@
       </dl>
       <p>
         Consider specifying <code>--timeout=0</code> along with
-        <code>--wait-until</code>, to prevent <code>ovn-nbctl</code> from terminating
-        after waiting only at most 5 seconds.
+        <code>--wait-until</code>, to prevent <code>ovs-vsctl</code> from
+        terminating after waiting only at most 5 seconds.
       </p>
     </dd>
 
diff --git a/manpages.mk b/manpages.mk
index 5f43aa387..5012977aa 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -1,33 +1,5 @@
 # Generated automatically -- do not modify!    -*- buffer-read-only: t -*-
 
-ovn/utilities/ovn-detrace.1: \
-	ovn/utilities/ovn-detrace.1.in \
-	lib/common-syn.man \
-	lib/common.man \
-	lib/ovs.tmac
-ovn/utilities/ovn-detrace.1.in:
-lib/common-syn.man:
-lib/common.man:
-lib/ovs.tmac:
-
-ovn/utilities/ovn-sbctl.8: \
-	ovn/utilities/ovn-sbctl.8.in \
-	lib/common.man \
-	lib/db-ctl-base.man \
-	lib/ovs.tmac \
-	lib/ssl-bootstrap.man \
-	lib/ssl.man \
-	lib/table.man \
-	lib/vlog.man
-ovn/utilities/ovn-sbctl.8.in:
-lib/common.man:
-lib/db-ctl-base.man:
-lib/ovs.tmac:
-lib/ssl-bootstrap.man:
-lib/ssl.man:
-lib/table.man:
-lib/vlog.man:
-
 ovsdb/ovsdb-client.1: \
 	ovsdb/ovsdb-client.1.in \
 	lib/common-syn.man \
diff --git a/ovn/.gitignore b/ovn/.gitignore
deleted file mode 100644
index d971938aa..000000000
--- 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/TODO.rst b/ovn/TODO.rst
deleted file mode 100644
index 33489174f..000000000
--- a/ovn/TODO.rst
+++ /dev/null
@@ -1,147 +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.
-
-      Convention for heading levels in Open vSwitch documentation:
-
-      =======  Heading 0 (reserved for the title in a document)
-      -------  Heading 1
-      ~~~~~~~  Heading 2
-      +++++++  Heading 3
-      '''''''  Heading 4
-
-      Avoid deeper levels because they do not render well.
-
-==============
-OVN To-do List
-==============
-
-* Get incremental updates in ovn-controller and ovn-northd in some
-  sensible way.
-
-* Live migration.
-
-  Russell Bryant: "When you're ready to have the destination take over, you
-  have to remove the iface-id from the source and add it at the destination and
-  I think it'd typically be configured on both ends, since it's a clone of the
-  source VM (and it's config)."
-
-* VLAN trunk ports.
-
-  Russell Bryant: "Today that would require creating 4096 ports for the VM and
-  attach to 4096 OVN networks, so doable, but not quite ideal."
-
-* Service function chaining.
-
-* MAC learning.
-
-  Han Zhou: "To support VMs that hosts workloads with their own macs, e.g.
-  containers, if not using OVN native container support."
-
-* Finish up ARP/ND support: re-checking bindings, expiring bindings.
-
-* Hitless upgrade, especially for data plane.
-
-* Use OpenFlow "bundles" for transactional data plane updates.
-
-* Dynamic IP to MAC binding enhancements.
-
-  OVN has basic support for establishing IP to MAC bindings dynamically, using
-  ARP.
-
-  * Ratelimiting.
-
-    From casual observation, Linux appears to generate at most one ARP per
-    second per destination.
-
-    This might be supported by adding a new OVN logical action for
-    rate-limiting.
-
-  * Tracking queries
-
-     It's probably best to only record in the database responses to queries
-     actually issued by an L3 logical router, so somehow they have to be
-     tracked, probably by putting a tentative binding without a MAC address
-     into the database.
-
-  * Renewal and expiration.
-
-    Something needs to make sure that bindings remain valid and expire those
-    that become stale.
-
-    One way to do this might be to add some support for time to the database
-    server itself.
-
-  * Table size limiting.
-
-    The table of MAC bindings must not be allowed to grow unreasonably large.
-
-  * MTU handling (fragmentation on output)
-
-* ovsdb-server
-
-  ovsdb-server should have adequate features for OVN but it probably needs work
-  for scale and possibly for availability as deployments grow.  Here are some
-  thoughts.
-
-  * Multithreading.
-
-    If it turns out that other changes don't let ovsdb-server scale
-    adequately, we can multithread ovsdb-server.  Initially one might
-    only break protocol handling into separate threads, leaving the
-    actual database work serialized through a lock.
-
-  * Reducing startup time.
-
-    As-is, if ovsdb-server restarts, every client will fetch a fresh copy of
-    the part of the database that it cares about.  With hundreds of clients,
-    this could cause heavy CPU load on ovsdb-server and use excessive network
-    bandwidth.  It would be better to allow incremental updates even across
-    connection loss.  One way might be to use "Difference Digests" as described
-    in Epstein et al., "What's the Difference? Efficient Set Reconciliation
-    Without Prior Context".  (I'm not yet aware of previous non-academic use of
-    this technique.)
-
-* Support multiple tunnel encapsulations in Chassis.
-
-  So far, both ovn-controller and ovn-controller-vtep only allow chassis to
-  have one tunnel encapsulation entry.  We should extend the implementation
-  to support multiple tunnel encapsulations.
-
-* Update learned MAC addresses from VTEP to OVN
-
-  The VTEP gateway stores all MAC addresses learned from its physical
-  interfaces in the 'Ucast_Macs_Local' and the 'Mcast_Macs_Local' tables.
-  ovn-controller-vtep should be able to update that information back to
-  ovn-sb database, so that other chassis know where to send packets destined
-  to the extended external network instead of broadcasting.
-
-* Translate ovn-sb Multicast_Group table into VTEP config
-
-  The ovn-controller-vtep daemon should be able to translate the
-  Multicast_Group table entry in ovn-sb database into Mcast_Macs_Remote table
-  configuration in VTEP database.
-
-* OVN OCF pacemaker script to support Active / Passive HA for OVN dbs provides
-  the option to configure the inactivity_probe value. The default 5 seconds
-  inactivity_probe value is not sufficient and ovsdb-server drops the client
-  IDL connections for openstack deployments when the neutron server is heavily
-  loaded.
-
-  We need to find a proper solution to solve this issue instead of increasing
-  the inactivity_probe value.
-
-* ACL
-
-  * Support FTP ALGs.
-
-  * Support reject action.
diff --git a/ovn/automake.mk b/ovn/automake.mk
deleted file mode 100644
index b33112ef1..000000000
--- a/ovn/automake.mk
+++ /dev/null
@@ -1,92 +0,0 @@
-# OVN southbound schema and IDL
-EXTRA_DIST += ovn/ovn-sb.ovsschema
-pkgdata_DATA += ovn/ovn-sb.ovsschema
-
-# OVN southbound E-R diagram
-#
-# If "python" or "dot" is not available, then we do not add graphical diagram
-# to the documentation.
-if HAVE_PYTHON
-if HAVE_DOT
-ovn/ovn-sb.gv: ovsdb/ovsdb-dot.in ovn/ovn-sb.ovsschema
-	$(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn/ovn-sb.ovsschema > $@
-ovn/ovn-sb.pic: ovn/ovn-sb.gv ovsdb/dot2pic
-	$(AM_V_GEN)(dot -T plain < ovn/ovn-sb.gv | $(PYTHON) $(srcdir)/ovsdb/dot2pic -f 3) > $@.tmp && \
-	mv $@.tmp $@
-OVN_SB_PIC = ovn/ovn-sb.pic
-OVN_SB_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_SB_PIC)
-CLEANFILES += ovn/ovn-sb.gv ovn/ovn-sb.pic
-endif
-endif
-
-# OVN southbound schema documentation
-EXTRA_DIST += ovn/ovn-sb.xml
-CLEANFILES += ovn/ovn-sb.5
-man_MANS += ovn/ovn-sb.5
-ovn/ovn-sb.5: \
-	ovsdb/ovsdb-doc ovn/ovn-sb.xml ovn/ovn-sb.ovsschema $(OVN_SB_PIC)
-	$(AM_V_GEN)$(OVSDB_DOC) \
-		$(OVN_SB_DOT_DIAGRAM_ARG) \
-		--version=$(VERSION) \
-		$(srcdir)/ovn/ovn-sb.ovsschema \
-		$(srcdir)/ovn/ovn-sb.xml > $@.tmp && \
-	mv $@.tmp $@
-
-# OVN northbound schema and IDL
-EXTRA_DIST += ovn/ovn-nb.ovsschema
-pkgdata_DATA += ovn/ovn-nb.ovsschema
-
-# OVN northbound E-R diagram
-#
-# If "python" or "dot" is not available, then we do not add graphical diagram
-# to the documentation.
-if HAVE_PYTHON
-if HAVE_DOT
-ovn/ovn-nb.gv: ovsdb/ovsdb-dot.in ovn/ovn-nb.ovsschema
-	$(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn/ovn-nb.ovsschema > $@
-ovn/ovn-nb.pic: ovn/ovn-nb.gv ovsdb/dot2pic
-	$(AM_V_GEN)(dot -T plain < ovn/ovn-nb.gv | $(PYTHON) $(srcdir)/ovsdb/dot2pic -f 3) > $@.tmp && \
-	mv $@.tmp $@
-OVN_NB_PIC = ovn/ovn-nb.pic
-OVN_NB_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_NB_PIC)
-CLEANFILES += ovn/ovn-nb.gv ovn/ovn-nb.pic
-endif
-endif
-
-# OVN northbound schema documentation
-EXTRA_DIST += ovn/ovn-nb.xml
-CLEANFILES += ovn/ovn-nb.5
-man_MANS += ovn/ovn-nb.5
-ovn/ovn-nb.5: \
-	ovsdb/ovsdb-doc ovn/ovn-nb.xml ovn/ovn-nb.ovsschema $(OVN_NB_PIC)
-	$(AM_V_GEN)$(OVSDB_DOC) \
-		$(OVN_NB_DOT_DIAGRAM_ARG) \
-		--version=$(VERSION) \
-		$(srcdir)/ovn/ovn-nb.ovsschema \
-		$(srcdir)/ovn/ovn-nb.xml > $@.tmp && \
-	mv $@.tmp $@
-
-man_MANS += ovn/ovn-architecture.7
-EXTRA_DIST += ovn/ovn-architecture.7.xml
-CLEANFILES += ovn/ovn-architecture.7
-
-EXTRA_DIST += \
-	ovn/TODO.rst
-
-# Version checking for ovn-nb.ovsschema.
-ALL_LOCAL += ovn/ovn-nb.ovsschema.stamp
-ovn/ovn-nb.ovsschema.stamp: ovn/ovn-nb.ovsschema
-	$(srcdir)/build-aux/cksum-schema-check $? $@
-CLEANFILES += ovn/ovn-nb.ovsschema.stamp
-
-# Version checking for ovn-sb.ovsschema.
-ALL_LOCAL += ovn/ovn-sb.ovsschema.stamp
-ovn/ovn-sb.ovsschema.stamp: ovn/ovn-sb.ovsschema
-	$(srcdir)/build-aux/cksum-schema-check $? $@
-CLEANFILES += ovn/ovn-sb.ovsschema.stamp
-
-include ovn/controller/automake.mk
-include ovn/controller-vtep/automake.mk
-include ovn/lib/automake.mk
-include ovn/northd/automake.mk
-include ovn/utilities/automake.mk
diff --git a/ovn/controller-vtep/.gitignore b/ovn/controller-vtep/.gitignore
deleted file mode 100644
index 3ec8072c7..000000000
--- a/ovn/controller-vtep/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/ovn-controller-vtep
-/ovn-controller-vtep.8
diff --git a/ovn/controller-vtep/automake.mk b/ovn/controller-vtep/automake.mk
deleted file mode 100644
index 0c83dc70a..000000000
--- a/ovn/controller-vtep/automake.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-bin_PROGRAMS += ovn/controller-vtep/ovn-controller-vtep
-ovn_controller_vtep_ovn_controller_vtep_SOURCES = \
-	ovn/controller-vtep/binding.c \
-	ovn/controller-vtep/binding.h \
-	ovn/controller-vtep/gateway.c \
-	ovn/controller-vtep/gateway.h \
-	ovn/controller-vtep/ovn-controller-vtep.c \
-	ovn/controller-vtep/ovn-controller-vtep.h \
-	ovn/controller-vtep/vtep.c \
-	ovn/controller-vtep/vtep.h
-ovn_controller_vtep_ovn_controller_vtep_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la vtep/libvtep.la
-man_MANS += ovn/controller-vtep/ovn-controller-vtep.8
-EXTRA_DIST += ovn/controller-vtep/ovn-controller-vtep.8.xml
-CLEANFILES += ovn/controller-vtep/ovn-controller-vtep.8
diff --git a/ovn/controller-vtep/binding.c b/ovn/controller-vtep/binding.c
deleted file mode 100644
index 9cbfadc71..000000000
--- a/ovn/controller-vtep/binding.c
+++ /dev/null
@@ -1,274 +0,0 @@
-/* Copyright (c) 2015 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 "binding.h"
-
-#include "openvswitch/shash.h"
-#include "lib/smap.h"
-#include "lib/util.h"
-#include "openvswitch/vlog.h"
-#include "ovn-controller-vtep.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "vtep/vtep-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(binding);
-
-/*
- * This module scans through the Port_Binding table in ovnsb.  If there is a
- * logical port binding entry for logical switch in vtep gateway chassis's
- * 'vtep_logical_switches' column, sets the binding's chassis column to the
- * corresponding vtep gateway chassis.
- *
- */
-
-
-/* Returns true if the 'vtep_lswitch' specified in 'port_binding_rec'
- * has already been bound to another port binding entry, and resets
- * 'port_binding_rec''s chassis column.  Otherwise, updates 'ls_to_pb'
- * and returns false. */
-static bool
-check_pb_conflict(struct shash *ls_to_pb,
-                  const struct sbrec_port_binding *port_binding_rec,
-                  const char *chassis_name,
-                  const char *vtep_lswitch)
-{
-    const struct sbrec_port_binding *pb_conflict =
-        shash_find_data(ls_to_pb, vtep_lswitch);
-
-    if (pb_conflict) {
-        VLOG_WARN("logical switch (%s), on vtep gateway chassis "
-                  "(%s) has already been associated with logical "
-                  "port (%s), ignore logical port (%s)",
-                  vtep_lswitch, chassis_name,
-                  pb_conflict->logical_port,
-                  port_binding_rec->logical_port);
-        sbrec_port_binding_set_chassis(port_binding_rec, NULL);
-
-        return true;
-    }
-
-    shash_add(ls_to_pb, vtep_lswitch, port_binding_rec);
-    return false;
-}
-
-/* Returns true if the 'vtep_lswitch' specified in 'port_binding_rec'
- * has already been bound to a different datapath, and resets
- * 'port_binding_rec''s chassis column.  Otherwise, updates 'ls_to_db' and
- * returns false. */
-static bool
-check_db_conflict(struct shash *ls_to_db,
-                  const struct sbrec_port_binding *port_binding_rec,
-                  const char *chassis_name,
-                  const char *vtep_lswitch)
-{
-    const struct sbrec_datapath_binding *db_conflict =
-        shash_find_data(ls_to_db, vtep_lswitch);
-
-    if (db_conflict && db_conflict != port_binding_rec->datapath) {
-        VLOG_WARN("logical switch (%s), on vtep gateway chassis "
-                  "(%s) has already been associated with logical "
-                  "datapath (with tunnel key %"PRId64"), ignore "
-                  "logical port (%s) which belongs to logical "
-                  "datapath (with tunnel key %"PRId64")",
-                  vtep_lswitch, chassis_name,
-                  db_conflict->tunnel_key,
-                  port_binding_rec->logical_port,
-                  port_binding_rec->datapath->tunnel_key);
-        sbrec_port_binding_set_chassis(port_binding_rec, NULL);
-
-        return true;
-    }
-
-    shash_replace(ls_to_db, vtep_lswitch, port_binding_rec->datapath);
-    return false;
-}
-
-/* Updates the 'port_binding_rec''s chassis column to 'chassis_rec'. */
-static void
-update_pb_chassis(const struct sbrec_port_binding *port_binding_rec,
-                  const struct sbrec_chassis *chassis_rec)
-{
-    if (port_binding_rec->chassis != chassis_rec) {
-        if (chassis_rec && port_binding_rec->chassis) {
-            VLOG_DBG("Changing chassis association of logical "
-                     "port (%s) from (%s) to (%s)",
-                     port_binding_rec->logical_port,
-                     port_binding_rec->chassis->name,
-                     chassis_rec->name);
-        }
-        sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec);
-    }
-}
-
-
-/* Checks and updates logical port to vtep logical switch bindings for each
- * physical switch in VTEP. */
-void
-binding_run(struct controller_vtep_ctx *ctx)
-{
-    if (!ctx->ovnsb_idl_txn) {
-        return;
-    }
-
-    /* 'ls_to_db'
-     *
-     * Maps vtep logical switch name to the datapath binding entry.  This is
-     * used to guarantee that each vtep logical switch is only included
-     * in only one ovn datapath (ovn logical switch).  See check_db_conflict()
-     * for details.
-     *
-     * 'ls_to_pb'
-     *
-     * Maps vtep logical switch name to the port binding entry.  This is used
-     * to guarantee that each vtep logical switch on a vtep physical switch
-     * is only bound to one logical port.  See check_pb_conflict() for
-     * details.
-     *
-     */
-    struct shash ls_to_db = SHASH_INITIALIZER(&ls_to_db);
-
-    /* Stores the 'chassis' and the 'ls_to_pb' map related to
-     * a vtep physcial switch. */
-    struct ps {
-        const struct sbrec_chassis *chassis_rec;
-        struct shash ls_to_pb;
-    };
-    struct shash ps_map = SHASH_INITIALIZER(&ps_map);
-    const struct vteprec_physical_switch *pswitch;
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
-        const struct sbrec_chassis *chassis_rec
-            = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
-        struct ps *ps = xmalloc(sizeof *ps);
-        size_t i;
-
-        /* 'chassis_rec' must exist. */
-        ovs_assert(chassis_rec);
-        ps->chassis_rec = chassis_rec;
-        shash_init(&ps->ls_to_pb);
-        for (i = 0; i < chassis_rec->n_vtep_logical_switches; i++) {
-            shash_add(&ps->ls_to_pb, chassis_rec->vtep_logical_switches[i],
-                      NULL);
-        }
-        shash_add(&ps_map, chassis_rec->name, ps);
-    }
-
-    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn,
-                              "ovn-controller-vtep: updating bindings");
-
-    const struct sbrec_port_binding *port_binding_rec;
-    /* Port binding for vtep gateway chassis must have type "vtep",
-     * and matched physical switch name and logical switch name. */
-    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) {
-        const char *type = port_binding_rec->type;
-        const char *vtep_pswitch = smap_get(&port_binding_rec->options,
-                                            "vtep-physical-switch");
-        const char *vtep_lswitch = smap_get(&port_binding_rec->options,
-                                            "vtep-logical-switch");
-        struct ps *ps
-            = vtep_pswitch ? shash_find_data(&ps_map, vtep_pswitch) : NULL;
-        bool found_ls
-            = ps && vtep_lswitch && shash_find(&ps->ls_to_pb, vtep_lswitch);
-
-        if (!strcmp(type, "vtep") && found_ls) {
-            bool pb_conflict, db_conflict;
-
-            pb_conflict = check_pb_conflict(&ps->ls_to_pb, port_binding_rec,
-                                            ps->chassis_rec->name,
-                                            vtep_lswitch);
-            db_conflict = check_db_conflict(&ls_to_db, port_binding_rec,
-                                            ps->chassis_rec->name,
-                                            vtep_lswitch);
-            /* Updates port binding's chassis column when there
-             * is no conflict. */
-            if (!pb_conflict && !db_conflict) {
-                update_pb_chassis(port_binding_rec, ps->chassis_rec);
-            }
-        } else if (port_binding_rec->chassis
-                   && shash_find(&ps_map, port_binding_rec->chassis->name)) {
-            /* Resets 'port_binding_rec' since it is no longer bound to
-             * any vtep logical switch. */
-            update_pb_chassis(port_binding_rec, NULL);
-        }
-    }
-
-    struct shash_node *iter, *next;
-    SHASH_FOR_EACH_SAFE (iter, next, &ps_map) {
-        struct ps *ps = iter->data;
-        struct shash_node *node;
-
-        SHASH_FOR_EACH (node, &ps->ls_to_pb) {
-            if (!node->data) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-                VLOG_DBG_RL(&rl, "No port binding entry for logical switch (%s)"
-                            "on vtep gateway chassis (%s)", node->name,
-                            ps->chassis_rec->name);
-            }
-        }
-        shash_delete(&ps_map, iter);
-        shash_destroy(&ps->ls_to_pb);
-        free(ps);
-    }
-    shash_destroy(&ls_to_db);
-    shash_destroy(&ps_map);
-}
-
-/* Removes all port binding association with vtep gateway chassis.
- * Returns true when done (i.e. there is no change made to 'ctx->ovnsb_idl'),
- * otherwise returns false. */
-bool
-binding_cleanup(struct controller_vtep_ctx *ctx)
-{
-    if (!ctx->ovnsb_idl_txn) {
-        return false;
-    }
-
-    struct shash ch_to_pb = SHASH_INITIALIZER(&ch_to_pb);
-    const struct sbrec_port_binding *port_binding_rec;
-    bool all_done = true;
-    /* Hashs all port binding entries using the associated chassis name. */
-    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) {
-        if (port_binding_rec->chassis) {
-            shash_add(&ch_to_pb, port_binding_rec->chassis->name,
-                      port_binding_rec);
-        }
-    }
-
-    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn,
-                              "ovn-controller-vtep: removing bindings");
-
-    const struct vteprec_physical_switch *pswitch;
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
-        const struct sbrec_chassis *chassis_rec
-            = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
-
-        if (!chassis_rec) {
-            continue;
-        }
-
-        for (;;) {
-            port_binding_rec = shash_find_and_delete(&ch_to_pb,
-                                                     chassis_rec->name);
-            if (!port_binding_rec) {
-                break;
-            }
-            all_done = false;
-            update_pb_chassis(port_binding_rec, NULL);
-        }
-    }
-    shash_destroy(&ch_to_pb);
-
-    return all_done;
-}
diff --git a/ovn/controller-vtep/binding.h b/ovn/controller-vtep/binding.h
deleted file mode 100644
index 374c1ccf8..000000000
--- a/ovn/controller-vtep/binding.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Copyright (c) 2015 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 OVN_BINDING_H
-#define OVN_BINDING_H 1
-
-#include <stdbool.h>
-
-struct controller_vtep_ctx;
-
-void binding_run(struct controller_vtep_ctx *);
-bool binding_cleanup(struct controller_vtep_ctx *);
-
-#endif /* ovn/controller-gw/binding.h */
diff --git a/ovn/controller-vtep/gateway.c b/ovn/controller-vtep/gateway.c
deleted file mode 100644
index 619c3c49a..000000000
--- a/ovn/controller-vtep/gateway.c
+++ /dev/null
@@ -1,230 +0,0 @@
-/* Copyright (c) 2015 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 "gateway.h"
-
-#include "openvswitch/poll-loop.h"
-#include "lib/simap.h"
-#include "lib/sset.h"
-#include "lib/util.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "vtep/vtep-idl.h"
-#include "ovn-controller-vtep.h"
-
-VLOG_DEFINE_THIS_MODULE(gateway);
-
-/*
- * Registers the physical switches in vtep to ovnsb as chassis.  For each
- * physical switch in the vtep database, finds all vtep logical switches that
- * are associated with the physical switch, and updates the corresponding
- * chassis's 'vtep_logical_switches' column.
- *
- */
-
-/* Global revalidation sequence number, incremented at each call to
- * 'revalidate_gateway()'. */
-static unsigned int gw_reval_seq;
-
-/* Maps all chassis created by the gateway module to their own reval_seq. */
-static struct simap gw_chassis_map = SIMAP_INITIALIZER(&gw_chassis_map);
-
-/* Creates and returns a new instance of 'struct sbrec_chassis'. */
-static const struct sbrec_chassis *
-create_chassis_rec(struct ovsdb_idl_txn *txn, const char *name,
-                   const char *encap_ip)
-{
-    const struct sbrec_chassis *chassis_rec;
-    struct sbrec_encap *encap_rec;
-
-    VLOG_INFO("add Chassis row for VTEP physical switch (%s)", name);
-
-    chassis_rec = sbrec_chassis_insert(txn);
-    sbrec_chassis_set_name(chassis_rec, name);
-    encap_rec = sbrec_encap_insert(txn);
-    sbrec_encap_set_type(encap_rec, OVN_SB_ENCAP_TYPE);
-    sbrec_encap_set_ip(encap_rec, encap_ip);
-    const struct smap options = SMAP_CONST1(&options, "csum", "false");
-    sbrec_encap_set_options(encap_rec, &options);
-    sbrec_chassis_set_encaps(chassis_rec, &encap_rec, 1);
-
-    return chassis_rec;
-}
-
-/* Revalidates chassis in ovnsb against vtep database.  Creates chassis for
- * new vtep physical switch.  And removes chassis which no longer have
- * physical switch in vtep.
- *
- * xxx: Support multiple tunnel encaps.
- *
- * */
-static void
-revalidate_gateway(struct controller_vtep_ctx *ctx)
-{
-    const struct vteprec_physical_switch *pswitch;
-
-    /* Increments the global revalidation sequence number. */
-    gw_reval_seq++;
-
-    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn,
-                              "ovn-controller-vtep: updating vtep chassis");
-
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
-        const struct sbrec_chassis *chassis_rec;
-        struct simap_node *gw_node;
-        const char *encap_ip;
-
-        encap_ip = pswitch->n_tunnel_ips ? pswitch->tunnel_ips[0] : "";
-        gw_node = simap_find(&gw_chassis_map, pswitch->name);
-        chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
-        if (chassis_rec) {
-            if (!gw_node &&
-                (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE)
-                 || strcmp(chassis_rec->encaps[0]->ip, encap_ip))) {
-                VLOG_WARN("Chassis config changing on startup, make sure "
-                          "multiple chassis are not configured : %s/%s->%s/%s",
-                          chassis_rec->encaps[0]->type,
-                          chassis_rec->encaps[0]->ip,
-                          OVN_SB_ENCAP_TYPE, encap_ip);
-            }
-            /* Updates chassis's encap if anything changed. */
-            if (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE)) {
-                VLOG_WARN("Chassis for VTEP physical switch (%s) can only have "
-                          "encap type \"%s\"", pswitch->name, OVN_SB_ENCAP_TYPE);
-                sbrec_encap_set_type(chassis_rec->encaps[0], OVN_SB_ENCAP_TYPE);
-            }
-            if (strcmp(chassis_rec->encaps[0]->ip, encap_ip)) {
-                sbrec_encap_set_ip(chassis_rec->encaps[0], encap_ip);
-            }
-            if (smap_get_bool(&chassis_rec->encaps[0]->options, "csum", true)) {
-                const struct smap options = SMAP_CONST1(&options, "csum",
-                                                                  "false");
-                sbrec_encap_set_options(chassis_rec->encaps[0], &options);
-            }
-        } else {
-            if (gw_node) {
-                VLOG_WARN("Chassis for VTEP physical switch (%s) disappears, "
-                          "maybe deleted by ovn-sbctl, adding it back",
-                          pswitch->name);
-            }
-            /* Creates a new chassis for the VTEP physical switch. */
-            create_chassis_rec(ctx->ovnsb_idl_txn, pswitch->name, encap_ip);
-        }
-        /* Updates or creates the simap node for 'pswitch->name'. */
-        simap_put(&gw_chassis_map, pswitch->name, gw_reval_seq);
-    }
-
-    struct simap_node *iter, *next;
-    /* For 'gw_node' in 'gw_chassis_map' whose data is not
-     * 'gw_reval_seq', it means the corresponding physical switch no
-     * longer exist.  So, garbage collects them. */
-    SIMAP_FOR_EACH_SAFE (iter, next, &gw_chassis_map) {
-        if (iter->data != gw_reval_seq) {
-            const struct sbrec_chassis *chassis_rec;
-
-            chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, iter->name);
-            if (chassis_rec) {
-                sbrec_chassis_delete(chassis_rec);
-            }
-            simap_delete(&gw_chassis_map, iter);
-        }
-    }
-}
-
-/* Updates the 'vtep_logical_switches' column in the Chassis table based
- * on vtep database configuration. */
-static void
-update_vtep_logical_switches(struct controller_vtep_ctx *ctx)
-{
-    const struct vteprec_physical_switch *pswitch;
-
-    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: "
-                              "updating chassis's vtep_logical_switches");
-
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
-        const struct sbrec_chassis *chassis_rec =
-            get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
-        struct sset lswitches = SSET_INITIALIZER(&lswitches);
-        size_t i;
-
-        for (i = 0; i < pswitch->n_ports; i++) {
-            const struct vteprec_physical_port *port = pswitch->ports[i];
-            size_t j;
-
-            for (j = 0; j < port->n_vlan_bindings; j++) {
-                const struct vteprec_logical_switch *vtep_lswitch;
-
-                vtep_lswitch = port->value_vlan_bindings[j];
-                /* If not already in 'lswitches', records it. */
-                if (!sset_find(&lswitches, vtep_lswitch->name)) {
-                    sset_add(&lswitches, vtep_lswitch->name);
-                }
-            }
-        }
-
-        const char **ls_arr = sset_array(&lswitches);
-        sbrec_chassis_set_vtep_logical_switches(chassis_rec, ls_arr,
-                                                sset_count(&lswitches));
-        free(ls_arr);
-        sset_destroy(&lswitches);
-    }
-}
-
-
-void
-gateway_run(struct controller_vtep_ctx *ctx)
-{
-    if (!ctx->ovnsb_idl_txn) {
-        return;
-    }
-
-    revalidate_gateway(ctx);
-    update_vtep_logical_switches(ctx);
-}
-
-/* Destroys the chassis table entries for vtep physical switches.
- * Returns true when done (i.e. there is no change made to 'ctx->ovnsb_idl'),
- * otherwise returns false. */
-bool
-gateway_cleanup(struct controller_vtep_ctx *ctx)
-{
-    static bool simap_destroyed = false;
-    const struct vteprec_physical_switch *pswitch;
-
-    if (!ctx->ovnsb_idl_txn) {
-        return false;
-    }
-
-    bool all_done = true;
-    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: "
-                              "unregistering vtep chassis");
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
-        const struct sbrec_chassis *chassis_rec;
-
-        chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
-        if (!chassis_rec) {
-            continue;
-        }
-        all_done = false;
-        sbrec_chassis_delete(chassis_rec);
-    }
-    if (!simap_destroyed) {
-        simap_destroy(&gw_chassis_map);
-        simap_destroyed = true;
-    }
-
-    return all_done;
-}
diff --git a/ovn/controller-vtep/gateway.h b/ovn/controller-vtep/gateway.h
deleted file mode 100644
index 0086191d9..000000000
--- a/ovn/controller-vtep/gateway.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* Copyright (c) 2015 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 OVN_GATEWAY_H
-#define OVN_GATEWAY_H 1
-
-#include <stdbool.h>
-
-struct controller_vtep_ctx;
-
-void gateway_run(struct controller_vtep_ctx *);
-bool gateway_cleanup(struct controller_vtep_ctx *);
-
-#endif /* ovn/controller-gw/gateway.h */
diff --git a/ovn/controller-vtep/ovn-controller-vtep.8.xml b/ovn/controller-vtep/ovn-controller-vtep.8.xml
deleted file mode 100644
index 2c706e46e..000000000
--- a/ovn/controller-vtep/ovn-controller-vtep.8.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-controller-vtep" section="8" title="ovn-controller-vtep">
-    <h1>Name</h1>
-    <p>ovn-controller-vtep -- Open Virtual Network local controller for
-       vtep enabled physical switches.
-    </p>
-
-    <h1>Synopsis</h1>
-    <p><code>ovn-controller-vtep</code> [<var>options</var>]
-    [<var>--vtep-db=vtep-database</var>] [<var>--ovnsb-db=ovnsb-database</var>]
-    </p>
-
-    <h1>Description</h1>
-    <p>
-      <code>ovn-controller-vtep</code> is the local controller daemon in
-      OVN, the Open Virtual Network, for VTEP enabled physical switches.
-      It connects up to the OVN Southbound database (see
-      <code>ovn-sb</code>(5)) over the OVSDB protocol, and down to the VTEP
-      database (see <code>vtep</code>(5)) over the OVSDB protocol.
-    </p>
-
-    <h2>PKI Options</h2>
-    <p>
-      PKI configuration is required in order to use SSL for the connections to
-      the VTEP and Southbound databases.
-    </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"/>
-    <xi:include href="lib/ssl-peer-ca-cert.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h1>Configuration</h1>
-    <p>
-      <code>ovn-controller-vtep</code> retrieves its configuration
-      information from both the ovnsb and the vtep database.  If the
-      database locations are not given from command line, the default
-      is the <code>db.sock</code> in local OVSDB's 'run' directory.
-      The datapath location must take one of the following forms:
-    </p>
-    <ul>
-      <li>
-        <p>
-          <code>ssl:<var>host</var>:<var>port</var></code>
-        </p>
-        <p>
-          The specified SSL <var>port</var> on the give <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, then
-          wrap <var>host</var> with square brackets, e.g.: <code>ssl:[::1]:6640</code>.
-          The <code>--private-key</code>, <code>--certificate</code> and either
-          of <code>--ca-cert</code> or <code>--bootstrap-ca-cert</code> options
-          are mandatory when this form is used.
-        </p>
-      </li>
-      <li>
-        <p>
-          <code>tcp:<var>host</var>:<var>port</var></code>
-        </p>
-        <p>
-          Connect to the given TCP <var>port</var> on <var>host</var>, where
-          <var>host</var> can be a DNS name (if built with unbound library) or
-          IP address (IPv4 or IPv6). If <var>host</var> is an IPv6 address,
-          then wrap <var>host</var> with square brackets,
-          e.g.: <code>tcp:[::1]:6640</code>.
-        </p>
-      </li>
-      <li>
-        <p>
-          <code>unix:<var>file</var></code>
-        </p>
-        <p>
-          On POSIX, connect to the Unix domain server socket named
-          <var>file</var>.
-        </p>
-        <p>
-          On Windows, connect to a localhost TCP port whose value is written
-          in <var>file</var>.
-        </p>
-      </li>
-    </ul>
-</manpage>
diff --git a/ovn/controller-vtep/ovn-controller-vtep.c b/ovn/controller-vtep/ovn-controller-vtep.c
deleted file mode 100644
index 292a3f464..000000000
--- a/ovn/controller-vtep/ovn-controller-vtep.c
+++ /dev/null
@@ -1,272 +0,0 @@
-/* Copyright (c) 2015, 2016 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 <errno.h>
-#include <getopt.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "command-line.h"
-#include "compiler.h"
-#include "daemon.h"
-#include "dirs.h"
-#include "openvswitch/dynamic-string.h"
-#include "fatal-signal.h"
-#include "openvswitch/poll-loop.h"
-#include "stream.h"
-#include "stream-ssl.h"
-#include "unixctl.h"
-#include "util.h"
-#include "openvswitch/vconn.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "vtep/vtep-idl.h"
-
-#include "binding.h"
-#include "gateway.h"
-#include "vtep.h"
-#include "ovn-controller-vtep.h"
-
-static unixctl_cb_func ovn_controller_vtep_exit;
-
-static void parse_options(int argc, char *argv[]);
-OVS_NO_RETURN static void usage(void);
-
-static char *vtep_remote;
-static char *ovnsb_remote;
-static char *default_db_;
-
-int
-main(int argc, char *argv[])
-{
-    struct unixctl_server *unixctl;
-    bool exiting;
-    int retval;
-
-    ovs_cmdl_proctitle_init(argc, argv);
-    set_program_name(argv[0]);
-    service_start(&argc, &argv);
-    parse_options(argc, argv);
-    fatal_ignore_sigpipe();
-
-    daemonize_start(false);
-
-    retval = unixctl_server_create(NULL, &unixctl);
-    if (retval) {
-        exit(EXIT_FAILURE);
-    }
-    unixctl_command_register("exit", "", 0, 0, ovn_controller_vtep_exit,
-                             &exiting);
-
-    daemonize_complete();
-
-    /* Connect to VTEP database. */
-    struct ovsdb_idl_loop vtep_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create(vtep_remote, &vteprec_idl_class, true, true));
-    ovsdb_idl_get_initial_snapshot(vtep_idl_loop.idl);
-
-    /* Connect to OVN SB database. */
-    struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true));
-    ovsdb_idl_get_initial_snapshot(ovnsb_idl_loop.idl);
-
-    /* Main loop. */
-    exiting = false;
-    while (!exiting) {
-        struct controller_vtep_ctx ctx = {
-            .vtep_idl = vtep_idl_loop.idl,
-            .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop),
-            .ovnsb_idl = ovnsb_idl_loop.idl,
-            .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
-        };
-
-        gateway_run(&ctx);
-        binding_run(&ctx);
-        vtep_run(&ctx);
-        unixctl_server_run(unixctl);
-
-        unixctl_server_wait(unixctl);
-        if (exiting) {
-            poll_immediate_wake();
-        }
-        ovsdb_idl_loop_commit_and_wait(&vtep_idl_loop);
-        ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-        poll_block();
-        if (should_service_stop()) {
-            exiting = true;
-        }
-    }
-
-    /* It's time to exit.  Clean up the databases. */
-    bool done = false;
-    while (!done) {
-        struct controller_vtep_ctx ctx = {
-            .vtep_idl = vtep_idl_loop.idl,
-            .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop),
-            .ovnsb_idl = ovnsb_idl_loop.idl,
-            .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
-        };
-
-        /* Run all of the cleanup functions, even if one of them returns false.
-         * We're done if all of them return true. */
-        done = binding_cleanup(&ctx);
-        done = gateway_cleanup(&ctx) && done;
-        done = vtep_cleanup(&ctx) && done;
-        if (done) {
-            poll_immediate_wake();
-        }
-
-        ovsdb_idl_loop_commit_and_wait(&vtep_idl_loop);
-        ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-        poll_block();
-    }
-
-    unixctl_server_destroy(unixctl);
-
-    ovsdb_idl_loop_destroy(&vtep_idl_loop);
-    ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
-
-    free(ovnsb_remote);
-    free(vtep_remote);
-    free(default_db_);
-    service_stop();
-
-    exit(retval);
-}
-
-static const char *
-default_db(void)
-{
-    if (!default_db_) {
-        default_db_ = xasprintf("unix:%s/db.sock", ovs_rundir());
-    }
-    return default_db_;
-}
-
-static void
-parse_options(int argc, char *argv[])
-{
-    enum {
-        OPT_PEER_CA_CERT = UCHAR_MAX + 1,
-        OPT_BOOTSTRAP_CA_CERT,
-        VLOG_OPTION_ENUMS,
-        DAEMON_OPTION_ENUMS,
-        SSL_OPTION_ENUMS,
-    };
-
-    static struct option long_options[] = {
-        {"ovnsb-db", required_argument, NULL, 'd'},
-        {"vtep-db", required_argument, NULL, 'D'},
-        {"help", no_argument, NULL, 'h'},
-        {"version", no_argument, NULL, 'V'},
-        VLOG_LONG_OPTIONS,
-        DAEMON_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
-        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
-        {NULL, 0, NULL, 0}
-    };
-    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
-
-    for (;;) {
-        int c;
-
-        c = getopt_long(argc, argv, short_options, long_options, NULL);
-        if (c == -1) {
-            break;
-        }
-
-        switch (c) {
-        case 'd':
-            ovnsb_remote = xstrdup(optarg);
-            break;
-
-        case 'D':
-            vtep_remote = xstrdup(optarg);
-            break;
-
-        case 'h':
-            usage();
-
-        case 'V':
-            ovs_print_version(OFP13_VERSION, OFP13_VERSION);
-            exit(EXIT_SUCCESS);
-
-        VLOG_OPTION_HANDLERS
-        DAEMON_OPTION_HANDLERS
-        STREAM_SSL_OPTION_HANDLERS
-
-        case OPT_PEER_CA_CERT:
-            stream_ssl_set_peer_ca_cert_file(optarg);
-            break;
-
-        case OPT_BOOTSTRAP_CA_CERT:
-            stream_ssl_set_ca_cert_file(optarg, true);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            abort();
-        }
-    }
-    free(short_options);
-
-    if (!ovnsb_remote) {
-        ovnsb_remote = xstrdup(default_sb_db());
-    }
-
-    if (!vtep_remote) {
-        vtep_remote = xstrdup(default_db());
-    }
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN controller VTEP\n\
-usage %s [OPTIONS]\n\
-\n\
-Options:\n\
-  --vtep-db=DATABASE        connect to vtep database at DATABASE\n\
-                            (default: %s)\n\
-  --ovnsb-db=DATABASE       connect to ovn-sb database at DATABASE\n\
-                            (default: %s)\n\
-  -h, --help                display this help message\n\
-  -o, --options             list available options\n\
-  -V, --version             display version information\n\
-", program_name, program_name, default_db(), default_sb_db());
-    stream_usage("database", true, false, true);
-    daemon_usage();
-    vlog_usage();
-    exit(EXIT_SUCCESS);
-}
-
-
-static void
-ovn_controller_vtep_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);
-}
diff --git a/ovn/controller-vtep/ovn-controller-vtep.h b/ovn/controller-vtep/ovn-controller-vtep.h
deleted file mode 100644
index 435a730d9..000000000
--- a/ovn/controller-vtep/ovn-controller-vtep.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* Copyright (c) 2015 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 OVN_CONTROLLER_VTEP_H
-#define OVN_CONTROLLER_VTEP_H 1
-
-#include "ovn/lib/ovn-sb-idl.h"
-
-struct ovsdb_idl;
-struct ovsdb_idl_txn;
-
-struct controller_vtep_ctx {
-    struct ovsdb_idl *ovnsb_idl;
-    struct ovsdb_idl_txn *ovnsb_idl_txn;
-
-    struct ovsdb_idl *vtep_idl;
-    struct ovsdb_idl_txn *vtep_idl_txn;
-};
-
-/* VTEP needs what VTEP needs. */
-#define OVN_SB_ENCAP_TYPE "vxlan"
-#define VTEP_ENCAP_TYPE "vxlan_over_ipv4"
-
-static inline const struct sbrec_chassis *
-get_chassis_by_name(struct ovsdb_idl *ovnsb_idl, const char *chassis_id)
-{
-    const struct sbrec_chassis *chassis_rec;
-
-    SBREC_CHASSIS_FOR_EACH(chassis_rec, ovnsb_idl) {
-        if (!strcmp(chassis_rec->name, chassis_id)) {
-            break;
-        }
-    }
-
-    return chassis_rec;
-}
-
-#endif /* ovn/ovn-controller-vtep.h */
diff --git a/ovn/controller-vtep/vtep.c b/ovn/controller-vtep/vtep.c
deleted file mode 100644
index a72b149eb..000000000
--- a/ovn/controller-vtep/vtep.c
+++ /dev/null
@@ -1,600 +0,0 @@
-/* Copyright (c) 2015 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 "vtep.h"
-
-#include "lib/hash.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/shash.h"
-#include "lib/smap.h"
-#include "lib/sset.h"
-#include "lib/util.h"
-#include "ovn-controller-vtep.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "vtep/vtep-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(vtep);
-
-struct vtep_rec_physical_locator_list_entry {
-    struct ovs_list locators_node;
-    const struct vteprec_physical_locator *vteprec_ploc;
-};
-
-struct mmr_hash_node_data {
-    const struct vteprec_mcast_macs_remote *mmr;
-    struct shash physical_locators;
-};
-
-/*
- * Scans through the Binding table in ovnsb, and updates the vtep logical
- * switch tunnel keys and the 'Ucast_Macs_Remote' table in the VTEP
- * database.
- *
- */
-
-/* Searches the 'chassis_rec->encaps' for the first vtep tunnel
- * configuration, returns the 'ip'.  Unless duplicated, the returned
- * pointer cannot live past current vtep_run() execution. */
-static const char *
-get_chassis_vtep_ip(const struct sbrec_chassis *chassis_rec)
-{
-    if (chassis_rec) {
-        size_t i;
-
-        for (i = 0; i < chassis_rec->n_encaps; i++) {
-            if (!strcmp(chassis_rec->encaps[i]->type, "vxlan")) {
-                return chassis_rec->encaps[i]->ip;
-            }
-        }
-    }
-
-    return NULL;
-}
-
-/* Creates a new 'Ucast_Macs_Remote'. */
-static struct vteprec_ucast_macs_remote *
-create_umr(struct ovsdb_idl_txn *vtep_idl_txn, const char *mac,
-           const struct vteprec_logical_switch *vtep_ls)
-{
-    struct vteprec_ucast_macs_remote *new_umr =
-        vteprec_ucast_macs_remote_insert(vtep_idl_txn);
-
-    vteprec_ucast_macs_remote_set_MAC(new_umr, mac);
-    vteprec_ucast_macs_remote_set_logical_switch(new_umr, vtep_ls);
-
-    return new_umr;
-}
-
-/* Creates a new 'Physical_Locator'. */
-static struct vteprec_physical_locator *
-create_pl(struct ovsdb_idl_txn *vtep_idl_txn, const char *chassis_ip)
-{
-    struct vteprec_physical_locator *new_pl =
-        vteprec_physical_locator_insert(vtep_idl_txn);
-
-    vteprec_physical_locator_set_dst_ip(new_pl, chassis_ip);
-    vteprec_physical_locator_set_encapsulation_type(new_pl, VTEP_ENCAP_TYPE);
-
-    return new_pl;
-}
-
-/* Creates a new 'Mcast_Macs_Remote'. */
-static void
-vtep_create_mmr(struct ovsdb_idl_txn *vtep_idl_txn, const char *mac,
-                const struct vteprec_logical_switch *vtep_ls,
-                const struct vteprec_physical_locator_set *ploc_set)
-{
-    struct vteprec_mcast_macs_remote *new_mmr =
-       vteprec_mcast_macs_remote_insert(vtep_idl_txn);
-
-    vteprec_mcast_macs_remote_set_MAC(new_mmr, mac);
-    vteprec_mcast_macs_remote_set_logical_switch(new_mmr, vtep_ls);
-    vteprec_mcast_macs_remote_set_locator_set(new_mmr, ploc_set);
-}
-
-/* Compares previous and new mmr locator sets and returns true if they
- * differ and false otherwise. This function also preps a new locator
- * set for database write.
- *
- * 'locators_list' is the new set of locators for the associated
- * 'Mcast_Macs_Remote' entry passed in and is queried to generate the
- * new set of locators in vtep database format. */
-static bool
-vtep_process_pls(const struct ovs_list *locators_list,
-                 const struct mmr_hash_node_data *mmr_ext,
-                 struct vteprec_physical_locator **locators)
-{
-    size_t n_locators_prev = 0;
-    size_t n_locators_new = ovs_list_size(locators_list);
-    bool locator_lists_differ = false;
-
-    if (mmr_ext) {
-        n_locators_prev = mmr_ext->mmr->locator_set->n_locators;
-    }
-    if (n_locators_prev != n_locators_new) {
-        locator_lists_differ = true;
-    }
-
-    if (n_locators_new) {
-        int i = 0;
-        struct vtep_rec_physical_locator_list_entry *ploc_entry;
-        LIST_FOR_EACH (ploc_entry, locators_node, locators_list) {
-            locators[i] = (struct vteprec_physical_locator *)
-                           ploc_entry->vteprec_ploc;
-            if (mmr_ext && !shash_find_data(&mmr_ext->physical_locators,
-                                            locators[i]->dst_ip)) {
-                    locator_lists_differ = true;
-            }
-            i++;
-        }
-    }
-
-    return locator_lists_differ;
-}
-
-/* Creates a new 'Mcast_Macs_Remote' entry if needed and also cleans up
- * out-dated remote mcast mac entries as needed. */
-static void
-vtep_update_mmr(struct ovsdb_idl_txn *vtep_idl_txn,
-                struct ovs_list *locators_list,
-                const struct vteprec_logical_switch *vtep_ls,
-                const struct mmr_hash_node_data *mmr_ext)
-{
-    struct vteprec_physical_locator **locators = NULL;
-    size_t n_locators_new = ovs_list_size(locators_list);
-    bool mmr_changed;
-
-    locators = xmalloc(n_locators_new * sizeof *locators);
-
-    mmr_changed = vtep_process_pls(locators_list, mmr_ext, locators);
-
-    if (mmr_ext && !n_locators_new) {
-        vteprec_mcast_macs_remote_delete(mmr_ext->mmr);
-    } else if ((mmr_ext && mmr_changed) ||
-               (!mmr_ext && n_locators_new)) {
-
-        const struct vteprec_physical_locator_set *ploc_set =
-            vteprec_physical_locator_set_insert(vtep_idl_txn);
-
-        vtep_create_mmr(vtep_idl_txn, "unknown-dst", vtep_ls, ploc_set);
-
-        vteprec_physical_locator_set_set_locators(ploc_set, locators,
-                                                  n_locators_new);
-    }
-    free(locators);
-}
-
-/* Updates the vtep Logical_Switch table entries' tunnel keys based
- * on the port bindings. */
-static void
-vtep_lswitch_run(struct shash *vtep_pbs, struct sset *vtep_pswitches,
-                 struct shash *vtep_lswitches)
-{
-    struct sset used_ls = SSET_INITIALIZER(&used_ls);
-    struct shash_node *node;
-
-    /* Collects the logical switch bindings from port binding entries.
-     * Since the binding module has already guaranteed that each vtep
-     * logical switch is bound only to one ovn-sb logical datapath,
-     * we can just iterate and assign tunnel key to vtep logical switch. */
-    SHASH_FOR_EACH (node, vtep_pbs) {
-        const struct sbrec_port_binding *port_binding_rec = node->data;
-        const char *pswitch_name = smap_get(&port_binding_rec->options,
-                                            "vtep-physical-switch");
-        const char *lswitch_name = smap_get(&port_binding_rec->options,
-                                            "vtep-logical-switch");
-        const struct vteprec_logical_switch *vtep_ls;
-
-        /* If 'port_binding_rec->chassis' exists then 'pswitch_name'
-         * and 'lswitch_name' must also exist. */
-        if (!pswitch_name || !lswitch_name) {
-            /* This could only happen when someone directly modifies the
-             * database,  (e.g. using ovn-sbctl). */
-            VLOG_ERR("logical port (%s) with no 'options:vtep-physical-"
-                     "switch' or 'options:vtep-logical-switch' specified "
-                     "is bound to chassis (%s).",
-                     port_binding_rec->logical_port,
-                     port_binding_rec->chassis->name);
-            continue;
-        }
-        vtep_ls = shash_find_data(vtep_lswitches, lswitch_name);
-        /* Also checks 'pswitch_name' since the same 'lswitch_name' could
-         * exist in multiple vtep database instances and be bound to different
-         * ovn logical networks. */
-        if (vtep_ls && sset_find(vtep_pswitches, pswitch_name)) {
-            int64_t tnl_key;
-
-            if (sset_find(&used_ls, lswitch_name)) {
-                continue;
-            }
-
-            tnl_key = port_binding_rec->datapath->tunnel_key;
-            if (vtep_ls->n_tunnel_key
-                && vtep_ls->tunnel_key[0] != tnl_key) {
-                VLOG_DBG("set vtep logical switch (%s) tunnel key from "
-                         "(%"PRId64") to (%"PRId64")", vtep_ls->name,
-                         vtep_ls->tunnel_key[0], tnl_key);
-            }
-            vteprec_logical_switch_set_tunnel_key(vtep_ls, &tnl_key, 1);
-
-            /* OVN is expected to always use source node replication mode,
-             * hence the replication mode is hard-coded for each logical
-             * switch in the context of ovn-controller-vtep. */
-            vteprec_logical_switch_set_replication_mode(vtep_ls, "source_node");
-            sset_add(&used_ls, lswitch_name);
-        }
-    }
-    /* Resets the tunnel keys for unused vtep logical switches. */
-    SHASH_FOR_EACH (node, vtep_lswitches) {
-        if (!sset_find(&used_ls, node->name)) {
-            int64_t tnl_key = 0;
-            vteprec_logical_switch_set_tunnel_key(node->data, &tnl_key, 1);
-        }
-    }
-    sset_destroy(&used_ls);
-}
-
-/* Updates the vtep 'Ucast_Macs_Remote' and 'Mcast_Macs_Remote' tables based
- * on non-vtep port bindings. */
-static void
-vtep_macs_run(struct ovsdb_idl_txn *vtep_idl_txn, struct shash *ucast_macs_rmts,
-              struct shash *mcast_macs_rmts, struct shash *physical_locators,
-              struct shash *vtep_lswitches, struct shash *non_vtep_pbs)
-{
-    struct shash_node *node;
-    struct hmap ls_map;
-
-    /* Maps from ovn logical datapath tunnel key (which is also the vtep
-     * logical switch tunnel key) to the corresponding vtep logical switch
-     * instance.  Also, the shash map 'added_macs' is used for checking
-     * duplicated MAC addresses in the same ovn logical datapath. 'mmr_ext'
-     * is used to track mmr info per LS that needs creation/update and
-     * 'locators_list' collects the new physical locators to be bound for
-     * an mmr_ext; 'physical_locators' is used to track existing locators and
-     * filter duplicates per logical switch. */
-    struct ls_hash_node {
-        struct hmap_node hmap_node;
-
-        const struct vteprec_logical_switch *vtep_ls;
-        struct shash added_macs;
-
-        struct ovs_list locators_list;
-        struct shash physical_locators;
-        struct mmr_hash_node_data *mmr_ext;
-    };
-
-    hmap_init(&ls_map);
-    SHASH_FOR_EACH (node, vtep_lswitches) {
-        const struct vteprec_logical_switch *vtep_ls = node->data;
-        struct ls_hash_node *ls_node;
-
-        if (!vtep_ls->n_tunnel_key) {
-            continue;
-        }
-        ls_node = xmalloc(sizeof *ls_node);
-        ls_node->vtep_ls = vtep_ls;
-        shash_init(&ls_node->added_macs);
-        shash_init(&ls_node->physical_locators);
-        ovs_list_init(&ls_node->locators_list);
-        ls_node->mmr_ext = NULL;
-        hmap_insert(&ls_map, &ls_node->hmap_node,
-                    hash_uint64((uint64_t) vtep_ls->tunnel_key[0]));
-    }
-
-    SHASH_FOR_EACH (node, non_vtep_pbs) {
-        const struct sbrec_port_binding *port_binding_rec = node->data;
-        const struct sbrec_chassis *chassis_rec;
-        struct ls_hash_node *ls_node;
-        const char *chassis_ip;
-        int64_t tnl_key;
-        size_t i;
-
-        chassis_rec = port_binding_rec->chassis;
-        if (!chassis_rec) {
-            continue;
-        }
-
-        tnl_key = port_binding_rec->datapath->tunnel_key;
-        HMAP_FOR_EACH_WITH_HASH (ls_node, hmap_node,
-                                 hash_uint64((uint64_t) tnl_key),
-                                 &ls_map) {
-            if (ls_node->vtep_ls->tunnel_key[0] == tnl_key) {
-                break;
-            }
-        }
-        /* If 'ls_node' is NULL, that means no vtep logical switch is
-         * attached to the corresponding ovn logical datapath, so pass.
-         */
-        if (!ls_node) {
-            continue;
-        }
-
-        chassis_ip = get_chassis_vtep_ip(chassis_rec);
-        /* Unreachable chassis, continue. */
-        if (!chassis_ip) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_INFO_RL(&rl, "VTEP tunnel encap on chassis (%s) not found",
-                         chassis_rec->name);
-            continue;
-        }
-
-        const struct vteprec_physical_locator *pl =
-            shash_find_data(physical_locators, chassis_ip);
-        if (!pl) {
-            pl = create_pl(vtep_idl_txn, chassis_ip);
-            shash_add(physical_locators, chassis_ip, pl);
-        }
-
-        const struct vteprec_physical_locator *ls_pl =
-            shash_find_data(&ls_node->physical_locators, chassis_ip);
-        if (!ls_pl) {
-            struct vtep_rec_physical_locator_list_entry *ploc_entry =
-                xmalloc(sizeof *ploc_entry);
-            ploc_entry->vteprec_ploc = pl;
-            ovs_list_push_back(&ls_node->locators_list,
-                               &ploc_entry->locators_node);
-            shash_add(&ls_node->physical_locators, chassis_ip, pl);
-        }
-
-        char *mac_tnlkey = xasprintf("%s_%"PRId64, "unknown-dst", tnl_key);
-        ls_node->mmr_ext = shash_find_data(mcast_macs_rmts, mac_tnlkey);
-
-        if (ls_node->mmr_ext &&
-            ls_node->mmr_ext->mmr->logical_switch == ls_node->vtep_ls) {
-
-            /* Delete the entry from the hash table so the mmr does not get
-             * removed from the DB later on during stale checking. */
-            shash_find_and_delete(mcast_macs_rmts, mac_tnlkey);
-        }
-        free(mac_tnlkey);
-
-        for (i = 0; i < port_binding_rec->n_mac; i++) {
-            const struct vteprec_ucast_macs_remote *umr;
-            const struct sbrec_port_binding *conflict;
-            char *mac = port_binding_rec->mac[i];
-
-            /* Checks for duplicate MAC in the same vtep logical switch. */
-            conflict = shash_find_data(&ls_node->added_macs, mac);
-            if (conflict) {
-                VLOG_WARN("MAC address (%s) has already been known to be "
-                          "on logical port (%s) in the same logical "
-                          "datapath, so just ignore this logical port (%s)",
-                          mac, conflict->logical_port,
-                          port_binding_rec->logical_port);
-                continue;
-            }
-            shash_add(&ls_node->added_macs, mac, port_binding_rec);
-
-            char *mac_ip_tnlkey = xasprintf("%s_%s_%"PRId64, mac, chassis_ip,
-                                            tnl_key);
-            umr = shash_find_data(ucast_macs_rmts, mac_ip_tnlkey);
-            /* If finds the 'umr' entry for the mac, ip, and tnl_key, deletes
-             * the entry from shash so that it is not gargage collected.
-             *
-             * If not found, creates a new 'umr' entry. */
-            if (umr && umr->logical_switch == ls_node->vtep_ls) {
-                shash_find_and_delete(ucast_macs_rmts, mac_ip_tnlkey);
-            } else {
-                const struct vteprec_ucast_macs_remote *new_umr;
-                new_umr = create_umr(vtep_idl_txn, mac, ls_node->vtep_ls);
-                vteprec_ucast_macs_remote_set_locator(new_umr, pl);
-            }
-            free(mac_ip_tnlkey);
-        }
-    }
-
-    /* Removes all remaining 'umr's, since they do not exist anymore. */
-    SHASH_FOR_EACH (node, ucast_macs_rmts) {
-        vteprec_ucast_macs_remote_delete(node->data);
-    }
-    struct ls_hash_node *iter, *next;
-    HMAP_FOR_EACH_SAFE (iter, next, hmap_node, &ls_map) {
-        struct vtep_rec_physical_locator_list_entry *ploc_entry;
-        vtep_update_mmr(vtep_idl_txn, &iter->locators_list,
-                        iter->vtep_ls, iter->mmr_ext);
-        LIST_FOR_EACH_POP(ploc_entry, locators_node,
-                          &iter->locators_list) {
-            free(ploc_entry);
-        }
-        hmap_remove(&ls_map, &iter->hmap_node);
-        shash_destroy(&iter->added_macs);
-        shash_destroy(&iter->physical_locators);
-        free(iter);
-    }
-    hmap_destroy(&ls_map);
-
-    /* Clean stale 'Mcast_Macs_Remote' */
-    struct mmr_hash_node_data *mmr_ext;
-    SHASH_FOR_EACH (node, mcast_macs_rmts) {
-        mmr_ext = node->data;
-        vteprec_mcast_macs_remote_delete(mmr_ext->mmr);
-    }
-}
-
-/* Resets all logical switches' 'tunnel_key' to NULL */
-static bool
-vtep_lswitch_cleanup(struct ovsdb_idl *vtep_idl)
-{
-   const struct vteprec_logical_switch *vtep_ls;
-    bool done = true;
-
-    VTEPREC_LOGICAL_SWITCH_FOR_EACH (vtep_ls, vtep_idl) {
-        if (vtep_ls->n_tunnel_key) {
-            vteprec_logical_switch_set_tunnel_key(vtep_ls, NULL, 0);
-            done = false;
-        }
-    }
-
-    return done;
-}
-
-/* Removes all entries in the 'Ucast_Macs_Remote' table in the vtep database.
- * Returns true when all done (i.e. no entry to remove). */
-static bool
-vtep_ucast_macs_cleanup(struct ovsdb_idl *vtep_idl)
-{
-    const struct vteprec_ucast_macs_remote *umr;
-
-    VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (umr, vtep_idl) {
-        vteprec_ucast_macs_remote_delete(umr);
-        return false;
-    }
-
-    return true;
-}
-
-/* Removes all entries in the 'Mcast_Macs_Remote' table in vtep database.
- * Returns true when all done (i.e. no entry to remove). */
-static bool
-vtep_mcast_macs_cleanup(struct ovsdb_idl *vtep_idl)
-{
-    const struct vteprec_mcast_macs_remote *mmr;
-
-    VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mmr, vtep_idl) {
-        vteprec_mcast_macs_remote_delete(mmr);
-        return false;
-    }
-
-    return true;
-}
-
-/* Updates vtep logical switch tunnel keys. */
-void
-vtep_run(struct controller_vtep_ctx *ctx)
-{
-    if (!ctx->vtep_idl_txn) {
-        return;
-    }
-
-    struct sset vtep_pswitches = SSET_INITIALIZER(&vtep_pswitches);
-    struct shash vtep_lswitches = SHASH_INITIALIZER(&vtep_lswitches);
-    struct shash ucast_macs_rmts = SHASH_INITIALIZER(&ucast_macs_rmts);
-    struct shash mcast_macs_rmts = SHASH_INITIALIZER(&mcast_macs_rmts);
-    struct shash physical_locators = SHASH_INITIALIZER(&physical_locators);
-    struct shash vtep_pbs = SHASH_INITIALIZER(&vtep_pbs);
-    struct shash non_vtep_pbs = SHASH_INITIALIZER(&non_vtep_pbs);
-    const struct vteprec_physical_switch *vtep_ps;
-    const struct vteprec_logical_switch *vtep_ls;
-    const struct vteprec_ucast_macs_remote *umr;
-    const struct sbrec_port_binding *port_binding_rec;
-    const struct vteprec_mcast_macs_remote *mmr;
-    struct shash_node *node;
-
-    /* Collects 'Physical_Switch's. */
-    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (vtep_ps, ctx->vtep_idl) {
-        sset_add(&vtep_pswitches, vtep_ps->name);
-    }
-
-    /* Collects 'Logical_Switch's. */
-    VTEPREC_LOGICAL_SWITCH_FOR_EACH (vtep_ls, ctx->vtep_idl) {
-        shash_add(&vtep_lswitches, vtep_ls->name, vtep_ls);
-    }
-
-    /* Collects 'Ucast_Macs_Remote's. */
-    VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (umr, ctx->vtep_idl) {
-        char *mac_ip_tnlkey =
-            xasprintf("%s_%s_%"PRId64, umr->MAC,
-                      umr->locator ? umr->locator->dst_ip : "",
-                      umr->logical_switch && umr->logical_switch->n_tunnel_key
-                          ? umr->logical_switch->tunnel_key[0] : INT64_MAX);
-
-        shash_add(&ucast_macs_rmts, mac_ip_tnlkey, umr);
-        free(mac_ip_tnlkey);
-    }
-
-    /* Collects 'Mcast_Macs_Remote's. */
-    VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mmr, ctx->vtep_idl) {
-        struct mmr_hash_node_data *mmr_ext = xmalloc(sizeof *mmr_ext);;
-        char *mac_tnlkey =
-            xasprintf("%s_%"PRId64, mmr->MAC,
-                      mmr->logical_switch && mmr->logical_switch->n_tunnel_key
-                          ? mmr->logical_switch->tunnel_key[0] : INT64_MAX);
-
-        shash_add(&mcast_macs_rmts, mac_tnlkey, mmr_ext);
-        mmr_ext->mmr = mmr;
-
-        shash_init(&mmr_ext->physical_locators);
-        for (size_t i = 0; i < mmr->locator_set->n_locators; i++) {
-            shash_add(&mmr_ext->physical_locators,
-                      mmr->locator_set->locators[i]->dst_ip,
-                      mmr->locator_set->locators[i]);
-        }
-
-        free(mac_tnlkey);
-    }
-
-    /* Collects 'Physical_Locator's. */
-    const struct vteprec_physical_locator *pl;
-    VTEPREC_PHYSICAL_LOCATOR_FOR_EACH (pl, ctx->vtep_idl) {
-        shash_add(&physical_locators, pl->dst_ip, pl);
-    }
-
-    /* Collects and classifies 'Port_Binding's. */
-    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) {
-        struct shash *target =
-            !strcmp(port_binding_rec->type, "vtep") ? &vtep_pbs : &non_vtep_pbs;
-
-        if (!port_binding_rec->chassis) {
-            continue;
-        }
-        shash_add(target, port_binding_rec->logical_port, port_binding_rec);
-    }
-
-    ovsdb_idl_txn_add_comment(ctx->vtep_idl_txn,
-                              "ovn-controller-vtep: update logical switch "
-                              "tunnel keys and 'ucast_macs_remote's");
-
-    vtep_lswitch_run(&vtep_pbs, &vtep_pswitches, &vtep_lswitches);
-    vtep_macs_run(ctx->vtep_idl_txn, &ucast_macs_rmts,
-                  &mcast_macs_rmts, &physical_locators,
-                  &vtep_lswitches, &non_vtep_pbs);
-
-    sset_destroy(&vtep_pswitches);
-    shash_destroy(&vtep_lswitches);
-    shash_destroy(&ucast_macs_rmts);
-    SHASH_FOR_EACH (node, &mcast_macs_rmts) {
-        struct mmr_hash_node_data *mmr_ext = node->data;
-        shash_destroy(&mmr_ext->physical_locators);
-        free(mmr_ext);
-    }
-    shash_destroy(&mcast_macs_rmts);
-    shash_destroy(&physical_locators);
-    shash_destroy(&vtep_pbs);
-    shash_destroy(&non_vtep_pbs);
-}
-
-/* Cleans up all related entries in vtep.  Returns true when done (i.e. there
- * is no change made to 'ctx->vtep_idl'), otherwise returns false. */
-bool
-vtep_cleanup(struct controller_vtep_ctx *ctx)
-{
-    if (!ctx->vtep_idl_txn) {
-        return false;
-    }
-
-    bool all_done;
-
-    ovsdb_idl_txn_add_comment(ctx->vtep_idl_txn,
-                              "ovn-controller-vtep: cleaning up vtep "
-                              "configuration");
-    all_done = vtep_lswitch_cleanup(ctx->vtep_idl);
-    all_done = vtep_ucast_macs_cleanup(ctx->vtep_idl) && all_done;
-    all_done = vtep_mcast_macs_cleanup(ctx->vtep_idl) && all_done;
-
-    return all_done;
-}
diff --git a/ovn/controller-vtep/vtep.h b/ovn/controller-vtep/vtep.h
deleted file mode 100644
index 97c87b7a7..000000000
--- a/ovn/controller-vtep/vtep.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Copyright (c) 2015 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 OVN_VTEP_H
-#define OVN_VTEP_H 1
-
-#include <stdbool.h>
-
-struct controller_vtep_ctx;
-
-void vtep_run(struct controller_vtep_ctx *);
-bool vtep_cleanup(struct controller_vtep_ctx *);
-
-#endif /* ovn/controller-vtep/vtep.h */
diff --git a/ovn/controller/.gitignore b/ovn/controller/.gitignore
deleted file mode 100644
index 4199a3741..000000000
--- a/ovn/controller/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/ovn-controller
-/ovn-controller.8
diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk
deleted file mode 100644
index 193ea690b..000000000
--- a/ovn/controller/automake.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-bin_PROGRAMS += ovn/controller/ovn-controller
-ovn_controller_ovn_controller_SOURCES = \
-	ovn/controller/bfd.c \
-	ovn/controller/bfd.h \
-	ovn/controller/binding.c \
-	ovn/controller/binding.h \
-	ovn/controller/chassis.c \
-	ovn/controller/chassis.h \
-	ovn/controller/encaps.c \
-	ovn/controller/encaps.h \
-	ovn/controller/ha-chassis.c \
-	ovn/controller/ha-chassis.h \
-	ovn/controller/ip-mcast.c \
-	ovn/controller/ip-mcast.h \
-	ovn/controller/lflow.c \
-	ovn/controller/lflow.h \
-	ovn/controller/lport.c \
-	ovn/controller/lport.h \
-	ovn/controller/ofctrl.c \
-	ovn/controller/ofctrl.h \
-	ovn/controller/pinctrl.c \
-	ovn/controller/pinctrl.h \
-	ovn/controller/patch.c \
-	ovn/controller/patch.h \
-	ovn/controller/ovn-controller.c \
-	ovn/controller/ovn-controller.h \
-	ovn/controller/physical.c \
-	ovn/controller/physical.h
-ovn_controller_ovn_controller_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la
-man_MANS += ovn/controller/ovn-controller.8
-EXTRA_DIST += ovn/controller/ovn-controller.8.xml
-CLEANFILES += ovn/controller/ovn-controller.8
diff --git a/ovn/controller/bfd.c b/ovn/controller/bfd.c
deleted file mode 100644
index 22db00af7..000000000
--- a/ovn/controller/bfd.c
+++ /dev/null
@@ -1,268 +0,0 @@
-/* Copyright (c) 2017 Red Hat, 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 "bfd.h"
-#include "encaps.h"
-#include "lport.h"
-#include "ovn-controller.h"
-
-#include "lib/hash.h"
-#include "lib/sset.h"
-#include "lib/util.h"
-#include "lib/vswitch-idl.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn-controller.h"
-
-VLOG_DEFINE_THIS_MODULE(ovn_bfd);
-
-void
-bfd_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    /* NOTE: this assumes that binding.c has added the
-     * ovsrec_interface table */
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
-}
-
-void
-bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int,
-                             struct sset *active_tunnels)
-{
-    int i;
-
-    if (!br_int) {
-        /* Nothing to do if integration bridge doesn't exist. */
-        return;
-    }
-
-    for (i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-
-        if (!strcmp(port_rec->name, br_int->name)) {
-            continue;
-        }
-
-        int j;
-        for (j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec;
-            iface_rec = port_rec->interfaces[j];
-
-            /* Check if this is a tunnel interface. */
-            if (smap_get(&iface_rec->options, "remote_ip")) {
-                /* Add ovn-chassis-id if the bfd_status of the tunnel
-                 * is active */
-                const char *bfd = smap_get(&iface_rec->bfd, "enable");
-                if (bfd && !strcmp(bfd, "true")) {
-                    const char *status = smap_get(&iface_rec->bfd_status,
-                                                  "state");
-                    if (status && !strcmp(status, "up")) {
-                        const char *id = smap_get(&port_rec->external_ids,
-                                                  "ovn-chassis-id");
-                        if (id) {
-                            char *chassis_name = NULL;
-
-                            if (encaps_tunnel_id_parse(id, &chassis_name,
-                                                       NULL)) {
-                                if (!sset_contains(active_tunnels,
-                                                   chassis_name)) {
-                                    sset_add(active_tunnels, chassis_name);
-                                }
-                                free(chassis_name);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-/* Loops through the HA chassis groups in the SB DB and returns
- * the set of chassis which the call can establish the BFD sessions
- * with.
- * Eg.
- * If there are 2 HA chassis groups.
- * Group name - hapgrp1
- *   - HA chassis - (HA1, HA2, HA3)
- *   - ref chassis - (C1, C2)
- *
- * Group name - hapgrp2
- *   - HA chassis - (HA1, HA4, HA5)
- *   - ref chassis - (C1, C3, C4)
- *
- * If 'our_chassis' is HA1 then this function returns
- *  bfd chassis set - (HA2, HA3, HA4 HA5, C1, C2, C3, C4)
- *
- * If 'our_chassis' is C1 then this function returns
- *  bfd chassis set - (HA1, HA2, HA3, HA4, HA5)
- *
- * If 'our_chassis' is HA5 then this function returns
- *  bfd chassis set - (HA1, HA4, C1, C3, C4)
- *
- * If 'our_chassis' is C2 then this function returns
- *  bfd chassis set - (HA1, HA2, HA3)
- *
- * If 'our_chassis' is C5 then this function returns empty bfd set.
- */
-static void
-bfd_calculate_chassis(
-    const struct sbrec_chassis *our_chassis,
-    const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table,
-    struct sset *bfd_chassis)
-{
-    const struct sbrec_ha_chassis_group *ha_chassis_grp;
-    SBREC_HA_CHASSIS_GROUP_TABLE_FOR_EACH (ha_chassis_grp,
-                                           ha_chassis_grp_table) {
-        bool is_ha_chassis = false;
-        struct sset grp_chassis = SSET_INITIALIZER(&grp_chassis);
-        const struct sbrec_ha_chassis *ha_ch;
-        bool bfd_setup_required = false;
-        if (ha_chassis_grp->n_ha_chassis < 2) {
-            /* No need to consider the chassis group for BFD if
-             * there is  1 or no chassis in it. */
-            continue;
-        }
-        for (size_t i = 0; i < ha_chassis_grp->n_ha_chassis; i++) {
-            ha_ch = ha_chassis_grp->ha_chassis[i];
-            if (!ha_ch->chassis) {
-                continue;
-            }
-            sset_add(&grp_chassis, ha_ch->chassis->name);
-            if (our_chassis == ha_ch->chassis) {
-                is_ha_chassis = true;
-                bfd_setup_required = true;
-            }
-        }
-
-        if (is_ha_chassis) {
-            /* It's an HA chassis. So add the ref_chassis to the bfd set. */
-            for (size_t i = 0; i < ha_chassis_grp->n_ref_chassis; i++) {
-                sset_add(&grp_chassis, ha_chassis_grp->ref_chassis[i]->name);
-            }
-        } else {
-            /* This is not an HA chassis. Check if this chassis is present
-             * in the ref_chassis list. If so add the ha_chassis to the
-             * sset .*/
-            for (size_t i = 0; i < ha_chassis_grp->n_ref_chassis; i++) {
-                if (our_chassis == ha_chassis_grp->ref_chassis[i]) {
-                    bfd_setup_required = true;
-                    break;
-                }
-            }
-        }
-
-        if (bfd_setup_required) {
-            const char *name;
-            SSET_FOR_EACH (name, &grp_chassis) {
-                sset_add(bfd_chassis, name);
-            }
-        }
-        sset_destroy(&grp_chassis);
-    }
-}
-
-void
-bfd_run(const struct ovsrec_interface_table *interface_table,
-        const struct ovsrec_bridge *br_int,
-        const struct sbrec_chassis *chassis_rec,
-        const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table,
-        const struct sbrec_sb_global_table *sb_global_table)
-{
-    if (!chassis_rec) {
-        return;
-    }
-    struct sset bfd_chassis = SSET_INITIALIZER(&bfd_chassis);
-    bfd_calculate_chassis(chassis_rec, ha_chassis_grp_table,
-                          &bfd_chassis);
-
-    /* Identify tunnels ports(connected to remote chassis id) to enable bfd */
-    struct sset tunnels = SSET_INITIALIZER(&tunnels);
-    struct sset bfd_ifaces = SSET_INITIALIZER(&bfd_ifaces);
-    for (size_t k = 0; k < br_int->n_ports; k++) {
-        const char *tunnel_id = smap_get(&br_int->ports[k]->external_ids,
-                                          "ovn-chassis-id");
-        if (tunnel_id) {
-            char *chassis_name = NULL;
-            char *port_name = br_int->ports[k]->name;
-
-            sset_add(&tunnels, port_name);
-
-            if (encaps_tunnel_id_parse(tunnel_id, &chassis_name, NULL)) {
-                if (sset_contains(&bfd_chassis, chassis_name)) {
-                    sset_add(&bfd_ifaces, port_name);
-                }
-                free(chassis_name);
-            }
-        }
-    }
-
-    const struct sbrec_sb_global *sb
-        = sbrec_sb_global_table_first(sb_global_table);
-    struct smap bfd = SMAP_INITIALIZER(&bfd);
-    smap_add(&bfd, "enable", "true");
-
-    if (sb) {
-        const char *min_rx = smap_get(&sb->options, "bfd-min-rx");
-        const char *decay_min_rx = smap_get(&sb->options, "bfd-decay-min-rx");
-        const char *min_tx = smap_get(&sb->options, "bfd-min-tx");
-        const char *mult = smap_get(&sb->options, "bfd-mult");
-        if (min_rx) {
-            smap_add(&bfd, "min_rx", min_rx);
-        }
-        if (decay_min_rx) {
-            smap_add(&bfd, "decay_min_rx", decay_min_rx);
-        }
-        if (min_tx) {
-            smap_add(&bfd, "min_tx", min_tx);
-        }
-        if (mult) {
-            smap_add(&bfd, "mult", mult);
-        }
-    }
-
-    /* Enable or disable bfd */
-    const struct ovsrec_interface *iface;
-    OVSREC_INTERFACE_TABLE_FOR_EACH (iface, interface_table) {
-        if (sset_contains(&tunnels, iface->name)) {
-            if (sset_contains(&bfd_ifaces, iface->name)) {
-                /* We need to enable BFD for this interface. Configure the
-                 * BFD params if
-                 *  - If BFD was disabled earlier
-                 *  - Or if CMS has updated BFD config options.
-                 */
-                if (!smap_equal(&iface->bfd, &bfd)) {
-                    ovsrec_interface_verify_bfd(iface);
-                    ovsrec_interface_set_bfd(iface, &bfd);
-                    VLOG_INFO("Enabled BFD on interface %s", iface->name);
-                }
-            } else {
-                /* We need to disable BFD for this interface if it was enabled
-                 * earlier. */
-                if (smap_count(&iface->bfd)) {
-                    ovsrec_interface_verify_bfd(iface);
-                    ovsrec_interface_set_bfd(iface, NULL);
-                    VLOG_INFO("Disabled BFD on interface %s", iface->name);
-                }
-            }
-        }
-    }
-
-    smap_destroy(&bfd);
-    sset_destroy(&tunnels);
-    sset_destroy(&bfd_ifaces);
-    sset_destroy(&bfd_chassis);
-}
diff --git a/ovn/controller/bfd.h b/ovn/controller/bfd.h
deleted file mode 100644
index 17fab5323..000000000
--- a/ovn/controller/bfd.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Copyright (c) 2017 Red Hat, 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 OVN_BFD_H
-#define OVN_BFD_H 1
-
-struct hmap;
-struct ovsdb_idl;
-struct ovsdb_idl_index;
-struct ovsrec_bridge;
-struct ovsrec_interface_table;
-struct ovsrec_open_vswitch_table;
-struct sbrec_chassis;
-struct sbrec_sb_global_table;
-struct sbrec_ha_chassis_group_table;
-struct sset;
-
-void bfd_register_ovs_idl(struct ovsdb_idl *);
-
-void bfd_run(const struct ovsrec_interface_table *,
-             const struct ovsrec_bridge *,
-             const struct sbrec_chassis *,
-             const struct sbrec_ha_chassis_group_table *,
-             const struct sbrec_sb_global_table *);
-
-void  bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int,
-                                   struct sset *active_tunnels);
-
-#endif
diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
deleted file mode 100644
index ace0f811b..000000000
--- a/ovn/controller/binding.c
+++ /dev/null
@@ -1,764 +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 "binding.h"
-#include "ha-chassis.h"
-#include "lflow.h"
-#include "lport.h"
-
-#include "lib/bitmap.h"
-#include "openvswitch/poll-loop.h"
-#include "lib/sset.h"
-#include "lib/util.h"
-#include "lib/netdev.h"
-#include "lib/vswitch-idl.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/chassis-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn-controller.h"
-
-VLOG_DEFINE_THIS_MODULE(binding);
-
-#define OVN_QOS_TYPE "linux-htb"
-
-struct qos_queue {
-    struct hmap_node node;
-    uint32_t queue_id;
-    uint32_t max_rate;
-    uint32_t burst;
-};
-
-void
-binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_qos);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_status);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
-}
-
-static void
-get_local_iface_ids(const struct ovsrec_bridge *br_int,
-                    struct shash *lport_to_iface,
-                    struct sset *local_lports,
-                    struct sset *egress_ifaces)
-{
-    int i;
-
-    for (i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-        const char *iface_id;
-        int j;
-
-        if (!strcmp(port_rec->name, br_int->name)) {
-            continue;
-        }
-
-        for (j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec;
-
-            iface_rec = port_rec->interfaces[j];
-            iface_id = smap_get(&iface_rec->external_ids, "iface-id");
-            int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
-
-            if (iface_id && ofport > 0) {
-                shash_add(lport_to_iface, iface_id, iface_rec);
-                sset_add(local_lports, iface_id);
-            }
-
-            /* Check if this is a tunnel interface. */
-            if (smap_get(&iface_rec->options, "remote_ip")) {
-                const char *tunnel_iface
-                    = smap_get(&iface_rec->status, "tunnel_egress_iface");
-                if (tunnel_iface) {
-                    sset_add(egress_ifaces, tunnel_iface);
-                }
-            }
-        }
-    }
-}
-
-static void
-add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                     struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                     const struct sbrec_datapath_binding *datapath,
-                     bool has_local_l3gateway, int depth,
-                     struct hmap *local_datapaths)
-{
-    uint32_t dp_key = datapath->tunnel_key;
-    struct local_datapath *ld = get_local_datapath(local_datapaths, dp_key);
-    if (ld) {
-        if (has_local_l3gateway) {
-            ld->has_local_l3gateway = true;
-        }
-        return;
-    }
-
-    ld = xzalloc(sizeof *ld);
-    hmap_insert(local_datapaths, &ld->hmap_node, dp_key);
-    ld->datapath = datapath;
-    ld->localnet_port = NULL;
-    ld->has_local_l3gateway = has_local_l3gateway;
-
-    if (depth >= 100) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "datapaths nested too deep");
-        return;
-    }
-
-    struct sbrec_port_binding *target =
-        sbrec_port_binding_index_init_row(sbrec_port_binding_by_datapath);
-    sbrec_port_binding_index_set_datapath(target, datapath);
-
-    const struct sbrec_port_binding *pb;
-    SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                       sbrec_port_binding_by_datapath) {
-        if (!strcmp(pb->type, "patch")) {
-            const char *peer_name = smap_get(&pb->options, "peer");
-            if (peer_name) {
-                const struct sbrec_port_binding *peer;
-
-                peer = lport_lookup_by_name(sbrec_port_binding_by_name,
-                                            peer_name);
-
-                if (peer && peer->datapath) {
-                    add_local_datapath__(sbrec_datapath_binding_by_key,
-                                         sbrec_port_binding_by_datapath,
-                                         sbrec_port_binding_by_name,
-                                         peer->datapath, false,
-                                         depth + 1, local_datapaths);
-                    ld->n_peer_ports++;
-                    ld->peer_ports = xrealloc(ld->peer_ports,
-                                              ld->n_peer_ports *
-                                              sizeof *ld->peer_ports);
-                    ld->peer_ports[ld->n_peer_ports - 1] = peer;
-                }
-            }
-        }
-    }
-    sbrec_port_binding_index_destroy_row(target);
-}
-
-static void
-add_local_datapath(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                   struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                   const struct sbrec_datapath_binding *datapath,
-                   bool has_local_l3gateway, struct hmap *local_datapaths)
-{
-    add_local_datapath__(sbrec_datapath_binding_by_key,
-                         sbrec_port_binding_by_datapath,
-                         sbrec_port_binding_by_name,
-                         datapath, has_local_l3gateway, 0, local_datapaths);
-}
-
-static void
-get_qos_params(const struct sbrec_port_binding *pb, struct hmap *queue_map)
-{
-    uint32_t max_rate = smap_get_int(&pb->options, "qos_max_rate", 0);
-    uint32_t burst = smap_get_int(&pb->options, "qos_burst", 0);
-    uint32_t queue_id = smap_get_int(&pb->options, "qdisc_queue_id", 0);
-
-    if ((!max_rate && !burst) || !queue_id) {
-        /* Qos is not configured for this port. */
-        return;
-    }
-
-    struct qos_queue *node = xzalloc(sizeof *node);
-    hmap_insert(queue_map, &node->node, hash_int(queue_id, 0));
-    node->max_rate = max_rate;
-    node->burst = burst;
-    node->queue_id = queue_id;
-}
-
-static const struct ovsrec_qos *
-get_noop_qos(struct ovsdb_idl_txn *ovs_idl_txn,
-             const struct ovsrec_qos_table *qos_table)
-{
-    const struct ovsrec_qos *qos;
-    OVSREC_QOS_TABLE_FOR_EACH (qos, qos_table) {
-        if (!strcmp(qos->type, "linux-noop")) {
-            return qos;
-        }
-    }
-
-    if (!ovs_idl_txn) {
-        return NULL;
-    }
-    qos = ovsrec_qos_insert(ovs_idl_txn);
-    ovsrec_qos_set_type(qos, "linux-noop");
-    return qos;
-}
-
-static bool
-set_noop_qos(struct ovsdb_idl_txn *ovs_idl_txn,
-             const struct ovsrec_port_table *port_table,
-             const struct ovsrec_qos_table *qos_table,
-             struct sset *egress_ifaces)
-{
-    if (!ovs_idl_txn) {
-        return false;
-    }
-
-    const struct ovsrec_qos *noop_qos = get_noop_qos(ovs_idl_txn, qos_table);
-    if (!noop_qos) {
-        return false;
-    }
-
-    const struct ovsrec_port *port;
-    size_t count = 0;
-
-    OVSREC_PORT_TABLE_FOR_EACH (port, port_table) {
-        if (sset_contains(egress_ifaces, port->name)) {
-            ovsrec_port_set_qos(port, noop_qos);
-            count++;
-        }
-        if (sset_count(egress_ifaces) == count) {
-            break;
-        }
-    }
-    return true;
-}
-
-static void
-set_qos_type(struct netdev *netdev, const char *type)
-{
-    int error = netdev_set_qos(netdev, type, NULL);
-    if (error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "%s: could not set qdisc type \"%s\" (%s)",
-                     netdev_get_name(netdev), type, ovs_strerror(error));
-    }
-}
-
-static void
-setup_qos(const char *egress_iface, struct hmap *queue_map)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
-    struct netdev *netdev_phy;
-
-    if (!egress_iface) {
-        /* Queues cannot be configured. */
-        return;
-    }
-
-    int error = netdev_open(egress_iface, NULL, &netdev_phy);
-    if (error) {
-        VLOG_WARN_RL(&rl, "%s: could not open netdev (%s)",
-                     egress_iface, ovs_strerror(error));
-        return;
-    }
-
-    /* Check current qdisc. */
-    const char *qdisc_type;
-    struct smap qdisc_details;
-
-    smap_init(&qdisc_details);
-    if (netdev_get_qos(netdev_phy, &qdisc_type, &qdisc_details) != 0 ||
-        qdisc_type[0] == '\0') {
-        smap_destroy(&qdisc_details);
-        netdev_close(netdev_phy);
-        /* Qos is not supported. */
-        return;
-    }
-    smap_destroy(&qdisc_details);
-
-    /* If we're not actually being requested to do any QoS:
-     *
-     *     - If the current qdisc type is OVN_QOS_TYPE, then we clear the qdisc
-     *       type to "".  Otherwise, it's possible that our own leftover qdisc
-     *       settings could cause strange behavior on egress.  Also, QoS is
-     *       expensive and may waste CPU time even if it's not really in use.
-     *
-     *       OVN isn't the only software that can configure qdiscs, and
-     *       physical interfaces are shared resources, so there is some risk in
-     *       this strategy: we could disrupt some other program's QoS.
-     *       Probably, to entirely avoid this possibility we would need to add
-     *       a configuration setting.
-     *
-     *     - Otherwise leave the qdisc alone. */
-    if (hmap_is_empty(queue_map)) {
-        if (!strcmp(qdisc_type, OVN_QOS_TYPE)) {
-            set_qos_type(netdev_phy, "");
-        }
-        netdev_close(netdev_phy);
-        return;
-    }
-
-    /* Configure qdisc. */
-    if (strcmp(qdisc_type, OVN_QOS_TYPE)) {
-        set_qos_type(netdev_phy, OVN_QOS_TYPE);
-    }
-
-    /* Check and delete if needed. */
-    struct netdev_queue_dump dump;
-    unsigned int queue_id;
-    struct smap queue_details;
-    struct qos_queue *sb_info;
-    struct hmap consistent_queues;
-
-    smap_init(&queue_details);
-    hmap_init(&consistent_queues);
-    NETDEV_QUEUE_FOR_EACH (&queue_id, &queue_details, &dump, netdev_phy) {
-        bool is_queue_needed = false;
-
-        HMAP_FOR_EACH_WITH_HASH (sb_info, node, hash_int(queue_id, 0),
-                                 queue_map) {
-            is_queue_needed = true;
-            if (sb_info->max_rate ==
-                smap_get_int(&queue_details, "max-rate", 0)
-                && sb_info->burst == smap_get_int(&queue_details, "burst", 0)) {
-                /* This queue is consistent. */
-                hmap_insert(&consistent_queues, &sb_info->node,
-                            hash_int(queue_id, 0));
-                break;
-            }
-        }
-
-        if (!is_queue_needed) {
-            error = netdev_delete_queue(netdev_phy, queue_id);
-            if (error) {
-                VLOG_WARN_RL(&rl, "%s: could not delete queue %u (%s)",
-                             egress_iface, queue_id, ovs_strerror(error));
-            }
-        }
-    }
-
-    /* Create/Update queues. */
-    HMAP_FOR_EACH (sb_info, node, queue_map) {
-        if (hmap_contains(&consistent_queues, &sb_info->node)) {
-            hmap_remove(&consistent_queues, &sb_info->node);
-            continue;
-        }
-
-        smap_clear(&queue_details);
-        smap_add_format(&queue_details, "max-rate", "%d", sb_info->max_rate);
-        smap_add_format(&queue_details, "burst", "%d", sb_info->burst);
-        error = netdev_set_queue(netdev_phy, sb_info->queue_id,
-                                 &queue_details);
-        if (error) {
-            VLOG_WARN_RL(&rl, "%s: could not configure queue %u (%s)",
-                         egress_iface, sb_info->queue_id, ovs_strerror(error));
-        }
-    }
-    smap_destroy(&queue_details);
-    hmap_destroy(&consistent_queues);
-    netdev_close(netdev_phy);
-}
-
-static void
-update_local_lport_ids(struct sset *local_lport_ids,
-                       const struct sbrec_port_binding *binding_rec)
-{
-        char buf[16];
-        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
-                 binding_rec->datapath->tunnel_key,
-                 binding_rec->tunnel_key);
-        sset_add(local_lport_ids, buf);
-}
-
-/*
- * Get the encap from the chassis for this port. The interface
- * may have an external_ids:encap-ip=<encap-ip> set; if so we
- * get the corresponding encap from the chassis.
- * If "encap-ip" external-ids is not set, we'll not bind the port
- * to any specific encap rec. and we'll pick up a tunnel port based on
- * the chassis name alone for the port.
- */
-static struct sbrec_encap *
-sbrec_get_port_encap(const struct sbrec_chassis *chassis_rec,
-                     const struct ovsrec_interface *iface_rec)
-{
-
-    if (!iface_rec) {
-        return NULL;
-    }
-
-    const char *encap_ip = smap_get(&iface_rec->external_ids, "encap-ip");
-    if (!encap_ip) {
-        return NULL;
-    }
-
-    struct sbrec_encap *best_encap = NULL;
-    uint32_t best_type = 0;
-    for (int i = 0; i < chassis_rec->n_encaps; i++) {
-        if (!strcmp(chassis_rec->encaps[i]->ip, encap_ip)) {
-            uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type);
-            if (tun_type > best_type) {
-                best_type = tun_type;
-                best_encap = chassis_rec->encaps[i];
-            }
-        }
-    }
-    return best_encap;
-}
-
-static bool
-is_our_chassis(const struct sbrec_chassis *chassis_rec,
-               const struct sbrec_port_binding *binding_rec,
-               const struct sset *active_tunnels,
-               const struct shash *lport_to_iface,
-               const struct sset *local_lports)
-{
-    const struct ovsrec_interface *iface_rec
-        = shash_find_data(lport_to_iface, binding_rec->logical_port);
-
-    bool our_chassis = false;
-    if (iface_rec
-        || (binding_rec->parent_port && binding_rec->parent_port[0] &&
-            sset_contains(local_lports, binding_rec->parent_port))) {
-        /* This port is in our chassis unless it is a localport. */
-        our_chassis = strcmp(binding_rec->type, "localport");
-    } else if (!strcmp(binding_rec->type, "l2gateway")) {
-        const char *chassis_id = smap_get(&binding_rec->options,
-                                          "l2gateway-chassis");
-        our_chassis = chassis_id && !strcmp(chassis_id, chassis_rec->name);
-    } else if (!strcmp(binding_rec->type, "chassisredirect") ||
-               !strcmp(binding_rec->type, "external")) {
-        our_chassis = ha_chassis_group_contains(binding_rec->ha_chassis_group,
-                                                chassis_rec) &&
-                      ha_chassis_group_is_active(binding_rec->ha_chassis_group,
-                                                 active_tunnels, chassis_rec);
-    } else if (!strcmp(binding_rec->type, "l3gateway")) {
-        const char *chassis_id = smap_get(&binding_rec->options,
-                                          "l3gateway-chassis");
-        our_chassis = chassis_id && !strcmp(chassis_id, chassis_rec->name);
-    }
-
-    return our_chassis;
-}
-
-static void
-consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                        struct ovsdb_idl_txn *ovs_idl_txn,
-                        struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                        struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                        struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                        const struct sset *active_tunnels,
-                        const struct sbrec_chassis *chassis_rec,
-                        const struct sbrec_port_binding *binding_rec,
-                        struct hmap *qos_map,
-                        struct hmap *local_datapaths,
-                        struct shash *lport_to_iface,
-                        struct sset *local_lports,
-                        struct sset *local_lport_ids)
-{
-    const struct ovsrec_interface *iface_rec
-        = shash_find_data(lport_to_iface, binding_rec->logical_port);
-
-    bool our_chassis = is_our_chassis(chassis_rec, binding_rec, active_tunnels,
-                                      lport_to_iface, local_lports);
-    if (iface_rec
-        || (binding_rec->parent_port && binding_rec->parent_port[0] &&
-            sset_contains(local_lports, binding_rec->parent_port))) {
-        if (binding_rec->parent_port && binding_rec->parent_port[0]) {
-            /* Add child logical port to the set of all local ports. */
-            sset_add(local_lports, binding_rec->logical_port);
-        }
-        add_local_datapath(sbrec_datapath_binding_by_key,
-                           sbrec_port_binding_by_datapath,
-                           sbrec_port_binding_by_name,
-                           binding_rec->datapath, false, local_datapaths);
-        if (iface_rec && qos_map && ovs_idl_txn) {
-            get_qos_params(binding_rec, qos_map);
-        }
-    } else if (!strcmp(binding_rec->type, "l2gateway")) {
-        if (our_chassis) {
-            sset_add(local_lports, binding_rec->logical_port);
-            add_local_datapath(sbrec_datapath_binding_by_key,
-                               sbrec_port_binding_by_datapath,
-                               sbrec_port_binding_by_name,
-                               binding_rec->datapath, false, local_datapaths);
-        }
-    } else if (!strcmp(binding_rec->type, "chassisredirect")) {
-        if (ha_chassis_group_contains(binding_rec->ha_chassis_group,
-                                      chassis_rec)) {
-            add_local_datapath(sbrec_datapath_binding_by_key,
-                               sbrec_port_binding_by_datapath,
-                               sbrec_port_binding_by_name,
-                               binding_rec->datapath, false, local_datapaths);
-        }
-    } else if (!strcmp(binding_rec->type, "l3gateway")) {
-        if (our_chassis) {
-            add_local_datapath(sbrec_datapath_binding_by_key,
-                               sbrec_port_binding_by_datapath,
-                               sbrec_port_binding_by_name,
-                               binding_rec->datapath, true, local_datapaths);
-        }
-    } else if (!strcmp(binding_rec->type, "localnet")) {
-        /* Add all localnet ports to local_lports so that we allocate ct zones
-         * for them. */
-        sset_add(local_lports, binding_rec->logical_port);
-    } else if (!strcmp(binding_rec->type, "external")) {
-        if (ha_chassis_group_contains(binding_rec->ha_chassis_group,
-                                      chassis_rec)) {
-            add_local_datapath(sbrec_datapath_binding_by_key,
-                               sbrec_port_binding_by_datapath,
-                               sbrec_port_binding_by_name,
-                               binding_rec->datapath, false, local_datapaths);
-        }
-    }
-
-    if (our_chassis
-        || !strcmp(binding_rec->type, "patch")
-        || !strcmp(binding_rec->type, "localport")
-        || !strcmp(binding_rec->type, "vtep")
-        || !strcmp(binding_rec->type, "localnet")) {
-        update_local_lport_ids(local_lport_ids, binding_rec);
-    }
-
-    ovs_assert(ovnsb_idl_txn);
-    if (ovnsb_idl_txn) {
-        const char *vif_chassis = smap_get(&binding_rec->options,
-                                           "requested-chassis");
-        bool can_bind = !vif_chassis || !vif_chassis[0]
-                        || !strcmp(vif_chassis, chassis_rec->name)
-                        || !strcmp(vif_chassis, chassis_rec->hostname);
-
-        if (can_bind && our_chassis) {
-            if (binding_rec->chassis != chassis_rec) {
-                if (binding_rec->chassis) {
-                    VLOG_INFO("Changing chassis for lport %s from %s to %s.",
-                              binding_rec->logical_port,
-                              binding_rec->chassis->name,
-                              chassis_rec->name);
-                } else {
-                    VLOG_INFO("Claiming lport %s for this chassis.",
-                              binding_rec->logical_port);
-                }
-                for (int i = 0; i < binding_rec->n_mac; i++) {
-                    VLOG_INFO("%s: Claiming %s",
-                              binding_rec->logical_port, binding_rec->mac[i]);
-                }
-                sbrec_port_binding_set_chassis(binding_rec, chassis_rec);
-            }
-            /* Check if the port encap binding, if any, has changed */
-            struct sbrec_encap *encap_rec = sbrec_get_port_encap(
-                                            chassis_rec, iface_rec);
-            if (encap_rec && binding_rec->encap != encap_rec) {
-                sbrec_port_binding_set_encap(binding_rec, encap_rec);
-            }
-        } else if (binding_rec->chassis == chassis_rec) {
-            VLOG_INFO("Releasing lport %s from this chassis.",
-                      binding_rec->logical_port);
-            if (binding_rec->encap)
-                sbrec_port_binding_set_encap(binding_rec, NULL);
-            sbrec_port_binding_set_chassis(binding_rec, NULL);
-        } else if (our_chassis) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_INFO_RL(&rl,
-                         "Not claiming lport %s, chassis %s "
-                         "requested-chassis %s",
-                         binding_rec->logical_port,
-                         chassis_rec->name,
-                         vif_chassis);
-        }
-    }
-}
-
-static void
-consider_localnet_port(const struct sbrec_port_binding *binding_rec,
-                       struct hmap *local_datapaths)
-{
-    struct local_datapath *ld
-        = get_local_datapath(local_datapaths,
-                             binding_rec->datapath->tunnel_key);
-    if (!ld) {
-        return;
-    }
-
-    if (ld->localnet_port && strcmp(ld->localnet_port->logical_port,
-                                    binding_rec->logical_port)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "localnet port '%s' already set for datapath "
-                     "'%"PRId64"', skipping the new port '%s'.",
-                     ld->localnet_port->logical_port,
-                     binding_rec->datapath->tunnel_key,
-                     binding_rec->logical_port);
-        return;
-    }
-    ld->localnet_port = binding_rec;
-}
-
-void
-binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-            struct ovsdb_idl_txn *ovs_idl_txn,
-            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-            struct ovsdb_idl_index *sbrec_port_binding_by_name,
-            const struct ovsrec_port_table *port_table,
-            const struct ovsrec_qos_table *qos_table,
-            const struct sbrec_port_binding_table *port_binding_table,
-            const struct ovsrec_bridge *br_int,
-            const struct sbrec_chassis *chassis_rec,
-            const struct sset *active_tunnels,
-            struct hmap *local_datapaths, struct sset *local_lports,
-            struct sset *local_lport_ids)
-{
-    if (!chassis_rec) {
-        return;
-    }
-
-    const struct sbrec_port_binding *binding_rec;
-    struct shash lport_to_iface = SHASH_INITIALIZER(&lport_to_iface);
-    struct sset egress_ifaces = SSET_INITIALIZER(&egress_ifaces);
-    struct hmap qos_map;
-
-    hmap_init(&qos_map);
-    if (br_int) {
-        get_local_iface_ids(br_int, &lport_to_iface, local_lports,
-                            &egress_ifaces);
-    }
-
-    /* Run through each binding record to see if it is resident on this
-     * chassis and update the binding accordingly.  This includes both
-     * directly connected logical ports and children of those ports. */
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
-        consider_local_datapath(ovnsb_idl_txn, ovs_idl_txn,
-                                sbrec_datapath_binding_by_key,
-                                sbrec_port_binding_by_datapath,
-                                sbrec_port_binding_by_name,
-                                active_tunnels, chassis_rec, binding_rec,
-                                sset_is_empty(&egress_ifaces) ? NULL :
-                                &qos_map, local_datapaths, &lport_to_iface,
-                                local_lports, local_lport_ids);
-
-    }
-
-    /* Run through each binding record to see if it is a localnet port
-     * on local datapaths discovered from above loop, and update the
-     * corresponding local datapath accordingly. */
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
-        if (!strcmp(binding_rec->type, "localnet")) {
-            consider_localnet_port(binding_rec, local_datapaths);
-        }
-    }
-
-    if (!sset_is_empty(&egress_ifaces)
-        && set_noop_qos(ovs_idl_txn, port_table, qos_table, &egress_ifaces)) {
-        const char *entry;
-        SSET_FOR_EACH (entry, &egress_ifaces) {
-            setup_qos(entry, &qos_map);
-        }
-    }
-
-    shash_destroy(&lport_to_iface);
-    sset_destroy(&egress_ifaces);
-    hmap_destroy(&qos_map);
-}
-
-/* Returns true if port-binding changes potentially require flow changes on
- * the current chassis. Returns false if we are sure there is no impact. */
-bool
-binding_evaluate_port_binding_changes(
-        const struct sbrec_port_binding_table *pb_table,
-        const struct ovsrec_bridge *br_int,
-        const struct sbrec_chassis *chassis_rec,
-        struct sset *active_tunnels,
-        struct sset *local_lports)
-{
-    if (!chassis_rec) {
-        return true;
-    }
-
-    bool changed = false;
-
-    const struct sbrec_port_binding *binding_rec;
-    struct shash lport_to_iface = SHASH_INITIALIZER(&lport_to_iface);
-    struct sset egress_ifaces = SSET_INITIALIZER(&egress_ifaces);
-    if (br_int) {
-        get_local_iface_ids(br_int, &lport_to_iface, local_lports,
-                            &egress_ifaces);
-    }
-    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec, pb_table) {
-        /* XXX: currently OVSDB change tracking doesn't support getting old
-         * data when the operation is update, so if a port-binding moved from
-         * this chassis to another, there is no easy way to find out the
-         * change. To workaround this problem, we just makes sure if
-         * any port *related to* this chassis has any change, then trigger
-         * recompute.
-         *
-         * - If a regular VIF is unbound from this chassis, the local ovsdb
-         *   interface table will be updated, which will trigger recompute.
-         *
-         * - If the port is not a regular VIF, always trigger recompute. */
-        if (binding_rec->chassis == chassis_rec
-            || is_our_chassis(chassis_rec, binding_rec,
-                              active_tunnels, &lport_to_iface, local_lports)
-            || strcmp(binding_rec->type, "")) {
-            changed = true;
-            break;
-        }
-    }
-
-    shash_destroy(&lport_to_iface);
-    sset_destroy(&egress_ifaces);
-    return changed;
-}
-
-/* Returns true if the database is all cleaned up, false if more work is
- * required. */
-bool
-binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                const struct sbrec_port_binding_table *port_binding_table,
-                const struct sbrec_chassis *chassis_rec)
-{
-    if (!ovnsb_idl_txn) {
-        return false;
-    }
-    if (!chassis_rec) {
-        return true;
-    }
-
-    const struct sbrec_port_binding *binding_rec;
-    bool any_changes = false;
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
-        if (binding_rec->chassis == chassis_rec) {
-            if (binding_rec->encap)
-                sbrec_port_binding_set_encap(binding_rec, NULL);
-            sbrec_port_binding_set_chassis(binding_rec, NULL);
-            any_changes = true;
-        }
-    }
-
-    if (any_changes) {
-        ovsdb_idl_txn_add_comment(
-            ovnsb_idl_txn,
-            "ovn-controller: removing all port bindings for '%s'",
-            chassis_rec->name);
-    }
-
-    return !any_changes;
-}
diff --git a/ovn/controller/binding.h b/ovn/controller/binding.h
deleted file mode 100644
index 8d9492630..000000000
--- a/ovn/controller/binding.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_BINDING_H
-#define OVN_BINDING_H 1
-
-#include <stdbool.h>
-
-struct hmap;
-struct ovsdb_idl;
-struct ovsdb_idl_index;
-struct ovsdb_idl_txn;
-struct ovsrec_bridge;
-struct ovsrec_port_table;
-struct ovsrec_qos_table;
-struct sbrec_chassis;
-struct sbrec_port_binding_table;
-struct sset;
-
-void binding_register_ovs_idl(struct ovsdb_idl *);
-void binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                 struct ovsdb_idl_txn *ovs_idl_txn,
-                 struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                 const struct ovsrec_port_table *,
-                 const struct ovsrec_qos_table *,
-                 const struct sbrec_port_binding_table *,
-                 const struct ovsrec_bridge *br_int,
-                 const struct sbrec_chassis *,
-                 const struct sset *active_tunnels,
-                 struct hmap *local_datapaths,
-                 struct sset *local_lports, struct sset *local_lport_ids);
-bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                     const struct sbrec_port_binding_table *,
-                     const struct sbrec_chassis *);
-bool binding_evaluate_port_binding_changes(
-        const struct sbrec_port_binding_table *,
-        const struct ovsrec_bridge *br_int,
-        const struct sbrec_chassis *,
-        struct sset *active_tunnels,
-        struct sset *local_lports);
-
-#endif /* ovn/binding.h */
diff --git a/ovn/controller/chassis.c b/ovn/controller/chassis.c
deleted file mode 100644
index 04b98d86c..000000000
--- a/ovn/controller/chassis.c
+++ /dev/null
@@ -1,671 +0,0 @@
-/* Copyright (c) 2015, 2016 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 <unistd.h>
-
-#include "chassis.h"
-
-#include "lib/smap.h"
-#include "lib/sset.h"
-#include "lib/vswitch-idl.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/vlog.h"
-#include "openvswitch/ofp-parse.h"
-#include "ovn/lib/chassis-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn-controller.h"
-#include "lib/util.h"
-
-VLOG_DEFINE_THIS_MODULE(chassis);
-
-#ifndef HOST_NAME_MAX
-/* For windows. */
-#define HOST_NAME_MAX 255
-#endif /* HOST_NAME_MAX */
-
-/*
- * Structure to hold chassis specific state (currently just chassis-id)
- * to avoid database lookups when changes happen while the controller is
- * running.
- */
-struct chassis_info {
-    /* Last ID we initialized the Chassis SB record with. */
-    struct ds id;
-
-    /* True if Chassis SB record is initialized, false otherwise. */
-    uint32_t id_inited : 1;
-};
-
-static struct chassis_info chassis_state = {
-    .id = DS_EMPTY_INITIALIZER,
-    .id_inited = false,
-};
-
-static void
-chassis_info_set_id(struct chassis_info *info, const char *id)
-{
-    ds_clear(&info->id);
-    ds_put_cstr(&info->id, id);
-    info->id_inited = true;
-}
-
-static bool
-chassis_info_id_inited(const struct chassis_info *info)
-{
-    return info->id_inited;
-}
-
-static const char *
-chassis_info_id(const struct chassis_info *info)
-{
-    return ds_cstr_ro(&info->id);
-}
-
-/*
- * Structure for storing the chassis config parsed from the ovs table.
- */
-struct ovs_chassis_cfg {
-    /* Single string fields parsed from external-ids. */
-    const char *hostname;
-    const char *bridge_mappings;
-    const char *datapath_type;
-    const char *encap_csum;
-    const char *cms_options;
-    const char *chassis_macs;
-
-    /* Set of encap types parsed from the 'ovn-encap-type' external-id. */
-    struct sset encap_type_set;
-    /* Set of encap IPs parsed from the 'ovn-encap-type' external-id. */
-    struct sset encap_ip_set;
-    /* Interface type list formatted in the OVN-SB Chassis required format. */
-    struct ds iface_types;
-};
-
-static void
-ovs_chassis_cfg_init(struct ovs_chassis_cfg *cfg)
-{
-    sset_init(&cfg->encap_type_set);
-    sset_init(&cfg->encap_ip_set);
-    ds_init(&cfg->iface_types);
-}
-
-static void
-ovs_chassis_cfg_destroy(struct ovs_chassis_cfg *cfg)
-{
-    sset_destroy(&cfg->encap_type_set);
-    sset_destroy(&cfg->encap_ip_set);
-    ds_destroy(&cfg->iface_types);
-}
-
-void
-chassis_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_iface_types);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_datapath_type);
-}
-
-static const char *
-get_hostname(const struct smap *ext_ids)
-{
-    const char *hostname = smap_get_def(ext_ids, "hostname", "");
-
-    if (strlen(hostname) == 0) {
-        static char hostname_[HOST_NAME_MAX + 1];
-
-        if (gethostname(hostname_, sizeof(hostname_))) {
-            hostname_[0] = 0;
-        }
-
-        return &hostname_[0];
-    }
-
-    return hostname;
-}
-
-static const char *
-get_bridge_mappings(const struct smap *ext_ids)
-{
-    return smap_get_def(ext_ids, "ovn-bridge-mappings", "");
-}
-
-static const char *
-get_chassis_mac_mappings(const struct smap *ext_ids)
-{
-    return smap_get_def(ext_ids, "ovn-chassis-mac-mappings", "");
-}
-
-static const char *
-get_cms_options(const struct smap *ext_ids)
-{
-    return smap_get_def(ext_ids, "ovn-cms-options", "");
-}
-
-static const char *
-get_encap_csum(const struct smap *ext_ids)
-{
-    return smap_get_def(ext_ids, "ovn-encap-csum", "true");
-}
-
-static const char *
-get_datapath_type(const struct ovsrec_bridge *br_int)
-{
-    if (br_int && br_int->datapath_type) {
-        return br_int->datapath_type;
-    }
-
-    return "";
-}
-
-static void
-update_chassis_transport_zones(const struct sset *transport_zones,
-                               const struct sbrec_chassis *chassis_rec)
-{
-    struct sset chassis_tzones_set = SSET_INITIALIZER(&chassis_tzones_set);
-    for (int i = 0; i < chassis_rec->n_transport_zones; i++) {
-        sset_add(&chassis_tzones_set, chassis_rec->transport_zones[i]);
-    }
-
-    /* Only update the transport zones if something changed */
-    if (!sset_equals(transport_zones, &chassis_tzones_set)) {
-        const char **ls_arr = sset_array(transport_zones);
-        sbrec_chassis_set_transport_zones(chassis_rec, ls_arr,
-                                          sset_count(transport_zones));
-        free(ls_arr);
-    }
-
-    sset_destroy(&chassis_tzones_set);
-}
-
-/*
- * Parse an ovs 'encap_type' string and stores the resulting types in the
- * 'encap_type_set' string set.
- */
-static bool
-chassis_parse_ovs_encap_type(const char *encap_type,
-                             struct sset *encap_type_set)
-{
-    sset_from_delimited_string(encap_type_set, encap_type, ",");
-
-    const char *type;
-
-    SSET_FOR_EACH (type, encap_type_set) {
-        if (!get_tunnel_type(type)) {
-            VLOG_INFO("Unknown tunnel type: %s", type);
-        }
-    }
-
-    return true;
-}
-
-/*
- * Parse an ovs 'encap_ip' string and stores the resulting IP representations
- * in the 'encap_ip_set' string set.
- */
-static bool
-chassis_parse_ovs_encap_ip(const char *encap_ip, struct sset *encap_ip_set)
-{
-    sset_from_delimited_string(encap_ip_set, encap_ip, ",");
-    return true;
-}
-
-/*
- * Parse the ovs 'iface_types' and store them in the format required by the
- * Chassis record.
- */
-static bool
-chassis_parse_ovs_iface_types(char **iface_types, size_t n_iface_types,
-                              struct ds *iface_types_str)
-{
-    for (size_t i = 0; i < n_iface_types; i++) {
-        ds_put_format(iface_types_str, "%s,", iface_types[i]);
-    }
-    ds_chomp(iface_types_str, ',');
-    return true;
-}
-
-/*
- * Parse the 'ovs_table' entry and populate 'ovs_cfg'.
- */
-static bool
-chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table,
-                         const struct ovsrec_bridge *br_int,
-                         struct ovs_chassis_cfg *ovs_cfg)
-{
-    const struct ovsrec_open_vswitch *cfg =
-        ovsrec_open_vswitch_table_first(ovs_table);
-
-    if (!cfg) {
-        VLOG_INFO("No Open_vSwitch row defined.");
-        return false;
-    }
-
-    const char *encap_type = smap_get(&cfg->external_ids, "ovn-encap-type");
-    const char *encap_ips = smap_get(&cfg->external_ids, "ovn-encap-ip");
-    if (!encap_type || !encap_ips) {
-        VLOG_INFO("Need to specify an encap type and ip");
-        return false;
-    }
-
-    ovs_cfg->hostname = get_hostname(&cfg->external_ids);
-    ovs_cfg->bridge_mappings = get_bridge_mappings(&cfg->external_ids);
-    ovs_cfg->datapath_type = get_datapath_type(br_int);
-    ovs_cfg->encap_csum = get_encap_csum(&cfg->external_ids);
-    ovs_cfg->cms_options = get_cms_options(&cfg->external_ids);
-    ovs_cfg->chassis_macs = get_chassis_mac_mappings(&cfg->external_ids);
-
-    if (!chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set)) {
-        return false;
-    }
-
-    if (!chassis_parse_ovs_encap_ip(encap_ips, &ovs_cfg->encap_ip_set)) {
-        sset_destroy(&ovs_cfg->encap_type_set);
-        return false;
-    }
-
-    if (!chassis_parse_ovs_iface_types(cfg->iface_types,
-                                       cfg->n_iface_types,
-                                       &ovs_cfg->iface_types)) {
-        sset_destroy(&ovs_cfg->encap_type_set);
-        sset_destroy(&ovs_cfg->encap_ip_set);
-    }
-
-    return true;
-}
-
-static void
-chassis_build_external_ids(struct smap *ext_ids, const char *bridge_mappings,
-                           const char *datapath_type, const char *cms_options,
-                           const char *chassis_macs, const char *iface_types)
-{
-    smap_replace(ext_ids, "ovn-bridge-mappings", bridge_mappings);
-    smap_replace(ext_ids, "datapath-type", datapath_type);
-    smap_replace(ext_ids, "ovn-cms-options", cms_options);
-    smap_replace(ext_ids, "iface-types", iface_types);
-    smap_replace(ext_ids, "ovn-chassis-mac-mappings", chassis_macs);
-}
-
-/*
- * Returns true if any external-id doesn't match the values in 'chassis-rec'.
- */
-static bool
-chassis_external_ids_changed(const char *bridge_mappings,
-                             const char *datapath_type,
-                             const char *cms_options,
-                             const char *chassis_macs,
-                             const struct ds *iface_types,
-                             const struct sbrec_chassis *chassis_rec)
-{
-    const char *chassis_bridge_mappings =
-        get_bridge_mappings(&chassis_rec->external_ids);
-
-    if (strcmp(bridge_mappings, chassis_bridge_mappings)) {
-        return true;
-    }
-
-    const char *chassis_datapath_type =
-        smap_get_def(&chassis_rec->external_ids, "datapath-type", "");
-
-    if (strcmp(datapath_type, chassis_datapath_type)) {
-        return true;
-    }
-
-    const char *chassis_cms_options =
-        get_cms_options(&chassis_rec->external_ids);
-
-    if (strcmp(cms_options, chassis_cms_options)) {
-        return true;
-    }
-
-    const char *chassis_mac_mappings =
-        get_chassis_mac_mappings(&chassis_rec->external_ids);
-    if (strcmp(chassis_macs, chassis_mac_mappings)) {
-        return true;
-    }
-
-    const char *chassis_iface_types =
-        smap_get_def(&chassis_rec->external_ids, "iface-types", "");
-
-    if (strcmp(ds_cstr_ro(iface_types), chassis_iface_types)) {
-        return true;
-    }
-
-    return false;
-}
-
-/*
- * Returns true if the tunnel config obtained by combining 'encap_type_set'
- * with 'encap_ip_set' and 'encap_csum' doesn't match the values in
- * 'chassis-rec'.
- */
-static bool
-chassis_tunnels_changed(const struct sset *encap_type_set,
-                        const struct sset *encap_ip_set,
-                        const char *encap_csum,
-                        const struct sbrec_chassis *chassis_rec)
-{
-    size_t encap_type_count = 0;
-
-    for (int i = 0; i < chassis_rec->n_encaps; i++) {
-        if (strcmp(chassis_rec->name, chassis_rec->encaps[i]->chassis_name)) {
-            return true;
-        }
-
-        if (!sset_contains(encap_type_set, chassis_rec->encaps[i]->type)) {
-            return true;
-        }
-        encap_type_count++;
-
-        if (!sset_contains(encap_ip_set, chassis_rec->encaps[i]->ip)) {
-            return true;
-        }
-
-        if (strcmp(smap_get_def(&chassis_rec->encaps[i]->options, "csum", ""),
-                   encap_csum)) {
-            return true;
-        }
-    }
-
-    size_t tunnel_count =
-        sset_count(encap_type_set) * sset_count(encap_ip_set);
-
-    if (tunnel_count != chassis_rec->n_encaps) {
-        return true;
-    }
-
-    if (sset_count(encap_type_set) != encap_type_count) {
-        return true;
-    }
-
-    return false;
-}
-
-/*
- * Build the new encaps config (full mesh of 'encap_type_set' and
- * 'encap_ip_set'). Allocates and stores the new 'n_encap' Encap records in
- * 'encaps'.
- */
-static struct sbrec_encap **
-chassis_build_encaps(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                     const struct sset *encap_type_set,
-                     const struct sset *encap_ip_set,
-                     const char *chassis_id,
-                     const char *encap_csum,
-                     size_t *n_encap)
-{
-    size_t tunnel_count = 0;
-
-    struct sbrec_encap **encaps =
-        xmalloc(sset_count(encap_type_set) * sset_count(encap_ip_set) *
-                sizeof(*encaps));
-    const struct smap options = SMAP_CONST1(&options, "csum", encap_csum);
-
-    const char *encap_ip;
-    const char *encap_type;
-
-    SSET_FOR_EACH (encap_ip, encap_ip_set) {
-        SSET_FOR_EACH (encap_type, encap_type_set) {
-            struct sbrec_encap *encap = sbrec_encap_insert(ovnsb_idl_txn);
-
-            sbrec_encap_set_type(encap, encap_type);
-            sbrec_encap_set_ip(encap, encap_ip);
-            sbrec_encap_set_options(encap, &options);
-            sbrec_encap_set_chassis_name(encap, chassis_id);
-
-            encaps[tunnel_count] = encap;
-            tunnel_count++;
-        }
-    }
-
-    *n_encap = tunnel_count;
-    return encaps;
-}
-
-/*
- * Returns a pointer to a chassis record from 'chassis_table' that
- * matches at least one tunnel config.
- */
-static const struct sbrec_chassis *
-chassis_get_stale_record(const struct sbrec_chassis_table *chassis_table,
-                         const struct ovs_chassis_cfg *ovs_cfg,
-                         const char *chassis_id)
-{
-    const struct sbrec_chassis *chassis_rec;
-
-    SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) {
-        for (size_t i = 0; i < chassis_rec->n_encaps; i++) {
-            if (sset_contains(&ovs_cfg->encap_type_set,
-                              chassis_rec->encaps[i]->type) &&
-                    sset_contains(&ovs_cfg->encap_ip_set,
-                                  chassis_rec->encaps[i]->ip)) {
-                return chassis_rec;
-            }
-            if (strcmp(chassis_rec->name, chassis_id) == 0) {
-                return chassis_rec;
-            }
-        }
-    }
-
-    return NULL;
-}
-
-/* If this is a chassis config update after we initialized the record once
- * then we should always be able to find it with the ID we saved in
- * chassis_state.
- * Otherwise (i.e., first time we create the record) then we check if there's
- * a stale record from a previous controller run that didn't end gracefully
- * and reuse it. If not then we create a new record.
- */
-static const struct sbrec_chassis *
-chassis_get_record(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                   struct ovsdb_idl_index *sbrec_chassis_by_name,
-                   const struct sbrec_chassis_table *chassis_table,
-                   const struct ovs_chassis_cfg *ovs_cfg,
-                   const char *chassis_id)
-{
-    const struct sbrec_chassis *chassis_rec;
-
-    if (chassis_info_id_inited(&chassis_state)) {
-        chassis_rec = chassis_lookup_by_name(sbrec_chassis_by_name,
-                                             chassis_info_id(&chassis_state));
-        if (!chassis_rec) {
-            VLOG_WARN("Could not find Chassis : stored (%s) ovs (%s)",
-                      chassis_info_id(&chassis_state), chassis_id);
-        }
-    } else {
-        chassis_rec =
-            chassis_get_stale_record(chassis_table, ovs_cfg, chassis_id);
-
-        if (!chassis_rec && ovnsb_idl_txn) {
-            chassis_rec = sbrec_chassis_insert(ovnsb_idl_txn);
-        }
-    }
-    return chassis_rec;
-}
-
-/* Update a Chassis record based on the config in the ovs config. */
-static void
-chassis_update(const struct sbrec_chassis *chassis_rec,
-               struct ovsdb_idl_txn *ovnsb_idl_txn,
-               const struct ovs_chassis_cfg *ovs_cfg,
-               const char *chassis_id,
-               const struct sset *transport_zones)
-{
-    if (strcmp(chassis_id, chassis_rec->name)) {
-        sbrec_chassis_set_name(chassis_rec, chassis_id);
-    }
-
-    if (strcmp(ovs_cfg->hostname, chassis_rec->hostname)) {
-        sbrec_chassis_set_hostname(chassis_rec, ovs_cfg->hostname);
-    }
-
-    if (chassis_external_ids_changed(ovs_cfg->bridge_mappings,
-                                     ovs_cfg->datapath_type,
-                                     ovs_cfg->cms_options,
-                                     ovs_cfg->chassis_macs,
-                                     &ovs_cfg->iface_types,
-                                     chassis_rec)) {
-        struct smap ext_ids;
-
-        smap_clone(&ext_ids, &chassis_rec->external_ids);
-        chassis_build_external_ids(&ext_ids, ovs_cfg->bridge_mappings,
-                                   ovs_cfg->datapath_type,
-                                   ovs_cfg->cms_options,
-                                   ovs_cfg->chassis_macs,
-                                   ds_cstr_ro(&ovs_cfg->iface_types));
-        sbrec_chassis_verify_external_ids(chassis_rec);
-        sbrec_chassis_set_external_ids(chassis_rec, &ext_ids);
-        smap_destroy(&ext_ids);
-    }
-
-    update_chassis_transport_zones(transport_zones, chassis_rec);
-
-    /* If any of the encaps should change, update them. */
-    bool tunnels_changed =
-        chassis_tunnels_changed(&ovs_cfg->encap_type_set,
-                                &ovs_cfg->encap_ip_set, ovs_cfg->encap_csum,
-                                chassis_rec);
-    if (!tunnels_changed) {
-        return;
-    }
-
-    struct sbrec_encap **encaps;
-    size_t n_encap;
-
-    encaps =
-        chassis_build_encaps(ovnsb_idl_txn, &ovs_cfg->encap_type_set,
-                             &ovs_cfg->encap_ip_set, chassis_id,
-                             ovs_cfg->encap_csum, &n_encap);
-    sbrec_chassis_set_encaps(chassis_rec, encaps, n_encap);
-    free(encaps);
-}
-
-/* Returns this chassis's Chassis record, if it is available. */
-const struct sbrec_chassis *
-chassis_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-            struct ovsdb_idl_index *sbrec_chassis_by_name,
-            const struct ovsrec_open_vswitch_table *ovs_table,
-            const struct sbrec_chassis_table *chassis_table,
-            const char *chassis_id,
-            const struct ovsrec_bridge *br_int,
-            const struct sset *transport_zones)
-{
-    struct ovs_chassis_cfg ovs_cfg;
-
-    /* Get the chassis config from the ovs table. */
-    ovs_chassis_cfg_init(&ovs_cfg);
-    if (!chassis_parse_ovs_config(ovs_table, br_int, &ovs_cfg)) {
-        return NULL;
-    }
-
-    const struct sbrec_chassis *chassis_rec =
-        chassis_get_record(ovnsb_idl_txn, sbrec_chassis_by_name,
-                           chassis_table, &ovs_cfg, chassis_id);
-
-    /* If we found (or created) a record, update it with the correct config
-     * and store the current chassis_id for fast lookup in case it gets
-     * modified in the ovs table.
-     */
-    if (chassis_rec && ovnsb_idl_txn) {
-        chassis_update(chassis_rec, ovnsb_idl_txn, &ovs_cfg, chassis_id,
-                       transport_zones);
-        chassis_info_set_id(&chassis_state, chassis_id);
-        ovsdb_idl_txn_add_comment(ovnsb_idl_txn,
-                                  "ovn-controller: registering chassis '%s'",
-                                  chassis_id);
-    }
-
-    ovs_chassis_cfg_destroy(&ovs_cfg);
-    return chassis_rec;
-}
-
-bool
-chassis_get_mac(const struct sbrec_chassis *chassis_rec,
-                const char *bridge_mapping,
-                struct eth_addr *chassis_mac)
-{
-    const char *tokens
-        = get_chassis_mac_mappings(&chassis_rec->external_ids);
-    if (!tokens[0]) {
-       return false;
-    }
-
-    char *save_ptr = NULL;
-    bool ret = false;
-    char *tokstr = xstrdup(tokens);
-
-    /* Format for a chassis mac configuration is:
-     * ovn-chassis-mac-mappings="bridge-name1:MAC1,bridge-name2:MAC2"
-     */
-    for (char *token = strtok_r(tokstr, ",", &save_ptr);
-         token != NULL;
-         token = strtok_r(NULL, ",", &save_ptr)) {
-        char *save_ptr2 = NULL;
-        char *chassis_mac_bridge = strtok_r(token, ":", &save_ptr2);
-        char *chassis_mac_str = strtok_r(NULL, "", &save_ptr2);
-
-        if (!strcmp(chassis_mac_bridge, bridge_mapping)) {
-            struct eth_addr temp_mac;
-
-            /* Return the first chassis mac. */
-            char *err_str = str_to_mac(chassis_mac_str, &temp_mac);
-            if (err_str) {
-                free(err_str);
-                continue;
-            }
-
-            ret = true;
-            *chassis_mac = temp_mac;
-            break;
-        }
-    }
-
-    free(tokstr);
-    return ret;
-}
-
-/* Returns true if the database is all cleaned up, false if more work is
- * required. */
-bool
-chassis_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                const struct sbrec_chassis *chassis_rec)
-{
-    if (!chassis_rec) {
-        return true;
-    }
-    if (ovnsb_idl_txn) {
-        ovsdb_idl_txn_add_comment(ovnsb_idl_txn,
-                                  "ovn-controller: unregistering chassis '%s'",
-                                  chassis_rec->name);
-        sbrec_chassis_delete(chassis_rec);
-    }
-    return false;
-}
-
-/*
- * Returns the last initialized chassis-id.
- */
-const char *
-chassis_get_id(void)
-{
-    if (chassis_info_id_inited(&chassis_state)) {
-        return chassis_info_id(&chassis_state);
-    }
-
-    return NULL;
-}
diff --git a/ovn/controller/chassis.h b/ovn/controller/chassis.h
deleted file mode 100644
index 16a131a3b..000000000
--- a/ovn/controller/chassis.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_CHASSIS_H
-#define OVN_CHASSIS_H 1
-
-#include <stdbool.h>
-
-struct ovsdb_idl;
-struct ovsdb_idl_index;
-struct ovsdb_idl_txn;
-struct ovsrec_bridge;
-struct ovsrec_open_vswitch_table;
-struct sbrec_chassis;
-struct sbrec_chassis_table;
-struct sset;
-struct eth_addr;
-
-void chassis_register_ovs_idl(struct ovsdb_idl *);
-const struct sbrec_chassis *chassis_run(
-    struct ovsdb_idl_txn *ovnsb_idl_txn,
-    struct ovsdb_idl_index *sbrec_chassis_by_name,
-    const struct ovsrec_open_vswitch_table *,
-    const struct sbrec_chassis_table *,
-    const char *chassis_id, const struct ovsrec_bridge *br_int,
-    const struct sset *transport_zones);
-bool chassis_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                     const struct sbrec_chassis *);
-bool chassis_get_mac(const struct sbrec_chassis *chassis,
-                     const char *bridge_mapping,
-                     struct eth_addr *chassis_mac);
-const char *chassis_get_id(void);
-
-#endif /* ovn/chassis.h */
diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c
deleted file mode 100644
index d4a436df3..000000000
--- a/ovn/controller/encaps.c
+++ /dev/null
@@ -1,409 +0,0 @@
-/* Copyright (c) 2015, 2016 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 "encaps.h"
-
-#include "lib/hash.h"
-#include "lib/sset.h"
-#include "lib/util.h"
-#include "lib/vswitch-idl.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn-controller.h"
-
-VLOG_DEFINE_THIS_MODULE(encaps);
-
-/*
- * Given there could be multiple tunnels with different IPs to the same
- * chassis we annotate the ovn-chassis-id with
- * <chassis_name>OVN_MVTEP_CHASSISID_DELIM<IP>.
- */
-#define	OVN_MVTEP_CHASSISID_DELIM '@'
-
-void
-encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options);
-}
-
-/* Enough context to create a new tunnel, using tunnel_add(). */
-struct tunnel_ctx {
-    /* Maps from a chassis name to "struct chassis_node *". */
-    struct shash chassis;
-
-    /* Names of all ports in the bridge, to allow checking uniqueness when
-     * adding a new tunnel. */
-    struct sset port_names;
-
-    struct ovsdb_idl_txn *ovs_txn;
-    const struct ovsrec_bridge *br_int;
-};
-
-struct chassis_node {
-    const struct ovsrec_port *port;
-    const struct ovsrec_bridge *bridge;
-};
-
-static char *
-tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id)
-{
-    int i;
-
-    for (i = 0; i < UINT16_MAX; i++) {
-        char *port_name;
-        port_name = xasprintf("ovn-%.6s-%x", chassis_id, i);
-
-        if (!sset_contains(&tc->port_names, port_name)) {
-            return port_name;
-        }
-
-        free(port_name);
-    }
-
-    return NULL;
-}
-
-/*
- * Returns a tunnel-id of the form 'chassis_id'-delimiter-'encap_ip'.
- */
-char *
-encaps_tunnel_id_create(const char *chassis_id, const char *encap_ip)
-{
-    return xasprintf("%s%c%s", chassis_id, OVN_MVTEP_CHASSISID_DELIM,
-                     encap_ip);
-}
-
-/*
- * Parses a 'tunnel_id' of the form <chassis_name><delimiter><IP>.
- * If the 'chassis_id' argument is not NULL the function will allocate memory
- * and store the chassis-id part of the tunnel-id at '*chassis_id'.
- * If the 'encap_ip' argument is not NULL the function will allocate memory
- * and store the encapsulation IP part of the tunnel-id at '*encap_ip'.
- */
-bool
-encaps_tunnel_id_parse(const char *tunnel_id, char **chassis_id,
-                       char **encap_ip)
-{
-    /* Find the delimiter.  Fail if there is no delimiter or if <chassis_name>
-     * or <IP> is the empty string.*/
-    const char *d = strchr(tunnel_id, OVN_MVTEP_CHASSISID_DELIM);
-    if (d == tunnel_id || !d || !d[1]) {
-        return false;
-    }
-
-    if (chassis_id) {
-        *chassis_id = xmemdup0(tunnel_id, d - tunnel_id);
-    }
-    if (encap_ip) {
-        *encap_ip = xstrdup(d + 1);
-    }
-    return true;
-}
-
-/*
- * Returns true if 'tunnel_id' contains 'chassis_id' and, if specified, the
- * given 'encap_ip'. Returns false otherwise.
- */
-bool
-encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
-                       const char *encap_ip)
-{
-    while (*tunnel_id == *chassis_id) {
-        if (!*tunnel_id) {
-            /* 'tunnel_id' and 'chassis_id' are equal strings.  This is a
-             * mismatch because 'tunnel_id' is missing the delimiter and IP. */
-            return false;
-        }
-        tunnel_id++;
-        chassis_id++;
-    }
-
-    /* We found the first byte that disagrees between 'tunnel_id' and
-     * 'chassis_id'.  If we consumed all of 'chassis_id' and arrived at the
-     * delimiter in 'tunnel_id' (and if 'encap_ip' is correct, if it was
-     * supplied), it's a match. */
-    return (*tunnel_id == OVN_MVTEP_CHASSISID_DELIM
-            && *chassis_id == '\0'
-            && (!encap_ip || !strcmp(tunnel_id + 1, encap_ip)));
-}
-
-static void
-tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
-           const char *new_chassis_id, const struct sbrec_encap *encap)
-{
-    struct smap options = SMAP_INITIALIZER(&options);
-    smap_add(&options, "remote_ip", encap->ip);
-    smap_add(&options, "key", "flow");
-    const char *dst_port = smap_get(&encap->options, "dst_port");
-    const char *csum = smap_get(&encap->options, "csum");
-    char *tunnel_entry_id = NULL;
-
-    /*
-     * Since a chassis may have multiple encap-ip, we can't just add the
-     * chassis name as as the "ovn-chassis-id" for the port; we use the
-     * combination of the chassis_name and the encap-ip to identify
-     * a specific tunnel to the chassis.
-     */
-    tunnel_entry_id = encaps_tunnel_id_create(new_chassis_id, encap->ip);
-    if (csum && (!strcmp(csum, "true") || !strcmp(csum, "false"))) {
-        smap_add(&options, "csum", csum);
-    }
-    if (dst_port) {
-        smap_add(&options, "dst_port", dst_port);
-    }
-
-    /* Add auth info if ipsec is enabled. */
-    if (sbg->ipsec) {
-        smap_add(&options, "remote_name", new_chassis_id);
-    }
-
-    /* If there's an existing chassis record that does not need any change,
-     * keep it.  Otherwise, create a new record (if there was an existing
-     * record, the new record will supplant it and encaps_run() will delete
-     * it). */
-    struct chassis_node *chassis = shash_find_data(&tc->chassis,
-                                                   tunnel_entry_id);
-    if (chassis
-        && chassis->port->n_interfaces == 1
-        && !strcmp(chassis->port->interfaces[0]->type, encap->type)
-        && smap_equal(&chassis->port->interfaces[0]->options, &options)) {
-        shash_find_and_delete(&tc->chassis, tunnel_entry_id);
-        free(chassis);
-        goto exit;
-    }
-
-    /* Choose a name for the new port.  If we're replacing an old port, reuse
-     * its name, otherwise generate a new, unique name. */
-    char *port_name = (chassis
-                       ? xstrdup(chassis->port->name)
-                       : tunnel_create_name(tc, new_chassis_id));
-    if (!port_name) {
-        VLOG_WARN("Unable to allocate unique name for '%s' tunnel",
-                  new_chassis_id);
-        goto exit;
-    }
-
-    struct ovsrec_interface *iface = ovsrec_interface_insert(tc->ovs_txn);
-    ovsrec_interface_set_name(iface, port_name);
-    ovsrec_interface_set_type(iface, encap->type);
-    ovsrec_interface_set_options(iface, &options);
-
-    struct ovsrec_port *port = ovsrec_port_insert(tc->ovs_txn);
-    ovsrec_port_set_name(port, port_name);
-    ovsrec_port_set_interfaces(port, &iface, 1);
-    const struct smap id = SMAP_CONST1(&id, "ovn-chassis-id", tunnel_entry_id);
-    ovsrec_port_set_external_ids(port, &id);
-
-    ovsrec_bridge_update_ports_addvalue(tc->br_int, port);
-
-    sset_add_and_free(&tc->port_names, port_name);
-
-exit:
-    free(tunnel_entry_id);
-    smap_destroy(&options);
-}
-
-struct sbrec_encap *
-preferred_encap(const struct sbrec_chassis *chassis_rec)
-{
-    struct sbrec_encap *best_encap = NULL;
-    uint32_t best_type = 0;
-
-    for (int i = 0; i < chassis_rec->n_encaps; i++) {
-        uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type);
-        if (tun_type > best_type) {
-            best_type = tun_type;
-            best_encap = chassis_rec->encaps[i];
-        }
-    }
-
-    return best_encap;
-}
-
-/*
- * For each peer chassis, get a preferred tunnel type and create as many tunnels
- * as there are VTEP of that type (differentiated by remote_ip) on that chassis.
- */
-static int
-chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc)
-{
-    struct sbrec_encap *encap = preferred_encap(chassis_rec);
-    int tuncnt = 0;
-
-    if (!encap) {
-        VLOG_INFO("chassis_tunnel_add: No supported encaps for '%s'", chassis_rec->name);
-        return tuncnt;
-    }
-
-    uint32_t pref_type = get_tunnel_type(encap->type);
-    for (int i = 0; i < chassis_rec->n_encaps; i++) {
-        uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type);
-        if (tun_type != pref_type) {
-            continue;
-        }
-        tunnel_add(tc, sbg, chassis_rec->name, chassis_rec->encaps[i]);
-        tuncnt++;
-    }
-    return tuncnt;
-}
-
-/*
-* Returns true if transport_zones and chassis_rec->transport_zones
-* have at least one common transport zone.
-*/
-static bool
-chassis_tzones_overlap(const struct sset *transport_zones,
-                       const struct sbrec_chassis *chassis_rec)
-{
-    /* If neither Chassis belongs to any transport zones, return true to
-     * form a tunnel between them */
-    if (!chassis_rec->n_transport_zones && sset_is_empty(transport_zones)) {
-        return true;
-    }
-
-    for (int i = 0; i < chassis_rec->n_transport_zones; i++) {
-        if (sset_contains(transport_zones, chassis_rec->transport_zones[i])) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void
-encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
-           const struct ovsrec_bridge_table *bridge_table,
-           const struct ovsrec_bridge *br_int,
-           const struct sbrec_chassis_table *chassis_table,
-           const char *chassis_id,
-           const struct sbrec_sb_global *sbg,
-           const struct sset *transport_zones)
-{
-    if (!ovs_idl_txn || !br_int) {
-        return;
-    }
-
-    const struct sbrec_chassis *chassis_rec;
-    const struct ovsrec_bridge *br;
-
-    struct tunnel_ctx tc = {
-        .chassis = SHASH_INITIALIZER(&tc.chassis),
-        .port_names = SSET_INITIALIZER(&tc.port_names),
-        .br_int = br_int
-    };
-
-    tc.ovs_txn = ovs_idl_txn;
-    ovsdb_idl_txn_add_comment(tc.ovs_txn,
-                              "ovn-controller: modifying OVS tunnels '%s'",
-                              chassis_id);
-
-    /* Collect all port names into tc.port_names.
-     *
-     * Collect all the OVN-created tunnels into tc.tunnel_hmap. */
-    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
-        for (size_t i = 0; i < br->n_ports; i++) {
-            const struct ovsrec_port *port = br->ports[i];
-            sset_add(&tc.port_names, port->name);
-
-            /*
-             * note that the id here is not just the chassis name, but the
-             * combination of <chassis_name><delim><encap_ip>
-             */
-            const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
-            if (id) {
-                if (!shash_find(&tc.chassis, id)) {
-                    struct chassis_node *chassis = xzalloc(sizeof *chassis);
-                    chassis->bridge = br;
-                    chassis->port = port;
-                    shash_add_assert(&tc.chassis, id, chassis);
-                } else {
-                    /* Duplicate port for ovn-chassis-id.  Arbitrarily choose
-                     * to delete this one. */
-                    ovsrec_bridge_update_ports_delvalue(br, port);
-                }
-            }
-        }
-    }
-
-    SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) {
-        if (strcmp(chassis_rec->name, chassis_id)) {
-            /* Create tunnels to the other Chassis belonging to the
-             * same transport zone */
-            if (!chassis_tzones_overlap(transport_zones, chassis_rec)) {
-                VLOG_DBG("Skipping encap creation for Chassis '%s' because "
-                         "it belongs to different transport zones",
-                         chassis_rec->name);
-                continue;
-            }
-
-            if (chassis_tunnel_add(chassis_rec, sbg, &tc) == 0) {
-                VLOG_INFO("Creating encap for '%s' failed", chassis_rec->name);
-                continue;
-            }
-        }
-    }
-
-    /* Delete any existing OVN tunnels that were not still around. */
-    struct shash_node *node, *next_node;
-    SHASH_FOR_EACH_SAFE (node, next_node, &tc.chassis) {
-        struct chassis_node *chassis = node->data;
-        ovsrec_bridge_update_ports_delvalue(chassis->bridge, chassis->port);
-        shash_delete(&tc.chassis, node);
-        free(chassis);
-    }
-    shash_destroy(&tc.chassis);
-    sset_destroy(&tc.port_names);
-}
-
-/* Returns true if the database is all cleaned up, false if more work is
- * required. */
-bool
-encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
-               const struct ovsrec_bridge *br_int)
-{
-    if (!br_int) {
-        return true;
-    }
-
-    /* Delete all the OVS-created tunnels from the integration bridge. */
-    struct ovsrec_port **ports
-        = xmalloc(sizeof *br_int->ports * br_int->n_ports);
-    size_t n = 0;
-    for (size_t i = 0; i < br_int->n_ports; i++) {
-        if (!smap_get(&br_int->ports[i]->external_ids, "ovn-chassis-id")) {
-            ports[n++] = br_int->ports[i];
-        }
-    }
-
-    bool any_changes = n != br_int->n_ports;
-    if (any_changes && ovs_idl_txn) {
-        ovsdb_idl_txn_add_comment(ovs_idl_txn,
-                                  "ovn-controller: destroying tunnels");
-        ovsrec_bridge_verify_ports(br_int);
-        ovsrec_bridge_set_ports(br_int, ports, n);
-    }
-    free(ports);
-
-    return !any_changes;
-}
diff --git a/ovn/controller/encaps.h b/ovn/controller/encaps.h
deleted file mode 100644
index afa41830a..000000000
--- a/ovn/controller/encaps.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (c) 2015 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 OVN_ENCAPS_H
-#define OVN_ENCAPS_H 1
-
-#include <stdbool.h>
-
-struct ovsdb_idl;
-struct ovsdb_idl_txn;
-struct ovsrec_bridge;
-struct ovsrec_bridge_table;
-struct sbrec_chassis_table;
-struct sbrec_sb_global;
-struct ovsrec_open_vswitch_table;
-struct sset;
-
-void encaps_register_ovs_idl(struct ovsdb_idl *);
-void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
-                const struct ovsrec_bridge_table *,
-                const struct ovsrec_bridge *br_int,
-                const struct sbrec_chassis_table *,
-                const char *chassis_id,
-                const struct sbrec_sb_global *,
-                const struct sset *transport_zones);
-
-bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
-                    const struct ovsrec_bridge *br_int);
-
-char *encaps_tunnel_id_create(const char *chassis_id, const char *encap_ip);
-bool  encaps_tunnel_id_parse(const char *tunnel_id, char **chassis_id,
-                             char **encap_ip);
-bool  encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
-                             const char *encap_ip);
-
-#endif /* ovn/encaps.h */
diff --git a/ovn/controller/ha-chassis.c b/ovn/controller/ha-chassis.c
deleted file mode 100644
index 498e5ce5a..000000000
--- a/ovn/controller/ha-chassis.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/* Copyright (c) 2019 Red Hat, 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 "ha-chassis.h"
-#include "lib/sset.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(ha_chassis);
-
-static int
-compare_chassis_prio_(const void *a_, const void *b_)
-{
-    const struct sbrec_ha_chassis *ch_a = a_;
-    const struct sbrec_ha_chassis *ch_b = b_;
-    int prio_diff = ch_b->priority - ch_a->priority;
-    if (!prio_diff) {
-        return strcmp(ch_b->chassis->name, ch_a->chassis->name);
-    }
-    return prio_diff;
-}
-
-/* Returns the ordered HA chassis list in the HA chassis group.
- * Eg. If an HA chassis group has 3 HA chassis
- *   - HA1 - pri 30
- *   - HA2 - pri 40 and
- *   - HA3 - pri 20
- * and the ref_chassis of HA chassis group is set to - C1 and C2.
- *
- * If active_tunnels is NULL, then it returns the ordered list
- *   -  (HA2, HA1, HA3)
- *
- * If active_tunnels is set to - (HA1, HA2, C1, C2) and
- * local_chassis is HA3, then it returns the ordered list
- *  - (HA2, HA1, HA3)
- *
- * If active_tunnels is set to - (HA1, C1, C2) and
- * local_chassis is HA3, then it returns the ordered list
- *  - (HA1, HA3)
- *
- * If active_tunnels is set to - (C1, C2) and
- * local_chassis is HA3, then it returns the ordered list
- *  - (HA3)
- *
- * If active_tunnels is set is empty and local_chassis is HA3,
- * then it returns NULL.
- */
-static struct ha_chassis_ordered *
-get_ordered_ha_chassis_list(const struct sbrec_ha_chassis_group *ha_ch_grp,
-                            const struct sset *active_tunnels,
-                            const struct sbrec_chassis *local_chassis)
-{
-    struct sbrec_ha_chassis *ha_ch_order =
-        xzalloc(sizeof *ha_ch_order * ha_ch_grp->n_ha_chassis);
-
-    size_t n_ha_ch = 0;
-
-    for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
-        if (!ha_ch_grp->ha_chassis[i]->chassis) {
-            continue;
-        }
-
-        /* Don't add it to the list for ordering if it is not active. */
-        if (ha_ch_grp->ha_chassis[i]->chassis != local_chassis &&
-            active_tunnels &&
-            !sset_contains(active_tunnels,
-                           ha_ch_grp->ha_chassis[i]->chassis->name)) {
-            continue;
-        }
-
-        ha_ch_order[n_ha_ch].chassis = ha_ch_grp->ha_chassis[i]->chassis;
-        ha_ch_order[n_ha_ch].priority = ha_ch_grp->ha_chassis[i]->priority;
-        n_ha_ch++;
-    }
-
-    if (!n_ha_ch) {
-        free(ha_ch_order);
-        return NULL;
-    }
-
-    struct ha_chassis_ordered *ordered_ha_ch;
-    if (n_ha_ch == 1) {
-        if (active_tunnels) {
-            /* If n_ha_ch is 1, it means only the local chassis is in the
-            * ha_ch_order list. Check if this local chassis has active
-            * bfd session with any of the referenced chassis. If so,
-            * then the local chassis can be active. Otherwise it can't.
-            * This can happen in the following scenario.
-            * Lets say we have chassis HA1 (prioirty 20) and HA2 (priority 10)
-            * in the ha_chasis_group and compute chassis C1 and C2 are in the
-            * reference chassis list. If HA1 chassis has lost the link and
-            * when this function is called for HA2 we need to consider
-            * HA2 as active since it has active BFD sessions with C1 and C2.
-            * On HA1 chassis, this function won't be called since
-            * active_tunnels set will be empty.
-            * */
-            bool can_local_chassis_be_active = false;
-            for (size_t i = 0; i < ha_ch_grp->n_ref_chassis; i++) {
-                if (sset_contains(active_tunnels,
-                                ha_ch_grp->ref_chassis[i]->name)) {
-                    can_local_chassis_be_active = true;
-                    break;
-                }
-            }
-            if (!can_local_chassis_be_active) {
-                free(ha_ch_order);
-                return NULL;
-            }
-        }
-    } else {
-        qsort(ha_ch_order, n_ha_ch, sizeof *ha_ch_order,
-              compare_chassis_prio_);
-    }
-
-    ordered_ha_ch = xmalloc(sizeof *ordered_ha_ch);
-    ordered_ha_ch->ha_ch = ha_ch_order;
-    ordered_ha_ch->n_ha_ch = n_ha_ch;
-
-    return ordered_ha_ch;
-}
-
-void
-ha_chassis_destroy_ordered(struct ha_chassis_ordered *ordered_ha_ch)
-{
-    if (ordered_ha_ch) {
-        free(ordered_ha_ch->ha_ch);
-        free(ordered_ha_ch);
-    }
-}
-
-
-/* Returns true if the local_chassis is the master of
- * the HA chassis group, false otherwise. */
-bool
-ha_chassis_group_is_active(
-    const struct sbrec_ha_chassis_group *ha_ch_grp,
-    const struct sset *active_tunnels,
-    const struct sbrec_chassis *local_chassis)
-{
-    if (!ha_ch_grp || !ha_ch_grp->n_ha_chassis) {
-        return false;
-    }
-
-    if (ha_ch_grp->n_ha_chassis == 1) {
-        return (ha_ch_grp->ha_chassis[0]->chassis == local_chassis);
-    }
-
-    if (sset_is_empty(active_tunnels)) {
-        /* If active tunnel sset is empty, it means it has lost
-         * connectivity with other chassis. */
-        return false;
-    }
-
-    struct ha_chassis_ordered *ordered_ha_ch =
-        get_ordered_ha_chassis_list(ha_ch_grp, active_tunnels, local_chassis);
-    if (!ordered_ha_ch) {
-        return false;
-    }
-
-    struct sbrec_chassis *active_ch = ordered_ha_ch->ha_ch[0].chassis;
-    ha_chassis_destroy_ordered(ordered_ha_ch);
-
-    return (active_ch == local_chassis);
-}
-
-bool
-ha_chassis_group_contains(
-    const struct sbrec_ha_chassis_group *ha_chassis_grp,
-    const struct sbrec_chassis *chassis)
-{
-    if (ha_chassis_grp && chassis) {
-        for (size_t i = 0; i < ha_chassis_grp->n_ha_chassis; i++) {
-            if (ha_chassis_grp->ha_chassis[i]->chassis == chassis) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-struct ha_chassis_ordered *
-ha_chassis_get_ordered(const struct sbrec_ha_chassis_group *ha_chassis_grp)
-{
-    if (!ha_chassis_grp || !ha_chassis_grp->n_ha_chassis) {
-        return NULL;
-    }
-
-    return get_ordered_ha_chassis_list(ha_chassis_grp, NULL, NULL);
-}
diff --git a/ovn/controller/ha-chassis.h b/ovn/controller/ha-chassis.h
deleted file mode 100644
index 3768c2a5c..000000000
--- a/ovn/controller/ha-chassis.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* Copyright (c) 2019 Red Hat, 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 OVN_HA_CHASSIS_H
-#define OVN_HA_CHASSIS_H 1
-
-#include <stdint.h>
-#include "openvswitch/hmap.h"
-#include "openvswitch/list.h"
-
-struct sbrec_chassis;
-struct sbrec_ha_chassis_group;
-struct sset;
-
-struct ha_chassis_ordered {
-    struct sbrec_ha_chassis *ha_ch;
-    size_t n_ha_ch;
-};
-
-/* Returns true if the local chassis is the active gateway among a set
- * of gateway_chassis.  Return false if the local chassis is currently a
- * backup in a set of multiple gateway_chassis. */
-bool ha_chassis_group_is_active(
-    const struct sbrec_ha_chassis_group *ha_chassis_grp,
-    const struct sset *active_tunnels,
-    const struct sbrec_chassis *local_chassis);
-
-bool ha_chassis_group_contains(
-    const struct sbrec_ha_chassis_group *ha_chassis_grp,
-    const struct sbrec_chassis *chassis);
-
-struct ha_chassis_ordered *ha_chassis_get_ordered(
-    const struct sbrec_ha_chassis_group *ha_chassis_grp);
-
-void ha_chassis_destroy_ordered(
-    struct ha_chassis_ordered *ordered_ha_ch);
-
-#endif /* OVN_HA_CHASSIS_H */
diff --git a/ovn/controller/ip-mcast.c b/ovn/controller/ip-mcast.c
deleted file mode 100644
index ef36be2ca..000000000
--- a/ovn/controller/ip-mcast.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/* Copyright (c) 2019, Red Hat, 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 "ip-mcast.h"
-#include "lport.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-/*
- * Used for (faster) updating of IGMP_Group ports.
- */
-struct igmp_group_port {
-    struct hmap_node hmap_node;
-    const struct sbrec_port_binding *port;
-};
-
-struct ovsdb_idl_index *
-igmp_group_index_create(struct ovsdb_idl *idl)
-{
-    const struct ovsdb_idl_index_column cols[] = {
-        { .column = &sbrec_igmp_group_col_address },
-        { .column = &sbrec_igmp_group_col_datapath },
-        { .column = &sbrec_igmp_group_col_chassis },
-    };
-
-    return ovsdb_idl_index_create(idl, cols, ARRAY_SIZE(cols));
-}
-
-/* Looks up an IGMP group based on an IPv4 (mapped in IPv6) or IPv6 'address'
- * and 'datapath'.
- */
-const struct sbrec_igmp_group *
-igmp_group_lookup(struct ovsdb_idl_index *igmp_groups,
-                  const struct in6_addr *address,
-                  const struct sbrec_datapath_binding *datapath,
-                  const struct sbrec_chassis *chassis)
-{
-    char addr_str[INET6_ADDRSTRLEN];
-
-    if (!ipv6_string_mapped(addr_str, address)) {
-        return NULL;
-    }
-
-    struct sbrec_igmp_group *target =
-        sbrec_igmp_group_index_init_row(igmp_groups);
-
-    sbrec_igmp_group_index_set_address(target, addr_str);
-    sbrec_igmp_group_index_set_datapath(target, datapath);
-    sbrec_igmp_group_index_set_chassis(target, chassis);
-
-    const struct sbrec_igmp_group *g =
-        sbrec_igmp_group_index_find(igmp_groups, target);
-    sbrec_igmp_group_index_destroy_row(target);
-    return g;
-}
-
-/* Creates and returns a new IGMP group based on an IPv4 (mapped in IPv6) or
- * IPv6 'address', 'datapath' and 'chassis'.
- */
-struct sbrec_igmp_group *
-igmp_group_create(struct ovsdb_idl_txn *idl_txn,
-                  const struct in6_addr *address,
-                  const struct sbrec_datapath_binding *datapath,
-                  const struct sbrec_chassis *chassis)
-{
-    char addr_str[INET6_ADDRSTRLEN];
-
-    if (!ipv6_string_mapped(addr_str, address)) {
-        return NULL;
-    }
-
-    struct sbrec_igmp_group *g = sbrec_igmp_group_insert(idl_txn);
-
-    sbrec_igmp_group_set_address(g, addr_str);
-    sbrec_igmp_group_set_datapath(g, datapath);
-    sbrec_igmp_group_set_chassis(g, chassis);
-
-    return g;
-}
-
-void
-igmp_group_update_ports(const struct sbrec_igmp_group *g,
-                        struct ovsdb_idl_index *datapaths,
-                        struct ovsdb_idl_index *port_bindings,
-                        const struct mcast_snooping *ms OVS_UNUSED,
-                        const struct mcast_group *mc_group)
-    OVS_REQ_RDLOCK(ms->rwlock)
-{
-    struct igmp_group_port *old_ports_storage =
-        (g->n_ports ? xmalloc(g->n_ports * sizeof *old_ports_storage) : NULL);
-
-    struct hmap old_ports = HMAP_INITIALIZER(&old_ports);
-
-    for (size_t i = 0; i < g->n_ports; i++) {
-        struct igmp_group_port *old_port = &old_ports_storage[i];
-
-        old_port->port = g->ports[i];
-        hmap_insert(&old_ports, &old_port->hmap_node,
-                    old_port->port->tunnel_key);
-    }
-
-    struct mcast_group_bundle *bundle;
-    uint64_t dp_key = g->datapath->tunnel_key;
-
-    LIST_FOR_EACH (bundle, bundle_node, &mc_group->bundle_lru) {
-        uint32_t port_key = (uintptr_t)bundle->port;
-        const struct sbrec_port_binding *sbrec_port =
-            lport_lookup_by_key(datapaths, port_bindings, dp_key, port_key);
-        if (!sbrec_port) {
-            continue;
-        }
-
-        struct hmap_node *node = hmap_first_with_hash(&old_ports, port_key);
-        if (!node) {
-            sbrec_igmp_group_update_ports_addvalue(g, sbrec_port);
-        } else {
-            hmap_remove(&old_ports, node);
-        }
-    }
-
-    struct igmp_group_port *igmp_port;
-    HMAP_FOR_EACH_POP (igmp_port, hmap_node, &old_ports) {
-        sbrec_igmp_group_update_ports_delvalue(g, igmp_port->port);
-    }
-
-    free(old_ports_storage);
-    hmap_destroy(&old_ports);
-}
-
-void
-igmp_group_delete(const struct sbrec_igmp_group *g)
-{
-    sbrec_igmp_group_delete(g);
-}
-
-bool
-igmp_group_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                   struct ovsdb_idl_index *igmp_groups)
-{
-    const struct sbrec_igmp_group *g;
-
-    if (!ovnsb_idl_txn) {
-        return true;
-    }
-
-    SBREC_IGMP_GROUP_FOR_EACH_BYINDEX (g, igmp_groups) {
-        igmp_group_delete(g);
-    }
-
-    return true;
-}
diff --git a/ovn/controller/ip-mcast.h b/ovn/controller/ip-mcast.h
deleted file mode 100644
index 6014f43d5..000000000
--- a/ovn/controller/ip-mcast.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* Copyright (c) 2019, Red Hat, 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 OVN_IP_MCAST_H
-#define OVN_IP_MCAST_H 1
-
-#include "mcast-snooping.h"
-
-struct ovsdb_idl;
-struct ovsdb_idl_txn;
-
-struct sbrec_chassis;
-struct sbrec_datapath_binding;
-
-struct ovsdb_idl_index *igmp_group_index_create(struct ovsdb_idl *);
-const struct sbrec_igmp_group *igmp_group_lookup(
-    struct ovsdb_idl_index *igmp_groups,
-    const struct in6_addr *address,
-    const struct sbrec_datapath_binding *datapath,
-    const struct sbrec_chassis *chassis);
-
-struct sbrec_igmp_group *igmp_group_create(
-    struct ovsdb_idl_txn *idl_txn,
-    const struct in6_addr *address,
-    const struct sbrec_datapath_binding *datapath,
-    const struct sbrec_chassis *chassis);
-
-void igmp_group_update_ports(const struct sbrec_igmp_group *g,
-                             struct ovsdb_idl_index *datapaths,
-                             struct ovsdb_idl_index *port_bindings,
-                             const struct mcast_snooping *ms,
-                             const struct mcast_group *mc_group)
-    OVS_REQ_RDLOCK(ms->rwlock);
-
-void igmp_group_delete(const struct sbrec_igmp_group *g);
-
-bool igmp_group_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                        struct ovsdb_idl_index *igmp_groups);
-
-#endif /* ovn/controller/ip-mcast.h */
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
deleted file mode 100644
index 1aafafb33..000000000
--- a/ovn/controller/lflow.c
+++ /dev/null
@@ -1,898 +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 "lflow.h"
-#include "coverage.h"
-#include "ha-chassis.h"
-#include "lport.h"
-#include "ofctrl.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/vlog.h"
-#include "ovn-controller.h"
-#include "ovn/actions.h"
-#include "ovn/expr.h"
-#include "ovn/lib/ovn-l7.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/extend-table.h"
-#include "packets.h"
-#include "physical.h"
-#include "simap.h"
-#include "sset.h"
-
-VLOG_DEFINE_THIS_MODULE(lflow);
-
-COVERAGE_DEFINE(lflow_run);
-
-/* Symbol table. */
-
-/* Contains "struct expr_symbol"s for fields supported by OVN lflows. */
-static struct shash symtab;
-
-void
-lflow_init(void)
-{
-    ovn_init_symtab(&symtab);
-}
-
-struct lookup_port_aux {
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath;
-    struct ovsdb_idl_index *sbrec_port_binding_by_name;
-    const struct sbrec_datapath_binding *dp;
-};
-
-struct condition_aux {
-    struct ovsdb_idl_index *sbrec_port_binding_by_name;
-    const struct sbrec_chassis *chassis;
-    const struct sset *active_tunnels;
-};
-
-static bool consider_logical_flow(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_logical_flow *,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *,
-    struct hmap *dhcp_opts,
-    struct hmap *dhcpv6_opts,
-    struct hmap *nd_ra_opts,
-    struct controller_event_options *controller_event_opts,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *lfrr,
-    uint32_t *conj_id_ofs);
-
-static bool
-lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
-{
-    const struct lookup_port_aux *aux = aux_;
-
-    const struct sbrec_port_binding *pb
-        = lport_lookup_by_name(aux->sbrec_port_binding_by_name, port_name);
-    if (pb && pb->datapath == aux->dp) {
-        *portp = pb->tunnel_key;
-        return true;
-    }
-
-    const struct sbrec_multicast_group *mg = mcgroup_lookup_by_dp_name(
-        aux->sbrec_multicast_group_by_name_datapath, aux->dp, port_name);
-    if (mg) {
-        *portp = mg->tunnel_key;
-        return true;
-    }
-
-    return false;
-}
-
-static bool
-is_chassis_resident_cb(const void *c_aux_, const char *port_name)
-{
-    const struct condition_aux *c_aux = c_aux_;
-
-    const struct sbrec_port_binding *pb
-        = lport_lookup_by_name(c_aux->sbrec_port_binding_by_name, port_name);
-    if (!pb) {
-        return false;
-    }
-    if (strcmp(pb->type, "chassisredirect")) {
-        /* for non-chassisredirect ports */
-        return pb->chassis && pb->chassis == c_aux->chassis;
-    } else {
-        if (ha_chassis_group_contains(pb->ha_chassis_group,
-                                      c_aux->chassis)) {
-            bool active = ha_chassis_group_is_active(pb->ha_chassis_group,
-                                                     c_aux->active_tunnels,
-                                                     c_aux->chassis);
-            return active;
-        }
-        return false;
-    }
-}
-
-static bool
-is_switch(const struct sbrec_datapath_binding *ldp)
-{
-    return smap_get(&ldp->external_ids, "logical-switch") != NULL;
-
-}
-
-void
-lflow_resource_init(struct lflow_resource_ref *lfrr)
-{
-    hmap_init(&lfrr->ref_lflow_table);
-    hmap_init(&lfrr->lflow_ref_table);
-}
-
-void
-lflow_resource_destroy(struct lflow_resource_ref *lfrr)
-{
-    struct ref_lflow_node *rlfn, *rlfn_next;
-    HMAP_FOR_EACH_SAFE (rlfn, rlfn_next, node, &lfrr->ref_lflow_table) {
-        free(rlfn->ref_name);
-        struct lflow_ref_list_node *lrln, *next;
-        LIST_FOR_EACH_SAFE (lrln, next, ref_list, &rlfn->ref_lflow_head) {
-            ovs_list_remove(&lrln->ref_list);
-            ovs_list_remove(&lrln->lflow_list);
-            free(lrln);
-        }
-        hmap_remove(&lfrr->ref_lflow_table, &rlfn->node);
-        free(rlfn);
-    }
-    hmap_destroy(&lfrr->ref_lflow_table);
-
-    struct lflow_ref_node *lfrn, *lfrn_next;
-    HMAP_FOR_EACH_SAFE (lfrn, lfrn_next, node, &lfrr->lflow_ref_table) {
-        hmap_remove(&lfrr->lflow_ref_table, &lfrn->node);
-        free(lfrn);
-    }
-    hmap_destroy(&lfrr->lflow_ref_table);
-}
-
-void
-lflow_resource_clear(struct lflow_resource_ref *lfrr)
-{
-    lflow_resource_destroy(lfrr);
-    lflow_resource_init(lfrr);
-}
-
-static struct ref_lflow_node*
-ref_lflow_lookup(struct hmap *ref_lflow_table,
-                 enum ref_type type, const char *ref_name)
-{
-    struct ref_lflow_node *rlfn;
-
-    HMAP_FOR_EACH_WITH_HASH (rlfn, node, hash_string(ref_name, type),
-                             ref_lflow_table) {
-        if (rlfn->type == type && !strcmp(rlfn->ref_name, ref_name)) {
-            return rlfn;
-        }
-    }
-    return NULL;
-}
-
-static struct lflow_ref_node*
-lflow_ref_lookup(struct hmap *lflow_ref_table,
-                 const struct uuid *lflow_uuid)
-{
-    struct lflow_ref_node *lfrn;
-
-    HMAP_FOR_EACH_WITH_HASH (lfrn, node, uuid_hash(lflow_uuid),
-                             lflow_ref_table) {
-        if (uuid_equals(&lfrn->lflow_uuid, lflow_uuid)) {
-            return lfrn;
-        }
-    }
-    return NULL;
-}
-
-static void
-lflow_resource_add(struct lflow_resource_ref *lfrr, enum ref_type type,
-                   const char *ref_name, const struct uuid *lflow_uuid)
-{
-    struct ref_lflow_node *rlfn = ref_lflow_lookup(&lfrr->ref_lflow_table,
-                                                   type, ref_name);
-    if (!rlfn) {
-        rlfn = xzalloc(sizeof *rlfn);
-        rlfn->node.hash = hash_string(ref_name, type);
-        rlfn->type = type;
-        rlfn->ref_name = xstrdup(ref_name);
-        ovs_list_init(&rlfn->ref_lflow_head);
-        hmap_insert(&lfrr->ref_lflow_table, &rlfn->node, rlfn->node.hash);
-    }
-
-    struct lflow_ref_node *lfrn = lflow_ref_lookup(&lfrr->lflow_ref_table,
-                                                   lflow_uuid);
-    if (!lfrn) {
-        lfrn = xzalloc(sizeof *lfrn);
-        lfrn->node.hash = uuid_hash(lflow_uuid);
-        lfrn->lflow_uuid = *lflow_uuid;
-        ovs_list_init(&lfrn->lflow_ref_head);
-        hmap_insert(&lfrr->lflow_ref_table, &lfrn->node, lfrn->node.hash);
-    }
-
-    struct lflow_ref_list_node *lrln = xzalloc(sizeof *lrln);
-    lrln->type = type;
-    lrln->ref_name = xstrdup(ref_name);
-    lrln->lflow_uuid = *lflow_uuid;
-    ovs_list_push_back(&rlfn->ref_lflow_head, &lrln->ref_list);
-    ovs_list_push_back(&lfrn->lflow_ref_head, &lrln->lflow_list);
-}
-
-static void
-lflow_resource_destroy_lflow(struct lflow_resource_ref *lfrr,
-                            const struct uuid *lflow_uuid)
-{
-    struct lflow_ref_node *lfrn = lflow_ref_lookup(&lfrr->lflow_ref_table,
-                                                   lflow_uuid);
-    if (!lfrn) {
-        return;
-    }
-
-    hmap_remove(&lfrr->lflow_ref_table, &lfrn->node);
-    struct lflow_ref_list_node *lrln, *next;
-    LIST_FOR_EACH_SAFE (lrln, next, lflow_list, &lfrn->lflow_ref_head) {
-        ovs_list_remove(&lrln->ref_list);
-        ovs_list_remove(&lrln->lflow_list);
-        free(lrln);
-    }
-    free(lfrn);
-}
-
-/* Adds the logical flows from the Logical_Flow table to flow tables. */
-static void
-add_logical_flows(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_dhcp_options_table *dhcp_options_table,
-    const struct sbrec_dhcpv6_options_table *dhcpv6_options_table,
-    const struct sbrec_logical_flow_table *logical_flow_table,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *chassis,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *flow_table,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *lfrr,
-    uint32_t *conj_id_ofs)
-{
-    const struct sbrec_logical_flow *lflow;
-
-    struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
-    struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts);
-    const struct sbrec_dhcp_options *dhcp_opt_row;
-    SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) {
-        dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code,
-                     dhcp_opt_row->type);
-    }
-
-
-    const struct sbrec_dhcpv6_options *dhcpv6_opt_row;
-    SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH (dhcpv6_opt_row,
-                                         dhcpv6_options_table) {
-       dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code,
-                    dhcpv6_opt_row->type);
-    }
-
-    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
-    nd_ra_opts_init(&nd_ra_opts);
-
-    struct controller_event_options controller_event_opts;
-    controller_event_opts_init(&controller_event_opts);
-
-    SBREC_LOGICAL_FLOW_TABLE_FOR_EACH (lflow, logical_flow_table) {
-        if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath,
-                                   sbrec_port_binding_by_name,
-                                   lflow, local_datapaths,
-                                   chassis, &dhcp_opts, &dhcpv6_opts,
-                                   &nd_ra_opts, &controller_event_opts,
-                                   addr_sets, port_groups,
-                                   active_tunnels, local_lport_ids,
-                                   flow_table, group_table, meter_table,
-                                   lfrr, conj_id_ofs)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
-            VLOG_ERR_RL(&rl, "Conjunction id overflow when processing lflow "
-                        UUID_FMT, UUID_ARGS(&lflow->header_.uuid));
-        }
-    }
-
-    dhcp_opts_destroy(&dhcp_opts);
-    dhcp_opts_destroy(&dhcpv6_opts);
-    nd_ra_opts_destroy(&nd_ra_opts);
-    controller_event_opts_destroy(&controller_event_opts);
-}
-
-bool
-lflow_handle_changed_flows(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_dhcp_options_table *dhcp_options_table,
-    const struct sbrec_dhcpv6_options_table *dhcpv6_options_table,
-    const struct sbrec_logical_flow_table *logical_flow_table,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *chassis,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *flow_table,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *lfrr,
-    uint32_t *conj_id_ofs)
-{
-    bool ret = true;
-    const struct sbrec_logical_flow *lflow;
-
-    struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
-    struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts);
-    const struct sbrec_dhcp_options *dhcp_opt_row;
-    SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) {
-        dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code,
-                     dhcp_opt_row->type);
-    }
-
-
-    const struct sbrec_dhcpv6_options *dhcpv6_opt_row;
-    SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH (dhcpv6_opt_row,
-                                         dhcpv6_options_table) {
-       dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code,
-                    dhcpv6_opt_row->type);
-    }
-
-    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
-    nd_ra_opts_init(&nd_ra_opts);
-
-    /* Handle removed flows first, and then other flows, so that when
-     * the flows being added and removed have same match conditions
-     * can be processed in the proper order */
-    SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, logical_flow_table) {
-        /* Remove any flows that should be removed. */
-        if (sbrec_logical_flow_is_deleted(lflow)) {
-            VLOG_DBG("handle deleted lflow "UUID_FMT,
-                     UUID_ARGS(&lflow->header_.uuid));
-            ofctrl_remove_flows(flow_table, &lflow->header_.uuid);
-            /* Delete entries from lflow resource reference. */
-            lflow_resource_destroy_lflow(lfrr, &lflow->header_.uuid);
-        }
-    }
-
-    struct controller_event_options controller_event_opts;
-    controller_event_opts_init(&controller_event_opts);
-
-    SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, logical_flow_table) {
-        if (!sbrec_logical_flow_is_deleted(lflow)) {
-            /* Now, add/modify existing flows. If the logical
-             * flow is a modification, just remove the flows
-             * for this row, and then add new flows. */
-            if (!sbrec_logical_flow_is_new(lflow)) {
-                VLOG_DBG("handle updated lflow "UUID_FMT,
-                         UUID_ARGS(&lflow->header_.uuid));
-                ofctrl_remove_flows(flow_table, &lflow->header_.uuid);
-                /* Delete entries from lflow resource reference. */
-                lflow_resource_destroy_lflow(lfrr, &lflow->header_.uuid);
-            }
-            VLOG_DBG("handle new lflow "UUID_FMT,
-                     UUID_ARGS(&lflow->header_.uuid));
-            if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath,
-                                       sbrec_port_binding_by_name,
-                                       lflow, local_datapaths,
-                                       chassis, &dhcp_opts, &dhcpv6_opts,
-                                       &nd_ra_opts, &controller_event_opts,
-                                       addr_sets, port_groups,
-                                       active_tunnels, local_lport_ids,
-                                       flow_table, group_table, meter_table,
-                                       lfrr, conj_id_ofs)) {
-                ret = false;
-                break;
-            }
-        }
-    }
-    dhcp_opts_destroy(&dhcp_opts);
-    dhcp_opts_destroy(&dhcpv6_opts);
-    nd_ra_opts_destroy(&nd_ra_opts);
-    controller_event_opts_destroy(&controller_event_opts);
-    return ret;
-}
-
-bool
-lflow_handle_changed_ref(
-    enum ref_type ref_type,
-    const char *ref_name,
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_dhcp_options_table *dhcp_options_table,
-    const struct sbrec_dhcpv6_options_table *dhcpv6_options_table,
-    const struct sbrec_logical_flow_table *logical_flow_table,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *chassis,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *flow_table,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *lfrr,
-    uint32_t *conj_id_ofs,
-    bool *changed)
-{
-    struct ref_lflow_node *rlfn = ref_lflow_lookup(&lfrr->ref_lflow_table,
-                                                   ref_type, ref_name);
-    if (!rlfn) {
-        *changed = false;
-        return true;
-    }
-    VLOG_DBG("Handle changed lflow reference for resource type: %d,"
-             " name: %s.", ref_type, ref_name);
-    *changed = false;
-    bool ret = true;
-
-    hmap_remove(&lfrr->ref_lflow_table, &rlfn->node);
-
-    struct lflow_ref_list_node *lrln, *next;
-    /* Detach the rlfn->ref_lflow_head nodes from the lfrr table and clean
-     * up all other nodes related to the lflows that uses the resource,
-     * so that the old nodes won't interfere with updating the lfrr table
-     * when reparsing the lflows. */
-    LIST_FOR_EACH (lrln, ref_list, &rlfn->ref_lflow_head) {
-        ovs_list_remove(&lrln->lflow_list);
-        lflow_resource_destroy_lflow(lfrr, &lrln->lflow_uuid);
-    }
-
-    struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
-    struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts);
-    const struct sbrec_dhcp_options *dhcp_opt_row;
-    SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) {
-        dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code,
-                     dhcp_opt_row->type);
-    }
-
-    const struct sbrec_dhcpv6_options *dhcpv6_opt_row;
-    SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH(dhcpv6_opt_row, dhcpv6_options_table) {
-       dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code,
-                    dhcpv6_opt_row->type);
-    }
-
-    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
-    nd_ra_opts_init(&nd_ra_opts);
-
-    struct controller_event_options controller_event_opts;
-    controller_event_opts_init(&controller_event_opts);
-
-    /* Re-parse the related lflows. */
-    LIST_FOR_EACH (lrln, ref_list, &rlfn->ref_lflow_head) {
-        const struct sbrec_logical_flow *lflow =
-            sbrec_logical_flow_table_get_for_uuid(logical_flow_table,
-                                                  &lrln->lflow_uuid);
-        if (!lflow) {
-            VLOG_DBG("Reprocess lflow "UUID_FMT" for resource type: %d,"
-                     " name: %s - not found.",
-                     UUID_ARGS(&lrln->lflow_uuid),
-                     ref_type, ref_name);
-            continue;
-        }
-        VLOG_DBG("Reprocess lflow "UUID_FMT" for resource type: %d,"
-                 " name: %s.",
-                 UUID_ARGS(&lrln->lflow_uuid),
-                 ref_type, ref_name);
-        ofctrl_remove_flows(flow_table, &lrln->lflow_uuid);
-
-        if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath,
-                                   sbrec_port_binding_by_name,
-                                   lflow, local_datapaths,
-                                   chassis, &dhcp_opts, &dhcpv6_opts,
-                                   &nd_ra_opts, &controller_event_opts,
-                                   addr_sets, port_groups,
-                                   active_tunnels, local_lport_ids,
-                                   flow_table, group_table, meter_table,
-                                   lfrr, conj_id_ofs)) {
-            ret = false;
-            break;
-        }
-        *changed = true;
-    }
-
-    LIST_FOR_EACH_SAFE (lrln, next, ref_list, &rlfn->ref_lflow_head) {
-        ovs_list_remove(&lrln->ref_list);
-        free(lrln);
-    }
-    free(rlfn);
-
-    dhcp_opts_destroy(&dhcp_opts);
-    dhcp_opts_destroy(&dhcpv6_opts);
-    nd_ra_opts_destroy(&nd_ra_opts);
-    controller_event_opts_destroy(&controller_event_opts);
-    return ret;
-}
-
-static bool
-update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs)
-{
-    if (*conj_id_ofs + n_conjs < *conj_id_ofs) {
-        /* overflow */
-        return false;
-    }
-    *conj_id_ofs += n_conjs;
-    return true;
-}
-
-static bool
-consider_logical_flow(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_logical_flow *lflow,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *chassis,
-    struct hmap *dhcp_opts,
-    struct hmap *dhcpv6_opts,
-    struct hmap *nd_ra_opts,
-    struct controller_event_options *controller_event_opts,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *flow_table,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *lfrr,
-    uint32_t *conj_id_ofs)
-{
-    /* Determine translation of logical table IDs to physical table IDs. */
-    bool ingress = !strcmp(lflow->pipeline, "ingress");
-
-    const struct sbrec_datapath_binding *ldp = lflow->logical_datapath;
-    if (!ldp) {
-        VLOG_DBG("lflow "UUID_FMT" has no datapath binding, skip",
-                 UUID_ARGS(&lflow->header_.uuid));
-        return true;
-    }
-    if (!get_local_datapath(local_datapaths, ldp->tunnel_key)) {
-        VLOG_DBG("lflow "UUID_FMT" is not for local datapath, skip",
-                 UUID_ARGS(&lflow->header_.uuid));
-        return true;
-    }
-
-    /* Determine translation of logical table IDs to physical table IDs. */
-    uint8_t first_ptable = (ingress
-                            ? OFTABLE_LOG_INGRESS_PIPELINE
-                            : OFTABLE_LOG_EGRESS_PIPELINE);
-    uint8_t ptable = first_ptable + lflow->table_id;
-    uint8_t output_ptable = (ingress
-                             ? OFTABLE_REMOTE_OUTPUT
-                             : OFTABLE_SAVE_INPORT);
-
-    /* Parse OVN logical actions.
-     *
-     * XXX Deny changes to 'outport' in egress pipeline. */
-    uint64_t ovnacts_stub[1024 / 8];
-    struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(ovnacts_stub);
-    struct ovnact_parse_params pp = {
-        .symtab = &symtab,
-        .dhcp_opts = dhcp_opts,
-        .dhcpv6_opts = dhcpv6_opts,
-        .nd_ra_opts = nd_ra_opts,
-        .controller_event_opts = controller_event_opts,
-
-        .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
-        .n_tables = LOG_PIPELINE_LEN,
-        .cur_ltable = lflow->table_id,
-    };
-    struct expr *prereqs;
-    char *error;
-
-    error = ovnacts_parse_string(lflow->actions, &pp, &ovnacts, &prereqs);
-    if (error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s",
-                     lflow->actions, error);
-        free(error);
-        ovnacts_free(ovnacts.data, ovnacts.size);
-        ofpbuf_uninit(&ovnacts);
-        return true;
-    }
-
-    /* Translate OVN match into table of OpenFlow matches. */
-    struct hmap matches;
-    struct expr *expr;
-
-    struct sset addr_sets_ref = SSET_INITIALIZER(&addr_sets_ref);
-    expr = expr_parse_string(lflow->match, &symtab, addr_sets, port_groups,
-                             &addr_sets_ref, &error);
-    const char *addr_set_name;
-    SSET_FOR_EACH (addr_set_name, &addr_sets_ref) {
-        lflow_resource_add(lfrr, REF_TYPE_ADDRSET, addr_set_name,
-                           &lflow->header_.uuid);
-    }
-    sset_destroy(&addr_sets_ref);
-
-    if (!error) {
-        if (prereqs) {
-            expr = expr_combine(EXPR_T_AND, expr, prereqs);
-            prereqs = NULL;
-        }
-        expr = expr_annotate(expr, &symtab, &error);
-    }
-    if (error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s",
-                     lflow->match, error);
-        expr_destroy(prereqs);
-        free(error);
-        ovnacts_free(ovnacts.data, ovnacts.size);
-        ofpbuf_uninit(&ovnacts);
-        return true;
-    }
-
-    struct lookup_port_aux aux = {
-        .sbrec_multicast_group_by_name_datapath
-            = sbrec_multicast_group_by_name_datapath,
-        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
-        .dp = lflow->logical_datapath
-    };
-    struct condition_aux cond_aux = {
-        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
-        .chassis = chassis,
-        .active_tunnels = active_tunnels,
-    };
-    expr = expr_simplify(expr, is_chassis_resident_cb, &cond_aux);
-    expr = expr_normalize(expr);
-    uint32_t n_conjs = expr_to_matches(expr, lookup_port_cb, &aux,
-                                       &matches);
-    expr_destroy(expr);
-
-    if (hmap_is_empty(&matches)) {
-        VLOG_DBG("lflow "UUID_FMT" matches are empty, skip",
-                 UUID_ARGS(&lflow->header_.uuid));
-        ovnacts_free(ovnacts.data, ovnacts.size);
-        ofpbuf_uninit(&ovnacts);
-        expr_matches_destroy(&matches);
-        return true;
-    }
-
-    /* Encode OVN logical actions into OpenFlow. */
-    uint64_t ofpacts_stub[1024 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-    struct ovnact_encode_params ep = {
-        .lookup_port = lookup_port_cb,
-        .aux = &aux,
-        .is_switch = is_switch(ldp),
-        .group_table = group_table,
-        .meter_table = meter_table,
-        .lflow_uuid = lflow->header_.uuid,
-
-        .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
-        .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
-        .egress_ptable = OFTABLE_LOG_EGRESS_PIPELINE,
-        .output_ptable = output_ptable,
-        .mac_bind_ptable = OFTABLE_MAC_BINDING,
-    };
-    ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
-    ovnacts_free(ovnacts.data, ovnacts.size);
-    ofpbuf_uninit(&ovnacts);
-
-    /* Prepare the OpenFlow matches for adding to the flow table. */
-    struct expr_match *m;
-    HMAP_FOR_EACH (m, hmap_node, &matches) {
-        match_set_metadata(&m->match,
-                           htonll(lflow->logical_datapath->tunnel_key));
-        if (m->match.wc.masks.conj_id) {
-            m->match.flow.conj_id += *conj_id_ofs;
-        }
-        if (is_switch(ldp)) {
-            unsigned int reg_index
-                = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0;
-            int64_t port_id = m->match.flow.regs[reg_index];
-            if (port_id) {
-                int64_t dp_id = lflow->logical_datapath->tunnel_key;
-                char buf[16];
-                snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64, dp_id, port_id);
-                if (!sset_contains(local_lport_ids, buf)) {
-                    VLOG_DBG("lflow "UUID_FMT
-                             " port %s in match is not local, skip",
-                             UUID_ARGS(&lflow->header_.uuid),
-                             buf);
-                    continue;
-                }
-            }
-        }
-        if (!m->n) {
-            ofctrl_add_flow(flow_table, ptable, lflow->priority,
-                            lflow->header_.uuid.parts[0], &m->match, &ofpacts,
-                            &lflow->header_.uuid);
-        } else {
-            uint64_t conj_stubs[64 / 8];
-            struct ofpbuf conj;
-
-            ofpbuf_use_stub(&conj, conj_stubs, sizeof conj_stubs);
-            for (int i = 0; i < m->n; i++) {
-                const struct cls_conjunction *src = &m->conjunctions[i];
-                struct ofpact_conjunction *dst;
-
-                dst = ofpact_put_CONJUNCTION(&conj);
-                dst->id = src->id + *conj_id_ofs;
-                dst->clause = src->clause;
-                dst->n_clauses = src->n_clauses;
-            }
-            ofctrl_add_flow(flow_table, ptable, lflow->priority, 0, &m->match,
-                            &conj, &lflow->header_.uuid);
-            ofpbuf_uninit(&conj);
-        }
-    }
-
-    /* Clean up. */
-    expr_matches_destroy(&matches);
-    ofpbuf_uninit(&ofpacts);
-    return update_conj_id_ofs(conj_id_ofs, n_conjs);
-}
-
-static void
-put_load(const uint8_t *data, size_t len,
-         enum mf_field_id dst, int ofs, int n_bits,
-         struct ofpbuf *ofpacts)
-{
-    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
-                                                       mf_from_id(dst), NULL,
-                                                       NULL);
-    bitwise_copy(data, len, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
-    bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
-}
-
-static void
-consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                       const struct sbrec_mac_binding *b,
-                       struct ovn_desired_flow_table *flow_table)
-{
-    const struct sbrec_port_binding *pb
-        = lport_lookup_by_name(sbrec_port_binding_by_name, b->logical_port);
-    if (!pb) {
-        return;
-    }
-
-    struct eth_addr mac;
-    if (!eth_addr_from_string(b->mac, &mac)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "bad 'mac' %s", b->mac);
-        return;
-    }
-
-    struct match match = MATCH_CATCHALL_INITIALIZER;
-    if (strchr(b->ip, '.')) {
-        ovs_be32 ip;
-        if (!ip_parse(b->ip, &ip)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
-            return;
-        }
-        match_set_reg(&match, 0, ntohl(ip));
-    } else {
-        struct in6_addr ip6;
-        if (!ipv6_parse(b->ip, &ip6)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
-            return;
-        }
-        ovs_be128 value;
-        memcpy(&value, &ip6, sizeof(value));
-        match_set_xxreg(&match, 0, ntoh128(value));
-    }
-
-    match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
-
-    uint64_t stub[1024 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
-    put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts,
-                    &b->header_.uuid);
-    ofpbuf_uninit(&ofpacts);
-}
-
-/* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN
- * southbound database. */
-static void
-add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                   const struct sbrec_mac_binding_table *mac_binding_table,
-                   struct ovn_desired_flow_table *flow_table)
-{
-    const struct sbrec_mac_binding *b;
-    SBREC_MAC_BINDING_TABLE_FOR_EACH (b, mac_binding_table) {
-        consider_neighbor_flow(sbrec_port_binding_by_name, b, flow_table);
-    }
-}
-
-/* Handles neighbor changes in mac_binding table. */
-void
-lflow_handle_changed_neighbors(
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_mac_binding_table *mac_binding_table,
-    struct ovn_desired_flow_table *flow_table)
-{
-
-    const struct sbrec_mac_binding *mb;
-    /* Handle deleted mac_bindings first, to avoid *duplicated flow* problem
-     * when same flow needs to be added. */
-    SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (mb, mac_binding_table) {
-        /* Remove any flows that should be removed. */
-        if (sbrec_mac_binding_is_deleted(mb)) {
-            VLOG_DBG("handle deleted mac_binding "UUID_FMT,
-                     UUID_ARGS(&mb->header_.uuid));
-            ofctrl_remove_flows(flow_table, &mb->header_.uuid);
-        }
-    }
-    SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (mb, mac_binding_table) {
-        if (!sbrec_mac_binding_is_deleted(mb)) {
-            if (!sbrec_mac_binding_is_new(mb)) {
-                VLOG_DBG("handle updated mac_binding "UUID_FMT,
-                         UUID_ARGS(&mb->header_.uuid));
-                ofctrl_remove_flows(flow_table, &mb->header_.uuid);
-            }
-            VLOG_DBG("handle new mac_binding "UUID_FMT,
-                     UUID_ARGS(&mb->header_.uuid));
-            consider_neighbor_flow(sbrec_port_binding_by_name, mb, flow_table);
-        }
-    }
-}
-
-
-/* Translates logical flows in the Logical_Flow table in the OVN_SB database
- * into OpenFlow flows.  See ovn-architecture(7) for more information. */
-void
-lflow_run(struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-          struct ovsdb_idl_index *sbrec_port_binding_by_name,
-          const struct sbrec_dhcp_options_table *dhcp_options_table,
-          const struct sbrec_dhcpv6_options_table *dhcpv6_options_table,
-          const struct sbrec_logical_flow_table *logical_flow_table,
-          const struct sbrec_mac_binding_table *mac_binding_table,
-          const struct sbrec_chassis *chassis,
-          const struct hmap *local_datapaths,
-          const struct shash *addr_sets,
-          const struct shash *port_groups,
-          const struct sset *active_tunnels,
-          const struct sset *local_lport_ids,
-          struct ovn_desired_flow_table *flow_table,
-          struct ovn_extend_table *group_table,
-          struct ovn_extend_table *meter_table,
-          struct lflow_resource_ref *lfrr,
-          uint32_t *conj_id_ofs)
-{
-    COVERAGE_INC(lflow_run);
-
-    add_logical_flows(sbrec_multicast_group_by_name_datapath,
-                      sbrec_port_binding_by_name, dhcp_options_table,
-                      dhcpv6_options_table, logical_flow_table,
-                      local_datapaths, chassis, addr_sets, port_groups,
-                      active_tunnels, local_lport_ids, flow_table, group_table,
-                      meter_table, lfrr, conj_id_ofs);
-    add_neighbor_flows(sbrec_port_binding_by_name, mac_binding_table,
-                       flow_table);
-}
-
-void
-lflow_destroy(void)
-{
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-    ovn_destroy_ovnfields();
-}
diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
deleted file mode 100644
index 4e1086eb6..000000000
--- a/ovn/controller/lflow.h
+++ /dev/null
@@ -1,184 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_LFLOW_H
-#define OVN_LFLOW_H 1
-
-#include "ovn/logical-fields.h"
-
-/* Logical_Flow table translation to OpenFlow
- * ==========================================
- *
- * The Logical_Flow table obtained from the OVN_Southbound database works in
- * terms of logical entities, that is, logical flows among logical datapaths
- * and logical ports.  This code translates these logical flows into OpenFlow
- * flows that, again, work in terms of logical entities implemented through
- * OpenFlow extensions (e.g. registers represent the logical input and output
- * ports).
- *
- * Physical-to-logical and logical-to-physical translation are implemented in
- * physical.[ch] as separate OpenFlow tables that run before and after,
- * respectively, the logical pipeline OpenFlow tables.
- */
-
-#include <stdint.h>
-#include "openvswitch/hmap.h"
-#include "openvswitch/uuid.h"
-#include "openvswitch/list.h"
-
-struct ovn_extend_table;
-struct ovsdb_idl_index;
-struct ovn_desired_flow_table;
-struct hmap;
-struct hmap_node;
-struct sbrec_chassis;
-struct sbrec_dhcp_options_table;
-struct sbrec_dhcpv6_options_table;
-struct sbrec_logical_flow_table;
-struct sbrec_mac_binding_table;
-struct simap;
-struct sset;
-struct uuid;
-
-/* OpenFlow table numbers.
- *
- * These are heavily documented in ovn-architecture(7), please update it if
- * you make any changes. */
-#define OFTABLE_PHY_TO_LOG            0
-#define OFTABLE_LOG_INGRESS_PIPELINE  8 /* First of LOG_PIPELINE_LEN tables. */
-#define OFTABLE_REMOTE_OUTPUT        32
-#define OFTABLE_LOCAL_OUTPUT         33
-#define OFTABLE_CHECK_LOOPBACK       34
-#define OFTABLE_LOG_EGRESS_PIPELINE  40 /* First of LOG_PIPELINE_LEN tables. */
-#define OFTABLE_SAVE_INPORT          64
-#define OFTABLE_LOG_TO_PHY           65
-#define OFTABLE_MAC_BINDING          66
-
-/* The number of tables for the ingress and egress pipelines. */
-#define LOG_PIPELINE_LEN 24
-
-enum ref_type {
-    REF_TYPE_ADDRSET,
-    REF_TYPE_PORTGROUP
-};
-
-/* Maintains the relationship for a pair of named resource and
- * a lflow, indexed by both ref_lflow_table and lflow_ref_table. */
-struct lflow_ref_list_node {
-    struct ovs_list lflow_list; /* list for same lflow */
-    struct ovs_list ref_list; /* list for same ref */
-    enum ref_type type;
-    char *ref_name;
-    struct uuid lflow_uuid;
-};
-
-struct ref_lflow_node {
-    struct hmap_node node;
-    enum ref_type type; /* key */
-    char *ref_name; /* key */
-    struct ovs_list ref_lflow_head;
-};
-
-struct lflow_ref_node {
-    struct hmap_node node;
-    struct uuid lflow_uuid; /* key */
-    struct ovs_list lflow_ref_head;
-};
-
-struct lflow_resource_ref {
-    /* A map from a referenced resource type & name (e.g. address_set AS1)
-     * to a list of lflows that are referencing the named resource. Data
-     * type of each node in this hmap is struct ref_lflow_node. The
-     * ref_lflow_head in each node points to a list of
-     * lflow_ref_list_node.ref_list. */
-    struct hmap ref_lflow_table;
-
-    /* A map from a lflow uuid to a list of named resources that are
-     * referenced by the lflow. Data type of each node in this hmap is
-     * struct lflow_ref_node. The lflow_ref_head in each node points to
-     * a list of lflow_ref_list_node.lflow_list. */
-    struct hmap lflow_ref_table;
-};
-
-void lflow_resource_init(struct lflow_resource_ref *);
-void lflow_resource_destroy(struct lflow_resource_ref *);
-void lflow_resource_clear(struct lflow_resource_ref *);
-
-void lflow_init(void);
-void lflow_run(struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-               struct ovsdb_idl_index *sbrec_port_binding_by_name,
-               const struct sbrec_dhcp_options_table *,
-               const struct sbrec_dhcpv6_options_table *,
-               const struct sbrec_logical_flow_table *,
-               const struct sbrec_mac_binding_table *,
-               const struct sbrec_chassis *chassis,
-               const struct hmap *local_datapaths,
-               const struct shash *addr_sets,
-               const struct shash *port_groups,
-               const struct sset *active_tunnels,
-               const struct sset *local_lport_ids,
-               struct ovn_desired_flow_table *,
-               struct ovn_extend_table *group_table,
-               struct ovn_extend_table *meter_table,
-               struct lflow_resource_ref *,
-               uint32_t *conj_id_ofs);
-
-bool lflow_handle_changed_flows(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_dhcp_options_table *,
-    const struct sbrec_dhcpv6_options_table *,
-    const struct sbrec_logical_flow_table *,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *,
-    uint32_t *conj_id_ofs);
-
-bool lflow_handle_changed_ref(
-    enum ref_type,
-    const char *ref_name,
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_dhcp_options_table *,
-    const struct sbrec_dhcpv6_options_table *,
-    const struct sbrec_logical_flow_table *,
-    const struct hmap *local_datapaths,
-    const struct sbrec_chassis *,
-    const struct shash *addr_sets,
-    const struct shash *port_groups,
-    const struct sset *active_tunnels,
-    const struct sset *local_lport_ids,
-    struct ovn_desired_flow_table *,
-    struct ovn_extend_table *group_table,
-    struct ovn_extend_table *meter_table,
-    struct lflow_resource_ref *,
-    uint32_t *conj_id_ofs,
-    bool *changed);
-
-void lflow_handle_changed_neighbors(
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_mac_binding_table *,
-    struct ovn_desired_flow_table *);
-
-void lflow_destroy(void);
-
-#endif /* ovn/lflow.h */
diff --git a/ovn/controller/lport.c b/ovn/controller/lport.c
deleted file mode 100644
index cc5c5fbb2..000000000
--- a/ovn/controller/lport.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/* Copyright (c) 2015, 2016 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 "lib/sset.h"
-#include "lport.h"
-#include "hash.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/ovn-sb-idl.h"
-VLOG_DEFINE_THIS_MODULE(lport);
-
-const struct sbrec_port_binding *
-lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                     const char *name)
-{
-    struct sbrec_port_binding *pb = sbrec_port_binding_index_init_row(
-        sbrec_port_binding_by_name);
-    sbrec_port_binding_index_set_logical_port(pb, name);
-
-    const struct sbrec_port_binding *retval = sbrec_port_binding_index_find(
-        sbrec_port_binding_by_name, pb);
-
-    sbrec_port_binding_index_destroy_row(pb);
-
-    return retval;
-}
-
-const struct sbrec_port_binding *
-lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                    uint64_t dp_key, uint64_t port_key)
-{
-    /* Lookup datapath corresponding to dp_key. */
-    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
-        sbrec_datapath_binding_by_key, dp_key);
-    if (!db) {
-        return NULL;
-    }
-
-    /* Build key for an indexed lookup. */
-    struct sbrec_port_binding *pb = sbrec_port_binding_index_init_row(
-        sbrec_port_binding_by_key);
-    sbrec_port_binding_index_set_datapath(pb, db);
-    sbrec_port_binding_index_set_tunnel_key(pb, port_key);
-
-    const struct sbrec_port_binding *retval = sbrec_port_binding_index_find(
-        sbrec_port_binding_by_key, pb);
-
-    sbrec_port_binding_index_destroy_row(pb);
-
-    return retval;
-}
-
-const struct sbrec_datapath_binding *
-datapath_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                       uint64_t dp_key)
-{
-    struct sbrec_datapath_binding *db = sbrec_datapath_binding_index_init_row(
-        sbrec_datapath_binding_by_key);
-    sbrec_datapath_binding_index_set_tunnel_key(db, dp_key);
-
-    const struct sbrec_datapath_binding *retval
-        = sbrec_datapath_binding_index_find(sbrec_datapath_binding_by_key,
-                                            db);
-
-    sbrec_datapath_binding_index_destroy_row(db);
-
-    return retval;
-}
-
-const struct sbrec_multicast_group *
-mcgroup_lookup_by_dp_name(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    const struct sbrec_datapath_binding *db, const char *name)
-{
-    /* Build key for an indexed lookup. */
-    struct sbrec_multicast_group *mc = sbrec_multicast_group_index_init_row(
-        sbrec_multicast_group_by_name_datapath);
-    sbrec_multicast_group_index_set_name(mc, name);
-    sbrec_multicast_group_index_set_datapath(mc, db);
-
-    const struct sbrec_multicast_group *retval
-        = sbrec_multicast_group_index_find(
-            sbrec_multicast_group_by_name_datapath, mc);
-
-    sbrec_multicast_group_index_destroy_row(mc);
-
-    return retval;
-}
diff --git a/ovn/controller/lport.h b/ovn/controller/lport.h
deleted file mode 100644
index 7dcd5bee0..000000000
--- a/ovn/controller/lport.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_LPORT_H
-#define OVN_LPORT_H 1
-
-#include <stdint.h>
-
-struct ovsdb_idl_index;
-struct sbrec_chassis;
-struct sbrec_datapath_binding;
-struct sbrec_multicast_group;
-struct sbrec_port_binding;
-
-
-/* Database indexes.
- * =================
- *
- * If the database IDL were a little smarter, it would allow us to directly
- * look up data based on values of its fields.  It's not that smart (yet), so
- * instead we define our own indexes.
- */
-
-const struct sbrec_port_binding *lport_lookup_by_name(
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const char *name);
-
-const struct sbrec_port_binding *lport_lookup_by_key(
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-    uint64_t dp_key, uint64_t port_key);
-
-const struct sbrec_datapath_binding *datapath_lookup_by_key(
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key, uint64_t dp_key);
-
-const struct sbrec_multicast_group *mcgroup_lookup_by_dp_name(
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
-    const struct sbrec_datapath_binding *, const char *name);
-
-#endif /* ovn/lport.h */
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
deleted file mode 100644
index 043abd69d..000000000
--- a/ovn/controller/ofctrl.c
+++ /dev/null
@@ -1,1393 +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 "bitmap.h"
-#include "byte-order.h"
-#include "dirs.h"
-#include "dp-packet.h"
-#include "flow.h"
-#include "hash.h"
-#include "hindex.h"
-#include "lflow.h"
-#include "ofctrl.h"
-#include "openflow/openflow.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/list.h"
-#include "openvswitch/match.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofp-flow.h"
-#include "openvswitch/ofp-group.h"
-#include "openvswitch/ofp-match.h"
-#include "openvswitch/ofp-msgs.h"
-#include "openvswitch/ofp-meter.h"
-#include "openvswitch/ofp-packet.h"
-#include "openvswitch/ofp-print.h"
-#include "openvswitch/ofp-util.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/vlog.h"
-#include "ovn-controller.h"
-#include "ovn/actions.h"
-#include "ovn/lib/extend-table.h"
-#include "openvswitch/poll-loop.h"
-#include "physical.h"
-#include "openvswitch/rconn.h"
-#include "socket-util.h"
-#include "util.h"
-#include "vswitch-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(ofctrl);
-
-/* An OpenFlow flow. */
-struct ovn_flow {
-    struct hmap_node match_hmap_node; /* For match based hashing. */
-    struct hindex_node uuid_hindex_node; /* For uuid based hashing. */
-    struct ovs_list list_node; /* For handling lists of flows. */
-
-    /* Key. */
-    uint8_t table_id;
-    uint16_t priority;
-    struct minimatch match;
-
-    /* Data. */
-    struct uuid sb_uuid;
-    struct ofpact *ofpacts;
-    size_t ofpacts_len;
-    uint64_t cookie;
-};
-
-static uint32_t ovn_flow_match_hash(const struct ovn_flow *);
-static struct ovn_flow *ovn_flow_lookup(struct hmap *flow_table,
-                                        const struct ovn_flow *target,
-                                        bool cmp_sb_uuid);
-static char *ovn_flow_to_string(const struct ovn_flow *);
-static void ovn_flow_log(const struct ovn_flow *, const char *action);
-static void ovn_flow_destroy(struct ovn_flow *);
-
-/* OpenFlow connection to the switch. */
-static struct rconn *swconn;
-
-/* Symbol table for OVN expressions. */
-static struct shash symtab;
-
-/* Last seen sequence number for 'swconn'.  When this differs from
- * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
-static unsigned int seqno;
-
-/* Connection state machine. */
-#define STATES                                  \
-    STATE(S_NEW)                                \
-    STATE(S_TLV_TABLE_REQUESTED)                \
-    STATE(S_TLV_TABLE_MOD_SENT)                 \
-    STATE(S_CLEAR_FLOWS)                        \
-    STATE(S_UPDATE_FLOWS)
-enum ofctrl_state {
-#define STATE(NAME) NAME,
-    STATES
-#undef STATE
-};
-
-/* An in-flight update to the switch's flow table.
- *
- * When we receive a barrier reply from the switch with the given 'xid', we
- * know that the switch is caught up to northbound database sequence number
- * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so
- * that it can store it into our Chassis record's nb_cfg column). */
-struct ofctrl_flow_update {
-    struct ovs_list list_node;  /* In 'flow_updates'. */
-    ovs_be32 xid;               /* OpenFlow transaction ID for barrier. */
-    int64_t nb_cfg;             /* Northbound database sequence number. */
-};
-
-static struct ofctrl_flow_update *
-ofctrl_flow_update_from_list_node(const struct ovs_list *list_node)
-{
-    return CONTAINER_OF(list_node, struct ofctrl_flow_update, list_node);
-}
-
-/* Currently in-flight updates. */
-static struct ovs_list flow_updates;
-
-/* nb_cfg of latest committed flow update. */
-static int64_t cur_cfg;
-
-/* Current state. */
-static enum ofctrl_state state;
-
-/* Transaction IDs for messages in flight to the switch. */
-static ovs_be32 xid, xid2;
-
-/* Counter for in-flight OpenFlow messages on 'swconn'.  We only send a new
- * round of flow table modifications to the switch when the counter falls to
- * zero, to avoid unbounded buffering. */
-static struct rconn_packet_counter *tx_counter;
-
-/* Flow table of "struct ovn_flow"s, that holds the flow table currently
- * installed in the switch. */
-static struct hmap installed_flows;
-
-/* A reference to the group_table. */
-static struct ovn_extend_table *groups;
-
-/* A reference to the meter_table. */
-static struct ovn_extend_table *meters;
-
-/* MFF_* field ID for our Geneve option.  In S_TLV_TABLE_MOD_SENT, this is
- * the option we requested (we don't know whether we obtained it yet).  In
- * S_CLEAR_FLOWS or S_UPDATE_FLOWS, this is really the option we have. */
-static enum mf_field_id mff_ovn_geneve;
-
-/* Indicates if flows need to be reinstalled for scenarios when ovs
- * is restarted, even if there is no change in the desired flow table. */
-static bool need_reinstall_flows;
-
-static ovs_be32 queue_msg(struct ofpbuf *);
-
-static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *);
-
-static struct ofpbuf *encode_group_mod(const struct ofputil_group_mod *);
-
-static struct ofpbuf *encode_meter_mod(const struct ofputil_meter_mod *);
-
-static void ovn_installed_flow_table_clear(void);
-static void ovn_installed_flow_table_destroy(void);
-
-static void ofctrl_recv(const struct ofp_header *, enum ofptype);
-
-void
-ofctrl_init(struct ovn_extend_table *group_table,
-            struct ovn_extend_table *meter_table,
-            int inactivity_probe_interval)
-{
-    swconn = rconn_create(inactivity_probe_interval, 0,
-                          DSCP_DEFAULT, 1 << OFP13_VERSION);
-    tx_counter = rconn_packet_counter_create();
-    hmap_init(&installed_flows);
-    ovs_list_init(&flow_updates);
-    ovn_init_symtab(&symtab);
-    groups = group_table;
-    meters = meter_table;
-}
-
-/* S_NEW, for a new connection.
- *
- * Sends NXT_TLV_TABLE_REQUEST and transitions to
- * S_TLV_TABLE_REQUESTED. */
-
-static void
-run_S_NEW(void)
-{
-    struct ofpbuf *buf = ofpraw_alloc(OFPRAW_NXT_TLV_TABLE_REQUEST,
-                                      rconn_get_version(swconn), 0);
-    xid = queue_msg(buf);
-    state = S_TLV_TABLE_REQUESTED;
-}
-
-static void
-recv_S_NEW(const struct ofp_header *oh OVS_UNUSED,
-           enum ofptype type OVS_UNUSED,
-           struct shash *pending_ct_zones OVS_UNUSED)
-{
-    OVS_NOT_REACHED();
-}
-
-/* S_TLV_TABLE_REQUESTED, when NXT_TLV_TABLE_REQUEST has been sent
- * and we're waiting for a reply.
- *
- * If we receive an NXT_TLV_TABLE_REPLY:
- *
- *     - If it contains our tunnel metadata option, assign its field ID to
- *       mff_ovn_geneve and transition to S_CLEAR_FLOWS.
- *
- *     - Otherwise, if there is an unused tunnel metadata field ID, send
- *       NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST, and transition to
- *       S_TLV_TABLE_MOD_SENT.
- *
- *     - Otherwise, log an error, disable Geneve, and transition to
- *       S_CLEAR_FLOWS.
- *
- * If we receive an OFPT_ERROR:
- *
- *     - Log an error, disable Geneve, and transition to S_CLEAR_FLOWS. */
-
-static void
-run_S_TLV_TABLE_REQUESTED(void)
-{
-}
-
-static bool
-process_tlv_table_reply(const struct ofputil_tlv_table_reply *reply)
-{
-    const struct ofputil_tlv_map *map;
-    uint64_t md_free = UINT64_MAX;
-    BUILD_ASSERT(TUN_METADATA_NUM_OPTS == 64);
-
-    LIST_FOR_EACH (map, list_node, &reply->mappings) {
-        if (map->option_class == OVN_GENEVE_CLASS
-            && map->option_type == OVN_GENEVE_TYPE
-            && map->option_len == OVN_GENEVE_LEN) {
-            if (map->index >= TUN_METADATA_NUM_OPTS) {
-                VLOG_ERR("desired Geneve tunnel option 0x%"PRIx16","
-                         "%"PRIu8",%"PRIu8" already in use with "
-                         "unsupported index %"PRIu16,
-                         map->option_class, map->option_type,
-                         map->option_len, map->index);
-                return false;
-            } else {
-                mff_ovn_geneve = MFF_TUN_METADATA0 + map->index;
-                state = S_CLEAR_FLOWS;
-                return true;
-            }
-        }
-
-        if (map->index < TUN_METADATA_NUM_OPTS) {
-            md_free &= ~(UINT64_C(1) << map->index);
-        }
-    }
-
-    VLOG_DBG("OVN Geneve option not found");
-    if (!md_free) {
-        VLOG_ERR("no Geneve options free for use by OVN");
-        return false;
-    }
-
-    unsigned int index = rightmost_1bit_idx(md_free);
-    mff_ovn_geneve = MFF_TUN_METADATA0 + index;
-    struct ofputil_tlv_map tm;
-    tm.option_class = OVN_GENEVE_CLASS;
-    tm.option_type = OVN_GENEVE_TYPE;
-    tm.option_len = OVN_GENEVE_LEN;
-    tm.index = index;
-
-    struct ofputil_tlv_table_mod ttm;
-    ttm.command = NXTTMC_ADD;
-    ovs_list_init(&ttm.mappings);
-    ovs_list_push_back(&ttm.mappings, &tm.list_node);
-
-    xid = queue_msg(ofputil_encode_tlv_table_mod(OFP13_VERSION, &ttm));
-    xid2 = queue_msg(ofputil_encode_barrier_request(OFP13_VERSION));
-    state = S_TLV_TABLE_MOD_SENT;
-
-    return true;
-}
-
-static void
-recv_S_TLV_TABLE_REQUESTED(const struct ofp_header *oh, enum ofptype type,
-                           struct shash *pending_ct_zones OVS_UNUSED)
-{
-    if (oh->xid != xid) {
-        ofctrl_recv(oh, type);
-        return;
-    } else if (type == OFPTYPE_NXT_TLV_TABLE_REPLY) {
-        struct ofputil_tlv_table_reply reply;
-        enum ofperr error = ofputil_decode_tlv_table_reply(oh, &reply);
-        if (!error) {
-            bool ok = process_tlv_table_reply(&reply);
-            ofputil_uninit_tlv_table(&reply.mappings);
-            if (ok) {
-                return;
-            }
-        } else {
-            VLOG_ERR("failed to decode TLV table request (%s)",
-                     ofperr_to_string(error));
-        }
-    } else if (type == OFPTYPE_ERROR) {
-        VLOG_ERR("switch refused to allocate Geneve option (%s)",
-                 ofperr_to_string(ofperr_decode_msg(oh, NULL)));
-    } else {
-        char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1);
-        VLOG_ERR("unexpected reply to TLV table request (%s)", s);
-        free(s);
-    }
-
-    /* Error path. */
-    mff_ovn_geneve = 0;
-    state = S_CLEAR_FLOWS;
-}
-
-/* S_TLV_TABLE_MOD_SENT, when NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST
- * have been sent and we're waiting for a reply to one or the other.
- *
- * If we receive an OFPT_ERROR:
- *
- *     - If the error is NXTTMFC_ALREADY_MAPPED or NXTTMFC_DUP_ENTRY, we
- *       raced with some other controller.  Transition to S_NEW.
- *
- *     - Otherwise, log an error, disable Geneve, and transition to
- *       S_CLEAR_FLOWS.
- *
- * If we receive OFPT_BARRIER_REPLY:
- *
- *     - Set the tunnel metadata field ID to the one that we requested.
- *       Transition to S_CLEAR_FLOWS.
- */
-
-static void
-run_S_TLV_TABLE_MOD_SENT(void)
-{
-}
-
-static void
-recv_S_TLV_TABLE_MOD_SENT(const struct ofp_header *oh, enum ofptype type,
-                          struct shash *pending_ct_zones OVS_UNUSED)
-{
-    if (oh->xid != xid && oh->xid != xid2) {
-        ofctrl_recv(oh, type);
-    } else if (oh->xid == xid2 && type == OFPTYPE_BARRIER_REPLY) {
-        state = S_CLEAR_FLOWS;
-    } else if (oh->xid == xid && type == OFPTYPE_ERROR) {
-        enum ofperr error = ofperr_decode_msg(oh, NULL);
-        if (error == OFPERR_NXTTMFC_ALREADY_MAPPED ||
-            error == OFPERR_NXTTMFC_DUP_ENTRY) {
-            VLOG_INFO("raced with another controller adding "
-                      "Geneve option (%s); trying again",
-                      ofperr_to_string(error));
-            state = S_NEW;
-        } else {
-            VLOG_ERR("error adding Geneve option (%s)",
-                     ofperr_to_string(error));
-            goto error;
-        }
-    } else {
-        char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1);
-        VLOG_ERR("unexpected reply to Geneve option allocation request (%s)",
-                 s);
-        free(s);
-        goto error;
-    }
-    return;
-
-error:
-    state = S_CLEAR_FLOWS;
-}
-
-/* S_CLEAR_FLOWS, after we've established a Geneve metadata field ID and it's
- * time to set up some flows.
- *
- * Sends an OFPT_TABLE_MOD to clear all flows, then transitions to
- * S_UPDATE_FLOWS. */
-
-static void
-run_S_CLEAR_FLOWS(void)
-{
-    VLOG_DBG("clearing all flows");
-
-    need_reinstall_flows = true;
-    /* Send a flow_mod to delete all flows. */
-    struct ofputil_flow_mod fm = {
-        .table_id = OFPTT_ALL,
-        .command = OFPFC_DELETE,
-    };
-    minimatch_init_catchall(&fm.match);
-    queue_msg(encode_flow_mod(&fm));
-    minimatch_destroy(&fm.match);
-
-    /* Send a group_mod to delete all groups. */
-    struct ofputil_group_mod gm;
-    memset(&gm, 0, sizeof gm);
-    gm.command = OFPGC11_DELETE;
-    gm.group_id = OFPG_ALL;
-    gm.command_bucket_id = OFPG15_BUCKET_ALL;
-    ovs_list_init(&gm.buckets);
-    queue_msg(encode_group_mod(&gm));
-    ofputil_uninit_group_mod(&gm);
-
-    /* Clear installed_flows, to match the state of the switch. */
-    ovn_installed_flow_table_clear();
-
-    /* Clear existing groups, to match the state of the switch. */
-    if (groups) {
-        ovn_extend_table_clear(groups, true);
-    }
-
-    /* Send a meter_mod to delete all meters. */
-    struct ofputil_meter_mod mm;
-    memset(&mm, 0, sizeof mm);
-    mm.command = OFPMC13_DELETE;
-    mm.meter.meter_id = OFPM13_ALL;
-    queue_msg(encode_meter_mod(&mm));
-
-    /* Clear existing meters, to match the state of the switch. */
-    if (meters) {
-        ovn_extend_table_clear(meters, true);
-    }
-
-    /* All flow updates are irrelevant now. */
-    struct ofctrl_flow_update *fup, *next;
-    LIST_FOR_EACH_SAFE (fup, next, list_node, &flow_updates) {
-        ovs_list_remove(&fup->list_node);
-        free(fup);
-    }
-
-    state = S_UPDATE_FLOWS;
-}
-
-static void
-recv_S_CLEAR_FLOWS(const struct ofp_header *oh, enum ofptype type,
-                   struct shash *pending_ct_zones OVS_UNUSED)
-{
-    ofctrl_recv(oh, type);
-}
-
-/* S_UPDATE_FLOWS, for maintaining the flow table over time.
- *
- * Compare the installed flows to the ones we want.  Send OFPT_FLOW_MOD as
- * necessary.
- *
- * This is a terminal state.  We only transition out of it if the connection
- * drops. */
-
-static void
-run_S_UPDATE_FLOWS(void)
-{
-    /* Nothing to do here.
-     *
-     * Being in this state enables ofctrl_put() to work, however. */
-}
-
-static void
-recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type,
-                    struct shash *pending_ct_zones)
-{
-    if (type == OFPTYPE_BARRIER_REPLY && !ovs_list_is_empty(&flow_updates)) {
-        struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
-            ovs_list_front(&flow_updates));
-        if (fup->xid == oh->xid) {
-            if (fup->nb_cfg >= cur_cfg) {
-                cur_cfg = fup->nb_cfg;
-            }
-            ovs_list_remove(&fup->list_node);
-            free(fup);
-        }
-
-        /* If the barrier xid is associated with an outstanding conntrack
-         * flush, the flush succeeded.  Move the pending ct zone entry
-         * to the next stage. */
-        struct shash_node *iter;
-        SHASH_FOR_EACH(iter, pending_ct_zones) {
-            struct ct_zone_pending_entry *ctzpe = iter->data;
-            if (ctzpe->state == CT_ZONE_OF_SENT && ctzpe->of_xid == oh->xid) {
-                ctzpe->state = CT_ZONE_DB_QUEUED;
-            }
-        }
-    } else {
-        ofctrl_recv(oh, type);
-    }
-}
-
-
-enum mf_field_id
-ofctrl_get_mf_field_id(void)
-{
-    if (!rconn_is_connected(swconn)) {
-        return 0;
-    }
-    return (state == S_CLEAR_FLOWS || state == S_UPDATE_FLOWS
-            ? mff_ovn_geneve : 0);
-}
-
-/* Runs the OpenFlow state machine against 'br_int', which is local to the
- * hypervisor on which we are running.  Attempts to negotiate a Geneve option
- * field for class OVN_GENEVE_CLASS, type OVN_GENEVE_TYPE. */
-void
-ofctrl_run(const struct ovsrec_bridge *br_int, struct shash *pending_ct_zones)
-{
-    char *target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int->name);
-    if (strcmp(target, rconn_get_target(swconn))) {
-        VLOG_INFO("%s: connecting to switch", target);
-        rconn_connect(swconn, target, target);
-    }
-    free(target);
-
-    rconn_run(swconn);
-
-    if (!rconn_is_connected(swconn)) {
-        return;
-    }
-    if (seqno != rconn_get_connection_seqno(swconn)) {
-        seqno = rconn_get_connection_seqno(swconn);
-        state = S_NEW;
-
-        /* Reset the state of any outstanding ct flushes to resend them. */
-        struct shash_node *iter;
-        SHASH_FOR_EACH(iter, pending_ct_zones) {
-            struct ct_zone_pending_entry *ctzpe = iter->data;
-            if (ctzpe->state == CT_ZONE_OF_SENT) {
-                ctzpe->state = CT_ZONE_OF_QUEUED;
-            }
-        }
-    }
-
-    bool progress = true;
-    for (int i = 0; progress && i < 50; i++) {
-        /* Allow the state machine to run. */
-        enum ofctrl_state old_state = state;
-        switch (state) {
-#define STATE(NAME) case NAME: run_##NAME(); break;
-            STATES
-#undef STATE
-        default:
-            OVS_NOT_REACHED();
-        }
-
-        /* Try to process a received packet. */
-        struct ofpbuf *msg = rconn_recv(swconn);
-        if (msg) {
-            const struct ofp_header *oh = msg->data;
-            enum ofptype type;
-            enum ofperr error;
-
-            error = ofptype_decode(&type, oh);
-            if (!error) {
-                switch (state) {
-#define STATE(NAME) case NAME: recv_##NAME(oh, type, pending_ct_zones); break;
-                    STATES
-#undef STATE
-                default:
-                    OVS_NOT_REACHED();
-                }
-            } else {
-                char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1);
-                VLOG_WARN("could not decode OpenFlow message (%s): %s",
-                          ofperr_to_string(error), s);
-                free(s);
-            }
-
-            ofpbuf_delete(msg);
-        }
-
-        /* If we did some work, plan to go around again. */
-        progress = old_state != state || msg;
-    }
-    if (progress) {
-        /* We bailed out to limit the amount of work we do in one go, to allow
-         * other code a chance to run.  We were still making progress at that
-         * point, so ensure that we come back again without waiting. */
-        poll_immediate_wake();
-    }
-}
-
-void
-ofctrl_wait(void)
-{
-    rconn_run_wait(swconn);
-    rconn_recv_wait(swconn);
-}
-
-void
-ofctrl_destroy(void)
-{
-    rconn_destroy(swconn);
-    ovn_installed_flow_table_destroy();
-    rconn_packet_counter_destroy(tx_counter);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-int64_t
-ofctrl_get_cur_cfg(void)
-{
-    return cur_cfg;
-}
-
-static ovs_be32
-queue_msg(struct ofpbuf *msg)
-{
-    const struct ofp_header *oh = msg->data;
-    ovs_be32 xid_ = oh->xid;
-    rconn_send(swconn, msg, tx_counter);
-    return xid_;
-}
-
-static void
-log_openflow_rl(struct vlog_rate_limit *rl, enum vlog_level level,
-                const struct ofp_header *oh, const char *title)
-{
-    if (!vlog_should_drop(&this_module, level, rl)) {
-        char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 2);
-        vlog(&this_module, level, "%s: %s", title, s);
-        free(s);
-    }
-}
-
-static void
-ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
-{
-    if (type == OFPTYPE_ECHO_REQUEST) {
-        queue_msg(ofputil_encode_echo_reply(oh));
-    } else if (type == OFPTYPE_ERROR) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
-        log_openflow_rl(&rl, VLL_INFO, oh, "OpenFlow error");
-    } else {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
-        log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored");
-    }
-}
-
-/* Flow table interfaces to the rest of ovn-controller. */
-
-/* Adds a flow to 'desired_flows' with the specified 'match' and 'actions' to
- * the OpenFlow table numbered 'table_id' with the given 'priority' and
- * OpenFlow 'cookie'.  The caller retains ownership of 'match' and 'actions'.
- *
- * The flow is also added to a hash index based on sb_uuid.
- *
- * This just assembles the desired flow table in memory.  Nothing is actually
- * sent to the switch until a later call to ofctrl_put().
- *
- * The caller should initialize its own hmap to hold the flows. */
-void
-ofctrl_check_and_add_flow(struct ovn_desired_flow_table *flow_table,
-                          uint8_t table_id, uint16_t priority,
-                          uint64_t cookie, const struct match *match,
-                          const struct ofpbuf *actions,
-                          const struct uuid *sb_uuid,
-                          bool log_duplicate_flow)
-{
-    struct ovn_flow *f = xmalloc(sizeof *f);
-    f->table_id = table_id;
-    f->priority = priority;
-    minimatch_init(&f->match, match);
-    f->ofpacts = xmemdup(actions->data, actions->size);
-    f->ofpacts_len = actions->size;
-    f->sb_uuid = *sb_uuid;
-    f->match_hmap_node.hash = ovn_flow_match_hash(f);
-    f->uuid_hindex_node.hash = uuid_hash(&f->sb_uuid);
-    f->cookie = cookie;
-
-    ovn_flow_log(f, "ofctrl_add_flow");
-
-    if (ovn_flow_lookup(&flow_table->match_flow_table, f, true)) {
-        if (log_duplicate_flow) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
-            if (!VLOG_DROP_DBG(&rl)) {
-                char *s = ovn_flow_to_string(f);
-                VLOG_DBG("dropping duplicate flow: %s", s);
-                free(s);
-            }
-        }
-        ovn_flow_destroy(f);
-        return;
-    }
-
-    hmap_insert(&flow_table->match_flow_table, &f->match_hmap_node,
-                f->match_hmap_node.hash);
-    hindex_insert(&flow_table->uuid_flow_table, &f->uuid_hindex_node,
-                  f->uuid_hindex_node.hash);
-}
-
-/* Removes a bundles of flows from the flow table. */
-void
-ofctrl_remove_flows(struct ovn_desired_flow_table *flow_table,
-                    const struct uuid *sb_uuid)
-{
-    struct ovn_flow *f, *next;
-    HINDEX_FOR_EACH_WITH_HASH_SAFE (f, next, uuid_hindex_node,
-                                    uuid_hash(sb_uuid),
-                                    &flow_table->uuid_flow_table) {
-        if (uuid_equals(&f->sb_uuid, sb_uuid)) {
-            ovn_flow_log(f, "ofctrl_remove_flow");
-            hmap_remove(&flow_table->match_flow_table,
-                        &f->match_hmap_node);
-            hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node);
-            ovn_flow_destroy(f);
-        }
-    }
-
-    /* remove any related group and meter info */
-    ovn_extend_table_remove_desired(groups, sb_uuid);
-    ovn_extend_table_remove_desired(meters, sb_uuid);
-}
-
-void
-ofctrl_add_flow(struct ovn_desired_flow_table *desired_flows,
-                uint8_t table_id, uint16_t priority, uint64_t cookie,
-                const struct match *match, const struct ofpbuf *actions,
-                const struct uuid *sb_uuid)
-{
-    ofctrl_check_and_add_flow(desired_flows, table_id, priority, cookie,
-                              match, actions, sb_uuid, true);
-}
-
-/* ovn_flow. */
-
-/* Returns a hash of the match key in 'f'. */
-static uint32_t
-ovn_flow_match_hash(const struct ovn_flow *f)
-{
-    return hash_2words((f->table_id << 16) | f->priority,
-                       minimatch_hash(&f->match, 0));
-}
-
-/* Duplicate an ovn_flow structure. */
-struct ovn_flow *
-ofctrl_dup_flow(struct ovn_flow *src)
-{
-    struct ovn_flow *dst = xmalloc(sizeof *dst);
-    dst->table_id = src->table_id;
-    dst->priority = src->priority;
-    minimatch_clone(&dst->match, &src->match);
-    dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
-    dst->ofpacts_len = src->ofpacts_len;
-    dst->sb_uuid = src->sb_uuid;
-    dst->match_hmap_node.hash = ovn_flow_match_hash(dst);
-    dst->uuid_hindex_node.hash = uuid_hash(&src->sb_uuid);
-    return dst;
-}
-
-/* Finds and returns an ovn_flow in 'flow_table' whose key is identical to
- * 'target''s key, or NULL if there is none. */
-static struct ovn_flow *
-ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target,
-                bool cmp_sb_uuid)
-{
-    struct ovn_flow *f;
-
-    HMAP_FOR_EACH_WITH_HASH (f, match_hmap_node, target->match_hmap_node.hash,
-                             flow_table) {
-        if (f->table_id == target->table_id
-            && f->priority == target->priority
-            && minimatch_equal(&f->match, &target->match)) {
-            if (!cmp_sb_uuid || uuid_equals(&target->sb_uuid, &f->sb_uuid)) {
-                return f;
-            }
-        }
-    }
-    return NULL;
-}
-
-static char *
-ovn_flow_to_string(const struct ovn_flow *f)
-{
-    struct ds s = DS_EMPTY_INITIALIZER;
-    ds_put_format(&s, "sb_uuid="UUID_FMT", ", UUID_ARGS(&f->sb_uuid));
-    ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
-    ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
-    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
-    ds_put_cstr(&s, ", actions=");
-    struct ofpact_format_params fp = { .s = &s };
-    ofpacts_format(f->ofpacts, f->ofpacts_len, &fp);
-    return ds_steal_cstr(&s);
-}
-
-static void
-ovn_flow_log(const struct ovn_flow *f, const char *action)
-{
-    if (VLOG_IS_DBG_ENABLED()) {
-        char *s = ovn_flow_to_string(f);
-        VLOG_DBG("%s flow: %s", action, s);
-        free(s);
-    }
-}
-
-static void
-ovn_flow_destroy(struct ovn_flow *f)
-{
-    if (f) {
-        minimatch_destroy(&f->match);
-        free(f->ofpacts);
-        free(f);
-    }
-}
-
-/* Flow tables of struct ovn_flow. */
-void
-ovn_desired_flow_table_init(struct ovn_desired_flow_table *flow_table)
-{
-    hmap_init(&flow_table->match_flow_table);
-    hindex_init(&flow_table->uuid_flow_table);
-}
-
-void
-ovn_desired_flow_table_clear(struct ovn_desired_flow_table *flow_table)
-{
-    struct ovn_flow *f, *next;
-    HMAP_FOR_EACH_SAFE (f, next, match_hmap_node,
-                        &flow_table->match_flow_table) {
-        hmap_remove(&flow_table->match_flow_table, &f->match_hmap_node);
-        hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node);
-        ovn_flow_destroy(f);
-    }
-}
-
-void
-ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *flow_table)
-{
-    ovn_desired_flow_table_clear(flow_table);
-    hmap_destroy(&flow_table->match_flow_table);
-    hindex_destroy(&flow_table->uuid_flow_table);
-}
-
-static void
-ovn_installed_flow_table_clear(void)
-{
-    struct ovn_flow *f, *next;
-    HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_flows) {
-        hmap_remove(&installed_flows, &f->match_hmap_node);
-        ovn_flow_destroy(f);
-    }
-}
-
-static void
-ovn_installed_flow_table_destroy(void)
-{
-    ovn_installed_flow_table_clear();
-    hmap_destroy(&installed_flows);
-}
-
-/* Flow table update. */
-
-static struct ofpbuf *
-encode_flow_mod(struct ofputil_flow_mod *fm)
-{
-    fm->buffer_id = UINT32_MAX;
-    fm->out_port = OFPP_ANY;
-    fm->out_group = OFPG_ANY;
-    return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF13_OXM);
-}
-
-static void
-add_flow_mod(struct ofputil_flow_mod *fm, struct ovs_list *msgs)
-{
-    struct ofpbuf *msg = encode_flow_mod(fm);
-    ovs_list_push_back(msgs, &msg->list_node);
-}
-
-/* group_table. */
-
-static struct ofpbuf *
-encode_group_mod(const struct ofputil_group_mod *gm)
-{
-    return ofputil_encode_group_mod(OFP13_VERSION, gm, NULL, -1);
-}
-
-static void
-add_group_mod(const struct ofputil_group_mod *gm, struct ovs_list *msgs)
-{
-    struct ofpbuf *msg = encode_group_mod(gm);
-    ovs_list_push_back(msgs, &msg->list_node);
-}
-
-
-static struct ofpbuf *
-encode_meter_mod(const struct ofputil_meter_mod *mm)
-{
-    return ofputil_encode_meter_mod(OFP13_VERSION, mm);
-}
-
-static void
-add_meter_mod(const struct ofputil_meter_mod *mm, struct ovs_list *msgs)
-{
-    struct ofpbuf *msg = encode_meter_mod(mm);
-    ovs_list_push_back(msgs, &msg->list_node);
-}
-
-static void
-add_ct_flush_zone(uint16_t zone_id, struct ovs_list *msgs)
-{
-    struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH_ZONE,
-                                      rconn_get_version(swconn), 0);
-    struct nx_zone_id *nzi = ofpbuf_put_zeros(msg, sizeof *nzi);
-    nzi->zone_id = htons(zone_id);
-
-    ovs_list_push_back(msgs, &msg->list_node);
-}
-
-static void
-add_meter_string(struct ovn_extend_table_info *m_desired,
-                 struct ovs_list *msgs)
-{
-    /* Create and install new meter. */
-    struct ofputil_meter_mod mm;
-    enum ofputil_protocol usable_protocols;
-    char *meter_string = xasprintf("meter=%"PRIu32",%s",
-                                   m_desired->table_id,
-                                   &m_desired->name[9]);
-    char *error = parse_ofp_meter_mod_str(&mm, meter_string, OFPMC13_ADD,
-                                          &usable_protocols);
-    if (!error) {
-        add_meter_mod(&mm, msgs);
-    } else {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_ERR_RL(&rl, "new meter %s %s", error, meter_string);
-        free(error);
-    }
-    free(meter_string);
-}
-
-static void
-add_meter(struct ovn_extend_table_info *m_desired,
-          const struct sbrec_meter_table *meter_table,
-          struct ovs_list *msgs)
-{
-    const struct sbrec_meter *sb_meter;
-    SBREC_METER_TABLE_FOR_EACH (sb_meter, meter_table) {
-        if (!strcmp(m_desired->name, sb_meter->name)) {
-            break;
-        }
-    }
-
-    if (!sb_meter) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_ERR_RL(&rl, "could not find meter named \"%s\"", m_desired->name);
-        return;
-    }
-
-    struct ofputil_meter_mod mm;
-    mm.command = OFPMC13_ADD;
-    mm.meter.meter_id = m_desired->table_id;
-    mm.meter.flags = OFPMF13_STATS;
-
-    if (!strcmp(sb_meter->unit, "pktps")) {
-        mm.meter.flags |= OFPMF13_PKTPS;
-    } else {
-        mm.meter.flags |= OFPMF13_KBPS;
-    }
-
-    mm.meter.n_bands = sb_meter->n_bands;
-    mm.meter.bands = xcalloc(mm.meter.n_bands, sizeof *mm.meter.bands);
-
-    for (size_t i = 0; i < sb_meter->n_bands; i++) {
-        struct sbrec_meter_band *sb_band = sb_meter->bands[i];
-        struct ofputil_meter_band *mm_band = &mm.meter.bands[i];
-
-        if (!strcmp(sb_band->action, "drop")) {
-            mm_band->type = OFPMBT13_DROP;
-        }
-
-        mm_band->prec_level = 0;
-        mm_band->rate = sb_band->rate;
-        mm_band->burst_size = sb_band->burst_size;
-
-        if (mm_band->burst_size) {
-            mm.meter.flags |= OFPMF13_BURST;
-        }
-    }
-
-    add_meter_mod(&mm, msgs);
-    free(mm.meter.bands);
-}
-
-/* The flow table can be updated if the connection to the switch is up and
- * in the correct state and not backlogged with existing flow_mods.  (Our
- * criteria for being backlogged appear very conservative, but the socket
- * between ovn-controller and OVS provides some buffering.) */
-static bool
-ofctrl_can_put(void)
-{
-    if (state != S_UPDATE_FLOWS
-        || rconn_packet_counter_n_packets(tx_counter)
-        || rconn_get_version(swconn) < 0) {
-        return false;
-    }
-    return true;
-}
-
-/* Replaces the flow table on the switch, if possible, by the flows added
- * with ofctrl_add_flow().
- *
- * Replaces the group table and meter table on the switch, if possible,
- * by the contents of '->desired'.
- *
- * Sends conntrack flush messages to each zone in 'pending_ct_zones' that
- * is in the CT_ZONE_OF_QUEUED state and then moves the zone into the
- * CT_ZONE_OF_SENT state.
- *
- * This should be called after ofctrl_run() within the main loop. */
-void
-ofctrl_put(struct ovn_desired_flow_table *flow_table,
-           struct shash *pending_ct_zones,
-           const struct sbrec_meter_table *meter_table,
-           int64_t nb_cfg,
-           bool flow_changed)
-{
-    static bool skipped_last_time = false;
-    static int64_t old_nb_cfg = 0;
-    bool need_put = false;
-    if (flow_changed || skipped_last_time || need_reinstall_flows) {
-        need_put = true;
-    } else if (nb_cfg != old_nb_cfg) {
-        /* nb_cfg changed since last ofctrl_put() call */
-        if (cur_cfg == old_nb_cfg) {
-            /* we were up-to-date already, so just update with the
-             * new nb_cfg */
-            cur_cfg = nb_cfg;
-        } else {
-            need_put = true;
-        }
-    }
-
-    old_nb_cfg = nb_cfg;
-
-    if (!need_put) {
-        VLOG_DBG("ofctrl_put not needed");
-        return;
-    }
-    if (!ofctrl_can_put()) {
-        VLOG_DBG("ofctrl_put can't be performed");
-        skipped_last_time = true;
-        return;
-    }
-
-    skipped_last_time = false;
-    need_reinstall_flows = false;
-
-    /* OpenFlow messages to send to the switch to bring it up-to-date. */
-    struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs);
-
-    /* Iterate through ct zones that need to be flushed. */
-    struct shash_node *iter;
-    SHASH_FOR_EACH(iter, pending_ct_zones) {
-        struct ct_zone_pending_entry *ctzpe = iter->data;
-        if (ctzpe->state == CT_ZONE_OF_QUEUED) {
-            add_ct_flush_zone(ctzpe->zone, &msgs);
-            ctzpe->state = CT_ZONE_OF_SENT;
-            ctzpe->of_xid = 0;
-        }
-    }
-
-    /* Iterate through all the desired groups. If there are new ones,
-     * add them to the switch. */
-    struct ovn_extend_table_info *desired;
-    EXTEND_TABLE_FOR_EACH_UNINSTALLED (desired, groups) {
-        /* Create and install new group. */
-        struct ofputil_group_mod gm;
-        enum ofputil_protocol usable_protocols;
-        char *group_string = xasprintf("group_id=%"PRIu32",%s",
-                                       desired->table_id,
-                                       desired->name);
-        char *error = parse_ofp_group_mod_str(&gm, OFPGC11_ADD, group_string,
-                                              NULL, NULL, &usable_protocols);
-        if (!error) {
-            add_group_mod(&gm, &msgs);
-        } else {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_ERR_RL(&rl, "new group %s %s", error, group_string);
-            free(error);
-        }
-        free(group_string);
-        ofputil_uninit_group_mod(&gm);
-    }
-
-    /* Iterate through all the desired meters. If there are new ones,
-     * add them to the switch. */
-    struct ovn_extend_table_info *m_desired;
-    EXTEND_TABLE_FOR_EACH_UNINSTALLED (m_desired, meters) {
-        if (!strncmp(m_desired->name, "__string: ", 10)) {
-            /* The "set-meter" action creates a meter entry name that
-             * describes the meter itself. */
-            add_meter_string(m_desired, &msgs);
-        } else {
-            add_meter(m_desired, meter_table, &msgs);
-        }
-    }
-
-    /* Iterate through all of the installed flows.  If any of them are no
-     * longer desired, delete them; if any of them should have different
-     * actions, update them. */
-    struct ovn_flow *i, *next;
-    HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) {
-        struct ovn_flow *d = ovn_flow_lookup(&flow_table->match_flow_table,
-                                             i, false);
-        if (!d) {
-            /* Installed flow is no longer desirable.  Delete it from the
-             * switch and from installed_flows. */
-            struct ofputil_flow_mod fm = {
-                .match = i->match,
-                .priority = i->priority,
-                .table_id = i->table_id,
-                .command = OFPFC_DELETE_STRICT,
-            };
-            add_flow_mod(&fm, &msgs);
-            ovn_flow_log(i, "removing installed");
-
-            hmap_remove(&installed_flows, &i->match_hmap_node);
-            ovn_flow_destroy(i);
-        } else {
-            if (!uuid_equals(&i->sb_uuid, &d->sb_uuid)) {
-                /* Update installed flow's UUID. */
-                i->sb_uuid = d->sb_uuid;
-            }
-            if (!ofpacts_equal(i->ofpacts, i->ofpacts_len,
-                               d->ofpacts, d->ofpacts_len)) {
-                /* Update actions in installed flow. */
-                struct ofputil_flow_mod fm = {
-                    .match = i->match,
-                    .priority = i->priority,
-                    .table_id = i->table_id,
-                    .ofpacts = d->ofpacts,
-                    .ofpacts_len = d->ofpacts_len,
-                    .command = OFPFC_MODIFY_STRICT,
-                };
-                add_flow_mod(&fm, &msgs);
-                ovn_flow_log(i, "updating installed");
-
-                /* Replace 'i''s actions by 'd''s. */
-                free(i->ofpacts);
-                i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len);
-                i->ofpacts_len = d->ofpacts_len;
-            }
-
-        }
-    }
-
-    /* Iterate through the desired flows and add those that aren't found
-     * in the installed flow table. */
-    struct ovn_flow *d;
-    HMAP_FOR_EACH (d, match_hmap_node, &flow_table->match_flow_table) {
-        i = ovn_flow_lookup(&installed_flows, d, false);
-        if (!i) {
-            /* Send flow_mod to add flow. */
-            struct ofputil_flow_mod fm = {
-                .match = d->match,
-                .priority = d->priority,
-                .table_id = d->table_id,
-                .ofpacts = d->ofpacts,
-                .ofpacts_len = d->ofpacts_len,
-                .new_cookie = htonll(d->cookie),
-                .command = OFPFC_ADD,
-            };
-            add_flow_mod(&fm, &msgs);
-            ovn_flow_log(d, "adding installed");
-
-            /* Copy 'd' from 'flow_table' to installed_flows. */
-            struct ovn_flow *new_node = ofctrl_dup_flow(d);
-            hmap_insert(&installed_flows, &new_node->match_hmap_node,
-                        new_node->match_hmap_node.hash);
-        }
-    }
-
-    /* Iterate through the installed groups from previous runs. If they
-     * are not needed delete them. */
-    struct ovn_extend_table_info *installed, *next_group;
-    EXTEND_TABLE_FOR_EACH_INSTALLED (installed, next_group, groups) {
-        /* Delete the group. */
-        struct ofputil_group_mod gm;
-        enum ofputil_protocol usable_protocols;
-        char *group_string = xasprintf("group_id=%"PRIu32"",
-                                       installed->table_id);
-        char *error = parse_ofp_group_mod_str(&gm, OFPGC11_DELETE,
-                                              group_string, NULL, NULL,
-                                              &usable_protocols);
-        if (!error) {
-            add_group_mod(&gm, &msgs);
-        } else {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_ERR_RL(&rl, "Error deleting group %d: %s",
-                        installed->table_id, error);
-            free(error);
-        }
-        free(group_string);
-        ofputil_uninit_group_mod(&gm);
-        ovn_extend_table_remove_existing(groups, installed);
-    }
-
-    /* Sync the contents of groups->desired to groups->existing. */
-    ovn_extend_table_sync(groups);
-
-    /* Iterate through the installed meters from previous runs. If they
-     * are not needed delete them. */
-    struct ovn_extend_table_info *m_installed, *next_meter;
-    EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_meter, meters) {
-        /* Delete the meter. */
-        struct ofputil_meter_mod mm = {
-            .command = OFPMC13_DELETE,
-            .meter = { .meter_id = m_installed->table_id },
-        };
-        add_meter_mod(&mm, &msgs);
-
-        ovn_extend_table_remove_existing(meters, m_installed);
-    }
-
-    /* Sync the contents of meters->desired to meters->existing. */
-    ovn_extend_table_sync(meters);
-
-    if (!ovs_list_is_empty(&msgs)) {
-        /* Add a barrier to the list of messages. */
-        struct ofpbuf *barrier = ofputil_encode_barrier_request(OFP13_VERSION);
-        const struct ofp_header *oh = barrier->data;
-        ovs_be32 xid_ = oh->xid;
-        ovs_list_push_back(&msgs, &barrier->list_node);
-
-        /* Queue the messages. */
-        struct ofpbuf *msg;
-        LIST_FOR_EACH_POP (msg, list_node, &msgs) {
-            queue_msg(msg);
-        }
-
-        /* Store the barrier's xid with any newly sent ct flushes. */
-        SHASH_FOR_EACH(iter, pending_ct_zones) {
-            struct ct_zone_pending_entry *ctzpe = iter->data;
-            if (ctzpe->state == CT_ZONE_OF_SENT && !ctzpe->of_xid) {
-                ctzpe->of_xid = xid_;
-            }
-        }
-
-        /* Track the flow update. */
-        struct ofctrl_flow_update *fup, *prev;
-        LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) {
-            if (nb_cfg < fup->nb_cfg) {
-                /* This ofctrl_flow_update is for a configuration later than
-                 * 'nb_cfg'.  This should not normally happen, because it means
-                 * that 'nb_cfg' in the SB_Global table of the southbound
-                 * database decreased, and it should normally be monotonically
-                 * increasing. */
-                VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64,
-                          fup->nb_cfg, nb_cfg);
-                ovs_list_remove(&fup->list_node);
-                free(fup);
-            } else if (nb_cfg == fup->nb_cfg) {
-                /* This ofctrl_flow_update is for the same configuration as
-                 * 'nb_cfg'.  Probably, some change to the physical topology
-                 * means that we had to revise the OpenFlow flow table even
-                 * though the logical topology did not change.  Update fp->xid,
-                 * so that we don't send a notification that we're up-to-date
-                 * until we're really caught up. */
-                VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg);
-                fup->xid = xid_;
-                goto done;
-            } else {
-                break;
-            }
-        }
-
-        /* Add a flow update. */
-        fup = xmalloc(sizeof *fup);
-        ovs_list_push_back(&flow_updates, &fup->list_node);
-        fup->xid = xid_;
-        fup->nb_cfg = nb_cfg;
-    done:;
-    } else if (!ovs_list_is_empty(&flow_updates)) {
-        /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table
-         * changes, so whenever we get up-to-date with the most recent flow
-         * table update, we're also up-to-date with 'nb_cfg'. */
-        struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
-            ovs_list_back(&flow_updates));
-        fup->nb_cfg = nb_cfg;
-    } else {
-        /* We were completely up-to-date before and still are. */
-        cur_cfg = nb_cfg;
-    }
-}
-
-/* Looks up the logical port with the name 'port_name' in 'br_int_'.  If
- * found, returns true and sets '*portp' to the OpenFlow port number
- * assigned to the port.  Otherwise, returns false. */
-static bool
-ofctrl_lookup_port(const void *br_int_, const char *port_name,
-                   unsigned int *portp)
-{
-    const struct ovsrec_bridge *br_int = br_int_;
-
-    for (int i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-        for (int j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
-            const char *iface_id = smap_get(&iface_rec->external_ids,
-                                            "iface-id");
-
-            if (iface_id && !strcmp(iface_id, port_name)) {
-                if (!iface_rec->n_ofport) {
-                    continue;
-                }
-
-                int64_t ofport = iface_rec->ofport[0];
-                if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) {
-                    continue;
-                }
-                *portp = ofport;
-                return true;
-            }
-        }
-    }
-
-    return false;
-}
-
-/* Generates a packet described by 'flow_s' in the syntax of an OVN
- * logical expression and injects it into 'br_int'.  The flow
- * description must contain an ingress logical port that is present on
- * 'br_int'.
- *
- * Returns NULL if successful, otherwise an error message that the caller
- * must free(). */
-char *
-ofctrl_inject_pkt(const struct ovsrec_bridge *br_int, const char *flow_s,
-                  const struct shash *addr_sets,
-                  const struct shash *port_groups)
-{
-    int version = rconn_get_version(swconn);
-    if (version < 0) {
-        return xstrdup("OpenFlow channel not ready.");
-    }
-
-    struct flow uflow;
-    char *error = expr_parse_microflow(flow_s, &symtab, addr_sets,
-                                       port_groups, ofctrl_lookup_port,
-                                       br_int, &uflow);
-    if (error) {
-        return error;
-    }
-
-    /* The physical OpenFlow port was stored in the logical ingress
-     * port, so put it in the correct location for a flow structure. */
-    uflow.in_port.ofp_port = u16_to_ofp(uflow.regs[MFF_LOG_INPORT - MFF_REG0]);
-    uflow.regs[MFF_LOG_INPORT - MFF_REG0] = 0;
-
-    if (!uflow.in_port.ofp_port) {
-        return xstrdup("ingress port not found on hypervisor.");
-    }
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    flow_compose(&packet, &uflow, NULL, 64);
-
-    uint64_t ofpacts_stub[1024 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-    resubmit->in_port = OFPP_IN_PORT;
-    resubmit->table_id = 0;
-
-    struct ofputil_packet_out po = {
-        .packet = dp_packet_data(&packet),
-        .packet_len = dp_packet_size(&packet),
-        .buffer_id = UINT32_MAX,
-        .ofpacts = ofpacts.data,
-        .ofpacts_len = ofpacts.size,
-    };
-    match_set_in_port(&po.flow_metadata, uflow.in_port.ofp_port);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    queue_msg(ofputil_encode_packet_out(&po, proto));
-    dp_packet_uninit(&packet);
-    ofpbuf_uninit(&ofpacts);
-
-    return NULL;
-}
-
-bool
-ofctrl_is_connected(void)
-{
-    return rconn_is_connected(swconn);
-}
-
-void
-ofctrl_set_probe_interval(int probe_interval)
-{
-    if (swconn) {
-        rconn_set_probe_interval(swconn, probe_interval);
-    }
-}
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
deleted file mode 100644
index ed8918aae..000000000
--- a/ovn/controller/ofctrl.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OFCTRL_H
-#define OFCTRL_H 1
-
-#include <stdint.h>
-
-#include "openvswitch/meta-flow.h"
-#include "ovsdb-idl.h"
-#include "hindex.h"
-
-struct ovn_extend_table;
-struct hmap;
-struct match;
-struct ofpbuf;
-struct ovsrec_bridge;
-struct sbrec_meter_table;
-struct shash;
-
-struct ovn_desired_flow_table {
-    /* Hash map flow table using flow match conditions as hash key.*/
-    struct hmap match_flow_table;
-
-    /* SB uuid index for the nodes in match_flow_table.*/
-    struct hindex uuid_flow_table;
-};
-
-/* Interface for OVN main loop. */
-void ofctrl_init(struct ovn_extend_table *group_table,
-                 struct ovn_extend_table *meter_table,
-                 int inactivity_probe_interval);
-void ofctrl_run(const struct ovsrec_bridge *br_int,
-                struct shash *pending_ct_zones);
-enum mf_field_id ofctrl_get_mf_field_id(void);
-void ofctrl_put(struct ovn_desired_flow_table *,
-                struct shash *pending_ct_zones,
-                const struct sbrec_meter_table *,
-                int64_t nb_cfg,
-                bool flow_changed);
-void ofctrl_wait(void);
-void ofctrl_destroy(void);
-int64_t ofctrl_get_cur_cfg(void);
-
-struct ovn_flow *ofctrl_dup_flow(struct ovn_flow *source);
-
-void ofctrl_ct_flush_zone(uint16_t zone_id);
-
-char *ofctrl_inject_pkt(const struct ovsrec_bridge *br_int,
-                        const char *flow_s, const struct shash *addr_sets,
-                        const struct shash *port_groups);
-
-/* Flow table interfaces to the rest of ovn-controller. */
-void ofctrl_add_flow(struct ovn_desired_flow_table *, uint8_t table_id,
-                     uint16_t priority, uint64_t cookie,
-                     const struct match *, const struct ofpbuf *ofpacts,
-                     const struct uuid *);
-
-void ofctrl_remove_flows(struct ovn_desired_flow_table *, const struct uuid *);
-
-void ovn_desired_flow_table_init(struct ovn_desired_flow_table *);
-void ovn_desired_flow_table_clear(struct ovn_desired_flow_table *);
-void ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *);
-
-void ofctrl_check_and_add_flow(struct ovn_desired_flow_table *,
-                               uint8_t table_id, uint16_t priority,
-                               uint64_t cookie, const struct match *,
-                               const struct ofpbuf *ofpacts,
-                               const struct uuid *, bool log_duplicate_flow);
-
-bool ofctrl_is_connected(void);
-void ofctrl_set_probe_interval(int probe_interval);
-
-#endif /* ovn/ofctrl.h */
diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml
deleted file mode 100644
index 780625ff8..000000000
--- a/ovn/controller/ovn-controller.8.xml
+++ /dev/null
@@ -1,456 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-controller" section="8" title="ovn-controller">
-    <h1>Name</h1>
-    <p>ovn-controller -- Open Virtual Network local controller</p>
-
-    <h1>Synopsis</h1>
-    <p><code>ovn-controller</code> [<var>options</var>] [<var>ovs-database</var>]</p>
-
-    <h1>Description</h1>
-    <p>
-      <code>ovn-controller</code> is the local controller daemon for
-      OVN, the Open Virtual Network.  It connects up to the OVN
-      Southbound database (see <code>ovn-sb</code>(5)) over the OVSDB
-      protocol, and down to the Open vSwitch database (see
-      <code>ovs-vswitchd.conf.db</code>(5)) over the OVSDB protocol and
-      to <code>ovs-vswitchd</code>(8) via OpenFlow.  Each hypervisor and
-      software gateway in an OVN deployment runs its own independent
-      copy of <code>ovn-controller</code>; thus,
-      <code>ovn-controller</code>'s downward connections are
-      machine-local and do not run over a physical network.
-    </p>
-
-    <h1>ACL Logging</h1>
-    <p>
-      ACL log messages are logged through <code>ovn-controller</code>'s
-      logging mechanism.  ACL log entries have the module
-      <code>acl_log</code> at log level <code>info</code>.  Configuring
-      logging is described below in the <code>Logging Options</code>
-      section.
-    </p>
-
-    <h1>Options</h1>
-
-    <h2>Daemon Options</h2>
-    <xi:include href="lib/daemon.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h2>Logging Options</h2>
-    <xi:include href="lib/vlog.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h2>PKI Options</h2>
-    <p>
-      PKI configuration is required in order to use SSL for the connections to
-      the Northbound and Southbound databases.
-    </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"/>
-    <xi:include href="lib/ssl-peer-ca-cert.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"/>
-
-
-    <h1>Configuration</h1>
-    <p>
-      <code>ovn-controller</code> retrieves most of its configuration
-      information from the local Open vSwitch's ovsdb-server instance.
-      The default location is <code>db.sock</code> in the local Open
-      vSwitch's "run" directory.  It may be overridden by specifying the
-      <var>ovs-database</var> argument as an OVSDB active or passive
-      connection method, as described in <code>ovsdb</code>(7).
-    </p>
-
-    <p>
-      <code>ovn-controller</code> assumes it gets configuration
-      information from the following keys in the <code>Open_vSwitch</code>
-      table of the local OVS instance:
-    </p>
-    <dl>
-      <dt><code>external_ids:system-id</code></dt>
-      <dd>The chassis name to use in the Chassis table.</dd>
-
-      <dt><code>external_ids:hostname</code></dt>
-      <dd>The hostname to use in the Chassis table.</dd>
-
-      <dt><code>external_ids:ovn-bridge</code></dt>
-      <dd>
-        The integration bridge to which logical ports are attached.  The
-        default is <code>br-int</code>.  If this bridge does not exist when
-        ovn-controller starts, it will be created automatically with the
-        default configuration suggested in <code>ovn-architecture</code>(7).
-      </dd>
-
-      <dt><code>external_ids:ovn-bridge-datapath-type</code></dt>
-      <dd>
-        This configuration is optional. If set, then the datapath type of
-        the integration bridge will be set to the configured value. If this
-        option is not set, then <code>ovn-controller</code> will not modify
-        the existing <code>datapath-type</code> of the integration bridge.
-      </dd>
-
-      <dt><code>external_ids:ovn-remote</code></dt>
-      <dd>
-        <p>
-          The OVN database that this system should connect to for its
-          configuration, in one of the same forms documented above for the
-          <var>ovs-database</var>.
-        </p>
-      </dd>
-
-      <dt><code>external_ids:ovn-remote-probe-interval</code></dt>
-      <dd>
-        <p>
-          The inactivity probe interval of the connection to the OVN database,
-          in milliseconds.
-          If the value is zero, it disables the connection keepalive feature.
-        </p>
-
-        <p>
-          If the value is nonzero, then it will be forced to a value of
-          at least 1000 ms.
-        </p>
-      </dd>
-
-      <dt><code>external_ids:ovn-openflow-probe-interval</code></dt>
-      <dd>
-        <p>
-          The inactivity probe interval of the OpenFlow connection to the
-          OpenvSwitch integration bridge, in seconds.
-          If the value is zero, it disables the connection keepalive feature.
-        </p>
-
-        <p>
-          If the value is nonzero, then it will be forced to a value of
-          at least 5s.
-        </p>
-      </dd>
-
-      <dt><code>external_ids:ovn-encap-type</code></dt>
-      <dd>
-        <p>
-          The encapsulation type that a chassis should use to connect to
-          this node.  Multiple encapsulation types may be specified with
-          a comma-separated list.  Each listed encapsulation type will
-          be paired with <code>ovn-encap-ip</code>.
-        </p>
-
-        <p>
-          Supported tunnel types for connecting hypervisors
-          are <code>geneve</code> and <code>stt</code>.  Gateways may
-          use <code>geneve</code>, <code>vxlan</code>, or
-          <code>stt</code>.
-        </p>
-
-        <p>
-          Due to the limited amount of metadata in <code>vxlan</code>,
-          the capabilities and performance of connected gateways will be
-          reduced versus other tunnel formats.
-        </p>
-      </dd>
-
-      <dt><code>external_ids:ovn-encap-ip</code></dt>
-      <dd>
-        The IP address that a chassis should use to connect to this node
-        using encapsulation types specified by
-        <code>external_ids:ovn-encap-type</code>.
-      </dd>
-
-      <dt><code>external_ids:ovn-bridge-mappings</code></dt>
-      <dd>
-        A list of key-value pairs that map a physical network name to a local
-        ovs bridge that provides connectivity to that network.  An example
-        value mapping two physical network names to two ovs bridges would be:
-        <code>physnet1:br-eth0,physnet2:br-eth1</code>.
-      </dd>
-
-      <dt><code>external_ids:ovn-encap-csum</code></dt>
-      <dd>
-        <code>ovn-encap-csum</code> indicates that encapsulation checksums can
-        be transmitted and received with reasonable performance. It is a hint
-        to senders transmitting data to this chassis that they should use
-        checksums to protect OVN metadata. Set to <code>true</code> to enable
-        or <code>false</code> to disable. Depending on the capabilities of the
-        network interface card, enabling encapsulation checksum may incur
-        performance loss. In such cases, encapsulation checksums can be disabled.
-      </dd>
-
-      <dt><code>external_ids:ovn-cms-options</code></dt>
-      <dd>
-        A list of options that will be consumed by the CMS Plugin and which
-        specific to this particular chassis. An example would be:
-        <code>cms_option1,cms_option2:foo</code>.
-      </dd>
-
-      <dt><code>external_ids:ovn-transport-zones</code></dt>
-      <dd>
-        <p>
-          The transport zone(s) that this chassis belongs to. Transport
-          zones is a way to group different chassis so that tunnels are only
-          formed between members of the same group(s). Multiple transport
-          zones may be specified with a comma-separated list. For example:
-          tz1,tz2,tz3.
-        </p>
-        <p>
-          If not set, the Chassis will be considered part of a default
-          transport zone.
-        </p>
-      </dd>
-      <dt><code>external_ids:ovn-chassis-mac-mappings</code></dt>
-      <dd>
-        A list of key-value pairs that map a chassis specific mac to
-        a physical network name. An example
-        value mapping two chassis macs to two physical network names would be:
-        <code>physnet1:aa:bb:cc:dd:ee:ff,physnet2:a1:b2:c3:d4:e5:f6</code>.
-        These are the macs that ovn-controller will replace a router port
-        mac with, if packet is going from a distributed router port on
-        vlan type logical switch.
-      </dd>
-    </dl>
-
-    <p>
-      <code>ovn-controller</code> reads the following values from the
-      <code>Open_vSwitch</code> database of the local OVS instance:
-    </p>
-
-    <dl>
-      <dt><code>datapath-type</code> from <ref table="Bridge" db="Open_vSwitch"/> table</dt>
-      <dd>
-        This value is read from local OVS integration bridge row of
-        <ref table="Bridge" db="Open_vSwitch"/> table and populated in
-        <ref key="datapath-type" table="Chassis" column="external_ids"
-        db="OVN_Southbound"/> of the <ref table="Chassis" db="OVN_Southbound"/>
-        table in the OVN_Southbound database.
-      </dd>
-
-      <dt><code>iface-types</code> from <ref table="Open_vSwitch" db="Open_vSwitch"/> table</dt>
-      <dd>
-        This value is populated in <ref key="iface-types" table="Chassis"
-        column="external_ids" db="OVN_Southbound"/> of the
-        <ref table="Chassis" db="OVN_Southbound"/> table in the OVN_Southbound
-        database.
-      </dd>
-
-      <dt><code>private_key</code>, <code>certificate</code>,
-          <code>ca_cert</code>, and <code>bootstrap_ca_cert</code>
-          from <ref table="SSL" db="Open_vSwitch"/> table</dt>
-      <dd>
-        These values provide the SSL configuration used for connecting
-        to the OVN southbound database server when an SSL connection type
-        is configured via <code>external_ids:ovn-remote</code>.  Note that
-        this SSL configuration can also be provided via command-line options,
-        the configuration in the database takes precedence if both are present.
-      </dd>
-    </dl>
-
-    <h1>Open vSwitch Database Usage</h1>
-
-    <p>
-      <code>ovn-controller</code> uses a number of <code>external_ids</code>
-      keys in the Open vSwitch database to keep track of ports and interfaces.
-      For proper operation, users should not change or clear these keys:
-    </p>
-
-    <dl>
-      <dt>
-        <code>external_ids:ovn-chassis-id</code> in the <code>Port</code> table
-      </dt>
-      <dd>
-        The presence of this key identifies a tunnel port within the
-        integration bridge as one created by <code>ovn-controller</code> to
-        reach a remote chassis.  Its value is the chassis ID of the remote
-        chassis.
-      </dd>
-
-      <dt>
-        <code>external_ids:ct-zone-*</code> in the <code>Bridge</code> table
-      </dt>
-      <dd>
-        Logical ports and gateway routers are assigned a connection
-        tracking zone by <code>ovn-controller</code> for stateful
-        services.  To keep state across restarts of
-        <code>ovn-controller</code>, these keys are stored in the
-        integration bridge's Bridge table.  The name contains a prefix
-        of <code>ct-zone-</code> followed by the name of the logical
-        port or gateway router's zone key.  The value for this key
-        identifies the zone used for this port.
-      </dd>
-
-      <dt>
-        <code>external_ids:ovn-localnet-port</code> in the <code>Port</code>
-        table
-      </dt>
-      <dd>
-        <p>
-          The presence of this key identifies a patch port as one created by
-          <code>ovn-controller</code> to connect the integration bridge and
-          another bridge to implement a <code>localnet</code> logical port.
-          Its value is the name of the logical port with <code>type</code>
-          set to <code>localnet</code> that the port implements. See
-          <code>external_ids:ovn-bridge-mappings</code>, above, for more
-          information.
-        </p>
-
-        <p>
-          Each <code>localnet</code> logical port is implemented as a pair of
-          patch ports, one in the integration bridge, one in a different
-          bridge, with the same <code>external_ids:ovn-localnet-port</code>
-          value.
-        </p>
-      </dd>
-
-      <dt>
-        <code>external_ids:ovn-l2gateway-port</code> in the <code>Port</code>
-        table
-      </dt>
-      <dd>
-        <p>
-          The presence of this key identifies a patch port as one created by
-          <code>ovn-controller</code> to connect the integration bridge and
-          another bridge to implement a <code>l2gateway</code> logical port.
-          Its value is the name of the logical port with <code>type</code>
-          set to <code>l2gateway</code> that the port implements. See
-          <code>external_ids:ovn-bridge-mappings</code>, above, for more
-          information.
-        </p>
-
-        <p>
-          Each <code>l2gateway</code> logical port is implemented as a pair
-          of patch ports, one in the integration bridge, one in a different
-          bridge, with the same <code>external_ids:ovn-l2gateway-port</code>
-          value.
-        </p>
-      </dd>
-
-      <dt>
-        <code>external-ids:ovn-l3gateway-port</code> in the <code>Port</code>
-        table
-      </dt>
-
-      <dd>
-        <p>
-          This key identifies a patch port as one created by
-          <code>ovn-controller</code> to implement a <code>l3gateway</code>
-          logical port. Its value is the name of the logical port with type
-          set to <code>l3gateway</code>. This patch port is similar to
-          the OVN logical patch port, except that <code>l3gateway</code>
-          port can only be bound to a paticular chassis.
-        </p>
-      </dd>
-
-      <dt>
-        <code>external-ids:ovn-logical-patch-port</code> in the
-        <code>Port</code> table
-      </dt>
-
-      <dd>
-        <p>
-          This key identifies a patch port as one created by
-          <code>ovn-controller</code> to implement an OVN logical patch port
-          within the integration bridge.  Its value is the name of the OVN
-          logical patch port that it implements.
-        </p>
-      </dd>
-    </dl>
-
-    <h1>OVN Southbound Database Usage</h1>
-
-    <p>
-      <code>ovn-controller</code> reads from much of the
-      <code>OVN_Southbound</code> database to guide its operation.
-      <code>ovn-controller</code> also writes to the following tables:
-    </p>
-
-    <dl>
-      <dt><code>Chassis</code></dt>
-      <dd>
-        Upon startup, <code>ovn-controller</code> creates a row in this table
-        to represent its own chassis.  Upon graceful termination, e.g. with
-        <code>ovs-appctl -t ovn-controller exit</code> (but not
-        <code>SIGTERM</code>), <code>ovn-controller</code> removes its row.
-      </dd>
-
-      <dt><code>Encap</code></dt>
-      <dd>
-        Upon startup, <code>ovn-controller</code> creates a row or rows in this
-        table that represent the tunnel encapsulations by which its chassis can
-        be reached, and points its <code>Chassis</code> row to them.  Upon
-        graceful termination, <code>ovn-controller</code> removes these rows.
-      </dd>
-
-      <dt><code>Port_Binding</code></dt>
-      <dd>
-        At runtime, <code>ovn-controller</code> sets the <code>chassis</code>
-        columns of ports that are resident on its chassis to point to its
-        <code>Chassis</code> row, and, conversely, clears the
-        <code>chassis</code> column of ports that point to its
-        <code>Chassis</code> row but are no longer resident on its chassis.
-        The <code>chassis</code> column has a weak reference type, so when
-        <code>ovn-controller</code> gracefully exits and removes its
-        <code>Chassis</code> row, the database server automatically clears any
-        remaining references to that row.
-      </dd>
-
-      <dt><code>MAC_Binding</code></dt>
-      <dd>
-        At runtime, <code>ovn-controller</code> updates the
-        <code>MAC_Binding</code> table as instructed by <code>put_arp</code>
-        and <code>put_nd</code> logical actions.  These changes persist beyond
-        the lifetime of <code>ovn-controller</code>.
-      </dd>
-    </dl>
-
-    <h1>Runtime Management Commands</h1>
-    <p>
-      <code>ovs-appctl</code> can send commands to a running
-      <code>ovn-controller</code> process.  The currently supported
-      commands are described below.
-      <dl>
-      <dt><code>exit</code></dt>
-      <dd>
-        Causes <code>ovn-controller</code> to gracefully terminate.
-      </dd>
-
-      <dt><code>ct-zone-list</code></dt>
-      <dd>
-        Lists each local logical port and its connection tracking zone.
-      </dd>
-
-      <dt><code>meter-table-list</code></dt>
-      <dd>
-        Lists each meter table entry and its local meter id.
-      </dd>
-
-      <dt><code>group-table-list</code></dt>
-      <dd>
-        Lists each group table entry and its local group id.
-      </dd>
-
-      <dt><code>inject-pkt</code> <var>microflow</var></dt>
-      <dd>
-      <p>
-        Injects <var>microflow</var> into the connected Open vSwitch
-        instance.  <var>microflow</var> must contain an ingress logical
-        port (<code>inport</code> argument) that is present on the Open
-        vSwitch instance.
-      </p>
-
-      <p>
-        The <var>microflow</var> argument describes the packet whose
-        forwarding is to be simulated, in the syntax of an OVN logical
-        expression, as described in <code>ovn-sb</code>(5), to express
-        constraints.  The parser understands prerequisites; for example,
-        if the expression refers to <code>ip4.src</code>, there is no
-        need to explicitly state <code>ip4</code> or <code>eth.type ==
-        0x800</code>.
-      </p>
-      </dd>
-
-      <dt><code>connection-status</code></dt>
-      <dd>
-        Show OVN SBDB connection status for the chassis.
-      </dd>
-      </dl>
-    </p>
-
-</manpage>
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
deleted file mode 100644
index cf6c8ae79..000000000
--- a/ovn/controller/ovn-controller.c
+++ /dev/null
@@ -1,2366 +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 "ovn-controller.h"
-
-#include <errno.h>
-#include <getopt.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "bfd.h"
-#include "binding.h"
-#include "chassis.h"
-#include "command-line.h"
-#include "compiler.h"
-#include "daemon.h"
-#include "dirs.h"
-#include "openvswitch/dynamic-string.h"
-#include "encaps.h"
-#include "fatal-signal.h"
-#include "ip-mcast.h"
-#include "openvswitch/hmap.h"
-#include "lflow.h"
-#include "lib/vswitch-idl.h"
-#include "lport.h"
-#include "ofctrl.h"
-#include "openvswitch/vconn.h"
-#include "openvswitch/vlog.h"
-#include "ovn/actions.h"
-#include "ovn/lib/chassis-index.h"
-#include "ovn/lib/extend-table.h"
-#include "ovn/lib/ip-mcast-index.h"
-#include "ovn/lib/mcast-group-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "patch.h"
-#include "physical.h"
-#include "pinctrl.h"
-#include "openvswitch/poll-loop.h"
-#include "lib/bitmap.h"
-#include "lib/hash.h"
-#include "smap.h"
-#include "sset.h"
-#include "stream-ssl.h"
-#include "stream.h"
-#include "unixctl.h"
-#include "util.h"
-#include "timeval.h"
-#include "timer.h"
-#include "stopwatch.h"
-#include "ovn/lib/inc-proc-eng.h"
-
-VLOG_DEFINE_THIS_MODULE(main);
-
-static unixctl_cb_func ovn_controller_exit;
-static unixctl_cb_func ct_zone_list;
-static unixctl_cb_func meter_table_list;
-static unixctl_cb_func group_table_list;
-static unixctl_cb_func inject_pkt;
-static unixctl_cb_func ovn_controller_conn_show;
-
-#define DEFAULT_BRIDGE_NAME "br-int"
-#define DEFAULT_PROBE_INTERVAL_MSEC 5000
-#define OFCTRL_DEFAULT_PROBE_INTERVAL_SEC 5
-
-#define CONTROLLER_LOOP_STOPWATCH_NAME "ovn-controller-flow-generation"
-
-static char *parse_options(int argc, char *argv[]);
-OVS_NO_RETURN static void usage(void);
-
-/* Pending packet to be injected into connected OVS. */
-struct pending_pkt {
-    /* Setting 'conn' indicates that a request is pending. */
-    struct unixctl_conn *conn;
-    char *flow_s;
-};
-
-struct local_datapath *
-get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
-{
-    struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
-    return (node
-            ? CONTAINER_OF(node, struct local_datapath, hmap_node)
-            : NULL);
-}
-
-uint32_t
-get_tunnel_type(const char *name)
-{
-    if (!strcmp(name, "geneve")) {
-        return GENEVE;
-    } else if (!strcmp(name, "stt")) {
-        return STT;
-    } else if (!strcmp(name, "vxlan")) {
-        return VXLAN;
-    }
-
-    return 0;
-}
-
-const struct ovsrec_bridge *
-get_bridge(const struct ovsrec_bridge_table *bridge_table, const char *br_name)
-{
-    const struct ovsrec_bridge *br;
-    OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
-        if (!strcmp(br->name, br_name)) {
-            return br;
-        }
-    }
-    return NULL;
-}
-
-static void
-update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
-                   const struct sbrec_chassis *chassis,
-                   const struct sset *local_ifaces,
-                   struct hmap *local_datapaths)
-{
-    /* Monitor Port_Bindings rows for local interfaces and local datapaths.
-     *
-     * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for
-     * local datapaths.
-     *
-     * Monitor Controller_Event rows for local chassis.
-     *
-     * Monitor IP_Multicast for local datapaths.
-     *
-     * Monitor IGMP_Groups for local chassis.
-     *
-     * We always monitor patch ports because they allow us to see the linkages
-     * between related logical datapaths.  That way, when we know that we have
-     * a VIF on a particular logical switch, we immediately know to monitor all
-     * the connected logical routers and logical switches. */
-    struct ovsdb_idl_condition pb = OVSDB_IDL_CONDITION_INIT(&pb);
-    struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf);
-    struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb);
-    struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg);
-    struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns);
-    struct ovsdb_idl_condition ce =  OVSDB_IDL_CONDITION_INIT(&ce);
-    struct ovsdb_idl_condition ip_mcast = OVSDB_IDL_CONDITION_INIT(&ip_mcast);
-    struct ovsdb_idl_condition igmp = OVSDB_IDL_CONDITION_INIT(&igmp);
-    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "patch");
-    /* XXX: We can optimize this, if we find a way to only monitor
-     * ports that have a Gateway_Chassis that point's to our own
-     * chassis */
-    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect");
-    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external");
-    if (chassis) {
-        /* This should be mostly redundant with the other clauses for port
-         * bindings, but it allows us to catch any ports that are assigned to
-         * us but should not be.  That way, we can clear their chassis
-         * assignments. */
-        sbrec_port_binding_add_clause_chassis(&pb, OVSDB_F_EQ,
-                                              &chassis->header_.uuid);
-
-        /* Ensure that we find out about l2gateway and l3gateway ports that
-         * should be present on this chassis.  Otherwise, we might never find
-         * out about those ports, if their datapaths don't otherwise have a VIF
-         * in this chassis. */
-        const char *id = chassis->name;
-        const struct smap l2 = SMAP_CONST1(&l2, "l2gateway-chassis", id);
-        sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l2);
-        const struct smap l3 = SMAP_CONST1(&l3, "l3gateway-chassis", id);
-        sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l3);
-
-        sbrec_controller_event_add_clause_chassis(&ce, OVSDB_F_EQ,
-                                                  &chassis->header_.uuid);
-        sbrec_igmp_group_add_clause_chassis(&igmp, OVSDB_F_EQ,
-                                            &chassis->header_.uuid);
-    }
-    if (local_ifaces) {
-        const char *name;
-        SSET_FOR_EACH (name, local_ifaces) {
-            sbrec_port_binding_add_clause_logical_port(&pb, OVSDB_F_EQ, name);
-            sbrec_port_binding_add_clause_parent_port(&pb, OVSDB_F_EQ, name);
-        }
-    }
-    if (local_datapaths) {
-        const struct local_datapath *ld;
-        HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-            struct uuid *uuid = CONST_CAST(struct uuid *,
-                                           &ld->datapath->header_.uuid);
-            sbrec_port_binding_add_clause_datapath(&pb, OVSDB_F_EQ, uuid);
-            sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ,
-                                                           uuid);
-            sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid);
-            sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid);
-            sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1);
-            sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ,
-                                                   uuid);
-        }
-    }
-    sbrec_port_binding_set_condition(ovnsb_idl, &pb);
-    sbrec_logical_flow_set_condition(ovnsb_idl, &lf);
-    sbrec_mac_binding_set_condition(ovnsb_idl, &mb);
-    sbrec_multicast_group_set_condition(ovnsb_idl, &mg);
-    sbrec_dns_set_condition(ovnsb_idl, &dns);
-    sbrec_controller_event_set_condition(ovnsb_idl, &ce);
-    sbrec_ip_multicast_set_condition(ovnsb_idl, &ip_mcast);
-    sbrec_igmp_group_set_condition(ovnsb_idl, &igmp);
-    ovsdb_idl_condition_destroy(&pb);
-    ovsdb_idl_condition_destroy(&lf);
-    ovsdb_idl_condition_destroy(&mb);
-    ovsdb_idl_condition_destroy(&mg);
-    ovsdb_idl_condition_destroy(&dns);
-    ovsdb_idl_condition_destroy(&ce);
-    ovsdb_idl_condition_destroy(&ip_mcast);
-    ovsdb_idl_condition_destroy(&igmp);
-}
-
-static const char *
-br_int_name(const struct ovsrec_open_vswitch *cfg)
-{
-    return smap_get_def(&cfg->external_ids, "ovn-bridge", DEFAULT_BRIDGE_NAME);
-}
-
-static const struct ovsrec_bridge *
-create_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
-              const struct ovsrec_open_vswitch_table *ovs_table)
-{
-    if (!ovs_idl_txn) {
-        return NULL;
-    }
-
-    const struct ovsrec_open_vswitch *cfg;
-    cfg = ovsrec_open_vswitch_table_first(ovs_table);
-    if (!cfg) {
-        return NULL;
-    }
-    const char *bridge_name = br_int_name(cfg);
-
-    ovsdb_idl_txn_add_comment(ovs_idl_txn,
-            "ovn-controller: creating integration bridge '%s'", bridge_name);
-
-    struct ovsrec_interface *iface;
-    iface = ovsrec_interface_insert(ovs_idl_txn);
-    ovsrec_interface_set_name(iface, bridge_name);
-    ovsrec_interface_set_type(iface, "internal");
-
-    struct ovsrec_port *port;
-    port = ovsrec_port_insert(ovs_idl_txn);
-    ovsrec_port_set_name(port, bridge_name);
-    ovsrec_port_set_interfaces(port, &iface, 1);
-
-    struct ovsrec_bridge *bridge;
-    bridge = ovsrec_bridge_insert(ovs_idl_txn);
-    ovsrec_bridge_set_name(bridge, bridge_name);
-    ovsrec_bridge_set_fail_mode(bridge, "secure");
-    const struct smap oc = SMAP_CONST1(&oc, "disable-in-band", "true");
-    ovsrec_bridge_set_other_config(bridge, &oc);
-    ovsrec_bridge_set_ports(bridge, &port, 1);
-
-    struct ovsrec_bridge **bridges;
-    size_t bytes = sizeof *bridges * cfg->n_bridges;
-    bridges = xmalloc(bytes + sizeof *bridges);
-    memcpy(bridges, cfg->bridges, bytes);
-    bridges[cfg->n_bridges] = bridge;
-    ovsrec_open_vswitch_verify_bridges(cfg);
-    ovsrec_open_vswitch_set_bridges(cfg, bridges, cfg->n_bridges + 1);
-    free(bridges);
-
-    return bridge;
-}
-
-static const struct ovsrec_bridge *
-get_br_int(const struct ovsrec_bridge_table *bridge_table,
-           const struct ovsrec_open_vswitch_table *ovs_table)
-{
-    const struct ovsrec_open_vswitch *cfg;
-    cfg = ovsrec_open_vswitch_table_first(ovs_table);
-    if (!cfg) {
-        return NULL;
-    }
-
-    return get_bridge(bridge_table, br_int_name(cfg));
-}
-
-static const struct ovsrec_bridge *
-process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
-               const struct ovsrec_bridge_table *bridge_table,
-               const struct ovsrec_open_vswitch_table *ovs_table)
-{
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
-                                                    ovs_table);
-    if (!br_int) {
-        br_int = create_br_int(ovs_idl_txn, ovs_table);
-    }
-    if (br_int && ovs_idl_txn) {
-        const struct ovsrec_open_vswitch *cfg;
-        cfg = ovsrec_open_vswitch_table_first(ovs_table);
-        ovs_assert(cfg);
-        const char *datapath_type = smap_get(&cfg->external_ids,
-                                             "ovn-bridge-datapath-type");
-        /* Check for the datapath_type and set it only if it is defined in
-         * cfg. */
-        if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) {
-            ovsrec_bridge_set_datapath_type(br_int, datapath_type);
-        }
-    }
-    return br_int;
-}
-
-static const char *
-get_ovs_chassis_id(const struct ovsrec_open_vswitch_table *ovs_table)
-{
-    const struct ovsrec_open_vswitch *cfg
-        = ovsrec_open_vswitch_table_first(ovs_table);
-    const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id")
-                                 : NULL;
-
-    if (!chassis_id) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing.");
-    }
-
-    return chassis_id;
-}
-
-/* Iterate address sets in the southbound database.  Create and update the
- * corresponding symtab entries as necessary. */
-static void
-addr_sets_init(const struct sbrec_address_set_table *address_set_table,
-               struct shash *addr_sets)
-{
-    const struct sbrec_address_set *as;
-    SBREC_ADDRESS_SET_TABLE_FOR_EACH (as, address_set_table) {
-        expr_const_sets_add(addr_sets, as->name,
-                            (const char *const *) as->addresses,
-                            as->n_addresses, true);
-    }
-}
-
-static void
-addr_sets_update(const struct sbrec_address_set_table *address_set_table,
-                 struct shash *addr_sets, struct sset *new,
-                 struct sset *deleted, struct sset *updated)
-{
-    const struct sbrec_address_set *as;
-    SBREC_ADDRESS_SET_TABLE_FOR_EACH_TRACKED (as, address_set_table) {
-        if (sbrec_address_set_is_deleted(as)) {
-            expr_const_sets_remove(addr_sets, as->name);
-            sset_add(deleted, as->name);
-        } else {
-            expr_const_sets_add(addr_sets, as->name,
-                                (const char *const *) as->addresses,
-                                as->n_addresses, true);
-            if (sbrec_address_set_is_new(as)) {
-                sset_add(new, as->name);
-            } else {
-                sset_add(updated, as->name);
-            }
-        }
-    }
-}
-
-/* Iterate port groups in the southbound database.  Create and update the
- * corresponding symtab entries as necessary. */
- static void
-port_groups_init(const struct sbrec_port_group_table *port_group_table,
-                 struct shash *port_groups)
-{
-    const struct sbrec_port_group *pg;
-    SBREC_PORT_GROUP_TABLE_FOR_EACH (pg, port_group_table) {
-        expr_const_sets_add(port_groups, pg->name,
-                            (const char *const *) pg->ports,
-                            pg->n_ports, false);
-    }
-}
-
-static void
-port_groups_update(const struct sbrec_port_group_table *port_group_table,
-                   struct shash *port_groups, struct sset *new,
-                   struct sset *deleted, struct sset *updated)
-{
-    const struct sbrec_port_group *pg;
-    SBREC_PORT_GROUP_TABLE_FOR_EACH_TRACKED (pg, port_group_table) {
-        if (sbrec_port_group_is_deleted(pg)) {
-            expr_const_sets_remove(port_groups, pg->name);
-            sset_add(deleted, pg->name);
-        } else {
-            expr_const_sets_add(port_groups, pg->name,
-                                (const char *const *) pg->ports,
-                                pg->n_ports, false);
-            if (sbrec_port_group_is_new(pg)) {
-                sset_add(new, pg->name);
-            } else {
-                sset_add(updated, pg->name);
-            }
-        }
-    }
-}
-
-static void
-update_ssl_config(const struct ovsrec_ssl_table *ssl_table)
-{
-    const struct ovsrec_ssl *ssl = ovsrec_ssl_table_first(ssl_table);
-
-    if (ssl) {
-        stream_ssl_set_key_and_cert(ssl->private_key, ssl->certificate);
-        stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
-    }
-}
-
-static int
-get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl)
-{
-    const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
-    return smap_get_int(&cfg->external_ids,
-                        "ovn-openflow-probe-interval",
-                        OFCTRL_DEFAULT_PROBE_INTERVAL_SEC);
-}
-
-/* Retrieves the pointer to the OVN Southbound database from 'ovs_idl' and
- * updates 'sbdb_idl' with that pointer. */
-static void
-update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl)
-{
-    const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
-
-    /* Set remote based on user configuration. */
-    const char *remote = NULL;
-    if (cfg) {
-        remote = smap_get(&cfg->external_ids, "ovn-remote");
-    }
-    ovsdb_idl_set_remote(ovnsb_idl, remote, true);
-
-    /* Set probe interval, based on user configuration and the remote. */
-    int default_interval = (remote && !stream_or_pstream_needs_probes(remote)
-                            ? 0 : DEFAULT_PROBE_INTERVAL_MSEC);
-    int interval = smap_get_int(&cfg->external_ids,
-                                "ovn-remote-probe-interval", default_interval);
-    ovsdb_idl_set_probe_interval(ovnsb_idl, interval);
-}
-
-static void
-update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths,
-                struct simap *ct_zones, unsigned long *ct_zone_bitmap,
-                struct shash *pending_ct_zones)
-{
-    struct simap_node *ct_zone, *ct_zone_next;
-    int scan_start = 1;
-    const char *user;
-    struct sset all_users = SSET_INITIALIZER(&all_users);
-
-    SSET_FOR_EACH(user, lports) {
-        sset_add(&all_users, user);
-    }
-
-    /* Local patched datapath (gateway routers) need zones assigned. */
-    const struct local_datapath *ld;
-    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-        /* XXX Add method to limit zone assignment to logical router
-         * datapaths with NAT */
-        char *dnat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "dnat");
-        char *snat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "snat");
-        sset_add(&all_users, dnat);
-        sset_add(&all_users, snat);
-        free(dnat);
-        free(snat);
-    }
-
-    /* Delete zones that do not exist in above sset. */
-    SIMAP_FOR_EACH_SAFE(ct_zone, ct_zone_next, ct_zones) {
-        if (!sset_contains(&all_users, ct_zone->name)) {
-            VLOG_DBG("removing ct zone %"PRId32" for '%s'",
-                     ct_zone->data, ct_zone->name);
-
-            struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending);
-            pending->state = CT_ZONE_DB_QUEUED; /* Skip flushing zone. */
-            pending->zone = ct_zone->data;
-            pending->add = false;
-            shash_add(pending_ct_zones, ct_zone->name, pending);
-
-            bitmap_set0(ct_zone_bitmap, ct_zone->data);
-            simap_delete(ct_zones, ct_zone);
-        }
-    }
-
-    /* xxx This is wasteful to assign a zone to each port--even if no
-     * xxx security policy is applied. */
-
-    /* Assign a unique zone id for each logical port and two zones
-     * to a gateway router. */
-    SSET_FOR_EACH(user, &all_users) {
-        int zone;
-
-        if (simap_contains(ct_zones, user)) {
-            continue;
-        }
-
-        /* We assume that there are 64K zones and that we own them all. */
-        zone = bitmap_scan(ct_zone_bitmap, 0, scan_start, MAX_CT_ZONES + 1);
-        if (zone == MAX_CT_ZONES + 1) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl, "exhausted all ct zones");
-            return;
-        }
-        scan_start = zone + 1;
-
-        VLOG_DBG("assigning ct zone %"PRId32" to '%s'", zone, user);
-
-        struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending);
-        pending->state = CT_ZONE_OF_QUEUED;
-        pending->zone = zone;
-        pending->add = true;
-        shash_add(pending_ct_zones, user, pending);
-
-        bitmap_set1(ct_zone_bitmap, zone);
-        simap_put(ct_zones, user, zone);
-    }
-
-    sset_destroy(&all_users);
-}
-
-static void
-commit_ct_zones(const struct ovsrec_bridge *br_int,
-                struct shash *pending_ct_zones)
-{
-    struct smap new_ids;
-    smap_clone(&new_ids, &br_int->external_ids);
-
-    bool updated = false;
-    struct shash_node *iter;
-    SHASH_FOR_EACH(iter, pending_ct_zones) {
-        struct ct_zone_pending_entry *ctzpe = iter->data;
-
-        /* The transaction is open, so any pending entries in the
-         * CT_ZONE_DB_QUEUED must be sent and any in CT_ZONE_DB_QUEUED
-         * need to be retried. */
-        if (ctzpe->state != CT_ZONE_DB_QUEUED
-            && ctzpe->state != CT_ZONE_DB_SENT) {
-            continue;
-        }
-
-        char *user_str = xasprintf("ct-zone-%s", iter->name);
-        if (ctzpe->add) {
-            char *zone_str = xasprintf("%"PRId32, ctzpe->zone);
-            smap_replace(&new_ids, user_str, zone_str);
-            free(zone_str);
-        } else {
-            smap_remove(&new_ids, user_str);
-        }
-        free(user_str);
-
-        ctzpe->state = CT_ZONE_DB_SENT;
-        updated = true;
-    }
-
-    if (updated) {
-        ovsrec_bridge_verify_external_ids(br_int);
-        ovsrec_bridge_set_external_ids(br_int, &new_ids);
-    }
-    smap_destroy(&new_ids);
-}
-
-static void
-restore_ct_zones(const struct ovsrec_bridge_table *bridge_table,
-                 const struct ovsrec_open_vswitch_table *ovs_table,
-                 struct simap *ct_zones, unsigned long *ct_zone_bitmap)
-{
-    const struct ovsrec_open_vswitch *cfg;
-    cfg = ovsrec_open_vswitch_table_first(ovs_table);
-    if (!cfg) {
-        return;
-    }
-
-    const struct ovsrec_bridge *br_int;
-    br_int = get_bridge(bridge_table, br_int_name(cfg));
-    if (!br_int) {
-        /* If the integration bridge hasn't been defined, assume that
-         * any existing ct-zone definitions aren't valid. */
-        return;
-    }
-
-    struct smap_node *node;
-    SMAP_FOR_EACH(node, &br_int->external_ids) {
-        if (strncmp(node->key, "ct-zone-", 8)) {
-            continue;
-        }
-
-        const char *user = node->key + 8;
-        int zone = atoi(node->value);
-
-        if (user[0] && zone) {
-            VLOG_DBG("restoring ct zone %"PRId32" for '%s'", zone, user);
-            bitmap_set1(ct_zone_bitmap, zone);
-            simap_put(ct_zones, user, zone);
-        }
-    }
-}
-
-static int64_t
-get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table)
-{
-    const struct sbrec_sb_global *sb
-        = sbrec_sb_global_table_first(sb_global_table);
-    return sb ? sb->nb_cfg : 0;
-}
-
-static const char *
-get_transport_zones(const struct ovsrec_open_vswitch_table *ovs_table)
-{
-    const struct ovsrec_open_vswitch *cfg
-        = ovsrec_open_vswitch_table_first(ovs_table);
-    return smap_get_def(&cfg->external_ids, "ovn-transport-zones", "");
-}
-
-static void
-ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    /* We do not monitor all tables by default, so modules must register
-     * their interest explicitly. */
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_fail_mode);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_other_config);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_external_ids);
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_ssl);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_bootstrap_ca_cert);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key);
-    chassis_register_ovs_idl(ovs_idl);
-    encaps_register_ovs_idl(ovs_idl);
-    binding_register_ovs_idl(ovs_idl);
-    bfd_register_ovs_idl(ovs_idl);
-    physical_register_ovs_idl(ovs_idl);
-}
-
-#define SB_NODES \
-    SB_NODE(chassis, "chassis") \
-    SB_NODE(encap, "encap") \
-    SB_NODE(address_set, "address_set") \
-    SB_NODE(port_group, "port_group") \
-    SB_NODE(multicast_group, "multicast_group") \
-    SB_NODE(datapath_binding, "datapath_binding") \
-    SB_NODE(port_binding, "port_binding") \
-    SB_NODE(mac_binding, "mac_binding") \
-    SB_NODE(logical_flow, "logical_flow") \
-    SB_NODE(dhcp_options, "dhcp_options") \
-    SB_NODE(dhcpv6_options, "dhcpv6_options") \
-    SB_NODE(dns, "dns")
-
-enum sb_engine_node {
-#define SB_NODE(NAME, NAME_STR) SB_##NAME,
-    SB_NODES
-#undef SB_NODE
-};
-
-#define SB_NODE(NAME, NAME_STR) ENGINE_FUNC_SB(NAME);
-    SB_NODES
-#undef SB_NODE
-
-#define OVS_NODES \
-    OVS_NODE(open_vswitch, "open_vswitch") \
-    OVS_NODE(bridge, "bridge") \
-    OVS_NODE(port, "port") \
-    OVS_NODE(qos, "qos")
-
-enum ovs_engine_node {
-#define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
-    OVS_NODES
-#undef OVS_NODE
-};
-
-#define OVS_NODE(NAME, NAME_STR) ENGINE_FUNC_OVS(NAME);
-    OVS_NODES
-#undef OVS_NODE
-
-struct ed_type_ofctrl_is_connected {
-    bool connected;
-};
-
-static void
-en_ofctrl_is_connected_init(struct engine_node *node)
-{
-    struct ed_type_ofctrl_is_connected *data =
-        (struct ed_type_ofctrl_is_connected *)node->data;
-    data->connected = false;
-}
-
-static void
-en_ofctrl_is_connected_cleanup(struct engine_node *node OVS_UNUSED)
-{
-}
-
-static void
-en_ofctrl_is_connected_run(struct engine_node *node)
-{
-    struct ed_type_ofctrl_is_connected *data =
-        (struct ed_type_ofctrl_is_connected *)node->data;
-    if (data->connected != ofctrl_is_connected()) {
-        data->connected = !data->connected;
-        node->changed = true;
-        return;
-    }
-    node->changed = false;
-}
-
-struct ed_type_addr_sets {
-    struct shash addr_sets;
-    bool change_tracked;
-    struct sset new;
-    struct sset deleted;
-    struct sset updated;
-};
-
-static void
-en_addr_sets_init(struct engine_node *node)
-{
-    struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data;
-    shash_init(&as->addr_sets);
-    as->change_tracked = false;
-    sset_init(&as->new);
-    sset_init(&as->deleted);
-    sset_init(&as->updated);
-}
-
-static void
-en_addr_sets_cleanup(struct engine_node *node)
-{
-    struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data;
-    expr_const_sets_destroy(&as->addr_sets);
-    shash_destroy(&as->addr_sets);
-    sset_destroy(&as->new);
-    sset_destroy(&as->deleted);
-    sset_destroy(&as->updated);
-}
-
-static void
-en_addr_sets_run(struct engine_node *node)
-{
-    struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data;
-
-    sset_clear(&as->new);
-    sset_clear(&as->deleted);
-    sset_clear(&as->updated);
-    expr_const_sets_destroy(&as->addr_sets);
-
-    struct sbrec_address_set_table *as_table =
-        (struct sbrec_address_set_table *)EN_OVSDB_GET(
-            engine_get_input("SB_address_set", node));
-
-    addr_sets_init(as_table, &as->addr_sets);
-
-    as->change_tracked = false;
-    node->changed = true;
-}
-
-static bool
-addr_sets_sb_address_set_handler(struct engine_node *node)
-{
-    struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data;
-
-    sset_clear(&as->new);
-    sset_clear(&as->deleted);
-    sset_clear(&as->updated);
-
-    struct sbrec_address_set_table *as_table =
-        (struct sbrec_address_set_table *)EN_OVSDB_GET(
-            engine_get_input("SB_address_set", node));
-
-    addr_sets_update(as_table, &as->addr_sets, &as->new,
-                     &as->deleted, &as->updated);
-
-    node->changed = !sset_is_empty(&as->new) || !sset_is_empty(&as->deleted)
-                    || !sset_is_empty(&as->updated);
-
-    as->change_tracked = true;
-    node->changed = true;
-    return true;
-}
-
-struct ed_type_port_groups{
-    struct shash port_groups;
-    bool change_tracked;
-    struct sset new;
-    struct sset deleted;
-    struct sset updated;
-};
-
-static void
-en_port_groups_init(struct engine_node *node)
-{
-    struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data;
-    shash_init(&pg->port_groups);
-    pg->change_tracked = false;
-    sset_init(&pg->new);
-    sset_init(&pg->deleted);
-    sset_init(&pg->updated);
-}
-
-static void
-en_port_groups_cleanup(struct engine_node *node)
-{
-    struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data;
-    expr_const_sets_destroy(&pg->port_groups);
-    shash_destroy(&pg->port_groups);
-    sset_destroy(&pg->new);
-    sset_destroy(&pg->deleted);
-    sset_destroy(&pg->updated);
-}
-
-static void
-en_port_groups_run(struct engine_node *node)
-{
-    struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data;
-
-    sset_clear(&pg->new);
-    sset_clear(&pg->deleted);
-    sset_clear(&pg->updated);
-    expr_const_sets_destroy(&pg->port_groups);
-
-    struct sbrec_port_group_table *pg_table =
-        (struct sbrec_port_group_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_group", node));
-
-    port_groups_init(pg_table, &pg->port_groups);
-
-    pg->change_tracked = false;
-    node->changed = true;
-}
-
-static bool
-port_groups_sb_port_group_handler(struct engine_node *node)
-{
-    struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data;
-
-    sset_clear(&pg->new);
-    sset_clear(&pg->deleted);
-    sset_clear(&pg->updated);
-
-    struct sbrec_port_group_table *pg_table =
-        (struct sbrec_port_group_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_group", node));
-
-    port_groups_update(pg_table, &pg->port_groups, &pg->new,
-                     &pg->deleted, &pg->updated);
-
-    node->changed = !sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted)
-                    || !sset_is_empty(&pg->updated);
-
-    pg->change_tracked = true;
-    node->changed = true;
-    return true;
-}
-
-struct ed_type_runtime_data {
-    /* Contains "struct local_datapath" nodes. */
-    struct hmap local_datapaths;
-
-    /* Contains the name of each logical port resident on the local
-     * hypervisor.  These logical ports include the VIFs (and their child
-     * logical ports, if any) that belong to VMs running on the hypervisor,
-     * l2gateway ports for which options:l2gateway-chassis designates the
-     * local hypervisor, and localnet ports. */
-    struct sset local_lports;
-
-    /* Contains the same ports as local_lports, but in the format:
-     * <datapath-tunnel-key>_<port-tunnel-key> */
-    struct sset local_lport_ids;
-    struct sset active_tunnels;
-
-    /* connection tracking zones. */
-    unsigned long ct_zone_bitmap[BITMAP_N_LONGS(MAX_CT_ZONES)];
-    struct shash pending_ct_zones;
-    struct simap ct_zones;
-};
-
-static void
-en_runtime_data_init(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)node->data;
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    hmap_init(&data->local_datapaths);
-    sset_init(&data->local_lports);
-    sset_init(&data->local_lport_ids);
-    sset_init(&data->active_tunnels);
-    shash_init(&data->pending_ct_zones);
-    simap_init(&data->ct_zones);
-
-    /* Initialize connection tracking zones. */
-    memset(data->ct_zone_bitmap, 0, sizeof data->ct_zone_bitmap);
-    bitmap_set1(data->ct_zone_bitmap, 0); /* Zone 0 is reserved. */
-    restore_ct_zones(bridge_table, ovs_table,
-                     &data->ct_zones, data->ct_zone_bitmap);
-}
-
-static void
-en_runtime_data_cleanup(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)node->data;
-
-    sset_destroy(&data->local_lports);
-    sset_destroy(&data->local_lport_ids);
-    sset_destroy(&data->active_tunnels);
-    struct local_datapath *cur_node, *next_node;
-    HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
-                        &data->local_datapaths) {
-        free(cur_node->peer_ports);
-        hmap_remove(&data->local_datapaths, &cur_node->hmap_node);
-        free(cur_node);
-    }
-    hmap_destroy(&data->local_datapaths);
-
-    simap_destroy(&data->ct_zones);
-    shash_destroy(&data->pending_ct_zones);
-}
-
-static void
-en_runtime_data_run(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)node->data;
-    struct hmap *local_datapaths = &data->local_datapaths;
-    struct sset *local_lports = &data->local_lports;
-    struct sset *local_lport_ids = &data->local_lport_ids;
-    struct sset *active_tunnels = &data->active_tunnels;
-    unsigned long *ct_zone_bitmap = data->ct_zone_bitmap;
-    struct shash *pending_ct_zones = &data->pending_ct_zones;
-    struct simap *ct_zones = &data->ct_zones;
-
-    static bool first_run = true;
-    if (first_run) {
-        /* don't cleanup since there is no data yet */
-        first_run = false;
-    } else {
-        struct local_datapath *cur_node, *next_node;
-        HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) {
-            free(cur_node->peer_ports);
-            hmap_remove(local_datapaths, &cur_node->hmap_node);
-            free(cur_node);
-        }
-        hmap_clear(local_datapaths);
-        sset_destroy(local_lports);
-        sset_destroy(local_lport_ids);
-        sset_destroy(active_tunnels);
-        sset_init(local_lports);
-        sset_init(local_lport_ids);
-        sset_init(active_tunnels);
-    }
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const char *chassis_id = get_ovs_chassis_id(ovs_table);
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-
-    ovs_assert(br_int && chassis_id);
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-
-    const struct sbrec_chassis *chassis
-        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    ovs_assert(chassis);
-
-    struct ed_type_ofctrl_is_connected *ed_ofctrl_is_connected =
-        (struct ed_type_ofctrl_is_connected *)engine_get_input(
-            "ofctrl_is_connected", node)->data;
-    if (ed_ofctrl_is_connected->connected) {
-        /* Calculate the active tunnels only if have an an active
-         * OpenFlow connection to br-int.
-         * If we don't have a connection to br-int, it could mean
-         * ovs-vswitchd is down for some reason and the BFD status
-         * in the Interface rows could be stale. So its better to
-         * consider 'active_tunnels' set to be empty if it's not
-         * connected. */
-        bfd_calculate_active_tunnels(br_int, active_tunnels);
-    }
-
-    struct ovsrec_port_table *port_table =
-        (struct ovsrec_port_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_port", node));
-
-    struct ovsrec_qos_table *qos_table =
-        (struct ovsrec_qos_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_qos", node));
-
-    struct sbrec_port_binding_table *pb_table =
-        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_binding", node));
-
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_datapath_binding", node),
-                "key");
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "datapath");
-
-    binding_run(engine_get_context()->ovnsb_idl_txn,
-                engine_get_context()->ovs_idl_txn,
-                sbrec_datapath_binding_by_key,
-                sbrec_port_binding_by_datapath,
-                sbrec_port_binding_by_name,
-                port_table, qos_table, pb_table,
-                br_int, chassis,
-                active_tunnels, local_datapaths,
-                local_lports, local_lport_ids);
-
-    update_ct_zones(local_lports, local_datapaths, ct_zones,
-                    ct_zone_bitmap, pending_ct_zones);
-
-    node->changed = true;
-}
-
-static bool
-runtime_data_sb_port_binding_handler(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)node->data;
-    struct sset *local_lports = &data->local_lports;
-    struct sset *active_tunnels = &data->active_tunnels;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const char *chassis_id = chassis_get_id();
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-
-    ovs_assert(br_int && chassis_id);
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-
-    const struct sbrec_chassis *chassis
-        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    ovs_assert(chassis);
-
-    struct sbrec_port_binding_table *pb_table =
-        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_binding", node));
-
-    bool changed = binding_evaluate_port_binding_changes(
-        pb_table, br_int, chassis, active_tunnels, local_lports);
-
-    return !changed;
-}
-
-struct ed_type_mff_ovn_geneve {
-    enum mf_field_id mff_ovn_geneve;
-};
-
-static void
-en_mff_ovn_geneve_init(struct engine_node *node)
-{
-    struct ed_type_mff_ovn_geneve *data =
-        (struct ed_type_mff_ovn_geneve *)node->data;
-    data->mff_ovn_geneve = 0;
-}
-
-static void
-en_mff_ovn_geneve_cleanup(struct engine_node *node OVS_UNUSED)
-{
-}
-
-static void
-en_mff_ovn_geneve_run(struct engine_node *node)
-{
-    struct ed_type_mff_ovn_geneve *data =
-        (struct ed_type_mff_ovn_geneve *)node->data;
-    enum mf_field_id mff_ovn_geneve = ofctrl_get_mf_field_id();
-    if (data->mff_ovn_geneve != mff_ovn_geneve) {
-        data->mff_ovn_geneve = mff_ovn_geneve;
-        node->changed = true;
-        return;
-    }
-    node->changed = false;
-}
-
-struct ed_type_flow_output {
-    /* desired flows */
-    struct ovn_desired_flow_table flow_table;
-    /* group ids for load balancing */
-    struct ovn_extend_table group_table;
-    /* meter ids for QoS */
-    struct ovn_extend_table meter_table;
-    /* conjunction id offset */
-    uint32_t conj_id_ofs;
-    /* lflow resource cross reference */
-    struct lflow_resource_ref lflow_resource_ref;
-};
-
-static void
-en_flow_output_init(struct engine_node *node)
-{
-    struct ed_type_flow_output *data =
-        (struct ed_type_flow_output *)node->data;
-    ovn_desired_flow_table_init(&data->flow_table);
-    ovn_extend_table_init(&data->group_table);
-    ovn_extend_table_init(&data->meter_table);
-    data->conj_id_ofs = 1;
-    lflow_resource_init(&data->lflow_resource_ref);
-}
-
-static void
-en_flow_output_cleanup(struct engine_node *node)
-{
-    struct ed_type_flow_output *data =
-        (struct ed_type_flow_output *)node->data;
-    ovn_desired_flow_table_destroy(&data->flow_table);
-    ovn_extend_table_destroy(&data->group_table);
-    ovn_extend_table_destroy(&data->meter_table);
-    lflow_resource_destroy(&data->lflow_resource_ref);
-}
-
-static void
-en_flow_output_run(struct engine_node *node)
-{
-    struct ed_type_runtime_data *rt_data =
-        (struct ed_type_runtime_data *)engine_get_input(
-            "runtime_data", node)->data;
-    struct hmap *local_datapaths = &rt_data->local_datapaths;
-    struct sset *local_lports = &rt_data->local_lports;
-    struct sset *local_lport_ids = &rt_data->local_lport_ids;
-    struct sset *active_tunnels = &rt_data->active_tunnels;
-    struct simap *ct_zones = &rt_data->ct_zones;
-
-    struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
-        (struct ed_type_mff_ovn_geneve *)engine_get_input(
-            "mff_ovn_geneve", node)->data;
-    enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-    const char *chassis_id = chassis_get_id();
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-    struct ed_type_addr_sets *as_data =
-        (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data;
-    struct shash *addr_sets = &as_data->addr_sets;
-
-    struct ed_type_port_groups *pg_data =
-        (struct ed_type_port_groups *)engine_get_input(
-            "port_groups", node)->data;
-    struct shash *port_groups = &pg_data->port_groups;
-
-    const struct sbrec_chassis *chassis = NULL;
-    if (chassis_id) {
-        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    }
-
-    ovs_assert(br_int && chassis);
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-    struct ovn_extend_table *group_table = &fo->group_table;
-    struct ovn_extend_table *meter_table = &fo->meter_table;
-    uint32_t *conj_id_ofs = &fo->conj_id_ofs;
-    struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref;
-
-    static bool first_run = true;
-    if (first_run) {
-        first_run = false;
-    } else {
-        ovn_desired_flow_table_clear(flow_table);
-        ovn_extend_table_clear(group_table, false /* desired */);
-        ovn_extend_table_clear(meter_table, false /* desired */);
-        lflow_resource_clear(lfrr);
-    }
-
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_multicast_group", node),
-                "name_datapath");
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct sbrec_dhcp_options_table *dhcp_table =
-        (struct sbrec_dhcp_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcp_options", node));
-
-    struct sbrec_dhcpv6_options_table *dhcpv6_table =
-        (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcpv6_options", node));
-
-    struct sbrec_logical_flow_table *logical_flow_table =
-        (struct sbrec_logical_flow_table *)EN_OVSDB_GET(
-            engine_get_input("SB_logical_flow", node));
-
-    struct sbrec_mac_binding_table *mac_binding_table =
-        (struct sbrec_mac_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_mac_binding", node));
-
-    *conj_id_ofs = 1;
-    lflow_run(sbrec_multicast_group_by_name_datapath,
-              sbrec_port_binding_by_name,
-              dhcp_table, dhcpv6_table,
-              logical_flow_table,
-              mac_binding_table,
-              chassis, local_datapaths, addr_sets,
-              port_groups, active_tunnels, local_lport_ids,
-              flow_table, group_table, meter_table, lfrr,
-              conj_id_ofs);
-
-    struct sbrec_multicast_group_table *multicast_group_table =
-        (struct sbrec_multicast_group_table *)EN_OVSDB_GET(
-            engine_get_input("SB_multicast_group", node));
-
-    struct sbrec_port_binding_table *port_binding_table =
-        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_binding", node));
-
-    physical_run(sbrec_port_binding_by_name,
-                 multicast_group_table,
-                 port_binding_table,
-                 mff_ovn_geneve,
-                 br_int, chassis, ct_zones,
-                 local_datapaths, local_lports,
-                 active_tunnels,
-                 flow_table);
-
-    node->changed = true;
-}
-
-static bool
-flow_output_sb_logical_flow_handler(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)engine_get_input(
-                "runtime_data", node)->data;
-    struct hmap *local_datapaths = &data->local_datapaths;
-    struct sset *local_lport_ids = &data->local_lport_ids;
-    struct sset *active_tunnels = &data->active_tunnels;
-    struct ed_type_addr_sets *as_data =
-        (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data;
-    struct shash *addr_sets = &as_data->addr_sets;
-
-    struct ed_type_port_groups *pg_data =
-        (struct ed_type_port_groups *)engine_get_input(
-            "port_groups", node)->data;
-    struct shash *port_groups = &pg_data->port_groups;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-    const char *chassis_id = chassis_get_id();
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-
-    const struct sbrec_chassis *chassis = NULL;
-    if (chassis_id) {
-        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    }
-
-    ovs_assert(br_int && chassis);
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-    struct ovn_extend_table *group_table = &fo->group_table;
-    struct ovn_extend_table *meter_table = &fo->meter_table;
-    uint32_t *conj_id_ofs = &fo->conj_id_ofs;
-    struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref;
-
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_multicast_group", node),
-                "name_datapath");
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct sbrec_dhcp_options_table *dhcp_table =
-        (struct sbrec_dhcp_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcp_options", node));
-
-    struct sbrec_dhcpv6_options_table *dhcpv6_table =
-        (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcpv6_options", node));
-
-    struct sbrec_logical_flow_table *logical_flow_table =
-        (struct sbrec_logical_flow_table *)EN_OVSDB_GET(
-            engine_get_input("SB_logical_flow", node));
-
-    bool handled = lflow_handle_changed_flows(
-              sbrec_multicast_group_by_name_datapath,
-              sbrec_port_binding_by_name,
-              dhcp_table, dhcpv6_table,
-              logical_flow_table,
-              local_datapaths, chassis, addr_sets,
-              port_groups, active_tunnels, local_lport_ids,
-              flow_table, group_table, meter_table, lfrr,
-              conj_id_ofs);
-
-    node->changed = true;
-    return handled;
-}
-
-static bool
-flow_output_sb_mac_binding_handler(struct engine_node *node)
-{
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct sbrec_mac_binding_table *mac_binding_table =
-        (struct sbrec_mac_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_mac_binding", node));
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-
-    lflow_handle_changed_neighbors(sbrec_port_binding_by_name,
-            mac_binding_table, flow_table);
-
-    node->changed = true;
-    return true;
-}
-
-static bool
-flow_output_sb_port_binding_handler(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)engine_get_input(
-                "runtime_data", node)->data;
-    struct hmap *local_datapaths = &data->local_datapaths;
-    struct sset *active_tunnels = &data->active_tunnels;
-    struct simap *ct_zones = &data->ct_zones;
-
-    struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
-        (struct ed_type_mff_ovn_geneve *)engine_get_input(
-            "mff_ovn_geneve", node)->data;
-    enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-    const char *chassis_id = chassis_get_id();
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-    const struct sbrec_chassis *chassis = NULL;
-    if (chassis_id) {
-        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    }
-    ovs_assert(br_int && chassis);
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct sbrec_port_binding_table *port_binding_table =
-        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-            engine_get_input("SB_port_binding", node));
-
-    /* XXX: now we handle port-binding changes for physical flow processing
-     * only, but port-binding change can have impact to logical flow
-     * processing, too, in below circumstances:
-     *
-     *  - When a port-binding for a lport is inserted/deleted but the lflow
-     *    using that lport doesn't change.
-     *
-     *    This can happen only when the lport name is used by ACL match
-     *    condition, which is specified by user. Even in that case, if the port
-     *    is actually bound on the current chassis it will trigger recompute on
-     *    that chassis since ovs interface would be updated. So the only
-     *    situation this would have real impact is when user defines an ACL
-     *    that includes lport that is not on current chassis, and there is a
-     *    port-binding creation/deletion related to that lport.e.g.: an ACL is
-     *    defined:
-     *
-     *    to-lport 1000 'outport=="A" && inport=="B"' allow-related
-     *
-     *    If "A" is on current chassis, but "B" is lport that hasn't been
-     *    created yet. When a lport "B" is created and bound on another
-     *    chassis, the ACL will not take effect on the current chassis until a
-     *    recompute is triggered later. This case doesn't seem to be a problem
-     *    for real world use cases because usually lport is created before
-     *    being referenced by name in ACLs.
-     *
-     *  - When is_chassis_resident(<lport>) is used in lflow. In this case the
-     *    port binding is not a regular VIF. It can be either "patch" or
-     *    "external", with ha-chassis-group assigned.  In current
-     *    "runtime_data" handling, port-binding changes for these types always
-     *    trigger recomputing. So it is fine even if we do not handle it here.
-     *    (due to the ovsdb tracking support for referenced table changes,
-     *    ha-chassis-group changes will appear as port-binding change).
-     *
-     *  - When a mac-binding doesn't change but the port-binding related to
-     *    that mac-binding is deleted. In this case the neighbor flow generated
-     *    for the mac-binding should be deleted. This would not cause any real
-     *    issue for now, since the port-binding related to mac-binding is
-     *    always logical router port, and any change to logical router port
-     *    would just trigger recompute.
-     *
-     * Although there is no correctness issue so far (except the unusual ACL
-     * use case, which doesn't seem to be a real problem), it might be better
-     * to handle this more gracefully, without the need to consider these
-     * tricky scenarios.  One approach is to maintain a mapping between lport
-     * names and the lflows that uses them, and reprocess the related lflows
-     * when related port-bindings change.
-     */
-    physical_handle_port_binding_changes(
-            sbrec_port_binding_by_name,
-            port_binding_table, mff_ovn_geneve,
-            chassis, ct_zones, local_datapaths,
-            active_tunnels, flow_table);
-
-    node->changed = true;
-    return true;
-}
-
-static bool
-flow_output_sb_multicast_group_handler(struct engine_node *node)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)engine_get_input(
-                "runtime_data", node)->data;
-    struct hmap *local_datapaths = &data->local_datapaths;
-    struct simap *ct_zones = &data->ct_zones;
-
-    struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
-        (struct ed_type_mff_ovn_geneve *)engine_get_input(
-            "mff_ovn_geneve", node)->data;
-    enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-    const char *chassis_id = chassis_get_id();
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-    const struct sbrec_chassis *chassis = NULL;
-    if (chassis_id) {
-        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    }
-    ovs_assert(br_int && chassis);
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-
-    struct sbrec_multicast_group_table *multicast_group_table =
-        (struct sbrec_multicast_group_table *)EN_OVSDB_GET(
-            engine_get_input("SB_multicast_group", node));
-
-    physical_handle_mc_group_changes(multicast_group_table,
-            mff_ovn_geneve, chassis, ct_zones, local_datapaths,
-            flow_table);
-
-    node->changed = true;
-    return true;
-
-}
-
-static bool
-_flow_output_resource_ref_handler(struct engine_node *node,
-                                 enum ref_type ref_type)
-{
-    struct ed_type_runtime_data *data =
-        (struct ed_type_runtime_data *)engine_get_input(
-                "runtime_data", node)->data;
-    struct hmap *local_datapaths = &data->local_datapaths;
-    struct sset *local_lport_ids = &data->local_lport_ids;
-    struct sset *active_tunnels = &data->active_tunnels;
-
-    struct ed_type_addr_sets *as_data =
-        (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data;
-    struct shash *addr_sets = &as_data->addr_sets;
-
-    struct ed_type_port_groups *pg_data =
-        (struct ed_type_port_groups *)engine_get_input(
-            "port_groups", node)->data;
-    struct shash *port_groups = &pg_data->port_groups;
-
-    struct ovsrec_open_vswitch_table *ovs_table =
-        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_open_vswitch", node));
-    struct ovsrec_bridge_table *bridge_table =
-        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_bridge", node));
-    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-    const char *chassis_id = chassis_get_id();
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_chassis", node),
-                "name");
-    const struct sbrec_chassis *chassis = NULL;
-    if (chassis_id) {
-        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-    }
-
-    ovs_assert(br_int && chassis);
-
-    struct ed_type_flow_output *fo =
-        (struct ed_type_flow_output *)node->data;
-    struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-    struct ovn_extend_table *group_table = &fo->group_table;
-    struct ovn_extend_table *meter_table = &fo->meter_table;
-    uint32_t *conj_id_ofs = &fo->conj_id_ofs;
-    struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref;
-
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_multicast_group", node),
-                "name_datapath");
-
-    struct ovsdb_idl_index *sbrec_port_binding_by_name =
-        engine_ovsdb_node_get_index(
-                engine_get_input("SB_port_binding", node),
-                "name");
-
-    struct sbrec_dhcp_options_table *dhcp_table =
-        (struct sbrec_dhcp_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcp_options", node));
-
-    struct sbrec_dhcpv6_options_table *dhcpv6_table =
-        (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET(
-            engine_get_input("SB_dhcpv6_options", node));
-
-    struct sbrec_logical_flow_table *logical_flow_table =
-        (struct sbrec_logical_flow_table *)EN_OVSDB_GET(
-            engine_get_input("SB_logical_flow", node));
-
-    bool changed;
-    const char *ref_name;
-    struct sset *new, *updated, *deleted;
-
-    switch (ref_type) {
-        case REF_TYPE_ADDRSET:
-            /* XXX: The change_tracked check may be added to inc-proc
-             * framework. */
-            if (!as_data->change_tracked) {
-                return false;
-            }
-            new = &as_data->new;
-            updated = &as_data->updated;
-            deleted = &as_data->deleted;
-            break;
-        case REF_TYPE_PORTGROUP:
-            if (!pg_data->change_tracked) {
-                return false;
-            }
-            new = &pg_data->new;
-            updated = &pg_data->updated;
-            deleted = &pg_data->deleted;
-            break;
-        default:
-            OVS_NOT_REACHED();
-    }
-
-
-    SSET_FOR_EACH (ref_name, deleted) {
-        if (!lflow_handle_changed_ref(ref_type, ref_name,
-                    sbrec_multicast_group_by_name_datapath,
-                    sbrec_port_binding_by_name,dhcp_table,
-                    dhcpv6_table, logical_flow_table,
-                    local_datapaths, chassis, addr_sets,
-                    port_groups, active_tunnels, local_lport_ids,
-                    flow_table, group_table, meter_table, lfrr,
-                    conj_id_ofs, &changed)) {
-            return false;
-        }
-        node->changed = changed || node->changed;
-    }
-    SSET_FOR_EACH (ref_name, updated) {
-        if (!lflow_handle_changed_ref(ref_type, ref_name,
-                    sbrec_multicast_group_by_name_datapath,
-                    sbrec_port_binding_by_name,dhcp_table,
-                    dhcpv6_table, logical_flow_table,
-                    local_datapaths, chassis, addr_sets,
-                    port_groups, active_tunnels, local_lport_ids,
-                    flow_table, group_table, meter_table, lfrr,
-                    conj_id_ofs, &changed)) {
-            return false;
-        }
-        node->changed = changed || node->changed;
-    }
-    SSET_FOR_EACH (ref_name, new) {
-        if (!lflow_handle_changed_ref(ref_type, ref_name,
-                    sbrec_multicast_group_by_name_datapath,
-                    sbrec_port_binding_by_name,dhcp_table,
-                    dhcpv6_table, logical_flow_table,
-                    local_datapaths, chassis, addr_sets,
-                    port_groups, active_tunnels, local_lport_ids,
-                    flow_table, group_table, meter_table, lfrr,
-                    conj_id_ofs, &changed)) {
-            return false;
-        }
-        node->changed = changed || node->changed;
-    }
-
-    return true;
-}
-
-static bool
-flow_output_addr_sets_handler(struct engine_node *node)
-{
-    return _flow_output_resource_ref_handler(node, REF_TYPE_ADDRSET);
-}
-
-static bool
-flow_output_port_groups_handler(struct engine_node *node)
-{
-    return _flow_output_resource_ref_handler(node, REF_TYPE_PORTGROUP);
-}
-
-struct ovn_controller_exit_args {
-    bool *exiting;
-    bool *restart;
-};
-
-int
-main(int argc, char *argv[])
-{
-    struct unixctl_server *unixctl;
-    bool exiting;
-    bool restart;
-    struct ovn_controller_exit_args exit_args = {&exiting, &restart};
-    int retval;
-
-    ovs_cmdl_proctitle_init(argc, argv);
-    set_program_name(argv[0]);
-    service_start(&argc, &argv);
-    char *ovs_remote = parse_options(argc, argv);
-    fatal_ignore_sigpipe();
-
-    daemonize_start(false);
-
-    retval = unixctl_server_create(NULL, &unixctl);
-    if (retval) {
-        exit(EXIT_FAILURE);
-    }
-    unixctl_command_register("exit", "", 0, 1, ovn_controller_exit,
-                             &exit_args);
-
-    daemonize_complete();
-
-    pinctrl_init();
-    lflow_init();
-
-    /* Connect to OVS OVSDB instance. */
-    struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true));
-    ctrl_register_ovs_idl(ovs_idl_loop.idl);
-    ovsdb_idl_get_initial_snapshot(ovs_idl_loop.idl);
-
-    /* Configure OVN SB database. */
-    struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create_unconnected(&sbrec_idl_class, true));
-    ovsdb_idl_set_leader_only(ovnsb_idl_loop.idl, false);
-
-    unixctl_command_register("connection-status", "", 0, 0,
-                             ovn_controller_conn_show, ovnsb_idl_loop.idl);
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name
-        = chassis_index_create(ovnsb_idl_loop.idl);
-    struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath
-        = mcast_group_index_create(ovnsb_idl_loop.idl);
-    struct ovsdb_idl_index *sbrec_port_binding_by_name
-        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
-                                  &sbrec_port_binding_col_logical_port);
-    struct ovsdb_idl_index *sbrec_port_binding_by_key
-        = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
-                                  &sbrec_port_binding_col_tunnel_key,
-                                  &sbrec_port_binding_col_datapath);
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath
-        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
-                                  &sbrec_port_binding_col_datapath);
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key
-        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
-                                  &sbrec_datapath_binding_col_tunnel_key);
-    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip
-        = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
-                                  &sbrec_mac_binding_col_logical_port,
-                                  &sbrec_mac_binding_col_ip);
-    struct ovsdb_idl_index *sbrec_ip_multicast
-        = ip_mcast_index_create(ovnsb_idl_loop.idl);
-    struct ovsdb_idl_index *sbrec_igmp_group
-        = igmp_group_index_create(ovnsb_idl_loop.idl);
-
-    ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
-    ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
-
-    /* Omit the external_ids column of all the tables except for -
-     *  - DNS. pinctrl.c uses the external_ids column of DNS,
-     *    which it shouldn't. This should be removed.
-     *
-     *  - Chassis - chassis.c copies the chassis configuration from
-     *              local open_vswitch table to the external_ids of
-     *              chassis.
-     *
-     *  - Datapath_binding - lflow.c is using this to check if the datapath
-     *                       is switch or not. This should be removed.
-     * */
-
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_sb_global_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_port_binding_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_connection_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ssl_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl,
-                   &sbrec_gateway_chassis_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ha_chassis_col_external_ids);
-    ovsdb_idl_omit(ovnsb_idl_loop.idl,
-                   &sbrec_ha_chassis_group_col_external_ids);
-
-    update_sb_monitors(ovnsb_idl_loop.idl, NULL, NULL, NULL);
-
-    stopwatch_create(CONTROLLER_LOOP_STOPWATCH_NAME, SW_MS);
-
-    /* Define inc-proc-engine nodes. */
-    struct ed_type_runtime_data ed_runtime_data;
-    struct ed_type_mff_ovn_geneve ed_mff_ovn_geneve;
-    struct ed_type_ofctrl_is_connected ed_ofctrl_is_connected;
-    struct ed_type_flow_output ed_flow_output;
-    struct ed_type_addr_sets ed_addr_sets;
-    struct ed_type_port_groups ed_port_groups;
-
-    ENGINE_NODE(runtime_data, "runtime_data");
-    ENGINE_NODE(mff_ovn_geneve, "mff_ovn_geneve");
-    ENGINE_NODE(ofctrl_is_connected, "ofctrl_is_connected");
-    ENGINE_NODE(flow_output, "flow_output");
-    ENGINE_NODE(addr_sets, "addr_sets");
-    ENGINE_NODE(port_groups, "port_groups");
-
-#define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
-    SB_NODES
-#undef SB_NODE
-
-#define OVS_NODE(NAME, NAME_STR) ENGINE_NODE_OVS(NAME, NAME_STR);
-    OVS_NODES
-#undef OVS_NODE
-
-    engine_ovsdb_node_add_index(&en_sb_chassis, "name", sbrec_chassis_by_name);
-    engine_ovsdb_node_add_index(&en_sb_multicast_group, "name_datapath",
-                                sbrec_multicast_group_by_name_datapath);
-    engine_ovsdb_node_add_index(&en_sb_port_binding, "name",
-                                sbrec_port_binding_by_name);
-    engine_ovsdb_node_add_index(&en_sb_port_binding, "key",
-                                sbrec_port_binding_by_key);
-    engine_ovsdb_node_add_index(&en_sb_port_binding, "datapath",
-                                sbrec_port_binding_by_datapath);
-    engine_ovsdb_node_add_index(&en_sb_datapath_binding, "key",
-                                sbrec_datapath_binding_by_key);
-
-    /* Add dependencies between inc-proc-engine nodes. */
-
-    engine_add_input(&en_addr_sets, &en_sb_address_set,
-                     addr_sets_sb_address_set_handler);
-    engine_add_input(&en_port_groups, &en_sb_port_group,
-                     port_groups_sb_port_group_handler);
-
-    engine_add_input(&en_flow_output, &en_addr_sets,
-                     flow_output_addr_sets_handler);
-    engine_add_input(&en_flow_output, &en_port_groups,
-                     flow_output_port_groups_handler);
-    engine_add_input(&en_flow_output, &en_runtime_data, NULL);
-    engine_add_input(&en_flow_output, &en_mff_ovn_geneve, NULL);
-
-    engine_add_input(&en_flow_output, &en_ovs_open_vswitch, NULL);
-    engine_add_input(&en_flow_output, &en_ovs_bridge, NULL);
-
-    engine_add_input(&en_flow_output, &en_sb_chassis, NULL);
-    engine_add_input(&en_flow_output, &en_sb_encap, NULL);
-    engine_add_input(&en_flow_output, &en_sb_multicast_group,
-                     flow_output_sb_multicast_group_handler);
-    engine_add_input(&en_flow_output, &en_sb_port_binding,
-                     flow_output_sb_port_binding_handler);
-    engine_add_input(&en_flow_output, &en_sb_mac_binding,
-                     flow_output_sb_mac_binding_handler);
-    engine_add_input(&en_flow_output, &en_sb_logical_flow,
-                     flow_output_sb_logical_flow_handler);
-    engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL);
-    engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL);
-    engine_add_input(&en_flow_output, &en_sb_dns, NULL);
-
-    engine_add_input(&en_runtime_data, &en_ofctrl_is_connected, NULL);
-
-    engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
-    engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
-    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
-    engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
-
-    engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
-    engine_add_input(&en_runtime_data, &en_sb_datapath_binding, NULL);
-    engine_add_input(&en_runtime_data, &en_sb_port_binding,
-                     runtime_data_sb_port_binding_handler);
-
-    engine_init(&en_flow_output);
-
-    ofctrl_init(&ed_flow_output.group_table,
-                &ed_flow_output.meter_table,
-                get_ofctrl_probe_interval(ovs_idl_loop.idl));
-
-    unixctl_command_register("group-table-list", "", 0, 0,
-                             group_table_list, &ed_flow_output.group_table);
-
-    unixctl_command_register("meter-table-list", "", 0, 0,
-                             meter_table_list, &ed_flow_output.meter_table);
-
-    unixctl_command_register("ct-zone-list", "", 0, 0,
-                             ct_zone_list, &ed_runtime_data.ct_zones);
-
-    struct pending_pkt pending_pkt = { .conn = NULL };
-    unixctl_command_register("inject-pkt", "MICROFLOW", 1, 1, inject_pkt,
-                             &pending_pkt);
-
-    uint64_t engine_run_id = 0;
-    uint64_t old_engine_run_id = 0;
-
-    unsigned int ovs_cond_seqno = UINT_MAX;
-    unsigned int ovnsb_cond_seqno = UINT_MAX;
-
-    /* Main loop. */
-    exiting = false;
-    restart = false;
-    while (!exiting) {
-        update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl);
-        update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl));
-        ofctrl_set_probe_interval(get_ofctrl_probe_interval(ovs_idl_loop.idl));
-        old_engine_run_id = engine_run_id;
-
-        struct ovsdb_idl_txn *ovs_idl_txn = ovsdb_idl_loop_run(&ovs_idl_loop);
-        unsigned int new_ovs_cond_seqno
-            = ovsdb_idl_get_condition_seqno(ovs_idl_loop.idl);
-        if (new_ovs_cond_seqno != ovs_cond_seqno) {
-            if (!new_ovs_cond_seqno) {
-                VLOG_INFO("OVS IDL reconnected, force recompute.");
-                engine_set_force_recompute(true);
-            }
-            ovs_cond_seqno = new_ovs_cond_seqno;
-        }
-
-        struct ovsdb_idl_txn *ovnsb_idl_txn
-            = ovsdb_idl_loop_run(&ovnsb_idl_loop);
-        unsigned int new_ovnsb_cond_seqno
-            = ovsdb_idl_get_condition_seqno(ovnsb_idl_loop.idl);
-        if (new_ovnsb_cond_seqno != ovnsb_cond_seqno) {
-            if (!new_ovnsb_cond_seqno) {
-                VLOG_INFO("OVNSB IDL reconnected, force recompute.");
-                engine_set_force_recompute(true);
-            }
-            ovnsb_cond_seqno = new_ovnsb_cond_seqno;
-        }
-
-        struct engine_context eng_ctx = {
-            .ovs_idl_txn = ovs_idl_txn,
-            .ovnsb_idl_txn = ovnsb_idl_txn
-        };
-
-        engine_set_context(&eng_ctx);
-
-        if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl)) {
-            /* Contains the transport zones that this Chassis belongs to */
-            struct sset transport_zones = SSET_INITIALIZER(&transport_zones);
-            sset_from_delimited_string(&transport_zones,
-                get_transport_zones(ovsrec_open_vswitch_table_get(
-                                    ovs_idl_loop.idl)), ",");
-
-            const struct ovsrec_bridge_table *bridge_table =
-                ovsrec_bridge_table_get(ovs_idl_loop.idl);
-            const struct ovsrec_open_vswitch_table *ovs_table =
-                ovsrec_open_vswitch_table_get(ovs_idl_loop.idl);
-            const struct sbrec_chassis_table *chassis_table =
-                sbrec_chassis_table_get(ovnsb_idl_loop.idl);
-            const struct ovsrec_bridge *br_int =
-                process_br_int(ovs_idl_txn, bridge_table, ovs_table);
-            const char *chassis_id = get_ovs_chassis_id(ovs_table);
-            const struct sbrec_chassis *chassis = NULL;
-            if (chassis_id) {
-                chassis = chassis_run(ovnsb_idl_txn, sbrec_chassis_by_name,
-                                      ovs_table, chassis_table, chassis_id,
-                                      br_int, &transport_zones);
-            }
-
-            if (br_int) {
-                ofctrl_run(br_int, &ed_runtime_data.pending_ct_zones);
-
-                if (chassis) {
-                    patch_run(ovs_idl_txn,
-                              ovsrec_bridge_table_get(ovs_idl_loop.idl),
-                              ovsrec_open_vswitch_table_get(ovs_idl_loop.idl),
-                              ovsrec_port_table_get(ovs_idl_loop.idl),
-                              sbrec_port_binding_table_get(ovnsb_idl_loop.idl),
-                              br_int, chassis);
-                    encaps_run(ovs_idl_txn,
-                               bridge_table, br_int,
-                               sbrec_chassis_table_get(ovnsb_idl_loop.idl),
-                               chassis_id,
-                               sbrec_sb_global_first(ovnsb_idl_loop.idl),
-                               &transport_zones);
-
-                    stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
-                                    time_msec());
-                    if (ovnsb_idl_txn) {
-                        engine_run(&en_flow_output, ++engine_run_id);
-                    }
-                    stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
-                                   time_msec());
-                    if (ovs_idl_txn) {
-                        commit_ct_zones(br_int,
-                                        &ed_runtime_data.pending_ct_zones);
-                        bfd_run(ovsrec_interface_table_get(ovs_idl_loop.idl),
-                                br_int, chassis,
-                                sbrec_ha_chassis_group_table_get(
-                                    ovnsb_idl_loop.idl),
-                                sbrec_sb_global_table_get(ovnsb_idl_loop.idl));
-                    }
-                    ofctrl_put(&ed_flow_output.flow_table,
-                               &ed_runtime_data.pending_ct_zones,
-                               sbrec_meter_table_get(ovnsb_idl_loop.idl),
-                               get_nb_cfg(sbrec_sb_global_table_get(
-                                              ovnsb_idl_loop.idl)),
-                               en_flow_output.changed);
-                    pinctrl_run(ovnsb_idl_txn,
-                                sbrec_datapath_binding_by_key,
-                                sbrec_port_binding_by_datapath,
-                                sbrec_port_binding_by_key,
-                                sbrec_port_binding_by_name,
-                                sbrec_mac_binding_by_lport_ip,
-                                sbrec_igmp_group,
-                                sbrec_ip_multicast,
-                                sbrec_dns_table_get(ovnsb_idl_loop.idl),
-                                sbrec_controller_event_table_get(
-                                    ovnsb_idl_loop.idl),
-                                br_int, chassis,
-                                &ed_runtime_data.local_datapaths,
-                                &ed_runtime_data.active_tunnels);
-
-                    if (en_runtime_data.changed) {
-                        update_sb_monitors(ovnsb_idl_loop.idl, chassis,
-                                           &ed_runtime_data.local_lports,
-                                           &ed_runtime_data.local_datapaths);
-                    }
-                }
-
-            }
-            if (old_engine_run_id == engine_run_id) {
-                if (engine_need_run(&en_flow_output)) {
-                    VLOG_DBG("engine did not run, force recompute next time: "
-                             "br_int %p, chassis %p", br_int, chassis);
-                    engine_set_force_recompute(true);
-                    poll_immediate_wake();
-                } else {
-                    VLOG_DBG("engine did not run, and it was not needed"
-                             " either: br_int %p, chassis %p",
-                             br_int, chassis);
-                }
-            } else {
-                engine_set_force_recompute(false);
-            }
-
-            if (ovnsb_idl_txn && chassis) {
-                int64_t cur_cfg = ofctrl_get_cur_cfg();
-                if (cur_cfg && cur_cfg != chassis->nb_cfg) {
-                    sbrec_chassis_set_nb_cfg(chassis, cur_cfg);
-                }
-            }
-
-
-            if (pending_pkt.conn) {
-                if (br_int && chassis) {
-                    char *error = ofctrl_inject_pkt(br_int, pending_pkt.flow_s,
-                        &ed_addr_sets.addr_sets, &ed_port_groups.port_groups);
-                    if (error) {
-                        unixctl_command_reply_error(pending_pkt.conn, error);
-                        free(error);
-                    } else {
-                        VLOG_DBG("Pending_pkt conn but br_int %p or chassis "
-                                 "%p not ready. run-id: %"PRIu64, br_int,
-                                 chassis, engine_run_id);
-                        unixctl_command_reply_error(pending_pkt.conn,
-                            "ovn-controller not ready.");
-                    }
-                }
-                pending_pkt.conn = NULL;
-                free(pending_pkt.flow_s);
-            }
-
-            sset_destroy(&transport_zones);
-
-            if (br_int) {
-                ofctrl_wait();
-                pinctrl_wait(ovnsb_idl_txn);
-            }
-        }
-
-        unixctl_server_run(unixctl);
-
-        unixctl_server_wait(unixctl);
-        if (exiting || pending_pkt.conn) {
-            poll_immediate_wake();
-        }
-
-        if (!ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop)) {
-            VLOG_INFO("OVNSB commit failed, force recompute next time.");
-            engine_set_force_recompute(true);
-        }
-
-        if (ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop) == 1) {
-            struct shash_node *iter, *iter_next;
-            SHASH_FOR_EACH_SAFE (iter, iter_next,
-                                 &ed_runtime_data.pending_ct_zones) {
-                struct ct_zone_pending_entry *ctzpe = iter->data;
-                if (ctzpe->state == CT_ZONE_DB_SENT) {
-                    shash_delete(&ed_runtime_data.pending_ct_zones, iter);
-                    free(ctzpe);
-                }
-            }
-        }
-
-        ovsdb_idl_track_clear(ovnsb_idl_loop.idl);
-        ovsdb_idl_track_clear(ovs_idl_loop.idl);
-        poll_block();
-        if (should_service_stop()) {
-            exiting = true;
-        }
-    }
-
-    engine_set_context(NULL);
-    engine_cleanup(&en_flow_output);
-
-    /* It's time to exit.  Clean up the databases if we are not restarting */
-    if (!restart) {
-        bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl);
-        while (!done) {
-            update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl);
-            update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl));
-
-            struct ovsdb_idl_txn *ovs_idl_txn
-                = ovsdb_idl_loop_run(&ovs_idl_loop);
-            struct ovsdb_idl_txn *ovnsb_idl_txn
-                = ovsdb_idl_loop_run(&ovnsb_idl_loop);
-
-            const struct ovsrec_bridge_table *bridge_table
-                = ovsrec_bridge_table_get(ovs_idl_loop.idl);
-            const struct ovsrec_open_vswitch_table *ovs_table
-                = ovsrec_open_vswitch_table_get(ovs_idl_loop.idl);
-
-            const struct sbrec_port_binding_table *port_binding_table
-                = sbrec_port_binding_table_get(ovnsb_idl_loop.idl);
-
-            const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
-                                                            ovs_table);
-            const char *chassis_id = chassis_get_id();
-            const struct sbrec_chassis *chassis
-                = (chassis_id
-                   ? chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id)
-                   : NULL);
-
-            /* Run all of the cleanup functions, even if one of them returns
-             * false. We're done if all of them return true. */
-            done = binding_cleanup(ovnsb_idl_txn, port_binding_table, chassis);
-            done = chassis_cleanup(ovnsb_idl_txn, chassis) && done;
-            done = encaps_cleanup(ovs_idl_txn, br_int) && done;
-            done = igmp_group_cleanup(ovnsb_idl_txn, sbrec_igmp_group) && done;
-            if (done) {
-                poll_immediate_wake();
-            }
-
-            ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-            ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
-            poll_block();
-        }
-    }
-
-    unixctl_server_destroy(unixctl);
-    lflow_destroy();
-    ofctrl_destroy();
-    pinctrl_destroy();
-
-    ovsdb_idl_loop_destroy(&ovs_idl_loop);
-    ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
-
-    free(ovs_remote);
-    service_stop();
-
-    exit(retval);
-}
-
-static char *
-parse_options(int argc, char *argv[])
-{
-    enum {
-        OPT_PEER_CA_CERT = UCHAR_MAX + 1,
-        OPT_BOOTSTRAP_CA_CERT,
-        VLOG_OPTION_ENUMS,
-        DAEMON_OPTION_ENUMS,
-        SSL_OPTION_ENUMS,
-    };
-
-    static struct option long_options[] = {
-        {"help", no_argument, NULL, 'h'},
-        {"version", no_argument, NULL, 'V'},
-        VLOG_LONG_OPTIONS,
-        DAEMON_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
-        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
-        {NULL, 0, NULL, 0}
-    };
-    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
-
-    for (;;) {
-        int c;
-
-        c = getopt_long(argc, argv, short_options, long_options, NULL);
-        if (c == -1) {
-            break;
-        }
-
-        switch (c) {
-        case 'h':
-            usage();
-
-        case 'V':
-            ovs_print_version(OFP13_VERSION, OFP13_VERSION);
-            exit(EXIT_SUCCESS);
-
-        VLOG_OPTION_HANDLERS
-        DAEMON_OPTION_HANDLERS
-        STREAM_SSL_OPTION_HANDLERS
-
-        case OPT_PEER_CA_CERT:
-            stream_ssl_set_peer_ca_cert_file(optarg);
-            break;
-
-        case OPT_BOOTSTRAP_CA_CERT:
-            stream_ssl_set_ca_cert_file(optarg, true);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            abort();
-        }
-    }
-    free(short_options);
-
-    argc -= optind;
-    argv += optind;
-
-    char *ovs_remote;
-    if (argc == 0) {
-        ovs_remote = xasprintf("unix:%s/db.sock", ovs_rundir());
-    } else if (argc == 1) {
-        ovs_remote = xstrdup(argv[0]);
-    } else {
-        VLOG_FATAL("exactly zero or one non-option argument required; "
-                   "use --help for usage");
-    }
-    return ovs_remote;
-}
-
-static void
-usage(void)
-{
-    printf("%s: OVN controller\n"
-           "usage %s [OPTIONS] [OVS-DATABASE]\n"
-           "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n",
-               program_name, program_name);
-    stream_usage("OVS-DATABASE", true, false, true);
-    daemon_usage();
-    vlog_usage();
-    printf("\nOther options:\n"
-           "  -h, --help              display this help message\n"
-           "  -V, --version           display version information\n");
-    exit(EXIT_SUCCESS);
-}
-
-static void
-ovn_controller_exit(struct unixctl_conn *conn, int argc,
-             const char *argv[], void *exit_args_)
-{
-    struct ovn_controller_exit_args *exit_args = exit_args_;
-    *exit_args->exiting = true;
-    *exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart");
-    unixctl_command_reply(conn, NULL);
-}
-
-static void
-ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
-             const char *argv[] OVS_UNUSED, void *ct_zones_)
-{
-    struct simap *ct_zones = ct_zones_;
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    struct simap_node *zone;
-
-    SIMAP_FOR_EACH(zone, ct_zones) {
-        ds_put_format(&ds, "%s %d\n", zone->name, zone->data);
-    }
-
-    unixctl_command_reply(conn, ds_cstr(&ds));
-    ds_destroy(&ds);
-}
-
-static void
-meter_table_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                 const char *argv[] OVS_UNUSED, void *meter_table_)
-{
-    struct ovn_extend_table *meter_table = meter_table_;
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    struct simap meters = SIMAP_INITIALIZER(&meters);
-
-    struct ovn_extend_table_info *m_installed, *next_meter;
-    EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_meter, meter_table) {
-        simap_put(&meters, m_installed->name, m_installed->table_id);
-    }
-
-    const struct simap_node **nodes = simap_sort(&meters);
-    size_t n_nodes = simap_count(&meters);
-    for (size_t i = 0; i < n_nodes; i++) {
-        const struct simap_node *node = nodes[i];
-        ds_put_format(&ds, "%s: %d\n", node->name, node->data);
-    }
-
-    free(nodes);
-    simap_destroy(&meters);
-
-    unixctl_command_reply(conn, ds_cstr(&ds));
-    ds_destroy(&ds);
-}
-
-static void
-group_table_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                 const char *argv[] OVS_UNUSED, void *group_table_)
-{
-    struct ovn_extend_table *group_table = group_table_;
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    struct simap groups = SIMAP_INITIALIZER(&groups);
-
-    struct ovn_extend_table_info *m_installed, *next_group;
-    EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_group, group_table) {
-        simap_put(&groups, m_installed->name, m_installed->table_id);
-    }
-
-    const struct simap_node **nodes = simap_sort(&groups);
-    size_t n_nodes = simap_count(&groups);
-    for (size_t i = 0; i < n_nodes; i++) {
-        const struct simap_node *node = nodes[i];
-        ds_put_format(&ds, "%s: %d\n", node->name, node->data);
-    }
-
-    free(nodes);
-    simap_destroy(&groups);
-
-    unixctl_command_reply(conn, ds_cstr(&ds));
-    ds_destroy(&ds);
-}
-
-static void
-inject_pkt(struct unixctl_conn *conn, int argc OVS_UNUSED,
-           const char *argv[], void *pending_pkt_)
-{
-    struct pending_pkt *pending_pkt = pending_pkt_;
-
-    if (pending_pkt->conn) {
-        unixctl_command_reply_error(conn, "already pending packet injection");
-        return;
-    }
-    pending_pkt->conn = conn;
-    pending_pkt->flow_s = xstrdup(argv[1]);
-}
-
-static void
-ovn_controller_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                         const char *argv[] OVS_UNUSED, void *idl_)
-{
-    const char *result = "not connected";
-    const struct ovsdb_idl *idl = idl_;
-
-    if (ovsdb_idl_is_connected(idl)) {
-       result = "connected";
-    }
-    unixctl_command_reply(conn, result);
-}
diff --git a/ovn/controller/ovn-controller.h b/ovn/controller/ovn-controller.h
deleted file mode 100644
index 078c9eabe..000000000
--- a/ovn/controller/ovn-controller.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_CONTROLLER_H
-#define OVN_CONTROLLER_H 1
-
-#include "simap.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-struct ovsrec_bridge_table;
-
-/* Linux supports a maximum of 64K zones, which seems like a fine default. */
-#define MAX_CT_ZONES 65535
-
-/* States to move through when a new conntrack zone has been allocated. */
-enum ct_zone_pending_state {
-    CT_ZONE_OF_QUEUED,    /* Waiting to send conntrack flush command. */
-    CT_ZONE_OF_SENT,      /* Sent and waiting for confirmation on flush. */
-    CT_ZONE_DB_QUEUED,    /* Waiting for DB transaction to open. */
-    CT_ZONE_DB_SENT,      /* Sent and waiting for confirmation from DB. */
-};
-
-struct ct_zone_pending_entry {
-    int zone;
-    bool add;             /* Is the entry being added? */
-    ovs_be32 of_xid;      /* Transaction id for barrier. */
-    enum ct_zone_pending_state state;
-};
-
-/* A logical datapath that has some relevance to this hypervisor.  A logical
- * datapath D is relevant to hypervisor H if:
- *
- *     - Some VIF or l2gateway or l3gateway port in D is located on H.
- *
- *     - D is reachable over a series of hops across patch ports, starting from
- *       a datapath relevant to H.
- *
- * The 'hmap_node''s hash value is 'datapath->tunnel_key'. */
-struct local_datapath {
-    struct hmap_node hmap_node;
-    const struct sbrec_datapath_binding *datapath;
-
-    /* The localnet port in this datapath, if any (at most one is allowed). */
-    const struct sbrec_port_binding *localnet_port;
-
-    /* True if this datapath contains an l3gateway port located on this
-     * hypervisor. */
-    bool has_local_l3gateway;
-
-    const struct sbrec_port_binding **peer_ports;
-    size_t n_peer_ports;
-};
-
-struct local_datapath *get_local_datapath(const struct hmap *,
-                                          uint32_t tunnel_key);
-
-const struct ovsrec_bridge *get_bridge(const struct ovsrec_bridge_table *,
-                                       const char *br_name);
-
-struct sbrec_encap *preferred_encap(const struct sbrec_chassis *);
-
-/* Must be a bit-field ordered from most-preferred (higher number) to
- * least-preferred (lower number). */
-enum chassis_tunnel_type {
-    GENEVE = 1 << 2,
-    STT    = 1 << 1,
-    VXLAN  = 1 << 0
-};
-
-uint32_t get_tunnel_type(const char *name);
-
-#endif /* ovn/ovn-controller.h */
diff --git a/ovn/controller/patch.c b/ovn/controller/patch.c
deleted file mode 100644
index a6770c6d5..000000000
--- a/ovn/controller/patch.c
+++ /dev/null
@@ -1,273 +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 "patch.h"
-
-#include "hash.h"
-#include "lflow.h"
-#include "lib/vswitch-idl.h"
-#include "lport.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/vlog.h"
-#include "ovn-controller.h"
-
-VLOG_DEFINE_THIS_MODULE(patch);
-
-static char *
-patch_port_name(const char *src, const char *dst)
-{
-    return xasprintf("patch-%s-to-%s", src, dst);
-}
-
-/* Return true if 'port' is a patch port with the specified 'peer'. */
-static bool
-match_patch_port(const struct ovsrec_port *port, const char *peer)
-{
-    for (size_t i = 0; i < port->n_interfaces; i++) {
-        struct ovsrec_interface *iface = port->interfaces[i];
-        if (strcmp(iface->type, "patch")) {
-            continue;
-        }
-        const char *iface_peer = smap_get(&iface->options, "peer");
-        if (iface_peer && !strcmp(iface_peer, peer)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/* Creates a patch port in bridge 'src' named 'src_name', whose peer is
- * 'dst_name' in bridge 'dst'.  Initializes the patch port's external-ids:'key'
- * to 'key'.
- *
- * If such a patch port already exists, removes it from 'existing_ports'. */
-static void
-create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
-                  const char *key, const char *value,
-                  const struct ovsrec_bridge *src, const char *src_name,
-                  const struct ovsrec_bridge *dst, const char *dst_name,
-                  struct shash *existing_ports)
-{
-    for (size_t i = 0; i < src->n_ports; i++) {
-        if (match_patch_port(src->ports[i], dst_name)) {
-            /* Patch port already exists on 'src'. */
-            shash_find_and_delete(existing_ports, src->ports[i]->name);
-            return;
-        }
-    }
-
-    ovsdb_idl_txn_add_comment(ovs_idl_txn,
-            "ovn-controller: creating patch port '%s' from '%s' to '%s'",
-            src_name, src->name, dst->name);
-
-    struct ovsrec_interface *iface;
-    iface = ovsrec_interface_insert(ovs_idl_txn);
-    ovsrec_interface_set_name(iface, src_name);
-    ovsrec_interface_set_type(iface, "patch");
-    const struct smap options = SMAP_CONST1(&options, "peer", dst_name);
-    ovsrec_interface_set_options(iface, &options);
-
-    struct ovsrec_port *port;
-    port = ovsrec_port_insert(ovs_idl_txn);
-    ovsrec_port_set_name(port, src_name);
-    ovsrec_port_set_interfaces(port, &iface, 1);
-    const struct smap ids = SMAP_CONST1(&ids, key, value);
-    ovsrec_port_set_external_ids(port, &ids);
-
-    struct ovsrec_port **ports;
-    ports = xmalloc(sizeof *ports * (src->n_ports + 1));
-    memcpy(ports, src->ports, sizeof *ports * src->n_ports);
-    ports[src->n_ports] = port;
-    ovsrec_bridge_verify_ports(src);
-    ovsrec_bridge_set_ports(src, ports, src->n_ports + 1);
-
-    free(ports);
-}
-
-static void
-remove_port(const struct ovsrec_bridge_table *bridge_table,
-            const struct ovsrec_port *port)
-{
-    const struct ovsrec_bridge *bridge;
-
-    /* We know the port we want to delete, but we have to find the bridge its
-     * on to do so.  Note this only runs on a config change that should be
-     * pretty rare. */
-    OVSREC_BRIDGE_TABLE_FOR_EACH (bridge, bridge_table) {
-        size_t i;
-        for (i = 0; i < bridge->n_ports; i++) {
-            if (bridge->ports[i] != port) {
-                continue;
-            }
-            struct ovsrec_port **new_ports;
-            new_ports = xmemdup(bridge->ports,
-                    sizeof *new_ports * (bridge->n_ports - 1));
-            if (i != bridge->n_ports - 1) {
-                /* Removed port was not last */
-                new_ports[i] = bridge->ports[bridge->n_ports - 1];
-            }
-            ovsrec_bridge_verify_ports(bridge);
-            ovsrec_bridge_set_ports(bridge, new_ports, bridge->n_ports - 1);
-            free(new_ports);
-            ovsrec_port_delete(port);
-            return;
-        }
-    }
-}
-
-/* Obtains external-ids:ovn-bridge-mappings from OVSDB and adds patch ports for
- * the local bridge mappings.  Removes any patch ports for bridge mappings that
- * already existed from 'existing_ports'. */
-static void
-add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
-                    const struct ovsrec_bridge_table *bridge_table,
-                    const struct ovsrec_open_vswitch_table *ovs_table,
-                    const struct sbrec_port_binding_table *port_binding_table,
-                    const struct ovsrec_bridge *br_int,
-                    struct shash *existing_ports,
-                    const struct sbrec_chassis *chassis)
-{
-    /* Get ovn-bridge-mappings. */
-    const char *mappings_cfg = "";
-    const struct ovsrec_open_vswitch *cfg;
-    cfg = ovsrec_open_vswitch_table_first(ovs_table);
-    if (cfg) {
-        mappings_cfg = smap_get(&cfg->external_ids, "ovn-bridge-mappings");
-        if (!mappings_cfg || !mappings_cfg[0]) {
-            return;
-        }
-    }
-
-    /* Parse bridge mappings. */
-    struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings);
-    char *cur, *next, *start;
-    next = start = xstrdup(mappings_cfg);
-    while ((cur = strsep(&next, ",")) && *cur) {
-        char *network, *bridge = cur;
-        const struct ovsrec_bridge *ovs_bridge;
-
-        network = strsep(&bridge, ":");
-        if (!bridge || !*network || !*bridge) {
-            VLOG_ERR("Invalid ovn-bridge-mappings configuration: '%s'",
-                    mappings_cfg);
-            break;
-        }
-
-        ovs_bridge = get_bridge(bridge_table, bridge);
-        if (!ovs_bridge) {
-            VLOG_WARN("Bridge '%s' not found for network '%s'",
-                    bridge, network);
-            continue;
-        }
-
-        shash_add(&bridge_mappings, network, ovs_bridge);
-    }
-    free(start);
-
-    const struct sbrec_port_binding *binding;
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) {
-        const char *patch_port_id;
-        if (!strcmp(binding->type, "localnet")) {
-            patch_port_id = "ovn-localnet-port";
-        } else if (!strcmp(binding->type, "l2gateway")) {
-            if (!binding->chassis
-                || strcmp(chassis->name, binding->chassis->name)) {
-                /* This L2 gateway port is not bound to this chassis,
-                 * so we should not create any patch ports for it. */
-                continue;
-            }
-            patch_port_id = "ovn-l2gateway-port";
-        } else {
-            /* not a localnet or L2 gateway port. */
-            continue;
-        }
-
-        const char *network = smap_get(&binding->options, "network_name");
-        if (!network) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_ERR_RL(&rl, "%s port '%s' has no network name.",
-                         binding->type, binding->logical_port);
-            continue;
-        }
-        struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings, network);
-        if (!br_ln) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
-                    "with network name '%s'",
-                    binding->type, binding->logical_port, network);
-            continue;
-        }
-
-        char *name1 = patch_port_name(br_int->name, binding->logical_port);
-        char *name2 = patch_port_name(binding->logical_port, br_int->name);
-        create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
-                          br_int, name1, br_ln, name2, existing_ports);
-        create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
-                          br_ln, name2, br_int, name1, existing_ports);
-        free(name1);
-        free(name2);
-    }
-
-    shash_destroy(&bridge_mappings);
-}
-
-void
-patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
-          const struct ovsrec_bridge_table *bridge_table,
-          const struct ovsrec_open_vswitch_table *ovs_table,
-          const struct ovsrec_port_table *port_table,
-          const struct sbrec_port_binding_table *port_binding_table,
-          const struct ovsrec_bridge *br_int,
-          const struct sbrec_chassis *chassis)
-{
-    if (!ovs_idl_txn) {
-        return;
-    }
-
-    /* Figure out what patch ports already exist.
-     *
-     * ovn-controller does not create or use ports of type "ovn-l3gateway-port"
-     * or "ovn-logical-patch-port", but older version did.  We still recognize
-     * them here, so that we delete them at the end of this function, to avoid
-     * leaving useless ports on upgrade. */
-    struct shash existing_ports = SHASH_INITIALIZER(&existing_ports);
-    const struct ovsrec_port *port;
-    OVSREC_PORT_TABLE_FOR_EACH (port, port_table) {
-        if (smap_get(&port->external_ids, "ovn-localnet-port")
-            || smap_get(&port->external_ids, "ovn-l2gateway-port")
-            || smap_get(&port->external_ids, "ovn-l3gateway-port")
-            || smap_get(&port->external_ids, "ovn-logical-patch-port")) {
-            shash_add(&existing_ports, port->name, port);
-        }
-    }
-
-    /* Create in the database any patch ports that should exist.  Remove from
-     * 'existing_ports' any patch ports that do exist in the database and
-     * should be there. */
-    add_bridge_mappings(ovs_idl_txn, bridge_table, ovs_table,
-                        port_binding_table, br_int, &existing_ports, chassis);
-
-    /* Now 'existing_ports' only still contains patch ports that exist in the
-     * database but shouldn't.  Delete them from the database. */
-    struct shash_node *port_node, *port_next_node;
-    SHASH_FOR_EACH_SAFE (port_node, port_next_node, &existing_ports) {
-        port = port_node->data;
-        shash_delete(&existing_ports, port_node);
-        remove_port(bridge_table, port);
-    }
-    shash_destroy(&existing_ports);
-}
diff --git a/ovn/controller/patch.h b/ovn/controller/patch.h
deleted file mode 100644
index dd052cfd8..000000000
--- a/ovn/controller/patch.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_PATCH_H
-#define OVN_PATCH_H 1
-
-/* Patch Ports
- * ===========
- *
- * This module adds and removes patch ports between the integration bridge and
- * physical bridges, as directed by other-config:ovn-bridge-mappings. */
-
-struct hmap;
-struct ovsdb_idl_txn;
-struct ovsrec_bridge;
-struct ovsrec_bridge_table;
-struct ovsrec_open_vswitch_table;
-struct ovsrec_port_table;
-struct sbrec_port_binding_table;
-struct sbrec_chassis;
-
-void patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
-               const struct ovsrec_bridge_table *,
-               const struct ovsrec_open_vswitch_table *,
-               const struct ovsrec_port_table *,
-               const struct sbrec_port_binding_table *,
-               const struct ovsrec_bridge *br_int,
-               const struct sbrec_chassis *);
-
-#endif /* ovn/patch.h */
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
deleted file mode 100644
index 316d3738c..000000000
--- a/ovn/controller/physical.c
+++ /dev/null
@@ -1,1459 +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 "binding.h"
-#include "byte-order.h"
-#include "encaps.h"
-#include "flow.h"
-#include "ha-chassis.h"
-#include "lflow.h"
-#include "lport.h"
-#include "chassis.h"
-#include "lib/bundle.h"
-#include "openvswitch/poll-loop.h"
-#include "lib/uuid.h"
-#include "ofctrl.h"
-#include "openvswitch/list.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/match.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/vlog.h"
-#include "openvswitch/ofp-parse.h"
-#include "ovn-controller.h"
-#include "ovn/lib/chassis-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "physical.h"
-#include "openvswitch/shash.h"
-#include "simap.h"
-#include "smap.h"
-#include "sset.h"
-#include "util.h"
-#include "vswitch-idl.h"
-
-VLOG_DEFINE_THIS_MODULE(physical);
-
-/* UUID to identify OF flows not associated with ovsdb rows. */
-static struct uuid *hc_uuid = NULL;
-
-void
-physical_register_ovs_idl(struct ovsdb_idl *ovs_idl)
-{
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
-    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
-
-    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport);
-    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids);
-}
-
-static struct simap localvif_to_ofport =
-    SIMAP_INITIALIZER(&localvif_to_ofport);
-static struct hmap tunnels = HMAP_INITIALIZER(&tunnels);
-
-/* Maps from a chassis to the OpenFlow port number of the tunnel that can be
- * used to reach that chassis. */
-struct chassis_tunnel {
-    struct hmap_node hmap_node;
-    char *chassis_id;
-    ofp_port_t ofport;
-    enum chassis_tunnel_type type;
-};
-
-/*
- * This function looks up the list of tunnel ports (provided by
- * ovn-chassis-id ports) and returns the tunnel for the given chassid-id and
- * encap-ip. The ovn-chassis-id is formed using the chassis-id and encap-ip.
- * The list is hashed using the chassis-id. If the encap-ip is not specified,
- * it means we'll just return a tunnel for that chassis-id, i.e. we just check
- * for chassis-id and if there is a match, we'll return the tunnel.
- * If encap-ip is also provided we use both chassis-id and encap-ip to do
- * a more specific lookup.
- */
-static struct chassis_tunnel *
-chassis_tunnel_find(const char *chassis_id, char *encap_ip)
-{
-    /*
-     * If the specific encap_ip is given, look for the chassisid_ip entry,
-     * else return the 1st found entry for the chassis.
-     */
-    struct chassis_tunnel *tun = NULL;
-    HMAP_FOR_EACH_WITH_HASH (tun, hmap_node, hash_string(chassis_id, 0),
-                             &tunnels) {
-        if (encaps_tunnel_id_match(tun->chassis_id, chassis_id, encap_ip)) {
-            return tun;
-        }
-    }
-    return NULL;
-}
-
-static void
-put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
-         struct ofpbuf *ofpacts)
-{
-    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
-                                                       mf_from_id(dst), NULL,
-                                                       NULL);
-    ovs_be64 n_value = htonll(value);
-    bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
-    bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
-}
-
-static void
-put_move(enum mf_field_id src, int src_ofs,
-         enum mf_field_id dst, int dst_ofs,
-         int n_bits,
-         struct ofpbuf *ofpacts)
-{
-    struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
-    move->src.field = mf_from_id(src);
-    move->src.ofs = src_ofs;
-    move->src.n_bits = n_bits;
-    move->dst.field = mf_from_id(dst);
-    move->dst.ofs = dst_ofs;
-    move->dst.n_bits = n_bits;
-}
-
-static void
-put_resubmit(uint8_t table_id, struct ofpbuf *ofpacts)
-{
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ofpacts);
-    resubmit->in_port = OFPP_IN_PORT;
-    resubmit->table_id = table_id;
-}
-
-/*
- * For a port binding, get the corresponding ovn-chassis-id tunnel port
- * from the associated encap.
- */
-static struct chassis_tunnel *
-get_port_binding_tun(const struct sbrec_port_binding *binding)
-{
-    struct sbrec_encap *encap = binding->encap;
-    struct sbrec_chassis *chassis = binding->chassis;
-    struct chassis_tunnel *tun = NULL;
-
-    if (encap) {
-        tun = chassis_tunnel_find(chassis->name, encap->ip);
-    }
-    if (!tun) {
-        tun = chassis_tunnel_find(chassis->name, NULL);
-    }
-    return tun;
-}
-
-static void
-put_encapsulation(enum mf_field_id mff_ovn_geneve,
-                  const struct chassis_tunnel *tun,
-                  const struct sbrec_datapath_binding *datapath,
-                  uint16_t outport, struct ofpbuf *ofpacts)
-{
-    if (tun->type == GENEVE) {
-        put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts);
-        put_load(outport, mff_ovn_geneve, 0, 32, ofpacts);
-        put_move(MFF_LOG_INPORT, 0, mff_ovn_geneve, 16, 15, ofpacts);
-    } else if (tun->type == STT) {
-        put_load(datapath->tunnel_key | ((uint64_t) outport << 24),
-                 MFF_TUN_ID, 0, 64, ofpacts);
-        put_move(MFF_LOG_INPORT, 0, MFF_TUN_ID, 40, 15, ofpacts);
-    } else if (tun->type == VXLAN) {
-        put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts);
-    } else {
-        OVS_NOT_REACHED();
-    }
-}
-
-static void
-put_stack(enum mf_field_id field, struct ofpact_stack *stack)
-{
-    stack->subfield.field = mf_from_id(field);
-    stack->subfield.ofs = 0;
-    stack->subfield.n_bits = stack->subfield.field->n_bits;
-}
-
-static const struct sbrec_port_binding *
-get_localnet_port(const struct hmap *local_datapaths, int64_t tunnel_key)
-{
-    const struct local_datapath *ld = get_local_datapath(local_datapaths,
-                                                         tunnel_key);
-    return ld ? ld->localnet_port : NULL;
-}
-
-/* Datapath zone IDs for connection tracking and NAT */
-struct zone_ids {
-    int ct;                     /* MFF_LOG_CT_ZONE. */
-    int dnat;                   /* MFF_LOG_DNAT_ZONE. */
-    int snat;                   /* MFF_LOG_SNAT_ZONE. */
-};
-
-static struct zone_ids
-get_zone_ids(const struct sbrec_port_binding *binding,
-             const struct simap *ct_zones)
-{
-    struct zone_ids zone_ids;
-
-    zone_ids.ct = simap_get(ct_zones, binding->logical_port);
-
-    const struct uuid *key = &binding->datapath->header_.uuid;
-
-    char *dnat = alloc_nat_zone_key(key, "dnat");
-    zone_ids.dnat = simap_get(ct_zones, dnat);
-    free(dnat);
-
-    char *snat = alloc_nat_zone_key(key, "snat");
-    zone_ids.snat = simap_get(ct_zones, snat);
-    free(snat);
-
-    return zone_ids;
-}
-
-static void
-put_replace_router_port_mac_flows(const struct
-                                  sbrec_port_binding *localnet_port,
-                                  const struct sbrec_chassis *chassis,
-                                  const struct hmap *local_datapaths,
-                                  struct ofpbuf *ofpacts_p,
-                                  ofp_port_t ofport,
-                                  struct ovn_desired_flow_table *flow_table)
-{
-    struct local_datapath *ld = get_local_datapath(local_datapaths,
-                                                   localnet_port->datapath->
-                                                   tunnel_key);
-    ovs_assert(ld);
-
-    uint32_t dp_key = localnet_port->datapath->tunnel_key;
-    uint32_t port_key = localnet_port->tunnel_key;
-    int tag = localnet_port->tag ? *localnet_port->tag : 0;
-    const char *network = smap_get(&localnet_port->options, "network_name");
-    struct eth_addr chassis_mac;
-
-    if (!network) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "Physical network not configured for datapath:"
-                     "%"PRId64" with localnet port",
-                     localnet_port->datapath->tunnel_key);
-        return;
-    }
-
-    /* Get chassis mac */
-    if (!chassis_get_mac(chassis, network, &chassis_mac)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        /* Keeping the log level low for backward compatibility.
-         * Chassis mac is a new configuration.
-         */
-        VLOG_DBG_RL(&rl, "Could not get chassis mac for network: %s", network);
-        return;
-    }
-
-    for (int i = 0; i < ld->n_peer_ports; i++) {
-        const struct sbrec_port_binding *rport_binding = ld->peer_ports[i];
-        struct eth_addr router_port_mac;
-        struct match match;
-        struct ofpact_mac *replace_mac;
-
-        /* Table 65, priority 150.
-         * =======================
-         *
-         * Implements output to localnet port.
-         * a. Flow replaces ingress router port mac with a chassis mac.
-         * b. Flow appends the vlan id localnet port is configured with.
-         */
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-
-        ovs_assert(rport_binding->n_mac == 1);
-        char *err_str = str_to_mac(rport_binding->mac[0], &router_port_mac);
-        if (err_str) {
-            /* Parsing of mac failed. */
-            VLOG_WARN("Parsing or router port mac failed for router port: %s, "
-                      "with error: %s", rport_binding->logical_port, err_str);
-            free(err_str);
-            return;
-        }
-
-        /* Replace Router mac flow */
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-        match_set_dl_src(&match, router_port_mac);
-
-        replace_mac = ofpact_put_SET_ETH_SRC(ofpacts_p);
-        replace_mac->mac = chassis_mac;
-
-        if (tag) {
-            struct ofpact_vlan_vid *vlan_vid;
-            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
-            vlan_vid->vlan_vid = tag;
-            vlan_vid->push_vlan_if_needed = true;
-        }
-
-        ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
-
-        ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 150, 0,
-                        &match, ofpacts_p, &localnet_port->header_.uuid);
-    }
-}
-
-static void
-put_local_common_flows(uint32_t dp_key, uint32_t port_key,
-                       uint32_t parent_port_key,
-                       const struct zone_ids *zone_ids,
-                       struct ofpbuf *ofpacts_p,
-                       struct ovn_desired_flow_table *flow_table)
-{
-    struct match match;
-
-    /* Table 33, priority 100.
-     * =======================
-     *
-     * Implements output to local hypervisor.  Each flow matches a
-     * logical output port on the local hypervisor, and resubmits to
-     * table 34.
-     */
-
-    match_init_catchall(&match);
-    ofpbuf_clear(ofpacts_p);
-
-    /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */
-    match_set_metadata(&match, htonll(dp_key));
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-
-    if (zone_ids) {
-        if (zone_ids->ct) {
-            put_load(zone_ids->ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p);
-        }
-        if (zone_ids->dnat) {
-            put_load(zone_ids->dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p);
-        }
-        if (zone_ids->snat) {
-            put_load(zone_ids->snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p);
-        }
-    }
-
-    /* Resubmit to table 34. */
-    put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
-    ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                    &match, ofpacts_p, hc_uuid);
-
-    /* Table 34, Priority 100.
-     * =======================
-     *
-     * Drop packets whose logical inport and outport are the same
-     * and the MLF_ALLOW_LOOPBACK flag is not set. */
-    match_init_catchall(&match);
-    ofpbuf_clear(ofpacts_p);
-    match_set_metadata(&match, htonll(dp_key));
-    match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-                         0, MLF_ALLOW_LOOPBACK);
-    match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100, 0,
-                    &match, ofpacts_p, hc_uuid);
-
-    /* Table 64, Priority 100.
-     * =======================
-     *
-     * If the packet is supposed to hair-pin because the
-     *   - "loopback" flag is set
-     *   - or if the destination is a nested container
-     *   - or if "nested_container" flag is set and the destination is the
-     *     parent port,
-     * temporarily set the in_port to zero, resubmit to
-     * table 65 for logical-to-physical translation, then restore
-     * the port number.
-     *
-     * If 'parent_port_key' is set, then the 'port_key' represents a nested
-     * container. */
-
-    bool nested_container = parent_port_key ? true: false;
-    match_init_catchall(&match);
-    ofpbuf_clear(ofpacts_p);
-    match_set_metadata(&match, htonll(dp_key));
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-    if (!nested_container) {
-        match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-                             MLF_ALLOW_LOOPBACK, MLF_ALLOW_LOOPBACK);
-    }
-
-    put_stack(MFF_IN_PORT, ofpact_put_STACK_PUSH(ofpacts_p));
-    put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p);
-    put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
-    put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0,
-                    &match, ofpacts_p, hc_uuid);
-
-    if (nested_container) {
-        /* It's a nested container and when the packet from the nested
-         * container is to be sent to the parent port, "nested_container"
-         * flag will be set. We need to temporarily set the in_port to zero
-         * as mentioned in the comment above.
-         *
-         * If a parent port has multiple child ports, then this if condition
-         * will be hit multiple times, but we want to add only one flow.
-         * ofctrl_add_flow() logs a warning message for duplicate flows.
-         * So use the function 'ofctrl_check_and_add_flow' which doesn't
-         * log a warning.
-         *
-         * Other option is to add this flow for all the ports which are not
-         * nested containers. In which case we will add this flow for all the
-         * ports even if they don't have any child ports which is
-         * unnecessary.
-         */
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, parent_port_key);
-        match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-                             MLF_NESTED_CONTAINER, MLF_NESTED_CONTAINER);
-
-        put_stack(MFF_IN_PORT, ofpact_put_STACK_PUSH(ofpacts_p));
-        put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p);
-        put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
-        put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
-        ofctrl_check_and_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0,
-                                  &match, ofpacts_p, hc_uuid, false);
-    }
-}
-
-static void
-load_logical_ingress_metadata(const struct sbrec_port_binding *binding,
-                              const struct zone_ids *zone_ids,
-                              struct ofpbuf *ofpacts_p)
-{
-    if (zone_ids) {
-        if (zone_ids->ct) {
-            put_load(zone_ids->ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p);
-        }
-        if (zone_ids->dnat) {
-            put_load(zone_ids->dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p);
-        }
-        if (zone_ids->snat) {
-            put_load(zone_ids->snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p);
-        }
-    }
-
-    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-    uint32_t dp_key = binding->datapath->tunnel_key;
-    uint32_t port_key = binding->tunnel_key;
-    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, ofpacts_p);
-    put_load(port_key, MFF_LOG_INPORT, 0, 32, ofpacts_p);
-}
-
-static void
-consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                      enum mf_field_id mff_ovn_geneve,
-                      const struct simap *ct_zones,
-                      const struct sset *active_tunnels,
-                      const struct hmap *local_datapaths,
-                      const struct sbrec_port_binding *binding,
-                      const struct sbrec_chassis *chassis,
-                      struct ovn_desired_flow_table *flow_table,
-                      struct ofpbuf *ofpacts_p)
-{
-    uint32_t dp_key = binding->datapath->tunnel_key;
-    uint32_t port_key = binding->tunnel_key;
-    if (!get_local_datapath(local_datapaths, dp_key)) {
-        return;
-    }
-
-    struct match match;
-    if (!strcmp(binding->type, "patch")
-        || (!strcmp(binding->type, "l3gateway")
-            && binding->chassis == chassis)) {
-        const char *peer_name = smap_get(&binding->options, "peer");
-        if (!peer_name) {
-            return;
-        }
-
-        const struct sbrec_port_binding *peer = lport_lookup_by_name(
-            sbrec_port_binding_by_name, peer_name);
-        if (!peer || strcmp(peer->type, binding->type)) {
-            return;
-        }
-        const char *peer_peer_name = smap_get(&peer->options, "peer");
-        if (!peer_peer_name || strcmp(peer_peer_name, binding->logical_port)) {
-            return;
-        }
-
-        struct zone_ids binding_zones = get_zone_ids(binding, ct_zones);
-        put_local_common_flows(dp_key, port_key, 0, &binding_zones,
-                               ofpacts_p, flow_table);
-
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-
-        size_t clone_ofs = ofpacts_p->size;
-        struct ofpact_nest *clone = ofpact_put_CLONE(ofpacts_p);
-        ofpact_put_CT_CLEAR(ofpacts_p);
-        put_load(0, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p);
-        put_load(0, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p);
-        put_load(0, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p);
-        struct zone_ids peer_zones = get_zone_ids(peer, ct_zones);
-        load_logical_ingress_metadata(peer, &peer_zones, ofpacts_p);
-        put_load(0, MFF_LOG_FLAGS, 0, 32, ofpacts_p);
-        put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
-        for (int i = 0; i < MFF_N_LOG_REGS; i++) {
-            put_load(0, MFF_LOG_REG0 + i, 0, 32, ofpacts_p);
-        }
-        put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p);
-        put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
-        clone = ofpbuf_at_assert(ofpacts_p, clone_ofs, sizeof *clone);
-        ofpacts_p->header = clone;
-        ofpact_finish_CLONE(ofpacts_p, &clone);
-
-        ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0,
-                        &match, ofpacts_p, &binding->header_.uuid);
-        return;
-    }
-
-    struct ha_chassis_ordered *ha_ch_ordered
-        = ha_chassis_get_ordered(binding->ha_chassis_group);
-
-    if (!strcmp(binding->type, "chassisredirect")
-        && (binding->chassis == chassis
-            || ha_chassis_group_is_active(binding->ha_chassis_group,
-                                          active_tunnels, chassis))) {
-
-        /* Table 33, priority 100.
-         * =======================
-         *
-         * Implements output to local hypervisor.  Each flow matches a
-         * logical output port on the local hypervisor, and resubmits to
-         * table 34.  For ports of type "chassisredirect", the logical
-         * output port is changed from the "chassisredirect" port to the
-         * underlying distributed port. */
-
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-
-        const char *distributed_port = smap_get_def(&binding->options,
-                                                    "distributed-port", "");
-        const struct sbrec_port_binding *distributed_binding
-            = lport_lookup_by_name(sbrec_port_binding_by_name,
-                                   distributed_port);
-
-        if (!distributed_binding) {
-            /* Packet will be dropped. */
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl, "No port binding record for distributed "
-                         "port %s referred by chassisredirect port %s",
-                         distributed_port,
-                         binding->logical_port);
-        } else if (binding->datapath !=
-                   distributed_binding->datapath) {
-            /* Packet will be dropped. */
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl,
-                         "chassisredirect port %s refers to "
-                         "distributed port %s in wrong datapath",
-                         binding->logical_port,
-                         distributed_port);
-        } else {
-            put_load(distributed_binding->tunnel_key,
-                     MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
-
-            struct zone_ids zone_ids = get_zone_ids(distributed_binding,
-                                                    ct_zones);
-            if (zone_ids.ct) {
-                put_load(zone_ids.ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p);
-            }
-            if (zone_ids.dnat) {
-                put_load(zone_ids.dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p);
-            }
-            if (zone_ids.snat) {
-                put_load(zone_ids.snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p);
-            }
-
-            /* Resubmit to table 34. */
-            put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
-        }
-
-        ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, ofpacts_p, &binding->header_.uuid);
-
-        goto out;
-    }
-
-    /* Find the OpenFlow port for the logical port, as 'ofport'.  This is
-     * one of:
-     *
-     *     - If the port is a VIF on the chassis we're managing, the
-     *       OpenFlow port for the VIF.  'tun' will be NULL.
-     *
-     *       The same logic handles ports that OVN implements as Open vSwitch
-     *       patch ports, that is, "localnet" and "l2gateway" ports.
-     *
-     *       For a container nested inside a VM and accessible via a VLAN,
-     *       'tag' is the VLAN ID; otherwise 'tag' is 0.
-     *
-     *       For a localnet or l2gateway patch port, if a VLAN ID was
-     *       configured, 'tag' is set to that VLAN ID; otherwise 'tag' is 0.
-     *
-     *     - If the port is on a remote chassis, the OpenFlow port for a
-     *       tunnel to the VIF's remote chassis.  'tun' identifies that
-     *       tunnel.
-     */
-
-    int tag = 0;
-    bool nested_container = false;
-    const struct sbrec_port_binding *parent_port = NULL;
-    ofp_port_t ofport;
-    bool is_remote = false;
-    if (binding->parent_port && *binding->parent_port) {
-        if (!binding->tag) {
-            goto out;
-        }
-        ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
-                                      binding->parent_port));
-        if (ofport) {
-            tag = *binding->tag;
-            nested_container = true;
-            parent_port = lport_lookup_by_name(
-                sbrec_port_binding_by_name, binding->parent_port);
-        }
-    } else {
-        ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
-                                      binding->logical_port));
-        const char *requested_chassis = smap_get(&binding->options,
-                                                 "requested-chassis");
-        if (ofport && requested_chassis && requested_chassis[0] &&
-            strcmp(requested_chassis, chassis->name) &&
-            strcmp(requested_chassis, chassis->hostname)) {
-            /* Even though there is an ofport for this port_binding, it is
-             * requested on a different chassis. So ignore this ofport.
-             */
-            ofport = 0;
-        }
-
-        if ((!strcmp(binding->type, "localnet")
-            || !strcmp(binding->type, "l2gateway"))
-            && ofport && binding->tag) {
-            tag = *binding->tag;
-        }
-    }
-
-    bool is_ha_remote = false;
-    const struct chassis_tunnel *tun = NULL;
-    const struct sbrec_port_binding *localnet_port =
-        get_localnet_port(local_datapaths, dp_key);
-    if (!ofport) {
-        /* It is remote port, may be reached by tunnel or localnet port */
-        is_remote = true;
-        if (localnet_port) {
-            ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
-                                          localnet_port->logical_port));
-            if (!ofport) {
-                goto out;
-            }
-        } else {
-            if (!ha_ch_ordered || ha_ch_ordered->n_ha_ch < 2) {
-                /* It's on a single remote chassis */
-                if (!binding->chassis) {
-                    goto out;
-                }
-                tun = chassis_tunnel_find(binding->chassis->name, NULL);
-                if (!tun) {
-                    goto out;
-                }
-                ofport = tun->ofport;
-            } else {
-                /* It's distributed across the chassis belonging to
-                 * an HA chassis group. */
-                is_ha_remote = true;
-            }
-        }
-    }
-
-    if (!is_remote) {
-        /* Packets that arrive from a vif can belong to a VM or
-         * to a container located inside that VM. Packets that
-         * arrive from containers have a tag (vlan) associated with them.
-         */
-
-        struct zone_ids zone_ids = get_zone_ids(binding, ct_zones);
-        uint32_t parent_port_key = parent_port ? parent_port->tunnel_key : 0;
-        /* Pass the parent port tunnel key if the port is a nested
-         * container. */
-        put_local_common_flows(dp_key, port_key, parent_port_key, &zone_ids,
-                               ofpacts_p, flow_table);
-
-        /* Table 0, Priority 150 and 100.
-         * ==============================
-         *
-         * Priority 150 is for tagged traffic. This may be containers in a
-         * VM or a VLAN on a local network. For such traffic, match on the
-         * tags and then strip the tag.
-         *
-         * Priority 100 is for traffic belonging to VMs or untagged locally
-         * connected networks.
-         *
-         * For both types of traffic: set MFF_LOG_INPORT to the logical
-         * input port, MFF_LOG_DATAPATH to the logical datapath, and
-         * resubmit into the logical ingress pipeline starting at table
-         * 16. */
-        ofpbuf_clear(ofpacts_p);
-        match_init_catchall(&match);
-        match_set_in_port(&match, ofport);
-
-        /* Match a VLAN tag and strip it, including stripping priority tags
-         * (e.g. VLAN ID 0).  In the latter case we'll add a second flow
-         * for frames that lack any 802.1Q header later. */
-        if (tag || !strcmp(binding->type, "localnet")
-            || !strcmp(binding->type, "l2gateway")) {
-            match_set_dl_vlan(&match, htons(tag), 0);
-            if (nested_container) {
-                /* When a packet comes from a container sitting behind a
-                 * parent_port, we should let it loopback to other containers
-                 * or the parent_port itself. Indicate this by setting the
-                 * MLF_NESTED_CONTAINER_BIT in MFF_LOG_FLAGS.*/
-                put_load(1, MFF_LOG_FLAGS, MLF_NESTED_CONTAINER_BIT, 1,
-                         ofpacts_p);
-            }
-            ofpact_put_STRIP_VLAN(ofpacts_p);
-        }
-
-        /* Remember the size with just strip vlan added so far,
-         * as we're going to remove this with ofpbuf_pull() later. */
-        uint32_t ofpacts_orig_size = ofpacts_p->size;
-
-        load_logical_ingress_metadata(binding, &zone_ids, ofpacts_p);
-
-        /* Resubmit to first logical ingress pipeline table. */
-        put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
-        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
-                        tag ? 150 : 100, 0, &match, ofpacts_p,
-                        &binding->header_.uuid);
-
-        if (!tag && (!strcmp(binding->type, "localnet")
-                     || !strcmp(binding->type, "l2gateway"))) {
-
-            /* Add a second flow for frames that lack any 802.1Q
-             * header.  For these, drop the OFPACT_STRIP_VLAN
-             * action. */
-            ofpbuf_pull(ofpacts_p, ofpacts_orig_size);
-            match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI));
-            ofctrl_add_flow(flow_table, 0, 100, 0, &match, ofpacts_p,
-                            &binding->header_.uuid);
-        }
-
-        /* Table 65, Priority 100.
-         * =======================
-         *
-         * Deliver the packet to the local vif. */
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-        if (tag) {
-            /* For containers sitting behind a local vif, tag the packets
-             * before delivering them. */
-            struct ofpact_vlan_vid *vlan_vid;
-            vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p);
-            vlan_vid->vlan_vid = tag;
-            vlan_vid->push_vlan_if_needed = true;
-        }
-        ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
-        if (tag) {
-            /* Revert the tag added to the packets headed to containers
-             * in the previous step. If we don't do this, the packets
-             * that are to be broadcasted to a VM in the same logical
-             * switch will also contain the tag. */
-            ofpact_put_STRIP_VLAN(ofpacts_p);
-        }
-        ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0,
-                        &match, ofpacts_p, &binding->header_.uuid);
-
-        if (!strcmp(binding->type, "localnet")) {
-            put_replace_router_port_mac_flows(binding, chassis,
-                                              local_datapaths, ofpacts_p,
-                                              ofport, flow_table);
-        }
-
-    } else if (!tun && !is_ha_remote) {
-        /* Remote port connected by localnet port */
-        /* Table 33, priority 100.
-         * =======================
-         *
-         * Implements switching to localnet port. Each flow matches a
-         * logical output port on remote hypervisor, switch the output port
-         * to connected localnet port and resubmits to same table.
-         */
-
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-
-        /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-
-        put_load(localnet_port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
-
-        /* Resubmit to table 33. */
-        put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
-        ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, ofpacts_p, &binding->header_.uuid);
-    } else {
-        /* Remote port connected by tunnel */
-
-        /* Table 32, priority 100.
-         * =======================
-         *
-         * Handles traffic that needs to be sent to a remote hypervisor.  Each
-         * flow matches an output port that includes a logical port on a remote
-         * hypervisor, and tunnels the packet to that hypervisor.
-         */
-        match_init_catchall(&match);
-        ofpbuf_clear(ofpacts_p);
-
-        /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */
-        match_set_metadata(&match, htonll(dp_key));
-        match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-
-        if (!is_ha_remote) {
-            /* Setup encapsulation */
-            const struct chassis_tunnel *rem_tun =
-                get_port_binding_tun(binding);
-            if (!rem_tun) {
-                goto out;
-            }
-            put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
-                              port_key, ofpacts_p);
-            /* Output to tunnel. */
-            ofpact_put_OUTPUT(ofpacts_p)->port = rem_tun->ofport;
-        } else {
-            /* Make sure all tunnel endpoints use the same encapsulation,
-             * and set it up */
-            for (size_t i = 0; i < ha_ch_ordered->n_ha_ch; i++) {
-                const struct sbrec_chassis *ch =
-                    ha_ch_ordered->ha_ch[i].chassis;
-                if (!ch) {
-                    continue;
-                }
-                if (!tun) {
-                    tun = chassis_tunnel_find(ch->name, NULL);
-                } else {
-                    struct chassis_tunnel *chassis_tunnel =
-                        chassis_tunnel_find(ch->name, NULL);
-                    if (chassis_tunnel &&
-                        tun->type != chassis_tunnel->type) {
-                        static struct vlog_rate_limit rl =
-                            VLOG_RATE_LIMIT_INIT(1, 1);
-                        VLOG_ERR_RL(&rl, "Port %s has Gateway_Chassis "
-                                            "with mixed encapsulations, only "
-                                            "uniform encapsulations are "
-                                            "supported.",
-                                    binding->logical_port);
-                        goto out;
-                    }
-                }
-            }
-            if (!tun) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_ERR_RL(&rl, "No tunnel endpoint found for HA chassis in "
-                                 "HA chassis group of port %s",
-                            binding->logical_port);
-                goto out;
-            }
-
-            put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
-                              port_key, ofpacts_p);
-
-            /* Output to tunnels with active/backup */
-            struct ofpact_bundle *bundle = ofpact_put_BUNDLE(ofpacts_p);
-
-            for (size_t i = 0; i < ha_ch_ordered->n_ha_ch; i++) {
-                const struct sbrec_chassis *ch =
-                    ha_ch_ordered->ha_ch[i].chassis;
-                if (!ch) {
-                    continue;
-                }
-                tun = chassis_tunnel_find(ch->name, NULL);
-                if (!tun) {
-                    continue;
-                }
-                if (bundle->n_slaves >= BUNDLE_MAX_SLAVES) {
-                    static struct vlog_rate_limit rl =
-                            VLOG_RATE_LIMIT_INIT(1, 1);
-                    VLOG_WARN_RL(&rl, "Remote endpoints for port beyond "
-                                        "BUNDLE_MAX_SLAVES");
-                    break;
-                }
-                ofpbuf_put(ofpacts_p, &tun->ofport,
-                            sizeof tun->ofport);
-                bundle = ofpacts_p->header;
-                bundle->n_slaves++;
-            }
-
-            bundle->algorithm = NX_BD_ALG_ACTIVE_BACKUP;
-            /* Although ACTIVE_BACKUP bundle algorithm seems to ignore
-             * the next two fields, those are always set */
-            bundle->basis = 0;
-            bundle->fields = NX_HASH_FIELDS_ETH_SRC;
-            ofpact_finish_BUNDLE(ofpacts_p, &bundle);
-        }
-        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0,
-                        &match, ofpacts_p, &binding->header_.uuid);
-    }
-out:
-    if (ha_ch_ordered) {
-        ha_chassis_destroy_ordered(ha_ch_ordered);
-    }
-}
-
-static void
-consider_mc_group(enum mf_field_id mff_ovn_geneve,
-                  const struct simap *ct_zones,
-                  const struct hmap *local_datapaths,
-                  const struct sbrec_chassis *chassis,
-                  const struct sbrec_multicast_group *mc,
-                  struct ovn_desired_flow_table *flow_table)
-{
-    uint32_t dp_key = mc->datapath->tunnel_key;
-    if (!get_local_datapath(local_datapaths, dp_key)) {
-        return;
-    }
-
-    struct sset remote_chassis = SSET_INITIALIZER(&remote_chassis);
-    struct match match;
-
-    match_init_catchall(&match);
-    match_set_metadata(&match, htonll(dp_key));
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, mc->tunnel_key);
-
-    /* Go through all of the ports in the multicast group:
-     *
-     *    - For remote ports, add the chassis to 'remote_chassis'.
-     *
-     *    - For local ports (other than logical patch ports), add actions
-     *      to 'ofpacts' to set the output port and resubmit.
-     *
-     *    - For logical patch ports, add actions to 'remote_ofpacts'
-     *      instead.  (If we put them in 'ofpacts', then the output
-     *      would happen on every hypervisor in the multicast group,
-     *      effectively duplicating the packet.)
-     */
-    struct ofpbuf ofpacts;
-    ofpbuf_init(&ofpacts, 0);
-    struct ofpbuf remote_ofpacts;
-    ofpbuf_init(&remote_ofpacts, 0);
-    for (size_t i = 0; i < mc->n_ports; i++) {
-        struct sbrec_port_binding *port = mc->ports[i];
-
-        if (port->datapath != mc->datapath) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, UUID_FMT": multicast group contains ports "
-                         "in wrong datapath",
-                         UUID_ARGS(&mc->header_.uuid));
-            continue;
-        }
-
-        int zone_id = simap_get(ct_zones, port->logical_port);
-        if (zone_id) {
-            put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts);
-        }
-
-        if (!strcmp(port->type, "patch")) {
-            put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32,
-                     &remote_ofpacts);
-            put_resubmit(OFTABLE_CHECK_LOOPBACK, &remote_ofpacts);
-        } else if (simap_contains(&localvif_to_ofport,
-                           (port->parent_port && *port->parent_port)
-                           ? port->parent_port : port->logical_port)
-                   || (!strcmp(port->type, "l3gateway")
-                       && port->chassis == chassis)) {
-            put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
-            put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts);
-        } else if (port->chassis && !get_localnet_port(local_datapaths,
-                                         mc->datapath->tunnel_key)) {
-            /* Add remote chassis only when localnet port not exist,
-             * otherwise multicast will reach remote ports through localnet
-             * port. */
-            sset_add(&remote_chassis, port->chassis->name);
-        }
-    }
-
-    /* Table 33, priority 100.
-     * =======================
-     *
-     * Handle output to the local logical ports in the multicast group, if
-     * any. */
-    bool local_ports = ofpacts.size > 0;
-    if (local_ports) {
-        /* Following delivery to local logical ports, restore the multicast
-         * group as the logical output port. */
-        put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
-
-        ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, &ofpacts, &mc->header_.uuid);
-    }
-
-    /* Table 32, priority 100.
-     * =======================
-     *
-     * Handle output to the remote chassis in the multicast group, if
-     * any. */
-    if (!sset_is_empty(&remote_chassis) || remote_ofpacts.size > 0) {
-        if (remote_ofpacts.size > 0) {
-            /* Following delivery to logical patch ports, restore the
-             * multicast group as the logical output port. */
-            put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32,
-                     &remote_ofpacts);
-        }
-
-        const char *chassis_name;
-        const struct chassis_tunnel *prev = NULL;
-        SSET_FOR_EACH (chassis_name, &remote_chassis) {
-            const struct chassis_tunnel *tun
-                = chassis_tunnel_find(chassis_name, NULL);
-            if (!tun) {
-                continue;
-            }
-
-            if (!prev || tun->type != prev->type) {
-                put_encapsulation(mff_ovn_geneve, tun, mc->datapath,
-                                  mc->tunnel_key, &remote_ofpacts);
-                prev = tun;
-            }
-            ofpact_put_OUTPUT(&remote_ofpacts)->port = tun->ofport;
-        }
-
-        if (remote_ofpacts.size) {
-            if (local_ports) {
-                put_resubmit(OFTABLE_LOCAL_OUTPUT, &remote_ofpacts);
-            }
-            ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0,
-                            &match, &remote_ofpacts, &mc->header_.uuid);
-        }
-    }
-    ofpbuf_uninit(&ofpacts);
-    ofpbuf_uninit(&remote_ofpacts);
-    sset_destroy(&remote_chassis);
-}
-
-/* Replaces 'old' by 'new' (destroying 'new').  Returns true if 'old' and 'new'
- * contained different data, false if they were the same. */
-static bool
-update_ofports(struct simap *old, struct simap *new)
-{
-    bool changed = !simap_equal(old, new);
-    simap_swap(old, new);
-    simap_destroy(new);
-    return changed;
-}
-
-void physical_handle_port_binding_changes(
-        struct ovsdb_idl_index *sbrec_port_binding_by_name,
-        const struct sbrec_port_binding_table *pb_table,
-        enum mf_field_id mff_ovn_geneve,
-        const struct sbrec_chassis *chassis,
-        const struct simap *ct_zones,
-        struct hmap *local_datapaths,
-        struct sset *active_tunnels,
-        struct ovn_desired_flow_table *flow_table)
-{
-    const struct sbrec_port_binding *binding;
-    struct ofpbuf ofpacts;
-    ofpbuf_init(&ofpacts, 0);
-    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding, pb_table) {
-        if (sbrec_port_binding_is_deleted(binding)) {
-            ofctrl_remove_flows(flow_table, &binding->header_.uuid);
-        } else {
-            if (!sbrec_port_binding_is_new(binding)) {
-                ofctrl_remove_flows(flow_table, &binding->header_.uuid);
-            }
-            consider_port_binding(sbrec_port_binding_by_name,
-                                  mff_ovn_geneve, ct_zones,
-                                  active_tunnels, local_datapaths,
-                                  binding, chassis,
-                                  flow_table, &ofpacts);
-        }
-    }
-
-}
-
-void
-physical_handle_mc_group_changes(
-        const struct sbrec_multicast_group_table *multicast_group_table,
-        enum mf_field_id mff_ovn_geneve,
-        const struct sbrec_chassis *chassis,
-        const struct simap *ct_zones,
-        const struct hmap *local_datapaths,
-        struct ovn_desired_flow_table *flow_table)
-{
-    const struct sbrec_multicast_group *mc;
-    SBREC_MULTICAST_GROUP_TABLE_FOR_EACH_TRACKED (mc, multicast_group_table) {
-        if (sbrec_multicast_group_is_deleted(mc)) {
-            ofctrl_remove_flows(flow_table, &mc->header_.uuid);
-        } else {
-            if (!sbrec_multicast_group_is_new(mc)) {
-                ofctrl_remove_flows(flow_table, &mc->header_.uuid);
-            }
-            consider_mc_group(mff_ovn_geneve, ct_zones, local_datapaths,
-                              chassis, mc, flow_table);
-        }
-    }
-}
-
-void
-physical_run(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-             const struct sbrec_multicast_group_table *multicast_group_table,
-             const struct sbrec_port_binding_table *port_binding_table,
-             enum mf_field_id mff_ovn_geneve,
-             const struct ovsrec_bridge *br_int,
-             const struct sbrec_chassis *chassis,
-             const struct simap *ct_zones,
-             const struct hmap *local_datapaths,
-             const struct sset *local_lports,
-             const struct sset *active_tunnels,
-             struct ovn_desired_flow_table *flow_table)
-{
-    if (!hc_uuid) {
-        hc_uuid = xmalloc(sizeof(struct uuid));
-        uuid_generate(hc_uuid);
-    }
-
-    /* This bool tracks physical mapping changes. */
-    bool physical_map_changed = false;
-
-    struct simap new_localvif_to_ofport =
-        SIMAP_INITIALIZER(&new_localvif_to_ofport);
-    struct simap new_tunnel_to_ofport =
-        SIMAP_INITIALIZER(&new_tunnel_to_ofport);
-    for (int i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-        if (!strcmp(port_rec->name, br_int->name)) {
-            continue;
-        }
-
-        const char *tunnel_id = smap_get(&port_rec->external_ids,
-                                         "ovn-chassis-id");
-        if (tunnel_id &&
-                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL)) {
-            continue;
-        }
-
-        const char *localnet = smap_get(&port_rec->external_ids,
-                                        "ovn-localnet-port");
-        const char *l2gateway = smap_get(&port_rec->external_ids,
-                                        "ovn-l2gateway-port");
-
-        for (int j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
-
-            /* Get OpenFlow port number. */
-            if (!iface_rec->n_ofport) {
-                continue;
-            }
-            int64_t ofport = iface_rec->ofport[0];
-            if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) {
-                continue;
-            }
-
-            /* Record as patch to local net, logical patch port, chassis, or
-             * local logical port. */
-            bool is_patch = !strcmp(iface_rec->type, "patch");
-            if (is_patch && localnet) {
-                /* localnet patch ports can be handled just like VIFs. */
-                simap_put(&new_localvif_to_ofport, localnet, ofport);
-                break;
-            } else if (is_patch && l2gateway) {
-                /* L2 gateway patch ports can be handled just like VIFs. */
-                simap_put(&new_localvif_to_ofport, l2gateway, ofport);
-                break;
-            } else if (tunnel_id) {
-                enum chassis_tunnel_type tunnel_type;
-                if (!strcmp(iface_rec->type, "geneve")) {
-                    tunnel_type = GENEVE;
-                    if (!mff_ovn_geneve) {
-                        continue;
-                    }
-                } else if (!strcmp(iface_rec->type, "stt")) {
-                    tunnel_type = STT;
-                } else if (!strcmp(iface_rec->type, "vxlan")) {
-                    tunnel_type = VXLAN;
-                } else {
-                    continue;
-                }
-
-                simap_put(&new_tunnel_to_ofport, tunnel_id, ofport);
-                /*
-                 * We split the tunnel_id to get the chassis-id
-                 * and hash the tunnel list on the chassis-id. The
-                 * reason to use the chassis-id alone is because 
-                 * there might be cases (multicast, gateway chassis)
-                 * where we need to tunnel to the chassis, but won't
-                 * have the encap-ip specifically.
-                 */
-                char *hash_id = NULL;
-                char *ip = NULL;
-
-                if (!encaps_tunnel_id_parse(tunnel_id, &hash_id, &ip)) {
-                    continue;
-                }
-                struct chassis_tunnel *tun = chassis_tunnel_find(hash_id, ip);
-                if (tun) {
-                    /* If the tunnel's ofport has changed, update. */
-                    if (tun->ofport != u16_to_ofp(ofport) ||
-                        tun->type != tunnel_type) {
-                        tun->ofport = u16_to_ofp(ofport);
-                        tun->type = tunnel_type;
-                        physical_map_changed = true;
-                    }
-                } else {
-                    tun = xmalloc(sizeof *tun);
-                    hmap_insert(&tunnels, &tun->hmap_node,
-                                hash_string(hash_id, 0));
-                    tun->chassis_id = xstrdup(tunnel_id);
-                    tun->ofport = u16_to_ofp(ofport);
-                    tun->type = tunnel_type;
-                    physical_map_changed = true;
-                }
-                free(hash_id);
-                free(ip);
-                break;
-            } else {
-                const char *iface_id = smap_get(&iface_rec->external_ids,
-                                                "iface-id");
-                if (iface_id) {
-                    simap_put(&new_localvif_to_ofport, iface_id, ofport);
-                }
-            }
-        }
-    }
-
-    /* Remove tunnels that are no longer here. */
-    struct chassis_tunnel *tun, *tun_next;
-    HMAP_FOR_EACH_SAFE (tun, tun_next, hmap_node, &tunnels) {
-        if (!simap_find(&new_tunnel_to_ofport, tun->chassis_id)) {
-            hmap_remove(&tunnels, &tun->hmap_node);
-            physical_map_changed = true;
-            free(tun->chassis_id);
-            free(tun);
-        }
-    }
-
-    /* Capture changed or removed openflow ports. */
-    physical_map_changed |= update_ofports(&localvif_to_ofport,
-                                           &new_localvif_to_ofport);
-    if (physical_map_changed) {
-        /* Reprocess logical flow table immediately. */
-        poll_immediate_wake();
-    }
-
-    struct ofpbuf ofpacts;
-    ofpbuf_init(&ofpacts, 0);
-
-    /* Set up flows in table 0 for physical-to-logical translation and in table
-     * 64 for logical-to-physical translation. */
-    const struct sbrec_port_binding *binding;
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) {
-        consider_port_binding(sbrec_port_binding_by_name,
-                              mff_ovn_geneve, ct_zones,
-                              active_tunnels, local_datapaths,
-                              binding, chassis,
-                              flow_table, &ofpacts);
-    }
-
-    /* Handle output to multicast groups, in tables 32 and 33. */
-    const struct sbrec_multicast_group *mc;
-    SBREC_MULTICAST_GROUP_TABLE_FOR_EACH (mc, multicast_group_table) {
-        consider_mc_group(mff_ovn_geneve, ct_zones, local_datapaths,
-                          chassis, mc, flow_table);
-    }
-
-    /* Table 0, priority 100.
-     * ======================
-     *
-     * Process packets that arrive from a remote hypervisor (by matching
-     * on tunnel in_port). */
-
-    /* Add flows for Geneve and STT encapsulations.  These
-     * encapsulations have metadata about the ingress and egress logical
-     * ports.  We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and
-     * MFF_LOG_OUTPORT from the tunnel key data, then resubmit to table
-     * 33 to handle packets to the local hypervisor. */
-    HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
-        struct match match = MATCH_CATCHALL_INITIALIZER;
-        match_set_in_port(&match, tun->ofport);
-
-        ofpbuf_clear(&ofpacts);
-        if (tun->type == GENEVE) {
-            put_move(MFF_TUN_ID, 0,  MFF_LOG_DATAPATH, 0, 24, &ofpacts);
-            put_move(mff_ovn_geneve, 16, MFF_LOG_INPORT, 0, 15,
-                     &ofpacts);
-            put_move(mff_ovn_geneve, 0, MFF_LOG_OUTPORT, 0, 16,
-                     &ofpacts);
-        } else if (tun->type == STT) {
-            put_move(MFF_TUN_ID, 40, MFF_LOG_INPORT,   0, 15, &ofpacts);
-            put_move(MFF_TUN_ID, 24, MFF_LOG_OUTPORT,  0, 16, &ofpacts);
-            put_move(MFF_TUN_ID,  0, MFF_LOG_DATAPATH, 0, 24, &ofpacts);
-        } else if (tun->type == VXLAN) {
-            /* We'll handle VXLAN later. */
-            continue;
-        } else {
-            OVS_NOT_REACHED();
-        }
-
-        put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-
-        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-                        &ofpacts, hc_uuid);
-    }
-
-    /* Add flows for VXLAN encapsulations.  Due to the limited amount of
-     * metadata, we only support VXLAN for connections to gateways.  The
-     * VNI is used to populate MFF_LOG_DATAPATH.  The gateway's logical
-     * port is set to MFF_LOG_INPORT.  Then the packet is resubmitted to
-     * table 16 to determine the logical egress port. */
-    HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
-        if (tun->type != VXLAN) {
-            continue;
-        }
-
-        SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) {
-            struct match match = MATCH_CATCHALL_INITIALIZER;
-
-            if (!binding->chassis ||
-                !encaps_tunnel_id_match(tun->chassis_id,
-                                        binding->chassis->name, NULL)) {
-                continue;
-            }
-
-            match_set_in_port(&match, tun->ofport);
-            match_set_tun_id(&match, htonll(binding->datapath->tunnel_key));
-
-            ofpbuf_clear(&ofpacts);
-            put_move(MFF_TUN_ID, 0,  MFF_LOG_DATAPATH, 0, 24, &ofpacts);
-            put_load(binding->tunnel_key, MFF_LOG_INPORT, 0, 15, &ofpacts);
-            /* For packets received from a vxlan tunnel, set a flag to that
-             * effect. */
-            put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1, &ofpacts);
-            put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
-
-            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-                            &ofpacts, hc_uuid);
-        }
-    }
-
-    /* Table 32, priority 150.
-     * =======================
-     *
-     * Handles packets received from a VXLAN tunnel which get resubmitted to
-     * OFTABLE_LOG_INGRESS_PIPELINE due to lack of needed metadata in VXLAN,
-     * explicitly skip sending back out any tunnels and resubmit to table 33
-     * for local delivery.
-     */
-    struct match match;
-    match_init_catchall(&match);
-    match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-                         MLF_RCV_FROM_VXLAN, MLF_RCV_FROM_VXLAN);
-
-    /* Resubmit to table 33. */
-    ofpbuf_clear(&ofpacts);
-    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
-                    &match, &ofpacts, hc_uuid);
-
-    /* Table 32, priority 150.
-     * =======================
-     *
-     * Packets that should not be sent to other hypervisors.
-     */
-    match_init_catchall(&match);
-    match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-                         MLF_LOCAL_ONLY, MLF_LOCAL_ONLY);
-    /* Resubmit to table 33. */
-    ofpbuf_clear(&ofpacts);
-    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
-                    &match, &ofpacts, hc_uuid);
-
-    /* Table 32, priority 150.
-     * =======================
-     *
-     * Handles packets received from ports of type "localport".  These ports
-     * are present on every hypervisor.  Traffic that originates at one should
-     * never go over a tunnel to a remote hypervisor, so resubmit them to table
-     * 33 for local delivery. */
-    match_init_catchall(&match);
-    ofpbuf_clear(&ofpacts);
-    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    const char *localport;
-    SSET_FOR_EACH (localport, local_lports) {
-        /* Iterate over all local logical ports and insert a drop
-         * rule with higher priority for every localport in this
-         * datapath. */
-        const struct sbrec_port_binding *pb = lport_lookup_by_name(
-            sbrec_port_binding_by_name, localport);
-        if (pb && !strcmp(pb->type, "localport")) {
-            match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, pb->tunnel_key);
-            match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
-            ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
-                            &match, &ofpacts, hc_uuid);
-        }
-    }
-
-    /* Table 32, Priority 0.
-     * =======================
-     *
-     * Resubmit packets that are not directed at tunnels or part of a
-     * multicast group to the local output table. */
-    match_init_catchall(&match);
-    ofpbuf_clear(&ofpacts);
-    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match, &ofpacts,
-                    hc_uuid);
-
-    /* Table 34, Priority 0.
-     * =======================
-     *
-     * Resubmit packets that don't output to the ingress port (already checked
-     * in table 33) to the logical egress pipeline, clearing the logical
-     * registers (for consistent behavior with packets that get tunneled). */
-    match_init_catchall(&match);
-    ofpbuf_clear(&ofpacts);
-    for (int i = 0; i < MFF_N_LOG_REGS; i++) {
-        put_load(0, MFF_REG0 + i, 0, 32, &ofpacts);
-    }
-    put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, 0, &match,
-                    &ofpacts, hc_uuid);
-
-    /* Table 64, Priority 0.
-     * =======================
-     *
-     * Resubmit packets that do not have the MLF_ALLOW_LOOPBACK flag set
-     * to table 65 for logical-to-physical translation. */
-    match_init_catchall(&match);
-    ofpbuf_clear(&ofpacts);
-    put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, 0, &match, &ofpacts,
-                    hc_uuid);
-
-    ofpbuf_uninit(&ofpacts);
-
-    simap_destroy(&new_tunnel_to_ofport);
-}
diff --git a/ovn/controller/physical.h b/ovn/controller/physical.h
deleted file mode 100644
index c5544e8de..000000000
--- a/ovn/controller/physical.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Copyright (c) 2015, 2016 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 OVN_PHYSICAL_H
-#define OVN_PHYSICAL_H 1
-
-/* Logical/Physical Translation
- * ============================
- *
- * This module implements physical-to-logical and logical-to-physical
- * translation as separate OpenFlow tables that run before the ingress pipeline
- * and after the egress pipeline, respectively, as well as to connect the
- * two pipelines.
- */
-
-#include "openvswitch/meta-flow.h"
-
-struct hmap;
-struct ovsdb_idl_index;
-struct ovsrec_bridge;
-struct simap;
-struct sbrec_multicast_group_table;
-struct sbrec_port_binding_table;
-struct sset;
-
-/* OVN Geneve option information.
- *
- * Keep these in sync with the documentation in ovn-architecture(7). */
-#define OVN_GENEVE_CLASS 0x0102  /* Assigned Geneve class for OVN. */
-#define OVN_GENEVE_TYPE 0x80     /* Critical option. */
-#define OVN_GENEVE_LEN 4
-
-void physical_register_ovs_idl(struct ovsdb_idl *);
-void physical_run(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                  const struct sbrec_multicast_group_table *,
-                  const struct sbrec_port_binding_table *,
-                  enum mf_field_id mff_ovn_geneve,
-                  const struct ovsrec_bridge *br_int,
-                  const struct sbrec_chassis *chassis,
-                  const struct simap *ct_zones,
-                  const struct hmap *local_datapaths,
-                  const struct sset *local_lports,
-                  const struct sset *active_tunnels,
-                  struct ovn_desired_flow_table *);
-void physical_handle_port_binding_changes(
-        struct ovsdb_idl_index *sbrec_port_binding_by_name,
-        const struct sbrec_port_binding_table *,
-        enum mf_field_id mff_ovn_geneve,
-        const struct sbrec_chassis *,
-        const struct simap *ct_zones,
-        struct hmap *local_datapaths,
-        struct sset *active_tunnels,
-        struct ovn_desired_flow_table *);
-
-void physical_handle_mc_group_changes(
-        const struct sbrec_multicast_group_table *,
-        enum mf_field_id mff_ovn_geneve,
-        const struct sbrec_chassis *,
-        const struct simap *ct_zones,
-        const struct hmap *local_datapaths,
-        struct ovn_desired_flow_table *);
-#endif /* ovn/physical.h */
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
deleted file mode 100644
index d857067a5..000000000
--- a/ovn/controller/pinctrl.c
+++ /dev/null
@@ -1,4343 +0,0 @@
-/* Copyright (c) 2015, 2016, 2017 Red Hat, Inc.
- * 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 "pinctrl.h"
-
-#include "coverage.h"
-#include "csum.h"
-#include "dirs.h"
-#include "dp-packet.h"
-#include "encaps.h"
-#include "flow.h"
-#include "ha-chassis.h"
-#include "lport.h"
-#include "nx-match.h"
-#include "ovn-controller.h"
-#include "latch.h"
-#include "lib/packets.h"
-#include "lib/sset.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofp-msgs.h"
-#include "openvswitch/ofp-packet.h"
-#include "openvswitch/ofp-print.h"
-#include "openvswitch/ofp-switch.h"
-#include "openvswitch/ofp-util.h"
-#include "openvswitch/vlog.h"
-
-#include "lib/dhcp.h"
-#include "ovn-controller.h"
-#include "ovn/actions.h"
-#include "ovn/lex.h"
-#include "ovn/lib/acl-log.h"
-#include "ovn/lib/ip-mcast-index.h"
-#include "ovn/lib/mcast-group-index.h"
-#include "ovn/lib/ovn-l7.h"
-#include "ovn/lib/ovn-util.h"
-#include "ovn/logical-fields.h"
-#include "openvswitch/poll-loop.h"
-#include "openvswitch/rconn.h"
-#include "socket-util.h"
-#include "seq.h"
-#include "timeval.h"
-#include "vswitch-idl.h"
-#include "lflow.h"
-#include "ip-mcast.h"
-
-VLOG_DEFINE_THIS_MODULE(pinctrl);
-
-/* pinctrl module creates a thread - pinctrl_handler to handle
- * the packet-ins from ovs-vswitchd. Some of the OVN actions
- * are translated to OF 'controller' actions. See include/ovn/actions.h
- * for more details.
- *
- * pinctrl_handler thread doesn't access the Southbound IDL object. But
- * some of the OVN actions which gets translated to 'controller'
- * OF action, require data from Southbound DB.  Below are the details
- * on how these actions are implemented.
- *
- * pinctrl_run() function is called by ovn-controller main thread.
- * A Mutex - 'pinctrl_mutex' is used between the pinctrl_handler() thread
- * and pinctrl_run().
- *
- *   - dns_lookup -     In order to do a DNS lookup, this action needs
- *                      to access the 'DNS' table. pinctrl_run() builds a
- *                      local DNS cache - 'dns_cache'. See sync_dns_cache()
- *                      for more details.
- *                      The function 'pinctrl_handle_dns_lookup()' (which is
- *                      called with in the pinctrl_handler thread) looks into
- *                      the local DNS cache to resolve the DNS requests.
- *
- *   - put_arp/put_nd - These actions stores the IPv4/IPv6 and MAC addresses
- *                      in the 'MAC_Binding' table.
- *                      The function 'pinctrl_handle_put_mac_binding()' (which
- *                      is called with in the pinctrl_handler thread), stores
- *                      the IPv4/IPv6 and MAC addresses in the
- *                      hmap - put_mac_bindings.
- *
- *                      pinctrl_run(), reads these mac bindings from the hmap
- *                      'put_mac_bindings' and writes to the 'MAC_Binding'
- *                      table in the Southbound DB.
- *
- *   - arp/nd_ns      - These actions generate an ARP/IPv6 Neighbor solicit
- *                      requests. The original packets are buffered and
- *                      injected back when put_arp/put_nd resolves
- *                      corresponding ARP/IPv6 Neighbor solicit requests.
- *                      When pinctrl_run(), writes the mac bindings from the
- *                      'put_mac_bindings' hmap to the MAC_Binding table in
- *                      SB DB, run_buffered_binding will add the buffered
- *                      packets to buffered_mac_bindings and notify
- *                      pinctrl_handler.
- *
- *                      The pinctrl_handler thread calls the function -
- *                      send_mac_binding_buffered_pkts(), which uses
- *                      the hmap - 'buffered_mac_bindings' and reinjects the
- *                      buffered packets.
- *
- *    - igmp          - This action punts an IGMP packet to the controller
- *                      which maintains multicast group information. The
- *                      multicast groups (mcast_snoop_map) are synced to
- *                      the 'IGMP_Group' table by ip_mcast_sync().
- *                      ip_mcast_sync() also reads the 'IP_Multicast'
- *                      (snooping and querier) configuration and builds a
- *                      local configuration mcast_cfg_map.
- *                      ip_mcast_snoop_run() which runs in the
- *                      pinctrl_handler() thread configures the per datapath
- *                      mcast_snoop_map entries according to mcast_cfg_map.
- *
- * pinctrl module also periodically sends IPv6 Router Solicitation requests
- * and gARPs (for the router gateway IPs and configured NAT addresses).
- *
- * IPv6 RA handling - pinctrl_run() prepares the IPv6 RA information
- *                    (see prepare_ipv6_ras()) in the shash 'ipv6_ras' by
- *                    looking into the Southbound DB table - Port_Binding.
- *
- *                    pinctrl_handler thread sends the periodic IPv6 RAs using
- *                    the shash - 'ipv6_ras'
- *
- * gARP handling    - pinctrl_run() prepares the gARP information
- *                    (see send_garp_prepare()) in the shash 'send_garp_data'
- *                    by looking into the Southbound DB table Port_Binding.
- *
- *                    pinctrl_handler() thread sends these gARPs using the
- *                    shash 'send_garp_data'.
- *
- * IGMP Queries     - pinctrl_run() prepares the IGMP queries (at most one
- *                    per local datapath) based on the mcast_snoop_map
- *                    contents and stores them in mcast_query_list.
- *
- *                    pinctrl_handler thread sends the periodic IGMP queries
- *                    by walking the mcast_query_list.
- *
- * Notification between pinctrl_handler() and pinctrl_run()
- * -------------------------------------------------------
- * 'struct seq' is used for notification between pinctrl_handler() thread
- *  and pinctrl_run().
- *  'pinctrl_handler_seq' is used by pinctrl_run() to
- *  wake up pinctrl_handler thread from poll_block() if any changes happened
- *  in 'send_garp_data', 'ipv6_ras' and 'buffered_mac_bindings' structures.
- *
- *  'pinctrl_main_seq' is used by pinctrl_handler() thread to wake up
- *  the main thread from poll_block() when mac bindings/igmp groups need to
- *  be updated in the Southboubd DB.
- * */
-
-static struct ovs_mutex pinctrl_mutex = OVS_MUTEX_INITIALIZER;
-static struct seq *pinctrl_handler_seq;
-static struct seq *pinctrl_main_seq;
-
-static void *pinctrl_handler(void *arg);
-
-struct pinctrl {
-    char *br_int_name;
-    pthread_t pinctrl_thread;
-    /* Latch to destroy the 'pinctrl_thread' */
-    struct latch pinctrl_thread_exit;
-};
-
-static struct pinctrl pinctrl;
-
-static void init_buffered_packets_map(void);
-static void destroy_buffered_packets_map(void);
-static void
-run_buffered_binding(struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                     struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                     const struct hmap *local_datapaths)
-    OVS_REQUIRES(pinctrl_mutex);
-
-static void pinctrl_handle_put_mac_binding(const struct flow *md,
-                                           const struct flow *headers,
-                                           bool is_arp)
-    OVS_REQUIRES(pinctrl_mutex);
-static void init_put_mac_bindings(void);
-static void destroy_put_mac_bindings(void);
-static void run_put_mac_bindings(
-    struct ovsdb_idl_txn *ovnsb_idl_txn,
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
-    OVS_REQUIRES(pinctrl_mutex);
-static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
-static void flush_put_mac_bindings(void);
-static void send_mac_binding_buffered_pkts(struct rconn *swconn)
-    OVS_REQUIRES(pinctrl_mutex);
-
-static void init_send_garps(void);
-static void destroy_send_garps(void);
-static void send_garp_wait(long long int send_garp_time);
-static void send_garp_prepare(
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct ovsrec_bridge *,
-    const struct sbrec_chassis *,
-    const struct hmap *local_datapaths,
-    const struct sset *active_tunnels)
-    OVS_REQUIRES(pinctrl_mutex);
-static void send_garp_run(struct rconn *swconn, long long int *send_garp_time)
-    OVS_REQUIRES(pinctrl_mutex);
-static void pinctrl_handle_nd_na(struct rconn *swconn,
-                                 const struct flow *ip_flow,
-                                 const struct match *md,
-                                 struct ofpbuf *userdata,
-                                 bool is_router);
-static void reload_metadata(struct ofpbuf *ofpacts,
-                            const struct match *md);
-static void pinctrl_handle_put_nd_ra_opts(
-    struct rconn *swconn,
-    const struct flow *ip_flow, struct dp_packet *pkt_in,
-    struct ofputil_packet_in *pin, struct ofpbuf *userdata,
-    struct ofpbuf *continuation);
-static void pinctrl_handle_nd_ns(struct rconn *swconn,
-                                 const struct flow *ip_flow,
-                                 struct dp_packet *pkt_in,
-                                 const struct match *md,
-                                 struct ofpbuf *userdata);
-static void pinctrl_handle_put_icmp4_frag_mtu(struct rconn *swconn,
-                                              const struct flow *in_flow,
-                                              struct dp_packet *pkt_in,
-                                              struct ofputil_packet_in *pin,
-                                              struct ofpbuf *userdata,
-                                              struct ofpbuf *continuation);
-static void
-pinctrl_handle_event(struct ofpbuf *userdata)
-    OVS_REQUIRES(pinctrl_mutex);
-static void wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn);
-static void init_ipv6_ras(void);
-static void destroy_ipv6_ras(void);
-static void ipv6_ra_wait(long long int send_ipv6_ra_time);
-static void prepare_ipv6_ras(
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct hmap *local_datapaths)
-    OVS_REQUIRES(pinctrl_mutex);
-static void send_ipv6_ras(struct rconn *swconn,
-                          long long int *send_ipv6_ra_time)
-    OVS_REQUIRES(pinctrl_mutex);
-
-static void ip_mcast_snoop_init(void);
-static void ip_mcast_snoop_destroy(void);
-static void ip_mcast_snoop_run(void)
-    OVS_REQUIRES(pinctrl_mutex);
-static void ip_mcast_querier_run(struct rconn *swconn,
-                                 long long int *query_time);
-static void ip_mcast_querier_wait(long long int query_time);
-static void ip_mcast_sync(
-    struct ovsdb_idl_txn *ovnsb_idl_txn,
-    const struct sbrec_chassis *chassis,
-    const struct hmap *local_datapaths,
-    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-    struct ovsdb_idl_index *sbrec_igmp_groups,
-    struct ovsdb_idl_index *sbrec_ip_multicast)
-    OVS_REQUIRES(pinctrl_mutex);
-static void pinctrl_ip_mcast_handle_igmp(
-    struct rconn *swconn,
-    const struct flow *ip_flow,
-    struct dp_packet *pkt_in,
-    const struct match *md,
-    struct ofpbuf *userdata);
-
-static bool may_inject_pkts(void);
-
-COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
-COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
-COVERAGE_DEFINE(pinctrl_drop_controller_event);
-
-struct empty_lb_backends_event {
-    struct hmap_node hmap_node;
-    long long int timestamp;
-
-    char *vip;
-    char *protocol;
-    char *load_balancer;
-};
-
-static struct hmap event_table[OVN_EVENT_MAX];
-static int64_t event_seq_num;
-
-static void
-init_event_table(void)
-{
-    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
-        hmap_init(&event_table[i]);
-    }
-}
-
-#define EVENT_TIMEOUT   10000
-static void
-empty_lb_backends_event_gc(bool flush)
-{
-    struct empty_lb_backends_event *cur_ce, *next_ce;
-    long long int now = time_msec();
-
-    HMAP_FOR_EACH_SAFE (cur_ce, next_ce, hmap_node,
-                        &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) {
-        if ((now < cur_ce->timestamp + EVENT_TIMEOUT) && !flush) {
-            continue;
-        }
-
-        free(cur_ce->vip);
-        free(cur_ce->protocol);
-        free(cur_ce->load_balancer);
-        hmap_remove(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
-                    &cur_ce->hmap_node);
-        free(cur_ce);
-    }
-}
-
-static void
-event_table_gc(bool flush)
-{
-    empty_lb_backends_event_gc(flush);
-}
-
-static void
-event_table_destroy(void)
-{
-    event_table_gc(true);
-    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
-        hmap_destroy(&event_table[i]);
-    }
-}
-
-static struct empty_lb_backends_event *
-pinctrl_find_empty_lb_backends_event(char *vip, char *protocol,
-                                     char *load_balancer, uint32_t hash)
-{
-    struct empty_lb_backends_event *ce;
-    HMAP_FOR_EACH_WITH_HASH (ce, hmap_node, hash,
-                             &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) {
-        if (!strcmp(ce->vip, vip) &&
-            !strcmp(ce->protocol, protocol) &&
-            !strcmp(ce->load_balancer, load_balancer)) {
-            return ce;
-        }
-    }
-    return NULL;
-}
-
-static const struct sbrec_controller_event *
-empty_lb_backends_lookup(struct empty_lb_backends_event *event,
-                         const struct sbrec_controller_event_table *ce_table,
-                         const struct sbrec_chassis *chassis)
-{
-    const struct sbrec_controller_event *sbrec_event;
-    const char *event_type = event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS);
-    char ref_uuid[UUID_LEN + 1];
-    sprintf(ref_uuid, UUID_FMT, UUID_ARGS(&chassis->header_.uuid));
-
-    SBREC_CONTROLLER_EVENT_TABLE_FOR_EACH (sbrec_event, ce_table) {
-        if (strcmp(sbrec_event->event_type, event_type)) {
-            continue;
-        }
-
-        char chassis_uuid[UUID_LEN + 1];
-        sprintf(chassis_uuid, UUID_FMT,
-                UUID_ARGS(&sbrec_event->chassis->header_.uuid));
-        if (strcmp(ref_uuid, chassis_uuid)) {
-            continue;
-        }
-
-        const char *vip = smap_get(&sbrec_event->event_info, "vip");
-        const char *protocol = smap_get(&sbrec_event->event_info, "protocol");
-        const char *load_balancer = smap_get(&sbrec_event->event_info,
-                                             "load_balancer");
-
-        if (!strcmp(event->vip, vip) &&
-            !strcmp(event->protocol, protocol) &&
-            !strcmp(event->load_balancer, load_balancer)) {
-            return sbrec_event;
-        }
-    }
-
-    return NULL;
-}
-
-static void
-controller_event_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                     const struct sbrec_controller_event_table *ce_table,
-                     const struct sbrec_chassis *chassis)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    if (!ovnsb_idl_txn) {
-        goto out;
-    }
-
-    struct empty_lb_backends_event *empty_lbs;
-    HMAP_FOR_EACH (empty_lbs, hmap_node,
-                   &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) {
-        const struct sbrec_controller_event *event;
-
-        event = empty_lb_backends_lookup(empty_lbs, ce_table, chassis);
-        if (!event) {
-            struct smap event_info = SMAP_INITIALIZER(&event_info);
-
-            smap_add(&event_info, "vip", empty_lbs->vip);
-            smap_add(&event_info, "protocol", empty_lbs->protocol);
-            smap_add(&event_info, "load_balancer", empty_lbs->load_balancer);
-
-            event = sbrec_controller_event_insert(ovnsb_idl_txn);
-            sbrec_controller_event_set_event_type(event,
-                    event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS));
-            sbrec_controller_event_set_seq_num(event, ++event_seq_num);
-            sbrec_controller_event_set_event_info(event, &event_info);
-            sbrec_controller_event_set_chassis(event, chassis);
-        }
-    }
-
-out:
-    event_table_gc(!!ovnsb_idl_txn);
-}
-
-void
-pinctrl_init(void)
-{
-    init_put_mac_bindings();
-    init_send_garps();
-    init_ipv6_ras();
-    init_buffered_packets_map();
-    init_event_table();
-    ip_mcast_snoop_init();
-    pinctrl.br_int_name = NULL;
-    pinctrl_handler_seq = seq_create();
-    pinctrl_main_seq = seq_create();
-
-    latch_init(&pinctrl.pinctrl_thread_exit);
-    pinctrl.pinctrl_thread = ovs_thread_create("ovn_pinctrl", pinctrl_handler,
-                                                &pinctrl);
-}
-
-static ovs_be32
-queue_msg(struct rconn *swconn, struct ofpbuf *msg)
-{
-    const struct ofp_header *oh = msg->data;
-    ovs_be32 xid = oh->xid;
-
-    rconn_send(swconn, msg, NULL);
-    return xid;
-}
-
-/* Sets up 'swconn', a newly (re)connected connection to a switch. */
-static void
-pinctrl_setup(struct rconn *swconn)
-{
-    /* Fetch the switch configuration.  The response later will allow us to
-     * change the miss_send_len to UINT16_MAX, so that we can enable
-     * asynchronous messages. */
-    queue_msg(swconn, ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST,
-                                   rconn_get_version(swconn), 0));
-
-    /* Set a packet-in format that supports userdata.  */
-    queue_msg(swconn,
-              ofputil_encode_set_packet_in_format(rconn_get_version(swconn),
-                                                  OFPUTIL_PACKET_IN_NXT2));
-}
-
-static void
-set_switch_config(struct rconn *swconn,
-                  const struct ofputil_switch_config *config)
-{
-    enum ofp_version version = rconn_get_version(swconn);
-    struct ofpbuf *request = ofputil_encode_set_config(config, version);
-    queue_msg(swconn, request);
-}
-
-static void
-set_actions_and_enqueue_msg(struct rconn *swconn,
-                            const struct dp_packet *packet,
-                            const struct match *md,
-                            struct ofpbuf *userdata)
-{
-    /* Copy metadata from 'md' into the packet-out via "set_field"
-     * actions, then add actions from 'userdata'.
-     */
-    uint64_t ofpacts_stub[4096 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-    enum ofp_version version = rconn_get_version(swconn);
-
-    reload_metadata(&ofpacts, md);
-    enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
-                                                      version, NULL, NULL,
-                                                      &ofpacts);
-    if (error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "failed to parse actions from userdata (%s)",
-                     ofperr_to_string(error));
-        ofpbuf_uninit(&ofpacts);
-        return;
-    }
-
-    struct ofputil_packet_out po = {
-        .packet = dp_packet_data(packet),
-        .packet_len = dp_packet_size(packet),
-        .buffer_id = UINT32_MAX,
-        .ofpacts = ofpacts.data,
-        .ofpacts_len = ofpacts.size,
-    };
-    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-    ofpbuf_uninit(&ofpacts);
-}
-
-struct buffer_info {
-    struct ofpbuf ofpacts;
-    struct dp_packet *p;
-};
-
-#define BUFFER_QUEUE_DEPTH     4
-struct buffered_packets {
-    struct hmap_node hmap_node;
-    struct ovs_list list;
-
-    /* key */
-    struct in6_addr ip;
-    struct eth_addr ea;
-
-    long long int timestamp;
-
-    struct buffer_info data[BUFFER_QUEUE_DEPTH];
-    uint32_t head, tail;
-};
-
-static struct hmap buffered_packets_map;
-static struct ovs_list buffered_mac_bindings;
-
-static void
-init_buffered_packets_map(void)
-{
-    hmap_init(&buffered_packets_map);
-    ovs_list_init(&buffered_mac_bindings);
-}
-
-static void
-destroy_buffered_packets(struct buffered_packets *bp)
-{
-    struct buffer_info *bi;
-
-    while (bp->head != bp->tail) {
-        bi = &bp->data[bp->head];
-        dp_packet_delete(bi->p);
-        ofpbuf_uninit(&bi->ofpacts);
-
-        bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH;
-    }
-}
-
-static void
-destroy_buffered_packets_map(void)
-{
-    struct buffered_packets *bp, *next;
-    HMAP_FOR_EACH_SAFE (bp, next, hmap_node, &buffered_packets_map) {
-        destroy_buffered_packets(bp);
-        hmap_remove(&buffered_packets_map, &bp->hmap_node);
-        free(bp);
-    }
-    hmap_destroy(&buffered_packets_map);
-
-    LIST_FOR_EACH_POP (bp, list, &buffered_mac_bindings) {
-        destroy_buffered_packets(bp);
-        free(bp);
-    }
-}
-
-static void
-buffered_push_packet(struct buffered_packets *bp,
-                     struct dp_packet *packet,
-                     const struct match *md)
-{
-    uint32_t next = (bp->tail + 1) % BUFFER_QUEUE_DEPTH;
-    struct buffer_info *bi = &bp->data[bp->tail];
-
-    ofpbuf_init(&bi->ofpacts, 4096);
-
-    reload_metadata(&bi->ofpacts, md);
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts);
-    resubmit->in_port = OFPP_CONTROLLER;
-    resubmit->table_id = OFTABLE_REMOTE_OUTPUT;
-
-    bi->p = packet;
-
-    if (next == bp->head) {
-        bi = &bp->data[bp->head];
-        dp_packet_delete(bi->p);
-        ofpbuf_uninit(&bi->ofpacts);
-        bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH;
-    }
-    bp->tail = next;
-}
-
-static void
-buffered_send_packets(struct rconn *swconn, struct buffered_packets *bp,
-                      struct eth_addr *addr)
-{
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-
-    while (bp->head != bp->tail) {
-        struct buffer_info *bi = &bp->data[bp->head];
-        struct eth_header *eth = dp_packet_data(bi->p);
-
-        eth->eth_dst = *addr;
-        struct ofputil_packet_out po = {
-            .packet = dp_packet_data(bi->p),
-            .packet_len = dp_packet_size(bi->p),
-            .buffer_id = UINT32_MAX,
-            .ofpacts = bi->ofpacts.data,
-            .ofpacts_len = bi->ofpacts.size,
-        };
-        match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-        queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-
-        ofpbuf_uninit(&bi->ofpacts);
-        dp_packet_delete(bi->p);
-
-        bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH;
-    }
-}
-
-#define BUFFER_MAP_TIMEOUT   10000
-static void
-buffered_packets_map_gc(void)
-{
-    struct buffered_packets *cur_qp, *next_qp;
-    long long int now = time_msec();
-
-    HMAP_FOR_EACH_SAFE (cur_qp, next_qp, hmap_node, &buffered_packets_map) {
-        if (now > cur_qp->timestamp + BUFFER_MAP_TIMEOUT) {
-            destroy_buffered_packets(cur_qp);
-            hmap_remove(&buffered_packets_map, &cur_qp->hmap_node);
-            free(cur_qp);
-        }
-    }
-}
-
-static struct buffered_packets *
-pinctrl_find_buffered_packets(const struct in6_addr *ip, uint32_t hash)
-{
-    struct buffered_packets *qp;
-
-    HMAP_FOR_EACH_WITH_HASH (qp, hmap_node, hash,
-                             &buffered_packets_map) {
-        if (IN6_ARE_ADDR_EQUAL(&qp->ip, ip)) {
-            return qp;
-        }
-    }
-    return NULL;
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static int
-pinctrl_handle_buffered_packets(const struct flow *ip_flow,
-                                struct dp_packet *pkt_in,
-                                const struct match *md, bool is_arp)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct buffered_packets *bp;
-    struct dp_packet *clone;
-    struct in6_addr addr;
-
-    if (is_arp) {
-        addr = in6_addr_mapped_ipv4(ip_flow->nw_dst);
-    } else {
-        addr = ip_flow->ipv6_dst;
-    }
-
-    uint32_t hash = hash_bytes(&addr, sizeof addr, 0);
-    bp = pinctrl_find_buffered_packets(&addr, hash);
-    if (!bp) {
-        if (hmap_count(&buffered_packets_map) >= 1000) {
-            COVERAGE_INC(pinctrl_drop_buffered_packets_map);
-            return -ENOMEM;
-        }
-
-        bp = xmalloc(sizeof *bp);
-        hmap_insert(&buffered_packets_map, &bp->hmap_node, hash);
-        bp->head = bp->tail = 0;
-        bp->ip = addr;
-    }
-    bp->timestamp = time_msec();
-    /* clone the packet to send it later with correct L2 address */
-    clone = dp_packet_clone_data(dp_packet_data(pkt_in),
-                                 dp_packet_size(pkt_in));
-    buffered_push_packet(bp, clone, md);
-
-    return 0;
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow,
-                   struct dp_packet *pkt_in,
-                   const struct match *md, struct ofpbuf *userdata)
-{
-    /* This action only works for IP packets, and the switch should only send
-     * us IP packets this way, but check here just to be sure. */
-    if (ip_flow->dl_type != htons(ETH_TYPE_IP)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "ARP action on non-IP packet (Ethertype %"PRIx16")",
-                     ntohs(ip_flow->dl_type));
-        return;
-    }
-
-    ovs_mutex_lock(&pinctrl_mutex);
-    pinctrl_handle_buffered_packets(ip_flow, pkt_in, md, true);
-    ovs_mutex_unlock(&pinctrl_mutex);
-
-    /* Compose an ARP packet. */
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    compose_arp__(&packet);
-
-    struct eth_header *eth = dp_packet_eth(&packet);
-    eth->eth_dst = ip_flow->dl_dst;
-    eth->eth_src = ip_flow->dl_src;
-
-    struct arp_eth_header *arp = dp_packet_l3(&packet);
-    arp->ar_op = htons(ARP_OP_REQUEST);
-    arp->ar_sha = ip_flow->dl_src;
-    put_16aligned_be32(&arp->ar_spa, ip_flow->nw_src);
-    arp->ar_tha = eth_addr_zero;
-    put_16aligned_be32(&arp->ar_tpa, ip_flow->nw_dst);
-
-    if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) {
-        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q),
-                      ip_flow->vlans[0].tci);
-    }
-
-    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-    dp_packet_uninit(&packet);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow,
-                    struct dp_packet *pkt_in,
-                    const struct match *md, struct ofpbuf *userdata,
-                    bool include_orig_ip_datagram)
-{
-    /* This action only works for IP packets, and the switch should only send
-     * us IP packets this way, but check here just to be sure. */
-    if (ip_flow->dl_type != htons(ETH_TYPE_IP) &&
-        ip_flow->dl_type != htons(ETH_TYPE_IPV6)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl,
-                     "ICMP action on non-IP packet (eth_type 0x%"PRIx16")",
-                     ntohs(ip_flow->dl_type));
-        return;
-    }
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    dp_packet_clear(&packet);
-    packet.packet_type = htonl(PT_ETH);
-
-    struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh);
-    eh->eth_dst = ip_flow->dl_dst;
-    eh->eth_src = ip_flow->dl_src;
-
-    if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) {
-        struct ip_header *in_ip = dp_packet_l3(pkt_in);
-        uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
-        if (in_ip_len < IP_HEADER_LEN) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl,
-                        "ICMP action on IP packet with invalid length (%u)",
-                        in_ip_len);
-            return;
-        }
-
-        struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-
-        eh->eth_type = htons(ETH_TYPE_IP);
-        dp_packet_set_l3(&packet, nh);
-        nh->ip_ihl_ver = IP_IHL_VER(5, 4);
-        nh->ip_tot_len = htons(sizeof(struct ip_header) +
-                               sizeof(struct icmp_header));
-        nh->ip_proto = IPPROTO_ICMP;
-        nh->ip_frag_off = htons(IP_DF);
-        packet_set_ipv4(&packet, ip_flow->nw_src, ip_flow->nw_dst,
-                        ip_flow->nw_tos, 255);
-
-        struct icmp_header *ih = dp_packet_put_zeros(&packet, sizeof *ih);
-        dp_packet_set_l4(&packet, ih);
-        packet_set_icmp(&packet, ICMP4_DST_UNREACH, 1);
-
-        if (include_orig_ip_datagram) {
-            /* RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes
-             * of header. MAY send more.
-             * RFC says return as much as we can without exceeding 576
-             * bytes.
-             * So, lets return as much as we can. */
-
-            /* Calculate available room to include the original IP + data. */
-            nh = dp_packet_l3(&packet);
-            uint16_t room = 576 - (sizeof *eh + ntohs(nh->ip_tot_len));
-            if (in_ip_len > room) {
-                in_ip_len = room;
-            }
-            dp_packet_put(&packet, in_ip, in_ip_len);
-
-            /* dp_packet_put may reallocate the buffer. Get the l3 and l4
-             * header pointers again. */
-            nh = dp_packet_l3(&packet);
-            ih = dp_packet_l4(&packet);
-            uint16_t ip_total_len = ntohs(nh->ip_tot_len) + in_ip_len;
-            nh->ip_tot_len = htons(ip_total_len);
-            ih->icmp_csum = 0;
-            ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len);
-            nh->ip_csum = 0;
-            nh->ip_csum = csum(nh, sizeof *nh);
-        }
-    } else {
-        struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-        struct icmp6_error_header *ih;
-        uint32_t icmpv6_csum;
-
-        eh->eth_type = htons(ETH_TYPE_IPV6);
-        dp_packet_set_l3(&packet, nh);
-        nh->ip6_vfc = 0x60;
-        nh->ip6_nxt = IPPROTO_ICMPV6;
-        nh->ip6_plen = htons(sizeof(*nh) + ICMP6_ERROR_HEADER_LEN);
-        packet_set_ipv6(&packet, &ip_flow->ipv6_src, &ip_flow->ipv6_dst,
-                        ip_flow->nw_tos, ip_flow->ipv6_label, 255);
-
-        ih = dp_packet_put_zeros(&packet, sizeof *ih);
-        dp_packet_set_l4(&packet, ih);
-        ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH;
-        ih->icmp6_base.icmp6_code = 1;
-        ih->icmp6_base.icmp6_cksum = 0;
-
-        uint8_t *data = dp_packet_put_zeros(&packet, sizeof *nh);
-        memcpy(data, dp_packet_l3(pkt_in), sizeof(*nh));
-
-        icmpv6_csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
-        ih->icmp6_base.icmp6_cksum = csum_finish(
-            csum_continue(icmpv6_csum, ih,
-                          sizeof(*nh) + ICMP6_ERROR_HEADER_LEN));
-    }
-
-    if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) {
-        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q),
-                      ip_flow->vlans[0].tci);
-    }
-
-    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-    dp_packet_uninit(&packet);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
-                         struct dp_packet *pkt_in,
-                         const struct match *md, struct ofpbuf *userdata)
-{
-    /* This action only works for TCP segments, and the switch should only send
-     * us TCP segments this way, but check here just to be sure. */
-    if (ip_flow->nw_proto != IPPROTO_TCP) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "TCP_RESET action on non-TCP packet");
-        return;
-    }
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    dp_packet_clear(&packet);
-    packet.packet_type = htonl(PT_ETH);
-
-    struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh);
-    eh->eth_dst = ip_flow->dl_dst;
-    eh->eth_src = ip_flow->dl_src;
-
-    if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) {
-        struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-
-        eh->eth_type = htons(ETH_TYPE_IPV6);
-        dp_packet_set_l3(&packet, nh);
-        nh->ip6_vfc = 0x60;
-        nh->ip6_nxt = IPPROTO_TCP;
-        nh->ip6_plen = htons(TCP_HEADER_LEN);
-        packet_set_ipv6(&packet, &ip_flow->ipv6_src, &ip_flow->ipv6_dst,
-                        ip_flow->nw_tos, ip_flow->ipv6_label, 255);
-    } else {
-        struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-
-        eh->eth_type = htons(ETH_TYPE_IP);
-        dp_packet_set_l3(&packet, nh);
-        nh->ip_ihl_ver = IP_IHL_VER(5, 4);
-        nh->ip_tot_len = htons(IP_HEADER_LEN + TCP_HEADER_LEN);
-        nh->ip_proto = IPPROTO_TCP;
-        nh->ip_frag_off = htons(IP_DF);
-        packet_set_ipv4(&packet, ip_flow->nw_src, ip_flow->nw_dst,
-                        ip_flow->nw_tos, 255);
-    }
-
-    struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th);
-    struct tcp_header *tcp_in = dp_packet_l4(pkt_in);
-    dp_packet_set_l4(&packet, th);
-    th->tcp_ctl = TCP_CTL(TCP_RST, 5);
-    if (ip_flow->tcp_flags & htons(TCP_ACK)) {
-        th->tcp_seq = tcp_in->tcp_ack;
-    } else {
-        uint32_t tcp_seq, ack_seq, tcp_len;
-
-        tcp_seq = ntohl(get_16aligned_be32(&tcp_in->tcp_seq));
-        tcp_len = TCP_OFFSET(tcp_in->tcp_ctl) * 4;
-        ack_seq = tcp_seq + dp_packet_l4_size(pkt_in) - tcp_len;
-        put_16aligned_be32(&th->tcp_ack, htonl(ack_seq));
-        put_16aligned_be32(&th->tcp_seq, 0);
-    }
-    packet_set_tcp_port(&packet, ip_flow->tp_dst, ip_flow->tp_src);
-
-    if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) {
-        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q),
-                      ip_flow->vlans[0].tci);
-    }
-
-    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-    dp_packet_uninit(&packet);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_put_dhcp_opts(
-    struct rconn *swconn,
-    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
-    struct ofpbuf *userdata, struct ofpbuf *continuation)
-{
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    struct dp_packet *pkt_out_ptr = NULL;
-    uint32_t success = 0;
-
-    /* Parse result field. */
-    const struct mf_field *f;
-    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
-    if (ofperr) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
-        goto exit;
-    }
-
-    /* Parse result offset and offer IP. */
-    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
-    ovs_be32 *offer_ip = ofpbuf_try_pull(userdata, sizeof *offer_ip);
-    if (!ofsp || !offer_ip) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "offset or offer_ip not present in the userdata");
-        goto exit;
-    }
-
-    /* Check that the result is valid and writable. */
-    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
-    ofperr = mf_check_dst(&dst, NULL);
-    if (ofperr) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
-        goto exit;
-    }
-
-    if (!userdata->size) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "DHCP options not present in the userdata");
-        goto exit;
-    }
-
-    /* Validate the DHCP request packet.
-     * Format of the DHCP packet is
-     * ------------------------------------------------------------------------
-     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)|
-     * ------------------------------------------------------------------------
-     */
-
-    const char *end = (char *)dp_packet_l4(pkt_in) + dp_packet_l4_size(pkt_in);
-    const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in);
-    if (!in_dhcp_ptr) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received");
-        goto exit;
-    }
-
-    const struct dhcp_header *in_dhcp_data
-        = (const struct dhcp_header *) in_dhcp_ptr;
-    in_dhcp_ptr += sizeof *in_dhcp_data;
-    if (in_dhcp_ptr > end) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received, "
-                     "bad data length");
-        goto exit;
-    }
-    if (in_dhcp_data->op != DHCP_OP_REQUEST) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid opcode in the DHCP packet: %d",
-                     in_dhcp_data->op);
-        goto exit;
-    }
-
-    /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
-     * options is the DHCP magic cookie followed by the actual DHCP options.
-     */
-    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
-    if (in_dhcp_ptr + sizeof magic_cookie > end ||
-        get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "DHCP magic cookie not present in the DHCP packet");
-        goto exit;
-    }
-    in_dhcp_ptr += sizeof magic_cookie;
-
-    const uint8_t *in_dhcp_msg_type = NULL;
-    ovs_be32 request_ip = in_dhcp_data->ciaddr;
-    while (in_dhcp_ptr < end) {
-        const struct dhcp_opt_header *in_dhcp_opt =
-            (const struct dhcp_opt_header *)in_dhcp_ptr;
-        if (in_dhcp_opt->code == DHCP_OPT_END) {
-            break;
-        }
-        if (in_dhcp_opt->code == DHCP_OPT_PAD) {
-            in_dhcp_ptr += 1;
-            continue;
-        }
-        in_dhcp_ptr += sizeof *in_dhcp_opt;
-        if (in_dhcp_ptr > end) {
-            break;
-        }
-        in_dhcp_ptr += in_dhcp_opt->len;
-        if (in_dhcp_ptr > end) {
-            break;
-        }
-
-        switch (in_dhcp_opt->code) {
-        case DHCP_OPT_MSG_TYPE:
-            if (in_dhcp_opt->len == 1) {
-                in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt);
-            }
-            break;
-        case DHCP_OPT_REQ_IP:
-            if (in_dhcp_opt->len == 4) {
-                request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
-            }
-            break;
-        default:
-            break;
-        }
-    }
-
-    /* Check that the DHCP Message Type (opt 53) is present or not with
-     * valid values - DHCP_MSG_DISCOVER or DHCP_MSG_REQUEST.
-     */
-    if (!in_dhcp_msg_type) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Missing DHCP message type");
-        goto exit;
-    }
-    if (*in_dhcp_msg_type != DHCP_MSG_DISCOVER &&
-        *in_dhcp_msg_type != DHCP_MSG_REQUEST) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid DHCP message type: %d", *in_dhcp_msg_type);
-        goto exit;
-    }
-
-    uint8_t msg_type;
-    if (*in_dhcp_msg_type == DHCP_MSG_DISCOVER) {
-        msg_type = DHCP_MSG_OFFER;
-    } else {
-        /* This is a DHCPREQUEST. If the client has requested an IP that
-         * does not match the offered IP address, reply with a NAK. The
-         * requested IP address may be supplied either via Requested IP Address
-         * (opt 50) or via ciaddr, depending on the client's state.
-         */
-        msg_type = DHCP_MSG_ACK;
-        if (request_ip != *offer_ip) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "DHCPREQUEST requested IP "IP_FMT" does not "
-                         "match offer "IP_FMT, IP_ARGS(request_ip),
-                         IP_ARGS(*offer_ip));
-            msg_type = DHCP_MSG_NAK;
-        }
-    }
-
-    /* Frame the DHCP reply packet
-     * Total DHCP options length will be options stored in the userdata +
-     * 16 bytes. Note that the DHCP options stored in userdata are not included
-     * in DHCPNAK messages.
-     *
-     * --------------------------------------------------------------
-     *| 4 Bytes (dhcp cookie) | 3 Bytes (option type) | DHCP options |
-     * --------------------------------------------------------------
-     *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
-     * --------------------------------------------------------------
-     */
-    uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
-    if (msg_type != DHCP_MSG_NAK) {
-        new_l4_size += userdata->size;
-    }
-    size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
-
-    struct dp_packet pkt_out;
-    dp_packet_init(&pkt_out, new_packet_size);
-    dp_packet_clear(&pkt_out);
-    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
-    pkt_out_ptr = &pkt_out;
-
-    /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
-    dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
-
-    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
-    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
-    pkt_out.l3_ofs = pkt_in->l3_ofs;
-    pkt_out.l4_ofs = pkt_in->l4_ofs;
-
-    struct udp_header *udp = dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
-
-    struct dhcp_header *dhcp_data = dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, DHCP_HEADER_LEN), DHCP_HEADER_LEN);
-    dhcp_data->op = DHCP_OP_REPLY;
-    dhcp_data->yiaddr = (msg_type == DHCP_MSG_NAK) ? 0 : *offer_ip;
-    dp_packet_put(&pkt_out, &magic_cookie, sizeof(ovs_be32));
-
-    uint16_t out_dhcp_opts_size = 12;
-    if (msg_type != DHCP_MSG_NAK) {
-      out_dhcp_opts_size += userdata->size;
-    }
-    uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
-                                                 out_dhcp_opts_size);
-    /* DHCP option - type */
-    out_dhcp_opts[0] = DHCP_OPT_MSG_TYPE;
-    out_dhcp_opts[1] = 1;
-    out_dhcp_opts[2] = msg_type;
-    out_dhcp_opts += 3;
-
-    if (msg_type != DHCP_MSG_NAK) {
-      memcpy(out_dhcp_opts, userdata->data, userdata->size);
-      out_dhcp_opts += userdata->size;
-    }
-
-    /* Padding */
-    out_dhcp_opts += 4;
-    /* End */
-    out_dhcp_opts[0] = DHCP_OPT_END;
-
-    udp->udp_len = htons(new_l4_size);
-
-    struct ip_header *out_ip = dp_packet_l3(&pkt_out);
-    out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs + new_l4_size);
-    udp->udp_csum = 0;
-    /* Checksum needs to be initialized to zero. */
-    out_ip->ip_csum = 0;
-    out_ip->ip_csum = csum(out_ip, sizeof *out_ip);
-
-    pin->packet = dp_packet_data(&pkt_out);
-    pin->packet_len = dp_packet_size(&pkt_out);
-
-    /* Log the response. */
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
-    const struct eth_header *l2 = dp_packet_eth(&pkt_out);
-    VLOG_INFO_RL(&rl, "DHCP%s "ETH_ADDR_FMT" "IP_FMT"",
-                 msg_type == DHCP_MSG_OFFER ? "OFFER" :
-                   (msg_type == DHCP_MSG_ACK ? "ACK": "NAK"),
-                 ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip));
-
-    success = 1;
-exit:
-    if (!ofperr) {
-        union mf_subvalue sv;
-        sv.u8_val = success;
-        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
-    }
-    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
-    if (pkt_out_ptr) {
-        dp_packet_uninit(pkt_out_ptr);
-    }
-}
-
-static bool
-compose_out_dhcpv6_opts(struct ofpbuf *userdata,
-                        struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid)
-{
-    while (userdata->size) {
-        struct dhcp_opt6_header *userdata_opt = ofpbuf_try_pull(
-            userdata, sizeof *userdata_opt);
-        if (!userdata_opt) {
-            return false;
-        }
-
-        size_t size = ntohs(userdata_opt->size);
-        uint8_t *userdata_opt_data = ofpbuf_try_pull(userdata, size);
-        if (!userdata_opt_data) {
-            return false;
-        }
-
-        switch (ntohs(userdata_opt->opt_code)) {
-        case DHCPV6_OPT_SERVER_ID_CODE:
-        {
-            /* The Server Identifier option carries a DUID
-             * identifying a server between a client and a server.
-             * See RFC 3315 Sec 9 and Sec 22.3.
-             *
-             * We use DUID Based on Link-layer Address [DUID-LL].
-             */
-
-            struct dhcpv6_opt_server_id *opt_server_id = ofpbuf_put_zeros(
-                out_dhcpv6_opts, sizeof *opt_server_id);
-
-            opt_server_id->opt.code = htons(DHCPV6_OPT_SERVER_ID_CODE);
-            opt_server_id->opt.len = htons(size + 4);
-            opt_server_id->duid_type = htons(DHCPV6_DUID_LL);
-            opt_server_id->hw_type = htons(DHCPV6_HW_TYPE_ETH);
-            memcpy(&opt_server_id->mac, userdata_opt_data,
-                    sizeof(struct eth_addr));
-            break;
-        }
-
-        case DHCPV6_OPT_IA_ADDR_CODE:
-        {
-            if (size != sizeof(struct in6_addr)) {
-                return false;
-            }
-
-            if (!iaid) {
-                /* If iaid is None, it means its an DHCPv6 information request.
-                 * Don't put IA_NA option in the response. */
-                 break;
-            }
-            /* IA Address option is used to specify IPv6 addresses associated
-             * with an IA_NA or IA_TA. The IA Address option must be
-             * encapsulated in the Options field of an IA_NA or IA_TA option.
-             *
-             * We will encapsulate the IA Address within the IA_NA option.
-             * Please see RFC 3315 section 22.5 and 22.6
-             */
-            struct dhcpv6_opt_ia_na *opt_ia_na = ofpbuf_put_zeros(
-                out_dhcpv6_opts, sizeof *opt_ia_na);
-            opt_ia_na->opt.code = htons(DHCPV6_OPT_IA_NA_CODE);
-            /* IA_NA length (in bytes)-
-             *  IAID - 4
-             *  T1   - 4
-             *  T2   - 4
-             *  IA Address - sizeof(struct dhcpv6_opt_ia_addr)
-             */
-            opt_ia_na->opt.len = htons(12 + sizeof(struct dhcpv6_opt_ia_addr));
-            opt_ia_na->iaid = iaid;
-            /* Set the lifetime of the address(es) to infinity */
-            opt_ia_na->t1 = OVS_BE32_MAX;
-            opt_ia_na->t2 = OVS_BE32_MAX;
-
-            struct dhcpv6_opt_ia_addr *opt_ia_addr = ofpbuf_put_zeros(
-                out_dhcpv6_opts, sizeof *opt_ia_addr);
-            opt_ia_addr->opt.code = htons(DHCPV6_OPT_IA_ADDR_CODE);
-            opt_ia_addr->opt.len = htons(size + 8);
-            memcpy(opt_ia_addr->ipv6.s6_addr, userdata_opt_data, size);
-            opt_ia_addr->t1 = OVS_BE32_MAX;
-            opt_ia_addr->t2 = OVS_BE32_MAX;
-            break;
-        }
-
-        case DHCPV6_OPT_DNS_SERVER_CODE:
-        {
-            struct dhcpv6_opt_header *opt_dns = ofpbuf_put_zeros(
-                out_dhcpv6_opts, sizeof *opt_dns);
-            opt_dns->code = htons(DHCPV6_OPT_DNS_SERVER_CODE);
-            opt_dns->len = htons(size);
-            ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size);
-            break;
-        }
-
-        case DHCPV6_OPT_DOMAIN_SEARCH_CODE:
-        {
-            struct dhcpv6_opt_header *opt_dsl = ofpbuf_put_zeros(
-                out_dhcpv6_opts, sizeof *opt_dsl);
-            opt_dsl->code = htons(DHCPV6_OPT_DOMAIN_SEARCH_CODE);
-            opt_dsl->len = htons(size + 2);
-            uint8_t *data = ofpbuf_put_zeros(out_dhcpv6_opts, size + 2);
-            *data = size;
-            memcpy(data + 1, userdata_opt_data, size);
-            break;
-        }
-
-        default:
-            return false;
-        }
-    }
-    return true;
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_put_dhcpv6_opts(
-    struct rconn *swconn,
-    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
-    struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    struct dp_packet *pkt_out_ptr = NULL;
-    uint32_t success = 0;
-
-    /* Parse result field. */
-    const struct mf_field *f;
-    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
-    if (ofperr) {
-       VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
-       goto exit;
-    }
-
-    /* Parse result offset. */
-    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
-    if (!ofsp) {
-        VLOG_WARN_RL(&rl, "offset not present in the userdata");
-        goto exit;
-    }
-
-    /* Check that the result is valid and writable. */
-    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
-    ofperr = mf_check_dst(&dst, NULL);
-    if (ofperr) {
-        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
-        goto exit;
-    }
-
-    if (!userdata->size) {
-        VLOG_WARN_RL(&rl, "DHCPv6 options not present in the userdata");
-        goto exit;
-    }
-
-    struct udp_header *in_udp = dp_packet_l4(pkt_in);
-    const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in);
-    if (!in_udp || !in_dhcpv6_data) {
-        VLOG_WARN_RL(&rl, "truncated dhcpv6 packet");
-        goto exit;
-    }
-
-    uint8_t out_dhcpv6_msg_type;
-    uint8_t in_dhcpv6_msg_type = *in_dhcpv6_data;
-    switch (in_dhcpv6_msg_type) {
-    case DHCPV6_MSG_TYPE_SOLICIT:
-        out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT;
-        break;
-
-    case DHCPV6_MSG_TYPE_REQUEST:
-    case DHCPV6_MSG_TYPE_CONFIRM:
-    case DHCPV6_MSG_TYPE_DECLINE:
-    case DHCPV6_MSG_TYPE_INFO_REQ:
-        out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_REPLY;
-        break;
-
-    default:
-        /* Invalid or unsupported DHCPv6 message type */
-        goto exit;
-    }
-
-    /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */
-    in_dhcpv6_data += 4;
-    /* We need to extract IAID from the IA-NA option of the client's DHCPv6
-     * solicit/request/confirm packet and copy the same IAID in the Server's
-     * response.
-     * DHCPv6 information packet (for stateless request will not have IA-NA
-     * option. So we don't need to copy that in the Server's response.
-     * */
-    ovs_be32 iaid = 0;
-    struct dhcpv6_opt_header const *in_opt_client_id = NULL;
-    size_t udp_len = ntohs(in_udp->udp_len);
-    size_t l4_len = dp_packet_l4_size(pkt_in);
-    uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
-    while (in_dhcpv6_data < end) {
-        struct dhcpv6_opt_header const *in_opt =
-             (struct dhcpv6_opt_header *)in_dhcpv6_data;
-        switch(ntohs(in_opt->code)) {
-        case DHCPV6_OPT_IA_NA_CODE:
-        {
-            struct dhcpv6_opt_ia_na *opt_ia_na = (
-                struct dhcpv6_opt_ia_na *)in_opt;
-            iaid = opt_ia_na->iaid;
-            break;
-        }
-
-        case DHCPV6_OPT_CLIENT_ID_CODE:
-            in_opt_client_id = in_opt;
-            break;
-
-        default:
-            break;
-        }
-        in_dhcpv6_data += sizeof *in_opt + ntohs(in_opt->len);
-    }
-
-    if (!in_opt_client_id) {
-        VLOG_WARN_RL(&rl, "DHCPv6 option - Client id not present in the "
-                     "DHCPv6 packet");
-        goto exit;
-    }
-
-    if (!iaid && in_dhcpv6_msg_type != DHCPV6_MSG_TYPE_INFO_REQ) {
-        VLOG_WARN_RL(&rl, "DHCPv6 option - IA NA not present in the "
-                     "DHCPv6 packet");
-        goto exit;
-    }
-
-    uint64_t out_ofpacts_dhcpv6_opts_stub[256 / 8];
-    struct ofpbuf out_dhcpv6_opts =
-        OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub);
-
-    if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts, iaid)) {
-        VLOG_WARN_RL(&rl, "Invalid userdata");
-        goto exit;
-    }
-
-    uint16_t new_l4_size
-        = (UDP_HEADER_LEN + 4 + sizeof *in_opt_client_id +
-           ntohs(in_opt_client_id->len) + out_dhcpv6_opts.size);
-    size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
-
-    struct dp_packet pkt_out;
-    dp_packet_init(&pkt_out, new_packet_size);
-    dp_packet_clear(&pkt_out);
-    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
-    pkt_out_ptr = &pkt_out;
-
-    /* Copy L2 and L3 headers from pkt_in. */
-    dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs),
-                  pkt_in->l4_ofs);
-
-    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
-    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
-    pkt_out.l3_ofs = pkt_in->l3_ofs;
-    pkt_out.l4_ofs = pkt_in->l4_ofs;
-
-    /* Pull the DHCPv6 message type and transaction id from the pkt_in.
-     * Need to preserve the transaction id in the DHCPv6 reply packet. */
-    struct udp_header *out_udp = dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
-    uint8_t *out_dhcpv6 = dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, 4), 4);
-
-    /* Set the proper DHCPv6 message type. */
-    *out_dhcpv6 = out_dhcpv6_msg_type;
-
-    /* Copy the Client Identifier. */
-    dp_packet_put(&pkt_out, in_opt_client_id,
-                  sizeof *in_opt_client_id + ntohs(in_opt_client_id->len));
-
-    /* Copy the DHCPv6 Options. */
-    dp_packet_put(&pkt_out, out_dhcpv6_opts.data, out_dhcpv6_opts.size);
-    out_udp->udp_len = htons(new_l4_size);
-    out_udp->udp_csum = 0;
-
-    struct ovs_16aligned_ip6_hdr *out_ip6 = dp_packet_l3(&pkt_out);
-    out_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = out_udp->udp_len;
-
-    uint32_t csum;
-    csum = packet_csum_pseudoheader6(dp_packet_l3(&pkt_out));
-    csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) -
-                         ((const unsigned char *)out_udp -
-                         (const unsigned char *)dp_packet_eth(&pkt_out)));
-    out_udp->udp_csum = csum_finish(csum);
-    if (!out_udp->udp_csum) {
-        out_udp->udp_csum = htons(0xffff);
-    }
-
-    pin->packet = dp_packet_data(&pkt_out);
-    pin->packet_len = dp_packet_size(&pkt_out);
-    ofpbuf_uninit(&out_dhcpv6_opts);
-    success = 1;
-exit:
-    if (!ofperr) {
-        union mf_subvalue sv;
-        sv.u8_val = success;
-        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
-    }
-    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
-    dp_packet_uninit(pkt_out_ptr);
-}
-
-static void
-put_be16(struct ofpbuf *buf, ovs_be16 x)
-{
-    ofpbuf_put(buf, &x, sizeof x);
-}
-
-static void
-put_be32(struct ofpbuf *buf, ovs_be32 x)
-{
-    ofpbuf_put(buf, &x, sizeof x);
-}
-
-struct dns_data {
-    uint64_t *dps;
-    size_t n_dps;
-    struct smap records;
-    bool delete;
-};
-
-static struct shash dns_cache = SHASH_INITIALIZER(&dns_cache);
-
-/* Called by pinctrl_run(). Runs within the main ovn-controller
- * thread context. */
-static void
-sync_dns_cache(const struct sbrec_dns_table *dns_table)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct shash_node *iter;
-    SHASH_FOR_EACH (iter, &dns_cache) {
-        struct dns_data *d = iter->data;
-        d->delete = true;
-    }
-
-    const struct sbrec_dns *sbrec_dns;
-    SBREC_DNS_TABLE_FOR_EACH (sbrec_dns, dns_table) {
-        const char *dns_id = smap_get(&sbrec_dns->external_ids, "dns_id");
-        if (!dns_id) {
-            continue;
-        }
-
-        struct dns_data *dns_data = shash_find_data(&dns_cache, dns_id);
-        if (!dns_data) {
-            dns_data = xmalloc(sizeof *dns_data);
-            smap_init(&dns_data->records);
-            shash_add(&dns_cache, dns_id, dns_data);
-            dns_data->n_dps = 0;
-            dns_data->dps = NULL;
-        } else {
-            free(dns_data->dps);
-        }
-
-        dns_data->delete = false;
-
-        if (!smap_equal(&dns_data->records, &sbrec_dns->records)) {
-            smap_clear(&dns_data->records);
-            smap_clone(&dns_data->records, &sbrec_dns->records);
-        }
-
-        dns_data->n_dps = sbrec_dns->n_datapaths;
-        dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t));
-        for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
-            dns_data->dps[i] = sbrec_dns->datapaths[i]->tunnel_key;
-        }
-    }
-
-    struct shash_node *next;
-    SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) {
-        struct dns_data *d = iter->data;
-        if (d->delete) {
-            shash_delete(&dns_cache, iter);
-            free(d);
-        }
-    }
-}
-
-static void
-destroy_dns_cache(void)
-{
-    struct shash_node *iter, *next;
-    SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) {
-        struct dns_data *d = iter->data;
-        shash_delete(&dns_cache, iter);
-        free(d);
-    }
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_dns_lookup(
-    struct rconn *swconn,
-    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
-    struct ofpbuf *userdata, struct ofpbuf *continuation)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    struct dp_packet *pkt_out_ptr = NULL;
-    uint32_t success = 0;
-
-    /* Parse result field. */
-    const struct mf_field *f;
-    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
-    if (ofperr) {
-       VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
-       goto exit;
-    }
-
-    /* Parse result offset. */
-    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
-    if (!ofsp) {
-        VLOG_WARN_RL(&rl, "offset not present in the userdata");
-        goto exit;
-    }
-
-    /* Check that the result is valid and writable. */
-    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
-    ofperr = mf_check_dst(&dst, NULL);
-    if (ofperr) {
-        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
-        goto exit;
-    }
-
-    /* Extract the DNS header */
-    struct dns_header const *in_dns_header = dp_packet_get_udp_payload(pkt_in);
-    if (!in_dns_header) {
-        VLOG_WARN_RL(&rl, "truncated dns packet");
-        goto exit;
-    }
-
-    /* Check if it is DNS request or not */
-    if (in_dns_header->lo_flag & 0x80) {
-        /* It's a DNS response packet which we are not interested in */
-        goto exit;
-    }
-
-    /* Check if at least one query request is present */
-    if (!in_dns_header->qdcount) {
-        goto exit;
-    }
-
-    struct udp_header *in_udp = dp_packet_l4(pkt_in);
-    size_t udp_len = ntohs(in_udp->udp_len);
-    size_t l4_len = dp_packet_l4_size(pkt_in);
-    uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
-    uint8_t *in_dns_data = (uint8_t *)(in_dns_header + 1);
-    uint8_t *in_queryname = in_dns_data;
-    uint8_t idx = 0;
-    struct ds query_name;
-    ds_init(&query_name);
-    /* Extract the query_name. If the query name is - 'www.ovn.org' it would be
-     * encoded as (in hex) - 03 77 77 77 03 6f 76 63 03 6f 72 67 00.
-     */
-    while ((in_dns_data + idx) < end && in_dns_data[idx]) {
-        uint8_t label_len = in_dns_data[idx++];
-        if (in_dns_data + idx + label_len > end) {
-            ds_destroy(&query_name);
-            goto exit;
-        }
-        ds_put_buffer(&query_name, (const char *) in_dns_data + idx, label_len);
-        idx += label_len;
-        ds_put_char(&query_name, '.');
-    }
-
-    idx++;
-    ds_chomp(&query_name, '.');
-    in_dns_data += idx;
-
-    /* Query should have TYPE and CLASS fields */
-    if (in_dns_data + (2 * sizeof(ovs_be16)) > end) {
-        ds_destroy(&query_name);
-        goto exit;
-    }
-
-    uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data));
-    /* Supported query types - A, AAAA and ANY */
-    if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA
-          || query_type == DNS_QUERY_TYPE_ANY)) {
-        ds_destroy(&query_name);
-        goto exit;
-    }
-
-    uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
-    const char *answer_ips = NULL;
-    struct shash_node *iter;
-    SHASH_FOR_EACH (iter, &dns_cache) {
-        struct dns_data *d = iter->data;
-        for (size_t i = 0; i < d->n_dps; i++) {
-            if (d->dps[i] == dp_key) {
-                answer_ips = smap_get(&d->records, ds_cstr(&query_name));
-                if (answer_ips) {
-                    break;
-                }
-            }
-        }
-
-        if (answer_ips) {
-            break;
-        }
-    }
-
-    ds_destroy(&query_name);
-    if (!answer_ips) {
-        goto exit;
-    }
-
-    struct lport_addresses ip_addrs;
-    if (!extract_ip_addresses(answer_ips, &ip_addrs)) {
-        goto exit;
-    }
-
-    uint16_t ancount = 0;
-    uint64_t dns_ans_stub[128 / 8];
-    struct ofpbuf dns_answer = OFPBUF_STUB_INITIALIZER(dns_ans_stub);
-
-    if (query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_ANY) {
-        for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) {
-            /* Copy the answer section */
-            /* Format of the answer section is
-             *  - NAME     -> The domain name
-             *  - TYPE     -> 2 octets containing one of the RR type codes
-             *  - CLASS    -> 2 octets which specify the class of the data
-             *                in the RDATA field.
-             *  - TTL      -> 32 bit unsigned int specifying the time
-             *                interval (in secs) that the resource record
-             *                 may be cached before it should be discarded.
-             *  - RDLENGTH -> 16 bit integer specifying the length of the
-             *                RDATA field.
-             *  - RDATA    -> a variable length string of octets that
-             *                describes the resource. In our case it will
-             *                be IP address of the domain name.
-             */
-            ofpbuf_put(&dns_answer, in_queryname, idx);
-            put_be16(&dns_answer, htons(DNS_QUERY_TYPE_A));
-            put_be16(&dns_answer, htons(DNS_CLASS_IN));
-            put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL));
-            put_be16(&dns_answer, htons(sizeof(ovs_be32)));
-            put_be32(&dns_answer, ip_addrs.ipv4_addrs[i].addr);
-            ancount++;
-        }
-    }
-
-    if (query_type == DNS_QUERY_TYPE_AAAA ||
-        query_type == DNS_QUERY_TYPE_ANY) {
-        for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) {
-            ofpbuf_put(&dns_answer, in_queryname, idx);
-            put_be16(&dns_answer, htons(DNS_QUERY_TYPE_AAAA));
-            put_be16(&dns_answer, htons(DNS_CLASS_IN));
-            put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL));
-            const struct in6_addr *ip6 = &ip_addrs.ipv6_addrs[i].addr;
-            put_be16(&dns_answer, htons(sizeof *ip6));
-            ofpbuf_put(&dns_answer, ip6, sizeof *ip6);
-            ancount++;
-        }
-    }
-
-    destroy_lport_addresses(&ip_addrs);
-
-    if (!ancount) {
-        ofpbuf_uninit(&dns_answer);
-        goto exit;
-    }
-
-    uint16_t new_l4_size = ntohs(in_udp->udp_len) +  dns_answer.size;
-    size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
-    struct dp_packet pkt_out;
-    dp_packet_init(&pkt_out, new_packet_size);
-    dp_packet_clear(&pkt_out);
-    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
-    pkt_out_ptr = &pkt_out;
-
-    /* Copy the L2 and L3 headers from the pkt_in as they would remain same.*/
-    dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
-
-    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
-    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
-    pkt_out.l3_ofs = pkt_in->l3_ofs;
-    pkt_out.l4_ofs = pkt_in->l4_ofs;
-
-    struct udp_header *out_udp = dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
-
-    /* Copy the DNS header. */
-    struct dns_header *out_dns_header = dp_packet_put(
-        &pkt_out, dp_packet_pull(pkt_in, sizeof *out_dns_header),
-        sizeof *out_dns_header);
-
-    /* Set the response bit to 1 in the flags. */
-    out_dns_header->lo_flag |= 0x80;
-
-    /* Set the answer RR. */
-    out_dns_header->ancount = htons(ancount);
-
-    /* Copy the Query section. */
-    dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in));
-
-    /* Copy the answer sections. */
-    dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
-    ofpbuf_uninit(&dns_answer);
-
-    out_udp->udp_len = htons(new_l4_size);
-    out_udp->udp_csum = 0;
-
-    struct eth_header *eth = dp_packet_data(&pkt_out);
-    if (eth->eth_type == htons(ETH_TYPE_IP)) {
-        struct ip_header *out_ip = dp_packet_l3(&pkt_out);
-        out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs
-                                   + new_l4_size);
-        /* Checksum needs to be initialized to zero. */
-        out_ip->ip_csum = 0;
-        out_ip->ip_csum = csum(out_ip, sizeof *out_ip);
-    } else {
-        struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);
-        nh->ip6_plen = htons(new_l4_size);
-
-        /* IPv6 needs UDP checksum calculated */
-        uint32_t csum;
-        csum = packet_csum_pseudoheader6(nh);
-        csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) -
-                             ((const unsigned char *)out_udp -
-                             (const unsigned char *)eth));
-        out_udp->udp_csum = csum_finish(csum);
-        if (!out_udp->udp_csum) {
-            out_udp->udp_csum = htons(0xffff);
-        }
-    }
-
-    pin->packet = dp_packet_data(&pkt_out);
-    pin->packet_len = dp_packet_size(&pkt_out);
-
-    success = 1;
-exit:
-    if (!ofperr) {
-        union mf_subvalue sv;
-        sv.u8_val = success;
-        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
-    }
-    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
-    dp_packet_uninit(pkt_out_ptr);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-    struct ofputil_packet_in pin;
-    struct ofpbuf continuation;
-    enum ofperr error = ofputil_decode_packet_in(msg, true, NULL, NULL, &pin,
-                                                 NULL, NULL, &continuation);
-
-    if (error) {
-        VLOG_WARN_RL(&rl, "error decoding packet-in: %s",
-                     ofperr_to_string(error));
-        return;
-    }
-    if (pin.reason != OFPR_ACTION) {
-        return;
-    }
-
-    struct ofpbuf userdata = ofpbuf_const_initializer(pin.userdata,
-                                                      pin.userdata_len);
-    const struct action_header *ah = ofpbuf_pull(&userdata, sizeof *ah);
-    if (!ah) {
-        VLOG_WARN_RL(&rl, "packet-in userdata lacks action header");
-        return;
-    }
-
-    struct dp_packet packet;
-    dp_packet_use_const(&packet, pin.packet, pin.packet_len);
-    struct flow headers;
-    flow_extract(&packet, &headers);
-
-    switch (ntohl(ah->opcode)) {
-    case ACTION_OPCODE_ARP:
-        pinctrl_handle_arp(swconn, &headers, &packet, &pin.flow_metadata,
-                           &userdata);
-        break;
-    case ACTION_OPCODE_IGMP:
-        pinctrl_ip_mcast_handle_igmp(swconn, &headers, &packet,
-                                     &pin.flow_metadata, &userdata);
-        break;
-
-    case ACTION_OPCODE_PUT_ARP:
-        ovs_mutex_lock(&pinctrl_mutex);
-        pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
-                                       true);
-        ovs_mutex_unlock(&pinctrl_mutex);
-        break;
-
-    case ACTION_OPCODE_PUT_DHCP_OPTS:
-        pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &userdata,
-                                     &continuation);
-        break;
-
-    case ACTION_OPCODE_ND_NA:
-        pinctrl_handle_nd_na(swconn, &headers, &pin.flow_metadata, &userdata,
-                             false);
-        break;
-
-    case ACTION_OPCODE_ND_NA_ROUTER:
-        pinctrl_handle_nd_na(swconn, &headers, &pin.flow_metadata, &userdata,
-                             true);
-        break;
-
-    case ACTION_OPCODE_PUT_ND:
-        ovs_mutex_lock(&pinctrl_mutex);
-        pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
-                                       false);
-        ovs_mutex_unlock(&pinctrl_mutex);
-        break;
-
-    case ACTION_OPCODE_PUT_DHCPV6_OPTS:
-        pinctrl_handle_put_dhcpv6_opts(swconn, &packet, &pin, &userdata,
-                                       &continuation);
-        break;
-
-    case ACTION_OPCODE_DNS_LOOKUP:
-        ovs_mutex_lock(&pinctrl_mutex);
-        pinctrl_handle_dns_lookup(swconn, &packet, &pin, &userdata,
-                                  &continuation);
-        ovs_mutex_unlock(&pinctrl_mutex);
-        break;
-
-    case ACTION_OPCODE_LOG:
-        handle_acl_log(&headers, &userdata);
-        break;
-
-    case ACTION_OPCODE_PUT_ND_RA_OPTS:
-        pinctrl_handle_put_nd_ra_opts(swconn, &headers, &packet, &pin,
-                                      &userdata, &continuation);
-        break;
-
-    case ACTION_OPCODE_ND_NS:
-        pinctrl_handle_nd_ns(swconn, &headers, &packet, &pin.flow_metadata,
-                             &userdata);
-        break;
-
-    case ACTION_OPCODE_ICMP:
-        pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata,
-                            &userdata, false);
-        break;
-
-    case ACTION_OPCODE_ICMP4_ERROR:
-        pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata,
-                            &userdata, true);
-        break;
-
-    case ACTION_OPCODE_TCP_RESET:
-        pinctrl_handle_tcp_reset(swconn, &headers, &packet, &pin.flow_metadata,
-                                 &userdata);
-        break;
-
-    case ACTION_OPCODE_PUT_ICMP4_FRAG_MTU:
-        pinctrl_handle_put_icmp4_frag_mtu(swconn, &headers, &packet,
-                                          &pin, &userdata, &continuation);
-        break;
-
-    case ACTION_OPCODE_EVENT:
-        ovs_mutex_lock(&pinctrl_mutex);
-        pinctrl_handle_event(&userdata);
-        ovs_mutex_unlock(&pinctrl_mutex);
-        break;
-
-    default:
-        VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
-                     ntohl(ah->opcode));
-        break;
-    }
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_recv(struct rconn *swconn, const struct ofp_header *oh,
-             enum ofptype type)
-{
-    if (type == OFPTYPE_ECHO_REQUEST) {
-        queue_msg(swconn, ofputil_encode_echo_reply(oh));
-    } else if (type == OFPTYPE_GET_CONFIG_REPLY) {
-        /* Enable asynchronous messages */
-        struct ofputil_switch_config config;
-
-        ofputil_decode_get_config_reply(oh, &config);
-        config.miss_send_len = UINT16_MAX;
-        set_switch_config(swconn, &config);
-    } else if (type == OFPTYPE_PACKET_IN) {
-        process_packet_in(swconn, oh);
-    } else {
-        if (VLOG_IS_DBG_ENABLED()) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
-
-            char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 2);
-
-            VLOG_DBG_RL(&rl, "OpenFlow packet ignored: %s", s);
-            free(s);
-        }
-    }
-}
-
-/* Called with in the main ovn-controller thread context. */
-static void
-notify_pinctrl_handler(void)
-{
-    seq_change(pinctrl_handler_seq);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-notify_pinctrl_main(void)
-{
-    seq_change(pinctrl_main_seq);
-}
-
-/* pinctrl_handler pthread function. */
-static void *
-pinctrl_handler(void *arg_)
-{
-    struct pinctrl *pctrl = arg_;
-    /* OpenFlow connection to the switch. */
-    struct rconn *swconn;
-    /* Last seen sequence number for 'swconn'.  When this differs from
-     * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
-    unsigned int conn_seq_no = 0;
-
-    char *br_int_name = NULL;
-    uint64_t new_seq;
-
-    /* Next IPV6 RA in seconds. */
-    static long long int send_ipv6_ra_time = LLONG_MAX;
-    /* Next GARP announcement in ms. */
-    static long long int send_garp_time = LLONG_MAX;
-    /* Next multicast query (IGMP) in ms. */
-    static long long int send_mcast_query_time = LLONG_MAX;
-
-    swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
-
-    while (!latch_is_set(&pctrl->pinctrl_thread_exit)) {
-        if (pctrl->br_int_name) {
-            if (!br_int_name || strcmp(br_int_name, pctrl->br_int_name)) {
-                free(br_int_name);
-                br_int_name = xstrdup(pctrl->br_int_name);
-            }
-        }
-
-        if (br_int_name) {
-            char *target;
-
-            target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int_name);
-            if (strcmp(target, rconn_get_target(swconn))) {
-                VLOG_INFO("%s: connecting to switch", target);
-                rconn_connect(swconn, target, target);
-            }
-            free(target);
-        } else {
-            rconn_disconnect(swconn);
-        }
-
-        ovs_mutex_lock(&pinctrl_mutex);
-        ip_mcast_snoop_run();
-        ovs_mutex_unlock(&pinctrl_mutex);
-
-        rconn_run(swconn);
-        if (rconn_is_connected(swconn)) {
-            if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
-                pinctrl_setup(swconn);
-                conn_seq_no = rconn_get_connection_seqno(swconn);
-            }
-
-            for (int i = 0; i < 50; i++) {
-                struct ofpbuf *msg = rconn_recv(swconn);
-                if (!msg) {
-                    break;
-                }
-
-                const struct ofp_header *oh = msg->data;
-                enum ofptype type;
-
-                ofptype_decode(&type, oh);
-                pinctrl_recv(swconn, oh, type);
-                ofpbuf_delete(msg);
-            }
-
-            if (may_inject_pkts()) {
-                ovs_mutex_lock(&pinctrl_mutex);
-                send_garp_run(swconn, &send_garp_time);
-                send_ipv6_ras(swconn, &send_ipv6_ra_time);
-                send_mac_binding_buffered_pkts(swconn);
-                ovs_mutex_unlock(&pinctrl_mutex);
-
-                ip_mcast_querier_run(swconn, &send_mcast_query_time);
-            }
-        }
-
-        rconn_run_wait(swconn);
-        rconn_recv_wait(swconn);
-        send_garp_wait(send_garp_time);
-        ipv6_ra_wait(send_ipv6_ra_time);
-        ip_mcast_querier_wait(send_mcast_query_time);
-
-        new_seq = seq_read(pinctrl_handler_seq);
-        seq_wait(pinctrl_handler_seq, new_seq);
-
-        latch_wait(&pctrl->pinctrl_thread_exit);
-        poll_block();
-    }
-
-    free(br_int_name);
-    rconn_destroy(swconn);
-    return NULL;
-}
-
-/* Called by ovn-controller. */
-void
-pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-            struct ovsdb_idl_index *sbrec_port_binding_by_key,
-            struct ovsdb_idl_index *sbrec_port_binding_by_name,
-            struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-            struct ovsdb_idl_index *sbrec_igmp_groups,
-            struct ovsdb_idl_index *sbrec_ip_multicast_opts,
-            const struct sbrec_dns_table *dns_table,
-            const struct sbrec_controller_event_table *ce_table,
-            const struct ovsrec_bridge *br_int,
-            const struct sbrec_chassis *chassis,
-            const struct hmap *local_datapaths,
-            const struct sset *active_tunnels)
-{
-    ovs_mutex_lock(&pinctrl_mutex);
-    if (br_int && (!pinctrl.br_int_name || strcmp(pinctrl.br_int_name,
-                                                  br_int->name))) {
-        if (pinctrl.br_int_name) {
-            free(pinctrl.br_int_name);
-        }
-        pinctrl.br_int_name = xstrdup(br_int->name);
-        /* Notify pinctrl_handler that integration bridge is
-         * set/changed. */
-        notify_pinctrl_handler();
-    }
-    run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
-                         sbrec_port_binding_by_key,
-                         sbrec_mac_binding_by_lport_ip);
-    send_garp_prepare(sbrec_port_binding_by_datapath,
-                      sbrec_port_binding_by_name, br_int, chassis,
-                      local_datapaths, active_tunnels);
-    prepare_ipv6_ras(sbrec_port_binding_by_datapath,
-                     sbrec_port_binding_by_name, local_datapaths);
-    sync_dns_cache(dns_table);
-    controller_event_run(ovnsb_idl_txn, ce_table, chassis);
-    ip_mcast_sync(ovnsb_idl_txn, chassis, local_datapaths,
-                  sbrec_datapath_binding_by_key,
-                  sbrec_port_binding_by_key,
-                  sbrec_igmp_groups,
-                  sbrec_ip_multicast_opts);
-    run_buffered_binding(sbrec_port_binding_by_datapath,
-                         sbrec_mac_binding_by_lport_ip,
-                         local_datapaths);
-    ovs_mutex_unlock(&pinctrl_mutex);
-}
-
-/* Table of ipv6_ra_state structures, keyed on logical port name.
- * Protected by pinctrl_mutex. */
-static struct shash ipv6_ras;
-
-struct ipv6_ra_config {
-    time_t min_interval;
-    time_t max_interval;
-    struct eth_addr eth_src;
-    struct eth_addr eth_dst;
-    struct in6_addr ipv6_src;
-    struct in6_addr ipv6_dst;
-    int32_t mtu;
-    uint8_t mo_flags; /* Managed/Other flags for RAs */
-    uint8_t la_flags; /* On-link/autonomous flags for address prefixes */
-    struct lport_addresses prefixes;
-};
-
-struct ipv6_ra_state {
-    long long int next_announce;
-    struct ipv6_ra_config *config;
-    int64_t port_key;
-    int64_t metadata;
-    bool delete_me;
-};
-
-static void
-init_ipv6_ras(void)
-{
-    shash_init(&ipv6_ras);
-}
-
-static void
-ipv6_ra_config_delete(struct ipv6_ra_config *config)
-{
-    if (config) {
-        destroy_lport_addresses(&config->prefixes);
-        free(config);
-    }
-}
-
-static void
-ipv6_ra_delete(struct ipv6_ra_state *ra)
-{
-    if (ra) {
-        ipv6_ra_config_delete(ra->config);
-        free(ra);
-    }
-}
-
-static void
-destroy_ipv6_ras(void)
-{
-    struct shash_node *iter, *next;
-    SHASH_FOR_EACH_SAFE (iter, next, &ipv6_ras) {
-        struct ipv6_ra_state *ra = iter->data;
-        ipv6_ra_delete(ra);
-        shash_delete(&ipv6_ras, iter);
-    }
-    shash_destroy(&ipv6_ras);
-}
-
-static struct ipv6_ra_config *
-ipv6_ra_update_config(const struct sbrec_port_binding *pb)
-{
-    struct ipv6_ra_config *config;
-
-    config = xzalloc(sizeof *config);
-
-    config->max_interval = smap_get_int(&pb->options, "ipv6_ra_max_interval",
-            ND_RA_MAX_INTERVAL_DEFAULT);
-    config->min_interval = smap_get_int(&pb->options, "ipv6_ra_min_interval",
-            nd_ra_min_interval_default(config->max_interval));
-    config->mtu = smap_get_int(&pb->options, "ipv6_ra_mtu", ND_MTU_DEFAULT);
-    config->la_flags = IPV6_ND_RA_OPT_PREFIX_ON_LINK;
-
-    const char *address_mode = smap_get(&pb->options, "ipv6_ra_address_mode");
-    if (!address_mode) {
-        VLOG_WARN("No address mode specified");
-        goto fail;
-    }
-    if (!strcmp(address_mode, "dhcpv6_stateless")) {
-        config->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
-        config->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS;
-    } else if (!strcmp(address_mode, "dhcpv6_stateful")) {
-        config->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
-    } else if (!strcmp(address_mode, "slaac")) {
-        config->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS;
-    } else {
-        VLOG_WARN("Invalid address mode %s", address_mode);
-        goto fail;
-    }
-
-    const char *prefixes = smap_get(&pb->options, "ipv6_ra_prefixes");
-    if (prefixes && !extract_ip_addresses(prefixes, &config->prefixes)) {
-        VLOG_WARN("Invalid IPv6 prefixes: %s", prefixes);
-        goto fail;
-    }
-
-    /* All nodes multicast addresses */
-    config->eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,00,00,01);
-    ipv6_parse("ff02::1", &config->ipv6_dst);
-
-    const char *eth_addr = smap_get(&pb->options, "ipv6_ra_src_eth");
-    if (!eth_addr || !eth_addr_from_string(eth_addr, &config->eth_src)) {
-        VLOG_WARN("Invalid ethernet source %s", eth_addr);
-        goto fail;
-    }
-    const char *ip_addr = smap_get(&pb->options, "ipv6_ra_src_addr");
-    if (!ip_addr || !ipv6_parse(ip_addr, &config->ipv6_src)) {
-        VLOG_WARN("Invalid IP source %s", ip_addr);
-        goto fail;
-    }
-
-    return config;
-
-fail:
-    ipv6_ra_config_delete(config);
-    return NULL;
-}
-
-static long long int
-ipv6_ra_calc_next_announce(time_t min_interval, time_t max_interval)
-{
-    long long int min_interval_ms = min_interval * 1000LL;
-    long long int max_interval_ms = max_interval * 1000LL;
-
-    return time_msec() + min_interval_ms +
-        random_range(max_interval_ms - min_interval_ms);
-}
-
-static void
-put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
-         struct ofpbuf *ofpacts)
-{
-    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
-                                                       mf_from_id(dst), NULL,
-                                                       NULL);
-    ovs_be64 n_value = htonll(value);
-    bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
-    bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static long long int
-ipv6_ra_send(struct rconn *swconn, struct ipv6_ra_state *ra)
-{
-    if (time_msec() < ra->next_announce) {
-        return ra->next_announce;
-    }
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    compose_nd_ra(&packet, ra->config->eth_src, ra->config->eth_dst,
-            &ra->config->ipv6_src, &ra->config->ipv6_dst,
-            255, ra->config->mo_flags, htons(IPV6_ND_RA_LIFETIME), 0, 0,
-            ra->config->mtu);
-
-    for (int i = 0; i < ra->config->prefixes.n_ipv6_addrs; i++) {
-        ovs_be128 addr;
-        memcpy(&addr, &ra->config->prefixes.ipv6_addrs[i].addr, sizeof addr);
-        packet_put_ra_prefix_opt(&packet,
-            ra->config->prefixes.ipv6_addrs[i].plen,
-            ra->config->la_flags, htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME),
-            htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME), addr);
-    }
-
-    uint64_t ofpacts_stub[4096 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-
-    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-    uint32_t dp_key = ra->metadata;
-    uint32_t port_key = ra->port_key;
-    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-    put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-    put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-    resubmit->in_port = OFPP_CONTROLLER;
-    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
-
-    struct ofputil_packet_out po = {
-        .packet = dp_packet_data(&packet),
-        .packet_len = dp_packet_size(&packet),
-        .buffer_id = UINT32_MAX,
-        .ofpacts = ofpacts.data,
-        .ofpacts_len = ofpacts.size,
-    };
-
-    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-    dp_packet_uninit(&packet);
-    ofpbuf_uninit(&ofpacts);
-
-    ra->next_announce = ipv6_ra_calc_next_announce(ra->config->min_interval,
-            ra->config->max_interval);
-
-    return ra->next_announce;
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-ipv6_ra_wait(long long int send_ipv6_ra_time)
-{
-    /* Set the poll timer for next IPv6 RA only if IPv6 RAs needs to
-     * be sent. */
-    if (!shash_is_empty(&ipv6_ras)) {
-        poll_timer_wait_until(send_ipv6_ra_time);
-    }
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-send_ipv6_ras(struct rconn *swconn, long long int *send_ipv6_ra_time)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    *send_ipv6_ra_time = LLONG_MAX;
-    struct shash_node *iter;
-    SHASH_FOR_EACH (iter, &ipv6_ras) {
-        struct ipv6_ra_state *ra = iter->data;
-        long long int next_ra = ipv6_ra_send(swconn, ra);
-        if (*send_ipv6_ra_time > next_ra) {
-            *send_ipv6_ra_time = next_ra;
-        }
-    }
-}
-
-/* Called by pinctrl_run(). Runs with in the main ovn-controller
- * thread context. */
-static void
-prepare_ipv6_ras(struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                 const struct hmap *local_datapaths)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct shash_node *iter, *iter_next;
-
-    SHASH_FOR_EACH (iter, &ipv6_ras) {
-        struct ipv6_ra_state *ra = iter->data;
-        ra->delete_me = true;
-    }
-
-    bool changed = false;
-    const struct local_datapath *ld;
-    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-        struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
-            sbrec_port_binding_by_datapath);
-        sbrec_port_binding_index_set_datapath(target, ld->datapath);
-
-        struct sbrec_port_binding *pb;
-        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                           sbrec_port_binding_by_datapath) {
-            if (!smap_get_bool(&pb->options, "ipv6_ra_send_periodic", false)) {
-                continue;
-            }
-
-            const char *peer_s = smap_get(&pb->options, "peer");
-            if (!peer_s) {
-                continue;
-            }
-
-            const struct sbrec_port_binding *peer
-                = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
-            if (!peer) {
-                continue;
-            }
-
-            struct ipv6_ra_config *config = ipv6_ra_update_config(pb);
-            if (!config) {
-                continue;
-            }
-
-            struct ipv6_ra_state *ra
-                = shash_find_data(&ipv6_ras, pb->logical_port);
-            if (!ra) {
-                ra = xzalloc(sizeof *ra);
-                ra->config = config;
-                ra->next_announce = ipv6_ra_calc_next_announce(
-                    ra->config->min_interval,
-                    ra->config->max_interval);
-                shash_add(&ipv6_ras, pb->logical_port, ra);
-                changed = true;
-            } else {
-                if (config->min_interval != ra->config->min_interval ||
-                    config->max_interval != ra->config->max_interval)
-                    ra->next_announce = ipv6_ra_calc_next_announce(
-                        config->min_interval,
-                        config->max_interval);
-                ipv6_ra_config_delete(ra->config);
-                ra->config = config;
-            }
-
-            /* Peer is the logical switch port that the logical
-             * router port is connected to. The RA is injected
-             * into that logical switch port.
-             */
-            ra->port_key = peer->tunnel_key;
-            ra->metadata = peer->datapath->tunnel_key;
-            ra->delete_me = false;
-
-            /* pinctrl_handler thread will send the IPv6 RAs. */
-        }
-        sbrec_port_binding_index_destroy_row(target);
-    }
-
-    /* Remove those that are no longer in the SB database */
-    SHASH_FOR_EACH_SAFE (iter, iter_next, &ipv6_ras) {
-        struct ipv6_ra_state *ra = iter->data;
-        if (ra->delete_me) {
-            shash_delete(&ipv6_ras, iter);
-            ipv6_ra_delete(ra);
-        }
-    }
-
-    if (changed) {
-        notify_pinctrl_handler();
-    }
-
-}
-
-/* Called by pinctrl_run(). Runs with in the main ovn-controller
- * thread context. */
-void
-pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
-{
-    wait_put_mac_bindings(ovnsb_idl_txn);
-    wait_controller_event(ovnsb_idl_txn);
-    int64_t new_seq = seq_read(pinctrl_main_seq);
-    seq_wait(pinctrl_main_seq, new_seq);
-}
-
-/* Called by ovn-controller. */
-void
-pinctrl_destroy(void)
-{
-    latch_set(&pinctrl.pinctrl_thread_exit);
-    pthread_join(pinctrl.pinctrl_thread, NULL);
-    latch_destroy(&pinctrl.pinctrl_thread_exit);
-    free(pinctrl.br_int_name);
-    destroy_send_garps();
-    destroy_ipv6_ras();
-    destroy_buffered_packets_map();
-    event_table_destroy();
-    destroy_put_mac_bindings();
-    destroy_dns_cache();
-    ip_mcast_snoop_destroy();
-    seq_destroy(pinctrl_main_seq);
-    seq_destroy(pinctrl_handler_seq);
-}
-
-/* Implementation of the "put_arp" and "put_nd" OVN actions.  These
- * actions send a packet to ovn-controller, using the flow as an API
- * (see actions.h for details).  This code implements the actions by
- * updating the MAC_Binding table in the southbound database.
- *
- * This code could be a lot simpler if the database could always be updated,
- * but in fact we can only update it when 'ovnsb_idl_txn' is nonnull.  Thus,
- * we buffer up a few put_mac_bindings (but we don't keep them longer
- * than 1 second) and apply them whenever a database transaction is
- * available. */
-
-/* Buffered "put_mac_binding" operation. */
-struct put_mac_binding {
-    struct hmap_node hmap_node; /* In 'put_mac_bindings'. */
-
-    long long int timestamp;    /* In milliseconds. */
-
-    /* Key. */
-    uint32_t dp_key;
-    uint32_t port_key;
-    struct in6_addr ip_key;
-
-    /* Value. */
-    struct eth_addr mac;
-};
-
-/* Contains "struct put_mac_binding"s. */
-static struct hmap put_mac_bindings;
-
-static void
-init_put_mac_bindings(void)
-{
-    hmap_init(&put_mac_bindings);
-}
-
-static void
-destroy_put_mac_bindings(void)
-{
-    flush_put_mac_bindings();
-    hmap_destroy(&put_mac_bindings);
-}
-
-static struct put_mac_binding *
-pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key,
-                             const struct in6_addr *ip_key, uint32_t hash)
-{
-    struct put_mac_binding *pa;
-    HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) {
-        if (pa->dp_key == dp_key
-            && pa->port_key == port_key
-            && IN6_ARE_ADDR_EQUAL(&pa->ip_key, ip_key)) {
-            return pa;
-        }
-    }
-    return NULL;
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_put_mac_binding(const struct flow *md,
-                               const struct flow *headers,
-                               bool is_arp)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    uint32_t dp_key = ntohll(md->metadata);
-    uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
-    struct in6_addr ip_key;
-
-    if (is_arp) {
-        ip_key = in6_addr_mapped_ipv4(htonl(md->regs[0]));
-    } else {
-        ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0));
-        memcpy(&ip_key, &ip6, sizeof ip_key);
-    }
-    uint32_t hash = hash_bytes(&ip_key, sizeof ip_key,
-                               hash_2words(dp_key, port_key));
-    struct put_mac_binding *pmb
-        = pinctrl_find_put_mac_binding(dp_key, port_key, &ip_key, hash);
-    if (!pmb) {
-        if (hmap_count(&put_mac_bindings) >= 1000) {
-            COVERAGE_INC(pinctrl_drop_put_mac_binding);
-            return;
-        }
-
-        pmb = xmalloc(sizeof *pmb);
-        hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash);
-        pmb->dp_key = dp_key;
-        pmb->port_key = port_key;
-        pmb->ip_key = ip_key;
-    }
-    pmb->timestamp = time_msec();
-    pmb->mac = headers->dl_src;
-
-    /* We can send the buffered packet once the main ovn-controller
-     * thread calls pinctrl_run() and it writes the mac_bindings stored
-     * in 'put_mac_bindings' hmap into the Southbound MAC_Binding table. */
-    notify_pinctrl_main();
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-send_mac_binding_buffered_pkts(struct rconn *swconn)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct buffered_packets *bp;
-    LIST_FOR_EACH_POP (bp, list, &buffered_mac_bindings) {
-        buffered_send_packets(swconn, bp, &bp->ea);
-        free(bp);
-    }
-    ovs_list_init(&buffered_mac_bindings);
-}
-
-static const struct sbrec_mac_binding *
-mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                   const char *logical_port,
-                   const char *ip)
-{
-    struct sbrec_mac_binding *mb = sbrec_mac_binding_index_init_row(
-        sbrec_mac_binding_by_lport_ip);
-    sbrec_mac_binding_index_set_logical_port(mb, logical_port);
-    sbrec_mac_binding_index_set_ip(mb, ip);
-
-    const struct sbrec_mac_binding *retval
-        = sbrec_mac_binding_index_find(sbrec_mac_binding_by_lport_ip,
-                                       mb);
-
-    sbrec_mac_binding_index_destroy_row(mb);
-
-    return retval;
-}
-
-static void
-run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                    const struct put_mac_binding *pmb)
-{
-    if (time_msec() > pmb->timestamp + 1000) {
-        return;
-    }
-
-    /* Convert logical datapath and logical port key into lport. */
-    const struct sbrec_port_binding *pb = lport_lookup_by_key(
-        sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
-        pmb->dp_key, pmb->port_key);
-    if (!pb) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-        VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" "
-                     "and port %"PRIu32, pmb->dp_key, pmb->port_key);
-        return;
-    }
-
-    /* Convert ethernet argument to string form for database. */
-    char mac_string[ETH_ADDR_STRLEN + 1];
-    snprintf(mac_string, sizeof mac_string,
-             ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac));
-
-    struct ds ip_s = DS_EMPTY_INITIALIZER;
-    ipv6_format_mapped(&pmb->ip_key, &ip_s);
-
-    /* Update or add an IP-MAC binding for this logical port. */
-    const struct sbrec_mac_binding *b =
-        mac_binding_lookup(sbrec_mac_binding_by_lport_ip, pb->logical_port,
-                           ds_cstr(&ip_s));
-    if (!b) {
-        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
-        sbrec_mac_binding_set_logical_port(b, pb->logical_port);
-        sbrec_mac_binding_set_ip(b, ds_cstr(&ip_s));
-        sbrec_mac_binding_set_mac(b, mac_string);
-        sbrec_mac_binding_set_datapath(b, pb->datapath);
-    } else if (strcmp(b->mac, mac_string)) {
-        sbrec_mac_binding_set_mac(b, mac_string);
-    }
-    ds_destroy(&ip_s);
-}
-
-/* Called by pinctrl_run(). Runs with in the main ovn-controller
- * thread context. */
-static void
-run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                     struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                     struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                     struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    if (!ovnsb_idl_txn) {
-        return;
-    }
-
-    const struct put_mac_binding *pmb;
-    HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) {
-        run_put_mac_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
-                            sbrec_port_binding_by_key,
-                            sbrec_mac_binding_by_lport_ip,
-                            pmb);
-    }
-    flush_put_mac_bindings();
-}
-
-static void
-run_buffered_binding(struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                     struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                     const struct hmap *local_datapaths)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    const struct local_datapath *ld;
-    bool notify = false;
-
-    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-        struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
-                sbrec_port_binding_by_datapath);
-        sbrec_port_binding_index_set_datapath(target, ld->datapath);
-
-        const struct sbrec_port_binding *pb;
-        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                           sbrec_port_binding_by_datapath) {
-            struct buffered_packets *cur_qp, *next_qp;
-            HMAP_FOR_EACH_SAFE (cur_qp, next_qp, hmap_node,
-                                &buffered_packets_map) {
-                struct ds ip_s = DS_EMPTY_INITIALIZER;
-                ipv6_format_mapped(&cur_qp->ip, &ip_s);
-                const struct sbrec_mac_binding *b = mac_binding_lookup(
-                        sbrec_mac_binding_by_lport_ip, pb->logical_port,
-                        ds_cstr(&ip_s));
-                if (b && ovs_scan(b->mac, ETH_ADDR_SCAN_FMT,
-                                  ETH_ADDR_SCAN_ARGS(cur_qp->ea))) {
-                    hmap_remove(&buffered_packets_map, &cur_qp->hmap_node);
-                    ovs_list_push_back(&buffered_mac_bindings, &cur_qp->list);
-                    notify = true;
-                }
-                ds_destroy(&ip_s);
-            }
-        }
-        sbrec_port_binding_index_destroy_row(target);
-    }
-    buffered_packets_map_gc();
-
-    if (notify) {
-        notify_pinctrl_handler();
-    }
-}
-
-static void
-wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
-{
-    if (ovnsb_idl_txn && !hmap_is_empty(&put_mac_bindings)) {
-        poll_immediate_wake();
-    }
-}
-
-static void
-flush_put_mac_bindings(void)
-{
-    struct put_mac_binding *pmb;
-    HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) {
-        free(pmb);
-    }
-}
-
-/*
- * Send gratuitous ARP for vif on localnet.
- *
- * When a new vif on localnet is added, gratuitous ARPs are sent announcing
- * the port's mac,ip mapping.  On localnet, such announcements are needed for
- * switches and routers on the broadcast segment to update their port-mac
- * and ARP tables.
- */
-struct garp_data {
-    struct eth_addr ea;          /* Ethernet address of port. */
-    ovs_be32 ipv4;               /* Ipv4 address of port. */
-    long long int announce_time; /* Next announcement in ms. */
-    int backoff;                 /* Backoff for the next announcement. */
-    uint32_t dp_key;             /* Datapath used to output this GARP. */
-    uint32_t port_key;           /* Port to inject the GARP into. */
-};
-
-/* Contains GARPs to be sent. Protected by pinctrl_mutex*/
-static struct shash send_garp_data;
-
-static void
-init_send_garps(void)
-{
-    shash_init(&send_garp_data);
-}
-
-static void
-destroy_send_garps(void)
-{
-    shash_destroy_free_data(&send_garp_data);
-}
-
-/* Runs with in the main ovn-controller thread context. */
-static void
-add_garp(const char *name, const struct eth_addr ea, ovs_be32 ip,
-         uint32_t dp_key, uint32_t port_key)
-{
-    struct garp_data *garp = xmalloc(sizeof *garp);
-    garp->ea = ea;
-    garp->ipv4 = ip;
-    garp->announce_time = time_msec() + 1000;
-    garp->backoff = 1;
-    garp->dp_key = dp_key;
-    garp->port_key = port_key;
-    shash_add(&send_garp_data, name, garp);
-
-    /* Notify pinctrl_handler so that it can wakeup and process
-     * these GARP requests. */
-    notify_pinctrl_handler();
-}
-
-/* Add or update a vif for which GARPs need to be announced. */
-static void
-send_garp_update(const struct sbrec_port_binding *binding_rec,
-                 struct shash *nat_addresses)
-{
-    volatile struct garp_data *garp = NULL;
-    /* Update GARP for NAT IP if it exists.  Consider port bindings with type
-     * "l3gateway" for logical switch ports attached to gateway routers, and
-     * port bindings with type "patch" for logical switch ports attached to
-     * distributed gateway ports. */
-    if (!strcmp(binding_rec->type, "l3gateway")
-        || !strcmp(binding_rec->type, "patch")) {
-        struct lport_addresses *laddrs = NULL;
-        while ((laddrs = shash_find_and_delete(nat_addresses,
-                                               binding_rec->logical_port))) {
-            int i;
-            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
-                char *name = xasprintf("%s-%s", binding_rec->logical_port,
-                                                laddrs->ipv4_addrs[i].addr_s);
-                garp = shash_find_data(&send_garp_data, name);
-                if (garp) {
-                    garp->dp_key = binding_rec->datapath->tunnel_key;
-                    garp->port_key = binding_rec->tunnel_key;
-                } else {
-                    add_garp(name, laddrs->ea,
-                             laddrs->ipv4_addrs[i].addr,
-                             binding_rec->datapath->tunnel_key,
-                             binding_rec->tunnel_key);
-                }
-                free(name);
-            }
-            destroy_lport_addresses(laddrs);
-            free(laddrs);
-        }
-        return;
-    }
-
-    /* Update GARP for vif if it exists. */
-    garp = shash_find_data(&send_garp_data, binding_rec->logical_port);
-    if (garp) {
-        garp->dp_key = binding_rec->datapath->tunnel_key;
-        garp->port_key = binding_rec->tunnel_key;
-        return;
-    }
-
-    /* Add GARP for new vif. */
-    int i;
-    for (i = 0; i < binding_rec->n_mac; i++) {
-        struct lport_addresses laddrs;
-        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)
-            || !laddrs.n_ipv4_addrs) {
-            continue;
-        }
-
-        add_garp(binding_rec->logical_port,
-                 laddrs.ea, laddrs.ipv4_addrs[0].addr,
-                 binding_rec->datapath->tunnel_key, binding_rec->tunnel_key);
-
-        destroy_lport_addresses(&laddrs);
-        break;
-    }
-}
-
-/* Remove a vif from GARP announcements. */
-static void
-send_garp_delete(const char *lport)
-{
-    struct garp_data *garp = shash_find_and_delete(&send_garp_data, lport);
-    free(garp);
-    notify_pinctrl_handler();
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static long long int
-send_garp(struct rconn *swconn, struct garp_data *garp,
-          long long int current_time)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    if (current_time < garp->announce_time) {
-        return garp->announce_time;
-    }
-
-    /* Compose a GARP request packet. */
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    compose_arp(&packet, ARP_OP_REQUEST, garp->ea, eth_addr_zero,
-                true, garp->ipv4, garp->ipv4);
-
-    /* Inject GARP request. */
-    uint64_t ofpacts_stub[4096 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-    enum ofp_version version = rconn_get_version(swconn);
-    put_load(garp->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-    put_load(garp->port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-    resubmit->in_port = OFPP_CONTROLLER;
-    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
-
-    struct ofputil_packet_out po = {
-        .packet = dp_packet_data(&packet),
-        .packet_len = dp_packet_size(&packet),
-        .buffer_id = UINT32_MAX,
-        .ofpacts = ofpacts.data,
-        .ofpacts_len = ofpacts.size,
-    };
-    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-    dp_packet_uninit(&packet);
-    ofpbuf_uninit(&ofpacts);
-
-    /* Set the next announcement.  At most 5 announcements are sent for a
-     * vif. */
-    if (garp->backoff < 16) {
-        garp->backoff *= 2;
-        garp->announce_time = current_time + garp->backoff * 1000;
-    } else {
-        garp->announce_time = LLONG_MAX;
-    }
-    return garp->announce_time;
-}
-
-/*
- * Multicast snooping configuration.
- */
-struct ip_mcast_snoop_cfg {
-    bool enabled;
-    bool querier_enabled;
-
-    uint32_t table_size;       /* Max number of allowed multicast groups. */
-    uint32_t idle_time_s;      /* Idle timeout for multicast groups. */
-    uint32_t query_interval_s; /* Multicast query interval. */
-    uint32_t query_max_resp_s; /* Multicast query max-response field. */
-    uint32_t seq_no;           /* Used for flushing learnt groups. */
-
-    struct eth_addr query_eth_src; /* Src ETH address used for queries. */
-    struct eth_addr query_eth_dst; /* Dst ETH address used for queries. */
-    ovs_be32 query_ipv4_src;       /* Src IPv4 address used for queries. */
-    ovs_be32 query_ipv4_dst;       /* Dsc IPv4 address used for queries. */
-};
-
-/*
- * Holds per-datapath information about multicast snooping. Maintained by
- * pinctrl_handler().
- */
-struct ip_mcast_snoop {
-    struct hmap_node hmap_node;    /* Linkage in the hash map. */
-    struct ovs_list query_node;    /* Linkage in the query list. */
-    struct ip_mcast_snoop_cfg cfg; /* Multicast configuration. */
-    struct mcast_snooping *ms;     /* Multicast group state. */
-    int64_t dp_key;                /* Datapath running the snooping. */
-
-    long long int query_time_ms;   /* Next query time in ms. */
-};
-
-/*
- * Holds the per-datapath multicast configuration state. Maintained by
- * pinctrl_run().
- */
-struct ip_mcast_snoop_state {
-    struct hmap_node hmap_node;
-    int64_t dp_key;
-    struct ip_mcast_snoop_cfg cfg;
-};
-
-/* Only default vlan supported for now. */
-#define IP_MCAST_VLAN 1
-
-/* Multicast snooping information stored independently by datapath key.
- * Protected by pinctrl_mutex. pinctrl_handler has RW access and pinctrl_main
- * has RO access.
- */
-static struct hmap mcast_snoop_map OVS_GUARDED_BY(pinctrl_mutex);
-
-/* Contains multicast queries to be sent. Only used by pinctrl_handler so no
- * locking needed.
- */
-static struct ovs_list mcast_query_list;
-
-/* Multicast config information stored independently by datapath key.
- * Protected by pinctrl_mutex. pinctrl_handler has RO access and pinctrl_main
- * has RW access. Read accesses from pinctrl_ip_mcast_handle_igmp() can be
- * performed without taking the lock as they are executed in the pinctrl_main
- * thread.
- */
-static struct hmap mcast_cfg_map OVS_GUARDED_BY(pinctrl_mutex);
-
-static void
-ip_mcast_snoop_cfg_load(struct ip_mcast_snoop_cfg *cfg,
-                        const struct sbrec_ip_multicast *ip_mcast)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-    memset(cfg, 0, sizeof *cfg);
-    cfg->enabled =
-        (ip_mcast->enabled && ip_mcast->enabled[0]);
-    cfg->querier_enabled =
-        (cfg->enabled && ip_mcast->querier && ip_mcast->querier[0]);
-
-    if (ip_mcast->table_size) {
-        cfg->table_size = ip_mcast->table_size[0];
-    } else {
-        cfg->table_size = OVN_MCAST_DEFAULT_MAX_ENTRIES;
-    }
-
-    if (ip_mcast->idle_timeout) {
-        cfg->idle_time_s = ip_mcast->idle_timeout[0];
-    } else {
-        cfg->idle_time_s = OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S;
-    }
-
-    if (ip_mcast->query_interval) {
-        cfg->query_interval_s = ip_mcast->query_interval[0];
-    } else {
-        cfg->query_interval_s = cfg->idle_time_s / 2;
-        if (cfg->query_interval_s < OVN_MCAST_MIN_QUERY_INTERVAL_S) {
-            cfg->query_interval_s = OVN_MCAST_MIN_QUERY_INTERVAL_S;
-        }
-    }
-
-    if (ip_mcast->query_max_resp) {
-        cfg->query_max_resp_s = ip_mcast->query_max_resp[0];
-    } else {
-        cfg->query_max_resp_s = OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S;
-    }
-
-    cfg->seq_no = ip_mcast->seq_no;
-
-    if (cfg->querier_enabled) {
-        /* Try to parse the source ETH address. */
-        if (!ip_mcast->eth_src ||
-                !eth_addr_from_string(ip_mcast->eth_src,
-                                      &cfg->query_eth_src)) {
-            VLOG_WARN_RL(&rl,
-                         "IGMP Querier enabled with invalid ETH src address");
-            /* Failed to parse the IPv4 source address. Disable the querier. */
-            cfg->querier_enabled = false;
-        }
-
-        /* Try to parse the source IP address. */
-        if (!ip_mcast->ip4_src ||
-                !ip_parse(ip_mcast->ip4_src, &cfg->query_ipv4_src)) {
-            VLOG_WARN_RL(&rl,
-                         "IGMP Querier enabled with invalid IPv4 src address");
-            /* Failed to parse the IPv4 source address. Disable the querier. */
-            cfg->querier_enabled = false;
-        }
-
-        /* IGMP queries must be sent to 224.0.0.1. */
-        cfg->query_eth_dst =
-            (struct eth_addr)ETH_ADDR_C(01, 00, 5E, 00, 00, 01);
-        cfg->query_ipv4_dst = htonl(0xe0000001);
-    }
-}
-
-static uint32_t
-ip_mcast_snoop_hash(int64_t dp_key)
-{
-    return hash_uint64(dp_key);
-}
-
-static struct ip_mcast_snoop_state *
-ip_mcast_snoop_state_add(int64_t dp_key)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct ip_mcast_snoop_state *ms_state = xmalloc(sizeof *ms_state);
-
-    ms_state->dp_key = dp_key;
-    hmap_insert(&mcast_cfg_map, &ms_state->hmap_node,
-                ip_mcast_snoop_hash(dp_key));
-    return ms_state;
-}
-
-static struct ip_mcast_snoop_state *
-ip_mcast_snoop_state_find(int64_t dp_key)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct ip_mcast_snoop_state *ms_state;
-    uint32_t hash = ip_mcast_snoop_hash(dp_key);
-
-    HMAP_FOR_EACH_WITH_HASH (ms_state, hmap_node, hash, &mcast_cfg_map) {
-        if (ms_state->dp_key == dp_key) {
-            return ms_state;
-        }
-    }
-    return NULL;
-}
-
-static bool
-ip_mcast_snoop_state_update(int64_t dp_key,
-                            const struct ip_mcast_snoop_cfg *cfg)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    bool notify = false;
-    struct ip_mcast_snoop_state *ms_state = ip_mcast_snoop_state_find(dp_key);
-
-    if (!ms_state) {
-        ms_state = ip_mcast_snoop_state_add(dp_key);
-        notify = true;
-    } else if (memcmp(cfg, &ms_state->cfg, sizeof *cfg)) {
-        notify = true;
-    }
-
-    ms_state->cfg = *cfg;
-    return notify;
-}
-
-static void
-ip_mcast_snoop_state_remove(struct ip_mcast_snoop_state *ms_state)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    hmap_remove(&mcast_cfg_map, &ms_state->hmap_node);
-    free(ms_state);
-}
-
-static bool
-ip_mcast_snoop_enable(struct ip_mcast_snoop *ip_ms)
-{
-    if (ip_ms->cfg.enabled) {
-        return true;
-    }
-
-    ip_ms->ms = mcast_snooping_create();
-    return ip_ms->ms != NULL;
-}
-
-static void
-ip_mcast_snoop_flush(struct ip_mcast_snoop *ip_ms)
-{
-    if (!ip_ms->cfg.enabled) {
-        return;
-    }
-
-    mcast_snooping_flush(ip_ms->ms);
-}
-
-static void
-ip_mcast_snoop_disable(struct ip_mcast_snoop *ip_ms)
-{
-    if (!ip_ms->cfg.enabled) {
-        return;
-    }
-
-    mcast_snooping_unref(ip_ms->ms);
-    ip_ms->ms = NULL;
-}
-
-static bool
-ip_mcast_snoop_configure(struct ip_mcast_snoop *ip_ms,
-                         const struct ip_mcast_snoop_cfg *cfg)
-{
-    if (cfg->enabled) {
-        if (!ip_mcast_snoop_enable(ip_ms)) {
-            return false;
-        }
-        if (ip_ms->cfg.seq_no != cfg->seq_no) {
-            ip_mcast_snoop_flush(ip_ms);
-        }
-
-        if (ip_ms->cfg.querier_enabled && !cfg->querier_enabled) {
-            ovs_list_remove(&ip_ms->query_node);
-        } else if (!ip_ms->cfg.querier_enabled && cfg->querier_enabled) {
-            ovs_list_push_back(&mcast_query_list, &ip_ms->query_node);
-        }
-    } else {
-        ip_mcast_snoop_disable(ip_ms);
-        goto set_fields;
-    }
-
-    ovs_rwlock_wrlock(&ip_ms->ms->rwlock);
-    if (cfg->table_size != ip_ms->cfg.table_size) {
-        mcast_snooping_set_max_entries(ip_ms->ms, cfg->table_size);
-    }
-
-    if (cfg->idle_time_s != ip_ms->cfg.idle_time_s) {
-        mcast_snooping_set_idle_time(ip_ms->ms, cfg->idle_time_s);
-    }
-    ovs_rwlock_unlock(&ip_ms->ms->rwlock);
-
-    if (cfg->query_interval_s != ip_ms->cfg.query_interval_s) {
-        long long int now = time_msec();
-
-        if (ip_ms->query_time_ms > now + cfg->query_interval_s * 1000) {
-            ip_ms->query_time_ms = now;
-        }
-    }
-
-set_fields:
-    memcpy(&ip_ms->cfg, cfg, sizeof ip_ms->cfg);
-    return true;
-}
-
-static struct ip_mcast_snoop *
-ip_mcast_snoop_add(int64_t dp_key, const struct ip_mcast_snoop_cfg *cfg)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct ip_mcast_snoop *ip_ms = xzalloc(sizeof *ip_ms);
-
-    ip_ms->dp_key = dp_key;
-    if (!ip_mcast_snoop_configure(ip_ms, cfg)) {
-        free(ip_ms);
-        return NULL;
-    }
-
-    hmap_insert(&mcast_snoop_map, &ip_ms->hmap_node,
-                ip_mcast_snoop_hash(dp_key));
-    return ip_ms;
-}
-
-static struct ip_mcast_snoop *
-ip_mcast_snoop_find(int64_t dp_key)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct ip_mcast_snoop *ip_ms;
-
-    HMAP_FOR_EACH_WITH_HASH (ip_ms, hmap_node, ip_mcast_snoop_hash(dp_key),
-                             &mcast_snoop_map) {
-        if (ip_ms->dp_key == dp_key) {
-            return ip_ms;
-        }
-    }
-    return NULL;
-}
-
-static void
-ip_mcast_snoop_remove(struct ip_mcast_snoop *ip_ms)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    hmap_remove(&mcast_snoop_map, &ip_ms->hmap_node);
-
-    if (ip_ms->cfg.querier_enabled) {
-        ovs_list_remove(&ip_ms->query_node);
-    }
-
-    ip_mcast_snoop_disable(ip_ms);
-    free(ip_ms);
-}
-
-static void
-ip_mcast_snoop_init(void)
-    OVS_NO_THREAD_SAFETY_ANALYSIS
-{
-    hmap_init(&mcast_snoop_map);
-    ovs_list_init(&mcast_query_list);
-    hmap_init(&mcast_cfg_map);
-}
-
-static void
-ip_mcast_snoop_destroy(void)
-    OVS_NO_THREAD_SAFETY_ANALYSIS
-{
-    struct ip_mcast_snoop *ip_ms, *ip_ms_next;
-
-    HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) {
-        ip_mcast_snoop_remove(ip_ms);
-    }
-    hmap_destroy(&mcast_snoop_map);
-
-    struct ip_mcast_snoop_state *ip_ms_state;
-
-    HMAP_FOR_EACH_POP (ip_ms_state, hmap_node, &mcast_cfg_map) {
-        free(ip_ms_state);
-    }
-}
-
-static void
-ip_mcast_snoop_run(void)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct ip_mcast_snoop *ip_ms, *ip_ms_next;
-
-    /* First read the config updated by pinctrl_main. If there's any new or
-     * updated config then apply it.
-     */
-    struct ip_mcast_snoop_state *ip_ms_state;
-
-    HMAP_FOR_EACH (ip_ms_state, hmap_node, &mcast_cfg_map) {
-        ip_ms = ip_mcast_snoop_find(ip_ms_state->dp_key);
-
-        if (!ip_ms) {
-            ip_mcast_snoop_add(ip_ms_state->dp_key, &ip_ms_state->cfg);
-        } else if (memcmp(&ip_ms_state->cfg, &ip_ms->cfg,
-                          sizeof ip_ms_state->cfg)) {
-            ip_mcast_snoop_configure(ip_ms, &ip_ms_state->cfg);
-        }
-    }
-
-    bool notify = false;
-
-    /* Then walk the multicast snoop instances. */
-    HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) {
-
-        /* Delete the stale ones. */
-        if (!ip_mcast_snoop_state_find(ip_ms->dp_key)) {
-            ip_mcast_snoop_remove(ip_ms);
-            continue;
-        }
-
-        /* If enabled run the snooping instance to timeout old groups. */
-        if (ip_ms->cfg.enabled) {
-            if (mcast_snooping_run(ip_ms->ms)) {
-                notify = true;
-            }
-
-            mcast_snooping_wait(ip_ms->ms);
-        }
-    }
-
-    if (notify) {
-        notify_pinctrl_main();
-    }
-}
-
-/*
- * This runs in the pinctrl main thread, so it has access to the southbound
- * database. It reads the IP_Multicast table and updates the local multicast
- * configuration. Then writes to the southbound database the updated
- * IGMP_Groups.
- */
-static void
-ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn,
-              const struct sbrec_chassis *chassis,
-              const struct hmap *local_datapaths,
-              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-              struct ovsdb_idl_index *sbrec_port_binding_by_key,
-              struct ovsdb_idl_index *sbrec_igmp_groups,
-              struct ovsdb_idl_index *sbrec_ip_multicast)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    bool notify = false;
-
-    if (!ovnsb_idl_txn || !chassis) {
-        return;
-    }
-
-    struct sbrec_ip_multicast *ip_mcast;
-    struct ip_mcast_snoop_state *ip_ms_state, *ip_ms_state_next;
-
-    /* First read and update our own local multicast configuration for the
-     * local datapaths.
-     */
-    SBREC_IP_MULTICAST_FOR_EACH_BYINDEX (ip_mcast, sbrec_ip_multicast) {
-
-        int64_t dp_key = ip_mcast->datapath->tunnel_key;
-        struct ip_mcast_snoop_cfg cfg;
-
-        ip_mcast_snoop_cfg_load(&cfg, ip_mcast);
-        if (ip_mcast_snoop_state_update(dp_key, &cfg)) {
-            notify = true;
-        }
-    }
-
-    /* Then delete the old entries. */
-    HMAP_FOR_EACH_SAFE (ip_ms_state, ip_ms_state_next, hmap_node,
-                        &mcast_cfg_map) {
-        if (!get_local_datapath(local_datapaths, ip_ms_state->dp_key)) {
-            ip_mcast_snoop_state_remove(ip_ms_state);
-            notify = true;
-        }
-    }
-
-    const struct sbrec_igmp_group *sbrec_igmp;
-
-    /* Then flush any IGMP_Group entries that are not needed anymore:
-     * - either multicast snooping was disabled on the datapath
-     * - or the group has expired.
-     */
-    SBREC_IGMP_GROUP_FOR_EACH_BYINDEX (sbrec_igmp, sbrec_igmp_groups) {
-        ovs_be32 group_addr;
-
-        if (!sbrec_igmp->datapath) {
-            continue;
-        }
-
-        int64_t dp_key = sbrec_igmp->datapath->tunnel_key;
-        struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key);
-
-        /* If the datapath doesn't exist anymore or IGMP snooping was disabled
-         * on it then delete the IGMP_Group entry.
-         */
-        if (!ip_ms || !ip_ms->cfg.enabled) {
-            igmp_group_delete(sbrec_igmp);
-            continue;
-        }
-
-        if (!ip_parse(sbrec_igmp->address, &group_addr)) {
-            continue;
-        }
-
-        ovs_rwlock_rdlock(&ip_ms->ms->rwlock);
-        struct mcast_group *mc_group =
-            mcast_snooping_lookup4(ip_ms->ms, group_addr,
-                                   IP_MCAST_VLAN);
-
-        if (!mc_group || ovs_list_is_empty(&mc_group->bundle_lru)) {
-            igmp_group_delete(sbrec_igmp);
-        }
-        ovs_rwlock_unlock(&ip_ms->ms->rwlock);
-    }
-
-    struct ip_mcast_snoop *ip_ms, *ip_ms_next;
-
-    /* Last: write new IGMP_Groups to the southbound DB and update existing
-     * ones (if needed). We also flush any old per-datapath multicast snoop
-     * structures.
-     */
-    HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) {
-        /* Flush any non-local snooping datapaths (e.g., stale). */
-        struct local_datapath *local_dp =
-            get_local_datapath(local_datapaths, ip_ms->dp_key);
-
-        if (!local_dp) {
-            continue;
-        }
-
-        /* Skip datapaths on which snooping is disabled. */
-        if (!ip_ms->cfg.enabled) {
-            continue;
-        }
-
-        struct mcast_group *mc_group;
-
-        ovs_rwlock_rdlock(&ip_ms->ms->rwlock);
-        LIST_FOR_EACH (mc_group, group_node, &ip_ms->ms->group_lru) {
-            if (ovs_list_is_empty(&mc_group->bundle_lru)) {
-                continue;
-            }
-            sbrec_igmp = igmp_group_lookup(sbrec_igmp_groups, &mc_group->addr,
-                                           local_dp->datapath, chassis);
-            if (!sbrec_igmp) {
-                sbrec_igmp = igmp_group_create(ovnsb_idl_txn, &mc_group->addr,
-                                               local_dp->datapath, chassis);
-            }
-
-            igmp_group_update_ports(sbrec_igmp, sbrec_datapath_binding_by_key,
-                                    sbrec_port_binding_by_key, ip_ms->ms,
-                                    mc_group);
-        }
-        ovs_rwlock_unlock(&ip_ms->ms->rwlock);
-    }
-
-    if (notify) {
-        notify_pinctrl_handler();
-    }
-}
-
-static void
-pinctrl_ip_mcast_handle_igmp(struct rconn *swconn OVS_UNUSED,
-                             const struct flow *ip_flow,
-                             struct dp_packet *pkt_in,
-                             const struct match *md,
-                             struct ofpbuf *userdata OVS_UNUSED)
-    OVS_NO_THREAD_SAFETY_ANALYSIS
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-    /* This action only works for IP packets, and the switch should only send
-     * us IP packets this way, but check here just to be sure.
-     */
-    if (ip_flow->dl_type != htons(ETH_TYPE_IP)) {
-        VLOG_WARN_RL(&rl,
-                     "IGMP action on non-IP packet (eth_type 0x%"PRIx16")",
-                     ntohs(ip_flow->dl_type));
-        return;
-    }
-
-    int64_t dp_key = ntohll(md->flow.metadata);
-    uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0];
-
-    const struct igmp_header *igmp;
-    size_t offset;
-
-    offset = (char *) dp_packet_l4(pkt_in) - (char *) dp_packet_data(pkt_in);
-    igmp = dp_packet_at(pkt_in, offset, IGMP_HEADER_LEN);
-    if (!igmp || csum(igmp, dp_packet_l4_size(pkt_in)) != 0) {
-        VLOG_WARN_RL(&rl, "multicast snooping received bad IGMP checksum");
-        return;
-    }
-
-    ovs_be32 ip4 = ip_flow->igmp_group_ip4;
-
-    struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key);
-    if (!ip_ms || !ip_ms->cfg.enabled) {
-        /* IGMP snooping is not configured or is disabled. */
-        return;
-    }
-
-    void *port_key_data = (void *)(uintptr_t)port_key;
-
-    bool group_change = false;
-
-    ovs_rwlock_wrlock(&ip_ms->ms->rwlock);
-    switch (ntohs(ip_flow->tp_src)) {
-     /* Only default VLAN is supported for now. */
-    case IGMP_HOST_MEMBERSHIP_REPORT:
-    case IGMPV2_HOST_MEMBERSHIP_REPORT:
-        group_change =
-            mcast_snooping_add_group4(ip_ms->ms, ip4, IP_MCAST_VLAN,
-                                      port_key_data);
-        break;
-    case IGMP_HOST_LEAVE_MESSAGE:
-        group_change =
-            mcast_snooping_leave_group4(ip_ms->ms, ip4, IP_MCAST_VLAN,
-                                        port_key_data);
-        break;
-    case IGMP_HOST_MEMBERSHIP_QUERY:
-        /* Shouldn't be receiving any of these since we are the multicast
-         * router. Store them for now.
-         */
-        group_change =
-            mcast_snooping_add_mrouter(ip_ms->ms, IP_MCAST_VLAN,
-                                       port_key_data);
-        break;
-    case IGMPV3_HOST_MEMBERSHIP_REPORT:
-        group_change =
-            mcast_snooping_add_report(ip_ms->ms, pkt_in, IP_MCAST_VLAN,
-                                      port_key_data);
-        break;
-    }
-    ovs_rwlock_unlock(&ip_ms->ms->rwlock);
-
-    if (group_change) {
-        notify_pinctrl_main();
-    }
-}
-
-static long long int
-ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms,
-                      long long int current_time)
-{
-    if (current_time < ip_ms->query_time_ms) {
-        return ip_ms->query_time_ms;
-    }
-
-    /* Compose a multicast query. */
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-
-    uint8_t ip_tos = 0;
-    uint8_t igmp_ttl = 1;
-
-    dp_packet_clear(&packet);
-    packet.packet_type = htonl(PT_ETH);
-
-    struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh);
-    eh->eth_dst = ip_ms->cfg.query_eth_dst;
-    eh->eth_src = ip_ms->cfg.query_eth_src;
-
-    struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-
-    eh->eth_type = htons(ETH_TYPE_IP);
-    dp_packet_set_l3(&packet, nh);
-    nh->ip_ihl_ver = IP_IHL_VER(5, 4);
-    nh->ip_tot_len = htons(sizeof(struct ip_header) +
-                            sizeof(struct igmpv3_query_header));
-    nh->ip_tos = IP_DSCP_CS6;
-    nh->ip_proto = IPPROTO_IGMP;
-    nh->ip_frag_off = htons(IP_DF);
-    packet_set_ipv4(&packet, ip_ms->cfg.query_ipv4_src,
-                    ip_ms->cfg.query_ipv4_dst, ip_tos, igmp_ttl);
-
-    nh->ip_csum = 0;
-    nh->ip_csum = csum(nh, sizeof *nh);
-
-    struct igmpv3_query_header *igh =
-        dp_packet_put_zeros(&packet, sizeof *igh);
-    dp_packet_set_l4(&packet, igh);
-
-    /* IGMP query max-response in tenths of seconds. */
-    uint8_t max_response = ip_ms->cfg.query_max_resp_s * 10;
-    uint8_t qqic = max_response;
-    packet_set_igmp3_query(&packet, max_response, 0, false, 0, qqic);
-
-    /* Inject multicast query. */
-    uint64_t ofpacts_stub[4096 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-    enum ofp_version version = rconn_get_version(swconn);
-    put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-    put_load(OVN_MCAST_FLOOD_TUNNEL_KEY, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
-    put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts);
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-    resubmit->in_port = OFPP_CONTROLLER;
-    resubmit->table_id = OFTABLE_LOCAL_OUTPUT;
-
-    struct ofputil_packet_out po = {
-        .packet = dp_packet_data(&packet),
-        .packet_len = dp_packet_size(&packet),
-        .buffer_id = UINT32_MAX,
-        .ofpacts = ofpacts.data,
-        .ofpacts_len = ofpacts.size,
-    };
-    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-    dp_packet_uninit(&packet);
-    ofpbuf_uninit(&ofpacts);
-
-    /* Set the next query time. */
-    ip_ms->query_time_ms = current_time + ip_ms->cfg.query_interval_s * 1000;
-    return ip_ms->query_time_ms;
-}
-
-static void
-ip_mcast_querier_run(struct rconn *swconn, long long int *query_time)
-{
-    if (ovs_list_is_empty(&mcast_query_list)) {
-        return;
-    }
-
-    /* Send multicast queries and update the next query time. */
-    long long int current_time = time_msec();
-    *query_time = LLONG_MAX;
-
-    struct ip_mcast_snoop *ip_ms;
-
-    LIST_FOR_EACH (ip_ms, query_node, &mcast_query_list) {
-        long long int next_query_time =
-            ip_mcast_querier_send(swconn, ip_ms, current_time);
-        if (*query_time > next_query_time) {
-            *query_time = next_query_time;
-        }
-    }
-}
-
-static void
-ip_mcast_querier_wait(long long int query_time)
-{
-    if (!ovs_list_is_empty(&mcast_query_list)) {
-        poll_timer_wait_until(query_time);
-    }
-}
-
-/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
-static void
-get_localnet_vifs_l3gwports(
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct ovsrec_bridge *br_int,
-    const struct sbrec_chassis *chassis,
-    const struct hmap *local_datapaths,
-    struct sset *localnet_vifs,
-    struct sset *local_l3gw_ports)
-{
-    for (int i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-        if (!strcmp(port_rec->name, br_int->name)) {
-            continue;
-        }
-        const char *tunnel_id = smap_get(&port_rec->external_ids,
-                                          "ovn-chassis-id");
-        if (tunnel_id &&
-                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL)) {
-            continue;
-        }
-        const char *localnet = smap_get(&port_rec->external_ids,
-                                        "ovn-localnet-port");
-        if (localnet) {
-            continue;
-        }
-        for (int j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
-            if (!iface_rec->n_ofport) {
-                continue;
-            }
-            /* Get localnet vif. */
-            const char *iface_id = smap_get(&iface_rec->external_ids,
-                                            "iface-id");
-            if (!iface_id) {
-                continue;
-            }
-            const struct sbrec_port_binding *pb
-                = lport_lookup_by_name(sbrec_port_binding_by_name, iface_id);
-            if (!pb) {
-                continue;
-            }
-            struct local_datapath *ld
-                = get_local_datapath(local_datapaths,
-                                     pb->datapath->tunnel_key);
-            if (ld && ld->localnet_port) {
-                sset_add(localnet_vifs, iface_id);
-            }
-        }
-    }
-
-    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
-        sbrec_port_binding_by_datapath);
-
-    const struct local_datapath *ld;
-    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-        const struct sbrec_port_binding *pb;
-
-        if (!ld->localnet_port) {
-            continue;
-        }
-
-        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
-         * that connect to gateway routers (if local), and consider port
-         * bindings of type "patch" since they might connect to
-         * distributed gateway ports with NAT addresses. */
-
-        sbrec_port_binding_index_set_datapath(target, ld->datapath);
-        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                           sbrec_port_binding_by_datapath) {
-            if ((ld->has_local_l3gateway && !strcmp(pb->type, "l3gateway"))
-                || !strcmp(pb->type, "patch")) {
-                sset_add(local_l3gw_ports, pb->logical_port);
-            }
-        }
-    }
-    sbrec_port_binding_index_destroy_row(target);
-}
-
-static bool
-pinctrl_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                            const struct sbrec_chassis *chassis,
-                            const struct sset *active_tunnels,
-                            const char *port_name)
-{
-    const struct sbrec_port_binding *pb
-        = lport_lookup_by_name(sbrec_port_binding_by_name, port_name);
-    if (!pb || !pb->chassis) {
-        return false;
-    }
-    if (strcmp(pb->type, "chassisredirect")) {
-        return pb->chassis == chassis;
-    } else {
-        return ha_chassis_group_is_active(pb->ha_chassis_group,
-                                          active_tunnels, chassis);
-    }
-}
-
-/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
- * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
- * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
- * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
- * fields of 'laddrs'.  The logical port name is stored in 'lport'.
- *
- * Returns true if at least 'MAC' is found in 'address', false otherwise.
- *
- * The caller must call destroy_lport_addresses() and free(*lport). */
-static bool
-extract_addresses_with_port(const char *addresses,
-                            struct lport_addresses *laddrs,
-                            char **lport)
-{
-    int ofs;
-    if (!extract_addresses(addresses, laddrs, &ofs)) {
-        return false;
-    } else if (ofs >= strlen(addresses)) {
-        return true;
-    }
-
-    struct lexer lexer;
-    lexer_init(&lexer, addresses + ofs);
-    lexer_get(&lexer);
-
-    if (lexer.error || lexer.token.type != LEX_T_ID
-        || !lexer_match_id(&lexer, "is_chassis_resident")) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
-        lexer_destroy(&lexer);
-        return true;
-    }
-
-    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
-                          "'is_chassis_resident' in address '%s'", addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    if (lexer.token.type != LEX_T_STRING) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl,
-                    "Syntax error: expecting quoted string after "
-                    "'is_chassis_resident' in address '%s'", addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    *lport = xstrdup(lexer.token.s);
-
-    lexer_get(&lexer);
-    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in "
-                          "'is_chassis_resident()' in address '%s'",
-                          addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    lexer_destroy(&lexer);
-    return true;
-}
-
-static void
-consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                     const char *nat_address,
-                     const struct sbrec_port_binding *pb,
-                     struct sset *nat_address_keys,
-                     const struct sbrec_chassis *chassis,
-                     const struct sset *active_tunnels,
-                     struct shash *nat_addresses)
-{
-    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
-    char *lport = NULL;
-    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
-        || (!lport && !strcmp(pb->type, "patch"))
-        || (lport && !pinctrl_is_chassis_resident(
-                sbrec_port_binding_by_name, chassis,
-                active_tunnels, lport))) {
-        destroy_lport_addresses(laddrs);
-        free(laddrs);
-        free(lport);
-        return;
-    }
-    free(lport);
-
-    int i;
-    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
-        char *name = xasprintf("%s-%s", pb->logical_port,
-                                        laddrs->ipv4_addrs[i].addr_s);
-        sset_add(nat_address_keys, name);
-        free(name);
-    }
-    shash_add(nat_addresses, pb->logical_port, laddrs);
-}
-
-static void
-get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                           struct sset *nat_address_keys,
-                           struct sset *local_l3gw_ports,
-                           const struct sbrec_chassis *chassis,
-                           const struct sset *active_tunnels,
-                           struct shash *nat_addresses)
-{
-    const char *gw_port;
-    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
-        const struct sbrec_port_binding *pb;
-
-        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
-        if (!pb) {
-            continue;
-        }
-
-        if (pb->n_nat_addresses) {
-            for (int i = 0; i < pb->n_nat_addresses; i++) {
-                consider_nat_address(sbrec_port_binding_by_name,
-                                     pb->nat_addresses[i], pb,
-                                     nat_address_keys, chassis,
-                                     active_tunnels,
-                                     nat_addresses);
-            }
-        } else {
-            /* Continue to support options:nat-addresses for version
-             * upgrade. */
-            const char *nat_addresses_options = smap_get(&pb->options,
-                                                         "nat-addresses");
-            if (nat_addresses_options) {
-                consider_nat_address(sbrec_port_binding_by_name,
-                                     nat_addresses_options, pb,
-                                     nat_address_keys, chassis,
-                                     active_tunnels,
-                                     nat_addresses);
-            }
-        }
-    }
-}
-
-static void
-send_garp_wait(long long int send_garp_time)
-{
-    /* Set the poll timer for next garp only if there is garp data to
-     * be sent. */
-    if (!shash_is_empty(&send_garp_data)) {
-        poll_timer_wait_until(send_garp_time);
-    }
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-send_garp_run(struct rconn *swconn, long long int *send_garp_time)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    if (shash_is_empty(&send_garp_data)) {
-        return;
-    }
-
-    /* Send GARPs, and update the next announcement. */
-    struct shash_node *iter;
-    long long int current_time = time_msec();
-    *send_garp_time = LLONG_MAX;
-    SHASH_FOR_EACH (iter, &send_garp_data) {
-        long long int next_announce = send_garp(swconn, iter->data,
-                                                current_time);
-        if (*send_garp_time > next_announce) {
-            *send_garp_time = next_announce;
-        }
-    }
-}
-
-/* Called by pinctrl_run(). Runs with in the main ovn-controller
- * thread context. */
-static void
-send_garp_prepare(struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                  struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                  const struct ovsrec_bridge *br_int,
-                  const struct sbrec_chassis *chassis,
-                  const struct hmap *local_datapaths,
-                  const struct sset *active_tunnels)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
-    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
-    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
-    struct shash nat_addresses;
-
-    shash_init(&nat_addresses);
-
-    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
-                                sbrec_port_binding_by_name,
-                                br_int, chassis, local_datapaths,
-                                &localnet_vifs, &local_l3gw_ports);
-
-    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
-                               &nat_ip_keys, &local_l3gw_ports,
-                               chassis, active_tunnels,
-                               &nat_addresses);
-    /* For deleted ports and deleted nat ips, remove from send_garp_data. */
-    struct shash_node *iter, *next;
-    SHASH_FOR_EACH_SAFE (iter, next, &send_garp_data) {
-        if (!sset_contains(&localnet_vifs, iter->name) &&
-            !sset_contains(&nat_ip_keys, iter->name)) {
-            send_garp_delete(iter->name);
-        }
-    }
-
-    /* Update send_garp_data. */
-    const char *iface_id;
-    SSET_FOR_EACH (iface_id, &localnet_vifs) {
-        const struct sbrec_port_binding *pb = lport_lookup_by_name(
-            sbrec_port_binding_by_name, iface_id);
-        if (pb) {
-            send_garp_update(pb, &nat_addresses);
-        }
-    }
-
-    /* Update send_garp_data for nat-addresses. */
-    const char *gw_port;
-    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
-        const struct sbrec_port_binding *pb
-            = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
-        if (pb) {
-            send_garp_update(pb, &nat_addresses);
-        }
-    }
-
-    /* pinctrl_handler thread will send the GARPs. */
-
-    sset_destroy(&localnet_vifs);
-    sset_destroy(&local_l3gw_ports);
-
-    SHASH_FOR_EACH_SAFE (iter, next, &nat_addresses) {
-        struct lport_addresses *laddrs = iter->data;
-        destroy_lport_addresses(laddrs);
-        shash_delete(&nat_addresses, iter);
-        free(laddrs);
-    }
-    shash_destroy(&nat_addresses);
-
-    sset_destroy(&nat_ip_keys);
-}
-
-static bool
-may_inject_pkts(void)
-{
-    return (!shash_is_empty(&ipv6_ras) ||
-            !shash_is_empty(&send_garp_data) ||
-            !ovs_list_is_empty(&mcast_query_list) ||
-            !ovs_list_is_empty(&buffered_mac_bindings));
-}
-
-static void
-reload_metadata(struct ofpbuf *ofpacts, const struct match *md)
-{
-    enum mf_field_id md_fields[] = {
-#if FLOW_N_REGS == 16
-        MFF_REG0,
-        MFF_REG1,
-        MFF_REG2,
-        MFF_REG3,
-        MFF_REG4,
-        MFF_REG5,
-        MFF_REG6,
-        MFF_REG7,
-        MFF_REG8,
-        MFF_REG9,
-        MFF_REG10,
-        MFF_REG11,
-        MFF_REG12,
-        MFF_REG13,
-        MFF_REG14,
-        MFF_REG15,
-#else
-#error
-#endif
-        MFF_METADATA,
-    };
-    for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) {
-        const struct mf_field *field = mf_from_id(md_fields[i]);
-        if (!mf_is_all_wild(field, &md->wc)) {
-            union mf_value value;
-            mf_get_value(field, &md->flow, &value);
-            ofpact_put_set_field(ofpacts, field, &value, NULL);
-        }
-    }
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_nd_na(struct rconn *swconn, const struct flow *ip_flow,
-                     const struct match *md,
-                     struct ofpbuf *userdata, bool is_router)
-{
-    /* This action only works for IPv6 ND packets, and the switch should only
-     * send us ND packets this way, but check here just to be sure. */
-    if (!is_nd(ip_flow, NULL)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "NA action on non-ND packet");
-        return;
-    }
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-
-    /* These flags are not exactly correct.  Look at section 7.2.4
-     * of RFC 4861. */
-    uint32_t rso_flags = ND_RSO_SOLICITED | ND_RSO_OVERRIDE;
-    if (is_router) {
-        rso_flags |= ND_RSO_ROUTER;
-    }
-    compose_nd_na(&packet, ip_flow->dl_dst, ip_flow->dl_src,
-                  &ip_flow->nd_target, &ip_flow->ipv6_src,
-                  htonl(rso_flags));
-
-    /* Reload previous packet metadata and set actions from userdata. */
-    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-    dp_packet_uninit(&packet);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
-                     struct dp_packet *pkt_in,
-                     const struct match *md, struct ofpbuf *userdata)
-{
-    /* This action only works for IPv6 packets. */
-    if (get_dl_type(ip_flow) != htons(ETH_TYPE_IPV6)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "NS action on non-IPv6 packet");
-        return;
-    }
-
-    ovs_mutex_lock(&pinctrl_mutex);
-    pinctrl_handle_buffered_packets(ip_flow, pkt_in, md, false);
-    ovs_mutex_unlock(&pinctrl_mutex);
-
-    uint64_t packet_stub[128 / 8];
-    struct dp_packet packet;
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-
-    compose_nd_ns(&packet, ip_flow->dl_src, &ip_flow->ipv6_src,
-                  &ip_flow->ipv6_dst);
-
-    /* Reload previous packet metadata and set actions from userdata. */
-    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-    dp_packet_uninit(&packet);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_put_nd_ra_opts(
-    struct rconn *swconn,
-    const struct flow *in_flow, struct dp_packet *pkt_in,
-    struct ofputil_packet_in *pin, struct ofpbuf *userdata,
-    struct ofpbuf *continuation)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    struct dp_packet *pkt_out_ptr = NULL;
-    uint32_t success = 0;
-
-    /* Parse result field. */
-    const struct mf_field *f;
-    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
-    if (ofperr) {
-       VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
-       goto exit;
-    }
-
-    /* Parse result offset. */
-    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
-    if (!ofsp) {
-        VLOG_WARN_RL(&rl, "offset not present in the userdata");
-        goto exit;
-    }
-
-    /* Check that the result is valid and writable. */
-    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
-    ofperr = mf_check_dst(&dst, NULL);
-    if (ofperr) {
-        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
-        goto exit;
-    }
-
-    if (!userdata->size) {
-        VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the userdata");
-        goto exit;
-    }
-
-    if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) ||
-        in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) {
-        VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported packet");
-        goto exit;
-    }
-
-    size_t new_packet_size = pkt_in->l4_ofs + userdata->size;
-    struct dp_packet pkt_out;
-    dp_packet_init(&pkt_out, new_packet_size);
-    dp_packet_clear(&pkt_out);
-    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
-    pkt_out_ptr = &pkt_out;
-
-    /* Copy L2 and L3 headers from pkt_in. */
-    dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs),
-                  pkt_in->l4_ofs);
-
-    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
-    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
-    pkt_out.l3_ofs = pkt_in->l3_ofs;
-    pkt_out.l4_ofs = pkt_in->l4_ofs;
-
-    /* Copy the ICMPv6 Router Advertisement data from 'userdata' field. */
-    dp_packet_put(&pkt_out, userdata->data, userdata->size);
-
-    /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */
-    struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);
-    nh->ip6_plen = htons(userdata->size);
-    struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out);
-    ra->icmph.icmp6_cksum = 0;
-    uint32_t icmp_csum = packet_csum_pseudoheader6(nh);
-    ra->icmph.icmp6_cksum = csum_finish(csum_continue(
-        icmp_csum, ra, userdata->size));
-    pin->packet = dp_packet_data(&pkt_out);
-    pin->packet_len = dp_packet_size(&pkt_out);
-    success = 1;
-
-exit:
-    if (!ofperr) {
-        union mf_subvalue sv;
-        sv.u8_val = success;
-        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
-    }
-    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
-    dp_packet_uninit(pkt_out_ptr);
-}
-
-/* Called with in the pinctrl_handler thread context. */
-static void
-pinctrl_handle_put_icmp4_frag_mtu(struct rconn *swconn,
-                                  const struct flow *in_flow,
-                                  struct dp_packet *pkt_in,
-                                  struct ofputil_packet_in *pin,
-                                  struct ofpbuf *userdata,
-                                  struct ofpbuf *continuation)
-{
-    enum ofp_version version = rconn_get_version(swconn);
-    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-    struct dp_packet *pkt_out = NULL;
-
-    /* This action only works for ICMPv4 packets. */
-    if (!is_icmpv4(in_flow, NULL)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "put_icmp4_frag_mtu action on non-ICMPv4 packet");
-        goto exit;
-    }
-
-    ovs_be16 *mtu = ofpbuf_try_pull(userdata, sizeof *mtu);
-    if (!mtu) {
-        goto exit;
-    }
-
-    pkt_out = dp_packet_clone(pkt_in);
-    pkt_out->l2_5_ofs = pkt_in->l2_5_ofs;
-    pkt_out->l2_pad_size = pkt_in->l2_pad_size;
-    pkt_out->l3_ofs = pkt_in->l3_ofs;
-    pkt_out->l4_ofs = pkt_in->l4_ofs;
-
-    struct ip_header *nh = dp_packet_l3(pkt_out);
-    struct icmp_header *ih = dp_packet_l4(pkt_out);
-    ovs_be16 old_frag_mtu = ih->icmp_fields.frag.mtu;
-    ih->icmp_fields.frag.mtu = *mtu;
-    ih->icmp_csum = recalc_csum16(ih->icmp_csum, old_frag_mtu, *mtu);
-    nh->ip_csum = 0;
-    nh->ip_csum = csum(nh, sizeof *nh);
-
-    pin->packet = dp_packet_data(pkt_out);
-    pin->packet_len = dp_packet_size(pkt_out);
-
-exit:
-    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
-    if (pkt_out) {
-        dp_packet_delete(pkt_out);
-    }
-}
-
-static void
-wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn)
-{
-    if (!ovnsb_idl_txn) {
-        return;
-    }
-
-    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
-        if (!hmap_is_empty(&event_table[i])) {
-            poll_immediate_wake();
-            break;
-        }
-    }
-}
-
-static bool
-pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
-{
-    struct controller_event_opt_header *userdata_opt;
-    uint32_t hash = 0;
-    char *vip = NULL;
-    char *protocol = NULL;
-    char *load_balancer = NULL;
-
-    while (userdata->size) {
-        userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt);
-        if (!userdata_opt) {
-            return false;
-        }
-        size_t size = ntohs(userdata_opt->size);
-        char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
-        if (!userdata_opt_data) {
-            return false;
-        }
-        switch (ntohs(userdata_opt->opt_code)) {
-        case EMPTY_LB_VIP:
-            vip = xmemdup0(userdata_opt_data, size);
-            break;
-        case EMPTY_LB_PROTOCOL:
-            protocol = xmemdup0(userdata_opt_data, size);
-            break;
-        case EMPTY_LB_LOAD_BALANCER:
-            load_balancer = xmemdup0(userdata_opt_data, size);
-            break;
-        default:
-            OVS_NOT_REACHED();
-        }
-        hash = hash_bytes(userdata_opt_data, size, hash);
-    }
-    if (!vip || !protocol || !load_balancer) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "missing lb parameters in userdata");
-        return false;
-    }
-
-    struct empty_lb_backends_event *event;
-
-    event = pinctrl_find_empty_lb_backends_event(vip, protocol,
-                                                 load_balancer, hash);
-    if (!event) {
-        if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
-            COVERAGE_INC(pinctrl_drop_controller_event);
-            return false;
-        }
-
-        event = xzalloc(sizeof *event);
-        hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
-                    &event->hmap_node, hash);
-        event->vip = vip;
-        event->protocol = protocol;
-        event->load_balancer = load_balancer;
-        event->timestamp = time_msec();
-        notify_pinctrl_main();
-    } else {
-        free(vip);
-        free(protocol);
-        free(load_balancer);
-    }
-    return true;
-}
-
-static void
-pinctrl_handle_event(struct ofpbuf *userdata)
-    OVS_REQUIRES(pinctrl_mutex)
-{
-    ovs_be32 *pevent;
-
-    pevent = ofpbuf_try_pull(userdata, sizeof *pevent);
-    if (!pevent) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "event not present in the userdata");
-        return;
-    }
-
-    switch (ntohl(*pevent)) {
-    case OVN_EVENT_EMPTY_LB_BACKENDS:
-        pinctrl_handle_empty_lb_backends_opts(userdata);
-        break;
-    default:
-        return;
-    }
-}
diff --git a/ovn/controller/pinctrl.h b/ovn/controller/pinctrl.h
deleted file mode 100644
index fcfce6bcf..000000000
--- a/ovn/controller/pinctrl.h
+++ /dev/null
@@ -1,51 +0,0 @@
-
-/* Copyright (c) 2015, 2016 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 PINCTRL_H
-#define PINCTRL_H 1
-
-#include <stdint.h>
-
-#include "lib/sset.h"
-#include "openvswitch/meta-flow.h"
-
-struct hmap;
-struct lport_index;
-struct ovsdb_idl_index;
-struct ovsdb_idl_txn;
-struct ovsrec_bridge;
-struct sbrec_chassis;
-struct sbrec_dns_table;
-struct sbrec_controller_event_table;
-
-void pinctrl_init(void);
-void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                 struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                 struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                 struct ovsdb_idl_index *sbrec_igmp_groups,
-                 struct ovsdb_idl_index *sbrec_ip_multicast_opts,
-                 const struct sbrec_dns_table *,
-                 const struct sbrec_controller_event_table *,
-                 const struct ovsrec_bridge *, const struct sbrec_chassis *,
-                 const struct hmap *local_datapaths,
-                 const struct sset *active_tunnels);
-void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
-void pinctrl_destroy(void);
-
-#endif /* ovn/pinctrl.h */
diff --git a/ovn/lib/.gitignore b/ovn/lib/.gitignore
deleted file mode 100644
index a80a1bce1..000000000
--- 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 f47b0af43..000000000
--- 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 55dc75b7f..000000000
--- 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/actions.c b/ovn/lib/actions.c
deleted file mode 100644
index 4eacc44ed..000000000
--- a/ovn/lib/actions.c
+++ /dev/null
@@ -1,2902 +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 <stdarg.h>
-#include <stdbool.h>
-#include "bitmap.h"
-#include "byte-order.h"
-#include "compiler.h"
-#include "ovn-l7.h"
-#include "hash.h"
-#include "lib/packets.h"
-#include "nx-match.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/json.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofpbuf.h"
-#include "openvswitch/vlog.h"
-#include "ovn/actions.h"
-#include "ovn/expr.h"
-#include "ovn/lex.h"
-#include "ovn/lib/acl-log.h"
-#include "ovn/lib/extend-table.h"
-#include "packets.h"
-#include "openvswitch/shash.h"
-#include "simap.h"
-#include "uuid.h"
-#include "socket-util.h"
-
-VLOG_DEFINE_THIS_MODULE(actions);
-
-/* Prototypes for functions to be defined by each action. */
-#define OVNACT(ENUM, STRUCT)                                        \
-    static void format_##ENUM(const struct STRUCT *, struct ds *);  \
-    static void encode_##ENUM(const struct STRUCT *,                \
-                              const struct ovnact_encode_params *,  \
-                              struct ofpbuf *ofpacts);              \
-    static void STRUCT##_free(struct STRUCT *a);
-OVNACTS
-#undef OVNACT
-
-/* Helpers. */
-
-/* Implementation of ovnact_put_<ENUM>(). */
-void *
-ovnact_put(struct ofpbuf *ovnacts, enum ovnact_type type, size_t len)
-{
-    ovs_assert(len == OVNACT_ALIGN(len));
-
-    ovnacts->header = ofpbuf_put_uninit(ovnacts, len);
-    struct ovnact *ovnact = ovnacts->header;
-    ovnact_init(ovnact, type, len);
-    return ovnact;
-}
-
-/* Implementation of ovnact_init_<ENUM>(). */
-void
-ovnact_init(struct ovnact *ovnact, enum ovnact_type type, size_t len)
-{
-    ovs_assert(len == OVNACT_ALIGN(len));
-    memset(ovnact, 0, len);
-    ovnact->type = type;
-    ovnact->len = len;
-}
-
-static size_t
-encode_start_controller_op(enum action_opcode opcode, bool pause,
-                           uint32_t meter_id, struct ofpbuf *ofpacts)
-{
-    size_t ofs = ofpacts->size;
-
-    struct ofpact_controller *oc = ofpact_put_CONTROLLER(ofpacts);
-    oc->max_len = UINT16_MAX;
-    oc->reason = OFPR_ACTION;
-    oc->pause = pause;
-    oc->meter_id = meter_id;
-
-    struct action_header ah = { .opcode = htonl(opcode) };
-    ofpbuf_put(ofpacts, &ah, sizeof ah);
-
-    return ofs;
-}
-
-static void
-encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts)
-{
-    struct ofpact_controller *oc = ofpbuf_at_assert(ofpacts, ofs, sizeof *oc);
-    ofpacts->header = oc;
-    oc->userdata_len = ofpacts->size - (ofs + sizeof *oc);
-    ofpact_finish_CONTROLLER(ofpacts, &oc);
-}
-
-static void
-encode_controller_op(enum action_opcode opcode, struct ofpbuf *ofpacts)
-{
-    size_t ofs = encode_start_controller_op(opcode, false, NX_CTLR_NO_METER,
-                                            ofpacts);
-    encode_finish_controller_op(ofs, ofpacts);
-}
-
-static void
-init_stack(struct ofpact_stack *stack, enum mf_field_id field)
-{
-    stack->subfield.field = mf_from_id(field);
-    stack->subfield.ofs = 0;
-    stack->subfield.n_bits = stack->subfield.field->n_bits;
-}
-
-struct arg {
-    const struct mf_subfield src;
-    enum mf_field_id dst;
-};
-
-static void
-encode_setup_args(const struct arg args[], size_t n_args,
-                  struct ofpbuf *ofpacts)
-{
-    /* 1. Save all of the destinations that will be modified. */
-    for (const struct arg *a = args; a < &args[n_args]; a++) {
-        ovs_assert(a->src.n_bits == mf_from_id(a->dst)->n_bits);
-        if (a->src.field->id != a->dst) {
-            init_stack(ofpact_put_STACK_PUSH(ofpacts), a->dst);
-        }
-    }
-
-    /* 2. Push the sources, in reverse order. */
-    for (size_t i = n_args - 1; i < n_args; i--) {
-        const struct arg *a = &args[i];
-        if (a->src.field->id != a->dst) {
-            ofpact_put_STACK_PUSH(ofpacts)->subfield = a->src;
-        }
-    }
-
-    /* 3. Pop the sources into the destinations. */
-    for (const struct arg *a = args; a < &args[n_args]; a++) {
-        if (a->src.field->id != a->dst) {
-            init_stack(ofpact_put_STACK_POP(ofpacts), a->dst);
-        }
-    }
-}
-
-static void
-encode_restore_args(const struct arg args[], size_t n_args,
-                    struct ofpbuf *ofpacts)
-{
-    for (size_t i = n_args - 1; i < n_args; i--) {
-        const struct arg *a = &args[i];
-        if (a->src.field->id != a->dst) {
-            init_stack(ofpact_put_STACK_POP(ofpacts), a->dst);
-        }
-    }
-}
-
-static void
-put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
-         struct ofpbuf *ofpacts)
-{
-    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
-                                                       mf_from_id(dst), NULL,
-                                                       NULL);
-    ovs_be64 n_value = htonll(value);
-    bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
-    bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
-}
-
-static uint8_t
-first_ptable(const struct ovnact_encode_params *ep,
-             enum ovnact_pipeline pipeline)
-{
-    return (pipeline == OVNACT_P_INGRESS
-            ? ep->ingress_ptable
-            : ep->egress_ptable);
-}
-
-#define MAX_NESTED_ACTION_DEPTH 32
-
-/* Context maintained during ovnacts_parse(). */
-struct action_context {
-    const struct ovnact_parse_params *pp; /* Parameters. */
-    struct lexer *lexer;        /* Lexer for pulling more tokens. */
-    struct ofpbuf *ovnacts;     /* Actions. */
-    struct expr *prereqs;       /* Prerequisites to apply to match. */
-    int depth;                  /* Current nested action depth. */
-};
-
-static void parse_actions(struct action_context *, enum lex_type sentinel);
-
-static bool
-action_parse_field(struct action_context *ctx,
-                   int n_bits, bool rw, struct expr_field *f)
-{
-    if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, f, &ctx->prereqs)) {
-        return false;
-    }
-
-    char *error = expr_type_check(f, n_bits, rw);
-    if (error) {
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return false;
-    }
-
-    return true;
-}
-
-static bool
-action_parse_port(struct action_context *ctx, uint16_t *port)
-{
-    if (lexer_is_int(ctx->lexer)) {
-        int value = ntohll(ctx->lexer->token.value.integer);
-        if (value <= UINT16_MAX) {
-            *port = value;
-            lexer_get(ctx->lexer);
-            return true;
-        }
-    }
-    lexer_syntax_error(ctx->lexer, "expecting port number");
-    return false;
-}
-
-/* Parses 'prerequisite' as an expression in the context of 'ctx', then adds it
- * as a conjunction with the existing 'ctx->prereqs'. */
-static void
-add_prerequisite(struct action_context *ctx, const char *prerequisite)
-{
-    struct expr *expr;
-    char *error;
-
-    expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL, NULL,
-                             &error);
-    ovs_assert(!error);
-    ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
-}
-
-static void
-ovnact_null_free(struct ovnact_null *a OVS_UNUSED)
-{
-}
-
-static void
-format_OUTPUT(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
-{
-    ds_put_cstr(s, "output;");
-}
-
-static void
-emit_resubmit(struct ofpbuf *ofpacts, uint8_t ptable)
-{
-    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ofpacts);
-    resubmit->in_port = OFPP_IN_PORT;
-    resubmit->table_id = ptable;
-}
-
-static void
-encode_OUTPUT(const struct ovnact_null *a OVS_UNUSED,
-              const struct ovnact_encode_params *ep,
-              struct ofpbuf *ofpacts)
-{
-    emit_resubmit(ofpacts, ep->output_ptable);
-}
-
-static void
-parse_NEXT(struct action_context *ctx)
-{
-    if (!ctx->pp->n_tables) {
-        lexer_error(ctx->lexer, "\"next\" action not allowed here.");
-        return;
-    }
-
-    int pipeline = ctx->pp->pipeline;
-    int table = ctx->pp->cur_ltable + 1;
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        if (lexer_is_int(ctx->lexer)) {
-            lexer_get_int(ctx->lexer, &table);
-        } else {
-            do {
-                if (lexer_match_id(ctx->lexer, "pipeline")) {
-                    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-                        return;
-                    }
-                    if (lexer_match_id(ctx->lexer, "ingress")) {
-                        pipeline = OVNACT_P_INGRESS;
-                    } else if (lexer_match_id(ctx->lexer, "egress")) {
-                        pipeline = OVNACT_P_EGRESS;
-                    } else {
-                        lexer_syntax_error(
-                            ctx->lexer, "expecting \"ingress\" or \"egress\"");
-                        return;
-                    }
-                } else if (lexer_match_id(ctx->lexer, "table")) {
-                    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS) ||
-                        !lexer_force_int(ctx->lexer, &table)) {
-                        return;
-                    }
-                } else {
-                    lexer_syntax_error(ctx->lexer,
-                                       "expecting \"pipeline\" or \"table\"");
-                    return;
-                }
-            } while (lexer_match(ctx->lexer, LEX_T_COMMA));
-        }
-        if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-            return;
-        }
-    }
-
-    if (pipeline == OVNACT_P_EGRESS && ctx->pp->pipeline == OVNACT_P_INGRESS) {
-        lexer_error(ctx->lexer,
-                    "\"next\" action cannot advance from ingress to egress "
-                    "pipeline (use \"output\" action instead)");
-    } else if (table >= ctx->pp->n_tables) {
-        lexer_error(ctx->lexer,
-                    "\"next\" action cannot advance beyond table %d.",
-                    ctx->pp->n_tables - 1);
-        return;
-    }
-
-    struct ovnact_next *next = ovnact_put_NEXT(ctx->ovnacts);
-    next->pipeline = pipeline;
-    next->ltable = table;
-    next->src_pipeline = ctx->pp->pipeline;
-    next->src_ltable = ctx->pp->cur_ltable;
-}
-
-static void
-format_NEXT(const struct ovnact_next *next, struct ds *s)
-{
-    if (next->pipeline != next->src_pipeline) {
-        ds_put_format(s, "next(pipeline=%s, table=%d);",
-                      (next->pipeline == OVNACT_P_INGRESS
-                       ? "ingress" : "egress"),
-                      next->ltable);
-    } else if (next->ltable != next->src_ltable + 1) {
-        ds_put_format(s, "next(%d);", next->ltable);
-    } else {
-        ds_put_cstr(s, "next;");
-    }
-}
-
-static void
-encode_NEXT(const struct ovnact_next *next,
-            const struct ovnact_encode_params *ep,
-            struct ofpbuf *ofpacts)
-{
-    emit_resubmit(ofpacts, first_ptable(ep, next->pipeline) + next->ltable);
-}
-
-static void
-ovnact_next_free(struct ovnact_next *a OVS_UNUSED)
-{
-}
-
-static void
-parse_LOAD(struct action_context *ctx, const struct expr_field *lhs)
-{
-    size_t ofs = ctx->ovnacts->size;
-    struct ovnact_load *load;
-    if (lhs->symbol->ovn_field) {
-        load = ovnact_put_OVNFIELD_LOAD(ctx->ovnacts);
-    } else {
-        load = ovnact_put_LOAD(ctx->ovnacts);
-    }
-
-    load->dst = *lhs;
-
-    char *error = expr_type_check(lhs, lhs->n_bits, true);
-    if (error) {
-        ctx->ovnacts->size = ofs;
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return;
-    }
-    if (!expr_constant_parse(ctx->lexer, lhs, &load->imm)) {
-        ctx->ovnacts->size = ofs;
-        return;
-    }
-}
-
-static enum expr_constant_type
-load_type(const struct ovnact_load *load)
-{
-    return load->dst.symbol->width > 0 ? EXPR_C_INTEGER : EXPR_C_STRING;
-}
-
-static void
-format_LOAD(const struct ovnact_load *load, struct ds *s)
-{
-    expr_field_format(&load->dst, s);
-    ds_put_cstr(s, " = ");
-    expr_constant_format(&load->imm, load_type(load), s);
-    ds_put_char(s, ';');
-}
-
-static void
-encode_LOAD(const struct ovnact_load *load,
-            const struct ovnact_encode_params *ep,
-            struct ofpbuf *ofpacts)
-{
-    const union expr_constant *c = &load->imm;
-    struct mf_subfield dst = expr_resolve_field(&load->dst);
-    struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, dst.field,
-                                                       NULL, NULL);
-
-    if (load->dst.symbol->width) {
-        bitwise_copy(&c->value, sizeof c->value, 0,
-                     sf->value, dst.field->n_bytes, dst.ofs,
-                     dst.n_bits);
-        if (c->masked) {
-            bitwise_copy(&c->mask, sizeof c->mask, 0,
-                         ofpact_set_field_mask(sf), dst.field->n_bytes,
-                         dst.ofs, dst.n_bits);
-        } else {
-            bitwise_one(ofpact_set_field_mask(sf), dst.field->n_bytes,
-                        dst.ofs, dst.n_bits);
-        }
-    } else {
-        uint32_t port;
-        if (!ep->lookup_port(ep->aux, load->imm.string, &port)) {
-            port = 0;
-        }
-        bitwise_put(port, sf->value,
-                    sf->field->n_bytes, 0, sf->field->n_bits);
-        bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, 0,
-                    sf->field->n_bits);
-    }
-}
-
-static void
-ovnact_load_free(struct ovnact_load *load)
-{
-    expr_constant_destroy(&load->imm, load_type(load));
-}
-
-static void
-format_assignment(const struct ovnact_move *move, const char *operator,
-                  struct ds *s)
-{
-    expr_field_format(&move->lhs, s);
-    ds_put_format(s, " %s ", operator);
-    expr_field_format(&move->rhs, s);
-    ds_put_char(s, ';');
-}
-
-static void
-format_MOVE(const struct ovnact_move *move, struct ds *s)
-{
-    format_assignment(move, "=", s);
-}
-
-static void
-format_EXCHANGE(const struct ovnact_move *move, struct ds *s)
-{
-    format_assignment(move, "<->", s);
-}
-
-static void
-parse_assignment_action(struct action_context *ctx, bool exchange,
-                        const struct expr_field *lhs)
-{
-    struct expr_field rhs;
-    if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, &rhs, &ctx->prereqs)) {
-        return;
-    }
-
-    const struct expr_symbol *ls = lhs->symbol;
-    const struct expr_symbol *rs = rhs.symbol;
-    if ((ls->width != 0) != (rs->width != 0)) {
-        if (exchange) {
-            lexer_error(ctx->lexer,
-                        "Can't exchange %s field (%s) with %s field (%s).",
-                        ls->width ? "integer" : "string",
-                        ls->name,
-                        rs->width ? "integer" : "string",
-                        rs->name);
-        } else {
-            lexer_error(ctx->lexer,
-                        "Can't assign %s field (%s) to %s field (%s).",
-                        rs->width ? "integer" : "string",
-                        rs->name,
-                        ls->width ? "integer" : "string",
-                        ls->name);
-        }
-        return;
-    }
-
-    if (lhs->n_bits != rhs.n_bits) {
-        if (exchange) {
-            lexer_error(ctx->lexer,
-                        "Can't exchange %d-bit field with %d-bit field.",
-                        lhs->n_bits, rhs.n_bits);
-        } else {
-            lexer_error(ctx->lexer,
-                        "Can't assign %d-bit value to %d-bit destination.",
-                        rhs.n_bits, lhs->n_bits);
-        }
-        return;
-    } else if (!lhs->n_bits &&
-               ls->field->n_bits != rs->field->n_bits) {
-        lexer_error(ctx->lexer, "String fields %s and %s are incompatible for "
-                    "%s.", ls->name, rs->name,
-                    exchange ? "exchange" : "assignment");
-        return;
-    }
-
-    char *error = expr_type_check(lhs, lhs->n_bits, true);
-    if (!error) {
-        error = expr_type_check(&rhs, rhs.n_bits, true);
-    }
-    if (error) {
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return;
-    }
-
-    struct ovnact_move *move;
-    move = (exchange
-            ? ovnact_put_EXCHANGE(ctx->ovnacts)
-            : ovnact_put_MOVE(ctx->ovnacts));
-    move->lhs = *lhs;
-    move->rhs = rhs;
-}
-
-static void
-encode_MOVE(const struct ovnact_move *move,
-            const struct ovnact_encode_params *ep OVS_UNUSED,
-            struct ofpbuf *ofpacts)
-{
-    struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
-    orm->src = expr_resolve_field(&move->rhs);
-    orm->dst = expr_resolve_field(&move->lhs);
-}
-
-static void
-encode_EXCHANGE(const struct ovnact_move *xchg,
-                const struct ovnact_encode_params *ep OVS_UNUSED,
-                struct ofpbuf *ofpacts)
-{
-    ofpact_put_STACK_PUSH(ofpacts)->subfield = expr_resolve_field(&xchg->rhs);
-    ofpact_put_STACK_PUSH(ofpacts)->subfield = expr_resolve_field(&xchg->lhs);
-    ofpact_put_STACK_POP(ofpacts)->subfield = expr_resolve_field(&xchg->rhs);
-    ofpact_put_STACK_POP(ofpacts)->subfield = expr_resolve_field(&xchg->lhs);
-}
-
-static void
-ovnact_move_free(struct ovnact_move *move OVS_UNUSED)
-{
-}
-
-static void
-parse_DEC_TTL(struct action_context *ctx)
-{
-    lexer_force_match(ctx->lexer, LEX_T_DECREMENT);
-    ovnact_put_DEC_TTL(ctx->ovnacts);
-    add_prerequisite(ctx, "ip");
-}
-
-static void
-format_DEC_TTL(const struct ovnact_null *null OVS_UNUSED, struct ds *s)
-{
-    ds_put_cstr(s, "ip.ttl--;");
-}
-
-static void
-encode_DEC_TTL(const struct ovnact_null *null OVS_UNUSED,
-               const struct ovnact_encode_params *ep OVS_UNUSED,
-               struct ofpbuf *ofpacts)
-{
-    ofpact_put_DEC_TTL(ofpacts);
-}
-
-static void
-parse_CT_NEXT(struct action_context *ctx)
-{
-    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
-        lexer_error(ctx->lexer,
-                    "\"ct_next\" action not allowed in last table.");
-        return;
-    }
-
-    add_prerequisite(ctx, "ip");
-    ovnact_put_CT_NEXT(ctx->ovnacts)->ltable = ctx->pp->cur_ltable + 1;
-}
-
-static void
-format_CT_NEXT(const struct ovnact_ct_next *ct_next OVS_UNUSED, struct ds *s)
-{
-    ds_put_cstr(s, "ct_next;");
-}
-
-static void
-encode_CT_NEXT(const struct ovnact_ct_next *ct_next,
-                const struct ovnact_encode_params *ep,
-                struct ofpbuf *ofpacts)
-{
-    struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
-    ct->recirc_table = first_ptable(ep, ep->pipeline) + ct_next->ltable;
-    ct->zone_src.field = ep->is_switch ? mf_from_id(MFF_LOG_CT_ZONE)
-                            : mf_from_id(MFF_LOG_DNAT_ZONE);
-    ct->zone_src.ofs = 0;
-    ct->zone_src.n_bits = 16;
-    ofpact_finish(ofpacts, &ct->ofpact);
-}
-
-static void
-ovnact_ct_next_free(struct ovnact_ct_next *a OVS_UNUSED)
-{
-}
-
-static void
-parse_ct_commit_arg(struct action_context *ctx,
-                    struct ovnact_ct_commit *cc)
-{
-    if (lexer_match_id(ctx->lexer, "ct_mark")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        if (ctx->lexer->token.type == LEX_T_INTEGER) {
-            cc->ct_mark = ntohll(ctx->lexer->token.value.integer);
-            cc->ct_mark_mask = UINT32_MAX;
-        } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) {
-            cc->ct_mark = ntohll(ctx->lexer->token.value.integer);
-            cc->ct_mark_mask = ntohll(ctx->lexer->token.mask.integer);
-        } else {
-            lexer_syntax_error(ctx->lexer, "expecting integer");
-            return;
-        }
-        lexer_get(ctx->lexer);
-    } else if (lexer_match_id(ctx->lexer, "ct_label")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        if (ctx->lexer->token.type == LEX_T_INTEGER) {
-            cc->ct_label = ctx->lexer->token.value.be128_int;
-            cc->ct_label_mask = OVS_BE128_MAX;
-        } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) {
-            cc->ct_label = ctx->lexer->token.value.be128_int;
-            cc->ct_label_mask = ctx->lexer->token.mask.be128_int;
-        } else {
-            lexer_syntax_error(ctx->lexer, "expecting integer");
-            return;
-        }
-        lexer_get(ctx->lexer);
-    } else {
-        lexer_syntax_error(ctx->lexer, NULL);
-    }
-}
-
-static void
-parse_CT_COMMIT(struct action_context *ctx)
-{
-    add_prerequisite(ctx, "ip");
-
-    struct ovnact_ct_commit *ct_commit = ovnact_put_CT_COMMIT(ctx->ovnacts);
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-            parse_ct_commit_arg(ctx, ct_commit);
-            if (ctx->lexer->error) {
-                return;
-            }
-            lexer_match(ctx->lexer, LEX_T_COMMA);
-        }
-    }
-}
-
-static void
-format_CT_COMMIT(const struct ovnact_ct_commit *cc, struct ds *s)
-{
-    ds_put_cstr(s, "ct_commit(");
-    if (cc->ct_mark_mask) {
-        ds_put_format(s, "ct_mark=%#"PRIx32, cc->ct_mark);
-        if (cc->ct_mark_mask != UINT32_MAX) {
-            ds_put_format(s, "/%#"PRIx32, cc->ct_mark_mask);
-        }
-    }
-    if (!ovs_be128_is_zero(cc->ct_label_mask)) {
-        if (ds_last(s) != '(') {
-            ds_put_cstr(s, ", ");
-        }
-
-        ds_put_format(s, "ct_label=");
-        ds_put_hex(s, &cc->ct_label, sizeof cc->ct_label);
-        if (!ovs_be128_equals(cc->ct_label_mask, OVS_BE128_MAX)) {
-            ds_put_char(s, '/');
-            ds_put_hex(s, &cc->ct_label_mask, sizeof cc->ct_label_mask);
-        }
-    }
-    if (!ds_chomp(s, '(')) {
-        ds_put_char(s, ')');
-    }
-    ds_put_char(s, ';');
-}
-
-static void
-encode_CT_COMMIT(const struct ovnact_ct_commit *cc,
-                 const struct ovnact_encode_params *ep OVS_UNUSED,
-                 struct ofpbuf *ofpacts)
-{
-    struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
-    ct->flags = NX_CT_F_COMMIT;
-    ct->recirc_table = NX_CT_RECIRC_NONE;
-    ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE);
-    ct->zone_src.ofs = 0;
-    ct->zone_src.n_bits = 16;
-
-    size_t set_field_offset = ofpacts->size;
-    ofpbuf_pull(ofpacts, set_field_offset);
-
-    if (cc->ct_mark_mask) {
-        const ovs_be32 value = htonl(cc->ct_mark);
-        const ovs_be32 mask = htonl(cc->ct_mark_mask);
-        ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_MARK), &value, &mask);
-    }
-
-    if (!ovs_be128_is_zero(cc->ct_label_mask)) {
-        ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_LABEL), &cc->ct_label,
-                             &cc->ct_label_mask);
-    }
-
-    ofpacts->header = ofpbuf_push_uninit(ofpacts, set_field_offset);
-    ct = ofpacts->header;
-    ofpact_finish(ofpacts, &ct->ofpact);
-}
-
-static void
-ovnact_ct_commit_free(struct ovnact_ct_commit *cc OVS_UNUSED)
-{
-}
-
-static void
-parse_ct_nat(struct action_context *ctx, const char *name,
-             struct ovnact_ct_nat *cn)
-{
-    add_prerequisite(ctx, "ip");
-
-    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
-        lexer_error(ctx->lexer,
-                    "\"%s\" action not allowed in last table.", name);
-        return;
-    }
-    cn->ltable = ctx->pp->cur_ltable + 1;
-
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        if (ctx->lexer->token.type != LEX_T_INTEGER
-            || ctx->lexer->token.format != LEX_F_IPV4) {
-            lexer_syntax_error(ctx->lexer, "expecting IPv4 address");
-            return;
-        }
-        cn->ip = ctx->lexer->token.value.ipv4;
-        lexer_get(ctx->lexer);
-
-        if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-            return;
-        }
-    }
-}
-
-static void
-parse_CT_DNAT(struct action_context *ctx)
-{
-    parse_ct_nat(ctx, "ct_dnat", ovnact_put_CT_DNAT(ctx->ovnacts));
-}
-
-static void
-parse_CT_SNAT(struct action_context *ctx)
-{
-    parse_ct_nat(ctx, "ct_snat", ovnact_put_CT_SNAT(ctx->ovnacts));
-}
-
-static void
-format_ct_nat(const struct ovnact_ct_nat *cn, const char *name, struct ds *s)
-{
-    ds_put_cstr(s, name);
-    if (cn->ip) {
-        ds_put_format(s, "("IP_FMT")", IP_ARGS(cn->ip));
-    }
-    ds_put_char(s, ';');
-}
-
-static void
-format_CT_DNAT(const struct ovnact_ct_nat *cn, struct ds *s)
-{
-    format_ct_nat(cn, "ct_dnat", s);
-}
-
-static void
-format_CT_SNAT(const struct ovnact_ct_nat *cn, struct ds *s)
-{
-    format_ct_nat(cn, "ct_snat", s);
-}
-
-static void
-encode_ct_nat(const struct ovnact_ct_nat *cn,
-              const struct ovnact_encode_params *ep,
-              bool snat, struct ofpbuf *ofpacts)
-{
-    const size_t ct_offset = ofpacts->size;
-    ofpbuf_pull(ofpacts, ct_offset);
-
-    struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
-    ct->recirc_table = cn->ltable + first_ptable(ep, ep->pipeline);
-    if (snat) {
-        ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE);
-    } else {
-        ct->zone_src.field = mf_from_id(MFF_LOG_DNAT_ZONE);
-    }
-    ct->zone_src.ofs = 0;
-    ct->zone_src.n_bits = 16;
-    ct->flags = 0;
-    ct->alg = 0;
-
-    struct ofpact_nat *nat;
-    size_t nat_offset;
-    nat_offset = ofpacts->size;
-    ofpbuf_pull(ofpacts, nat_offset);
-
-    nat = ofpact_put_NAT(ofpacts);
-    nat->flags = 0;
-    nat->range_af = AF_UNSPEC;
-
-    if (cn->ip) {
-        nat->range_af = AF_INET;
-        nat->range.addr.ipv4.min = cn->ip;
-        if (snat) {
-            nat->flags |= NX_NAT_F_SRC;
-        } else {
-            nat->flags |= NX_NAT_F_DST;
-        }
-    }
-
-    ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
-    ct = ofpacts->header;
-    if (cn->ip) {
-        ct->flags |= NX_CT_F_COMMIT;
-    }
-    ofpact_finish(ofpacts, &ct->ofpact);
-    ofpbuf_push_uninit(ofpacts, ct_offset);
-}
-
-static void
-encode_CT_DNAT(const struct ovnact_ct_nat *cn,
-               const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
-{
-    encode_ct_nat(cn, ep, false, ofpacts);
-}
-
-static void
-encode_CT_SNAT(const struct ovnact_ct_nat *cn,
-               const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
-{
-    encode_ct_nat(cn, ep, true, ofpacts);
-}
-
-static void
-ovnact_ct_nat_free(struct ovnact_ct_nat *ct_nat OVS_UNUSED)
-{
-}
-
-static void
-parse_ct_lb_action(struct action_context *ctx)
-{
-    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
-        lexer_error(ctx->lexer, "\"ct_lb\" action not allowed in last table.");
-        return;
-    }
-
-    add_prerequisite(ctx, "ip");
-
-    struct ovnact_ct_lb_dst *dsts = NULL;
-    size_t allocated_dsts = 0;
-    size_t n_dsts = 0;
-
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-            struct ovnact_ct_lb_dst dst;
-            if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
-                /* IPv6 address and port */
-                if (ctx->lexer->token.type != LEX_T_INTEGER
-                    || ctx->lexer->token.format != LEX_F_IPV6) {
-                    free(dsts);
-                    lexer_syntax_error(ctx->lexer, "expecting IPv6 address");
-                    return;
-                }
-                dst.family = AF_INET6;
-                dst.ipv6 = ctx->lexer->token.value.ipv6;
-
-                lexer_get(ctx->lexer);
-                if (!lexer_match(ctx->lexer, LEX_T_RSQUARE)) {
-                    free(dsts);
-                    lexer_syntax_error(ctx->lexer, "no closing square "
-                                                   "bracket");
-                    return;
-                }
-                dst.port = 0;
-                if (lexer_match(ctx->lexer, LEX_T_COLON)
-                    && !action_parse_port(ctx, &dst.port)) {
-                    free(dsts);
-                    return;
-                }
-            } else {
-                if (ctx->lexer->token.type != LEX_T_INTEGER
-                    || (ctx->lexer->token.format != LEX_F_IPV4
-                    && ctx->lexer->token.format != LEX_F_IPV6)) {
-                    free(dsts);
-                    lexer_syntax_error(ctx->lexer, "expecting IP address");
-                    return;
-                }
-
-                /* Parse IP. */
-                if (ctx->lexer->token.format == LEX_F_IPV4) {
-                    dst.family = AF_INET;
-                    dst.ipv4 = ctx->lexer->token.value.ipv4;
-                } else {
-                    dst.family = AF_INET6;
-                    dst.ipv6 = ctx->lexer->token.value.ipv6;
-                }
-
-                lexer_get(ctx->lexer);
-                dst.port = 0;
-                if (lexer_match(ctx->lexer, LEX_T_COLON)) {
-                    if (dst.family == AF_INET6) {
-                        free(dsts);
-                        lexer_syntax_error(ctx->lexer, "IPv6 address needs "
-                                "square brackets if port is included");
-                        return;
-                    } else if (!action_parse_port(ctx, &dst.port)) {
-                        free(dsts);
-                        return;
-                    }
-                }
-            }
-            lexer_match(ctx->lexer, LEX_T_COMMA);
-
-            /* Append to dsts. */
-            if (n_dsts >= allocated_dsts) {
-                dsts = x2nrealloc(dsts, &allocated_dsts, sizeof *dsts);
-            }
-            dsts[n_dsts++] = dst;
-        }
-    }
-
-    struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
-    cl->ltable = ctx->pp->cur_ltable + 1;
-    cl->dsts = dsts;
-    cl->n_dsts = n_dsts;
-}
-
-static void
-format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s)
-{
-    ds_put_cstr(s, "ct_lb");
-    if (cl->n_dsts) {
-        ds_put_char(s, '(');
-        for (size_t i = 0; i < cl->n_dsts; i++) {
-            if (i) {
-                ds_put_cstr(s, ", ");
-            }
-
-            const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
-            if (dst->family == AF_INET) {
-                ds_put_format(s, IP_FMT, IP_ARGS(dst->ipv4));
-                if (dst->port) {
-                    ds_put_format(s, ":%"PRIu16, dst->port);
-                }
-            } else {
-                if (dst->port) {
-                    ds_put_char(s, '[');
-                }
-                ipv6_format_addr(&dst->ipv6, s);
-                if (dst->port) {
-                    ds_put_format(s, "]:%"PRIu16, dst->port);
-                }
-            }
-        }
-        ds_put_char(s, ')');
-    }
-    ds_put_char(s, ';');
-}
-
-static void
-encode_CT_LB(const struct ovnact_ct_lb *cl,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    uint8_t recirc_table = cl->ltable + first_ptable(ep, ep->pipeline);
-    if (!cl->n_dsts) {
-        /* ct_lb without any destinations means that this is an established
-         * connection and we just need to do a NAT. */
-        const size_t ct_offset = ofpacts->size;
-        ofpbuf_pull(ofpacts, ct_offset);
-
-        struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
-        struct ofpact_nat *nat;
-        size_t nat_offset;
-        ct->zone_src.field = ep->is_switch ? mf_from_id(MFF_LOG_CT_ZONE)
-                                : mf_from_id(MFF_LOG_DNAT_ZONE);
-        ct->zone_src.ofs = 0;
-        ct->zone_src.n_bits = 16;
-        ct->flags = 0;
-        ct->recirc_table = recirc_table;
-        ct->alg = 0;
-
-        nat_offset = ofpacts->size;
-        ofpbuf_pull(ofpacts, nat_offset);
-
-        nat = ofpact_put_NAT(ofpacts);
-        nat->flags = 0;
-        nat->range_af = AF_UNSPEC;
-
-        ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
-        ct = ofpacts->header;
-        ofpact_finish(ofpacts, &ct->ofpact);
-        ofpbuf_push_uninit(ofpacts, ct_offset);
-        return;
-    }
-
-    uint32_t table_id = 0;
-    struct ofpact_group *og;
-    uint32_t zone_reg = ep->is_switch ? MFF_LOG_CT_ZONE - MFF_REG0
-                            : MFF_LOG_DNAT_ZONE - MFF_REG0;
-
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    ds_put_format(&ds, "type=select,selection_method=dp_hash");
-
-    BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
-    BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
-    BUILD_ASSERT(MFF_LOG_DNAT_ZONE >= MFF_REG0);
-    BUILD_ASSERT(MFF_LOG_DNAT_ZONE < MFF_REG0 + FLOW_N_REGS);
-    for (size_t bucket_id = 0; bucket_id < cl->n_dsts; bucket_id++) {
-        const struct ovnact_ct_lb_dst *dst = &cl->dsts[bucket_id];
-        char ip_addr[INET6_ADDRSTRLEN];
-        if (dst->family == AF_INET) {
-            inet_ntop(AF_INET, &dst->ipv4, ip_addr, sizeof ip_addr);
-        } else {
-            inet_ntop(AF_INET6, &dst->ipv6, ip_addr, sizeof ip_addr);
-        }
-        ds_put_format(&ds, ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions="
-                      "ct(nat(dst=%s%s%s", bucket_id,
-                      dst->family == AF_INET6 && dst->port ? "[" : "",
-                      ip_addr,
-                      dst->family == AF_INET6 && dst->port ? "]" : "");
-        if (dst->port) {
-            ds_put_format(&ds, ":%"PRIu16, dst->port);
-        }
-        ds_put_format(&ds, "),commit,table=%d,zone=NXM_NX_REG%d[0..15])",
-                      recirc_table, zone_reg);
-    }
-
-    table_id = ovn_extend_table_assign_id(ep->group_table, ds_cstr(&ds),
-                                          ep->lflow_uuid);
-    ds_destroy(&ds);
-    if (table_id == EXT_TABLE_ID_INVALID) {
-        return;
-    }
-
-    /* Create an action to set the group. */
-    og = ofpact_put_GROUP(ofpacts);
-    og->group_id = table_id;
-}
-
-static void
-ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
-{
-    free(ct_lb->dsts);
-}
-
-static void
-format_CT_CLEAR(const struct ovnact_null *null OVS_UNUSED, struct ds *s)
-{
-    ds_put_cstr(s, "ct_clear;");
-}
-
-static void
-encode_CT_CLEAR(const struct ovnact_null *null OVS_UNUSED,
-                const struct ovnact_encode_params *ep OVS_UNUSED,
-                struct ofpbuf *ofpacts)
-{
-    ofpact_put_CT_CLEAR(ofpacts);
-}
-
-/* Implements the "arp", "nd_na", and "clone" actions, which execute nested
- * actions on a packet derived from the one being processed. */
-static void
-parse_nested_action(struct action_context *ctx, enum ovnact_type type,
-                    const char *prereq)
-{
-    if (!lexer_force_match(ctx->lexer, LEX_T_LCURLY)) {
-        return;
-    }
-
-    if (ctx->depth + 1 == MAX_NESTED_ACTION_DEPTH) {
-        lexer_error(ctx->lexer, "maximum depth of nested actions reached");
-        return;
-    }
-
-    uint64_t stub[1024 / 8];
-    struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub);
-
-    struct action_context inner_ctx = {
-        .pp = ctx->pp,
-        .lexer = ctx->lexer,
-        .ovnacts = &nested,
-        .prereqs = NULL,
-        .depth = ctx->depth + 1,
-    };
-    parse_actions(&inner_ctx, LEX_T_RCURLY);
-
-    if (prereq) {
-        /* XXX Not really sure what we should do with prerequisites for "arp"
-         * and "nd_na" actions. */
-        expr_destroy(inner_ctx.prereqs);
-        add_prerequisite(ctx, prereq);
-    } else {
-        /* For "clone", the inner prerequisites should just add to the outer
-         * ones. */
-        ctx->prereqs = expr_combine(EXPR_T_AND,
-                                    inner_ctx.prereqs, ctx->prereqs);
-    }
-
-    if (inner_ctx.lexer->error) {
-        ovnacts_free(nested.data, nested.size);
-        ofpbuf_uninit(&nested);
-        return;
-    }
-
-    struct ovnact_nest *on = ovnact_put(ctx->ovnacts, type,
-                                        OVNACT_ALIGN(sizeof *on));
-    on->nested_len = nested.size;
-    on->nested = ofpbuf_steal_data(&nested);
-}
-
-static void
-parse_ARP(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ARP, "ip4");
-}
-
-static void
-parse_ICMP4(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ICMP4, "ip4");
-}
-
-static void
-parse_ICMP4_ERROR(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ICMP4_ERROR, "ip4");
-}
-
-static void
-parse_ICMP6(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ICMP6, "ip6");
-}
-
-static void
-parse_TCP_RESET(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_TCP_RESET, "tcp");
-}
-
-static void
-parse_ND_NA(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ND_NA, "nd_ns");
-}
-
-static void
-parse_ND_NA_ROUTER(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ND_NA_ROUTER, "nd_ns");
-}
-
-static void
-parse_ND_NS(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_ND_NS, "ip6");
-}
-
-static void
-parse_CLONE(struct action_context *ctx)
-{
-    parse_nested_action(ctx, OVNACT_CLONE, NULL);
-}
-
-static void
-format_nested_action(const struct ovnact_nest *on, const char *name,
-                     struct ds *s)
-{
-    ds_put_format(s, "%s { ", name);
-    ovnacts_format(on->nested, on->nested_len, s);
-    ds_put_format(s, " };");
-}
-
-static void
-format_ARP(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "arp", s);
-}
-
-static void
-format_ICMP4(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "icmp4", s);
-}
-
-static void
-format_ICMP4_ERROR(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "icmp4_error", s);
-}
-
-static void
-format_ICMP6(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "icmp6", s);
-}
-
-static void
-format_IGMP(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
-{
-    ds_put_cstr(s, "igmp;");
-}
-
-static void
-format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "tcp_reset", s);
-}
-
-static void
-format_ND_NA(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "nd_na", s);
-}
-
-static void
-format_ND_NA_ROUTER(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "nd_na_router", s);
-}
-
-static void
-format_ND_NS(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "nd_ns", s);
-}
-
-static void
-format_CLONE(const struct ovnact_nest *nest, struct ds *s)
-{
-    format_nested_action(nest, "clone", s);
-}
-
-static void
-format_TRIGGER_EVENT(const struct ovnact_controller_event *event,
-                     struct ds *s)
-{
-    ds_put_format(s, "trigger_event(event = \"%s\"",
-                  event_to_string(event->event_type));
-    for (const struct ovnact_gen_option *o = event->options;
-         o < &event->options[event->n_options]; o++) {
-        ds_put_cstr(s, ", ");
-        ds_put_format(s, "%s = ", o->option->name);
-        expr_constant_set_format(&o->value, s);
-    }
-    ds_put_cstr(s, ");");
-}
-
-static void
-encode_nested_actions(const struct ovnact_nest *on,
-                      const struct ovnact_encode_params *ep,
-                      enum action_opcode opcode,
-                      struct ofpbuf *ofpacts)
-{
-    /* Convert nested actions into ofpacts. */
-    uint64_t inner_ofpacts_stub[1024 / 8];
-    struct ofpbuf inner_ofpacts = OFPBUF_STUB_INITIALIZER(inner_ofpacts_stub);
-    ovnacts_encode(on->nested, on->nested_len, ep, &inner_ofpacts);
-
-    /* Add a "controller" action with the actions nested inside "{...}",
-     * converted to OpenFlow, as its userdata.  ovn-controller will convert the
-     * packet to ARP or NA and then send the packet and actions back to the
-     * switch inside an OFPT_PACKET_OUT message. */
-    size_t oc_offset = encode_start_controller_op(opcode, false,
-                                                  NX_CTLR_NO_METER, ofpacts);
-    ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
-                                 ofpacts, OFP13_VERSION);
-    encode_finish_controller_op(oc_offset, ofpacts);
-
-    /* Free memory. */
-    ofpbuf_uninit(&inner_ofpacts);
-}
-
-static void
-encode_ARP(const struct ovnact_nest *on,
-           const struct ovnact_encode_params *ep,
-           struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ARP, ofpacts);
-}
-
-static void
-encode_ICMP4(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ICMP, ofpacts);
-}
-
-static void
-encode_ICMP4_ERROR(const struct ovnact_nest *on,
-                   const struct ovnact_encode_params *ep,
-                   struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ICMP4_ERROR, ofpacts);
-}
-
-static void
-encode_ICMP6(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ICMP, ofpacts);
-}
-
-static void
-encode_IGMP(const struct ovnact_null *a OVS_UNUSED,
-            const struct ovnact_encode_params *ep OVS_UNUSED,
-            struct ofpbuf *ofpacts)
-{
-    encode_controller_op(ACTION_OPCODE_IGMP, ofpacts);
-}
-
-static void
-encode_TCP_RESET(const struct ovnact_nest *on,
-                 const struct ovnact_encode_params *ep,
-                 struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts);
-}
-
-static void
-encode_ND_NA(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ND_NA, ofpacts);
-}
-
-static void
-encode_ND_NA_ROUTER(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ND_NA_ROUTER, ofpacts);
-}
-
-static void
-encode_ND_NS(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    encode_nested_actions(on, ep, ACTION_OPCODE_ND_NS, ofpacts);
-}
-
-static void
-encode_CLONE(const struct ovnact_nest *on,
-             const struct ovnact_encode_params *ep,
-             struct ofpbuf *ofpacts)
-{
-    size_t ofs = ofpacts->size;
-    ofpact_put_CLONE(ofpacts);
-    ovnacts_encode(on->nested, on->nested_len, ep, ofpacts);
-
-    struct ofpact_nest *clone = ofpbuf_at_assert(ofpacts, ofs, sizeof *clone);
-    ofpacts->header = clone;
-    ofpact_finish_CLONE(ofpacts, &clone);
-}
-
-static void
-encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
-        const struct ovnact_controller_event *event)
-{
-    for (const struct ovnact_gen_option *o = event->options;
-         o < &event->options[event->n_options]; o++) {
-        struct controller_event_opt_header *hdr =
-            ofpbuf_put_uninit(ofpacts, sizeof *hdr);
-        const union expr_constant *c = o->value.values;
-        size_t size;
-        hdr->opt_code = htons(o->option->code);
-        if (!strcmp(o->option->type, "str")) {
-            size = strlen(c->string);
-            hdr->size = htons(size);
-            ofpbuf_put(ofpacts, c->string, size);
-        } else {
-            /* All empty_lb_backends fields are of type 'str' */
-            OVS_NOT_REACHED();
-        }
-    }
-}
-
-static void
-encode_TRIGGER_EVENT(const struct ovnact_controller_event *event,
-                     const struct ovnact_encode_params *ep OVS_UNUSED,
-                     struct ofpbuf *ofpacts)
-{
-    size_t oc_offset;
-
-    oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false,
-                                           NX_CTLR_NO_METER, ofpacts);
-    ovs_be32 ofs = htonl(event->event_type);
-    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
-
-    switch (event->event_type) {
-    case OVN_EVENT_EMPTY_LB_BACKENDS:
-        encode_event_empty_lb_backends_opts(ofpacts, event);
-        break;
-    case OVN_EVENT_MAX:
-    default:
-        OVS_NOT_REACHED();
-    }
-
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-static void
-ovnact_nest_free(struct ovnact_nest *on)
-{
-    ovnacts_free(on->nested, on->nested_len);
-    free(on->nested);
-}
-
-static void
-parse_get_mac_bind(struct action_context *ctx, int width,
-                   struct ovnact_get_mac_bind *get_mac)
-{
-    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
-    action_parse_field(ctx, 0, false, &get_mac->port);
-    lexer_force_match(ctx->lexer, LEX_T_COMMA);
-    action_parse_field(ctx, width, false, &get_mac->ip);
-    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-}
-
-static void
-format_get_mac_bind(const struct ovnact_get_mac_bind *get_mac,
-                    const char *name, struct ds *s)
-{
-    ds_put_format(s, "%s(", name);
-    expr_field_format(&get_mac->port, s);
-    ds_put_cstr(s, ", ");
-    expr_field_format(&get_mac->ip, s);
-    ds_put_cstr(s, ");");
-}
-
-static void
-format_GET_ARP(const struct ovnact_get_mac_bind *get_mac, struct ds *s)
-{
-    format_get_mac_bind(get_mac, "get_arp", s);
-}
-
-static void
-format_GET_ND(const struct ovnact_get_mac_bind *get_mac, struct ds *s)
-{
-    format_get_mac_bind(get_mac, "get_nd", s);
-}
-
-static void
-encode_get_mac(const struct ovnact_get_mac_bind *get_mac,
-               enum mf_field_id ip_field,
-               const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
-{
-    const struct arg args[] = {
-        { expr_resolve_field(&get_mac->port), MFF_LOG_OUTPORT },
-        { expr_resolve_field(&get_mac->ip), ip_field },
-    };
-    encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-
-    put_load(0, MFF_ETH_DST, 0, 48, ofpacts);
-    emit_resubmit(ofpacts, ep->mac_bind_ptable);
-
-    encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
-}
-
-static void
-encode_GET_ARP(const struct ovnact_get_mac_bind *get_mac,
-               const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
-{
-    encode_get_mac(get_mac, MFF_REG0, ep, ofpacts);
-}
-
-static void
-encode_GET_ND(const struct ovnact_get_mac_bind *get_mac,
-              const struct ovnact_encode_params *ep,
-              struct ofpbuf *ofpacts)
-{
-    encode_get_mac(get_mac, MFF_XXREG0, ep, ofpacts);
-}
-
-static void
-ovnact_get_mac_bind_free(struct ovnact_get_mac_bind *get_mac OVS_UNUSED)
-{
-}
-
-static void
-parse_put_mac_bind(struct action_context *ctx, int width,
-                   struct ovnact_put_mac_bind *put_mac)
-{
-    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
-    action_parse_field(ctx, 0, false, &put_mac->port);
-    lexer_force_match(ctx->lexer, LEX_T_COMMA);
-    action_parse_field(ctx, width, false, &put_mac->ip);
-    lexer_force_match(ctx->lexer, LEX_T_COMMA);
-    action_parse_field(ctx, 48, false, &put_mac->mac);
-    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-}
-
-static void
-format_put_mac_bind(const struct ovnact_put_mac_bind *put_mac,
-                    const char *name, struct ds *s)
-{
-    ds_put_format(s, "%s(", name);
-    expr_field_format(&put_mac->port, s);
-    ds_put_cstr(s, ", ");
-    expr_field_format(&put_mac->ip, s);
-    ds_put_cstr(s, ", ");
-    expr_field_format(&put_mac->mac, s);
-    ds_put_cstr(s, ");");
-}
-
-static void
-format_PUT_ARP(const struct ovnact_put_mac_bind *put_mac, struct ds *s)
-{
-    format_put_mac_bind(put_mac, "put_arp", s);
-}
-
-static void
-format_PUT_ND(const struct ovnact_put_mac_bind *put_mac, struct ds *s)
-{
-    format_put_mac_bind(put_mac, "put_nd", s);
-}
-
-static void
-encode_put_mac(const struct ovnact_put_mac_bind *put_mac,
-               enum mf_field_id ip_field, enum action_opcode opcode,
-               struct ofpbuf *ofpacts)
-{
-    const struct arg args[] = {
-        { expr_resolve_field(&put_mac->port), MFF_LOG_INPORT },
-        { expr_resolve_field(&put_mac->ip), ip_field },
-        { expr_resolve_field(&put_mac->mac), MFF_ETH_SRC }
-    };
-    encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-    encode_controller_op(opcode, ofpacts);
-    encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
-}
-
-static void
-encode_PUT_ARP(const struct ovnact_put_mac_bind *put_mac,
-               const struct ovnact_encode_params *ep OVS_UNUSED,
-               struct ofpbuf *ofpacts)
-{
-    encode_put_mac(put_mac, MFF_REG0, ACTION_OPCODE_PUT_ARP, ofpacts);
-}
-
-static void
-encode_PUT_ND(const struct ovnact_put_mac_bind *put_mac,
-              const struct ovnact_encode_params *ep OVS_UNUSED,
-              struct ofpbuf *ofpacts)
-{
-    encode_put_mac(put_mac, MFF_XXREG0, ACTION_OPCODE_PUT_ND, ofpacts);
-}
-
-static void
-ovnact_put_mac_bind_free(struct ovnact_put_mac_bind *put_mac OVS_UNUSED)
-{
-}
-
-static void
-parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
-              const struct hmap *gen_opts, const char *opts_type)
-{
-    if (ctx->lexer->token.type != LEX_T_ID) {
-        lexer_syntax_error(ctx->lexer, NULL);
-        return;
-    }
-
-    o->option = gen_opts ? gen_opts_find(gen_opts, ctx->lexer->token.s) : NULL;
-    if (!o->option) {
-        lexer_syntax_error(ctx->lexer, "expecting %s option name", opts_type);
-        return;
-    }
-    lexer_get(ctx->lexer);
-
-    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-        return;
-    }
-
-    if (!expr_constant_set_parse(ctx->lexer, &o->value)) {
-        memset(&o->value, 0, sizeof o->value);
-        return;
-    }
-
-    if (!strcmp(o->option->type, "str")) {
-        if (o->value.type != EXPR_C_STRING) {
-            lexer_error(ctx->lexer, "%s option %s requires string value.",
-                        opts_type, o->option->name);
-            return;
-        }
-    } else {
-        if (o->value.type != EXPR_C_INTEGER) {
-            lexer_error(ctx->lexer, "%s option %s requires numeric value.",
-                        opts_type, o->option->name);
-            return;
-        }
-    }
-}
-
-static const struct ovnact_gen_option *
-find_offerip(const struct ovnact_gen_option *options, size_t n)
-{
-    for (const struct ovnact_gen_option *o = options; o < &options[n]; o++) {
-        if (o->option->code == 0) {
-            return o;
-        }
-    }
-    return NULL;
-}
-
-static void
-free_gen_options(struct ovnact_gen_option *options, size_t n)
-{
-    for (struct ovnact_gen_option *o = options; o < &options[n]; o++) {
-        expr_constant_set_destroy(&o->value);
-    }
-    free(options);
-}
-
-static void
-validate_empty_lb_backends(struct action_context *ctx,
-                           const struct ovnact_gen_option *options,
-                           size_t n_options)
-{
-    for (const struct ovnact_gen_option *o = options;
-         o < &options[n_options]; o++) {
-        const union expr_constant *c = o->value.values;
-        struct sockaddr_storage ss;
-        struct uuid uuid;
-
-        if (o->value.n_values > 1 || !c->string) {
-            lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
-                        o->option->name);
-            return;
-        }
-
-        switch (o->option->code) {
-        case EMPTY_LB_VIP:
-            if (!inet_parse_active(c->string, 0, &ss, false)) {
-                lexer_error(ctx->lexer, "Invalid load balancer VIP '%s'",
-                            c->string);
-                return;
-            }
-            break;
-        case EMPTY_LB_PROTOCOL:
-            if (strcmp(c->string, "tcp") && strcmp(c->string, "udp")) {
-                lexer_error(ctx->lexer,
-                    "Load balancer protocol '%s' is not 'tcp' or 'udp'",
-                    c->string);
-                return;
-            }
-            break;
-        case EMPTY_LB_LOAD_BALANCER:
-            if (!uuid_from_string(&uuid, c->string)) {
-                lexer_error(ctx->lexer, "Load balancer '%s' is not a UUID",
-                            c->string);
-                return;
-            }
-            break;
-        }
-    }
-}
-
-static void
-parse_trigger_event(struct action_context *ctx,
-                    struct ovnact_controller_event *event)
-{
-    int event_type = 0;
-
-    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
-
-    /* Event type must be listed first */
-    if (!lexer_match_id(ctx->lexer, "event")) {
-        lexer_syntax_error(ctx->lexer, "Expecting 'event' option");
-        return;
-    }
-    if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-        return;
-    }
-
-    if (ctx->lexer->token.type != LEX_T_STRING ||
-        strlen(ctx->lexer->token.s) >= 64) {
-        lexer_syntax_error(ctx->lexer, "Expecting string");
-        return;
-    }
-
-    event_type = string_to_event(ctx->lexer->token.s);
-    if (event_type < 0 || event_type >= OVN_EVENT_MAX) {
-        lexer_syntax_error(ctx->lexer, "Unknown event '%d'", event_type);
-        return;
-    }
-
-    event->event_type = event_type;
-    lexer_get(ctx->lexer);
-
-    lexer_match(ctx->lexer, LEX_T_COMMA);
-
-    size_t allocated_options = 0;
-    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-        if (event->n_options >= allocated_options) {
-            event->options = x2nrealloc(event->options, &allocated_options,
-                                     sizeof *event->options);
-        }
-
-        struct ovnact_gen_option *o = &event->options[event->n_options++];
-        memset(o, 0, sizeof *o);
-        parse_gen_opt(ctx, o,
-                      &ctx->pp->controller_event_opts->event_opts[event_type],
-                      event_to_string(event_type));
-        if (ctx->lexer->error) {
-            return;
-        }
-
-        lexer_match(ctx->lexer, LEX_T_COMMA);
-    }
-
-    switch (event_type) {
-    case OVN_EVENT_EMPTY_LB_BACKENDS:
-        validate_empty_lb_backends(ctx, event->options, event->n_options);
-        break;
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static void
-ovnact_controller_event_free(struct ovnact_controller_event *event OVS_UNUSED)
-{
-}
-
-static void
-parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
-               struct ovnact_put_opts *po, const struct hmap *gen_opts,
-               const char *opts_type)
-{
-    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts / put_nd_ra_opts. */
-    lexer_get(ctx->lexer); /* Skip '('. */
-
-    /* Validate that the destination is a 1-bit, modifiable field. */
-    char *error = expr_type_check(dst, 1, true);
-    if (error) {
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return;
-    }
-    po->dst = *dst;
-
-    size_t allocated_options = 0;
-    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-        if (po->n_options >= allocated_options) {
-            po->options = x2nrealloc(po->options, &allocated_options,
-                                     sizeof *po->options);
-        }
-
-        struct ovnact_gen_option *o = &po->options[po->n_options++];
-        memset(o, 0, sizeof *o);
-        parse_gen_opt(ctx, o, gen_opts, opts_type);
-        if (ctx->lexer->error) {
-            return;
-        }
-
-        lexer_match(ctx->lexer, LEX_T_COMMA);
-    }
-}
-
-/* Parses the "put_dhcp_opts" and "put_dhcpv6_opts" actions.
- *
- * The caller has already consumed "<dst> =", so this just parses the rest. */
-static void
-parse_put_dhcp_opts(struct action_context *ctx,
-                    const struct expr_field *dst,
-                    struct ovnact_put_opts *po)
-{
-    const struct hmap *dhcp_opts =
-        (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ?
-            ctx->pp->dhcpv6_opts : ctx->pp->dhcp_opts;
-    const char *opts_type =
-        (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ? "DHCPv6" : "DHCPv4";
-
-    parse_put_opts(ctx, dst, po, dhcp_opts, opts_type);
-
-    if (!ctx->lexer->error && po->ovnact.type == OVNACT_PUT_DHCPV4_OPTS
-        && !find_offerip(po->options, po->n_options)) {
-        lexer_error(ctx->lexer,
-                    "put_dhcp_opts requires offerip to be specified.");
-        return;
-    }
-}
-
-static void
-format_put_opts(const char *name, const struct ovnact_put_opts *pdo,
-                struct ds *s)
-{
-    expr_field_format(&pdo->dst, s);
-    ds_put_format(s, " = %s(", name);
-    for (const struct ovnact_gen_option *o = pdo->options;
-         o < &pdo->options[pdo->n_options]; o++) {
-        if (o != pdo->options) {
-            ds_put_cstr(s, ", ");
-        }
-        ds_put_format(s, "%s = ", o->option->name);
-        expr_constant_set_format(&o->value, s);
-    }
-    ds_put_cstr(s, ");");
-}
-
-static void
-format_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo, struct ds *s)
-{
-    format_put_opts("put_dhcp_opts", pdo, s);
-}
-
-static void
-format_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo, struct ds *s)
-{
-    format_put_opts("put_dhcpv6_opts", pdo, s);
-}
-
-static void
-encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
-                         struct ofpbuf *ofpacts)
-{
-    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
-    opt_header[0] = o->option->code;
-
-    const union expr_constant *c = o->value.values;
-    size_t n_values = o->value.n_values;
-    if (!strcmp(o->option->type, "bool") ||
-        !strcmp(o->option->type, "uint8")) {
-        opt_header[1] = 1;
-        ofpbuf_put(ofpacts, &c->value.u8_val, 1);
-    } else if (!strcmp(o->option->type, "uint16")) {
-        opt_header[1] = 2;
-        ofpbuf_put(ofpacts, &c->value.be16_int, 2);
-    } else if (!strcmp(o->option->type, "uint32")) {
-        opt_header[1] = 4;
-        ofpbuf_put(ofpacts, &c->value.be32_int, 4);
-    } else if (!strcmp(o->option->type, "ipv4")) {
-        opt_header[1] = n_values * sizeof(ovs_be32);
-        for (size_t i = 0; i < n_values; i++) {
-            ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32));
-        }
-    } else if (!strcmp(o->option->type, "static_routes")) {
-        size_t no_of_routes = n_values;
-        if (no_of_routes % 2) {
-            no_of_routes -= 1;
-        }
-        opt_header[1] = 0;
-
-        /* Calculating the length of this option first because when
-         * we call ofpbuf_put, it might reallocate the buffer if the
-         * tail room is short making "opt_header" pointer invalid.
-         * So running the for loop twice.
-         */
-        for (size_t i = 0; i < no_of_routes; i += 2) {
-            uint8_t plen = 32;
-            if (c[i].masked) {
-                plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4);
-            }
-            opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
-        }
-
-        /* Copied from RFC 3442. Please refer to this RFC for the format of
-         * the classless static route option.
-         *
-         *  The following table contains some examples of how various subnet
-         *  number/mask combinations can be encoded:
-         *
-         *  Subnet number   Subnet mask      Destination descriptor
-         *  0               0                0
-         *  10.0.0.0        255.0.0.0        8.10
-         *  10.0.0.0        255.255.255.0    24.10.0.0
-         *  10.17.0.0       255.255.0.0      16.10.17
-         *  10.27.129.0     255.255.255.0    24.10.27.129
-         *  10.229.0.128    255.255.255.128  25.10.229.0.128
-         *  10.198.122.47   255.255.255.255  32.10.198.122.47
-         */
-
-        for (size_t i = 0; i < no_of_routes; i += 2) {
-            uint8_t plen = 32;
-            if (c[i].masked) {
-                plen = ip_count_cidr_bits(c[i].mask.ipv4);
-            }
-            ofpbuf_put(ofpacts, &plen, 1);
-            ofpbuf_put(ofpacts, &c[i].value.ipv4, DIV_ROUND_UP(plen, 8));
-            ofpbuf_put(ofpacts, &c[i + 1].value.ipv4,
-                       sizeof(ovs_be32));
-        }
-    } else if (!strcmp(o->option->type, "str")) {
-        opt_header[1] = strlen(c->string);
-        ofpbuf_put(ofpacts, c->string, opt_header[1]);
-    }
-}
-
-static void
-encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
-                         struct ofpbuf *ofpacts)
-{
-    struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt);
-    const union expr_constant *c = o->value.values;
-    size_t n_values = o->value.n_values;
-    size_t size;
-
-    opt->opt_code = htons(o->option->code);
-
-    if (!strcmp(o->option->type, "ipv6")) {
-        size = n_values * sizeof(struct in6_addr);
-        opt->size = htons(size);
-        for (size_t i = 0; i < n_values; i++) {
-            ofpbuf_put(ofpacts, &c[i].value.ipv6, sizeof(struct in6_addr));
-        }
-    } else if (!strcmp(o->option->type, "mac")) {
-        size = sizeof(struct eth_addr);
-        opt->size = htons(size);
-        ofpbuf_put(ofpacts, &c->value.mac, size);
-    } else if (!strcmp(o->option->type, "str")) {
-        size = strlen(c->string);
-        opt->size = htons(size);
-        ofpbuf_put(ofpacts, c->string, size);
-    }
-}
-
-static void
-encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
-                       const struct ovnact_encode_params *ep OVS_UNUSED,
-                       struct ofpbuf *ofpacts)
-{
-    struct mf_subfield dst = expr_resolve_field(&pdo->dst);
-
-    size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_PUT_DHCP_OPTS,
-                                                  true, NX_CTLR_NO_METER,
-                                                  ofpacts);
-    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
-    ovs_be32 ofs = htonl(dst.ofs);
-    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
-
-    /* Encode the offerip option first, because it's a special case and needs
-     * to be first in the actual DHCP response, and then encode the rest
-     * (skipping offerip the second time around). */
-    const struct ovnact_gen_option *offerip_opt = find_offerip(
-        pdo->options, pdo->n_options);
-    ovs_be32 offerip = offerip_opt->value.values[0].value.ipv4;
-    ofpbuf_put(ofpacts, &offerip, sizeof offerip);
-
-    for (const struct ovnact_gen_option *o = pdo->options;
-         o < &pdo->options[pdo->n_options]; o++) {
-        if (o != offerip_opt) {
-            encode_put_dhcpv4_option(o, ofpacts);
-        }
-    }
-
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-static void
-encode_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo,
-                       const struct ovnact_encode_params *ep OVS_UNUSED,
-                       struct ofpbuf *ofpacts)
-{
-    struct mf_subfield dst = expr_resolve_field(&pdo->dst);
-
-    size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_DHCPV6_OPTS, true, NX_CTLR_NO_METER, ofpacts);
-    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
-    ovs_be32 ofs = htonl(dst.ofs);
-    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
-
-    for (const struct ovnact_gen_option *o = pdo->options;
-         o < &pdo->options[pdo->n_options]; o++) {
-        encode_put_dhcpv6_option(o, ofpacts);
-    }
-
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-static void
-ovnact_put_opts_free(struct ovnact_put_opts *pdo)
-{
-    free_gen_options(pdo->options, pdo->n_options);
-}
-
-static void
-parse_SET_QUEUE(struct action_context *ctx)
-{
-    int queue_id;
-
-    if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)
-        || !lexer_get_int(ctx->lexer, &queue_id)
-        || !lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-        return;
-    }
-
-    if (queue_id < QDISC_MIN_QUEUE_ID || queue_id > QDISC_MAX_QUEUE_ID) {
-        lexer_error(ctx->lexer, "Queue ID %d for set_queue is "
-                    "not in valid range %d to %d.",
-                    queue_id, QDISC_MIN_QUEUE_ID, QDISC_MAX_QUEUE_ID);
-        return;
-    }
-
-    ovnact_put_SET_QUEUE(ctx->ovnacts)->queue_id = queue_id;
-}
-
-static void
-format_SET_QUEUE(const struct ovnact_set_queue *set_queue, struct ds *s)
-{
-    ds_put_format(s, "set_queue(%d);", set_queue->queue_id);
-}
-
-static void
-encode_SET_QUEUE(const struct ovnact_set_queue *set_queue,
-                 const struct ovnact_encode_params *ep OVS_UNUSED,
-                 struct ofpbuf *ofpacts)
-{
-    ofpact_put_SET_QUEUE(ofpacts)->queue_id = set_queue->queue_id;
-}
-
-static void
-ovnact_set_queue_free(struct ovnact_set_queue *a OVS_UNUSED)
-{
-}
-
-static void
-parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst,
-                 struct ovnact_dns_lookup *dl)
-{
-    lexer_get(ctx->lexer); /* Skip dns_lookup. */
-    lexer_get(ctx->lexer); /* Skip '('. */
-    if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-        lexer_error(ctx->lexer, "dns_lookup doesn't take any parameters");
-        return;
-    }
-    /* Validate that the destination is a 1-bit, modifiable field. */
-    char *error = expr_type_check(dst, 1, true);
-    if (error) {
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return;
-    }
-    dl->dst = *dst;
-    add_prerequisite(ctx, "udp");
-}
-
-static void
-format_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, struct ds *s)
-{
-    expr_field_format(&dl->dst, s);
-    ds_put_cstr(s, " = dns_lookup();");
-}
-
-static void
-encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl,
-                  const struct ovnact_encode_params *ep OVS_UNUSED,
-                  struct ofpbuf *ofpacts)
-{
-    struct mf_subfield dst = expr_resolve_field(&dl->dst);
-
-    size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DNS_LOOKUP,
-                                                  true, NX_CTLR_NO_METER,
-                                                  ofpacts);
-    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
-    ovs_be32 ofs = htonl(dst.ofs);
-    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-
-static void
-ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
-{
-}
-
-/* Parses the "put_nd_ra_opts" action.
- * The caller has already consumed "<dst> =", so this just parses the rest. */
-static void
-parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
-                     struct ovnact_put_opts *po)
-{
-    parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA");
-
-    if (ctx->lexer->error) {
-        return;
-    }
-
-    bool addr_mode_stateful = false;
-    bool prefix_set = false;
-    bool slla_present = false;
-    /* Let's validate the options. */
-    for (struct ovnact_gen_option *o = po->options;
-            o < &po->options[po->n_options]; o++) {
-        const union expr_constant *c = o->value.values;
-        if (o->value.n_values > 1) {
-            lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
-                        o->option->name);
-            return;
-        }
-
-        bool ok = true;
-        switch (o->option->code) {
-        case ND_RA_FLAG_ADDR_MODE:
-            ok = (c->string && (!strcmp(c->string, "slaac") ||
-                                !strcmp(c->string, "dhcpv6_stateful") ||
-                                !strcmp(c->string, "dhcpv6_stateless")));
-            if (ok && !strcmp(c->string, "dhcpv6_stateful")) {
-                addr_mode_stateful = true;
-            }
-            break;
-
-        case ND_OPT_SOURCE_LINKADDR:
-            ok = c->format == LEX_F_ETHERNET;
-            slla_present = true;
-            break;
-
-        case ND_OPT_PREFIX_INFORMATION:
-            ok = c->format == LEX_F_IPV6 && c->masked;
-            prefix_set = true;
-            break;
-
-        case ND_OPT_MTU:
-            ok = c->format == LEX_F_DECIMAL;
-            break;
-        }
-
-        if (!ok) {
-            lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
-                        o->option->name);
-            return;
-        }
-    }
-
-    if (!slla_present) {
-        lexer_error(ctx->lexer, "slla option not present");
-        return;
-    }
-
-    if (!addr_mode_stateful && !prefix_set) {
-        lexer_error(ctx->lexer, "prefix option needs "
-                    "to be set when address mode is slaac/dhcpv6_stateless.");
-        return;
-    }
-
-    add_prerequisite(ctx, "ip6");
-}
-
-static void
-format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
-                      struct ds *s)
-{
-    format_put_opts("put_nd_ra_opts", po, s);
-}
-
-static void
-encode_put_nd_ra_option(const struct ovnact_gen_option *o,
-                        struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
-{
-    const union expr_constant *c = o->value.values;
-
-    switch (o->option->code) {
-    case ND_RA_FLAG_ADDR_MODE:
-    {
-        struct ovs_ra_msg *ra = ofpbuf_at(ofpacts, ra_offset, sizeof *ra);
-        if (!strcmp(c->string, "dhcpv6_stateful")) {
-            ra->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
-        } else if (!strcmp(c->string, "dhcpv6_stateless")) {
-            ra->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
-        }
-        break;
-    }
-
-    case ND_OPT_SOURCE_LINKADDR:
-    {
-        struct ovs_nd_lla_opt *lla_opt =
-            ofpbuf_put_uninit(ofpacts, sizeof *lla_opt);
-        lla_opt->type = ND_OPT_SOURCE_LINKADDR;
-        lla_opt->len = 1;
-        lla_opt->mac = c->value.mac;
-        break;
-    }
-
-    case ND_OPT_MTU:
-    {
-        struct ovs_nd_mtu_opt *mtu_opt =
-            ofpbuf_put_uninit(ofpacts, sizeof *mtu_opt);
-        mtu_opt->type = ND_OPT_MTU;
-        mtu_opt->len = 1;
-        mtu_opt->reserved = 0;
-        put_16aligned_be32(&mtu_opt->mtu, c->value.be32_int);
-        break;
-    }
-
-    case ND_OPT_PREFIX_INFORMATION:
-    {
-        struct ovs_nd_prefix_opt *prefix_opt =
-            ofpbuf_put_uninit(ofpacts, sizeof *prefix_opt);
-        uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6);
-        struct ovs_ra_msg *ra = ofpbuf_at(ofpacts, ra_offset, sizeof *ra);
-        prefix_opt->type = ND_OPT_PREFIX_INFORMATION;
-        prefix_opt->len = 4;
-        prefix_opt->prefix_len = prefix_len;
-        prefix_opt->la_flags = IPV6_ND_RA_OPT_PREFIX_ON_LINK;
-        if (!(ra->mo_flags & IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG)) {
-            prefix_opt->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS;
-        }
-        put_16aligned_be32(&prefix_opt->valid_lifetime,
-                           htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME));
-        put_16aligned_be32(&prefix_opt->preferred_lifetime,
-                           htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME));
-        put_16aligned_be32(&prefix_opt->reserved, 0);
-        memcpy(prefix_opt->prefix.be32, &c->value.be128[7].be32,
-               sizeof(ovs_be32[4]));
-        break;
-    }
-    }
-}
-
-static void
-encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
-                      const struct ovnact_encode_params *ep OVS_UNUSED,
-                      struct ofpbuf *ofpacts)
-{
-    struct mf_subfield dst = expr_resolve_field(&po->dst);
-
-    size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_ND_RA_OPTS, true, NX_CTLR_NO_METER, ofpacts);
-    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
-    ovs_be32 ofs = htonl(dst.ofs);
-    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
-
-    /* Frame the complete ICMPv6 Router Advertisement data encoding
-     * the ND RA options in it, in the userdata field, so that when
-     * pinctrl module receives the ICMPv6 Router Solicitation packet
-     * it can copy the userdata field AS IS and resume the packet.
-     */
-    size_t ra_offset = ofpacts->size;
-    struct ovs_ra_msg *ra = ofpbuf_put_zeros(ofpacts, sizeof *ra);
-    ra->icmph.icmp6_type = ND_ROUTER_ADVERT;
-    ra->cur_hop_limit = IPV6_ND_RA_CUR_HOP_LIMIT;
-    ra->mo_flags = 0;
-    ra->router_lifetime = htons(IPV6_ND_RA_LIFETIME);
-
-    for (const struct ovnact_gen_option *o = po->options;
-         o < &po->options[po->n_options]; o++) {
-        encode_put_nd_ra_option(o, ofpacts, ra_offset);
-    }
-
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-
-static void
-parse_log_arg(struct action_context *ctx, struct ovnact_log *log)
-{
-    if (lexer_match_id(ctx->lexer, "verdict")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        if (lexer_match_id(ctx->lexer, "drop")) {
-            log->verdict = LOG_VERDICT_DROP;
-        } else if (lexer_match_id(ctx->lexer, "reject")) {
-            log->verdict = LOG_VERDICT_REJECT;
-        } else if (lexer_match_id(ctx->lexer, "allow")) {
-            log->verdict = LOG_VERDICT_ALLOW;
-        } else {
-            lexer_syntax_error(ctx->lexer, "unknown verdict");
-            return;
-        }
-    } else if (lexer_match_id(ctx->lexer, "name")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        /* If multiple names are given, use the most recent. */
-        if (ctx->lexer->token.type == LEX_T_STRING) {
-            /* Arbitrarily limit the name length to 64 bytes, since
-             * these will be encoded in datapath actions. */
-            if (strlen(ctx->lexer->token.s) >= 64) {
-                lexer_syntax_error(ctx->lexer, "name must be shorter "
-                                               "than 64 characters");
-                return;
-            }
-            free(log->name);
-            log->name = xstrdup(ctx->lexer->token.s);
-        } else {
-            lexer_syntax_error(ctx->lexer, "expecting string");
-            return;
-        }
-        lexer_get(ctx->lexer);
-    } else if (lexer_match_id(ctx->lexer, "severity")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        if (ctx->lexer->token.type == LEX_T_ID) {
-            uint8_t severity = log_severity_from_string(ctx->lexer->token.s);
-            if (severity != UINT8_MAX) {
-                log->severity = severity;
-                lexer_get(ctx->lexer);
-                return;
-            } else {
-                lexer_syntax_error(ctx->lexer, "unknown severity");
-                return;
-            }
-        }
-        lexer_syntax_error(ctx->lexer, "expecting severity");
-    } else if (lexer_match_id(ctx->lexer, "meter")) {
-        if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-            return;
-        }
-        /* If multiple meters are given, use the most recent. */
-        if (ctx->lexer->token.type == LEX_T_STRING) {
-            free(log->meter);
-            log->meter = xstrdup(ctx->lexer->token.s);
-        } else {
-            lexer_syntax_error(ctx->lexer, "expecting string");
-            return;
-        }
-        lexer_get(ctx->lexer);
-    } else {
-        lexer_syntax_error(ctx->lexer, NULL);
-    }
-}
-
-static void
-parse_LOG(struct action_context *ctx)
-{
-    struct ovnact_log *log = ovnact_put_LOG(ctx->ovnacts);
-
-    /* Provide default values. */
-    log->severity = LOG_SEVERITY_INFO;
-    log->verdict = LOG_VERDICT_UNKNOWN;
-
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
-            parse_log_arg(ctx, log);
-            if (ctx->lexer->error) {
-                return;
-            }
-            lexer_match(ctx->lexer, LEX_T_COMMA);
-        }
-    }
-    if (log->verdict == LOG_VERDICT_UNKNOWN) {
-        lexer_syntax_error(ctx->lexer, "expecting verdict");
-    }
-}
-
-static void
-format_LOG(const struct ovnact_log *log, struct ds *s)
-{
-    ds_put_cstr(s, "log(");
-
-    if (log->name) {
-        ds_put_format(s, "name=\"%s\", ", log->name);
-    }
-
-    ds_put_format(s, "verdict=%s, ", log_verdict_to_string(log->verdict));
-    ds_put_format(s, "severity=%s", log_severity_to_string(log->severity));
-
-    if (log->meter) {
-        ds_put_format(s, ", meter=\"%s\"", log->meter);
-    }
-
-    ds_put_cstr(s, ");");
-}
-
-static void
-encode_LOG(const struct ovnact_log *log,
-           const struct ovnact_encode_params *ep, struct ofpbuf *ofpacts)
-{
-    uint32_t meter_id = NX_CTLR_NO_METER;
-
-    if (log->meter) {
-        meter_id = ovn_extend_table_assign_id(ep->meter_table, log->meter,
-                                              ep->lflow_uuid);
-        if (meter_id == EXT_TABLE_ID_INVALID) {
-            VLOG_WARN("Unable to assign id for log meter: %s", log->meter);
-            return;
-        }
-    }
-
-    size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_LOG, false,
-                                                  meter_id, ofpacts);
-
-    struct log_pin_header *lph = ofpbuf_put_uninit(ofpacts, sizeof *lph);
-    lph->verdict = log->verdict;
-    lph->severity = log->severity;
-
-    if (log->name) {
-        int name_len = strlen(log->name);
-        ofpbuf_put(ofpacts, log->name, name_len);
-    }
-
-    encode_finish_controller_op(oc_offset, ofpacts);
-}
-
-static void
-ovnact_log_free(struct ovnact_log *log)
-{
-    free(log->name);
-    free(log->meter);
-}
-
-static void
-parse_set_meter_action(struct action_context *ctx)
-{
-    uint64_t rate = 0;
-    uint64_t burst = 0;
-
-    lexer_force_match(ctx->lexer, LEX_T_LPAREN); /* Skip '('. */
-    if (ctx->lexer->token.type == LEX_T_INTEGER
-        && ctx->lexer->token.format == LEX_F_DECIMAL) {
-        rate = ntohll(ctx->lexer->token.value.integer);
-    }
-    lexer_get(ctx->lexer);
-    if (lexer_match(ctx->lexer, LEX_T_COMMA)) {  /* Skip ','. */
-        if (ctx->lexer->token.type == LEX_T_INTEGER
-            && ctx->lexer->token.format == LEX_F_DECIMAL) {
-            burst = ntohll(ctx->lexer->token.value.integer);
-        }
-        lexer_get(ctx->lexer);
-    }
-    lexer_force_match(ctx->lexer, LEX_T_RPAREN); /* Skip ')'. */
-
-    if (!rate) {
-        lexer_error(ctx->lexer,
-                    "Rate %"PRId64" for set_meter is not in valid.",
-                    rate);
-        return;
-    }
-
-    struct ovnact_set_meter *cl = ovnact_put_SET_METER(ctx->ovnacts);
-    cl->rate = rate;
-    cl->burst = burst;
-}
-
-static void
-format_SET_METER(const struct ovnact_set_meter *cl, struct ds *s)
-{
-    if (cl->burst) {
-        ds_put_format(s, "set_meter(%"PRId64", %"PRId64");",
-                      cl->rate, cl->burst);
-    } else {
-        ds_put_format(s, "set_meter(%"PRId64");", cl->rate);
-    }
-}
-
-static void
-encode_SET_METER(const struct ovnact_set_meter *cl,
-                 const struct ovnact_encode_params *ep,
-                 struct ofpbuf *ofpacts)
-{
-    uint32_t table_id;
-    struct ofpact_meter *om;
-
-    /* Use the special "__string:" prefix to indicate that the name
-     * describes the meter itself. */
-    char *name;
-    if (cl->burst) {
-        name = xasprintf("__string: kbps burst stats bands=type=drop "
-                         "rate=%"PRId64" burst_size=%"PRId64"", cl->rate,
-                         cl->burst);
-    } else {
-        name = xasprintf("__string: kbps stats bands=type=drop "
-                         "rate=%"PRId64"", cl->rate);
-    }
-
-    table_id = ovn_extend_table_assign_id(ep->meter_table, name,
-                                          ep->lflow_uuid);
-    free(name);
-    if (table_id == EXT_TABLE_ID_INVALID) {
-        return;
-    }
-
-    /* Create an action to set the meter. */
-    om = ofpact_put_METER(ofpacts);
-    om->meter_id = table_id;
-}
-
-static void
-ovnact_set_meter_free(struct ovnact_set_meter *ct OVS_UNUSED)
-{
-}
-
-static void
-format_OVNFIELD_LOAD(const struct ovnact_load *load , struct ds *s)
-{
-    const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name);
-    switch (f->id) {
-    case OVN_ICMP4_FRAG_MTU:
-        ds_put_format(s, "%s = %u;", f->name,
-                      ntohs(load->imm.value.be16_int));
-        break;
-
-    case OVN_FIELD_N_IDS:
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static void
-encode_OVNFIELD_LOAD(const struct ovnact_load *load,
-            const struct ovnact_encode_params *ep OVS_UNUSED,
-            struct ofpbuf *ofpacts)
-{
-    const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name);
-    switch (f->id) {
-    case OVN_ICMP4_FRAG_MTU: {
-        size_t oc_offset = encode_start_controller_op(
-            ACTION_OPCODE_PUT_ICMP4_FRAG_MTU, true, NX_CTLR_NO_METER,
-            ofpacts);
-        ofpbuf_put(ofpacts, &load->imm.value.be16_int, sizeof(ovs_be16));
-        encode_finish_controller_op(oc_offset, ofpacts);
-        break;
-    }
-    case OVN_FIELD_N_IDS:
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static void
-parse_check_pkt_larger(struct action_context *ctx,
-                       const struct expr_field *dst,
-                       struct ovnact_check_pkt_larger *cipl)
-{
-     /* Validate that the destination is a 1-bit, modifiable field. */
-    char *error = expr_type_check(dst, 1, true);
-    if (error) {
-        lexer_error(ctx->lexer, "%s", error);
-        free(error);
-        return;
-    }
-
-    int pkt_len;
-    lexer_get(ctx->lexer); /* Skip check_pkt_len. */
-    if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)
-        || !lexer_get_int(ctx->lexer, &pkt_len)
-        || !lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-        return;
-    }
-
-    cipl->dst = *dst;
-    cipl->pkt_len = pkt_len;
-}
-
-static void
-format_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl,
-                        struct ds *s)
-{
-    expr_field_format(&cipl->dst, s);
-    ds_put_format(s, " = check_pkt_larger(%d);", cipl->pkt_len);
-}
-
-static void
-encode_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl,
-                        const struct ovnact_encode_params *ep OVS_UNUSED,
-                        struct ofpbuf *ofpacts)
-{
-    struct ofpact_check_pkt_larger *check_pkt_larger =
-        ofpact_put_CHECK_PKT_LARGER(ofpacts);
-    check_pkt_larger->pkt_len = cipl->pkt_len;
-    check_pkt_larger->dst = expr_resolve_field(&cipl->dst);
-}
-
-static void
-ovnact_check_pkt_larger_free(struct ovnact_check_pkt_larger *cipl OVS_UNUSED)
-{
-}
-
-/* Parses an assignment or exchange or put_dhcp_opts action. */
-static void
-parse_set_action(struct action_context *ctx)
-{
-    struct expr_field lhs;
-    if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, &lhs, &ctx->prereqs)) {
-        return;
-    }
-
-    if (lexer_match(ctx->lexer, LEX_T_EXCHANGE)) {
-        parse_assignment_action(ctx, true, &lhs);
-    } else if (lexer_match(ctx->lexer, LEX_T_EQUALS)) {
-        if (ctx->lexer->token.type != LEX_T_ID) {
-            parse_LOAD(ctx, &lhs);
-        } else if (!strcmp(ctx->lexer->token.s, "put_dhcp_opts")
-                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            parse_put_dhcp_opts(ctx, &lhs, ovnact_put_PUT_DHCPV4_OPTS(
-                                    ctx->ovnacts));
-        } else if (!strcmp(ctx->lexer->token.s, "put_dhcpv6_opts")
-                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            parse_put_dhcp_opts(ctx, &lhs, ovnact_put_PUT_DHCPV6_OPTS(
-                                    ctx->ovnacts));
-        } else if (!strcmp(ctx->lexer->token.s, "dns_lookup")
-                   && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts));
-        } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts")
-                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            parse_put_nd_ra_opts(ctx, &lhs,
-                                 ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts));
-        } else if (!strcmp(ctx->lexer->token.s, "check_pkt_larger")
-                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            parse_check_pkt_larger(ctx, &lhs,
-                                   ovnact_put_CHECK_PKT_LARGER(ctx->ovnacts));
-        } else {
-            parse_assignment_action(ctx, false, &lhs);
-        }
-    } else {
-        lexer_syntax_error(ctx->lexer, "expecting `=' or `<->'");
-    }
-}
-
-static bool
-parse_action(struct action_context *ctx)
-{
-    if (ctx->lexer->token.type != LEX_T_ID) {
-        lexer_syntax_error(ctx->lexer, NULL);
-        return false;
-    }
-
-    enum lex_type lookahead = lexer_lookahead(ctx->lexer);
-    if (lookahead == LEX_T_EQUALS || lookahead == LEX_T_EXCHANGE
-        || lookahead == LEX_T_LSQUARE) {
-        parse_set_action(ctx);
-    } else if (lexer_match_id(ctx->lexer, "next")) {
-        parse_NEXT(ctx);
-    } else if (lexer_match_id(ctx->lexer, "output")) {
-        ovnact_put_OUTPUT(ctx->ovnacts);
-    } else if (lexer_match_id(ctx->lexer, "ip.ttl")) {
-        parse_DEC_TTL(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_next")) {
-        parse_CT_NEXT(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_commit")) {
-        parse_CT_COMMIT(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_dnat")) {
-        parse_CT_DNAT(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_snat")) {
-        parse_CT_SNAT(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_lb")) {
-        parse_ct_lb_action(ctx);
-    } else if (lexer_match_id(ctx->lexer, "ct_clear")) {
-        ovnact_put_CT_CLEAR(ctx->ovnacts);
-    } else if (lexer_match_id(ctx->lexer, "clone")) {
-        parse_CLONE(ctx);
-    } else if (lexer_match_id(ctx->lexer, "arp")) {
-        parse_ARP(ctx);
-    } else if (lexer_match_id(ctx->lexer, "icmp4")) {
-        parse_ICMP4(ctx);
-    } else if (lexer_match_id(ctx->lexer, "icmp4_error")) {
-        parse_ICMP4_ERROR(ctx);
-    } else if (lexer_match_id(ctx->lexer, "icmp6")) {
-        parse_ICMP6(ctx);
-    } else if (lexer_match_id(ctx->lexer, "igmp")) {
-        ovnact_put_IGMP(ctx->ovnacts);
-    } else if (lexer_match_id(ctx->lexer, "tcp_reset")) {
-        parse_TCP_RESET(ctx);
-    } else if (lexer_match_id(ctx->lexer, "nd_na")) {
-        parse_ND_NA(ctx);
-    } else if (lexer_match_id(ctx->lexer, "nd_na_router")) {
-        parse_ND_NA_ROUTER(ctx);
-    } else if (lexer_match_id(ctx->lexer, "nd_ns")) {
-        parse_ND_NS(ctx);
-    } else if (lexer_match_id(ctx->lexer, "get_arp")) {
-        parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts));
-    } else if (lexer_match_id(ctx->lexer, "put_arp")) {
-        parse_put_mac_bind(ctx, 32, ovnact_put_PUT_ARP(ctx->ovnacts));
-    } else if (lexer_match_id(ctx->lexer, "get_nd")) {
-        parse_get_mac_bind(ctx, 128, ovnact_put_GET_ND(ctx->ovnacts));
-    } else if (lexer_match_id(ctx->lexer, "put_nd")) {
-        parse_put_mac_bind(ctx, 128, ovnact_put_PUT_ND(ctx->ovnacts));
-    } else if (lexer_match_id(ctx->lexer, "set_queue")) {
-        parse_SET_QUEUE(ctx);
-    } else if (lexer_match_id(ctx->lexer, "log")) {
-        parse_LOG(ctx);
-    } else if (lexer_match_id(ctx->lexer, "set_meter")) {
-        parse_set_meter_action(ctx);
-    } else if (lexer_match_id(ctx->lexer, "trigger_event")) {
-        parse_trigger_event(ctx, ovnact_put_TRIGGER_EVENT(ctx->ovnacts));
-    } else {
-        lexer_syntax_error(ctx->lexer, "expecting action");
-    }
-    lexer_force_match(ctx->lexer, LEX_T_SEMICOLON);
-    return !ctx->lexer->error;
-}
-
-static void
-parse_actions(struct action_context *ctx, enum lex_type sentinel)
-{
-    /* "drop;" by itself is a valid (empty) set of actions, but it can't be
-     * combined with other actions because that doesn't make sense. */
-    if (ctx->lexer->token.type == LEX_T_ID
-        && !strcmp(ctx->lexer->token.s, "drop")
-        && lexer_lookahead(ctx->lexer) == LEX_T_SEMICOLON) {
-        lexer_get(ctx->lexer);  /* Skip "drop". */
-        lexer_get(ctx->lexer);  /* Skip ";". */
-        lexer_force_match(ctx->lexer, sentinel);
-        return;
-    }
-
-    while (!lexer_match(ctx->lexer, sentinel)) {
-        if (!parse_action(ctx)) {
-            return;
-        }
-    }
-}
-
-/* Parses OVN actions, in the format described for the "actions" column in the
- * Logical_Flow table in ovn-sb(5), and appends the parsed versions of the
- * actions to 'ovnacts' as "struct ovnact"s.  The caller must eventually free
- * the parsed ovnacts with ovnacts_free().
- *
- * 'pp' provides most of the parameters for translation.
- *
- * Some actions add extra requirements (prerequisites) to the flow's match.  If
- * so, this function sets '*prereqsp' to the actions' prerequisites; otherwise,
- * it sets '*prereqsp' to NULL.  The caller owns '*prereqsp' and must
- * eventually free it.
- *
- * Returns true if successful, false if an error occurred.  Upon return,
- * returns true if and only if lexer->error is NULL.
- */
-bool
-ovnacts_parse(struct lexer *lexer, const struct ovnact_parse_params *pp,
-              struct ofpbuf *ovnacts, struct expr **prereqsp)
-{
-    size_t ovnacts_start = ovnacts->size;
-
-    struct action_context ctx = {
-        .pp = pp,
-        .lexer = lexer,
-        .ovnacts = ovnacts,
-        .prereqs = NULL,
-    };
-    if (!lexer->error) {
-        parse_actions(&ctx, LEX_T_END);
-    }
-
-    if (!lexer->error) {
-        *prereqsp = ctx.prereqs;
-        return true;
-    } else {
-        ofpbuf_pull(ovnacts, ovnacts_start);
-        ovnacts_free(ovnacts->data, ovnacts->size);
-        ofpbuf_push_uninit(ovnacts, ovnacts_start);
-
-        ovnacts->size = ovnacts_start;
-        expr_destroy(ctx.prereqs);
-        *prereqsp = NULL;
-        return false;
-    }
-}
-
-/* Like ovnacts_parse(), but the actions are taken from 's'. */
-char * OVS_WARN_UNUSED_RESULT
-ovnacts_parse_string(const char *s, const struct ovnact_parse_params *pp,
-                     struct ofpbuf *ofpacts, struct expr **prereqsp)
-{
-    struct lexer lexer;
-
-    lexer_init(&lexer, s);
-    lexer_get(&lexer);
-    ovnacts_parse(&lexer, pp, ofpacts, prereqsp);
-    char *error = lexer_steal_error(&lexer);
-    lexer_destroy(&lexer);
-
-    return error;
-}
-
-/* Formatting ovnacts. */
-
-static void
-ovnact_format(const struct ovnact *a, struct ds *s)
-{
-    switch (a->type) {
-#define OVNACT(ENUM, STRUCT)                                            \
-        case OVNACT_##ENUM:                                             \
-            format_##ENUM(ALIGNED_CAST(const struct STRUCT *, a), s);   \
-            break;
-        OVNACTS
-#undef OVNACT
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Appends a string represen