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@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@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@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@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@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@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@machine_1-ip:/path/to/chassis_1-cert.pem
-      $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \
-                    machine_1@machine_1-ip:/path/to/cacert.pem
-      $ scp chassis_2-cert.pem \
-                    machine_2@machine_2-ip:/path/to/chassis_2-cert.pem
-      $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \
-                    machine_2@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/NEWS b/NEWS
index d58a67141..f5a0b8faf 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 - 03 Sep 2019
 ---------------------
diff --git a/configure.ac b/configure.ac
index e3603926b..1d45c4fdd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -210,8 +210,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..a66d109e3 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -1,15 +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 \
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
index b33112ef1..afaf0688c 100644
--- a/ovn/automake.mk
+++ b/ovn/automake.mk
@@ -66,13 +66,6 @@ ovn/ovn-nb.5: \
 		$(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
@@ -85,8 +78,5 @@ 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 b115d1a57..000000000
--- a/ovn/controller/pinctrl.c
+++ /dev/null
@@ -1,4342 +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;
-    }
-
-    /* Check that the packet stores at least the minimal headers. */
-    if (dp_packet_l4_size(pkt_in) < (UDP_HEADER_LEN + DNS_HEADER_LEN)) {
-        VLOG_WARN_RL(&rl, "truncated dns packet");
-        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;
-    uint16_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'. */
-
-    /* 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->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)
-{
-    /* 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/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 representing the 'ovnacts_len' bytes of ovnacts in
- * 'ovnacts' to 'string'. */
-void
-ovnacts_format(const struct ovnact *ovnacts, size_t ovnacts_len,
-               struct ds *string)
-{
-    if (!ovnacts_len) {
-        ds_put_cstr(string, "drop;");
-    } else {
-        const struct ovnact *a;
-
-        OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-            if (a != ovnacts) {
-                ds_put_char(string, ' ');
-            }
-            ovnact_format(a, string);
-        }
-    }
-}
-
-/* Encoding ovnacts to OpenFlow. */
-
-static void
-ovnact_encode(const struct ovnact *a, const struct ovnact_encode_params *ep,
-              struct ofpbuf *ofpacts)
-{
-    switch (a->type) {
-#define OVNACT(ENUM, STRUCT)                                            \
-        case OVNACT_##ENUM:                                             \
-            encode_##ENUM(ALIGNED_CAST(const struct STRUCT *, a),       \
-                          ep, ofpacts);                                 \
-            break;
-        OVNACTS
-#undef OVNACT
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Appends ofpacts to 'ofpacts' that represent the actions in the 'ovnacts_len'
- * bytes of actions starting at 'ovnacts'. */
-void
-ovnacts_encode(const struct ovnact *ovnacts, size_t ovnacts_len,
-               const struct ovnact_encode_params *ep,
-               struct ofpbuf *ofpacts)
-{
-    if (ovnacts) {
-        const struct ovnact *a;
-
-        OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-            ovnact_encode(a, ep, ofpacts);
-        }
-    }
-}
-
-/* Freeing ovnacts. */
-
-static void
-ovnact_free(struct ovnact *a)
-{
-    switch (a->type) {
-#define OVNACT(ENUM, STRUCT)                                            \
-        case OVNACT_##ENUM:                                             \
-            STRUCT##_free(ALIGNED_CAST(struct STRUCT *, a));            \
-            break;
-        OVNACTS
-#undef OVNACT
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Frees each of the actions in the 'ovnacts_len' bytes of actions starting at
- * 'ovnacts'.
- *
- * Does not call free(ovnacts); the caller must do so if desirable. */
-void
-ovnacts_free(struct ovnact *ovnacts, size_t ovnacts_len)
-{
-    if (ovnacts) {
-        struct ovnact *a;
-
-        OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-            ovnact_free(a);
-        }
-    }
-}
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index 7cac67fb6..c74430005 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -6,23 +6,8 @@ ovn_lib_libovn_la_LDFLAGS = \
 ovn_lib_libovn_la_SOURCES = \
 	ovn/lib/acl-log.c \
 	ovn/lib/acl-log.h \
-	ovn/lib/actions.c \
-	ovn/lib/chassis-index.c \
-	ovn/lib/chassis-index.h \
-	ovn/lib/expr.c \
-	ovn/lib/extend-table.h \
-	ovn/lib/extend-table.c \
-	ovn/lib/ip-mcast-index.c \
-	ovn/lib/ip-mcast-index.h \
-	ovn/lib/mcast-group-index.c \
-	ovn/lib/mcast-group-index.h \
-	ovn/lib/lex.c \
-	ovn/lib/ovn-l7.h \
 	ovn/lib/ovn-util.c \
 	ovn/lib/ovn-util.h \
-	ovn/lib/logical-fields.c \
-	ovn/lib/inc-proc-eng.c \
-	ovn/lib/inc-proc-eng.h
 nodist_ovn_lib_libovn_la_SOURCES = \
 	ovn/lib/ovn-nb-idl.c \
 	ovn/lib/ovn-nb-idl.h \
diff --git a/ovn/lib/chassis-index.c b/ovn/lib/chassis-index.c
deleted file mode 100644
index 10f70fb4a..000000000
--- a/ovn/lib/chassis-index.c
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Copyright (c) 2016, 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 "ovn/lib/chassis-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-struct ovsdb_idl_index *
-chassis_index_create(struct ovsdb_idl *idl)
-{
-    return ovsdb_idl_index_create1(idl, &sbrec_chassis_col_name);
-}
-
-/* Finds and returns the chassis with the given 'name', or NULL if no such
- * chassis exists. */
-const struct sbrec_chassis *
-chassis_lookup_by_name(struct ovsdb_idl_index *sbrec_chassis_by_name,
-                       const char *name)
-{
-    struct sbrec_chassis *target = sbrec_chassis_index_init_row(
-        sbrec_chassis_by_name);
-    sbrec_chassis_index_set_name(target, name);
-
-    struct sbrec_chassis *retval = sbrec_chassis_index_find(
-        sbrec_chassis_by_name, target);
-
-    sbrec_chassis_index_destroy_row(target);
-
-    return retval;
-}
-
-struct ovsdb_idl_index *
-ha_chassis_group_index_create(struct ovsdb_idl *idl)
-{
-    return ovsdb_idl_index_create1(idl, &sbrec_ha_chassis_group_col_name);
-}
-
-/* Finds and returns the HA chassis group with the given 'name', or NULL
- * if no such HA chassis group exists. */
-const struct sbrec_ha_chassis_group *
-ha_chassis_group_lookup_by_name(
-    struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
-    const char *name)
-{
-    struct sbrec_ha_chassis_group *target =
-        sbrec_ha_chassis_group_index_init_row(sbrec_ha_chassis_grp_by_name);
-    sbrec_ha_chassis_group_index_set_name(target, name);
-
-    struct sbrec_ha_chassis_group *retval =
-        sbrec_ha_chassis_group_index_find(sbrec_ha_chassis_grp_by_name,
-                                          target);
-
-    sbrec_ha_chassis_group_index_destroy_row(target);
-
-    return retval;
-}
diff --git a/ovn/lib/chassis-index.h b/ovn/lib/chassis-index.h
deleted file mode 100644
index 9bc610ad2..000000000
--- a/ovn/lib/chassis-index.h
+++ /dev/null
@@ -1,30 +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_CHASSIS_INDEX_H
-#define OVN_CHASSIS_INDEX_H 1
-
-struct ovsdb_idl;
-
-struct ovsdb_idl_index *chassis_index_create(struct ovsdb_idl *);
-
-const struct sbrec_chassis *chassis_lookup_by_name(
-    struct ovsdb_idl_index *sbrec_chassis_by_name, const char *name);
-
-struct ovsdb_idl_index *ha_chassis_group_index_create(struct ovsdb_idl *idl);
-const struct sbrec_ha_chassis_group *ha_chassis_group_lookup_by_name(
-    struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name, const char *name);
-
-#endif /* ovn/lib/chassis-index.h */
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
deleted file mode 100644
index e4c650f7c..000000000
--- a/ovn/lib/expr.c
+++ /dev/null
@@ -1,3450 +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 "byte-order.h"
-#include "openvswitch/json.h"
-#include "nx-match.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/match.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/vlog.h"
-#include "openvswitch/shash.h"
-#include "ovn/expr.h"
-#include "ovn/lex.h"
-#include "ovn/logical-fields.h"
-#include "simap.h"
-#include "sset.h"
-#include "util.h"
-
-VLOG_DEFINE_THIS_MODULE(expr);
-
-static struct expr *parse_and_annotate(const char *s,
-                                       const struct shash *symtab,
-                                       struct ovs_list *nesting,
-                                       char **errorp);
-
-/* Returns the name of measurement level 'level'. */
-const char *
-expr_level_to_string(enum expr_level level)
-{
-    switch (level) {
-    case EXPR_L_NOMINAL: return "nominal";
-    case EXPR_L_BOOLEAN: return "Boolean";
-    case EXPR_L_ORDINAL: return "ordinal";
-    default: OVS_NOT_REACHED();
-    }
-}
-
-/* Relational operators. */
-
-/* Returns a string form of relational operator 'relop'. */
-const char *
-expr_relop_to_string(enum expr_relop relop)
-{
-    switch (relop) {
-    case EXPR_R_EQ: return "==";
-    case EXPR_R_NE: return "!=";
-    case EXPR_R_LT: return "<";
-    case EXPR_R_LE: return "<=";
-    case EXPR_R_GT: return ">";
-    case EXPR_R_GE: return ">=";
-    default: OVS_NOT_REACHED();
-    }
-}
-
-bool
-expr_relop_from_token(enum lex_type type, enum expr_relop *relop)
-{
-    enum expr_relop r;
-
-    switch ((int) type) {
-    case LEX_T_EQ: r = EXPR_R_EQ; break;
-    case LEX_T_NE: r = EXPR_R_NE; break;
-    case LEX_T_LT: r = EXPR_R_LT; break;
-    case LEX_T_LE: r = EXPR_R_LE; break;
-    case LEX_T_GT: r = EXPR_R_GT; break;
-    case LEX_T_GE: r = EXPR_R_GE; break;
-    default: return false;
-    }
-
-    if (relop) {
-        *relop = r;
-    }
-    return true;
-}
-
-/* Returns the relational operator that 'relop' becomes if you turn the
- * relation's operands around, e.g. EXPR_R_EQ does not change because "a == b"
- * and "b == a" are equivalent, but EXPR_R_LE becomes EXPR_R_GE because "a <=
- * b" is equivalent to "b >= a". */
-static enum expr_relop
-expr_relop_turn(enum expr_relop relop)
-{
-    switch (relop) {
-    case EXPR_R_EQ: return EXPR_R_EQ;
-    case EXPR_R_NE: return EXPR_R_NE;
-    case EXPR_R_LT: return EXPR_R_GT;
-    case EXPR_R_LE: return EXPR_R_GE;
-    case EXPR_R_GT: return EXPR_R_LT;
-    case EXPR_R_GE: return EXPR_R_LE;
-    default: OVS_NOT_REACHED();
-    }
-}
-
-/* Returns the relational operator that is the opposite of 'relop'. */
-static enum expr_relop
-expr_relop_invert(enum expr_relop relop)
-{
-    switch (relop) {
-    case EXPR_R_EQ: return EXPR_R_NE;
-    case EXPR_R_NE: return EXPR_R_EQ;
-    case EXPR_R_LT: return EXPR_R_GE;
-    case EXPR_R_LE: return EXPR_R_GT;
-    case EXPR_R_GT: return EXPR_R_LE;
-    case EXPR_R_GE: return EXPR_R_LT;
-    default: OVS_NOT_REACHED();
-    }
-}
-
-/* Checks whether 'relop' is true for strcmp()-like 3-way comparison result
- * 'cmp'. */
-static bool
-expr_relop_test(enum expr_relop relop, int cmp)
-{
-    switch (relop) {
-    case EXPR_R_EQ: return cmp == 0;
-    case EXPR_R_NE: return cmp != 0;
-    case EXPR_R_LT: return cmp < 0;
-    case EXPR_R_LE: return cmp <= 0;
-    case EXPR_R_GT: return cmp > 0;
-    case EXPR_R_GE: return cmp >= 0;
-    default: OVS_NOT_REACHED();
-    }
-}
-
-/* Constructing and manipulating expressions. */
-
-/* Creates and returns a logical AND or OR expression (according to 'type',
- * which must be EXPR_T_AND or EXPR_T_OR) that initially has no
- * sub-expressions.  (To satisfy the invariants for expressions, the caller
- * must add at least two sub-expressions whose types are different from
- * 'type'.) */
-struct expr *
-expr_create_andor(enum expr_type type)
-{
-    struct expr *e = xmalloc(sizeof *e);
-    e->type = type;
-    ovs_list_init(&e->andor);
-    return e;
-}
-
-/* Returns a logical AND or OR expression (according to 'type', which must be
- * EXPR_T_AND or EXPR_T_OR) whose sub-expressions are 'a' and 'b', with some
- * flexibility:
- *
- *     - If 'a' or 'b' is NULL, just returns the other one (which means that if
- *       that other one is not of the given 'type', then the returned
- *       expression is not either).
- *
- *     - If 'a' or 'b', or both, have type 'type', then they are combined into
- *       a single node that satisfies the invariants for expressions. */
-struct expr *
-expr_combine(enum expr_type type, struct expr *a, struct expr *b)
-{
-    if (!a) {
-        return b;
-    } else if (!b) {
-        return a;
-    } else if (a->type == type) {
-        if (b->type == type) {
-            ovs_list_splice(&a->andor, b->andor.next, &b->andor);
-            free(b);
-        } else {
-            ovs_list_push_back(&a->andor, &b->node);
-        }
-        return a;
-    } else if (b->type == type) {
-        ovs_list_push_front(&b->andor, &a->node);
-        return b;
-    } else {
-        struct expr *e = expr_create_andor(type);
-        ovs_list_push_back(&e->andor, &a->node);
-        ovs_list_push_back(&e->andor, &b->node);
-        return e;
-    }
-}
-
-static void
-expr_insert_andor(struct expr *andor, struct expr *before, struct expr *new)
-{
-    if (new->type == andor->type) {
-        if (andor->type == EXPR_T_AND) {
-            /* Conjunction junction, what's your function? */
-        }
-        ovs_list_splice(&before->node, new->andor.next, &new->andor);
-        free(new);
-    } else {
-        ovs_list_insert(&before->node, &new->node);
-    }
-}
-
-/* Returns an EXPR_T_BOOLEAN expression with value 'b'. */
-struct expr *
-expr_create_boolean(bool b)
-{
-    struct expr *e = xmalloc(sizeof *e);
-    e->type = EXPR_T_BOOLEAN;
-    e->boolean = b;
-    return e;
-}
-
-static void
-expr_not(struct expr *expr)
-{
-    struct expr *sub;
-
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        expr->cmp.relop = expr_relop_invert(expr->cmp.relop);
-        break;
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        LIST_FOR_EACH (sub, node, &expr->andor) {
-            expr_not(sub);
-        }
-        expr->type = expr->type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND;
-        break;
-
-    case EXPR_T_BOOLEAN:
-        expr->boolean = !expr->boolean;
-        break;
-
-    case EXPR_T_CONDITION:
-        expr->cond.not = !expr->cond.not;
-        break;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static struct expr *
-expr_fix_andor(struct expr *expr, bool short_circuit)
-{
-    struct expr *sub, *next;
-
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        if (sub->type == EXPR_T_BOOLEAN) {
-            if (sub->boolean == short_circuit) {
-                expr_destroy(expr);
-                return expr_create_boolean(short_circuit);
-            } else {
-                ovs_list_remove(&sub->node);
-                expr_destroy(sub);
-            }
-        }
-    }
-
-    if (ovs_list_is_short(&expr->andor)) {
-        if (ovs_list_is_empty(&expr->andor)) {
-            free(expr);
-            return expr_create_boolean(!short_circuit);
-        } else {
-            sub = expr_from_node(ovs_list_front(&expr->andor));
-            free(expr);
-            return sub;
-        }
-    } else {
-        return expr;
-    }
-}
-
-/* Returns 'expr' modified so that top-level oddities are fixed up:
- *
- *     - Eliminates any EXPR_T_BOOLEAN operands at the top level.
- *
- *     - Replaces one-operand EXPR_T_AND or EXPR_T_OR by its subexpression. */
-static struct expr *
-expr_fix(struct expr *expr)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return expr;
-
-    case EXPR_T_AND:
-        return expr_fix_andor(expr, false);
-
-    case EXPR_T_OR:
-        return expr_fix_andor(expr, true);
-
-    case EXPR_T_BOOLEAN:
-        return expr;
-
-    case EXPR_T_CONDITION:
-        return expr;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Formatting. */
-
-/* Searches bits [0,width) in 'sv' for a contiguous sequence of 1-bits.  If one
- * such sequence exists, stores the index of the first 1-bit into '*startp' and
- * the number of 1-bits into '*n_bitsp'.  Stores 0 into both variables if no
- * such sequence, or more than one, exists. */
-static void
-find_bitwise_range(const union mf_subvalue *sv, int width,
-                   int *startp, int *n_bitsp)
-{
-    unsigned int start = bitwise_scan(sv, sizeof *sv, true, 0, width);
-    if (start < width) {
-        unsigned int end = bitwise_scan(sv, sizeof *sv, false, start, width);
-        if (end >= width
-            || bitwise_scan(sv, sizeof *sv, true, end, width) >= width) {
-            *startp = start;
-            *n_bitsp = end - start;
-            return;
-        }
-    }
-    *startp = *n_bitsp = 0;
-}
-
-static void
-expr_format_cmp(const struct expr *e, struct ds *s)
-{
-    /* The common case is numerical comparisons.
-     * Handle string comparisons as a special case. */
-    if (!e->cmp.symbol->width) {
-        ds_put_format(s, "%s %s ", e->cmp.symbol->name,
-                      expr_relop_to_string(e->cmp.relop));
-        json_string_escape(e->cmp.string, s);
-        return;
-    }
-
-    int ofs, n;
-    find_bitwise_range(&e->cmp.mask, e->cmp.symbol->width, &ofs, &n);
-    if (n == 1 && (e->cmp.relop == EXPR_R_EQ || e->cmp.relop == EXPR_R_NE)) {
-        bool positive;
-
-        positive = bitwise_get_bit(&e->cmp.value, sizeof e->cmp.value, ofs);
-        positive ^= e->cmp.relop == EXPR_R_NE;
-        if (!positive) {
-            ds_put_char(s, '!');
-        }
-        ds_put_cstr(s, e->cmp.symbol->name);
-        if (e->cmp.symbol->width > 1) {
-            ds_put_format(s, "[%d]", ofs);
-        }
-        return;
-    }
-
-    ds_put_cstr(s, e->cmp.symbol->name);
-    if (n > 0 && n < e->cmp.symbol->width) {
-        if (n > 1) {
-            ds_put_format(s, "[%d..%d]", ofs, ofs + n - 1);
-        } else {
-            ds_put_format(s, "[%d]", ofs);
-        }
-    }
-
-    ds_put_format(s, " %s ", expr_relop_to_string(e->cmp.relop));
-
-    if (n) {
-        union mf_subvalue value;
-
-        memset(&value, 0, sizeof value);
-        bitwise_copy(&e->cmp.value, sizeof e->cmp.value, ofs,
-                     &value, sizeof value, 0,
-                     n);
-        mf_format_subvalue(&value, s);
-    } else {
-        mf_format_subvalue(&e->cmp.value, s);
-        ds_put_char(s, '/');
-        mf_format_subvalue(&e->cmp.mask, s);
-    }
-}
-
-static void
-expr_format_andor(const struct expr *e, const char *op, struct ds *s)
-{
-    struct expr *sub;
-    int i = 0;
-
-    LIST_FOR_EACH (sub, node, &e->andor) {
-        if (i++) {
-            ds_put_format(s, " %s ", op);
-        }
-
-        if (sub->type == EXPR_T_AND || sub->type == EXPR_T_OR) {
-            ds_put_char(s, '(');
-            expr_format(sub, s);
-            ds_put_char(s, ')');
-        } else {
-            expr_format(sub, s);
-        }
-    }
-}
-
-static void
-expr_format_condition(const struct expr *e, struct ds *s)
-{
-    if (e->cond.not) {
-        ds_put_char(s, '!');
-    }
-    switch (e->cond.type) {
-    case EXPR_COND_CHASSIS_RESIDENT:
-        ds_put_format(s, "is_chassis_resident(");
-        json_string_escape(e->cond.string, s);
-        ds_put_char(s, ')');
-        break;
-    }
-}
-
-/* Appends a string form of 'e' to 's'.  The string form is acceptable for
- * parsing back into an equivalent expression. */
-void
-expr_format(const struct expr *e, struct ds *s)
-{
-    switch (e->type) {
-    case EXPR_T_CMP:
-        expr_format_cmp(e, s);
-        break;
-
-    case EXPR_T_AND:
-        expr_format_andor(e, "&&", s);
-        break;
-
-    case EXPR_T_OR:
-        expr_format_andor(e, "||", s);
-        break;
-
-    case EXPR_T_BOOLEAN:
-        ds_put_char(s, e->boolean ? '1' : '0');
-        break;
-
-    case EXPR_T_CONDITION:
-        expr_format_condition(e, s);
-        break;
-    }
-}
-
-/* Prints a string form of 'e' on stdout, followed by a new-line. */
-void
-expr_print(const struct expr *e)
-{
-    struct ds output;
-
-    ds_init(&output);
-    expr_format(e, &output);
-    puts(ds_cstr(&output));
-    ds_destroy(&output);
-}
-
-/* Parsing. */
-
-#define MAX_PAREN_DEPTH 100
-
-/* Context maintained during expr_parse(). */
-struct expr_context {
-    struct lexer *lexer;           /* Lexer for pulling more tokens. */
-    const struct shash *symtab;    /* Symbol table. */
-    const struct shash *addr_sets; /* Address set table. */
-    const struct shash *port_groups; /* Port group table. */
-    struct sset *addr_sets_ref;       /* The set of address set referenced. */
-    bool not;                    /* True inside odd number of NOT operators. */
-    unsigned int paren_depth;    /* Depth of nested parentheses. */
-};
-
-struct expr *expr_parse__(struct expr_context *);
-static void expr_not(struct expr *);
-static bool parse_field(struct expr_context *, struct expr_field *);
-
-static struct expr *
-make_cmp__(const struct expr_field *f, enum expr_relop r,
-             const union expr_constant *c)
-{
-    struct expr *e = xzalloc(sizeof *e);
-    e->type = EXPR_T_CMP;
-    e->cmp.symbol = f->symbol;
-    e->cmp.relop = r;
-    if (f->symbol->width) {
-        bitwise_copy(&c->value, sizeof c->value, 0,
-                     &e->cmp.value, sizeof e->cmp.value, f->ofs,
-                     f->n_bits);
-        if (c->masked) {
-            bitwise_copy(&c->mask, sizeof c->mask, 0,
-                         &e->cmp.mask, sizeof e->cmp.mask, f->ofs,
-                         f->n_bits);
-        } else {
-            bitwise_one(&e->cmp.mask, sizeof e->cmp.mask, f->ofs,
-                        f->n_bits);
-        }
-    } else {
-        e->cmp.string = xstrdup(c->string);
-    }
-    return e;
-}
-
-/* Returns the minimum reasonable width for integer constant 'c'. */
-static int
-expr_constant_width(const union expr_constant *c)
-{
-    if (c->masked) {
-        return mf_subvalue_width(&c->mask);
-    }
-
-    switch (c->format) {
-    case LEX_F_DECIMAL:
-    case LEX_F_HEXADECIMAL:
-        return mf_subvalue_width(&c->value);
-
-    case LEX_F_IPV4:
-        return 32;
-
-    case LEX_F_IPV6:
-        return 128;
-
-    case LEX_F_ETHERNET:
-        return 48;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static bool
-type_check(struct expr_context *ctx, const struct expr_field *f,
-           struct expr_constant_set *cs)
-{
-    if (cs->type != (f->symbol->width ? EXPR_C_INTEGER : EXPR_C_STRING)) {
-        lexer_error(ctx->lexer,
-                    "%s field %s is not compatible with %s constant.",
-                    f->symbol->width ? "Integer" : "String",
-                    f->symbol->name,
-                    cs->type == EXPR_C_INTEGER ? "integer" : "string");
-        return false;
-    }
-
-    if (f->symbol->width) {
-        for (size_t i = 0; i < cs->n_values; i++) {
-            int w = expr_constant_width(&cs->values[i]);
-            if (w > f->symbol->width) {
-                lexer_error(ctx->lexer,
-                            "%d-bit constant is not compatible with %d-bit "
-                            "field %s.", w, f->symbol->width, f->symbol->name);
-                return false;
-            }
-        }
-    }
-
-    return true;
-}
-
-static struct expr *
-make_cmp(struct expr_context *ctx,
-         const struct expr_field *f, enum expr_relop r,
-         struct expr_constant_set *cs)
-{
-    struct expr *e = NULL;
-
-    if (!type_check(ctx, f, cs)) {
-        goto exit;
-    }
-
-    if (r != EXPR_R_EQ && r != EXPR_R_NE) {
-        if (cs->in_curlies) {
-            lexer_error(ctx->lexer, "Only == and != operators may be used "
-                        "with value sets.");
-            goto exit;
-        }
-        if (f->symbol->level == EXPR_L_NOMINAL ||
-            f->symbol->level == EXPR_L_BOOLEAN) {
-            lexer_error(ctx->lexer, "Only == and != operators may be used "
-                        "with %s field %s.",
-                        expr_level_to_string(f->symbol->level),
-                        f->symbol->name);
-            goto exit;
-        }
-        if (!cs->n_values) {
-            lexer_error(ctx->lexer, "Only == and != operators may be used "
-                        "to compare a field against an empty value set.");
-            goto exit;
-        }
-        if (cs->values[0].masked) {
-            lexer_error(ctx->lexer, "Only == and != operators may be used "
-                        "with masked constants.  Consider using subfields "
-                        "instead (e.g. eth.src[0..15] > 0x1111 in place of "
-                        "eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff).");
-            goto exit;
-        }
-    }
-
-    if (f->symbol->level == EXPR_L_NOMINAL) {
-        if (f->symbol->predicate) {
-            ovs_assert(f->symbol->width > 0);
-            for (size_t i = 0; i < cs->n_values; i++) {
-                const union mf_subvalue *value = &cs->values[i].value;
-                bool positive = (value->integer & htonll(1)) != 0;
-                positive ^= r == EXPR_R_NE;
-                positive ^= ctx->not;
-                if (!positive) {
-                    const char *name = f->symbol->name;
-                    lexer_error(ctx->lexer,
-                                "Nominal predicate %s may only be tested "
-                                "positively, e.g. `%s' or `%s == 1' but not "
-                                "`!%s' or `%s == 0'.",
-                                name, name, name, name, name);
-                    goto exit;
-                }
-            }
-        } else if (r != (ctx->not ? EXPR_R_NE : EXPR_R_EQ)) {
-            lexer_error(ctx->lexer, "Nominal field %s may only be tested for "
-                        "equality (taking enclosing `!' operators into "
-                        "account).", f->symbol->name);
-            goto exit;
-        }
-    }
-
-    if (!cs->n_values) {
-        e = expr_create_boolean(r == EXPR_R_NE);
-        goto exit;
-    }
-    e = make_cmp__(f, r, &cs->values[0]);
-    for (size_t i = 1; i < cs->n_values; i++) {
-        e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
-                         e, make_cmp__(f, r, &cs->values[i]));
-    }
-exit:
-    expr_constant_set_destroy(cs);
-    return e;
-}
-
-static bool
-parse_field(struct expr_context *ctx, struct expr_field *f)
-{
-    const struct expr_symbol *symbol;
-
-    if (ctx->lexer->token.type != LEX_T_ID) {
-        lexer_syntax_error(ctx->lexer, "expecting field name");
-        return false;
-    }
-
-    symbol = shash_find_data(ctx->symtab, ctx->lexer->token.s);
-    if (!symbol) {
-        lexer_syntax_error(ctx->lexer, "expecting field name");
-        return false;
-    }
-    lexer_get(ctx->lexer);
-
-    f->symbol = symbol;
-    if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
-        int low, high;
-
-        if (!symbol->width) {
-            lexer_error(ctx->lexer,
-                        "Cannot select subfield of string field %s.",
-                        symbol->name);
-            return false;
-        }
-
-        if (!lexer_force_int(ctx->lexer, &low)) {
-            return false;
-        }
-        if (lexer_match(ctx->lexer, LEX_T_ELLIPSIS)) {
-            if (!lexer_force_int(ctx->lexer, &high)) {
-                return false;
-            }
-        } else {
-            high = low;
-        }
-
-        if (!lexer_force_match(ctx->lexer, LEX_T_RSQUARE)) {
-            return false;
-        }
-
-        if (low > high) {
-            lexer_error(ctx->lexer, "Invalid bit range %d to %d.", low, high);
-            return false;
-        } else if (high >= symbol->width) {
-            lexer_error(ctx->lexer,
-                        "Cannot select bits %d to %d of %d-bit field %s.",
-                        low, high, symbol->width, symbol->name);
-            return false;
-        } else if (symbol->level == EXPR_L_NOMINAL
-                   && (low != 0 || high != symbol->width - 1)) {
-            lexer_error(ctx->lexer,
-                        "Cannot select subfield of nominal field %s.",
-                        symbol->name);
-            return false;
-        }
-
-        f->ofs = low;
-        f->n_bits = high - low + 1;
-    } else {
-        f->ofs = 0;
-        f->n_bits = symbol->width;
-    }
-
-    return true;
-}
-
-static bool
-parse_relop(struct expr_context *ctx, enum expr_relop *relop)
-{
-    if (expr_relop_from_token(ctx->lexer->token.type, relop)) {
-        lexer_get(ctx->lexer);
-        return true;
-    } else {
-        lexer_syntax_error(ctx->lexer, "expecting relational operator");
-        return false;
-    }
-}
-
-static bool
-assign_constant_set_type(struct expr_context *ctx,
-                         struct expr_constant_set *cs,
-                         enum expr_constant_type type)
-{
-    if (!cs->n_values || cs->type == type) {
-        cs->type = type;
-        return true;
-    } else {
-        lexer_syntax_error(ctx->lexer, "expecting %s",
-                           cs->type == EXPR_C_INTEGER ? "integer" : "string");
-        return false;
-    }
-}
-
-static bool
-parse_addr_sets(struct expr_context *ctx, struct expr_constant_set *cs,
-                size_t *allocated_values)
-{
-    if (ctx->addr_sets_ref) {
-        sset_add(ctx->addr_sets_ref, ctx->lexer->token.s);
-    }
-
-    struct expr_constant_set *addr_sets
-        = (ctx->addr_sets
-           ? shash_find_data(ctx->addr_sets, ctx->lexer->token.s)
-           : NULL);
-    if (!addr_sets) {
-        lexer_syntax_error(ctx->lexer, "expecting address set name");
-        return false;
-    }
-
-    if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) {
-        return false;
-    }
-
-    size_t n_values = cs->n_values + addr_sets->n_values;
-    if (n_values >= *allocated_values) {
-        cs->values = xrealloc(cs->values, n_values * sizeof *cs->values);
-        *allocated_values = n_values;
-    }
-    for (size_t i = 0; i < addr_sets->n_values; i++) {
-        cs->values[cs->n_values++] = addr_sets->values[i];
-    }
-
-    return true;
-}
-
-static bool
-parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs,
-                 size_t *allocated_values)
-{
-    struct expr_constant_set *port_group
-        = (ctx->port_groups
-           ? shash_find_data(ctx->port_groups, ctx->lexer->token.s)
-           : NULL);
-    if (!port_group) {
-        lexer_syntax_error(ctx->lexer, "expecting port group name");
-        return false;
-    }
-
-    if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
-        return false;
-    }
-
-    size_t n_values = cs->n_values + port_group->n_values;
-    if (n_values >= *allocated_values) {
-        cs->values = xrealloc(cs->values, n_values * sizeof *cs->values);
-        *allocated_values = n_values;
-    }
-    for (size_t i = 0; i < port_group->n_values; i++) {
-        cs->values[cs->n_values++].string =
-            xstrdup(port_group->values[i].string);
-    }
-
-    return true;
-}
-
-static bool
-parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
-               size_t *allocated_values)
-{
-    if (cs->n_values >= *allocated_values) {
-        cs->values = x2nrealloc(cs->values, allocated_values,
-                                sizeof *cs->values);
-    }
-
-    if (ctx->lexer->token.type == LEX_T_STRING) {
-        if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
-            return false;
-        }
-        cs->values[cs->n_values++].string = xstrdup(ctx->lexer->token.s);
-        lexer_get(ctx->lexer);
-        return true;
-    } else if (ctx->lexer->token.type == LEX_T_INTEGER ||
-               ctx->lexer->token.type == LEX_T_MASKED_INTEGER) {
-        if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) {
-            return false;
-        }
-
-        union expr_constant *c = &cs->values[cs->n_values++];
-        c->value = ctx->lexer->token.value;
-        c->format = ctx->lexer->token.format;
-        c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER;
-        if (c->masked) {
-            c->mask = ctx->lexer->token.mask;
-        }
-        lexer_get(ctx->lexer);
-        return true;
-    } else if (ctx->lexer->token.type == LEX_T_MACRO) {
-        if (!parse_addr_sets(ctx, cs, allocated_values)) {
-            return false;
-        }
-        lexer_get(ctx->lexer);
-        return true;
-    } else if (ctx->lexer->token.type == LEX_T_PORT_GROUP) {
-        if (!parse_port_group(ctx, cs, allocated_values)) {
-            return false;
-        }
-        lexer_get(ctx->lexer);
-        return true;
-    } else {
-        lexer_syntax_error(ctx->lexer, "expecting constant");
-        return false;
-    }
-}
-
-/* Parses a single or {}-enclosed set of integer or string constants into 'cs',
- * which the caller need not have initialized.  Returns true on success, in
- * which case the caller owns 'cs', false on failure, in which case 'cs' is
- * indeterminate. */
-static bool
-parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs)
-{
-    size_t allocated_values = 0;
-    bool ok;
-
-    memset(cs, 0, sizeof *cs);
-    if (lexer_match(ctx->lexer, LEX_T_LCURLY)) {
-        ok = true;
-        cs->in_curlies = true;
-        do {
-            if (!parse_constant(ctx, cs, &allocated_values)) {
-                ok = false;
-                break;
-            }
-            lexer_match(ctx->lexer, LEX_T_COMMA);
-        } while (!lexer_match(ctx->lexer, LEX_T_RCURLY));
-    } else {
-        ok = parse_constant(ctx, cs, &allocated_values);
-    }
-    if (!ok) {
-        expr_constant_set_destroy(cs);
-    }
-    return ok;
-}
-
-/* Parses from 'lexer' a single integer or string constant compatible with the
- * type of 'f' into 'c'.
- *
- * Returns true if successful, false if an error occurred.  Upon return,
- * returns true if and only if lexer->error is NULL.  On failure, 'c' is
- * indeterminate. */
-bool
-expr_constant_parse(struct lexer *lexer, const struct expr_field *f,
-                    union expr_constant *c)
-{
-    if (lexer->error) {
-        return false;
-    }
-
-    struct expr_context ctx = { .lexer = lexer };
-
-    struct expr_constant_set cs;
-    memset(&cs, 0, sizeof cs);
-    size_t allocated_values = 0;
-    if (parse_constant(&ctx, &cs, &allocated_values)
-        && type_check(&ctx, f, &cs)) {
-        *c = cs.values[0];
-        cs.n_values = 0;
-    }
-    expr_constant_set_destroy(&cs);
-
-    return !lexer->error;
-}
-
-/* Appends to 's' a re-parseable representation of constant 'c' with the given
- * 'type'. */
-void
-expr_constant_format(const union expr_constant *c,
-                     enum expr_constant_type type, struct ds *s)
-{
-    if (type == EXPR_C_STRING) {
-        json_string_escape(c->string, s);
-    } else {
-        struct lex_token token;
-        token.type = c->masked ? LEX_T_MASKED_INTEGER : LEX_T_INTEGER;
-        token.s = NULL;
-        token.format = c->format;
-        token.value = c->value;
-        if (c->masked) {
-            token.mask = c->mask;
-        }
-
-        lex_token_format(&token, s);
-    }
-}
-
-/* Frees the contents of 'c', which has the specified 'type'.
- *
- * Does not free(c). */
-void
-expr_constant_destroy(const union expr_constant *c,
-                      enum expr_constant_type type)
-{
-    if (c && type == EXPR_C_STRING) {
-        free(c->string);
-    }
-}
-
-/* Parses from 'lexer' a single or {}-enclosed set of at least one integer or
- * string constants into 'cs', which the caller need not have initialized.
- *
- * Returns true if successful, false if an error occurred.  Upon return,
- * returns true if and only if lexer->error is NULL.  On failure, 'cs' is
- * indeterminate. */
-bool
-expr_constant_set_parse(struct lexer *lexer, struct expr_constant_set *cs)
-{
-    if (!lexer->error) {
-        struct expr_context ctx = { .lexer = lexer };
-        parse_constant_set(&ctx, cs);
-    }
-    return !lexer->error;
-}
-
-/* Appends to 's' a re-parseable representation of 'cs'. */
-void
-expr_constant_set_format(const struct expr_constant_set *cs, struct ds *s)
-{
-    bool curlies = cs->in_curlies || cs->n_values != 1;
-    if (curlies) {
-        ds_put_char(s, '{');
-    }
-
-    for (const union expr_constant *c = cs->values;
-         c < &cs->values[cs->n_values]; c++) {
-        if (c != cs->values) {
-            ds_put_cstr(s, ", ");
-        }
-
-        expr_constant_format(c, cs->type, s);
-    }
-
-    if (curlies) {
-        ds_put_char(s, '}');
-    }
-}
-
-void
-expr_constant_set_destroy(struct expr_constant_set *cs)
-{
-    if (cs) {
-        if (cs->type == EXPR_C_STRING) {
-            for (size_t i = 0; i < cs->n_values; i++) {
-                free(cs->values[i].string);
-            }
-        }
-        free(cs->values);
-    }
-}
-
-/* Adds an constant set named 'name' to 'const_sets', replacing any existing
- * constant set entry with the given name. */
-void
-expr_const_sets_add(struct shash *const_sets, const char *name,
-                    const char *const *values, size_t n_values,
-                    bool convert_to_integer)
-{
-    /* Replace any existing entry for this name. */
-    expr_const_sets_remove(const_sets, name);
-
-    struct expr_constant_set *cs = xzalloc(sizeof *cs);
-    cs->in_curlies = true;
-    cs->n_values = 0;
-    cs->values = xmalloc(n_values * sizeof *cs->values);
-    if (convert_to_integer) {
-        cs->type = EXPR_C_INTEGER;
-        for (size_t i = 0; i < n_values; i++) {
-            /* Use the lexer to convert each constant set into the proper
-             * integer format. */
-            struct lexer lex;
-            lexer_init(&lex, values[i]);
-            lexer_get(&lex);
-            if (lex.token.type != LEX_T_INTEGER
-                && lex.token.type != LEX_T_MASKED_INTEGER) {
-                VLOG_WARN("Invalid constant set entry: '%s', token type: %d",
-                          values[i], lex.token.type);
-            } else {
-                union expr_constant *c = &cs->values[cs->n_values++];
-                c->value = lex.token.value;
-                c->format = lex.token.format;
-                c->masked = lex.token.type == LEX_T_MASKED_INTEGER;
-                if (c->masked) {
-                    c->mask = lex.token.mask;
-                }
-            }
-            lexer_destroy(&lex);
-        }
-    } else {
-        cs->type = EXPR_C_STRING;
-        for (size_t i = 0; i < n_values; i++) {
-            union expr_constant *c = &cs->values[cs->n_values++];
-            c->string = xstrdup(values[i]);
-        }
-    }
-
-    shash_add(const_sets, name, cs);
-}
-
-void
-expr_const_sets_remove(struct shash *const_sets, const char *name)
-{
-    struct expr_constant_set *cs = shash_find_and_delete(const_sets, name);
-    if (cs) {
-        expr_constant_set_destroy(cs);
-        free(cs);
-    }
-}
-
-/* Destroy all contents of 'const_sets'. */
-void
-expr_const_sets_destroy(struct shash *const_sets)
-{
-    struct shash_node *node, *next;
-
-    SHASH_FOR_EACH_SAFE (node, next, const_sets) {
-        struct expr_constant_set *cs = node->data;
-
-        shash_delete(const_sets, node);
-        expr_constant_set_destroy(cs);
-        free(cs);
-    }
-}
-
-static struct expr *
-parse_chassis_resident(struct expr_context *ctx)
-{
-    if (ctx->lexer->token.type != LEX_T_STRING) {
-        lexer_syntax_error(ctx->lexer, "expecting string");
-        return NULL;
-    }
-
-    struct expr *e = xzalloc(sizeof *e);
-    e->type = EXPR_T_CONDITION;
-    e->cond.type = EXPR_COND_CHASSIS_RESIDENT;
-    e->cond.not = false;
-    e->cond.string = xstrdup(ctx->lexer->token.s);
-
-    lexer_get(ctx->lexer);
-    if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-        expr_destroy(e);
-        return NULL;
-    }
-
-    return e;
-}
-
-static struct expr *
-expr_parse_primary(struct expr_context *ctx, bool *atomic)
-{
-    *atomic = false;
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        if (ctx->paren_depth >= MAX_PAREN_DEPTH) {
-            lexer_error(ctx->lexer, "Parentheses nested too deeply.");
-            return NULL;
-        }
-
-        ctx->paren_depth++;
-        struct expr *e = expr_parse__(ctx);
-        ctx->paren_depth--;
-
-        if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
-            expr_destroy(e);
-            return NULL;
-        }
-        *atomic = true;
-        return e;
-    }
-
-    if (ctx->lexer->token.type == LEX_T_ID) {
-        struct expr_field f;
-        enum expr_relop r;
-        struct expr_constant_set c;
-
-        if (lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-            if (lexer_match_id(ctx->lexer, "is_chassis_resident")) {
-                lexer_get(ctx->lexer); /* Skip "(". */
-                *atomic = true;
-                return parse_chassis_resident(ctx);
-            }
-            lexer_error(ctx->lexer, "parsing function name");
-            return NULL;
-        }
-
-        if (!parse_field(ctx, &f)) {
-            return NULL;
-        }
-
-        if (!expr_relop_from_token(ctx->lexer->token.type, &r)) {
-            if (!f.n_bits || ctx->lexer->token.type == LEX_T_EQUALS) {
-                lexer_syntax_error(ctx->lexer,
-                                   "expecting relational operator");
-                return NULL;
-            } else if (f.n_bits > 1 && !ctx->not) {
-                lexer_error(ctx->lexer,
-                            "Explicit `!= 0' is required for inequality "
-                            "test of multibit field against 0.");
-                return NULL;
-            }
-
-            *atomic = true;
-
-            union expr_constant *cst = xzalloc(sizeof *cst);
-            cst->format = LEX_F_HEXADECIMAL;
-            cst->masked = false;
-
-            c.type = EXPR_C_INTEGER;
-            c.values = cst;
-            c.n_values = 1;
-            c.in_curlies = false;
-            return make_cmp(ctx, &f, EXPR_R_NE, &c);
-        } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c)) {
-            return make_cmp(ctx, &f, r, &c);
-        } else {
-            return NULL;
-        }
-    } else {
-        struct expr_constant_set c1;
-        if (!parse_constant_set(ctx, &c1)) {
-            return NULL;
-        }
-
-        if (!expr_relop_from_token(ctx->lexer->token.type, NULL)
-            && c1.n_values == 1
-            && c1.type == EXPR_C_INTEGER
-            && c1.values[0].format == LEX_F_DECIMAL
-            && !c1.values[0].masked
-            && !c1.in_curlies) {
-            uint64_t x = ntohll(c1.values[0].value.integer);
-            if (x <= 1) {
-                *atomic = true;
-                expr_constant_set_destroy(&c1);
-                return expr_create_boolean(x);
-            }
-        }
-
-        enum expr_relop r1;
-        struct expr_field f;
-        if (!parse_relop(ctx, &r1) || !parse_field(ctx, &f)) {
-            expr_constant_set_destroy(&c1);
-            return NULL;
-        }
-
-        if (!expr_relop_from_token(ctx->lexer->token.type, NULL)) {
-            return make_cmp(ctx, &f, expr_relop_turn(r1), &c1);
-        }
-
-        enum expr_relop r2;
-        struct expr_constant_set c2;
-        if (!parse_relop(ctx, &r2) || !parse_constant_set(ctx, &c2)) {
-            expr_constant_set_destroy(&c1);
-            return NULL;
-        } else {
-            /* Reject "1 == field == 2", "1 < field > 2", and so on. */
-            if (!(((r1 == EXPR_R_LT || r1 == EXPR_R_LE) &&
-                   (r2 == EXPR_R_LT || r2 == EXPR_R_LE)) ||
-                  ((r1 == EXPR_R_GT || r1 == EXPR_R_GE) &&
-                   (r2 == EXPR_R_GT || r2 == EXPR_R_GE)))) {
-                lexer_error(ctx->lexer, "Range expressions must have the "
-                            "form `x < field < y' or `x > field > y', with "
-                            "each `<' optionally replaced by `<=' or `>' by "
-                            "`>=').");
-                expr_constant_set_destroy(&c1);
-                expr_constant_set_destroy(&c2);
-                return NULL;
-            }
-
-            struct expr *e1 = make_cmp(ctx, &f, expr_relop_turn(r1), &c1);
-            struct expr *e2 = make_cmp(ctx, &f, r2, &c2);
-            if (ctx->lexer->error) {
-                expr_destroy(e1);
-                expr_destroy(e2);
-                return NULL;
-            }
-            return expr_combine(EXPR_T_AND, e1, e2);
-        }
-    }
-}
-
-static struct expr *
-expr_parse_not(struct expr_context *ctx)
-{
-    bool atomic;
-
-    if (lexer_match(ctx->lexer, LEX_T_LOG_NOT)) {
-        ctx->not = !ctx->not;
-        struct expr *expr = expr_parse_primary(ctx, &atomic);
-        ctx->not = !ctx->not;
-
-        if (expr) {
-            if (!atomic) {
-                lexer_error(ctx->lexer,
-                            "Missing parentheses around operand of !.");
-                expr_destroy(expr);
-                return NULL;
-            }
-            expr_not(expr);
-        }
-        return expr;
-    } else {
-        return expr_parse_primary(ctx, &atomic);
-    }
-}
-
-struct expr *
-expr_parse__(struct expr_context *ctx)
-{
-    struct expr *e = expr_parse_not(ctx);
-    if (!e) {
-        return NULL;
-    }
-
-    enum lex_type lex_type = ctx->lexer->token.type;
-    if (lex_type == LEX_T_LOG_AND || lex_type == LEX_T_LOG_OR) {
-        enum expr_type expr_type
-            = lex_type == LEX_T_LOG_AND ? EXPR_T_AND : EXPR_T_OR;
-
-        lexer_get(ctx->lexer);
-        do {
-            struct expr *e2 = expr_parse_not(ctx);
-            if (!e2) {
-                expr_destroy(e);
-                return NULL;
-            }
-            e = expr_combine(expr_type, e, e2);
-        } while (lexer_match(ctx->lexer, lex_type));
-        if (ctx->lexer->token.type == LEX_T_LOG_AND
-            || ctx->lexer->token.type == LEX_T_LOG_OR) {
-            expr_destroy(e);
-            lexer_error(ctx->lexer,
-                        "&& and || must be parenthesized when used together.");
-            return NULL;
-        }
-    }
-    return e;
-}
-
-/* Parses an expression from 'lexer' using the symbols in 'symtab' and
- * address set table in 'addr_sets'.  If successful, returns the new
- * expression; on failure, returns NULL.  Returns nonnull if and only if
- * lexer->error is NULL. */
-struct expr *
-expr_parse(struct lexer *lexer, const struct shash *symtab,
-           const struct shash *addr_sets,
-           const struct shash *port_groups,
-           struct sset *addr_sets_ref)
-{
-    struct expr_context ctx = { .lexer = lexer,
-                                .symtab = symtab,
-                                .addr_sets = addr_sets,
-                                .port_groups = port_groups,
-                                .addr_sets_ref = addr_sets_ref };
-    return lexer->error ? NULL : expr_parse__(&ctx);
-}
-
-/* Parses the expression in 's' using the symbols in 'symtab' and
- * address set table in 'addr_sets'.  If successful, returns the new
- * expression and sets '*errorp' to NULL.  On failure, returns NULL and
- * sets '*errorp' to an explanatory error message.  The caller must
- * eventually free the returned expression (with expr_destroy()) or
- * error (with free()). */
-struct expr *
-expr_parse_string(const char *s, const struct shash *symtab,
-                  const struct shash *addr_sets,
-                  const struct shash *port_groups,
-                  struct sset *addr_sets_ref,
-                  char **errorp)
-{
-    struct lexer lexer;
-
-    lexer_init(&lexer, s);
-    lexer_get(&lexer);
-    struct expr *expr = expr_parse(&lexer, symtab, addr_sets, port_groups,
-                                   addr_sets_ref);
-    lexer_force_end(&lexer);
-    *errorp = lexer_steal_error(&lexer);
-    if (*errorp) {
-        expr_destroy(expr);
-        expr = NULL;
-    }
-    lexer_destroy(&lexer);
-
-    return expr;
-}
-
-/* Parses a field or subfield from 'lexer' into 'field', obtaining field names
- * from 'symtab'.  Returns true if successful, false if an error occurred.
- * Upon return, returns true if and only if lexer->error is NULL. */
-bool
-expr_field_parse(struct lexer *lexer, const struct shash *symtab,
-                 struct expr_field *field, struct expr **prereqsp)
-{
-    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
-    if (parse_field(&ctx, field) && field->symbol->predicate) {
-        lexer_error(lexer, "Predicate symbol %s used where lvalue required.",
-                    field->symbol->name);
-    }
-    if (!lexer->error) {
-        const struct expr_symbol *symbol = field->symbol;
-        while (symbol) {
-            if (symbol->prereqs) {
-                char *error;
-                struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting);
-                struct expr *e = parse_and_annotate(symbol->prereqs, symtab,
-                                                    &nesting, &error);
-                if (error) {
-                    lexer_error(lexer, "%s", error);
-                    free(error);
-                    break;
-                }
-                *prereqsp = expr_combine(EXPR_T_AND, *prereqsp, e);
-            }
-
-            if (!symbol->parent) {
-                break;
-            }
-            symbol = symbol->parent;
-        }
-    }
-    if (!lexer->error) {
-        return true;
-    }
-    memset(field, 0, sizeof *field);
-    return false;
-}
-
-/* Appends to 's' a re-parseable representation of 'field'. */
-void
-expr_field_format(const struct expr_field *field, struct ds *s)
-{
-    ds_put_cstr(s, field->symbol->name);
-    if (field->ofs || field->n_bits != field->symbol->width) {
-        if (field->n_bits != 1) {
-            ds_put_format(s, "[%d..%d]",
-                          field->ofs, field->ofs + field->n_bits - 1);
-        } else {
-            ds_put_format(s, "[%d]", field->ofs);
-        }
-    }
-}
-
-void
-expr_symbol_format(const struct expr_symbol *symbol, struct ds *s)
-{
-    ds_put_format(s, "%s = ", symbol->name);
-    if (symbol->parent) {
-        struct expr_field f = { symbol->parent,
-                                symbol->parent_ofs,
-                                symbol->width };
-        expr_field_format(&f, s);
-    } else if (symbol->predicate) {
-        ds_put_cstr(s, symbol->predicate);
-    } else if (symbol->ovn_field) {
-        ds_put_cstr(s, symbol->name);
-    } else {
-        nx_format_field_name(symbol->field->id, OFP13_VERSION, s);
-    }
-}
-
-static struct expr_symbol *
-add_symbol(struct shash *symtab, const char *name, int width,
-           const char *prereqs, enum expr_level level,
-           bool must_crossproduct, bool rw)
-{
-    struct expr_symbol *symbol = xzalloc(sizeof *symbol);
-    symbol->name = xstrdup(name);
-    symbol->prereqs = prereqs && prereqs[0] ? xstrdup(prereqs) : NULL;
-    symbol->width = width;
-    symbol->level = level;
-    symbol->must_crossproduct = must_crossproduct;
-    symbol->rw = rw;
-    shash_add_assert(symtab, symbol->name, symbol);
-    return symbol;
-}
-
-/* Adds field 'id' to symbol table 'symtab' under the given 'name'.  Whenever
- * 'name' is referenced, expression annotation (see expr_annotate()) will
- * ensure that 'prereqs' are also true.  If 'must_crossproduct' is true, then
- * conversion to flows will never attempt to use the field as a conjunctive
- * match dimension (see "Crossproducting" in the large comment on struct
- * expr_symbol in expr.h for an example).
- *
- * A given field 'id' must only be used for a single symbol in a symbol table.
- * Use subfields to duplicate or subset a field (you can even make a subfield
- * include all the bits of the "parent" field if you like). */
-struct expr_symbol *
-expr_symtab_add_field(struct shash *symtab, const char *name,
-                      enum mf_field_id id, const char *prereqs,
-                      bool must_crossproduct)
-{
-    const struct mf_field *field = mf_from_id(id);
-    struct expr_symbol *symbol;
-
-    symbol = add_symbol(symtab, name, field->n_bits, prereqs,
-                        (field->maskable == MFM_FULLY
-                         ? EXPR_L_ORDINAL
-                         : EXPR_L_NOMINAL),
-                        must_crossproduct, field->writable);
-    symbol->field = field;
-    return symbol;
-}
-
-static bool
-parse_field_from_string(const char *s, const struct shash *symtab,
-                        struct expr_field *field, char **errorp)
-{
-    struct lexer lexer;
-    lexer_init(&lexer, s);
-    lexer_get(&lexer);
-
-    struct expr_context ctx = { .lexer = &lexer, .symtab = symtab };
-    parse_field(&ctx, field);
-    lexer_force_end(&lexer);
-    *errorp = lexer_steal_error(&lexer);
-    lexer_destroy(&lexer);
-
-    return !*errorp;
-}
-
-/* Adds 'name' as a subfield of a larger field in 'symtab'.  Whenever
- * 'name' is referenced, expression annotation (see expr_annotate()) will
- * ensure that 'prereqs' are also true.
- *
- * 'subfield' must describe the subfield as a string, e.g. "vlan.tci[0..11]"
- * for the low 12 bits of a larger field named "vlan.tci". */
-struct expr_symbol *
-expr_symtab_add_subfield(struct shash *symtab, const char *name,
-                         const char *prereqs, const char *subfield)
-{
-    struct expr_symbol *symbol;
-    struct expr_field f;
-    char *error;
-
-    if (!parse_field_from_string(subfield, symtab, &f, &error)) {
-        VLOG_WARN("%s: error parsing %s subfield (%s)", subfield, name, error);
-        free(error);
-        return NULL;
-    }
-
-    enum expr_level level = f.symbol->level;
-    if (level != EXPR_L_ORDINAL) {
-        VLOG_WARN("can't define %s as subfield of %s field %s",
-                  name, expr_level_to_string(level), f.symbol->name);
-    }
-
-    symbol = add_symbol(symtab, name, f.n_bits, prereqs, level, false,
-                        f.symbol->rw);
-    symbol->parent = f.symbol;
-    symbol->parent_ofs = f.ofs;
-    return symbol;
-}
-
-/* Adds a string-valued symbol named 'name' to 'symtab' with the specified
- * 'prereqs'. */
-struct expr_symbol *
-expr_symtab_add_string(struct shash *symtab, const char *name,
-                       enum mf_field_id id, const char *prereqs)
-{
-    const struct mf_field *field = mf_from_id(id);
-    struct expr_symbol *symbol;
-
-    symbol = add_symbol(symtab, name, 0, prereqs, EXPR_L_NOMINAL, false,
-                        field->writable);
-    symbol->field = field;
-    return symbol;
-}
-
-static enum expr_level
-expr_get_level(const struct expr *expr)
-{
-    const struct expr *sub;
-    enum expr_level level = EXPR_L_ORDINAL;
-
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return (expr->cmp.symbol->level == EXPR_L_NOMINAL
-                ? EXPR_L_NOMINAL
-                : EXPR_L_BOOLEAN);
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        LIST_FOR_EACH (sub, node, &expr->andor) {
-            enum expr_level sub_level = expr_get_level(sub);
-            level = MIN(level, sub_level);
-        }
-        return level;
-
-    case EXPR_T_BOOLEAN:
-    case EXPR_T_CONDITION:
-        return EXPR_L_BOOLEAN;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static enum expr_level
-expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
-{
-    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL, errorp);
-    enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
-    expr_destroy(expr);
-    return level;
-}
-
-/* Adds a predicate symbol, whose value is the given Boolean 'expression',
- * named 'name' to 'symtab'.  For example, "ip4 && ip4.proto == 6" might be an
- * appropriate predicate named "tcp4". */
-struct expr_symbol *
-expr_symtab_add_predicate(struct shash *symtab, const char *name,
-                          const char *expansion)
-{
-    struct expr_symbol *symbol;
-    enum expr_level level;
-    char *error;
-
-    level = expr_parse_level(expansion, symtab, &error);
-    if (error) {
-        VLOG_WARN("%s: error parsing %s expansion (%s)",
-                  expansion, name, error);
-        free(error);
-        return NULL;
-    }
-
-    symbol = add_symbol(symtab, name, 1, NULL, level, false, false);
-    symbol->predicate = xstrdup(expansion);
-    return symbol;
-}
-
-struct expr_symbol *
-expr_symtab_add_ovn_field(struct shash *symtab, const char *name,
-                          enum ovn_field_id id)
-{
-    const struct ovn_field *ovn_field = ovn_field_from_id(id);
-    struct expr_symbol *symbol;
-
-    symbol = add_symbol(symtab, name, ovn_field->n_bits, NULL,
-                        EXPR_L_NOMINAL, false, true);
-    symbol->ovn_field = ovn_field;
-    return symbol;
-}
-
-/* Destroys 'symtab' and all of its symbols. */
-void
-expr_symtab_destroy(struct shash *symtab)
-{
-    struct shash_node *node, *next;
-
-    SHASH_FOR_EACH_SAFE (node, next, symtab) {
-        struct expr_symbol *symbol = node->data;
-
-        shash_delete(symtab, node);
-        free(symbol->name);
-        free(symbol->prereqs);
-        free(symbol->predicate);
-        free(symbol);
-    }
-}
-
-/* Cloning. */
-
-static struct expr *
-expr_clone_cmp(struct expr *expr)
-{
-    struct expr *new = xmemdup(expr, sizeof *expr);
-    if (!new->cmp.symbol->width) {
-        new->cmp.string = xstrdup(new->cmp.string);
-    }
-    return new;
-}
-
-static struct expr *
-expr_clone_andor(struct expr *expr)
-{
-    struct expr *new = expr_create_andor(expr->type);
-    struct expr *sub;
-
-    LIST_FOR_EACH (sub, node, &expr->andor) {
-        struct expr *new_sub = expr_clone(sub);
-        ovs_list_push_back(&new->andor, &new_sub->node);
-    }
-    return new;
-}
-
-static struct expr *
-expr_clone_condition(struct expr *expr)
-{
-    struct expr *new = xmemdup(expr, sizeof *expr);
-    new->cond.string = xstrdup(new->cond.string);
-    return new;
-}
-
-/* Returns a clone of 'expr'.  This is a "deep copy": neither the returned
- * expression nor any of its substructure will be shared with 'expr'. */
-struct expr *
-expr_clone(struct expr *expr)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return expr_clone_cmp(expr);
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        return expr_clone_andor(expr);
-
-    case EXPR_T_BOOLEAN:
-        return expr_create_boolean(expr->boolean);
-
-    case EXPR_T_CONDITION:
-        return expr_clone_condition(expr);
-    }
-    OVS_NOT_REACHED();
-}
-
-/* Destroys 'expr' and all of the sub-expressions it references. */
-void
-expr_destroy(struct expr *expr)
-{
-    if (!expr) {
-        return;
-    }
-
-    struct expr *sub, *next;
-
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        if (!expr->cmp.symbol->width) {
-            free(expr->cmp.string);
-        }
-        break;
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            ovs_list_remove(&sub->node);
-            expr_destroy(sub);
-        }
-        break;
-
-    case EXPR_T_BOOLEAN:
-        break;
-
-    case EXPR_T_CONDITION:
-        free(expr->cond.string);
-        break;
-    }
-    free(expr);
-}
-
-/* Annotation. */
-
-/* An element in a linked list of symbols.
- *
- * Used to detect when a symbol is being expanded recursively, to allow
- * flagging an error. */
-struct annotation_nesting {
-    struct ovs_list node;
-    const struct expr_symbol *symbol;
-};
-
-static struct expr *expr_annotate_(struct expr *, const struct shash *symtab,
-                                   struct ovs_list *nesting, char **errorp);
-
-static struct expr *
-parse_and_annotate(const char *s, const struct shash *symtab,
-                   struct ovs_list *nesting, char **errorp)
-{
-    char *error;
-    struct expr *expr;
-
-    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, &error);
-    if (expr) {
-        expr = expr_annotate_(expr, symtab, nesting, &error);
-    }
-    if (expr) {
-        *errorp = NULL;
-    } else {
-        *errorp = xasprintf("Error parsing expression `%s' encountered as "
-                            "prerequisite or predicate of initial expression: "
-                            "%s", s, error);
-        free(error);
-    }
-    return expr;
-}
-
-static struct expr *
-expr_annotate_cmp(struct expr *expr, const struct shash *symtab,
-                  bool append_prereqs, struct ovs_list *nesting, char **errorp)
-{
-    const struct expr_symbol *symbol = expr->cmp.symbol;
-    const struct annotation_nesting *iter;
-    LIST_FOR_EACH (iter, node, nesting) {
-        if (iter->symbol == symbol) {
-            *errorp = xasprintf("Recursive expansion of symbol `%s'.",
-                                symbol->name);
-            expr_destroy(expr);
-            return NULL;
-        }
-    }
-
-    struct annotation_nesting an;
-    an.symbol = symbol;
-    ovs_list_push_back(nesting, &an.node);
-
-    struct expr *prereqs = NULL;
-    if (append_prereqs && symbol->prereqs) {
-        prereqs = parse_and_annotate(symbol->prereqs, symtab, nesting, errorp);
-        if (!prereqs) {
-            goto error;
-        }
-    }
-
-    if (symbol->parent) {
-        expr->cmp.symbol = symbol->parent;
-        mf_subvalue_shift(&expr->cmp.value, symbol->parent_ofs);
-        mf_subvalue_shift(&expr->cmp.mask, symbol->parent_ofs);
-    } else if (symbol->predicate) {
-        struct expr *predicate;
-
-        predicate = parse_and_annotate(symbol->predicate, symtab,
-                                       nesting, errorp);
-        if (!predicate) {
-            goto error;
-        }
-
-        bool positive = (expr->cmp.value.integer & htonll(1)) != 0;
-        positive ^= expr->cmp.relop == EXPR_R_NE;
-        if (!positive) {
-            expr_not(predicate);
-        }
-
-        expr_destroy(expr);
-        expr = predicate;
-    }
-
-    *errorp = NULL;
-    ovs_list_remove(&an.node);
-    return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr;
-
-error:
-    expr_destroy(expr);
-    expr_destroy(prereqs);
-    ovs_list_remove(&an.node);
-    return NULL;
-}
-
-/* Append (logical AND) prerequisites for given symbol to the expression. */
-static struct expr *
-expr_append_prereqs(struct expr *expr, const struct expr_symbol *symbol,
-                    const struct shash *symtab, struct ovs_list *nesting,
-                    char **errorp)
-{
-    struct expr *prereqs = NULL;
-
-    if (symbol->prereqs) {
-        prereqs = parse_and_annotate(symbol->prereqs, symtab, nesting, errorp);
-        if (!prereqs) {
-            expr_destroy(expr);
-            return NULL;
-        }
-    }
-
-    return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr;
-}
-
-static const struct expr_symbol *expr_get_unique_symbol(
-    const struct expr *expr);
-
-/* Ordinarily, annotation adds prerequisites to the expression, and that's what
- * this function does if 'append_prereqs' is true.  If 'append_prereqs' is
- * false, this function ignores prerequisites (in which case the caller must
- * have arranged to deal with them). */
-static struct expr *
-expr_annotate__(struct expr *expr, const struct shash *symtab,
-                bool append_prereqs, struct ovs_list *nesting, char **errorp)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return expr_annotate_cmp(expr, symtab, append_prereqs, nesting,
-                                 errorp);
-
-    case EXPR_T_AND:
-    case EXPR_T_OR: {
-        struct expr *sub, *next;
-
-        /* Detect whether every term in 'expr' mentions the same symbol.  If
-         * so, then suppress prerequisites for that symbol for those terms and
-         * instead apply them once at our higher level.
-         *
-         * If 'append_prereqs' is false, though, we're not supposed to handle
-         * prereqs at all (because our caller is already doing it). */
-        if (append_prereqs) {
-            const struct expr_symbol *sym = expr_get_unique_symbol(expr);
-            if (sym) {
-                append_prereqs = false;
-                expr = expr_append_prereqs(expr, sym, symtab, nesting, errorp);
-                if (!expr) {
-                    return NULL;
-                }
-            }
-        }
-
-        LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            ovs_list_remove(&sub->node);
-            struct expr *new_sub = expr_annotate__(sub, symtab, append_prereqs,
-                                                   nesting, errorp);
-            if (!new_sub) {
-                expr_destroy(expr);
-                return NULL;
-            }
-            expr_insert_andor(expr, next, new_sub);
-        }
-        *errorp = NULL;
-        return expr;
-    }
-
-    case EXPR_T_BOOLEAN:
-    case EXPR_T_CONDITION:
-        *errorp = NULL;
-        return expr;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Same interface and purpose as expr_annotate(), with an additional parameter
- * for internal bookkeeping.
- *
- * Uses 'nesting' to ensure that a given symbol is not recursively expanded. */
-static struct expr *
-expr_annotate_(struct expr *expr, const struct shash *symtab,
-               struct ovs_list *nesting, char **errorp)
-{
-    return expr_annotate__(expr, symtab, true, nesting, errorp);
-}
-
-/* "Annotates" 'expr', which does the following:
- *
- *     - Applies prerequisites, by locating each comparison operator whose
- *       field has a prerequisite and adding a logical AND against those
- *       prerequisites.
- *
- *     - Expands references to subfield symbols, by replacing them by
- *       references to their underlying field symbols (suitably shifted).
- *
- *     - Expands references to predicate symbols, by replacing them by the
- *       expressions that they expand to.
- *
- * In each case, annotation occurs recursively as necessary.
- *
- * If successful, returns the annotated expression and sets '*errorp' to NULL.
- * On failure, returns NULL and sets '*errorp' to an explanatory error message,
- * which the caller must free.  In either case, the caller transfers ownership
- * of 'expr' and receives ownership of the returned expression, if any. */
-struct expr *
-expr_annotate(struct expr *expr, const struct shash *symtab, char **errorp)
-{
-    struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting);
-    return expr_annotate_(expr, symtab, &nesting, errorp);
-}
-
-static struct expr *
-expr_simplify_eq(struct expr *expr)
-{
-    const union mf_subvalue *mask = &expr->cmp.mask;
-    if (is_all_zeros(mask, sizeof *mask)) {
-        /* Simplify "ip4.dst == 0/0" to just "1" (plus a prerequisite). */
-        expr_destroy(expr);
-        return expr_create_boolean(true);
-    }
-    return expr;
-}
-
-static struct expr *
-expr_simplify_ne(struct expr *expr)
-{
-    struct expr *new = NULL;
-    const union mf_subvalue *value = &expr->cmp.value;
-    const union mf_subvalue *mask = &expr->cmp.mask;
-    int w = expr->cmp.symbol->width;
-    int i;
-
-    for (i = 0; (i = bitwise_scan(mask, sizeof *mask, true, i, w)) < w; i++) {
-        struct expr *e;
-
-        e = xzalloc(sizeof *e);
-        e->type = EXPR_T_CMP;
-        e->cmp.symbol = expr->cmp.symbol;
-        e->cmp.relop = EXPR_R_EQ;
-        bitwise_put_bit(&e->cmp.value, sizeof e->cmp.value, i,
-                        !bitwise_get_bit(value, sizeof *value, i));
-        bitwise_put1(&e->cmp.mask, sizeof e->cmp.mask, i);
-
-        new = expr_combine(EXPR_T_OR, new, e);
-    }
-    if (!new) {
-        /* Handle a comparison like "ip4.dst != 0/0", where the mask has no
-         * 1-bits.
-         *
-         * The correct result for this expression may not be obvious.  It's
-         * easier to understand that "ip4.dst == 0/0" should be true, since 0/0
-         * matches every IPv4 address; then, "ip4.dst != 0/0" should have the
-         * opposite result. */
-        new = expr_create_boolean(false);
-    }
-
-    expr_destroy(expr);
-
-    return new;
-}
-
-static struct expr *
-expr_simplify_relational(struct expr *expr)
-{
-    const union mf_subvalue *value = &expr->cmp.value;
-    int start, n_bits, end;
-
-    find_bitwise_range(&expr->cmp.mask, expr->cmp.symbol->width,
-                       &start, &n_bits);
-    ovs_assert(n_bits > 0);
-    end = start + n_bits;
-
-    /* Handle some special cases.
-     *
-     * These optimize to just "true":
-     *
-     *    tcp.dst >= 0
-     *    tcp.dst <= 65535
-     *
-     * These are easier to understand, and equivalent, when treated as if
-     * > or < were !=:
-     *
-     *    tcp.dst > 0
-     *    tcp.dst < 65535
-     */
-    bool lt = expr->cmp.relop == EXPR_R_LT || expr->cmp.relop == EXPR_R_LE;
-    bool eq = expr->cmp.relop == EXPR_R_LE || expr->cmp.relop == EXPR_R_GE;
-    if (bitwise_scan(value, sizeof *value, !lt, start, end) == end) {
-        if (eq) {
-            expr_destroy(expr);
-            return expr_create_boolean(true);
-        } else {
-            return expr_simplify_ne(expr);
-        }
-    }
-
-    /* Reduce "tcp.dst >= 1234" to "tcp.dst == 1234 || tcp.dst > 1234",
-     * and similarly for "tcp.dst <= 1234". */
-    struct expr *new = NULL;
-    if (eq) {
-        new = xmemdup(expr, sizeof *expr);
-        new->cmp.relop = EXPR_R_EQ;
-    }
-
-    for (int z = bitwise_scan(value, sizeof *value, lt, start, end);
-         z < end;
-         z = bitwise_scan(value, sizeof *value, lt, z + 1, end)) {
-        struct expr *e;
-
-        e = xmemdup(expr, sizeof *expr);
-        e->cmp.relop = EXPR_R_EQ;
-        bitwise_toggle_bit(&e->cmp.value, sizeof e->cmp.value, z);
-        bitwise_zero(&e->cmp.value, sizeof e->cmp.value, start, z - start);
-        bitwise_zero(&e->cmp.mask, sizeof e->cmp.mask, start, z - start);
-        new = expr_combine(EXPR_T_OR, new, e);
-    }
-    expr_destroy(expr);
-    return new ? new : expr_create_boolean(false);
-}
-
-/* Resolves condition and replaces the expression with a boolean. */
-static struct expr *
-expr_simplify_condition(struct expr *expr,
-                        bool (*is_chassis_resident)(const void *c_aux,
-                                                    const char *port_name),
-                        const void *c_aux)
-{
-    bool result;
-
-    switch (expr->cond.type) {
-    case EXPR_COND_CHASSIS_RESIDENT:
-        result = is_chassis_resident(c_aux, expr->cond.string);
-        break;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-
-    result ^= expr->cond.not;
-    expr_destroy(expr);
-    return expr_create_boolean(result);
-}
-
-/* Takes ownership of 'expr' and returns an equivalent expression whose
- * EXPR_T_CMP nodes use only tests for equality (EXPR_R_EQ). */
-struct expr *
-expr_simplify(struct expr *expr,
-              bool (*is_chassis_resident)(const void *c_aux,
-                                          const char *port_name),
-              const void *c_aux)
-{
-    struct expr *sub, *next;
-
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return (!expr->cmp.symbol->width ? expr
-                : expr->cmp.relop == EXPR_R_EQ ? expr_simplify_eq(expr)
-                : expr->cmp.relop == EXPR_R_NE ? expr_simplify_ne(expr)
-                : expr_simplify_relational(expr));
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            ovs_list_remove(&sub->node);
-            expr_insert_andor(expr, next,
-                              expr_simplify(sub, is_chassis_resident, c_aux));
-        }
-        return expr_fix(expr);
-
-    case EXPR_T_BOOLEAN:
-        return expr;
-
-    case EXPR_T_CONDITION:
-        return expr_simplify_condition(expr, is_chassis_resident, c_aux);
-    }
-    OVS_NOT_REACHED();
-}
-
-/* Tests whether 'expr' is an expression over exactly one symbol: that is,
- * whether it is either a EXPR_T_CMP node or a tree of ANDs and ORs all over
- * the same symbol.  If it is, returns the symbol in question.  If it is not
- * (that is, if there is more than one symbol or no symbols at all), returns
- * NULL. */
-static const struct expr_symbol *
-expr_get_unique_symbol(const struct expr *expr)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return expr->cmp.symbol;
-
-    case EXPR_T_AND:
-    case EXPR_T_OR: {
-        const struct expr_symbol *prev = NULL;
-        struct expr *sub;
-
-        LIST_FOR_EACH (sub, node, &expr->andor) {
-            const struct expr_symbol *symbol = expr_get_unique_symbol(sub);
-            if (!symbol || (prev && symbol != prev)) {
-                return NULL;
-            }
-            prev = symbol;
-        }
-        return prev;
-    }
-
-    case EXPR_T_BOOLEAN:
-    case EXPR_T_CONDITION:
-        return NULL;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-struct expr_sort {
-    struct expr *expr;
-    const struct expr_symbol *symbol;
-    enum expr_type type;
-};
-
-static int
-compare_expr_sort(const void *a_, const void *b_)
-{
-    const struct expr_sort *a = a_;
-    const struct expr_sort *b = b_;
-
-    if (a->type != b->type) {
-        return a->type < b->type ? -1 : 1;
-    } else if (a->symbol) {
-        int cmp = strcmp(a->symbol->name, b->symbol->name);
-        if (cmp) {
-            return cmp;
-        }
-
-        enum expr_type a_type = a->expr->type;
-        enum expr_type b_type = a->expr->type;
-        return a_type < b_type ? -1 : a_type > b_type;
-    } else if (a->type == EXPR_T_AND || a->type == EXPR_T_OR) {
-        size_t a_len = ovs_list_size(&a->expr->andor);
-        size_t b_len = ovs_list_size(&b->expr->andor);
-        return a_len < b_len ? -1 : a_len > b_len;
-    } else {
-        return 0;
-    }
-}
-
-static struct expr *crush_cmps(struct expr *, const struct expr_symbol *);
-
-static bool
-disjunction_matches_string(const struct expr *or, const char *s)
-{
-    const struct expr *sub;
-
-    LIST_FOR_EACH (sub, node, &or->andor) {
-        if (!strcmp(sub->cmp.string, s)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-/* Implementation of crush_cmps() for expr->type == EXPR_T_AND and a
- * string-typed 'symbol'. */
-static struct expr *
-crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
-{
-    ovs_assert(!ovs_list_is_short(&expr->andor));
-
-    struct expr *singleton = NULL;
-
-    /* First crush each subexpression into either a single EXPR_T_CMP or an
-     * EXPR_T_OR with EXPR_T_CMP subexpressions. */
-    struct expr *sub, *next = NULL;
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        ovs_list_remove(&sub->node);
-        struct expr *new = crush_cmps(sub, symbol);
-        switch (new->type) {
-        case EXPR_T_CMP:
-            if (!singleton) {
-                ovs_list_insert(&next->node, &new->node);
-                singleton = new;
-            } else {
-                bool match = !strcmp(new->cmp.string, singleton->cmp.string);
-                expr_destroy(new);
-                if (!match) {
-                    expr_destroy(expr);
-                    return expr_create_boolean(false);
-                }
-            }
-            break;
-        case EXPR_T_AND:
-            OVS_NOT_REACHED();
-        case EXPR_T_OR:
-            ovs_list_insert(&next->node, &new->node);
-            break;
-        case EXPR_T_BOOLEAN:
-            if (!new->boolean) {
-                expr_destroy(expr);
-                return new;
-            }
-            free(new);
-            break;
-        case EXPR_T_CONDITION:
-            OVS_NOT_REACHED();
-        }
-    }
-
-    /* If we have a singleton, then the result is either the singleton itself
-     * (if the ORs allow the singleton) or false. */
-    if (singleton) {
-        LIST_FOR_EACH (sub, node, &expr->andor) {
-            if (sub->type == EXPR_T_OR
-                && !disjunction_matches_string(sub, singleton->cmp.string)) {
-                expr_destroy(expr);
-                return expr_create_boolean(false);
-            }
-        }
-        ovs_list_remove(&singleton->node);
-        expr_destroy(expr);
-        return singleton;
-    }
-
-    /* Otherwise the result is the intersection of all of the ORs. */
-    struct sset result = SSET_INITIALIZER(&result);
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        struct sset strings = SSET_INITIALIZER(&strings);
-        const struct expr *s;
-        LIST_FOR_EACH (s, node, &sub->andor) {
-            sset_add(&strings, s->cmp.string);
-        }
-        if (sset_is_empty(&result)) {
-            sset_swap(&result, &strings);
-        } else {
-            sset_intersect(&result, &strings);
-        }
-        sset_destroy(&strings);
-
-        if (sset_is_empty(&result)) {
-            expr_destroy(expr);
-            sset_destroy(&result);
-            return expr_create_boolean(false);
-        }
-    }
-
-    expr_destroy(expr);
-    expr = expr_create_andor(EXPR_T_OR);
-
-    const char *string;
-    SSET_FOR_EACH (string, &result) {
-        sub = xmalloc(sizeof *sub);
-        sub->type = EXPR_T_CMP;
-        sub->cmp.relop = EXPR_R_EQ;
-        sub->cmp.symbol = symbol;
-        sub->cmp.string = xstrdup(string);
-        ovs_list_push_back(&expr->andor, &sub->node);
-    }
-    sset_destroy(&result);
-    return expr_fix(expr);
-}
-
-/* Implementation of crush_cmps() for expr->type == EXPR_T_AND and a
- * numeric-typed 'symbol'. */
-static struct expr *
-crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
-{
-    ovs_assert(!ovs_list_is_short(&expr->andor));
-
-    union mf_subvalue value, mask;
-    memset(&value, 0, sizeof value);
-    memset(&mask, 0, sizeof mask);
-
-    struct expr *sub, *next = NULL;
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        ovs_list_remove(&sub->node);
-        struct expr *new = crush_cmps(sub, symbol);
-        switch (new->type) {
-        case EXPR_T_CMP:
-            if (!mf_subvalue_intersect(&value, &mask,
-                                       &new->cmp.value, &new->cmp.mask,
-                                       &value, &mask)) {
-                expr_destroy(new);
-                expr_destroy(expr);
-                return expr_create_boolean(false);
-            }
-            expr_destroy(new);
-            break;
-        case EXPR_T_AND:
-            OVS_NOT_REACHED();
-        case EXPR_T_OR:
-            ovs_list_insert(&next->node, &new->node);
-            break;
-        case EXPR_T_BOOLEAN:
-            if (!new->boolean) {
-                expr_destroy(expr);
-                return new;
-            }
-            expr_destroy(new);
-            break;
-        case EXPR_T_CONDITION:
-            OVS_NOT_REACHED();
-        }
-    }
-    if (ovs_list_is_empty(&expr->andor)) {
-        if (is_all_zeros(&mask, sizeof mask)) {
-            expr_destroy(expr);
-            return expr_create_boolean(true);
-        } else {
-            struct expr *cmp;
-            cmp = xmalloc(sizeof *cmp);
-            cmp->type = EXPR_T_CMP;
-            cmp->cmp.symbol = symbol;
-            cmp->cmp.relop = EXPR_R_EQ;
-            cmp->cmp.value = value;
-            cmp->cmp.mask = mask;
-            expr_destroy(expr);
-            return cmp;
-        }
-    } else if (ovs_list_is_short(&expr->andor)) {
-        /* Transform "a && (b || c || d)" into "ab || ac || ad" where "ab" is
-         * computed as "a && b", etc. */
-        struct expr *disjuncts = expr_from_node(ovs_list_pop_front(&expr->andor));
-        struct expr *or;
-
-        or = xmalloc(sizeof *or);
-        or->type = EXPR_T_OR;
-        ovs_list_init(&or->andor);
-
-        ovs_assert(disjuncts->type == EXPR_T_OR);
-        LIST_FOR_EACH_SAFE (sub, next, node, &disjuncts->andor) {
-            ovs_assert(sub->type == EXPR_T_CMP);
-            ovs_list_remove(&sub->node);
-            if (mf_subvalue_intersect(&value, &mask,
-                                      &sub->cmp.value, &sub->cmp.mask,
-                                      &sub->cmp.value, &sub->cmp.mask)) {
-                ovs_list_push_back(&or->andor, &sub->node);
-            } else {
-                expr_destroy(sub);
-            }
-        }
-        free(disjuncts);
-        free(expr);
-        if (ovs_list_is_empty(&or->andor)) {
-            free(or);
-            return expr_create_boolean(false);
-        } else if (ovs_list_is_short(&or->andor)) {
-            struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor));
-            free(or);
-            return cmp;
-        } else {
-            return or;
-        }
-    } else {
-        /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into
-         *           "(xa0b0 || xa0b1 || xa1b0 || xa1b1) && ...". */
-        struct expr *as = expr_from_node(ovs_list_pop_front(&expr->andor));
-        struct expr *bs = expr_from_node(ovs_list_pop_front(&expr->andor));
-        struct expr *new = NULL;
-        struct expr *or;
-
-        or = xmalloc(sizeof *or);
-        or->type = EXPR_T_OR;
-        ovs_list_init(&or->andor);
-
-        struct expr *a;
-        LIST_FOR_EACH (a, node, &as->andor) {
-            union mf_subvalue a_value, a_mask;
-
-            ovs_assert(a->type == EXPR_T_CMP);
-            if (!mf_subvalue_intersect(&value, &mask,
-                                       &a->cmp.value, &a->cmp.mask,
-                                       &a_value, &a_mask)) {
-                continue;
-            }
-
-            struct expr *b;
-            LIST_FOR_EACH (b, node, &bs->andor) {
-                ovs_assert(b->type == EXPR_T_CMP);
-                if (!new) {
-                    new = xmalloc(sizeof *new);
-                    new->type = EXPR_T_CMP;
-                    new->cmp.symbol = symbol;
-                    new->cmp.relop = EXPR_R_EQ;
-                }
-                if (mf_subvalue_intersect(&a_value, &a_mask,
-                                          &b->cmp.value, &b->cmp.mask,
-                                          &new->cmp.value, &new->cmp.mask)) {
-                    ovs_list_push_back(&or->andor, &new->node);
-                    new = NULL;
-                }
-            }
-        }
-        expr_destroy(as);
-        expr_destroy(bs);
-        free(new);
-
-        if (ovs_list_is_empty(&or->andor)) {
-            expr_destroy(expr);
-            free(or);
-            return expr_create_boolean(false);
-        } else if (ovs_list_is_short(&or->andor)) {
-            struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor));
-            free(or);
-            if (ovs_list_is_empty(&expr->andor)) {
-                expr_destroy(expr);
-                return crush_cmps(cmp, symbol);
-            } else {
-                return crush_cmps(expr_combine(EXPR_T_AND, cmp, expr), symbol);
-            }
-        } else if (!ovs_list_is_empty(&expr->andor)) {
-            struct expr *e = expr_combine(EXPR_T_AND, or, expr);
-            ovs_assert(!ovs_list_is_short(&e->andor));
-            return crush_cmps(e, symbol);
-        } else {
-            expr_destroy(expr);
-            return crush_cmps(or, symbol);
-        }
-    }
-}
-
-static int
-compare_cmps_3way(const struct expr *a, const struct expr *b)
-{
-    ovs_assert(a->cmp.symbol == b->cmp.symbol);
-    if (!a->cmp.symbol->width) {
-        return strcmp(a->cmp.string, b->cmp.string);
-    } else {
-        int d = memcmp(&a->cmp.value, &b->cmp.value, sizeof a->cmp.value);
-        if (!d) {
-            d = memcmp(&a->cmp.mask, &b->cmp.mask, sizeof a->cmp.mask);
-        }
-        return d;
-    }
-}
-
-static int
-compare_cmps_cb(const void *a_, const void *b_)
-{
-    const struct expr *const *ap = a_;
-    const struct expr *const *bp = b_;
-    const struct expr *a = *ap;
-    const struct expr *b = *bp;
-    return compare_cmps_3way(a, b);
-}
-
-/* Implementation of crush_cmps() for expr->type == EXPR_T_OR. */
-static struct expr *
-crush_or(struct expr *expr, const struct expr_symbol *symbol)
-{
-    struct expr *sub, *next = NULL;
-
-    /* First, crush all the subexpressions.  That might eliminate the
-     * OR-expression entirely; if so, return the result.  Otherwise, 'expr'
-     * is now a disjunction of cmps over the same symbol. */
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        ovs_list_remove(&sub->node);
-        expr_insert_andor(expr, next, crush_cmps(sub, symbol));
-    }
-    expr = expr_fix(expr);
-    if (expr->type != EXPR_T_OR) {
-        return expr;
-    }
-
-    /* Sort subexpressions by value and mask, to bring together duplicates. */
-    size_t n = ovs_list_size(&expr->andor);
-    struct expr **subs = xmalloc(n * sizeof *subs);
-
-    size_t i = 0;
-    LIST_FOR_EACH (sub, node, &expr->andor) {
-        subs[i++] = sub;
-    }
-    ovs_assert(i == n);
-
-    qsort(subs, n, sizeof *subs, compare_cmps_cb);
-
-    /* Eliminate duplicates. */
-    ovs_list_init(&expr->andor);
-    ovs_list_push_back(&expr->andor, &subs[0]->node);
-    for (i = 1; i < n; i++) {
-        struct expr *a = expr_from_node(ovs_list_back(&expr->andor));
-        struct expr *b = subs[i];
-        if (compare_cmps_3way(a, b)) {
-            ovs_list_push_back(&expr->andor, &b->node);
-        } else {
-            expr_destroy(b);
-        }
-    }
-    free(subs);
-    return expr_fix(expr);
-}
-
-/* Takes ownership of 'expr', which must have a unique symbol in the sense of
- * 'expr_get_unique_symbol(expr)', where 'symbol' is the symbol returned by
- * that function.  Returns an equivalent expression owned by the caller that is
- * a single EXPR_T_CMP or a disjunction of them or a EXPR_T_BOOLEAN. */
-static struct expr *
-crush_cmps(struct expr *expr, const struct expr_symbol *symbol)
-{
-    switch (expr->type) {
-    case EXPR_T_OR:
-        return crush_or(expr, symbol);
-
-    case EXPR_T_AND:
-        return (symbol->width
-                ? crush_and_numeric(expr, symbol)
-                : crush_and_string(expr, symbol));
-
-    case EXPR_T_CMP:
-        return expr;
-
-    case EXPR_T_BOOLEAN:
-        return expr;
-
-    /* Should not hit expression type condition, since crush_cmps is only
-     * called during expr_normalize, after expr_simplify which resolves
-     * all conditions. */
-    case EXPR_T_CONDITION:
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Applied to an EXPR_T_AND 'expr' whose subexpressions are in terms of only
- * EXPR_T_CMP, EXPR_T_AND, and EXPR_T_OR, this takes ownership of 'expr' and
- * returns a new expression in terms of EXPR_T_CMP, EXPR_T_AND, EXPR_T_OR, or
- * EXPR_T_BOOLEAN.
- *
- * The function attempts to bring together and combine clauses of the original
- * 'expr' that were in terms of a single variable.  For example, it combines
- * (x[0] == 1 && x[1] == 1) into the single x[0..1] == 3. */
-static struct expr *
-expr_sort(struct expr *expr)
-{
-    ovs_assert(expr->type == EXPR_T_AND);
-
-    size_t n = ovs_list_size(&expr->andor);
-    struct expr_sort *subs = xmalloc(n * sizeof *subs);
-    struct expr *sub;
-    size_t i;
-
-    i = 0;
-    LIST_FOR_EACH (sub, node, &expr->andor) {
-        subs[i].expr = sub;
-        subs[i].symbol = expr_get_unique_symbol(sub);
-        subs[i].type = subs[i].symbol ? EXPR_T_CMP : sub->type;
-        i++;
-    }
-    ovs_assert(i == n);
-
-    qsort(subs, n, sizeof *subs, compare_expr_sort);
-
-    ovs_list_init(&expr->andor);
-    free(expr);
-    expr = NULL;
-
-    for (i = 0; i < n; ) {
-        if (subs[i].symbol) {
-            size_t j;
-            for (j = i + 1; j < n; j++) {
-                if (subs[i].symbol != subs[j].symbol) {
-                    break;
-                }
-            }
-
-            struct expr *crushed;
-            if (j == i + 1) {
-                crushed = crush_cmps(subs[i].expr, subs[i].symbol);
-            } else {
-                struct expr *combined = subs[i].expr;
-                for (size_t k = i + 1; k < j; k++) {
-                    combined = expr_combine(EXPR_T_AND, combined,
-                                            subs[k].expr);
-                }
-                ovs_assert(!ovs_list_is_short(&combined->andor));
-                crushed = crush_cmps(combined, subs[i].symbol);
-            }
-            if (crushed->type == EXPR_T_BOOLEAN) {
-                if (!crushed->boolean) {
-                    for (size_t k = j; k < n; k++) {
-                        expr_destroy(subs[k].expr);
-                    }
-                    expr_destroy(expr);
-                    expr = crushed;
-                    break;
-                } else {
-                    free(crushed);
-                }
-            } else {
-                expr = expr_combine(EXPR_T_AND, expr, crushed);
-            }
-            i = j;
-        } else {
-            expr = expr_combine(EXPR_T_AND, expr, subs[i++].expr);
-        }
-    }
-    free(subs);
-
-    return expr;
-}
-
-static struct expr *expr_normalize_or(struct expr *expr);
-
-/* Returns 'expr', which is an AND, reduced to OR(AND(clause)) where
- * a clause is a cmp or a disjunction of cmps on a single field. */
-static struct expr *
-expr_normalize_and(struct expr *expr)
-{
-    expr = expr_sort(expr);
-    if (expr->type != EXPR_T_AND) {
-        return expr;
-    }
-
-    struct expr *a, *b;
-    LIST_FOR_EACH_SAFE (a, b, node, &expr->andor) {
-        if (&b->node == &expr->andor
-            || a->type != EXPR_T_CMP || b->type != EXPR_T_CMP
-            || a->cmp.symbol != b->cmp.symbol) {
-            continue;
-        } else if (a->cmp.symbol->width
-                   ? mf_subvalue_intersect(&a->cmp.value, &a->cmp.mask,
-                                           &b->cmp.value, &b->cmp.mask,
-                                           &b->cmp.value, &b->cmp.mask)
-                   : !strcmp(a->cmp.string, b->cmp.string)) {
-            ovs_list_remove(&a->node);
-            expr_destroy(a);
-        } else {
-            expr_destroy(expr);
-            return expr_create_boolean(false);
-        }
-    }
-    if (ovs_list_is_short(&expr->andor)) {
-        struct expr *sub = expr_from_node(ovs_list_front(&expr->andor));
-        free(expr);
-        return sub;
-    }
-
-    struct expr *sub;
-    LIST_FOR_EACH (sub, node, &expr->andor) {
-        if (sub->type == EXPR_T_CMP) {
-            continue;
-        }
-
-        ovs_assert(sub->type == EXPR_T_OR);
-        const struct expr_symbol *symbol = expr_get_unique_symbol(sub);
-        if (!symbol || symbol->must_crossproduct) {
-            struct expr *or = expr_create_andor(EXPR_T_OR);
-            struct expr *k;
-
-            LIST_FOR_EACH (k, node, &sub->andor) {
-                struct expr *and = expr_create_andor(EXPR_T_AND);
-                struct expr *m;
-
-                LIST_FOR_EACH (m, node, &expr->andor) {
-                    struct expr *term = m == sub ? k : m;
-                    if (term->type == EXPR_T_AND) {
-                        struct expr *p;
-
-                        LIST_FOR_EACH (p, node, &term->andor) {
-                            struct expr *new = expr_clone(p);
-                            ovs_list_push_back(&and->andor, &new->node);
-                        }
-                    } else {
-                        struct expr *new = expr_clone(term);
-                        ovs_list_push_back(&and->andor, &new->node);
-                    }
-                }
-                ovs_list_push_back(&or->andor, &and->node);
-            }
-            expr_destroy(expr);
-            return expr_normalize_or(or);
-        }
-    }
-    return expr;
-}
-
-static struct expr *
-expr_normalize_or(struct expr *expr)
-{
-    struct expr *sub, *next;
-
-    LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        if (sub->type == EXPR_T_AND) {
-            ovs_list_remove(&sub->node);
-
-            struct expr *new = expr_normalize_and(sub);
-            if (new->type == EXPR_T_BOOLEAN) {
-                if (new->boolean) {
-                    expr_destroy(expr);
-                    return new;
-                }
-                free(new);
-            } else {
-                expr_insert_andor(expr, next, new);
-            }
-        } else {
-            ovs_assert(sub->type == EXPR_T_CMP);
-        }
-    }
-    if (ovs_list_is_empty(&expr->andor)) {
-        free(expr);
-        return expr_create_boolean(false);
-    }
-    if (ovs_list_is_short(&expr->andor)) {
-        struct expr *e = expr_from_node(ovs_list_pop_front(&expr->andor));
-        free(expr);
-        return e;
-    }
-
-    return expr;
-}
-
-/* Takes ownership of 'expr', which is either a constant "true" or "false" or
- * an expression in terms of only relationals, AND, and OR.  Returns either a
- * constant "true" or "false" or 'expr' reduced to OR(AND(clause)) where a
- * clause is a cmp or a disjunction of cmps on a single field.  This form is
- * significant because it is a form that can be directly converted to OpenFlow
- * flows with the Open vSwitch "conjunctive match" extension.
- *
- * 'expr' must already have been simplified, with expr_simplify(). */
-struct expr *
-expr_normalize(struct expr *expr)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return expr;
-
-    case EXPR_T_AND:
-        return expr_normalize_and(expr);
-
-    case EXPR_T_OR:
-        return expr_normalize_or(expr);
-
-    case EXPR_T_BOOLEAN:
-        return expr;
-
-    /* Should not hit expression type condition, since expr_normalize is
-     * only called after expr_simplify, which resolves all conditions. */
-    case EXPR_T_CONDITION:
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Creates, initializes, and returns a new 'struct expr_match'.  If 'm' is
- * nonnull then it is copied into the new expr_match, otherwise the new
- * expr_match's 'match' member is initialized to a catch-all match for the
- * caller to refine in-place.
- *
- * If 'conj_id' is nonzero, adds one conjunction based on 'conj_id', 'clause',
- * and 'n_clauses' to the returned 'struct expr_match', otherwise the
- * expr_match will not have any conjunctions.
- *
- * The caller should use expr_match_add() to add the expr_match to a hash table
- * after it is finalized. */
-static struct expr_match *
-expr_match_new(const struct match *m, uint8_t clause, uint8_t n_clauses,
-               uint32_t conj_id)
-{
-    struct expr_match *match = xmalloc(sizeof *match);
-    if (m) {
-        match->match = *m;
-    } else {
-        match_init_catchall(&match->match);
-    }
-    if (conj_id) {
-        match->conjunctions = xmalloc(sizeof *match->conjunctions);
-        match->conjunctions[0].id = conj_id;
-        match->conjunctions[0].clause = clause;
-        match->conjunctions[0].n_clauses = n_clauses;
-        match->n = 1;
-        match->allocated = 1;
-    } else {
-        match->conjunctions = NULL;
-        match->n = 0;
-        match->allocated = 0;
-    }
-    return match;
-}
-
-/* Adds 'match' to hash table 'matches', which becomes the new owner of
- * 'match'.
- *
- * This might actually destroy 'match' because it gets merged together with
- * some existing conjunction.*/
-static void
-expr_match_add(struct hmap *matches, struct expr_match *match)
-{
-    uint32_t hash = match_hash(&match->match, 0);
-    struct expr_match *m;
-
-    HMAP_FOR_EACH_WITH_HASH (m, hmap_node, hash, matches) {
-        if (match_equal(&m->match, &match->match)) {
-            if (!m->n || !match->n) {
-                free(m->conjunctions);
-                m->conjunctions = NULL;
-                m->n = 0;
-                m->allocated = 0;
-            } else {
-                ovs_assert(match->n == 1);
-                if (m->n >= m->allocated) {
-                    m->conjunctions = x2nrealloc(m->conjunctions,
-                                                 &m->allocated,
-                                                 sizeof *m->conjunctions);
-                }
-                m->conjunctions[m->n++] = match->conjunctions[0];
-            }
-            free(match->conjunctions);
-            free(match);
-            return;
-        }
-    }
-
-    hmap_insert(matches, &match->hmap_node, hash);
-}
-
-/* Applies EXPR_T_CMP-typed 'expr' to 'm'.  This will only work properly if 'm'
- * doesn't already match on 'expr->cmp.symbol', because it replaces any
- * existing match on that symbol instead of intersecting with it.
- *
- * If 'expr' is a comparison on a string field, uses 'lookup_port' and 'aux' to
- * convert the string to a port number.  In such a case, if the port can't be
- * found, returns false.  In all other cases, returns true. */
-static bool
-constrain_match(const struct expr *expr,
-                bool (*lookup_port)(const void *aux,
-                                    const char *port_name,
-                                    unsigned int *portp),
-                const void *aux, struct match *m)
-{
-    ovs_assert(expr->type == EXPR_T_CMP);
-    if (expr->cmp.symbol->width) {
-        mf_mask_subfield(expr->cmp.symbol->field, &expr->cmp.value,
-                         &expr->cmp.mask, m);
-    } else {
-        unsigned int port;
-        if (!lookup_port(aux, expr->cmp.string, &port)) {
-            return false;
-        }
-
-        struct mf_subfield sf;
-        sf.field = expr->cmp.symbol->field;
-        sf.ofs = 0;
-        sf.n_bits = expr->cmp.symbol->field->n_bits;
-
-        union mf_subvalue x;
-        memset(&x, 0, sizeof x);
-        x.integer = htonll(port);
-
-        mf_write_subfield(&sf, &x, m);
-    }
-    return true;
-}
-
-static bool
-add_disjunction(const struct expr *or,
-                bool (*lookup_port)(const void *aux, const char *port_name,
-                                    unsigned int *portp),
-                const void *aux,
-                struct match *m, uint8_t clause, uint8_t n_clauses,
-                uint32_t conj_id, struct hmap *matches)
-{
-    struct expr *sub;
-    int n = 0;
-
-    ovs_assert(or->type == EXPR_T_OR);
-    LIST_FOR_EACH (sub, node, &or->andor) {
-        struct expr_match *match = expr_match_new(m, clause, n_clauses,
-                                                  conj_id);
-        if (constrain_match(sub, lookup_port, aux, &match->match)) {
-            expr_match_add(matches, match);
-            n++;
-        } else {
-            free(match->conjunctions);
-            free(match);
-        }
-    }
-
-    /* If n == 1, then this didn't really need to be a disjunction.  Oh well,
-     * that shouldn't happen much. */
-    return n > 0;
-}
-
-static void
-add_conjunction(const struct expr *and,
-                bool (*lookup_port)(const void *aux, const char *port_name,
-                                    unsigned int *portp),
-                const void *aux, uint32_t *n_conjsp, struct hmap *matches)
-{
-    struct match match;
-    int n_clauses = 0;
-    struct expr *sub;
-
-    match_init_catchall(&match);
-
-    ovs_assert(and->type == EXPR_T_AND);
-    LIST_FOR_EACH (sub, node, &and->andor) {
-        switch (sub->type) {
-        case EXPR_T_CMP:
-            if (!constrain_match(sub, lookup_port, aux, &match)) {
-                return;
-            }
-            break;
-        case EXPR_T_OR:
-            n_clauses++;
-            break;
-        case EXPR_T_AND:
-        case EXPR_T_BOOLEAN:
-        case EXPR_T_CONDITION:
-        default:
-            OVS_NOT_REACHED();
-        }
-    }
-
-    if (!n_clauses) {
-        expr_match_add(matches, expr_match_new(&match, 0, 0, 0));
-    } else if (n_clauses == 1) {
-        LIST_FOR_EACH (sub, node, &and->andor) {
-            if (sub->type == EXPR_T_OR) {
-                add_disjunction(sub, lookup_port, aux, &match, 0, 0, 0,
-                                matches);
-            }
-        }
-    } else {
-        int clause = 0;
-        (*n_conjsp)++;
-        LIST_FOR_EACH (sub, node, &and->andor) {
-            if (sub->type == EXPR_T_OR) {
-                if (!add_disjunction(sub, lookup_port, aux, &match, clause++,
-                                     n_clauses, *n_conjsp, matches)) {
-                    /* This clause can't ever match, so we might as well skip
-                     * adding the other clauses--the overall disjunctive flow
-                     * can't ever match.  Ideally we would also back out all of
-                     * the clauses we already added, but that seems like a lot
-                     * of trouble for a case that might never occur in
-                     * practice. */
-                    return;
-                }
-            }
-        }
-
-        /* Add the flow that matches on conj_id. */
-        match_set_conj_id(&match, *n_conjsp);
-        expr_match_add(matches, expr_match_new(&match, 0, 0, 0));
-    }
-}
-
-static void
-add_cmp_flow(const struct expr *cmp,
-             bool (*lookup_port)(const void *aux, const char *port_name,
-                                 unsigned int *portp),
-             const void *aux, struct hmap *matches)
-{
-    struct expr_match *m = expr_match_new(NULL, 0, 0, 0);
-    if (constrain_match(cmp, lookup_port, aux, &m->match)) {
-        expr_match_add(matches, m);
-    } else {
-        free(m);
-    }
-}
-
-/* Converts 'expr', which must be in the form returned by expr_normalize(), to
- * a collection of Open vSwitch flows in 'matches', which this function
- * initializes to an hmap of "struct expr_match" structures.  Returns the
- * number of conjunctive match IDs consumed by 'matches', which uses
- * conjunctive match IDs beginning with 0; the caller must offset or remap them
- * into the desired range as necessary.
- *
- * The matches inserted into 'matches' will be of three distinct kinds:
- *
- *     - Ordinary flows.  The caller should add these OpenFlow flows with
- *       its desired actions.
- *
- *     - Conjunctive flows, distinguished by 'n > 0' in the expr_match
- *       structure.  The caller should add these OpenFlow flows with the
- *       conjunction(id, k/n) actions as specified in the 'conjunctions' array,
- *       remapping the ids.
- *
- *     - conj_id flows, distinguished by matching on the "conj_id" field.  The
- *       caller should remap the conj_id and add the OpenFlow flow with its
- *       desired actions.
- *
- * 'lookup_port' must be a function to map from a port name to a port number.
- * When successful, 'lookup_port' stores the port number into '*portp' and
- * returns true; when there is no port by the given name, it returns false.
- * 'aux' is passed to 'lookup_port' as auxiliary data.  Any comparisons against
- * string fields in 'expr' are translated into integers through this function.
- * A comparison against a string that is not in 'ports' acts like a Boolean
- * "false"; that is, it will always fail to match.  For a simple expression,
- * this means that the overall expression always fails to match, but an
- * expression with a disjunction on the string field might still match on other
- * port names.
- *
- * (This treatment of string fields might be too simplistic in general, but it
- * seems reasonable for now when string fields are used only for ports.) */
-uint32_t
-expr_to_matches(const struct expr *expr,
-                bool (*lookup_port)(const void *aux, const char *port_name,
-                                    unsigned int *portp),
-                const void *aux, struct hmap *matches)
-{
-    uint32_t n_conjs = 0;
-
-    hmap_init(matches);
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        add_cmp_flow(expr, lookup_port, aux, matches);
-        break;
-
-    case EXPR_T_AND:
-        add_conjunction(expr, lookup_port, aux, &n_conjs, matches);
-        break;
-
-    case EXPR_T_OR:
-        if (expr_get_unique_symbol(expr)) {
-            struct expr *sub;
-
-            LIST_FOR_EACH (sub, node, &expr->andor) {
-                add_cmp_flow(sub, lookup_port, aux, matches);
-            }
-        } else {
-            struct expr *sub;
-
-            LIST_FOR_EACH (sub, node, &expr->andor) {
-                if (sub->type == EXPR_T_AND) {
-                    add_conjunction(sub, lookup_port, aux, &n_conjs, matches);
-                } else {
-                    add_cmp_flow(sub, lookup_port, aux, matches);
-                }
-            }
-        }
-        break;
-
-    case EXPR_T_BOOLEAN:
-        if (expr->boolean) {
-            struct expr_match *m = expr_match_new(NULL, 0, 0, 0);
-            expr_match_add(matches, m);
-        } else {
-            /* No match. */
-        }
-        break;
-
-    /* Should not hit expression type condition, since expr_to_matches is
-     * only called after expr_simplify, which resolves all conditions. */
-    case EXPR_T_CONDITION:
-    default:
-        OVS_NOT_REACHED();
-    }
-    return n_conjs;
-}
-
-/* Destroys all of the 'struct expr_match'es in 'matches', as well as the
- * 'matches' hmap itself. */
-void
-expr_matches_destroy(struct hmap *matches)
-{
-    struct expr_match *m;
-
-    HMAP_FOR_EACH_POP (m, hmap_node, matches) {
-        free(m->conjunctions);
-        free(m);
-    }
-    hmap_destroy(matches);
-}
-
-/* Prints a representation of the 'struct expr_match'es in 'matches' to
- * 'stream'. */
-void
-expr_matches_print(const struct hmap *matches, FILE *stream)
-{
-    if (hmap_is_empty(matches)) {
-        fputs("(no flows)\n", stream);
-        return;
-    }
-
-    const struct expr_match *m;
-    HMAP_FOR_EACH (m, hmap_node, matches) {
-        char *s = match_to_string(&m->match, NULL, OFP_DEFAULT_PRIORITY);
-        fputs(s, stream);
-        free(s);
-
-        if (m->n) {
-            for (int i = 0; i < m->n; i++) {
-                const struct cls_conjunction *c = &m->conjunctions[i];
-                fprintf(stream, "%c conjunction(%"PRIu32", %d/%d)",
-                        i == 0 ? ':' : ',', c->id, c->clause, c->n_clauses);
-            }
-        }
-        putc('\n', stream);
-    }
-}
-
-/* Returns true if 'expr' honors the invariants for expressions (see the large
- * comment above "struct expr" in expr.h), false otherwise. */
-bool
-expr_honors_invariants(const struct expr *expr)
-{
-    const struct expr *sub;
-
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        if (expr->cmp.symbol->width) {
-            for (int i = 0; i < ARRAY_SIZE(expr->cmp.value.be64); i++) {
-                if (expr->cmp.value.be64[i] & ~expr->cmp.mask.be64[i]) {
-                    return false;
-                }
-            }
-        }
-        return true;
-
-    case EXPR_T_AND:
-    case EXPR_T_OR:
-        if (ovs_list_is_short(&expr->andor)) {
-            return false;
-        }
-        LIST_FOR_EACH (sub, node, &expr->andor) {
-            if (sub->type == expr->type || !expr_honors_invariants(sub)) {
-                return false;
-            }
-        }
-        return true;
-
-    case EXPR_T_BOOLEAN:
-    case EXPR_T_CONDITION:
-        return true;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static bool
-expr_is_normalized_and(const struct expr *expr)
-{
-    /* XXX should also check that no symbol is repeated. */
-    const struct expr *sub;
-
-    LIST_FOR_EACH (sub, node, &expr->andor) {
-        if (!expr_get_unique_symbol(sub)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-/* Returns true if 'expr' is in the form returned by expr_normalize(), false
- * otherwise. */
-bool
-expr_is_normalized(const struct expr *expr)
-{
-    switch (expr->type) {
-    case EXPR_T_CMP:
-        return true;
-
-    case EXPR_T_AND:
-        return expr_is_normalized_and(expr);
-
-    case EXPR_T_OR:
-        if (!expr_get_unique_symbol(expr)) {
-            const struct expr *sub;
-
-            LIST_FOR_EACH (sub, node, &expr->andor) {
-                if (!expr_get_unique_symbol(sub)
-                    && !expr_is_normalized_and(sub)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-
-    case EXPR_T_BOOLEAN:
-        return true;
-
-    case EXPR_T_CONDITION:
-        return false;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static bool
-expr_evaluate_andor(const struct expr *e, const struct flow *f,
-                    bool short_circuit,
-                    bool (*lookup_port)(const void *aux, const char *port_name,
-                                        unsigned int *portp),
-                    const void *aux)
-{
-    const struct expr *sub;
-
-    LIST_FOR_EACH (sub, node, &e->andor) {
-        if (expr_evaluate(sub, f, lookup_port, aux) == short_circuit) {
-            return short_circuit;
-        }
-    }
-    return !short_circuit;
-}
-
-static bool
-expr_evaluate_cmp(const struct expr *e, const struct flow *f,
-                  bool (*lookup_port)(const void *aux, const char *port_name,
-                                      unsigned int *portp),
-                  const void *aux)
-{
-    const struct expr_symbol *s = e->cmp.symbol;
-    const struct mf_field *field = s->field;
-
-    int cmp;
-    if (e->cmp.symbol->width) {
-        int n_bytes = field->n_bytes;
-        const uint8_t *cst = &e->cmp.value.u8[sizeof e->cmp.value - n_bytes];
-        const uint8_t *mask = &e->cmp.mask.u8[sizeof e->cmp.mask - n_bytes];
-
-        /* Get field value and mask off undesired bits. */
-        union mf_value value;
-        mf_get_value(field, f, &value);
-        for (int i = 0; i < field->n_bytes; i++) {
-            value.b[i] &= mask[i];
-        }
-
-        /* Compare against constant. */
-        cmp = memcmp(&value, cst, n_bytes);
-    } else {
-        /* Get field value. */
-        struct mf_subfield sf = { .field = field, .ofs = 0,
-                                  .n_bits = field->n_bits };
-        uint64_t value = mf_get_subfield(&sf, f);
-
-        /* Get constant. */
-        unsigned int cst;
-        if (!lookup_port(aux, e->cmp.string, &cst)) {
-            return false;
-        }
-
-        /* Compare. */
-        cmp = value < cst ? -1 : value > cst;
-    }
-
-    return expr_relop_test(e->cmp.relop, cmp);
-}
-
-/* Evaluates 'e' against microflow 'uflow' and returns the result.
- *
- * 'lookup_port' must be a function to map from a port name to a port number
- * and 'aux' auxiliary data to pass to it; see expr_to_matches() for more
- * details.
- *
- * This isn't particularly fast.  For performance-sensitive tasks, use
- * expr_to_matches() and the classifier. */
-bool
-expr_evaluate(const struct expr *e, const struct flow *uflow,
-              bool (*lookup_port)(const void *aux, const char *port_name,
-                                  unsigned int *portp),
-              const void *aux)
-{
-    switch (e->type) {
-    case EXPR_T_CMP:
-        return expr_evaluate_cmp(e, uflow, lookup_port, aux);
-
-    case EXPR_T_AND:
-        return expr_evaluate_andor(e, uflow, false, lookup_port, aux);
-
-    case EXPR_T_OR:
-        return expr_evaluate_andor(e, uflow, true, lookup_port, aux);
-
-    case EXPR_T_BOOLEAN:
-        return e->boolean;
-
-    case EXPR_T_CONDITION:
-        /* Assume tests calling expr_evaluate are not chassis specific, so
-         * is_chassis_resident evaluates as true. */
-        return (e->cond.not ? false : true);
-
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-/* Action parsing helper. */
-
-/* Checks that 'f' is 'n_bits' wide (where 'n_bits == 0' means that 'f' must be
- * a string field) and, if 'rw' is true, that 'f' is modifiable.  Returns NULL
- * if 'f' is acceptable, otherwise a malloc()'d error message that the caller
- * must free(). */
-char * OVS_WARN_UNUSED_RESULT
-expr_type_check(const struct expr_field *f, int n_bits, bool rw)
-{
-    if (n_bits != f->n_bits) {
-        if (n_bits && f->n_bits) {
-            return xasprintf("Cannot use %d-bit field %s[%d..%d] "
-                             "where %d-bit field is required.",
-                             f->n_bits, f->symbol->name,
-                             f->ofs, f->ofs + f->n_bits - 1,
-                             n_bits);
-        } else if (n_bits) {
-            return xasprintf("Cannot use string field %s where numeric "
-                             "field is required.", f->symbol->name);
-        } else {
-            return xasprintf("Cannot use numeric field %s where string "
-                             "field is required.", f->symbol->name);
-        }
-    }
-
-    if (rw && !f->symbol->rw) {
-        return xasprintf("Field %s is not modifiable.", f->symbol->name);
-    }
-
-    return NULL;
-}
-
-/* Returns the mf_subfield that corresponds to 'f'. */
-struct mf_subfield
-expr_resolve_field(const struct expr_field *f)
-{
-    const struct expr_symbol *symbol = f->symbol;
-    int ofs = f->ofs;
-
-    while (symbol->parent) {
-        ofs += symbol->parent_ofs;
-        symbol = symbol->parent;
-    }
-
-    int n_bits = symbol->width ? f->n_bits : symbol->field->n_bits;
-    return (struct mf_subfield) { symbol->field, ofs, n_bits };
-}
-
-static bool
-microflow_is_chassis_resident_cb(const void *c_aux OVS_UNUSED,
-                                 const char *port_name OVS_UNUSED)
-{
-    /* Assume tests calling expr_parse_microflow are not chassis specific, so
-     * is_chassis_resident need not be supplied and should return true. */
-    return true;
-}
-
-static struct expr *
-expr_parse_microflow__(struct lexer *lexer,
-                       const struct shash *symtab,
-                       bool (*lookup_port)(const void *aux,
-                                           const char *port_name,
-                                           unsigned int *portp),
-                       const void *aux,
-                       struct expr *e, struct flow *uflow)
-{
-    char *error;
-    e = expr_annotate(e, symtab, &error);
-    if (error) {
-        lexer_error(lexer, "%s", error);
-        free(error);
-        return NULL;
-    }
-
-    struct ds annotated = DS_EMPTY_INITIALIZER;
-    expr_format(e, &annotated);
-
-    e = expr_simplify(e, microflow_is_chassis_resident_cb, NULL);
-    e = expr_normalize(e);
-
-    struct match m = MATCH_CATCHALL_INITIALIZER;
-
-    switch (e->type) {
-    case EXPR_T_BOOLEAN:
-        if (!e->boolean) {
-            lexer_error(lexer, "Constraints are contradictory.");
-        }
-        break;
-
-    case EXPR_T_OR:
-        lexer_error(lexer, "Constraints are ambiguous: %s.",
-                    ds_cstr(&annotated));
-        break;
-
-    case EXPR_T_CMP:
-        constrain_match(e, lookup_port, aux, &m);
-        break;
-
-    case EXPR_T_AND: {
-        struct expr *sub;
-        LIST_FOR_EACH (sub, node, &e->andor) {
-            if (sub->type == EXPR_T_CMP) {
-                constrain_match(sub, lookup_port, aux, &m);
-            } else {
-                ovs_assert(sub->type == EXPR_T_OR);
-                lexer_error(lexer, "Constraints are ambiguous: %s.",
-                            ds_cstr(&annotated));
-                break;
-            }
-        }
-    }
-        break;
-
-    /* Should not hit expression type condition, since
-     * expr_simplify was called above. */
-    case EXPR_T_CONDITION:
-    default:
-        OVS_NOT_REACHED();
-    }
-    ds_destroy(&annotated);
-
-    *uflow = m.flow;
-    return e;
-}
-
-/* Parses 's' as a microflow, using symbols from 'symtab', address set
- * table from 'addr_sets', and looking up port numbers using 'lookup_port'
- * and 'aux'.  On success, stores the result in 'uflow' and returns
- * NULL, otherwise zeros 'uflow' and returns an error message that the
- * caller must free().
- *
- * A "microflow" is a description of a single stream of packets, such as half a
- * TCP connection.  's' uses the syntax of an OVN logical expression to express
- * constraints that describe the microflow.  For example, "ip4 && tcp.src ==
- * 80" would set uflow->dl_type to ETH_TYPE_IP, uflow->nw_proto to IPPROTO_TCP,
- * and uflow->tp_src to 80.
- *
- * Microflow expressions can be erroneous in two ways.  First, they can be
- * ambiguous.  For example, "tcp.src == 80" is ambiguous because it does not
- * state IPv4 or IPv6 as the Ethernet type.  "ip4 && tcp.src > 1024" is also
- * ambiguous because it does not constrain bits of tcp.src to particular
- * values.  Second, they can be contradictory, e.g. "ip4 && ip6".  This
- * function will report both types of errors.
- *
- * This function isn't that smart, so it can yield errors for some "clever"
- * formulations of particular microflows that area accepted other ways.  For
- * example, all of the following expressions are equivalent:
- *     ip4 && tcp.src[1..15] == 0x28
- *     ip4 && tcp.src > 79 && tcp.src < 82
- *     ip4 && 80 <= tcp.src <= 81
- *     ip4 && tcp.src == {80, 81}
- * but as of this writing this function only accepts the first two, rejecting
- * the last two as ambiguous.  Just don't be too clever. */
-char * OVS_WARN_UNUSED_RESULT
-expr_parse_microflow(const char *s, 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)
-{
-    struct lexer lexer;
-    lexer_init(&lexer, s);
-    lexer_get(&lexer);
-
-    struct expr *e = expr_parse(&lexer, symtab, addr_sets, port_groups, NULL);
-    lexer_force_end(&lexer);
-
-    if (e) {
-        e = expr_parse_microflow__(&lexer, symtab, lookup_port, aux, e, uflow);
-    }
-
-    char *error = lexer_steal_error(&lexer);
-    lexer_destroy(&lexer);
-    expr_destroy(e);
-
-    if (error) {
-        memset(uflow, 0, sizeof *uflow);
-    }
-    return error;
-}
diff --git a/ovn/lib/extend-table.c b/ovn/lib/extend-table.c
deleted file mode 100644
index ccf70ca72..000000000
--- a/ovn/lib/extend-table.c
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (c) 2017 DtDream Technology Co.,Ltd.
- *
- * 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 <string.h>
-
-#include "bitmap.h"
-#include "hash.h"
-#include "lib/uuid.h"
-#include "openvswitch/vlog.h"
-#include "ovn/lib/extend-table.h"
-
-VLOG_DEFINE_THIS_MODULE(extend_table);
-
-void
-ovn_extend_table_init(struct ovn_extend_table *table)
-{
-    table->table_ids = bitmap_allocate(MAX_EXT_TABLE_ID);
-    bitmap_set1(table->table_ids, 0); /* table id 0 is invalid. */
-    hmap_init(&table->desired);
-    hmap_init(&table->existing);
-}
-
-static void
-ovn_extend_table_info_destroy(struct hmap *target)
-{
-    struct ovn_extend_table_info *e, *next;
-    HMAP_FOR_EACH_SAFE (e, next, hmap_node, target) {
-        hmap_remove(target, &e->hmap_node);
-        free(e->name);
-        free(e);
-    }
-    hmap_destroy(target);
-}
-
-void
-ovn_extend_table_destroy(struct ovn_extend_table *table)
-{
-    bitmap_free(table->table_ids);
-
-    ovn_extend_table_info_destroy(&table->desired);
-    ovn_extend_table_info_destroy(&table->existing);
-}
-
-/* Finds and returns a group_info in 'existing' whose key is identical
- * to 'target''s key, or NULL if there is none. */
-struct ovn_extend_table_info *
-ovn_extend_table_lookup(struct hmap *exisiting,
-                        const struct ovn_extend_table_info *target)
-{
-    struct ovn_extend_table_info *e;
-
-    HMAP_FOR_EACH_WITH_HASH (e, hmap_node, target->hmap_node.hash,
-                             exisiting) {
-        if (e->table_id == target->table_id) {
-            return e;
-        }
-   }
-    return NULL;
-}
-
-/* Clear either desired or existing in ovn_extend_table. */
-void
-ovn_extend_table_clear(struct ovn_extend_table *table, bool existing)
-{
-    struct ovn_extend_table_info *g, *next;
-    struct hmap *target = existing ? &table->existing : &table->desired;
-
-    HMAP_FOR_EACH_SAFE (g, next, hmap_node, target) {
-        hmap_remove(target, &g->hmap_node);
-        /* Don't unset bitmap for desired group_info if the group_id
-         * was not freshly reserved. */
-        if (existing || g->new_table_id) {
-            bitmap_set0(table->table_ids, g->table_id);
-        }
-        free(g->name);
-        free(g);
-    }
-}
-
-/* Remove an entry from existing table */
-void
-ovn_extend_table_remove_existing(struct ovn_extend_table *table,
-                                 struct ovn_extend_table_info *existing)
-{
-    /* Remove 'existing' from 'groups->existing' */
-    hmap_remove(&table->existing, &existing->hmap_node);
-    free(existing->name);
-
-    /* Dealloc group_id. */
-    bitmap_set0(table->table_ids, existing->table_id);
-    free(existing);
-}
-
-/* Remove entries in desired table that are created by the lflow_uuid */
-void
-ovn_extend_table_remove_desired(struct ovn_extend_table *table,
-                                const struct uuid *lflow_uuid)
-{
-    struct ovn_extend_table_info *e, *next_e;
-    HMAP_FOR_EACH_SAFE (e, next_e, hmap_node, &table->desired) {
-        if (uuid_equals(&e->lflow_uuid, lflow_uuid)) {
-            hmap_remove(&table->desired, &e->hmap_node);
-            free(e->name);
-            if (e->new_table_id) {
-                bitmap_set0(table->table_ids, e->table_id);
-            }
-            free(e);
-        }
-    }
-
-}
-
-static struct ovn_extend_table_info*
-ovn_extend_info_clone(struct ovn_extend_table_info *source)
-{
-    struct ovn_extend_table_info *clone = xmalloc(sizeof *clone);
-    clone->name = xstrdup(source->name);
-    clone->table_id = source->table_id;
-    clone->new_table_id = source->new_table_id;
-    clone->hmap_node.hash = source->hmap_node.hash;
-    clone->lflow_uuid = source->lflow_uuid;
-    return clone;
-}
-
-void
-ovn_extend_table_sync(struct ovn_extend_table *table)
-{
-    struct ovn_extend_table_info *desired, *next;
-
-    /* Copy the contents of desired to existing. */
-    HMAP_FOR_EACH_SAFE (desired, next, hmap_node, &table->desired) {
-        if (!ovn_extend_table_lookup(&table->existing, desired)) {
-            desired->new_table_id = false;
-            struct ovn_extend_table_info *clone =
-                ovn_extend_info_clone(desired);
-            hmap_insert(&table->existing, &clone->hmap_node,
-                        clone->hmap_node.hash);
-        }
-    }
-}
-
-/* Assign a new table ID for the table information from the bitmap.
- * If it already exists, return the old ID. */
-uint32_t
-ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
-                           struct uuid lflow_uuid)
-{
-    uint32_t table_id = 0, hash;
-    struct ovn_extend_table_info *table_info;
-
-    hash = hash_string(name, 0);
-
-    /* Check whether we have non installed but allocated group_id. */
-    HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) {
-        if (!strcmp(table_info->name, name) &&
-            table_info->new_table_id) {
-            return table_info->table_id;
-        }
-    }
-
-    /* Check whether we already have an installed entry for this
-     * combination. */
-    HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->existing) {
-        if (!strcmp(table_info->name, name)) {
-            table_id = table_info->table_id;
-        }
-    }
-
-    bool new_table_id = false;
-    if (!table_id) {
-        /* Reserve a new group_id. */
-        table_id = bitmap_scan(table->table_ids, 0, 1, MAX_EXT_TABLE_ID + 1);
-        new_table_id = true;
-    }
-
-    if (table_id == MAX_EXT_TABLE_ID + 1) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_ERR_RL(&rl, "%"PRIu32" out of table ids.", table_id);
-        return EXT_TABLE_ID_INVALID;
-    }
-    bitmap_set1(table->table_ids, table_id);
-
-    table_info = xmalloc(sizeof *table_info);
-    table_info->name = xstrdup(name);
-    table_info->table_id = table_id;
-    table_info->hmap_node.hash = hash;
-    table_info->new_table_id = new_table_id;
-    table_info->lflow_uuid = lflow_uuid;
-
-    hmap_insert(&table->desired,
-                &table_info->hmap_node, table_info->hmap_node.hash);
-
-    return table_id;
-}
diff --git a/ovn/lib/extend-table.h b/ovn/lib/extend-table.h
deleted file mode 100644
index 5be13fee1..000000000
--- a/ovn/lib/extend-table.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2017 DtDream Technology Co.,Ltd.
- *
- * 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 EXTEND_TABLE_H
-#define EXTEND_TABLE_H 1
-
-#define MAX_EXT_TABLE_ID 65535
-#define EXT_TABLE_ID_INVALID 0
-
-#include "openvswitch/hmap.h"
-#include "openvswitch/list.h"
-#include "openvswitch/uuid.h"
-
-/* Used to manage expansion tables associated with Flow table,
- * such as the Group Table or Meter Table. */
-struct ovn_extend_table {
-    unsigned long *table_ids;  /* Used as a bitmap with value set
-                                * for allocated group ids in either
-                                * desired or existing. */
-    struct hmap desired;
-    struct hmap existing;
-};
-
-struct ovn_extend_table_info {
-    struct hmap_node hmap_node;
-    char *name;         /* Name for the table entity. */
-    struct uuid lflow_uuid;
-    uint32_t table_id;
-    bool new_table_id;  /* 'True' if 'table_id' was reserved from
-                         * ovn_extend_table's 'table_ids' bitmap. */
-};
-
-void ovn_extend_table_init(struct ovn_extend_table *);
-
-void ovn_extend_table_destroy(struct ovn_extend_table *);
-
-struct ovn_extend_table_info *ovn_extend_table_lookup(
-    struct hmap *, const struct ovn_extend_table_info *);
-
-void ovn_extend_table_clear(struct ovn_extend_table *, bool);
-
-void ovn_extend_table_remove_existing(struct ovn_extend_table *,
-                                      struct ovn_extend_table_info *);
-
-void ovn_extend_table_remove_desired(struct ovn_extend_table *,
-                                     const struct uuid *lflow_uuid);
-
-/* Copy the contents of desired to existing. */
-void ovn_extend_table_sync(struct ovn_extend_table *);
-
-uint32_t ovn_extend_table_assign_id(struct ovn_extend_table *,
-                                    const char *name,
-                                    struct uuid lflow_uuid);
-
-/* Iterates 'DESIRED' through all of the 'ovn_extend_table_info's in
- * 'TABLE'->desired that are not in 'TABLE'->existing.  (The loop body
- * presumably adds them.) */
-#define EXTEND_TABLE_FOR_EACH_UNINSTALLED(DESIRED, TABLE) \
-    HMAP_FOR_EACH (DESIRED, hmap_node, &(TABLE)->desired) \
-        if (!ovn_extend_table_lookup(&(TABLE)->existing, DESIRED))
-
-/* Iterates 'EXISTING' through all of the 'ovn_extend_table_info's in
- * 'TABLE'->existing that are not in 'TABLE'->desired.  (The loop body
- * presumably removes them.) */
-#define EXTEND_TABLE_FOR_EACH_INSTALLED(EXISTING, NEXT, TABLE)         \
-    HMAP_FOR_EACH_SAFE (EXISTING, NEXT, hmap_node, &(TABLE)->existing) \
-        if (!ovn_extend_table_lookup(&(TABLE)->desired, EXISTING))
-
-#endif /* ovn/lib/extend-table.h */
diff --git a/ovn/lib/inc-proc-eng.c b/ovn/lib/inc-proc-eng.c
deleted file mode 100644
index 1ddea1a85..000000000
--- a/ovn/lib/inc-proc-eng.c
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (c) 2018 eBay 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 "lib/util.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/vlog.h"
-#include "inc-proc-eng.h"
-
-VLOG_DEFINE_THIS_MODULE(inc_proc_eng);
-
-static bool engine_force_recompute = false;
-static const struct engine_context *engine_context;
-
-void
-engine_set_force_recompute(bool val)
-{
-    engine_force_recompute = val;
-}
-
-const struct engine_context *
-engine_get_context(void)
-{
-    return engine_context;
-}
-
-void
-engine_set_context(const struct engine_context *ctx)
-{
-    engine_context = ctx;
-}
-
-void
-engine_init(struct engine_node *node)
-{
-    for (size_t i = 0; i < node->n_inputs; i++) {
-        engine_init(node->inputs[i].node);
-    }
-    if (node->init) {
-        node->init(node);
-    }
-}
-
-void
-engine_cleanup(struct engine_node *node)
-{
-    for (size_t i = 0; i < node->n_inputs; i++) {
-        engine_cleanup(node->inputs[i].node);
-    }
-    if (node->cleanup) {
-        node->cleanup(node);
-    }
-}
-
-struct engine_node *
-engine_get_input(const char *input_name, struct engine_node *node)
-{
-    size_t i;
-    for (i = 0; i < node->n_inputs; i++) {
-        if (!strcmp(node->inputs[i].node->name, input_name)) {
-            return node->inputs[i].node;
-        }
-    }
-    OVS_NOT_REACHED();
-    return NULL;
-}
-
-void
-engine_add_input(struct engine_node *node, struct engine_node *input,
-                 bool (*change_handler)(struct engine_node *))
-{
-    ovs_assert(node->n_inputs < ENGINE_MAX_INPUT);
-    node->inputs[node->n_inputs].node = input;
-    node->inputs[node->n_inputs].change_handler = change_handler;
-    node->n_inputs ++;
-}
-
-struct ovsdb_idl_index *
-engine_ovsdb_node_get_index(struct engine_node *node, const char *name)
-{
-    struct ed_type_ovsdb_table *ed = (struct ed_type_ovsdb_table *)node->data;
-    for (size_t i = 0; i < ed->n_indexes; i++) {
-        if (!strcmp(ed->indexes[i].name, name)) {
-            return ed->indexes[i].index;
-        }
-    }
-    OVS_NOT_REACHED();
-    return NULL;
-}
-
-void
-engine_ovsdb_node_add_index(struct engine_node *node, const char *name,
-                            struct ovsdb_idl_index *index)
-{
-    struct ed_type_ovsdb_table *ed = (struct ed_type_ovsdb_table *)node->data;
-    ovs_assert(ed->n_indexes < ENGINE_MAX_OVSDB_INDEX);
-
-    ed->indexes[ed->n_indexes].name = name;
-    ed->indexes[ed->n_indexes].index = index;
-    ed->n_indexes ++;
-}
-
-void
-engine_run(struct engine_node *node, uint64_t run_id)
-{
-    if (node->run_id == run_id) {
-        return;
-    }
-    node->run_id = run_id;
-
-    node->changed = false;
-    if (!node->n_inputs) {
-        node->run(node);
-        VLOG_DBG("node: %s, changed: %d", node->name, node->changed);
-        return;
-    }
-
-    for (size_t i = 0; i < node->n_inputs; i++) {
-        engine_run(node->inputs[i].node, run_id);
-    }
-
-    bool need_compute = false;
-    bool need_recompute = false;
-
-    if (engine_force_recompute) {
-        need_recompute = true;
-    } else {
-        for (size_t i = 0; i < node->n_inputs; i++) {
-            if (node->inputs[i].node->changed) {
-                need_compute = true;
-                if (!node->inputs[i].change_handler) {
-                    need_recompute = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    if (need_recompute) {
-        VLOG_DBG("node: %s, recompute (%s)", node->name,
-                 engine_force_recompute ? "forced" : "triggered");
-        node->run(node);
-    } else if (need_compute) {
-        for (size_t i = 0; i < node->n_inputs; i++) {
-            if (node->inputs[i].node->changed) {
-                VLOG_DBG("node: %s, handle change for input %s",
-                         node->name, node->inputs[i].node->name);
-                if (!node->inputs[i].change_handler(node)) {
-                    VLOG_DBG("node: %s, can't handle change for input %s, "
-                             "fall back to recompute",
-                             node->name, node->inputs[i].node->name);
-                    node->run(node);
-                    break;
-                }
-            }
-        }
-    }
-
-    VLOG_DBG("node: %s, changed: %d", node->name, node->changed);
-}
-
-bool
-engine_need_run(struct engine_node *node)
-{
-    size_t i;
-
-    if (!node->n_inputs) {
-        node->run(node);
-        VLOG_DBG("input node: %s, changed: %d", node->name, node->changed);
-        return node->changed;
-    }
-
-    for (i = 0; i < node->n_inputs; i++) {
-        if (engine_need_run(node->inputs[i].node)) {
-            return true;
-        }
-    }
-
-    return false;
-}
diff --git a/ovn/lib/inc-proc-eng.h b/ovn/lib/inc-proc-eng.h
deleted file mode 100644
index aab899e13..000000000
--- a/ovn/lib/inc-proc-eng.h
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (c) 2018 eBay 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 INC_PROC_ENG_H
-#define INC_PROC_ENG_H 1
-
-/* The Incremental Processing Engine is a framework for incrementally
- * processing changes from different inputs. The main user is ovn-controller.
- * To compute desired states (e.g. openflow rules) based on many inputs (e.g.
- * south-bound DB tables, local OVSDB interfaces, etc.), it is straightforward
- * to recompute everything when there is any change in any inputs, but it
- * is inefficient when the size of the input data becomes large. Instead,
- * tracking the changes and update the desired states based on what's changed
- * is more efficient and scalable. However, it is not straightforward to
- * implement the change-based processing when there are a big number of
- * inputs. In addition, what makes it more complicated is that intermediate
- * results needs to be computed, which needs to be reused in different part
- * of the processing and finally generates the final desired states. It is
- * proved to be difficult and error-prone to implement this kind of complex
- * processing by ad-hoc implementation.
- *
- * This framework is to provide a generic way to solve the above problem.
- * It does not understand the processing logic, but provides a unified way
- * to describe the inputs and dependencies clearly, with interfaces for
- * users to implement the processing logic for how to handle each input
- * changes.
- *
- * The engine is composed of engine_nodes. Each engine_node is either
- * an input, an output or both (intermediate result). Each engine node
- * maintains its own data, which is persistent across interactions. Each node
- * has zero to ENGINE_MAX_INPUT inputs, which creates a DAG (directed
- * acyclic graph). For each input of each engine_node, there is a
- * change_handler to process changes of that input, and update the data
- * of the engine_node. Then the user can simply call the run() method
- * of the engine so that the processing will happen in the order according
- * to the dependencies defined and handle the changes incrementally.
- *
- * While the more fine-grained dependencies and change-handlers are
- * implemented, the more efficient the processing will be, it is not
- * realistic to implement all change-processing for all inputs (and
- * intermediate results). The engine doesn't require change-handler to be
- * implemented for every input of every node. Users can choose to implement
- * the most important change-handlers (for the changes happens most
- * frequently) for overall performance. When there is no change_handler
- * defined for a certain input on a certain engine_node, the run() method
- * of the engine_node will be called to fall-back to a full recompute
- * against all its inputs.
- */
-
-#define ENGINE_MAX_INPUT 256
-#define ENGINE_MAX_OVSDB_INDEX 256
-
-struct engine_context {
-    struct ovsdb_idl_txn *ovs_idl_txn;
-    struct ovsdb_idl_txn *ovnsb_idl_txn;
-};
-
-struct engine_node;
-
-struct engine_node_input {
-    /* The input node. */
-    struct engine_node *node;
-
-    /* Change handler for changes of the input node. The changes may need to be
-     * evaluated against all the other inputs. Returns:
-     *  - true: if change can be handled
-     *  - false: if change cannot be handled (indicating full recompute needed)
-     */
-    bool (*change_handler)(struct engine_node *node);
-};
-
-struct engine_node {
-    /* A unique id to distinguish each iteration of the engine_run(). */
-    uint64_t run_id;
-
-    /* A unique name for each node. */
-    char *name;
-
-    /* Number of inputs of this node. */
-    size_t n_inputs;
-
-    /* Inputs of this node. */
-    struct engine_node_input inputs[ENGINE_MAX_INPUT];
-
-    /* Data of this node. It is vague and interpreted by the related functions.
-     * The content of the data should be changed only by the change_handlers
-     * and run() function of the current node. Users should ensure that the
-     * data is read-only in change-handlers of the nodes that depends on this
-     * node. */
-    void *data;
-
-    /* Whether the data changed in the last engine run. */
-    bool changed;
-
-    /* Method to initialize data. It may be NULL. */
-    void (*init)(struct engine_node *);
-
-    /* Method to clean up data. It may be NULL. */
-    void (*cleanup)(struct engine_node *);
-
-    /* Fully processes all inputs of this node and regenerates the data
-     * of this node */
-    void (*run)(struct engine_node *);
-};
-
-/* Initialize the data for the engine nodes recursively. It calls each node's
- * init() method if not NULL. It should be called before the main loop. */
-void engine_init(struct engine_node *);
-
-/* Execute the processing recursively, which should be called in the main
- * loop. */
-void engine_run(struct engine_node *, uint64_t run_id);
-
-/* Clean up the data for the engine nodes recursively. It calls each node's
- * cleanup() method if not NULL. It should be called before the program
- * terminates. */
-void engine_cleanup(struct engine_node *);
-
-/* Check if engine needs to run, i.e. any change to be processed. */
-bool
-engine_need_run(struct engine_node *);
-
-/* Get the input node with <name> for <node> */
-struct engine_node * engine_get_input(const char *input_name,
-                                      struct engine_node *);
-
-/* Add an input (dependency) for <node>, with corresponding change_handler,
- * which can be NULL. If the change_handler is NULL, the engine will not
- * be able to process the change incrementally, and will fall back to call
- * the run method to recompute. */
-void engine_add_input(struct engine_node *node, struct engine_node *input,
-                      bool (*change_handler)(struct engine_node *));
-
-/* Force the engine to recompute everything if set to true. It is used
- * in circumstances when we are not sure there is change or not, or
- * when there is change but the engine couldn't be executed in that
- * iteration, and the change can't be tracked across iterations */
-void engine_set_force_recompute(bool val);
-
-const struct engine_context * engine_get_context(void);
-
-void engine_set_context(const struct engine_context *);
-
-struct ed_ovsdb_index {
-    const char *name;
-    struct ovsdb_idl_index *index;
-};
-
-struct ed_type_ovsdb_table {
-    const void *table;
-    size_t n_indexes;
-    struct ed_ovsdb_index indexes[ENGINE_MAX_OVSDB_INDEX];
-};
-
-#define EN_OVSDB_GET(NODE) \
-    (((struct ed_type_ovsdb_table *)NODE->data)->table)
-
-struct ovsdb_idl_index * engine_ovsdb_node_get_index(struct engine_node *,
-                                                     const char *name);
-
-void engine_ovsdb_node_add_index(struct engine_node *, const char *name,
-                                 struct ovsdb_idl_index *);
-
-/* Macro to define an engine node. */
-#define ENGINE_NODE(NAME, NAME_STR) \
-    struct engine_node en_##NAME = { \
-        .name = NAME_STR, \
-        .data = &ed_##NAME, \
-        .init = en_##NAME##_init, \
-        .run = en_##NAME##_run, \
-        .cleanup = en_##NAME##_cleanup, \
-    };
-
-/* Macro to define member functions of an engine node which represents
- * a table of OVSDB */
-#define ENGINE_FUNC_OVSDB(DB_NAME, TBL_NAME) \
-static void \
-en_##DB_NAME##_##TBL_NAME##_run(struct engine_node *node) \
-{ \
-    const struct DB_NAME##rec_##TBL_NAME##_table *table = \
-        EN_OVSDB_GET(node); \
-    if (DB_NAME##rec_##TBL_NAME##_table_track_get_first(table)) { \
-        node->changed = true; \
-        return; \
-    } \
-    node->changed = false; \
-} \
-static void (*en_##DB_NAME##_##TBL_NAME##_init)(struct engine_node *node) \
-            = NULL; \
-static void (*en_##DB_NAME##_##TBL_NAME##_cleanup)(struct engine_node *node) \
-            = NULL;
-
-/* Macro to define member functions of an engine node which represents
- * a table of OVN SB DB */
-#define ENGINE_FUNC_SB(TBL_NAME) \
-    ENGINE_FUNC_OVSDB(sb, TBL_NAME)
-
-/* Macro to define member functions of an engine node which represents
- * a table of open_vswitch DB */
-#define ENGINE_FUNC_OVS(TBL_NAME) \
-    ENGINE_FUNC_OVSDB(ovs, TBL_NAME)
-
-/* Macro to define an engine node which represents a table of OVSDB */
-#define ENGINE_NODE_OVSDB(DB_NAME, DB_NAME_STR, TBL_NAME, TBL_NAME_STR, IDL) \
-    struct ed_type_ovsdb_table ed_##DB_NAME##_##TBL_NAME; \
-    memset(&ed_##DB_NAME##_##TBL_NAME, 0, sizeof ed_##DB_NAME##_##TBL_NAME); \
-    ovs_assert(IDL); \
-    ed_##DB_NAME##_##TBL_NAME.table = \
-        DB_NAME##rec_##TBL_NAME##_table_get(IDL); \
-    ENGINE_NODE(DB_NAME##_##TBL_NAME, DB_NAME_STR"_"TBL_NAME_STR)
-
-/* Macro to define an engine node which represents a table of OVN SB DB */
-#define ENGINE_NODE_SB(TBL_NAME, TBL_NAME_STR) \
-    ENGINE_NODE_OVSDB(sb, "SB", TBL_NAME, TBL_NAME_STR, ovnsb_idl_loop.idl);
-
-/* Macro to define an engine node which represents a table of open_vswitch
- * DB */
-#define ENGINE_NODE_OVS(TBL_NAME, TBL_NAME_STR) \
-    ENGINE_NODE_OVSDB(ovs, "OVS", TBL_NAME, TBL_NAME_STR, ovs_idl_loop.idl);
-
-#endif /* ovn/lib/inc-proc-eng.h */
diff --git a/ovn/lib/ip-mcast-index.c b/ovn/lib/ip-mcast-index.c
deleted file mode 100644
index 1f6ebc4ae..000000000
--- a/ovn/lib/ip-mcast-index.c
+++ /dev/null
@@ -1,40 +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 "ovn/lib/ip-mcast-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-struct ovsdb_idl_index *
-ip_mcast_index_create(struct ovsdb_idl *idl)
-{
-    return ovsdb_idl_index_create1(idl, &sbrec_ip_multicast_col_datapath);
-}
-
-const struct sbrec_ip_multicast *
-ip_mcast_lookup(struct ovsdb_idl_index *ip_mcast_index,
-                const struct sbrec_datapath_binding *datapath)
-{
-    struct sbrec_ip_multicast *target =
-        sbrec_ip_multicast_index_init_row(ip_mcast_index);
-    sbrec_ip_multicast_index_set_datapath(target, datapath);
-
-    struct sbrec_ip_multicast *ip_mcast =
-        sbrec_ip_multicast_index_find(ip_mcast_index, target);
-    sbrec_ip_multicast_index_destroy_row(target);
-
-    return ip_mcast;
-}
diff --git a/ovn/lib/ip-mcast-index.h b/ovn/lib/ip-mcast-index.h
deleted file mode 100644
index a23b4a7e6..000000000
--- a/ovn/lib/ip-mcast-index.h
+++ /dev/null
@@ -1,36 +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_INDEX_H
-#define OVN_IP_MCAST_INDEX_H 1
-
-struct ovsdb_idl;
-
-struct sbrec_datapath_binding;
-
-#define OVN_MCAST_MIN_IDLE_TIMEOUT_S           15
-#define OVN_MCAST_MAX_IDLE_TIMEOUT_S           3600
-#define OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S       300
-#define OVN_MCAST_MIN_QUERY_INTERVAL_S         1
-#define OVN_MCAST_MAX_QUERY_INTERVAL_S         OVN_MCAST_MAX_IDLE_TIMEOUT_S
-#define OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S 1
-#define OVN_MCAST_DEFAULT_MAX_ENTRIES          2048
-
-struct ovsdb_idl_index *ip_mcast_index_create(struct ovsdb_idl *);
-const struct sbrec_ip_multicast *ip_mcast_lookup(
-    struct ovsdb_idl_index *ip_mcast_index,
-    const struct sbrec_datapath_binding *datapath);
-
-#endif /* ovn/lib/ip-mcast-index.h */
diff --git a/ovn/lib/lex.c b/ovn/lib/lex.c
deleted file mode 100644
index 7a2ab4111..000000000
--- a/ovn/lib/lex.c
+++ /dev/null
@@ -1,1023 +0,0 @@
-/*
- * Copyright (c) 2015, 2016, 2017 Nicira, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include <ctype.h>
-#include <errno.h>
-#include <stdarg.h>
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
-#include "ovn/lex.h"
-#include "packets.h"
-#include "util.h"
-
-/* Returns a string that represents 'format'. */
-const char *
-lex_format_to_string(enum lex_format format)
-{
-    switch (format) {
-    case LEX_F_DECIMAL:
-        return "decimal";
-    case LEX_F_HEXADECIMAL:
-        return "hexadecimal";
-    case LEX_F_IPV4:
-        return "IPv4";
-    case LEX_F_IPV6:
-        return "IPv6";
-    case LEX_F_ETHERNET:
-        return "Ethernet";
-    default:
-        abort();
-    }
-}
-
-/* Initializes 'token'. */
-void
-lex_token_init(struct lex_token *token)
-{
-    token->type = LEX_T_END;
-    token->s = NULL;
-}
-
-/* Frees memory owned by 'token'. */
-void
-lex_token_destroy(struct lex_token *token)
-{
-    if (token->s != token->buffer) {
-        free(token->s);
-    }
-    token->s = NULL;
-}
-
-/* Exchanges 'a' and 'b'. */
-void
-lex_token_swap(struct lex_token *a, struct lex_token *b)
-{
-    struct lex_token tmp = *a;
-    *a = *b;
-    *b = tmp;
-
-    /* Before swap, if 's' was pointed to 'buffer', its value shall be changed
-     * to point to the 'buffer' with the copied value. */
-    if (a->s == b->buffer) {
-        a->s = a->buffer;
-    }
-    if (b->s == a->buffer) {
-        b->s = b->buffer;
-    }
-}
-
-/* The string 's' need not be null-terminated at 'length'. */
-void
-lex_token_strcpy(struct lex_token *token, const char *s, size_t length)
-{
-    lex_token_destroy(token);
-    token->s = (length + 1 <= sizeof token->buffer
-                ? token->buffer
-                : xmalloc(length + 1));
-    memcpy(token->s, s, length);
-    token->s[length] = '\0';
-}
-
-void
-lex_token_strset(struct lex_token *token, char *s)
-{
-    lex_token_destroy(token);
-    token->s = s;
-}
-
-void
-lex_token_vsprintf(struct lex_token *token, const char *format, va_list args)
-{
-    lex_token_destroy(token);
-
-    va_list args2;
-    va_copy(args2, args);
-    token->s = (vsnprintf(token->buffer, sizeof token->buffer, format, args)
-                < sizeof token->buffer
-                ? token->buffer
-                : xvasprintf(format, args2));
-    va_end(args2);
-}
-
-/* lex_token_format(). */
-
-static size_t
-lex_token_n_zeros(enum lex_format format)
-{
-    switch (format) {
-    case LEX_F_DECIMAL:     return offsetof(union mf_subvalue, integer);
-    case LEX_F_HEXADECIMAL: return 0;
-    case LEX_F_IPV4:        return offsetof(union mf_subvalue, ipv4);
-    case LEX_F_IPV6:        return offsetof(union mf_subvalue, ipv6);
-    case LEX_F_ETHERNET:    return offsetof(union mf_subvalue, mac);
-    default: OVS_NOT_REACHED();
-    }
-}
-
-/* Returns the effective format for 'token', that is, the format in which it
- * should actually be printed.  This is ordinarily the same as 'token->format',
- * but it's always possible that someone sets up a token with a format that
- * won't work for a value, e.g. 'token->value' is wider than 32 bits but the
- * format is LEX_F_IPV4.  (The lexer itself won't do that; this is an attempt
- * to avoid confusion in the future.) */
-static enum lex_format
-lex_token_get_format(const struct lex_token *token)
-{
-    size_t n_zeros = lex_token_n_zeros(token->format);
-    return (is_all_zeros(&token->value, n_zeros)
-            && (token->type != LEX_T_MASKED_INTEGER
-                || is_all_zeros(&token->mask, n_zeros))
-            ? token->format
-            : LEX_F_HEXADECIMAL);
-}
-
-static void
-lex_token_format_value(const union mf_subvalue *value,
-                       enum lex_format format, struct ds *s)
-{
-    switch (format) {
-    case LEX_F_DECIMAL:
-        ds_put_format(s, "%"PRIu64, ntohll(value->integer));
-        break;
-
-    case LEX_F_HEXADECIMAL:
-        mf_format_subvalue(value, s);
-        break;
-
-    case LEX_F_IPV4:
-        ds_put_format(s, IP_FMT, IP_ARGS(value->ipv4));
-        break;
-
-    case LEX_F_IPV6:
-        ipv6_format_addr(&value->ipv6, s);
-        break;
-
-    case LEX_F_ETHERNET:
-        ds_put_format(s, ETH_ADDR_FMT, ETH_ADDR_ARGS(value->mac));
-        break;
-
-    default:
-        OVS_NOT_REACHED();
-    }
-
-}
-
-static void
-lex_token_format_masked_integer(const struct lex_token *token, struct ds *s)
-{
-    enum lex_format format = lex_token_get_format(token);
-
-    lex_token_format_value(&token->value, format, s);
-    ds_put_char(s, '/');
-
-    const union mf_subvalue *mask = &token->mask;
-    if (format == LEX_F_IPV4 && ip_is_cidr(mask->ipv4)) {
-        ds_put_format(s, "%d", ip_count_cidr_bits(mask->ipv4));
-    } else if (token->format == LEX_F_IPV6 && ipv6_is_cidr(&mask->ipv6)) {
-        ds_put_format(s, "%d", ipv6_count_cidr_bits(&mask->ipv6));
-    } else {
-        lex_token_format_value(&token->mask, format, s);
-    }
-}
-
-/* Appends a string representation of 'token' to 's', in a format that can be
- * losslessly parsed back by the lexer.  (LEX_T_END and LEX_T_ERROR can't be
- * parsed back.) */
-void
-lex_token_format(const struct lex_token *token, struct ds *s)
-{
-    switch (token->type) {
-    case LEX_T_END:
-        ds_put_cstr(s, "$");
-        break;
-
-    case LEX_T_ID:
-        ds_put_cstr(s, token->s);
-        break;
-
-    case LEX_T_ERROR:
-        ds_put_cstr(s, "error(");
-        json_string_escape(token->s, s);
-        ds_put_char(s, ')');
-        break;
-
-    case LEX_T_STRING:
-        json_string_escape(token->s, s);
-        break;
-
-    case LEX_T_INTEGER:
-        lex_token_format_value(&token->value, lex_token_get_format(token), s);
-        break;
-
-    case LEX_T_MASKED_INTEGER:
-        lex_token_format_masked_integer(token, s);
-        break;
-
-    case LEX_T_MACRO:
-        ds_put_format(s, "$%s", token->s);
-        break;
-
-    case LEX_T_PORT_GROUP:
-        ds_put_format(s, "@%s", token->s);
-        break;
-
-    case LEX_T_LPAREN:
-        ds_put_cstr(s, "(");
-        break;
-    case LEX_T_RPAREN:
-        ds_put_cstr(s, ")");
-        break;
-    case LEX_T_LCURLY:
-        ds_put_cstr(s, "{");
-        break;
-    case LEX_T_RCURLY:
-        ds_put_cstr(s, "}");
-        break;
-    case LEX_T_LSQUARE:
-        ds_put_cstr(s, "[");
-        break;
-    case LEX_T_RSQUARE:
-        ds_put_cstr(s, "]");
-        break;
-    case LEX_T_EQ:
-        ds_put_cstr(s, "==");
-        break;
-    case LEX_T_NE:
-        ds_put_cstr(s, "!=");
-        break;
-    case LEX_T_LT:
-        ds_put_cstr(s, "<");
-        break;
-    case LEX_T_LE:
-        ds_put_cstr(s, "<=");
-        break;
-    case LEX_T_GT:
-        ds_put_cstr(s, ">");
-        break;
-    case LEX_T_GE:
-        ds_put_cstr(s, ">=");
-        break;
-    case LEX_T_LOG_NOT:
-        ds_put_cstr(s, "!");
-        break;
-    case LEX_T_LOG_AND:
-        ds_put_cstr(s, "&&");
-        break;
-    case LEX_T_LOG_OR:
-        ds_put_cstr(s, "||");
-        break;
-    case LEX_T_ELLIPSIS:
-        ds_put_cstr(s, "..");
-        break;
-    case LEX_T_COMMA:
-        ds_put_cstr(s, ",");
-        break;
-    case LEX_T_SEMICOLON:
-        ds_put_cstr(s, ";");
-        break;
-    case LEX_T_EQUALS:
-        ds_put_cstr(s, "=");
-        break;
-    case LEX_T_EXCHANGE:
-        ds_put_cstr(s, "<->");
-        break;
-    case LEX_T_DECREMENT:
-        ds_put_cstr(s, "--");
-        break;
-    case LEX_T_COLON:
-        ds_put_char(s, ':');
-        break;
-    default:
-        OVS_NOT_REACHED();
-    }
-
-}
-
-/* lex_token_parse(). */
-
-static void OVS_PRINTF_FORMAT(2, 3)
-lex_error(struct lex_token *token, const char *message, ...)
-{
-    ovs_assert(!token->s);
-    token->type = LEX_T_ERROR;
-
-    va_list args;
-    va_start(args, message);
-    lex_token_vsprintf(token, message, args);
-    va_end(args);
-}
-
-static void
-lex_parse_hex_integer(const char *start, size_t len, struct lex_token *token)
-{
-    const char *in = start + (len - 1);
-    uint8_t *out = token->value.u8 + (sizeof token->value.u8 - 1);
-
-    for (int i = 0; i < len; i++) {
-        int hexit = hexit_value(in[-i]);
-        if (hexit < 0) {
-            lex_error(token, "Invalid syntax in hexadecimal constant.");
-            return;
-        } else if (hexit) {
-            /* Check within loop to ignore any number of leading zeros. */
-            if (i / 2 >= sizeof token->value.u8) {
-                lex_error(token, "Hexadecimal constant requires more than "
-                          "%"PRIuSIZE" bits.", 8 * sizeof token->value.u8);
-                return;
-            }
-            out[-(i / 2)] |= i % 2 ? hexit << 4 : hexit;
-        }
-    }
-    token->format = LEX_F_HEXADECIMAL;
-}
-
-static const char *
-lex_parse_integer__(const char *p, struct lex_token *token)
-{
-    lex_token_init(token);
-    token->type = LEX_T_INTEGER;
-    memset(&token->value, 0, sizeof token->value);
-
-    /* Find the extent of an "integer" token, which can be in decimal or
-     * hexadecimal, or an Ethernet address or IPv4 or IPv6 address, as 'start'
-     * through 'end'.
-     *
-     * Special cases we handle here are:
-     *
-     *     - The ellipsis token "..", used as e.g. 123..456.  A doubled dot
-     *       is never valid syntax as part of an "integer", so we stop if
-     *       we encounter two dots in a row.
-     *
-     *     - Syntax like 1.2.3.4:1234 to indicate an IPv4 address followed by a
-     *       port number should be considered three tokens: 1.2.3.4 : 1234.
-     *       The obvious approach is to allow just dots or just colons within a
-     *       given integer, but that would disallow IPv4-mapped IPv6 addresses,
-     *       e.g. ::ffff:192.0.2.128.  However, even in those addresses, a
-     *       colon never follows a dot, so we stop if we encounter a colon
-     *       after a dot.
-     *
-     *       (There is no corresponding way to parse an IPv6 address followed
-     *       by a port number: ::1:2:3:4:1234 is unavoidably ambiguous.)
-     */
-    const char *start = p;
-    const char *end = start;
-    bool saw_dot = false;
-    while (isalnum((unsigned char) *end)
-           || (*end == ':' && !saw_dot)
-           || (*end == '.' && end[1] != '.')) {
-        if (*end == '.') {
-            saw_dot = true;
-        }
-        end++;
-    }
-    size_t len = end - start;
-
-    int n;
-    struct eth_addr mac;
-
-    if (!len) {
-        lex_error(token, "Integer constant expected.");
-    } else if (len == 17
-               && ovs_scan(start, ETH_ADDR_SCAN_FMT"%n",
-                           ETH_ADDR_SCAN_ARGS(mac), &n)
-               && n == len) {
-        token->value.mac = mac;
-        token->format = LEX_F_ETHERNET;
-    } else if (start + strspn(start, "0123456789") == end) {
-        if (p[0] == '0' && len > 1) {
-            lex_error(token, "Decimal constants must not have leading zeros.");
-        } else {
-            unsigned long long int integer;
-            char *tail;
-
-            errno = 0;
-            integer = strtoull(p, &tail, 10);
-            if (tail != end || errno == ERANGE) {
-                lex_error(token, "Decimal constants must be less than 2**64.");
-            } else {
-                token->value.integer = htonll(integer);
-                token->format = LEX_F_DECIMAL;
-            }
-        }
-    } else if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
-        if (len > 2) {
-            lex_parse_hex_integer(start + 2, len - 2, token);
-        } else {
-            lex_error(token, "Hex digits expected following 0%c.", p[1]);
-        }
-    } else if (len < INET6_ADDRSTRLEN) {
-        char copy[INET6_ADDRSTRLEN];
-        memcpy(copy, p, len);
-        copy[len] = '\0';
-
-        if (ip_parse(copy, &token->value.ipv4)) {
-            token->format = LEX_F_IPV4;
-        } else if (ipv6_parse(copy, &token->value.ipv6)) {
-            token->format = LEX_F_IPV6;
-        } else {
-            lex_error(token, "Invalid numeric constant.");
-        }
-    } else {
-        lex_error(token, "Invalid numeric constant.");
-    }
-
-    ovs_assert(token->type == LEX_T_INTEGER || token->type == LEX_T_ERROR);
-    return end;
-}
-
-static const char *
-lex_parse_mask(const char *p, struct lex_token *token)
-{
-    struct lex_token mask;
-
-    /* Parse just past the '/' as a second integer.  Handle errors. */
-    p = lex_parse_integer__(p + 1, &mask);
-    if (mask.type == LEX_T_ERROR) {
-        lex_token_swap(&mask, token);
-        lex_token_destroy(&mask);
-        return p;
-    }
-    ovs_assert(mask.type == LEX_T_INTEGER);
-
-    /* Now convert the value and mask into a masked integer token.
-     * We have a few special cases. */
-    token->type = LEX_T_MASKED_INTEGER;
-    memset(&token->mask, 0, sizeof token->mask);
-    uint32_t prefix_bits = ntohll(mask.value.integer);
-    if (token->format == mask.format) {
-        /* Same format value and mask is always OK. */
-        token->mask = mask.value;
-    } else if (token->format == LEX_F_IPV4
-               && mask.format == LEX_F_DECIMAL
-               && prefix_bits <= 32) {
-        /* IPv4 address with decimal mask is a CIDR prefix. */
-        token->mask.integer = htonll(ntohl(be32_prefix_mask(prefix_bits)));
-    } else if (token->format == LEX_F_IPV6
-               && mask.format == LEX_F_DECIMAL
-               && prefix_bits <= 128) {
-        /* IPv6 address with decimal mask is a CIDR prefix. */
-        token->mask.ipv6 = ipv6_create_mask(prefix_bits);
-    } else if (token->format == LEX_F_DECIMAL
-               && mask.format == LEX_F_HEXADECIMAL
-               && token->value.integer == 0) {
-        /* Special case for e.g. 0/0x1234. */
-        token->format = LEX_F_HEXADECIMAL;
-        token->mask = mask.value;
-    } else {
-        lex_error(token, "Value and mask have incompatible formats.");
-        return p;
-    }
-
-    /* Check invariant that a 1-bit in the value corresponds to a 1-bit in the
-     * mask. */
-    for (int i = 0; i < ARRAY_SIZE(token->mask.be32); i++) {
-        ovs_be32 v = token->value.be32[i];
-        ovs_be32 m = token->mask.be32[i];
-
-        if (v & ~m) {
-            lex_error(token, "Value contains unmasked 1-bits.");
-            break;
-        }
-    }
-
-    /* Done! */
-    lex_token_destroy(&mask);
-    return p;
-}
-
-static const char *
-lex_parse_integer(const char *p, struct lex_token *token)
-{
-    p = lex_parse_integer__(p, token);
-    if (token->type == LEX_T_INTEGER && *p == '/') {
-        p = lex_parse_mask(p, token);
-    }
-    return p;
-}
-
-static const char *
-lex_parse_string(const char *p, struct lex_token *token)
-{
-    const char *start = ++p;
-    char * s = NULL;
-    for (;;) {
-        switch (*p) {
-        case '\0':
-            lex_error(token, "Input ends inside quoted string.");
-            return p;
-
-        case '"':
-            token->type = (json_string_unescape(start, p - start, &s)
-                           ? LEX_T_STRING : LEX_T_ERROR);
-            lex_token_strset(token, s);
-            return p + 1;
-
-        case '\\':
-            p++;
-            if (*p) {
-                p++;
-            }
-            break;
-
-        default:
-            p++;
-            break;
-        }
-    }
-}
-
-static bool
-lex_is_id1(unsigned char c)
-{
-    return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
-            || c == '_' || c == '.');
-}
-
-static bool
-lex_is_idn(unsigned char c)
-{
-    return lex_is_id1(c) || (c >= '0' && c <= '9');
-}
-
-static const char *
-lex_parse_id(const char *p, enum lex_type type, struct lex_token *token)
-{
-    const char *start = p;
-
-    do {
-        p++;
-    } while (lex_is_idn(*p));
-
-    token->type = type;
-    lex_token_strcpy(token, start, p - start);
-    return p;
-}
-
-static const char *
-lex_parse_addr_set(const char *p, struct lex_token *token)
-{
-    p++;
-    if (!lex_is_id1(*p)) {
-        lex_error(token, "`$' must be followed by a valid identifier.");
-        return p;
-    }
-
-    return lex_parse_id(p, LEX_T_MACRO, token);
-}
-
-static const char *
-lex_parse_port_group(const char *p, struct lex_token *token)
-{
-    p++;
-    if (!lex_is_id1(*p)) {
-        lex_error(token, "`@' must be followed by a valid identifier.");
-        return p;
-    }
-
-    return lex_parse_id(p, LEX_T_PORT_GROUP, token);
-}
-
-/* Initializes 'token' and parses the first token from the beginning of
- * null-terminated string 'p' into 'token'.  Stores a pointer to the start of
- * the token (after skipping white space and comments, if any) into '*startp'.
- * Returns the character position at which to begin parsing the next token. */
-const char *
-lex_token_parse(struct lex_token *token, const char *p, const char **startp)
-{
-    lex_token_init(token);
-
-next:
-    *startp = p;
-    switch (*p) {
-    case '\0':
-        token->type = LEX_T_END;
-        return p;
-
-    case ' ': case '\t': case '\n': case '\r': case '\v': case '\f':
-        p++;
-        goto next;
-
-    case '/':
-        p++;
-        if (*p == '/') {
-            do {
-                p++;
-            } while (*p != '\0' && *p != '\n');
-            goto next;
-        } else if (*p == '*') {
-            p++;
-            for (;;) {
-                if (*p == '*' && p[1] == '/') {
-                    p += 2;
-                    goto next;
-                } else if (*p == '\0' || *p == '\n') {
-                    lex_error(token, "`/*' without matching `*/'.");
-                    return p;
-                } else {
-                    p++;
-                }
-            }
-            goto next;
-        } else {
-            lex_error(token,
-                      "`/' is only valid as part of `//' or `/*'.");
-        }
-        break;
-
-    case '(':
-        token->type = LEX_T_LPAREN;
-        p++;
-        break;
-
-    case ')':
-        token->type = LEX_T_RPAREN;
-        p++;
-        break;
-
-    case '{':
-        token->type = LEX_T_LCURLY;
-        p++;
-        break;
-
-    case '}':
-        token->type = LEX_T_RCURLY;
-        p++;
-        break;
-
-    case '[':
-        token->type = LEX_T_LSQUARE;
-        p++;
-        break;
-
-    case ']':
-        token->type = LEX_T_RSQUARE;
-        p++;
-        break;
-
-    case '=':
-        p++;
-        if (*p == '=') {
-            token->type = LEX_T_EQ;
-            p++;
-        } else {
-            token->type = LEX_T_EQUALS;
-        }
-        break;
-
-    case '!':
-        p++;
-        if (*p == '=') {
-            token->type = LEX_T_NE;
-            p++;
-        } else {
-            token->type = LEX_T_LOG_NOT;
-        }
-        break;
-
-    case '&':
-        p++;
-        if (*p == '&') {
-            token->type = LEX_T_LOG_AND;
-            p++;
-        } else {
-            lex_error(token, "`&' is only valid as part of `&&'.");
-        }
-        break;
-
-    case '|':
-        p++;
-        if (*p == '|') {
-            token->type = LEX_T_LOG_OR;
-            p++;
-        } else {
-            lex_error(token, "`|' is only valid as part of `||'.");
-        }
-        break;
-
-    case '<':
-        p++;
-        if (*p == '=') {
-            token->type = LEX_T_LE;
-            p++;
-        } else if (*p == '-' && p[1] == '>') {
-            token->type = LEX_T_EXCHANGE;
-            p += 2;
-        } else {
-            token->type = LEX_T_LT;
-        }
-        break;
-
-    case '>':
-        p++;
-        if (*p == '=') {
-            token->type = LEX_T_GE;
-            p++;
-        } else {
-            token->type = LEX_T_GT;
-        }
-        break;
-
-    case '.':
-        p++;
-        if (*p == '.') {
-            token->type = LEX_T_ELLIPSIS;
-            p++;
-        } else {
-            lex_error(token, "`.' is only valid as part of `..' or a number.");
-        }
-        break;
-
-    case ',':
-        p++;
-        token->type = LEX_T_COMMA;
-        break;
-
-    case ';':
-        p++;
-        token->type = LEX_T_SEMICOLON;
-        break;
-
-    case '-':
-        p++;
-        if (*p == '-') {
-            token->type = LEX_T_DECREMENT;
-            p++;
-        } else {
-            lex_error(token, "`-' is only valid as part of `--'.");
-        }
-        break;
-
-    case '$':
-        p = lex_parse_addr_set(p, token);
-        break;
-
-    case '@':
-        p = lex_parse_port_group(p, token);
-        break;
-
-    case ':':
-        if (p[1] != ':') {
-            token->type = LEX_T_COLON;
-            p++;
-            break;
-        }
-        /* IPv6 address beginning with "::". */
-        /* fall through */
-    case '0': case '1': case '2': case '3': case '4':
-    case '5': case '6': case '7': case '8': case '9':
-        p = lex_parse_integer(p, token);
-        break;
-
-    case '"':
-        p = lex_parse_string(p, token);
-        break;
-
-    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
-    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
-        /* We need to distinguish an Ethernet address or IPv6 address from an
-         * identifier.  Fortunately, Ethernet addresses and IPv6 addresses that
-         * are ambiguous based on the first character, always start with hex
-         * digits followed by a colon, but identifiers never do. */
-        p = (p[strspn(p, "0123456789abcdefABCDEF")] == ':'
-             ? lex_parse_integer(p, token)
-             : lex_parse_id(p, LEX_T_ID, token));
-        break;
-
-    default:
-        if (lex_is_id1(*p)) {
-            p = lex_parse_id(p, LEX_T_ID, token);
-        } else {
-            if (isprint((unsigned char) *p)) {
-                lex_error(token, "Invalid character `%c' in input.", *p);
-            } else {
-                lex_error(token, "Invalid byte 0x%d in input.", *p);
-            }
-            p++;
-        }
-        break;
-    }
-
-    return p;
-}
-
-/* Initializes 'lexer' for parsing 'input'.
- *
- * While the lexer is in use, 'input' must remain available, but the caller
- * otherwise retains ownership of 'input'.
- *
- * The caller must call lexer_get() to obtain the first token. */
-void
-lexer_init(struct lexer *lexer, const char *input)
-{
-    lexer->input = input;
-    lexer->start = NULL;
-    lex_token_init(&lexer->token);
-    lexer->error = NULL;
-}
-
-/* Frees storage associated with 'lexer'. */
-void
-lexer_destroy(struct lexer *lexer)
-{
-    lex_token_destroy(&lexer->token);
-    free(lexer->error);
-}
-
-/* Obtains the next token from 'lexer' into 'lexer->token', and returns the
- * token's type.  The caller may examine 'lexer->token' directly to obtain full
- * information about the token. */
-enum lex_type
-lexer_get(struct lexer *lexer)
-{
-    lex_token_destroy(&lexer->token);
-    lexer->input = lex_token_parse(&lexer->token, lexer->input, &lexer->start);
-    return lexer->token.type;
-}
-
-/* Returns the type of the next token that will be fetched by lexer_get(),
- * without advancing 'lexer->token' to that token. */
-enum lex_type
-lexer_lookahead(const struct lexer *lexer)
-{
-    struct lex_token next;
-    enum lex_type type;
-    const char *start;
-
-    lex_token_parse(&next, lexer->input, &start);
-    type = next.type;
-    lex_token_destroy(&next);
-    return type;
-}
-
-/* If 'lexer''s current token has the given 'type', advances 'lexer' to the
- * next token and returns true.  Otherwise returns false. */
-bool
-lexer_match(struct lexer *lexer, enum lex_type type)
-{
-    if (lexer->token.type == type) {
-        lexer_get(lexer);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool
-lexer_force_match(struct lexer *lexer, enum lex_type t)
-{
-    if (t == LEX_T_END) {
-        return lexer_force_end(lexer);
-    } else if (lexer_match(lexer, t)) {
-        return true;
-    } else {
-        struct lex_token token = { .type = t };
-        struct ds s = DS_EMPTY_INITIALIZER;
-        lex_token_format(&token, &s);
-
-        lexer_syntax_error(lexer, "expecting `%s'", ds_cstr(&s));
-
-        ds_destroy(&s);
-
-        return false;
-    }
-}
-
-/* If 'lexer''s current token is the identifier given in 'id', advances 'lexer'
- * to the next token and returns true.  Otherwise returns false.  */
-bool
-lexer_match_id(struct lexer *lexer, const char *id)
-{
-    if (lexer->token.type == LEX_T_ID && !strcmp(lexer->token.s, id)) {
-        lexer_get(lexer);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool
-lexer_is_int(const struct lexer *lexer)
-{
-    return (lexer->token.type == LEX_T_INTEGER
-            && lexer->token.format == LEX_F_DECIMAL
-            && ntohll(lexer->token.value.integer) <= INT_MAX);
-}
-
-bool
-lexer_get_int(struct lexer *lexer, int *value)
-{
-    if (lexer_is_int(lexer)) {
-        *value = ntohll(lexer->token.value.integer);
-        lexer_get(lexer);
-        return true;
-    } else {
-        *value = 0;
-        return false;
-    }
-}
-
-bool
-lexer_force_int(struct lexer *lexer, int *value)
-{
-    bool ok = lexer_get_int(lexer, value);
-    if (!ok) {
-        lexer_syntax_error(lexer, "expecting small integer");
-    }
-    return ok;
-}
-
-bool
-lexer_force_end(struct lexer *lexer)
-{
-    if (lexer->token.type == LEX_T_END) {
-        return true;
-    } else {
-        lexer_syntax_error(lexer, "expecting end of input");
-        return false;
-    }
-}
-
-static bool
-lexer_error_handle_common(struct lexer *lexer)
-{
-    if (lexer->error) {
-        /* Already have an error, suppress this one since the cascade seems
-         * unlikely to be useful. */
-        return true;
-    } else if (lexer->token.type == LEX_T_ERROR) {
-        /* The lexer signaled an error.  Nothing at a higher level accepts an
-         * error token, so we'll inevitably end up here with some meaningless
-         * parse error.  Report the lexical error instead. */
-        lexer->error = xstrdup(lexer->token.s);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void OVS_PRINTF_FORMAT(2, 3)
-lexer_error(struct lexer *lexer, const char *message, ...)
-{
-    if (lexer_error_handle_common(lexer)) {
-        return;
-    }
-
-    va_list args;
-    va_start(args, message);
-    lexer->error = xvasprintf(message, args);
-    va_end(args);
-}
-
-void OVS_PRINTF_FORMAT(2, 3)
-lexer_syntax_error(struct lexer *lexer, const char *message, ...)
-{
-    if (lexer_error_handle_common(lexer)) {
-        return;
-    }
-
-    struct ds s;
-
-    ds_init(&s);
-    ds_put_cstr(&s, "Syntax error");
-    if (lexer->token.type == LEX_T_END) {
-        ds_put_cstr(&s, " at end of input");
-    } else if (lexer->start) {
-        ds_put_format(&s, " at `%.*s'",
-                      (int) (lexer->input - lexer->start),
-                      lexer->start);
-    }
-
-    if (message) {
-        ds_put_char(&s, ' ');
-
-        va_list args;
-        va_start(args, message);
-        ds_put_format_valist(&s, message, args);
-        va_end(args);
-    }
-    ds_put_char(&s, '.');
-
-    lexer->error = ds_steal_cstr(&s);
-}
-
-char *
-lexer_steal_error(struct lexer *lexer)
-{
-    char *error = lexer->error;
-    lexer->error = NULL;
-    return error;
-}
diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c
deleted file mode 100644
index 4ad5bf481..000000000
--- a/ovn/lib/logical-fields.c
+++ /dev/null
@@ -1,261 +0,0 @@
-/* Copyright (c) 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 "openvswitch/shash.h"
-#include "ovn/expr.h"
-#include "ovn/logical-fields.h"
-#include "ovs-thread.h"
-#include "packets.h"
-
-/* Silence a warning. */
-extern const struct ovn_field ovn_fields[OVN_FIELD_N_IDS];
-
-const struct ovn_field ovn_fields[OVN_FIELD_N_IDS] = {
-    {
-        OVN_ICMP4_FRAG_MTU,
-        "icmp4.frag_mtu",
-        2, 16,
-    },
-};
-
-static struct shash ovnfield_by_name;
-
-static void
-add_subregister(const char *name,
-                const char *parent_name, int parent_idx,
-                int width, int idx,
-                struct shash *symtab)
-{
-    int lsb = width * idx;
-    int msb = lsb + (width - 1);
-    char *expansion = xasprintf("%s%d[%d..%d]",
-                                parent_name, parent_idx, lsb, msb);
-    expr_symtab_add_subfield(symtab, name, NULL, expansion);
-    free(expansion);
-}
-
-static void
-add_ct_bit(const char *name, int index, struct shash *symtab)
-{
-    char *expansion = xasprintf("ct_state[%d]", index);
-    const char *prereqs = index == CS_TRACKED_BIT ? NULL : "ct.trk";
-    expr_symtab_add_subfield(symtab, name, prereqs, expansion);
-    free(expansion);
-}
-
-void
-ovn_init_symtab(struct shash *symtab)
-{
-    shash_init(symtab);
-
-    /* Reserve a pair of registers for the logical inport and outport.  A full
-     * 32-bit register each is bigger than we need, but the expression code
-     * doesn't yet support string fields that occupy less than a full OXM. */
-    expr_symtab_add_string(symtab, "inport", MFF_LOG_INPORT, NULL);
-    expr_symtab_add_string(symtab, "outport", MFF_LOG_OUTPORT, NULL);
-
-    /* Logical registers:
-     *     128-bit xxregs
-     *     64-bit xregs
-     *     32-bit regs
-     *
-     * The expression language doesn't handle overlapping fields properly
-     * unless they're formally defined as subfields.  It's a little awkward. */
-    for (int xxi = 0; xxi < MFF_N_LOG_REGS / 4; xxi++) {
-        char *xxname = xasprintf("xxreg%d", xxi);
-        expr_symtab_add_field(symtab, xxname, MFF_XXREG0 + xxi, NULL, false);
-        free(xxname);
-    }
-    for (int xi = 0; xi < MFF_N_LOG_REGS / 2; xi++) {
-        char *xname = xasprintf("xreg%d", xi);
-        int xxi = xi / 2;
-        if (xxi < MFF_N_LOG_REGS / 4) {
-            add_subregister(xname, "xxreg", xxi, 64, 1 - xi % 2, symtab);
-        } else {
-            expr_symtab_add_field(symtab, xname, MFF_XREG0 + xi, NULL, false);
-        }
-        free(xname);
-    }
-    for (int i = 0; i < MFF_N_LOG_REGS; i++) {
-        char *name = xasprintf("reg%d", i);
-        int xxi = i / 4;
-        int xi = i / 2;
-        if (xxi < MFF_N_LOG_REGS / 4) {
-            add_subregister(name, "xxreg", xxi, 32, 3 - i % 4, symtab);
-        } else if (xi < MFF_N_LOG_REGS / 2) {
-            add_subregister(name, "xreg", xi, 32, 1 - i % 2, symtab);
-        } else {
-            expr_symtab_add_field(symtab, name, MFF_REG0 + i, NULL, false);
-        }
-        free(name);
-    }
-
-    /* Flags used in logical to physical transformation. */
-    expr_symtab_add_field(symtab, "flags", MFF_LOG_FLAGS, NULL, false);
-    char flags_str[16];
-    snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_ALLOW_LOOPBACK_BIT);
-    expr_symtab_add_subfield(symtab, "flags.loopback", NULL, flags_str);
-    snprintf(flags_str, sizeof flags_str, "flags[%d]",
-             MLF_FORCE_SNAT_FOR_DNAT_BIT);
-    expr_symtab_add_subfield(symtab, "flags.force_snat_for_dnat", NULL,
-                             flags_str);
-    snprintf(flags_str, sizeof flags_str, "flags[%d]",
-             MLF_FORCE_SNAT_FOR_LB_BIT);
-    expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL,
-                             flags_str);
-
-    /* Connection tracking state. */
-    expr_symtab_add_field(symtab, "ct_mark", MFF_CT_MARK, NULL, false);
-
-    expr_symtab_add_field(symtab, "ct_label", MFF_CT_LABEL, NULL, false);
-    expr_symtab_add_subfield(symtab, "ct_label.blocked", NULL, "ct_label[0]");
-
-    expr_symtab_add_field(symtab, "ct_state", MFF_CT_STATE, NULL, false);
-
-#define CS_STATE(ENUM, INDEX, NAME) \
-    add_ct_bit("ct."NAME, CS_##ENUM##_BIT, symtab);
-    CS_STATES
-#undef CS_STATE
-
-    /* Data fields. */
-    expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
-    expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
-    expr_symtab_add_field(symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
-    expr_symtab_add_predicate(symtab, "eth.bcast",
-                              "eth.dst == ff:ff:ff:ff:ff:ff");
-    expr_symtab_add_subfield(symtab, "eth.mcast", NULL, "eth.dst[40]");
-
-    expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
-    expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]");
-    expr_symtab_add_subfield(symtab, "vlan.pcp", "vlan.present",
-                             "vlan.tci[13..15]");
-    expr_symtab_add_subfield(symtab, "vlan.vid", "vlan.present",
-                             "vlan.tci[0..11]");
-
-    expr_symtab_add_predicate(symtab, "ip4", "eth.type == 0x800");
-    expr_symtab_add_predicate(symtab, "ip6", "eth.type == 0x86dd");
-    expr_symtab_add_predicate(symtab, "ip", "ip4 || ip6");
-    expr_symtab_add_field(symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
-    expr_symtab_add_field(symtab, "ip.dscp", MFF_IP_DSCP_SHIFTED, "ip", false);
-    expr_symtab_add_field(symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
-    expr_symtab_add_field(symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
-
-    expr_symtab_add_field(symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
-    expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
-    expr_symtab_add_predicate(symtab, "ip4.mcast", "ip4.dst[28..31] == 0xe");
-
-    expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1");
-    expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
-              false);
-    expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
-              false);
-
-    expr_symtab_add_predicate(symtab, "igmp", "ip4 && ip.proto == 2");
-
-    expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
-    expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
-    expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
-
-    expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58");
-    expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
-                          true);
-    expr_symtab_add_field(symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
-                          true);
-
-    expr_symtab_add_predicate(symtab, "icmp", "icmp4 || icmp6");
-
-    expr_symtab_add_field(symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
-    expr_symtab_add_predicate(symtab, "ip.is_frag", "ip.frag[0]");
-    expr_symtab_add_predicate(symtab, "ip.later_frag", "ip.frag[1]");
-    expr_symtab_add_predicate(symtab, "ip.first_frag",
-                              "ip.is_frag && !ip.later_frag");
-
-    expr_symtab_add_predicate(symtab, "arp", "eth.type == 0x806");
-    expr_symtab_add_field(symtab, "arp.op", MFF_ARP_OP, "arp", false);
-    expr_symtab_add_field(symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false);
-
-    expr_symtab_add_predicate(symtab, "nd",
-              "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_ns",
-              "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_na",
-              "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_rs",
-              "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_ra",
-              "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false);
-    expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false);
-    expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
-
-    expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
-    expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
-    expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
-    expr_symtab_add_field(symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
-
-    expr_symtab_add_predicate(symtab, "udp", "ip.proto == 17");
-    expr_symtab_add_field(symtab, "udp.src", MFF_UDP_SRC, "udp", false);
-    expr_symtab_add_field(symtab, "udp.dst", MFF_UDP_DST, "udp", false);
-
-    expr_symtab_add_predicate(symtab, "sctp", "ip.proto == 132");
-    expr_symtab_add_field(symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
-    expr_symtab_add_field(symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
-
-    shash_init(&ovnfield_by_name);
-    for (int i = 0; i < OVN_FIELD_N_IDS; i++) {
-        const struct ovn_field *of = &ovn_fields[i];
-        ovs_assert(of->id == i); /* Fields must be in the enum order. */
-        shash_add_once(&ovnfield_by_name, of->name, of);
-    }
-    expr_symtab_add_ovn_field(symtab, "icmp4.frag_mtu", OVN_ICMP4_FRAG_MTU);
-}
-
-const char *
-event_to_string(enum ovn_controller_event event)
-{
-    switch (event) {
-    case OVN_EVENT_EMPTY_LB_BACKENDS:
-        return "empty_lb_backends";
-    case OVN_EVENT_MAX:
-    default:
-        return "";
-    }
-}
-
-int
-string_to_event(const char *s)
-{
-    if (!strcmp(s, "empty_lb_backends")) {
-        return OVN_EVENT_EMPTY_LB_BACKENDS;
-    }
-    return -1;
-}
-
-const struct ovn_field *
-ovn_field_from_name(const char *name)
-{
-    return shash_find_data(&ovnfield_by_name, name);
-}
-
-void
-ovn_destroy_ovnfields(void)
-{
-    shash_destroy(&ovnfield_by_name);
-}
diff --git a/ovn/lib/mcast-group-index.c b/ovn/lib/mcast-group-index.c
deleted file mode 100644
index 740311e00..000000000
--- a/ovn/lib/mcast-group-index.c
+++ /dev/null
@@ -1,43 +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 "ovn/lib/mcast-group-index.h"
-#include "ovn/lib/ovn-sb-idl.h"
-
-struct ovsdb_idl_index *
-mcast_group_index_create(struct ovsdb_idl *idl)
-{
-    return ovsdb_idl_index_create2(idl, &sbrec_multicast_group_col_name,
-                                   &sbrec_multicast_group_col_datapath);
-}
-
-const struct sbrec_multicast_group *
-mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index,
-                   const char *name,
-                   const struct sbrec_datapath_binding *datapath)
-{
-    struct sbrec_multicast_group *target =
-        sbrec_multicast_group_index_init_row(mcgroup_index);
-    sbrec_multicast_group_index_set_name(target, name);
-    sbrec_multicast_group_index_set_datapath(target, datapath);
-
-    struct sbrec_multicast_group *mcgroup =
-        sbrec_multicast_group_index_find(mcgroup_index, target);
-    sbrec_multicast_group_index_destroy_row(target);
-
-    return mcgroup;
-}
diff --git a/ovn/lib/mcast-group-index.h b/ovn/lib/mcast-group-index.h
deleted file mode 100644
index 859e6a72f..000000000
--- a/ovn/lib/mcast-group-index.h
+++ /dev/null
@@ -1,32 +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_MCAST_GROUP_INDEX_H
-#define OVN_MCAST_GROUP_INDEX_H 1
-
-struct ovsdb_idl;
-
-struct sbrec_datapath_binding;
-
-#define OVN_MCAST_FLOOD_TUNNEL_KEY   65535
-#define OVN_MCAST_UNKNOWN_TUNNEL_KEY (OVN_MCAST_FLOOD_TUNNEL_KEY - 1)
-
-struct ovsdb_idl_index *mcast_group_index_create(struct ovsdb_idl *);
-const struct sbrec_multicast_group *
-mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index,
-                   const char *name,
-                   const struct sbrec_datapath_binding *datapath);
-
-#endif /* ovn/lib/mcast-group-index.h */
diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
deleted file mode 100644
index c93def450..000000000
--- a/ovn/lib/ovn-l7.h
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (c) 2016 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_DHCP_H
-#define OVN_DHCP_H 1
-
-#include <sys/types.h>
-#include <netinet/in.h>
-#include <netinet/icmp6.h>
-#include "openvswitch/hmap.h"
-#include "hash.h"
-#include "ovn/logical-fields.h"
-
-/* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */
-struct gen_opts_map {
-    struct hmap_node hmap_node;
-    char *name;
-    char *type;
-    size_t code;
-};
-
-#define DHCP_OPTION(NAME, CODE, TYPE) \
-    {.name = NAME, .code = CODE, .type = TYPE}
-
-#define OFFERIP              DHCP_OPTION("offerip", 0, "ipv4")
-#define DHCP_OPT_NETMASK     DHCP_OPTION("netmask", 1, "ipv4")
-#define DHCP_OPT_ROUTER      DHCP_OPTION("router", 3, "ipv4")
-#define DHCP_OPT_DNS_SERVER  DHCP_OPTION("dns_server", 6, "ipv4")
-#define DHCP_OPT_LOG_SERVER  DHCP_OPTION("log_server", 7, "ipv4")
-#define DHCP_OPT_LPR_SERVER  DHCP_OPTION("lpr_server", 9, "ipv4")
-#define DHCP_OPT_DOMAIN_NAME DHCP_OPTION("domain_name", 15, "str")
-#define DHCP_OPT_SWAP_SERVER DHCP_OPTION("swap_server", 16, "ipv4")
-
-#define DHCP_OPT_POLICY_FILTER \
-    DHCP_OPTION("policy_filter", 21, "ipv4")
-
-#define DHCP_OPT_ROUTER_SOLICITATION \
-    DHCP_OPTION("router_solicitation", 32, "ipv4")
-
-#define DHCP_OPT_NIS_SERVER  DHCP_OPTION("nis_server", 41, "ipv4")
-#define DHCP_OPT_NTP_SERVER  DHCP_OPTION("ntp_server", 42, "ipv4")
-#define DHCP_OPT_SERVER_ID   DHCP_OPTION("server_id", 54, "ipv4")
-#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, "ipv4")
-
-#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \
-    DHCP_OPTION("classless_static_route", 121, "static_routes")
-#define DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE \
-    DHCP_OPTION("ms_classless_static_route", 249, "static_routes")
-
-#define DHCP_OPT_IP_FORWARD_ENABLE DHCP_OPTION("ip_forward_enable", 19, "bool")
-#define DHCP_OPT_ROUTER_DISCOVERY DHCP_OPTION("router_discovery", 31, "bool")
-#define DHCP_OPT_ETHERNET_ENCAP DHCP_OPTION("ethernet_encap", 36, "bool")
-
-#define DHCP_OPT_DEFAULT_TTL DHCP_OPTION("default_ttl", 23, "uint8")
-
-#define DHCP_OPT_TCP_TTL  DHCP_OPTION("tcp_ttl", 37, "uint8")
-#define DHCP_OPT_MTU      DHCP_OPTION("mtu", 26, "uint16")
-#define DHCP_OPT_LEASE_TIME DHCP_OPTION("lease_time", 51, "uint32")
-#define DHCP_OPT_T1 DHCP_OPTION("T1", 58, "uint32")
-#define DHCP_OPT_T2 DHCP_OPTION("T2", 59, "uint32")
-
-#define DHCP_OPT_BOOTFILE DHCP_OPTION("bootfile_name", 67, "str")
-#define DHCP_OPT_WPAD DHCP_OPTION("wpad", 252, "str")
-#define DHCP_OPT_PATH_PREFIX DHCP_OPTION("path_prefix", 210, "str")
-#define DHCP_OPT_TFTP_SERVER_ADDRESS \
-    DHCP_OPTION("tftp_server_address", 150, "ipv4")
-
-static inline uint32_t
-gen_opt_hash(char *opt_name)
-{
-    return hash_string(opt_name, 0);
-}
-
-static inline uint32_t
-dhcp_opt_hash(char *opt_name)
-{
-    return gen_opt_hash(opt_name);
-}
-
-static inline struct gen_opts_map *
-gen_opts_find(const struct hmap *gen_opts, char *opt_name)
-{
-    struct gen_opts_map *gen_opt;
-    HMAP_FOR_EACH_WITH_HASH (gen_opt, hmap_node, gen_opt_hash(opt_name),
-                             gen_opts) {
-        if (!strcmp(gen_opt->name, opt_name)) {
-            return gen_opt;
-        }
-    }
-
-    return NULL;
-}
-
-static inline struct gen_opts_map *
-dhcp_opts_find(const struct hmap *dhcp_opts, char *opt_name)
-{
-    return gen_opts_find(dhcp_opts, opt_name);
-}
-
-static inline void
-gen_opt_add(struct hmap *gen_opts, char *opt_name, size_t code, char *type)
-{
-    struct gen_opts_map *gen_opt = xzalloc(sizeof *gen_opt);
-    gen_opt->name = xstrdup(opt_name);
-    gen_opt->code = code;
-    gen_opt->type = xstrdup(type);
-    hmap_insert(gen_opts, &gen_opt->hmap_node, gen_opt_hash(opt_name));
-}
-
-static inline void
-dhcp_opt_add(struct hmap *dhcp_opts, char *opt_name, size_t code, char *type)
-{
-    gen_opt_add(dhcp_opts, opt_name, code, type);
-}
-
-static inline void
-gen_opts_destroy(struct hmap *gen_opts)
-{
-    struct gen_opts_map *gen_opt;
-    HMAP_FOR_EACH_POP (gen_opt, hmap_node, gen_opts) {
-        free(gen_opt->name);
-        free(gen_opt->type);
-        free(gen_opt);
-    }
-    hmap_destroy(gen_opts);
-}
-
-static inline void
-dhcp_opts_destroy(struct hmap *dhcp_opts)
-{
-    gen_opts_destroy(dhcp_opts);
-}
-
-OVS_PACKED(
-struct dhcp_opt_header {
-    uint8_t code;
-    uint8_t len;
-});
-
-#define DHCP_OPT_PAYLOAD(hdr) \
-    (void *)((char *)hdr + sizeof(struct dhcp_opt_header))
-
-/* Used in the OpenFlow PACKET_IN userdata */
-struct dhcp_opt6_header {
-    ovs_be16 opt_code;
-    ovs_be16 size;
-};
-
-/* Supported DHCPv6 Message Types */
-#define DHCPV6_MSG_TYPE_SOLICIT     1
-#define DHCPV6_MSG_TYPE_ADVT        2
-#define DHCPV6_MSG_TYPE_REQUEST     3
-#define DHCPV6_MSG_TYPE_CONFIRM     4
-#define DHCPV6_MSG_TYPE_REPLY       7
-#define DHCPV6_MSG_TYPE_DECLINE     9
-#define DHCPV6_MSG_TYPE_INFO_REQ    11
-
-
-/* DHCPv6 Option codes */
-#define DHCPV6_OPT_CLIENT_ID_CODE        1
-#define DHCPV6_OPT_SERVER_ID_CODE        2
-#define DHCPV6_OPT_IA_NA_CODE            3
-#define DHCPV6_OPT_IA_ADDR_CODE          5
-#define DHCPV6_OPT_DNS_SERVER_CODE       23
-#define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
-
-#define DHCPV6_OPT_SERVER_ID \
-    DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac")
-
-#define DHCPV6_OPT_IA_ADDR  \
-    DHCP_OPTION("ia_addr", DHCPV6_OPT_IA_ADDR_CODE, "ipv6")
-
-#define DHCPV6_OPT_DNS_SERVER  \
-    DHCP_OPTION("dns_server", DHCPV6_OPT_DNS_SERVER_CODE, "ipv6")
-
-#define DHCPV6_OPT_DOMAIN_SEARCH \
-    DHCP_OPTION("domain_search", DHCPV6_OPT_DOMAIN_SEARCH_CODE, "str")
-
-OVS_PACKED(
-struct dhcpv6_opt_header {
-    ovs_be16 code;
-    ovs_be16 len;
-});
-
-OVS_PACKED(
-struct dhcpv6_opt_server_id {
-    struct dhcpv6_opt_header opt;
-    ovs_be16 duid_type;
-    ovs_be16 hw_type;
-    struct eth_addr mac;
-});
-
-
-OVS_PACKED(
-struct dhcpv6_opt_ia_addr {
-    struct dhcpv6_opt_header opt;
-    struct in6_addr ipv6;
-    ovs_be32 t1;
-    ovs_be32 t2;
-});
-
-OVS_PACKED(
-struct dhcpv6_opt_ia_na {
-    struct dhcpv6_opt_header opt;
-    ovs_be32 iaid;
-    ovs_be32 t1;
-    ovs_be32 t2;
-});
-
-#define DHCPV6_DUID_LL      3
-#define DHCPV6_HW_TYPE_ETH  1
-
-#define DHCPV6_OPT_PAYLOAD(opt) \
-    (void *)((char *)opt + sizeof(struct dhcpv6_opt_header))
-
-static inline struct gen_opts_map *
-nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name)
-{
-    return gen_opts_find(nd_ra_opts, opt_name);
-}
-
-static inline void
-nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code,
-               char *type)
-{
-    gen_opt_add(nd_ra_opts, opt_name, code, type);
-}
-
-static inline void
-nd_ra_opts_destroy(struct hmap *nd_ra_opts)
-{
-    gen_opts_destroy(nd_ra_opts);
-}
-
-
-#define ND_RA_FLAG_ADDR_MODE    0
-
-
-/* Default values of various IPv6 Neighbor Discovery protocol options and
- * flags. See RFC 4861 for more information.
- * */
-#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG         0x80
-#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG           0x40
-
-#define IPV6_ND_RA_CUR_HOP_LIMIT                    255
-#define IPV6_ND_RA_LIFETIME                         0xffff
-#define IPV6_ND_RA_REACHABLE_TIME                   0
-#define IPV6_ND_RA_RETRANSMIT_TIMER                 0
-
-#define IPV6_ND_RA_OPT_PREFIX_ON_LINK               0x80
-#define IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS            0x40
-#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME        0xffffffff
-#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME    0xffffffff
-
-static inline void
-nd_ra_opts_init(struct hmap *nd_ra_opts)
-{
-    nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str");
-    nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac");
-    nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6");
-    nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
-}
-
-#define EMPTY_LB_VIP           1
-#define EMPTY_LB_PROTOCOL      2
-#define EMPTY_LB_LOAD_BALANCER 3
-
-/* Used in the OpenFlow PACKET_IN userdata */
-struct controller_event_opt_header {
-    ovs_be16 opt_code;
-    ovs_be16 size;
-};
-
-struct controller_event_options {
-    struct hmap event_opts[OVN_EVENT_MAX];
-};
-
-static inline void
-controller_event_opt_add(struct controller_event_options *event_opts,
-                         enum ovn_controller_event event_type, char *opt_name,
-                         size_t opt_code, char *opt_type)
-{
-    gen_opt_add(&event_opts->event_opts[event_type], opt_name, opt_code,
-                opt_type);
-}
-
-static inline void
-controller_event_opts_init(struct controller_event_options *opts)
-{
-    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
-        hmap_init(&opts->event_opts[i]);
-    }
-    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "vip",
-                             EMPTY_LB_VIP, "str");
-    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "protocol",
-                             EMPTY_LB_PROTOCOL, "str");
-    controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS,
-                             "load_balancer", EMPTY_LB_LOAD_BALANCER, "str");
-}
-
-static inline void
-controller_event_opts_destroy(struct controller_event_options *opts)
-{
-    for (size_t i = 0; i < OVN_EVENT_MAX; i++) {
-        gen_opts_destroy(&opts->event_opts[i]);
-    }
-}
-
-#endif /* OVN_DHCP_H */
diff --git a/ovn/northd/.gitignore b/ovn/northd/.gitignore
deleted file mode 100644
index 97a59801b..000000000
--- a/ovn/northd/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/ovn-northd
-/ovn-northd.8
diff --git a/ovn/northd/automake.mk b/ovn/northd/automake.mk
deleted file mode 100644
index 93aebe8b1..000000000
--- a/ovn/northd/automake.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# ovn-northd
-bin_PROGRAMS += ovn/northd/ovn-northd
-ovn_northd_ovn_northd_SOURCES = ovn/northd/ovn-northd.c
-ovn_northd_ovn_northd_LDADD = \
-	ovn/lib/libovn.la \
-	ovsdb/libovsdb.la \
-	lib/libopenvswitch.la
-man_MANS += ovn/northd/ovn-northd.8
-EXTRA_DIST += ovn/northd/ovn-northd.8.xml
-CLEANFILES += ovn/northd/ovn-northd.8
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
deleted file mode 100644
index d2267de0e..000000000
--- a/ovn/northd/ovn-northd.8.xml
+++ /dev/null
@@ -1,2544 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-northd" section="8" title="ovn-northd">
-    <h1>Name</h1>
-    <p>ovn-northd -- Open Virtual Network central control daemon</p>
-
-    <h1>Synopsis</h1>
-    <p><code>ovn-northd</code> [<var>options</var>]</p>
-
-    <h1>Description</h1>
-    <p>
-      <code>ovn-northd</code> is a centralized daemon responsible for
-      translating the high-level OVN configuration into logical
-      configuration consumable by daemons such as
-      <code>ovn-controller</code>.  It translates the logical network
-      configuration in terms of conventional network concepts, taken
-      from the OVN Northbound Database (see <code>ovn-nb</code>(5)),
-      into logical datapath flows in the OVN Southbound Database (see
-      <code>ovn-sb</code>(5)) below it.
-    </p>
-
-    <h1>Options</h1>
-    <dl>
-      <dt><code>--ovnnb-db=<var>database</var></code></dt>
-      <dd>
-        The OVSDB database containing the OVN Northbound Database.  If the
-        <env>OVN_NB_DB</env> environment variable is set, its value is used
-        as the default.  Otherwise, the default is
-        <code>unix:@RUNDIR@/ovnnb_db.sock</code>.
-      </dd>
-      <dt><code>--ovnsb-db=<var>database</var></code></dt>
-      <dd>
-        The OVSDB database containing the OVN Southbound Database.  If the
-        <env>OVN_SB_DB</env> environment variable is set, its value is used
-        as the default.  Otherwise, the default is
-        <code>unix:@RUNDIR@/ovnsb_db.sock</code>.
-      </dd>
-    </dl>
-    <p>
-      <var>database</var> in the above options must be an OVSDB active or
-      passive connection method, as described in <code>ovsdb</code>(7).
-    </p>
-
-    <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"/>
-
-    <h2>Other Options</h2>
-    <xi:include href="lib/unixctl.xml"
-     xmlns:xi="http://www.w3.org/2003/XInclude"/>
-    <h3></h3>
-    <xi:include href="lib/common.xml"
-     xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-    <h1>Runtime Management Commands</h1>
-    <p>
-      <code>ovs-appctl</code> can send commands to a running
-      <code>ovn-northd</code> process.  The currently supported commands
-      are described below.
-      <dl>
-      <dt><code>exit</code></dt>
-      <dd>
-        Causes <code>ovn-northd</code> to gracefully terminate.
-      </dd>
-      </dl>
-    </p>
-
-    <h1>Active-Standby for High Availability</h1>
-    <p>
-      You may run <code>ovn-northd</code> more than once in an OVN deployment.
-      OVN will automatically ensure that only one of them is active at a time.
-      If multiple instances of <code>ovn-northd</code> are running and the
-      active <code>ovn-northd</code> fails, one of the hot standby instances
-      of <code>ovn-northd</code> will automatically take over.
-    </p>
-
-    <h1>Logical Flow Table Structure</h1>
-
-    <p>
-      One of the main purposes of <code>ovn-northd</code> is to populate the
-      <code>Logical_Flow</code> table in the <code>OVN_Southbound</code>
-      database.  This section describes how <code>ovn-northd</code> does this
-      for switch and router logical datapaths.
-    </p>
-
-    <h2>Logical Switch Datapaths</h2>
-
-    <h3>Ingress Table 0: Admission Control and Ingress Port Security - L2</h3>
-
-    <p>
-      Ingress table 0 contains these logical flows:
-    </p>
-
-    <ul>
-      <li>
-        Priority 100 flows to drop packets with VLAN tags or multicast Ethernet
-        source addresses.
-      </li>
-
-      <li>
-        Priority 50 flows that implement ingress port security for each enabled
-        logical port.  For logical ports on which port security is enabled,
-        these match the <code>inport</code> and the valid <code>eth.src</code>
-        address(es) and advance only those packets to the next flow table.  For
-        logical ports on which port security is not enabled, these advance all
-        packets that match the <code>inport</code>.
-      </li>
-    </ul>
-
-    <p>
-      There are no flows for disabled logical ports because the default-drop
-      behavior of logical flow tables causes packets that ingress from them to
-      be dropped.
-    </p>
-
-    <h3>Ingress Table 1: Ingress Port Security - IP</h3>
-
-    <p>
-      Ingress table 1 contains these logical flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For each element in the port security set having one or more IPv4 or
-          IPv6 addresses (or both),
-        </p>
-
-        <ul>
-          <li>
-            Priority 90 flow to allow IPv4 traffic if it has IPv4 addresses
-            which match the <code>inport</code>, valid <code>eth.src</code>
-            and valid <code>ip4.src</code> address(es).
-          </li>
-
-          <li>
-            Priority 90 flow to allow IPv4 DHCP discovery traffic if it has a
-            valid <code>eth.src</code>. This is necessary since DHCP discovery
-            messages are sent from the unspecified IPv4 address (0.0.0.0) since
-            the IPv4 address has not yet been assigned.
-          </li>
-
-          <li>
-            Priority 90 flow to allow IPv6 traffic if it has IPv6 addresses
-            which match the <code>inport</code>, valid <code>eth.src</code> and
-            valid <code>ip6.src</code> address(es).
-          </li>
-
-          <li>
-            Priority 90 flow to allow IPv6 DAD (Duplicate Address Detection)
-            traffic if it has a valid <code>eth.src</code>. This is is
-            necessary since DAD include requires joining an multicast group and
-            sending neighbor solicitations for the newly assigned address. Since
-            no address is yet assigned, these are sent from the unspecified
-            IPv6 address (::).
-          </li>
-
-          <li>
-            Priority 80 flow to drop IP (both IPv4 and IPv6) traffic which
-            match the <code>inport</code> and valid <code>eth.src</code>.
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and advances to
-        the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 2: Ingress Port Security - Neighbor discovery</h3>
-
-    <p>
-      Ingress table 2 contains these logical flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For each element in the port security set,
-        </p>
-
-        <ul>
-          <li>
-            Priority 90 flow to allow ARP traffic which match the
-            <code>inport</code> and valid <code>eth.src</code> and
-            <code>arp.sha</code>. If the element has one or more
-            IPv4 addresses, then it also matches the valid
-            <code>arp.spa</code>.
-          </li>
-
-          <li>
-            Priority 90 flow to allow IPv6 Neighbor Solicitation and
-            Advertisement traffic which match the <code>inport</code>,
-            valid <code>eth.src</code> and
-            <code>nd.sll</code>/<code>nd.tll</code>.
-            If the element has one or more IPv6 addresses, then it also
-            matches the valid <code>nd.target</code> address(es) for Neighbor
-            Advertisement traffic.
-          </li>
-
-          <li>
-            Priority 80 flow to drop ARP and IPv6 Neighbor Solicitation and
-            Advertisement traffic which match the <code>inport</code> and
-            valid <code>eth.src</code>.
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and advances to
-        the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3>
-
-    <p>
-      This table prepares flows for possible stateful ACL processing in
-      ingress table <code>ACLs</code>.  It contains a priority-0 flow that
-      simply moves traffic to the next table.  If stateful ACLs are used in the
-      logical datapath, a priority-100 flow is added that sets a hint
-      (with <code>reg0[0] = 1; next;</code>) for table
-      <code>Pre-stateful</code> to send IP packets to the connection tracker
-      before eventually advancing to ingress table <code>ACLs</code>. If
-      special ports such as route ports or localnet ports can't use ct(), a
-      priority-110 flow is added to skip over stateful ACLs.
-    </p>
-
-    <h3>Ingress Table 4: Pre-LB</h3>
-
-    <p>
-      This table prepares flows for possible stateful load balancing processing
-      in ingress table <code>LB</code> and <code>Stateful</code>.  It contains
-      a priority-0 flow that simply moves traffic to the next table. Moreover
-      it contains a priority-110 flow to move IPv6 Neighbor Discovery traffic
-      to the next table. If load balancing rules with virtual IP addresses
-      (and ports) are configured in <code>OVN_Northbound</code> database for a
-      logical switch datapath, a priority-100 flow is added for each configured
-      virtual IP address <var>VIP</var>. For IPv4 <var>VIPs</var>, the match is
-      <code>ip &amp;&amp; ip4.dst == <var>VIP</var></code>. For IPv6
-      <var>VIPs</var>, the match is <code>ip &amp;&amp;
-      ip6.dst == <var>VIP</var></code>. The flow sets an action
-      <code>reg0[0] = 1; next;</code> to act as a hint for table
-      <code>Pre-stateful</code> to send IP packets to the connection tracker
-      for packet de-fragmentation before eventually advancing to ingress table
-      <code>LB</code>.
-    </p>
-
-    <h3>Ingress Table 5: Pre-stateful</h3>
-
-    <p>
-      This table prepares flows for all possible stateful processing
-      in next tables.  It contains a priority-0 flow that simply moves
-      traffic to the next table.  A priority-100 flow sends the packets to
-      connection tracker based on a hint provided by the previous tables
-      (with a match for <code>reg0[0] == 1</code>) by using the
-      <code>ct_next;</code> action.
-    </p>
-
-    <h3>Ingress table 6: <code>from-lport</code> ACLs</h3>
-
-    <p>
-      Logical flows in this table closely reproduce those in the
-      <code>ACL</code> table in the <code>OVN_Northbound</code> database
-      for the <code>from-lport</code> direction. The <code>priority</code>
-      values from the <code>ACL</code> table have a limited range and have
-      1000 added to them to leave room for OVN default flows at both
-      higher and lower priorities.
-    </p>
-    <ul>
-      <li>
-        <code>allow</code> ACLs translate into logical flows with
-        the <code>next;</code> action.  If there are any stateful ACLs
-        on this datapath, then <code>allow</code> ACLs translate to
-        <code>ct_commit; next;</code> (which acts as a hint for the next tables
-        to commit the connection to conntrack),
-      </li>
-      <li>
-        <code>allow-related</code> ACLs translate into logical
-        flows with the <code>ct_commit(ct_label=0/1); next;</code> actions
-        for new connections and <code>reg0[1] = 1; next;</code> for existing
-        connections.
-      </li>
-      <li>
-        Other ACLs translate to <code>drop;</code> for new or untracked
-        connections and <code>ct_commit(ct_label=1/1);</code> for known
-        connections.  Setting <code>ct_label</code> marks a connection
-        as one that was previously allowed, but should no longer be
-        allowed due to a policy change.
-      </li>
-    </ul>
-
-    <p>
-      This table also contains a priority 0 flow with action
-      <code>next;</code>, so that ACLs allow packets by default.  If the
-      logical datapath has a statetful ACL, the following flows will
-      also be added:
-    </p>
-
-    <ul>
-      <li>
-        A priority-1 flow that sets the hint to commit IP traffic to the
-        connection tracker (with action <code>reg0[1] = 1; next;</code>).  This
-        is needed for the default allow policy because, while the initiator's
-        direction may not have any stateful rules, the server's may and then
-        its return traffic would not be known and marked as invalid.
-      </li>
-
-      <li>
-        A priority-65535 flow that allows any traffic in the reply
-        direction for a connection that has been committed to the
-        connection tracker (i.e., established flows), as long as
-        the committed flow does not have <code>ct_label.blocked</code> set.
-        We only handle traffic in the reply direction here because
-        we want all packets going in the request direction to still
-        go through the flows that implement the currently defined
-        policy based on ACLs.  If a connection is no longer allowed by
-        policy, <code>ct_label.blocked</code> will get set and packets in the
-        reply direction will no longer be allowed, either.
-      </li>
-
-      <li>
-        A priority-65535 flow that allows any traffic that is considered
-        related to a committed flow in the connection tracker (e.g., an
-        ICMP Port Unreachable from a non-listening UDP port), as long
-        as the committed flow does not have <code>ct_label.blocked</code> set.
-      </li>
-
-      <li>
-        A priority-65535 flow that drops all traffic marked by the
-        connection tracker as invalid.
-      </li>
-
-      <li>
-        A priority-65535 flow that drops all traffic in the reply direction
-        with <code>ct_label.blocked</code> set meaning that the connection
-        should no longer be allowed due to a policy change.  Packets
-        in the request direction are skipped here to let a newly created
-        ACL re-allow this connection.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 7: <code>from-lport</code> QoS Marking</h3>
-
-    <p>
-      Logical flows in this table closely reproduce those in the
-      <code>QoS</code> table with the <code>action</code> column set in
-      the <code>OVN_Northbound</code> database for the
-      <code>from-lport</code> direction.
-    </p>
-
-    <ul>
-      <li>
-        For every qos_rules entry in a logical switch with DSCP marking
-        enabled, a flow will be added at the priority mentioned in the
-        QoS table.
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and advances to
-        the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 8: <code>from-lport</code> QoS Meter</h3>
-
-    <p>
-      Logical flows in this table closely reproduce those in the
-      <code>QoS</code> table with the  <code>bandwidth</code> column set
-      in the <code>OVN_Northbound</code> database for the
-      <code>from-lport</code> direction.
-    </p>
-
-    <ul>
-      <li>
-        For every qos_rules entry in a logical switch with metering
-        enabled, a flow will be added at the priorirty mentioned in the
-        QoS table.
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and advances to
-        the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 9: LB</h3>
-
-    <p>
-      It contains a priority-0 flow that simply moves traffic to the next
-      table.  For established connections a priority 100 flow matches on
-      <code>ct.est &amp;&amp; !ct.rel &amp;&amp; !ct.new &amp;&amp;
-      !ct.inv</code> and sets an action <code>reg0[2] = 1; next;</code> to act
-      as a hint for table <code>Stateful</code> to send packets through
-      connection tracker to NAT the packets.  (The packet will automatically
-      get DNATed to the same IP address as the first packet in that
-      connection.)
-    </p>
-
-    <h3>Ingress Table 10: Stateful</h3>
-
-    <ul>
-      <li>
-        For all the configured load balancing rules for a switch in
-        <code>OVN_Northbound</code> database that includes a L4 port
-        <var>PORT</var> of protocol <var>P</var> and IP address
-        <var>VIP</var>, a priority-120 flow is added.  For IPv4 <var>VIPs
-        </var>, the flow matches <code>ct.new &amp;&amp; ip &amp;&amp;
-        ip4.dst == <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp;
-        <var>P</var>.dst == <var>PORT</var></code>.  For IPv6 <var>VIPs</var>,
-        the flow matches <code>ct.new &amp;&amp; ip &amp;&amp; ip6.dst == <var>
-        VIP </var>&amp;&amp; <var>P</var> &amp;&amp; <var>P</var>.dst == <var>
-        PORT</var></code>. The flow's action is <code>ct_lb(<var>args</var>)
-        </code>, where <var>args</var> contains comma separated IP addresses
-        (and optional port numbers) to load balance to.  The address family of
-        the IP addresses of <var>args</var> is the same as the address family
-        of <var>VIP</var>
-      </li>
-      <li>
-        For all the configured load balancing rules for a switch in
-        <code>OVN_Northbound</code> database that includes just an IP address
-        <var>VIP</var> to match on, OVN adds a priority-110 flow.  For IPv4
-        <var>VIPs</var>, the flow matches <code>ct.new &amp;&amp; ip &amp;&amp;
-        ip4.dst == <var>VIP</var></code>. For IPv6 <var>VIPs</var>,
-        the flow matches <code>ct.new &amp;&amp; ip &amp;&amp; ip6.dst == <var>
-        VIP</var></code>. The action on this flow is <code>
-        ct_lb(<var>args</var>)</code>, where <var>args</var> contains comma
-        separated IP addresses of the same address family as <var>VIP</var>.
-      </li>
-      <li>
-        A priority-100 flow commits packets to connection tracker using
-        <code>ct_commit; next;</code> action based on a hint provided by
-        the previous tables (with a match for <code>reg0[1] == 1</code>).
-      </li>
-      <li>
-        A priority-100 flow sends the packets to connection tracker using
-        <code>ct_lb;</code> as the action based on a hint provided by the
-        previous tables (with a match for <code>reg0[2] == 1</code>).
-      </li>
-      <li>
-        A priority-0 flow that simply moves traffic to the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 11: ARP/ND responder</h3>
-
-    <p>
-      This table implements ARP/ND responder in a logical switch for known
-      IPs.  The advantage of the ARP responder flow is to limit ARP
-      broadcasts by locally responding to ARP requests without the need to
-      send to other hypervisors.  One common case is when the inport is a
-      logical port associated with a VIF and the broadcast is responded to
-      on the local hypervisor rather than broadcast across the whole
-      network and responded to by the destination VM.  This behavior is
-      proxy ARP.
-    </p>
-
-    <p>
-      ARP requests arrive from VMs from a logical switch inport of type
-      default.  For this case, the logical switch proxy ARP rules can be
-      for other VMs or logical router ports.  Logical switch proxy ARP
-      rules may be programmed both for mac binding of IP addresses on
-      other logical switch VIF ports (which are of the default logical
-      switch port type, representing connectivity to VMs or containers),
-      and for mac binding of IP addresses on logical switch router type
-      ports, representing their logical router port peers.  In order to
-      support proxy ARP for logical router ports, an IP address must be
-      configured on the logical switch router type port, with the same
-      value as the peer logical router port.  The configured MAC addresses
-      must match as well.  When a VM sends an ARP request for a distributed
-      logical router port and if the peer router type port of the attached
-      logical switch does not have an IP address configured, the ARP request
-      will be broadcast on the logical switch.  One of the copies of the ARP
-      request will go through the logical switch router type port to the
-      logical router datapath, where the logical router ARP responder will
-      generate a reply.  The MAC binding of a distributed logical router,
-      once learned by an associated VM, is used for all that VM's
-      communication needing routing.  Hence, the action of a VM re-arping for
-      the mac binding of the logical router port should be rare.
-    </p>
-
-    <p>
-      Logical switch ARP responder proxy ARP rules can also be hit when
-      receiving ARP requests externally on a L2 gateway port.  In this case,
-      the hypervisor acting as an L2 gateway, responds to the ARP request on
-      behalf of a destination VM.
-    </p>
-
-    <p>
-      Note that ARP requests received from <code>localnet</code> or
-      <code>vtep</code> logical inports can either go directly to VMs, in
-      which case the VM responds or can hit an ARP responder for a logical
-      router port if the packet is used to resolve a logical router port
-      next hop address.  In either case, logical switch ARP responder rules
-      will not be hit.  It contains these logical flows:
-     </p>
-
-    <ul>
-      <li>
-        Priority-100 flows to skip the ARP responder if inport is of type
-        <code>localnet</code> or <code>vtep</code> and advances directly
-        to the next table.  ARP requests sent to <code>localnet</code> or
-        <code>vtep</code> ports can be received by multiple hypervisors.
-        Now, because the same mac binding rules are downloaded to all
-        hypervisors, each of the multiple hypervisors will respond.  This
-        will confuse L2 learning on the source of the ARP requests.  ARP
-        requests received on an inport of type <code>router</code> are not
-        expected to hit any logical switch ARP responder flows.  However,
-        no skip flows are installed for these packets, as there would be
-        some additional flow cost for this and the value appears limited.
-      </li>
-
-      <li>
-        <p>
-          Priority-50 flows that match ARP requests to each known IP address
-          <var>A</var> of every logical switch port, and respond with ARP
-          replies directly with corresponding Ethernet address <var>E</var>:
-        </p>
-
-        <pre>
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-arp.op = 2; /* ARP reply. */
-arp.tha = arp.sha;
-arp.sha = <var>E</var>;
-arp.tpa = arp.spa;
-arp.spa = <var>A</var>;
-outport = inport;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          These flows are omitted for logical ports (other than router ports or
-          <code>localport</code> ports) that are down.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Priority-50 flows that match IPv6 ND neighbor solicitations to
-          each known IP address <var>A</var> (and <var>A</var>'s
-          solicited node address) of every logical switch port except of type
-          router, and respond with neighbor advertisements directly with
-          corresponding Ethernet address <var>E</var>:
-        </p>
-
-        <pre>
-nd_na {
-    eth.src = <var>E</var>;
-    ip6.src = <var>A</var>;
-    nd.target = <var>A</var>;
-    nd.tll = <var>E</var>;
-    outport = inport;
-    flags.loopback = 1;
-    output;
-};
-        </pre>
-
-        <p>
-          Priority-50 flows that match IPv6 ND neighbor solicitations to
-          each known IP address <var>A</var> (and <var>A</var>'s
-          solicited node address) of logical switch port of type router, and
-          respond with neighbor advertisements directly with
-          corresponding Ethernet address <var>E</var>:
-        </p>
-
-        <pre>
-nd_na_router {
-    eth.src = <var>E</var>;
-    ip6.src = <var>A</var>;
-    nd.target = <var>A</var>;
-    nd.tll = <var>E</var>;
-    outport = inport;
-    flags.loopback = 1;
-    output;
-};
-        </pre>
-
-        <p>
-          These flows are omitted for logical ports (other than router ports or
-          <code>localport</code> ports) that are down.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Priority-100 flows with match criteria like the ARP and ND flows
-          above, except that they only match packets from the
-          <code>inport</code> that owns the IP addresses in question, with
-          action <code>next;</code>.  These flows prevent OVN from replying to,
-          for example, an ARP request emitted by a VM for its own IP address.
-          A VM only makes this kind of request to attempt to detect a duplicate
-          IP address assignment, so sending a reply will prevent the VM from
-          accepting the IP address that it owns.
-        </p>
-
-        <p>
-          In place of <code>next;</code>, it would be reasonable to use
-          <code>drop;</code> for the flows' actions.  If everything is working
-          as it is configured, then this would produce equivalent results,
-          since no host should reply to the request.  But ARPing for one's own
-          IP address is intended to detect situations where the network is not
-          working as configured, so dropping the request would frustrate that
-          intent.
-        </p>
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and advances to
-        the next table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 12: DHCP option processing</h3>
-
-    <p>
-      This table adds the DHCPv4 options to a DHCPv4 packet from the
-      logical ports configured with IPv4 address(es) and DHCPv4 options,
-      and similarly for DHCPv6 options. This table also adds flows for the
-      logical ports of type <code>external</code>.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority-100 logical flow is added for these logical ports
-          which matches the IPv4 packet with <code>udp.src</code> = 68 and
-          <code>udp.dst</code> = 67 and applies the action
-          <code>put_dhcp_opts</code> and advances the packet to the next table.
-        </p>
-
-        <pre>
-reg0[3] = put_dhcp_opts(offer_ip = <var>ip</var>, <var>options</var>...);
-next;
-        </pre>
-
-        <p>
-          For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a
-          DHCP reply, adds the DHCP offer IP <var>ip</var> and options to the
-          packet, and stores 1 into reg0[3].  For other kinds of packets, it
-          just stores 0 into reg0[3].  Either way, it continues to the next
-          table.
-        </p>
-
-      </li>
-
-      <li>
-        <p>
-          A priority-100 logical flow is added for these logical ports
-          which matches the IPv6 packet with <code>udp.src</code> = 546 and
-          <code>udp.dst</code> = 547 and applies the action
-          <code>put_dhcpv6_opts</code> and advances the packet to the next
-          table.
-        </p>
-
-        <pre>
-reg0[3] = put_dhcpv6_opts(ia_addr = <var>ip</var>, <var>options</var>...);
-next;
-        </pre>
-
-        <p>
-          For DHCPv6 Solicit/Request/Confirm packets, this transforms the
-          packet into a DHCPv6 Advertise/Reply, adds the DHCPv6 offer IP
-          <var>ip</var> and options to the packet, and stores 1 into reg0[3].
-          For other kinds of packets, it just stores 0 into reg0[3]. Either
-          way, it continues to the next table.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 flow that matches all packets to advances to table 11.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 13: DHCP responses</h3>
-
-    <p>
-      This table implements DHCP responder for the DHCP replies generated by
-      the previous table.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority 100 logical flow is added for the logical ports configured
-          with DHCPv4 options which matches IPv4 packets with <code>udp.src == 68
-          &amp;&amp; udp.dst == 67 &amp;&amp; reg0[3] == 1</code> and
-          responds back to the <code>inport</code> after applying these
-          actions.  If <code>reg0[3]</code> is set to 1, it means that the
-          action <code>put_dhcp_opts</code> was successful.
-        </p>
-
-        <pre>
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-ip4.dst = <var>A</var>;
-ip4.src = <var>S</var>;
-udp.src = 67;
-udp.dst = 68;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          where <var>E</var> is the server MAC address and <var>S</var> is the
-          server IPv4 address defined in the DHCPv4 options and <var>A</var> is
-          the IPv4 address defined in the logical port's addresses column.
-        </p>
-
-        <p>
-          (This terminates ingress packet processing; the packet does not go
-           to the next ingress table.)
-        </p>
-      </li>
-
-      <li>
-        <p>
-          A priority 100 logical flow is added for the logical ports configured
-          with DHCPv6 options which matches IPv6 packets with <code>udp.src == 546
-          &amp;&amp; udp.dst == 547 &amp;&amp; reg0[3] == 1</code> and
-          responds back to the <code>inport</code> after applying these
-          actions.  If <code>reg0[3]</code> is set to 1, it means that the
-          action <code>put_dhcpv6_opts</code> was successful.
-        </p>
-
-        <pre>
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-ip6.dst = <var>A</var>;
-ip6.src = <var>S</var>;
-udp.src = 547;
-udp.dst = 546;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          where <var>E</var> is the server MAC address and <var>S</var> is the
-          server IPv6 LLA address  generated from the <code>server_id</code>
-          defined in the DHCPv6 options and <var>A</var> is
-          the IPv6 address defined in the logical port's addresses column.
-        </p>
-
-        <p>
-          (This terminates packet processing; the packet does not go on the
-          next ingress table.)
-        </p>
-      </li>
-
-      <li>
-        A priority-0 flow that matches all packets to advances to table 12.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 14 DNS Lookup</h3>
-
-    <p>
-      This table looks up and resolves the DNS names to the corresponding
-      configured IP address(es).
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority-100 logical flow for each logical switch datapath
-          if it is configured with DNS records, which matches the IPv4 and IPv6
-          packets with <code>udp.dst</code> = 53 and applies the action
-          <code>dns_lookup</code> and advances the packet to the next table.
-        </p>
-
-        <pre>
-reg0[4] = dns_lookup(); next;
-        </pre>
-
-        <p>
-          For valid DNS packets, this transforms the packet into a DNS
-          reply if the DNS name can be resolved, and stores 1 into reg0[4].
-          For failed DNS resolution or other kinds of packets, it just stores
-          0 into reg0[4]. Either way, it continues to the next table.
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress Table 15 DNS Responses</h3>
-
-    <p>
-      This table implements DNS responder for the DNS replies generated by
-      the previous table.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority-100 logical flow for each logical switch datapath
-          if it is configured with DNS records, which matches the IPv4 and IPv6
-          packets with <code>udp.dst = 53 &amp;&amp; reg0[4] == 1</code>
-          and responds back to the <code>inport</code> after applying these
-          actions.  If <code>reg0[4]</code> is set to 1, it means that the
-          action <code>dns_lookup</code> was successful.
-        </p>
-
-        <pre>
-eth.dst &lt;-&gt; eth.src;
-ip4.src &lt;-&gt; ip4.dst;
-udp.dst = udp.src;
-udp.src = 53;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          (This terminates ingress packet processing; the packet does not go
-           to the next ingress table.)
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress table 16 External ports</h3>
-
-    <p>
-      Traffic from the <code>external</code> logical ports enter the ingress
-      datapath pipeline via the <code>localnet</code> port. This table adds the
-      below logical flows to handle the traffic from these ports.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority-100 flow is added for each <code>external</code> logical
-          port which doesn't reside on a chassis to drop the ARP/IPv6 NS
-          request to the router IP(s) (of the logical switch) which matches
-          on the <code>inport</code> of the <code>external</code> logical port
-          and the valid <code>eth.src</code> address(es) of the
-          <code>external</code> logical port.
-        </p>
-
-        <p>
-          This flow guarantees that the ARP/NS request to the router IP
-          address from the external ports is responded by only the chassis
-          which has claimed these external ports. All the other chassis,
-          drops these packets.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 flow that matches all packets to advances to table 17.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 17 Destination Lookup</h3>
-
-    <p>
-      This table implements switching behavior.  It contains these logical
-      flows:
-    </p>
-
-    <ul>
-      <li>
-        A priority-100 flow that outputs all packets with an Ethernet broadcast
-        or multicast <code>eth.dst</code> to the <code>MC_FLOOD</code>
-        multicast group, which <code>ovn-northd</code> populates with all
-        enabled logical ports.
-      </li>
-
-      <li>
-        <p>
-          One priority-50 flow that matches each known Ethernet address against
-          <code>eth.dst</code> and outputs the packet to the single associated
-          output port.
-        </p>
-
-        <p>
-          For the Ethernet address on a logical switch port of type
-          <code>router</code>, when that logical switch port's
-          <ref column="addresses" table="Logical_Switch_Port"
-          db="OVN_Northbound"/> column is set to <code>router</code> and
-          the connected logical router port specifies a
-          <code>redirect-chassis</code>:
-        </p>
-
-        <ul>
-          <li>
-            The flow for the connected logical router port's Ethernet
-            address is only programmed on the <code>redirect-chassis</code>.
-          </li>
-
-          <li>
-            If the logical router has rules specified in
-            <ref column="nat" table="Logical_Router" db="OVN_Northbound"/> with
-            <ref column="external_mac" table="NAT" db="OVN_Northbound"/>, then
-            those addresses are also used to populate the switch's destination
-            lookup on the chassis where
-            <ref column="logical_port" table="NAT" db="OVN_Northbound"/> is
-            resident.
-          </li>
-        </ul>
-
-        <p>
-          For the Ethernet address on a logical switch port of type
-          <code>router</code>, when that logical switch port's
-          <ref column="addresses" table="Logical_Switch_Port"
-          db="OVN_Northbound"/> column is set to <code>router</code> and
-          the connected logical router port specifies a
-          <code>reside-on-redirect-chassis</code> and the logical router
-          to which the connected logical router port belongs to has a
-          <code>redirect-chassis</code> distributed gateway logical router
-          port:
-        </p>
-
-        <ul>
-          <li>
-            The flow for the connected logical router port's Ethernet
-            address is only programmed on the <code>redirect-chassis</code>.
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        One priority-0 fallback flow that matches all packets and outputs them
-        to the <code>MC_UNKNOWN</code> multicast group, which
-        <code>ovn-northd</code> populates with all enabled logical ports that
-        accept unknown destination packets.  As a small optimization, if no
-        logical ports accept unknown destination packets,
-        <code>ovn-northd</code> omits this multicast group and logical flow.
-      </li>
-    </ul>
-
-    <h3>Egress Table 0: Pre-LB</h3>
-
-    <p>
-      This table is similar to ingress table <code>Pre-LB</code>.  It
-      contains a priority-0 flow that simply moves traffic to the next table.
-      Moreover it contains a priority-110 flow to move IPv6 Neighbor Discovery
-      traffic to the next table. If any load balancing rules exist for the
-      datapath, a priority-100 flow is added with a match of <code>ip</code>
-      and action of <code>reg0[0] = 1; next;</code> to act as a hint for
-      table <code>Pre-stateful</code> to send IP packets to the connection
-      tracker for packet de-fragmentation.
-    </p>
-
-    <h3>Egress Table 1: <code>to-lport</code> Pre-ACLs</h3>
-
-    <p>
-      This is similar to ingress table <code>Pre-ACLs</code> except for
-     <code>to-lport</code> traffic.
-    </p>
-
-    <h3>Egress Table 2: Pre-stateful</h3>
-
-    <p>
-      This is similar to ingress table <code>Pre-stateful</code>.
-    </p>
-
-    <h3>Egress Table 3: LB</h3>
-    <p>
-      This is similar to ingress table <code>LB</code>.
-    </p>
-
-    <h3>Egress Table 4: <code>to-lport</code> ACLs</h3>
-
-    <p>
-      This is similar to ingress table <code>ACLs</code> except for
-      <code>to-lport</code> ACLs.
-    </p>
-
-    <p>
-      In addition, the following flows are added.
-    </p>
-    <ul>
-      <li>
-        A priority 34000 logical flow is added for each logical port which
-        has DHCPv4 options defined to allow the DHCPv4 reply packet and which has
-        DHCPv6 options defined to allow the DHCPv6 reply packet from the
-        <code>Ingress Table 13: DHCP responses</code>.
-      </li>
-
-      <li>
-        A priority 34000 logical flow is added for each logical switch datapath
-        configured with DNS records with the match <code>udp.dst = 53</code>
-        to allow the DNS reply packet from the
-        <code>Ingress Table 15:DNS responses</code>.
-      </li>
-    </ul>
-
-    <h3>Egress Table 5: <code>to-lport</code> QoS Marking</h3>
-
-    <p>
-      This is similar to ingress table <code>QoS marking</code> except
-      they apply to <code>to-lport</code> QoS rules.
-    </p>
-
-    <h3>Egress Table 6: <code>to-lport</code> QoS Meter</h3>
-
-    <p>
-      This is similar to ingress table <code>QoS meter</code> except
-      they apply to <code>to-lport</code> QoS rules.
-    </p>
-
-    <h3>Egress Table 7: Stateful</h3>
-
-    <p>
-      This is similar to ingress table <code>Stateful</code> except that
-      there are no rules added for load balancing new connections.
-    </p>
-
-    <h3>Egress Table 8: Egress Port Security - IP</h3>
-
-    <p>
-      This is similar to the port security logic in table
-      <code>Ingress Port Security - IP</code> except that <code>outport</code>,
-      <code>eth.dst</code>, <code>ip4.dst</code> and <code>ip6.dst</code>
-      are checked instead of <code>inport</code>, <code>eth.src</code>,
-      <code>ip4.src</code> and <code>ip6.src</code>
-    </p>
-
-    <h3>Egress Table 9: Egress Port Security - L2</h3>
-
-    <p>
-      This is similar to the ingress port security logic in ingress table
-      <code>Admission Control and Ingress Port Security - L2</code>,
-      but with important differences.  Most obviously, <code>outport</code> and
-      <code>eth.dst</code> are checked instead of <code>inport</code> and
-      <code>eth.src</code>.  Second, packets directed to broadcast or multicast
-      <code>eth.dst</code> are always accepted instead of being subject to the
-      port security rules; this is implemented through a priority-100 flow that
-      matches on <code>eth.mcast</code> with action <code>output;</code>.
-      Finally, to ensure that even broadcast and multicast packets are not
-      delivered to disabled logical ports, a priority-150 flow for each
-      disabled logical <code>outport</code> overrides the priority-100 flow
-      with a <code>drop;</code> action.
-    </p>
-
-    <h2>Logical Router Datapaths</h2>
-
-    <p>
-      Logical router datapaths will only exist for <ref table="Logical_Router"
-      db="OVN_Northbound"/> rows in the <ref db="OVN_Northbound"/> database
-      that do not have <ref column="enabled" table="Logical_Router"
-      db="OVN_Northbound"/> set to <code>false</code>
-    </p>
-
-    <h3>Ingress Table 0: L2 Admission Control</h3>
-
-    <p>
-      This table drops packets that the router shouldn't see at all based on
-      their Ethernet headers.  It contains the following flows:
-    </p>
-
-    <ul>
-      <li>
-        Priority-100 flows to drop packets with VLAN tags or multicast Ethernet
-        source addresses.
-      </li>
-
-      <li>
-        <p>
-          For each enabled router port <var>P</var> with Ethernet address
-          <var>E</var>, a priority-50 flow that matches <code>inport ==
-          <var>P</var> &amp;&amp; (eth.mcast || eth.dst ==
-          <var>E</var></code>), with action <code>next;</code>.
-        </p>
-
-        <p>
-          For the gateway port on a distributed logical router (where
-          one of the logical router ports specifies a
-          <code>redirect-chassis</code>), the above flow matching
-          <code>eth.dst == <var>E</var></code> is only programmed on
-          the gateway port instance on the
-          <code>redirect-chassis</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          For each <code>dnat_and_snat</code> NAT rule on a distributed
-          router that specifies an external Ethernet address <var>E</var>,
-          a priority-50 flow that matches <code>inport == <var>GW</var>
-          &amp;&amp; eth.dst == <var>E</var></code>, where <var>GW</var>
-          is the logical router gateway port, with action
-          <code>next;</code>.
-        </p>
-
-        <p>
-          This flow is only programmed on the gateway port instance on
-          the chassis where the <code>logical_port</code> specified in
-          the NAT rule resides.
-        </p>
-      </li>
-    </ul>
-
-    <p>
-      Other packets are implicitly dropped.
-    </p>
-
-    <h3>Ingress Table 1: IP Input</h3>
-
-    <p>
-      This table is the core of the logical router datapath functionality.  It
-      contains the following flows to implement very basic IP host
-      functionality.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          L3 admission control: A priority-100 flow drops packets that match
-          any of the following:
-        </p>
-
-        <ul>
-          <li>
-            <code>ip4.src[28..31] == 0xe</code> (multicast source)
-          </li>
-          <li>
-            <code>ip4.src == 255.255.255.255</code> (broadcast source)
-          </li>
-          <li>
-            <code>ip4.src == 127.0.0.0/8 || ip4.dst == 127.0.0.0/8</code>
-            (localhost source or destination)
-          </li>
-          <li>
-            <code>ip4.src == 0.0.0.0/8 || ip4.dst == 0.0.0.0/8</code> (zero
-            network source or destination)
-          </li>
-          <li>
-            <code>ip4.src</code> or <code>ip6.src</code> is any IP
-            address owned by the router, unless the packet was recirculated
-            due to egress loopback as indicated by
-            <code>REGBIT_EGRESS_LOOPBACK</code>.
-          </li>
-          <li>
-            <code>ip4.src</code> is the broadcast address of any IP network
-            known to the router.
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        <p>
-          ICMP echo reply.  These flows reply to ICMP echo requests received
-          for the router's IP address.  Let <var>A</var> be an IP address
-          owned by a router port.  Then, for each <var>A</var> that is
-          an IPv4 address, a priority-90 flow matches on
-          <code>ip4.dst == <var>A</var></code> and
-          <code>icmp4.type == 8 &amp;&amp; icmp4.code == 0</code>
-          (ICMP echo request).  For each <var>A</var> that is an IPv6
-          address, a priority-90 flow matches on
-          <code>ip6.dst == <var>A</var></code> and
-          <code>icmp6.type == 128 &amp;&amp; icmp6.code == 0</code>
-          (ICMPv6 echo request).  The port of the router that receives the
-          echo request does not matter. Also, the <code>ip.ttl</code> of
-          the echo request packet is not checked, so it complies with
-          RFC 1812, section 4.2.2.9. Flows for ICMPv4 echo requests use the
-          following actions:
-        </p>
-
-        <pre>
-ip4.dst &lt;-&gt; ip4.src;
-ip.ttl = 255;
-icmp4.type = 0;
-flags.loopback = 1;
-next;
-        </pre>
-
-        <p>
-          Flows for ICMPv6 echo requests use the following actions:
-        </p>
-
-        <pre>
-ip6.dst &lt;-&gt; ip6.src;
-ip.ttl = 255;
-icmp6.type = 129;
-flags.loopback = 1;
-next;
-        </pre>
-      </li>
-
-      <li>
-        <p>
-          Reply to ARP requests.
-        </p>
-
-        <p>
-          These flows reply to ARP requests for the router's own IP address
-          and populates mac binding table of the logical router port.
-          The ARP requests are handled only if the requestor's IP belongs
-          to the same subnets of the logical router port.
-          For each router port <var>P</var> that owns IP address <var>A</var>,
-          which belongs to subnet <var>S</var> with prefix length <var>L</var>,
-          and Ethernet address <var>E</var>, a priority-90 flow matches
-          <code>inport == <var>P</var> &amp;&amp;
-          arp.spa == <var>S</var>/<var>L</var> &amp;&amp; arp.op == 1
-          &amp;&amp; arp.tpa == <var>A</var></code> (ARP request) with the
-          following actions:
-        </p>
-
-        <pre>
-put_arp(inport, arp.spa, arp.sha);
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-arp.op = 2; /* ARP reply. */
-arp.tha = arp.sha;
-arp.sha = <var>E</var>;
-arp.tpa = arp.spa;
-arp.spa = <var>A</var>;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          For the gateway port on a distributed logical router (where
-          one of the logical router ports specifies a
-          <code>redirect-chassis</code>), the above flows are only
-          programmed on the gateway port instance on the
-          <code>redirect-chassis</code>.  This behavior avoids generation
-          of multiple ARP responses from different chassis, and allows
-          upstream MAC learning to point to the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          For the logical router port with the option
-          <code>reside-on-redirect-chassis</code> set (which is centralized),
-          the above flows are only programmed on the gateway port instance on
-          the <code>redirect-chassis</code> (if the logical router has a
-          distributed gateway port). This behavior avoids generation
-          of multiple ARP responses from different chassis, and allows
-          upstream MAC learning to point to the
-          <code>redirect-chassis</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          These flows handles ARP requests not for router's own IP address.
-          They use the SPA and SHA to populate the logical router port's
-          mac binding table, with priority 80.  The typical use case of
-          these flows are GARP requests handling.  For the gateway port
-          on a distributed logical router, these flows are only programmed
-          on the gateway port instance on the <code>redirect-chassis</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          These flows reply to ARP requests for the virtual IP addresses
-          configured in the router for DNAT or load balancing.  For a
-          configured DNAT IP address or a load balancer IPv4 VIP <var>A</var>,
-          for each router port <var>P</var> with Ethernet
-          address <var>E</var>, a priority-90 flow matches
-          <code>inport == <var>P</var> &amp;&amp; arp.op == 1 &amp;&amp;
-          arp.tpa == <var>A</var></code> (ARP request)
-          with the following actions:
-        </p>
-
-        <pre>
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-arp.op = 2; /* ARP reply. */
-arp.tha = arp.sha;
-arp.sha = <var>E</var>;
-arp.tpa = arp.spa;
-arp.spa = <var>A</var>;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          For the gateway port on a distributed logical router with NAT
-          (where one of the logical router ports specifies a
-          <code>redirect-chassis</code>):
-        </p>
-
-        <ul>
-          <li>
-            If the corresponding NAT rule cannot be handled in a
-            distributed manner, then this flow is only programmed on
-            the gateway port instance on the
-            <code>redirect-chassis</code>.  This behavior avoids
-            generation of multiple ARP responses from different chassis,
-            and allows upstream MAC learning to point to the
-            <code>redirect-chassis</code>.
-          </li>
-
-          <li>
-            <p>
-              If the corresponding NAT rule can be handled in a distributed
-              manner, then this flow is only programmed on the gateway port
-              instance where the <code>logical_port</code> specified in the
-              NAT rule resides.
-            </p>
-
-            <p>
-              Some of the actions are different for this case, using the
-              <code>external_mac</code> specified in the NAT rule rather
-              than the gateway port's Ethernet address <var>E</var>:
-            </p>
-
-            <pre>
-eth.src = <var>external_mac</var>;
-arp.sha = <var>external_mac</var>;
-            </pre>
-
-            <p>
-              This behavior avoids generation of multiple ARP responses
-              from different chassis, and allows upstream MAC learning to
-              point to the correct chassis.
-            </p>
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        ARP reply handling.  This flow uses ARP replies to populate the
-        logical router's ARP table.  A priority-90 flow with match <code>arp.op
-        == 2</code> has actions <code>put_arp(inport, arp.spa,
-        arp.sha);</code>.
-      </li>
-
-      <li>
-        <p>
-          Reply to IPv6 Neighbor Solicitations.  These flows reply to
-          Neighbor Solicitation requests for the router's own IPv6
-          address and load balancing IPv6 VIPs and populate the logical
-          router's mac binding table.
-        </p>
-
-        <p>
-          For each router port <var>P</var> that
-          owns IPv6 address <var>A</var>, solicited node address <var>S</var>,
-          and Ethernet address <var>E</var>, a priority-90 flow matches
-          <code>inport == <var>P</var> &amp;&amp;
-          nd_ns &amp;&amp; ip6.dst == {<var>A</var>, <var>E</var>} &amp;&amp;
-          nd.target == <var>A</var></code> with the following actions:
-        </p>
-
-        <pre>
-put_nd(inport, ip6.src, nd.sll);
-nd_na_router {
-    eth.src = <var>E</var>;
-    ip6.src = <var>A</var>;
-    nd.target = <var>A</var>;
-    nd.tll = <var>E</var>;
-    outport = inport;
-    flags.loopback = 1;
-    output;
-};
-        </pre>
-
-        <p>
-          For each router port <var>P</var> that has load balancing VIP
-          <var>A</var>, solicited node address <var>S</var>, and Ethernet
-          address <var>E</var>, a priority-90 flow matches
-          <code>inport == <var>P</var> &amp;&amp;
-          nd_ns &amp;&amp; ip6.dst == {<var>A</var>, <var>E</var>} &amp;&amp;
-          nd.target == <var>A</var></code> with the following actions:
-        </p>
-
-        <pre>
-put_nd(inport, ip6.src, nd.sll);
-nd_na {
-    eth.src = <var>E</var>;
-    ip6.src = <var>A</var>;
-    nd.target = <var>A</var>;
-    nd.tll = <var>E</var>;
-    outport = inport;
-    flags.loopback = 1;
-    output;
-};
-        </pre>
-
-        <p>
-          For the gateway port on a distributed logical router (where
-          one of the logical router ports specifies a
-          <code>redirect-chassis</code>), the above flows replying to
-          IPv6 Neighbor Solicitations are only programmed on the
-          gateway port instance on the <code>redirect-chassis</code>.
-          This behavior avoids generation of multiple replies from
-          different chassis, and allows upstream MAC learning to point
-          to the <code>redirect-chassis</code>.
-        </p>
-      </li>
-
-      <li>
-        IPv6 neighbor advertisement handling.  This flow uses neighbor
-        advertisements to populate the logical router's mac binding
-        table.  A priority-90 flow with match <code>nd_na</code>
-        has actions <code>put_nd(inport, nd.target, nd.tll);</code>.
-      </li>
-
-      <li>
-        IPv6 neighbor solicitation for non-hosted addresses handling.
-        This flow uses neighbor solicitations to populate the logical
-        router's mac binding table (ones that were directed at the
-        logical router would have matched the priority-90 neighbor
-        solicitation flow already).  A priority-80 flow with match
-        <code>nd_ns</code> has actions
-        <code>put_nd(inport, ip6.src, nd.sll);</code>.
-      </li>
-
-      <li>
-        <p>
-          UDP port unreachable.  Priority-80 flows generate ICMP port
-          unreachable messages in reply to UDP datagrams directed to the
-          router's IP address, except in the special case of gateways,
-          which accept traffic directed to a router IP for load balancing
-          and NAT purposes.
-        </p>
-
-        <p>
-          These flows should not match IP fragments with nonzero offset.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          TCP reset.  Priority-80 flows generate TCP reset messages in reply
-          to TCP datagrams directed to the router's IP address, except in
-          the special case of gateways, which accept traffic directed to a
-          router IP for load balancing and NAT purposes.
-        </p>
-
-        <p>
-          These flows should not match IP fragments with nonzero offset.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Protocol or address unreachable. Priority-70 flows generate ICMP
-          protocol or address unreachable messages for IPv4 and IPv6
-          respectively in reply to packets directed to the router's IP
-          address on IP protocols other than UDP, TCP, and ICMP, except in the
-          special case of gateways, which accept traffic directed to a router
-          IP for load balancing purposes.
-        </p>
-
-        <p>
-          These flows should not match IP fragments with nonzero offset.
-        </p>
-      </li>
-
-      <li>
-        Drop other IP traffic to this router.  These flows drop any other
-        traffic destined to an IP address of this router that is not already
-        handled by one of the flows above, which amounts to ICMP (other than
-        echo requests) and fragments with nonzero offsets.  For each IP address
-        <var>A</var> owned by the router, a priority-60 flow matches
-        <code>ip4.dst == <var>A</var></code> and drops the traffic.  An
-        exception is made and the above flow is not added if the router
-        port's own IP address is used to SNAT packets passing through that
-        router.
-      </li>
-    </ul>
-
-    <p>
-      The flows above handle all of the traffic that might be directed to the
-      router itself.  The following flows (with lower priorities) handle the
-      remaining traffic, potentially for forwarding:
-    </p>
-
-    <ul>
-      <li>
-        Drop Ethernet local broadcast.  A priority-50 flow with match
-        <code>eth.bcast</code> drops traffic destined to the local Ethernet
-        broadcast address.  By definition this traffic should not be forwarded.
-      </li>
-
-      <li>
-        <p>
-          ICMP time exceeded.  For each router port <var>P</var>, whose IP
-          address is <var>A</var>, a priority-40 flow with match <code>inport
-          == <var>P</var> &amp;&amp; ip.ttl == {0, 1} &amp;&amp;
-          !ip.later_frag</code> matches packets whose TTL has expired, with the
-          following actions to send an ICMP time exceeded reply for IPv4 and
-          IPv6 respectively:
-        </p>
-
-        <pre>
-icmp4 {
-    icmp4.type = 11; /* Time exceeded. */
-    icmp4.code = 0;  /* TTL exceeded in transit. */
-    ip4.dst = ip4.src;
-    ip4.src = <var>A</var>;
-    ip.ttl = 255;
-    next;
-};
-
-icmp6 {
-    icmp6.type = 3; /* Time exceeded. */
-    icmp6.code = 0;  /* TTL exceeded in transit. */
-    ip6.dst = ip6.src;
-    ip6.src = <var>A</var>;
-    ip.ttl = 255;
-    next;
-};
-        </pre>
-      </li>
-
-      <li>
-        TTL discard.  A priority-30 flow with match <code>ip.ttl == {0,
-        1}</code> and actions <code>drop;</code> drops other packets whose TTL
-        has expired, that should not receive a ICMP error reply (i.e. fragments
-        with nonzero offset).
-      </li>
-
-      <li>
-        Next table.  A priority-0 flows match all packets that aren't already
-        handled and uses actions <code>next;</code> to feed them to the next
-        table.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 2: DEFRAG</h3>
-
-    <p>
-      This is to send packets to connection tracker for tracking and
-      defragmentation.  It contains a priority-0 flow that simply moves traffic
-      to the next table.  If load balancing rules with virtual IP addresses
-      (and ports) are configured in <code>OVN_Northbound</code> database for a
-      Gateway router, a priority-100 flow is added for each configured virtual
-      IP address <var>VIP</var>. For IPv4 <var>VIPs</var> the flow matches
-      <code>ip &amp;&amp; ip4.dst == <var>VIP</var></code>.  For IPv6
-      <var>VIPs</var>, the flow matches <code>ip &amp;&amp; ip6.dst ==
-      <var>VIP</var></code>.  The flow uses the action <code>ct_next;</code>
-      to send IP packets to the connection tracker for packet de-fragmentation
-      and tracking before sending it to the next table.
-    </p>
-
-    <h3>Ingress Table 3: UNSNAT</h3>
-
-    <p>
-      This is for already established connections' reverse traffic.
-      i.e., SNAT has already been done in egress pipeline and now the
-      packet has entered the ingress pipeline as part of a reply.  It is
-      unSNATted here.
-    </p>
-
-    <p>Ingress Table 3: UNSNAT on Gateway Routers</p>
-
-    <ul>
-      <li>
-        <p>
-          If the Gateway router has been configured to force SNAT any
-          previously DNATted packets to <var>B</var>, a priority-110 flow
-          matches <code>ip &amp;&amp; ip4.dst == <var>B</var></code> with
-          an action <code>ct_snat; </code>.
-        </p>
-
-        <p>
-          If the Gateway router has been configured to force SNAT any
-          previously load-balanced packets to <var>B</var>, a priority-100 flow
-          matches <code>ip &amp;&amp; ip4.dst == <var>B</var></code> with
-          an action <code>ct_snat; </code>.
-        </p>
-
-        <p>
-          For each NAT configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var></code> with an action
-          <code>ct_snat; </code>.
-        </p>
-
-        <p>
-          A priority-0 logical flow with match <code>1</code> has actions
-          <code>next;</code>.
-        </p>
-      </li>
-    </ul>
-
-    <p>Ingress Table 3: UNSNAT on Distributed Routers</p>
-
-    <ul>
-      <li>
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
-          where <var>GW</var> is the logical router gateway port, with an
-          action <code>ct_snat;</code>.
-        </p>
-
-        <p>
-          If the NAT rule cannot be handled in a distributed manner, then
-          the priority-100 flow above is only programmed on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-50 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var></code> with an action
-          <code>REGBIT_NAT_REDIRECT = 1; next;</code>.  This flow is for
-          east/west traffic to a NAT destination IPv4 address.  By
-          setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the
-          ingress table <code>Gateway Redirect</code> this will trigger a
-          redirect to the instance of the gateway port on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          A priority-0 logical flow with match <code>1</code> has actions
-          <code>next;</code>.
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress Table 4: DNAT</h3>
-
-    <p>
-      Packets enter the pipeline with destination IP address that needs to
-      be DNATted from a virtual IP address to a real IP address.  Packets
-      in the reverse direction needs to be unDNATed.
-    </p>
-
-    <p>Ingress Table 4: Load balancing DNAT rules</p>
-
-    <p>
-      Following load balancing DNAT flows are added for Gateway router or
-      Router with gateway port. These flows are programmed only on the
-      <code>redirect-chassis</code>.  These flows do not get programmed for
-      load balancers with IPv6 <var>VIPs</var>.
-    </p>
-
-    <ul>
-      <li>
-        For all the configured load balancing rules for a Gateway router or
-        Router with gateway port in <code>OVN_Northbound</code> database that
-        includes a L4 port <var>PORT</var> of protocol <var>P</var> and IPv4
-        address <var>VIP</var>, a priority-120 flow that matches on
-        <code>ct.new &amp;&amp; ip &amp;&amp; ip4.dst == <var>VIP</var>
-        &amp;&amp; <var>P</var> &amp;&amp; <var>P</var>.dst == <var>PORT
-        </var></code> with an action of <code>ct_lb(<var>args</var>)</code>,
-        where <var>args</var> contains comma separated IPv4 addresses (and
-        optional port numbers) to load balance to.  If the router is configured
-        to force SNAT any load-balanced packets, the above action will be
-        replaced by <code>flags.force_snat_for_lb = 1;
-        ct_lb(<var>args</var>);</code>.
-      </li>
-
-      <li>
-        For all the configured load balancing rules for a router in
-        <code>OVN_Northbound</code> database that includes a L4 port
-        <var>PORT</var> of protocol <var>P</var> and IPv4 address
-        <var>VIP</var>, a priority-120 flow that matches on
-        <code>ct.est &amp;&amp; ip &amp;&amp; ip4.dst == <var>VIP</var>
-        &amp;&amp; <var>P</var> &amp;&amp; <var>P</var>.dst == <var>PORT
-        </var></code> with an action of <code>ct_dnat;</code>. If the router is
-        configured to force SNAT any load-balanced packets, the above action
-        will be replaced by <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
-      </li>
-
-      <li>
-        For all the configured load balancing rules for a router in
-        <code>OVN_Northbound</code> database that includes just an IP address
-        <var>VIP</var> to match on, a priority-110 flow that matches on
-        <code>ct.new &amp;&amp; ip &amp;&amp; ip4.dst ==
-        <var>VIP</var></code> with an action of
-        <code>ct_lb(<var>args</var>)</code>, where <var>args</var> contains
-        comma separated IPv4 addresses.  If the router is configured to force
-        SNAT any load-balanced packets, the above action will be replaced by
-        <code>flags.force_snat_for_lb = 1; ct_lb(<var>args</var>);</code>.
-      </li>
-
-      <li>
-        For all the configured load balancing rules for a router in
-        <code>OVN_Northbound</code> database that includes just an IP address
-        <var>VIP</var> to match on, a priority-110 flow that matches on
-        <code>ct.est &amp;&amp; ip &amp;&amp; ip4.dst ==
-        <var>VIP</var></code> with an action of <code>ct_dnat;</code>.
-        If the router is configured to force SNAT any load-balanced
-        packets, the above action will be replaced by
-        <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
-      </li>
-    </ul>
-
-    <p>Ingress Table 4: DNAT on Gateway Routers</p>
-
-    <ul>
-      <li>
-        For each configuration in the OVN Northbound database, that asks
-        to change the destination IP address of a packet from <var>A</var> to
-        <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
-        ip4.dst == <var>A</var></code> with an action
-        <code>flags.loopback = 1; ct_dnat(<var>B</var>);</code>.  If the
-        Gateway router is configured to force SNAT any DNATed packet,
-        the above action will be replaced by
-        <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
-        ct_dnat(<var>B</var>);</code>.
-      </li>
-
-      <li>
-        For all IP packets of a Gateway router, a priority-50 flow with an
-        action <code>flags.loopback = 1; ct_dnat;</code>.
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <p>Ingress Table 4: DNAT on Distributed Routers</p>
-
-    <p>
-      On distributed routers, the DNAT table only handles packets
-      with destination IP address that needs to be DNATted from a
-      virtual IP address to a real IP address.  The unDNAT processing
-      in the reverse direction is handled in a separate table in the
-      egress pipeline.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the destination IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
-          where <var>GW</var> is the logical router gateway port, with an
-          action <code>ct_dnat(<var>B</var>);</code>.
-        </p>
-
-        <p>
-          If the NAT rule cannot be handled in a distributed manner, then
-          the priority-100 flow above is only programmed on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the destination IP address of a packet from <var>A</var> to
-          <var>B</var>, a priority-50 flow matches <code>ip &amp;&amp;
-          ip4.dst == <var>B</var></code> with an action
-          <code>REGBIT_NAT_REDIRECT = 1; next;</code>.  This flow is for
-          east/west traffic to a NAT destination IPv4 address.  By
-          setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the
-          ingress table <code>Gateway Redirect</code> this will trigger a
-          redirect to the instance of the gateway port on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          A priority-0 logical flow with match <code>1</code> has actions
-          <code>next;</code>.
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress Table 5: IPv6 ND RA option processing</h3>
-
-    <ul>
-      <li>
-        <p>
-          A priority-50 logical flow is added for each logical router port
-          configured with IPv6 ND RA options which matches IPv6 ND Router
-          Solicitation packet and applies the action
-          <code>put_nd_ra_opts</code> and advances the packet to the next
-          table.
-        </p>
-
-        <pre>
-reg0[5] = put_nd_ra_opts(<var>options</var>);next;
-        </pre>
-
-        <p>
-          For a valid IPv6 ND RS packet, this transforms the packet into an
-          IPv6 ND RA reply and sets the RA options to the packet and stores 1
-          into reg0[5]. For other kinds of packets, it just stores 0 into
-          reg0[5]. Either way, it continues to the next table.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 6: IPv6 ND RA responder</h3>
-
-    <p>
-      This table implements IPv6 ND RA responder for the IPv6 ND RA replies
-      generated by the previous table.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          A priority-50 logical flow is added for each logical router port
-          configured with IPv6 ND RA options which matches IPv6 ND RA
-          packets and <code>reg0[5] == 1</code> and responds back to the
-          <code>inport</code> after applying these actions.
-          If <code>reg0[5]</code> is set to 1, it means that the action
-          <code>put_nd_ra_opts</code> was successful.
-        </p>
-
-        <pre>
-eth.dst = eth.src;
-eth.src = <var>E</var>;
-ip6.dst = ip6.src;
-ip6.src = <var>I</var>;
-outport = <var>P</var>;
-flags.loopback = 1;
-output;
-        </pre>
-
-        <p>
-          where <var>E</var> is the MAC address and <var>I</var> is the IPv6
-          link local address of the logical router port.
-        </p>
-
-        <p>
-          (This terminates packet processing in ingress pipeline; the packet
-          does not go to the next ingress table.)
-        </p>
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 7: IP Routing</h3>
-
-    <p>
-      A packet that arrives at this table is an IP packet that should be
-      routed to the address in <code>ip4.dst</code> or
-      <code>ip6.dst</code>.  This table implements IP routing, setting
-      <code>reg0</code> (or <code>xxreg0</code> for IPv6) to the next-hop IP
-      address (leaving <code>ip4.dst</code> or <code>ip6.dst</code>, the
-      packet's final destination, unchanged) and advances to the next
-      table for ARP resolution.  It also sets <code>reg1</code> (or
-      <code>xxreg1</code>) to the IP address owned by the selected router
-      port (ingress table <code>ARP Request</code> will generate an ARP
-      request, if needed, with <code>reg0</code> as the target protocol
-      address and <code>reg1</code> as the source protocol address).
-    </p>
-
-    <p>
-      This table contains the following logical flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-400
-          logical flow for each ip source/destination couple that matches the
-          <code>dnat_and_snat</code> NAT rules configured. These flows will
-          allow to properly forward traffic to the external connections if
-          available and avoid sending it through the tunnel.
-          Assuming the two following NAT rules have been configured:
-        </p>
-
-        <pre>
-external_ip{0,1} = <var>EIP{0,1}</var>;
-external_mac{0,1} = <var>MAC{0,1}</var>;
-logical_ip{0,1} = <var>LIP{0,1}</var>;
-        </pre>
-
-        <p>
-            the following action will be applied:
-        </p>
-
-        <pre>
-eth.dst = <var>MAC0</var>;
-eth.src = <var>MAC1</var>;
-reg0 = ip4.dst;
-reg1 = <var>EIP1</var>;
-outport = <code>redirect-chassis-port</code>;
-<code>REGBIT_DISTRIBUTED_NAT = 1; next;</code>.
-        </pre>
-
-        <p>
-            Morover a priority-400 logical flow is configured for each
-            <code>dnat_and_snat</code> NAT rule configured in order to
-            not send traffic for local FIP through the overlay tunnels
-            but manage it in the local hypervisor
-        </p>
-      </li>
-
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-300
-          logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has
-          actions <code>ip.ttl--; next;</code>.  The <code>outport</code>
-          will be set later in the Gateway Redirect table.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          IPv4 routing table.  For each route to IPv4 network <var>N</var> with
-          netmask <var>M</var>, on router port <var>P</var> with IP address
-          <var>A</var> and Ethernet
-          address <var>E</var>, a logical flow with match <code>ip4.dst ==
-          <var>N</var>/<var>M</var></code>, whose priority is the number of
-          1-bits in <var>M</var>, has the following actions:
-        </p>
-
-        <pre>
-ip.ttl--;
-reg0 = <var>G</var>;
-reg1 = <var>A</var>;
-eth.src = <var>E</var>;
-outport = <var>P</var>;
-flags.loopback = 1;
-next;
-        </pre>
-
-        <p>
-          (Ingress table 1 already verified that <code>ip.ttl--;</code> will
-          not yield a TTL exceeded error.)
-        </p>
-
-        <p>
-          If the route has a gateway, <var>G</var> is the gateway IP address.
-          Instead, if the route is from a configured static route, <var>G</var>
-          is the next hop IP address.  Else it is <code>ip4.dst</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          IPv6 routing table.  For each route to IPv6 network
-          <var>N</var> with netmask <var>M</var>, on router port
-          <var>P</var> with IP address <var>A</var> and Ethernet address
-          <var>E</var>, a logical flow with match in CIDR notation
-          <code>ip6.dst == <var>N</var>/<var>M</var></code>,
-          whose priority is the integer value of <var>M</var>, has the
-          following actions:
-        </p>
-
-        <pre>
-ip.ttl--;
-xxreg0 = <var>G</var>;
-xxreg1 = <var>A</var>;
-eth.src = <var>E</var>;
-outport = <var>P</var>;
-flags.loopback = 1;
-next;
-        </pre>
-
-        <p>
-          (Ingress table 1 already verified that <code>ip.ttl--;</code> will
-          not yield a TTL exceeded error.)
-        </p>
-
-        <p>
-          If the route has a gateway, <var>G</var> is the gateway IP address.
-          Instead, if the route is from a configured static route, <var>G</var>
-          is the next hop IP address.  Else it is <code>ip6.dst</code>.
-        </p>
-
-        <p>
-          If the address <var>A</var> is in the link-local scope, the
-          route will be limited to sending on the ingress port.
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress Table 8: ARP/ND Resolution</h3>
-
-    <p>
-      Any packet that reaches this table is an IP packet whose next-hop
-      IPv4 address is in <code>reg0</code> or IPv6 address is in
-      <code>xxreg0</code>.  (<code>ip4.dst</code> or
-      <code>ip6.dst</code> contains the final destination.)  This table
-      resolves the IP address in <code>reg0</code> (or
-      <code>xxreg0</code>) into an output port in <code>outport</code>
-      and an Ethernet address in <code>eth.dst</code>, using the
-      following flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-400
-          logical flow with match <code>REGBIT_DISTRIBUTED_NAT == 1</code>
-          has action <code>next;</code>
-        </p>
-        <p>
-          For distributed logical routers where one of the logical router
-          ports specifies a <code>redirect-chassis</code>, a priority-200
-          logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has
-          actions <code>eth.dst = <var>E</var>; next;</code>, where
-          <var>E</var> is the ethernet address of the router's distributed
-          gateway port.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Static MAC bindings.  MAC bindings can be known statically based on
-          data in the <code>OVN_Northbound</code> database.  For router ports
-          connected to logical switches, MAC bindings can be known statically
-          from the <code>addresses</code> column in the
-          <code>Logical_Switch_Port</code> table.  For router ports
-          connected to other logical routers, MAC bindings can be known
-          statically from the <code>mac</code> and <code>networks</code>
-          column in the <code>Logical_Router_Port</code> table.
-        </p>
-
-        <p>
-          For each IPv4 address <var>A</var> whose host is known to have
-          Ethernet address <var>E</var> on router port <var>P</var>, a
-          priority-100 flow with match <code>outport === <var>P</var>
-          &amp;&amp; reg0 == <var>A</var></code> has actions
-          <code>eth.dst = <var>E</var>; next;</code>.
-        </p>
-
-        <p>
-          For each IPv6 address <var>A</var> whose host is known to have
-          Ethernet address <var>E</var> on router port <var>P</var>, a
-          priority-100 flow with match <code>outport === <var>P</var>
-          &amp;&amp; xxreg0 == <var>A</var></code> has actions
-          <code>eth.dst = <var>E</var>; next;</code>.
-        </p>
-
-        <p>
-          For each logical router port with an IPv4 address <var>A</var> and
-          a mac address of <var>E</var> that is reachable via a different
-          logical router port <var>P</var>, a priority-100 flow with
-          match <code>outport === <var>P</var> &amp;&amp; reg0 ==
-          <var>A</var></code> has actions <code>eth.dst = <var>E</var>;
-          next;</code>.
-        </p>
-
-        <p>
-          For each logical router port with an IPv6 address <var>A</var> and
-          a mac address of <var>E</var> that is reachable via a different
-          logical router port <var>P</var>, a priority-100 flow with
-          match <code>outport === <var>P</var> &amp;&amp; xxreg0 ==
-          <var>A</var></code> has actions <code>eth.dst = <var>E</var>;
-          next;</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Dynamic MAC bindings.  These flows resolve MAC-to-IP bindings
-          that have become known dynamically through ARP or neighbor
-          discovery.  (The ingress table <code>ARP Request</code> will
-          issue an ARP or neighbor solicitation request for cases where
-          the binding is not yet known.)
-        </p>
-
-        <p>
-          A priority-0 logical flow with match <code>ip4</code> has actions
-          <code>get_arp(outport, reg0); next;</code>.
-        </p>
-
-        <p>
-          A priority-0 logical flow with match <code>ip6</code> has actions
-          <code>get_nd(outport, xxreg0); next;</code>.
-        </p>
-      </li>
-    </ul>
-
-    <h3>Ingress Table 9: Check packet length</h3>
-
-    <p>
-      For distributed logical routers with distributed gateway port configured
-      with <code>options:gateway_mtu</code> to a valid integer value, this
-      table adds a priority-50 logical flow with the match
-      <code>ip4 &amp;&amp; outport == <var>GW_PORT</var></code> where
-      <var>GW_PORT</var> is the distributed gateway router port and applies the
-      action <code>check_pkt_larger</code> and advances the packet to the
-      next table.
-    </p>
-
-    <pre>
-REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next;
-    </pre>
-
-    <p>
-      where <var>L</var> is the packet length to check for. If the packet
-      is larger than <var>L</var>, it stores 1 in the register bit
-      <code>REGBIT_PKT_LARGER</code>. The value of
-      <var>L</var> is taken from <ref column="options:gateway_mtu"
-      table="Logical_Router_Port" db="OVN_Northbound"/> column of
-      <ref table="Logical_Router_Port" db="OVN_Northbound"/> row.
-    </p>
-
-    <p>
-      This table adds one priority-0 fallback flow that matches all packets
-      and advances to the next table.
-    </p>
-
-    <h3>Ingress Table 10: Handle larger packets</h3>
-
-    <p>
-      For distributed logical routers with distributed gateway port configured
-      with <code>options:gateway_mtu</code> to a valid integer value, this
-      table adds the following priority-50 logical flow for each
-      logical router port with the match <code>ip4 &amp;&amp;
-      inport == <var>LRP</var> &amp;&amp; outport == <var>GW_PORT</var>
-      &amp;&amp; REGBIT_PKT_LARGER</code>, where <var>LRP</var> is the logical
-      router port and <var>GW_PORT</var> is the distributed gateway router port
-      and applies the following action
-    </p>
-
-    <pre>
-icmp4 {
-    icmp4.type = 3; /* Destination Unreachable. */
-    icmp4.code = 4;  /* Frag Needed and DF was Set. */
-    icmp4.frag_mtu = <var>M</var>;
-    eth.dst = <var>E</var>;
-    ip4.dst = ip4.src;
-    ip4.src = <var>I</var>;
-    ip.ttl = 255;
-    REGBIT_EGRESS_LOOPBACK = 1;
-    next(pipeline=ingress, table=0);
-};
-    </pre>
-
-    <ul>
-      <li>
-        Where <var>M</var> is the (fragment MTU - 58) whose value is taken from
-        <ref column="options:gateway_mtu" table="Logical_Router_Port"
-        db="OVN_Northbound"/> column of
-        <ref table="Logical_Router_Port" db="OVN_Northbound"/> row.
-      </li>
-
-      <li>
-        <var>E</var> is the Ethernet address of the logical router port.
-      </li>
-
-      <li>
-        <var>I</var> is the IPv4 address of the logical router port.
-      </li>
-    </ul>
-
-    <p>
-      This table adds one priority-0 fallback flow that matches all packets
-      and advances to the next table.
-    </p>
-
-    <h3>Ingress Table 11: Gateway Redirect</h3>
-
-    <p>
-      For distributed logical routers where one of the logical router
-      ports specifies a <code>redirect-chassis</code>, this table redirects
-      certain packets to the distributed gateway port instance on the
-      <code>redirect-chassis</code>.  This table has the following flows:
-    </p>
-
-    <ul>
-      <li>
-        A priority-300 logical flow with match
-        <code>REGBIT_DISTRIBUTED_NAT == 1</code> has action
-        <code>next;</code>
-      </li>
-      <li>
-        A priority-200 logical flow with match
-        <code>REGBIT_NAT_REDIRECT == 1</code> has actions
-        <code>outport = <var>CR</var>; next;</code>, where <var>CR</var>
-        is the <code>chassisredirect</code> port representing the instance
-        of the logical router distributed gateway port on the
-        <code>redirect-chassis</code>.
-      </li>
-
-      <li>
-        A priority-150 logical flow with match
-        <code>outport == <var>GW</var> &amp;&amp;
-        eth.dst == 00:00:00:00:00:00</code> has actions
-        <code>outport = <var>CR</var>; next;</code>, where
-        <var>GW</var> is the logical router distributed gateway
-        port and <var>CR</var> is the <code>chassisredirect</code>
-        port representing the instance of the logical router
-        distributed gateway port on the
-        <code>redirect-chassis</code>.
-      </li>
-
-      <li>
-        For each NAT rule in the OVN Northbound database that can
-        be handled in a distributed manner, a priority-100 logical
-        flow with match <code>ip4.src == <var>B</var> &amp;&amp;
-        outport == <var>GW</var></code>, where <var>GW</var> is
-        the logical router distributed gateway port, with actions
-        <code>next;</code>.
-      </li>
-
-      <li>
-        A priority-50 logical flow with match
-        <code>outport == <var>GW</var></code> has actions
-        <code>outport = <var>CR</var>; next;</code>, where
-        <var>GW</var> is the logical router distributed gateway
-        port and <var>CR</var> is the <code>chassisredirect</code>
-        port representing the instance of the logical router
-        distributed gateway port on the
-        <code>redirect-chassis</code>.
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Ingress Table 12: ARP Request</h3>
-
-    <p>
-      In the common case where the Ethernet destination has been resolved, this
-      table outputs the packet.  Otherwise, it composes and sends an ARP or
-      IPv6 Neighbor Solicitation request.  It holds the following flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          Unknown MAC address.  A priority-100 flow for IPv4 packets with match
-          <code>eth.dst == 00:00:00:00:00:00</code> has the following actions:
-        </p>
-
-        <pre>
-arp {
-    eth.dst = ff:ff:ff:ff:ff:ff;
-    arp.spa = reg1;
-    arp.tpa = reg0;
-    arp.op = 1;  /* ARP request. */
-    output;
-};
-        </pre>
-
-        <p>
-          Unknown MAC address.  For each IPv6 static route associated with the
-          router with the nexthop IP: <var>G</var>, a priority-200 flow
-          for IPv6 packets with match
-          <code>eth.dst == 00:00:00:00:00:00 &amp;&amp;
-          xxreg0 == <var>G</var></code>
-          with the following actions is added:
-        </p>
-
-        <pre>
-nd_ns {
-    eth.dst = <var>E</var>;
-    ip6.dst = <var>I</var>
-    nd.target = <var>G</var>;
-    output;
-};
-        </pre>
-
-        <p>
-          Where <var>E</var> is the multicast mac derived from the Gateway IP,
-          <var>I</var> is the solicited-node multicast address corresponding
-          to the target address <var>G</var>.
-        </p>
-
-        <p>
-          Unknown MAC address.  A priority-100 flow for IPv6 packets with match
-          <code>eth.dst == 00:00:00:00:00:00</code> has the following actions:
-        </p>
-
-        <pre>
-nd_ns {
-    nd.target = xxreg0;
-    output;
-};
-        </pre>
-
-        <p>
-          (Ingress table <code>IP Routing</code> initialized <code>reg1</code>
-          with the IP address owned by <code>outport</code> and
-          <code>(xx)reg0</code> with the next-hop IP address)
-        </p>
-
-        <p>
-          The IP packet that triggers the ARP/IPv6 NS request is dropped.
-        </p>
-      </li>
-
-      <li>
-        Known MAC address.  A priority-0 flow with match <code>1</code> has
-        actions <code>output;</code>.
-      </li>
-    </ul>
-
-    <h3>Egress Table 0: UNDNAT</h3>
-
-    <p>
-      This is for already established connections' reverse traffic.
-      i.e., DNAT has already been done in ingress pipeline and now the
-      packet has entered the egress pipeline as part of a reply.  For
-      NAT on a distributed router, it is unDNATted here.  For Gateway
-      routers, the unDNAT processing is carried out in the ingress DNAT
-      table.
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For all the configured load balancing rules for a router with gateway
-          port in <code>OVN_Northbound</code> database that includes an IPv4
-          address <code>VIP</code>, for every backend IPv4 address <var>B</var>
-          defined for the <code>VIP</code> a priority-120 flow is programmed on
-          <code>redirect-chassis</code> that matches
-          <code>ip &amp;&amp; ip4.src == <var>B</var> &amp;&amp;
-          outport == <var>GW</var></code>, where <var>GW</var> is the logical
-          router gateway port with an action <code>ct_dnat;</code>. If the
-          backend IPv4 address <var>B</var> is also configured with L4 port
-          <var>PORT</var> of protocol <var>P</var>, then the
-          match also includes <code>P.src</code> == <var>PORT</var>.  These
-          flows are not added for load balancers with IPv6 <var>VIPs</var>.
-        </p>
-
-        <p>
-          If the router is configured to force SNAT  any load-balanced packets,
-          above action will be replaced by
-          <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          For each configuration in the OVN Northbound database that asks
-          to change the destination IP address of a packet from an IP
-          address of <var>A</var> to <var>B</var>, a priority-100 flow
-          matches <code>ip &amp;&amp; ip4.src == <var>B</var>
-          &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
-          is the logical router gateway port, with an action
-          <code>ct_dnat;</code>.
-        </p>
-
-        <p>
-          If the NAT rule cannot be handled in a distributed manner, then
-          the priority-100 flow above is only programmed on the
-          <code>redirect-chassis</code>.
-        </p>
-
-        <p>
-          If the NAT rule can be handled in a distributed manner, then
-          there is an additional action
-          <code>eth.src = <var>EA</var>;</code>, where <var>EA</var>
-          is the ethernet address associated with the IP address
-          <var>A</var> in the NAT rule.  This allows upstream MAC
-          learning to point to the correct chassis.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Egress Table 1: SNAT</h3>
-
-    <p>
-      Packets that are configured to be SNATed get their source IP address
-      changed based on the configuration in the OVN Northbound database.
-    </p>
-
-    <p>Egress Table 1: SNAT on Gateway Routers</p>
-
-    <ul>
-      <li>
-        <p>
-          If the Gateway router in the OVN Northbound database has been
-          configured to force SNAT a packet (that has been previously DNATted)
-          to <var>B</var>, a priority-100 flow matches
-          <code>flags.force_snat_for_dnat == 1 &amp;&amp; ip</code> with an
-          action <code>ct_snat(<var>B</var>);</code>.
-        </p>
-        <p>
-          If the Gateway router in the OVN Northbound database has been
-          configured to force SNAT a packet (that has been previously
-          load-balanced) to <var>B</var>, a priority-100 flow matches
-          <code>flags.force_snat_for_lb == 1 &amp;&amp; ip</code> with an
-          action <code>ct_snat(<var>B</var>);</code>.
-        </p>
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from an IP address of
-          <var>A</var> or to change the source IP address of a packet that
-          belongs to network <var>A</var> to <var>B</var>, a flow matches
-          <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
-          <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
-          is calculated based on the mask of <var>A</var>, with matches
-          having larger masks getting higher priorities.
-        </p>
-        <p>
-          A priority-0 logical flow with match <code>1</code> has actions
-          <code>next;</code>.
-        </p>
-      </li>
-    </ul>
-
-    <p>Egress Table 1: SNAT on Distributed Routers</p>
-
-    <ul>
-      <li>
-        <p>
-          For each configuration in the OVN Northbound database, that asks
-          to change the source IP address of a packet from an IP address of
-          <var>A</var> or to change the source IP address of a packet that
-          belongs to network <var>A</var> to <var>B</var>, a flow matches
-          <code>ip &amp;&amp; ip4.src == <var>A</var> &amp;&amp;
-          outport == <var>GW</var></code>, where <var>GW</var> is the
-          logical router gateway port, with an action
-          <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
-          is calculated based on the mask of <var>A</var>, with matches
-          having larger masks getting higher priorities.
-        </p>
-
-        <p>
-          If the NAT rule cannot be handled in a distributed manner, then
-          the flow above is only programmed on the
-          <code>redirect-chassis</code> increasing flow priority by 128 in
-          order to be run first
-        </p>
-
-        <p>
-          If the NAT rule can be handled in a distributed manner, then
-          there is an additional action
-          <code>eth.src = <var>EA</var>;</code>, where <var>EA</var>
-          is the ethernet address associated with the IP address
-          <var>A</var> in the NAT rule.  This allows upstream MAC
-          learning to point to the correct chassis.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Egress Table 2: Egress Loopback</h3>
-
-    <p>
-      For distributed logical routers where one of the logical router
-      ports specifies a <code>redirect-chassis</code>.
-    </p>
-
-    <p>
-      Earlier in the ingress pipeline, some east-west traffic was
-      redirected to the <code>chassisredirect</code> port, based on
-      flows in the <code>UNSNAT</code> and <code>DNAT</code> ingress
-      tables setting the <code>REGBIT_NAT_REDIRECT</code> flag, which
-      then triggered a match to a flow in the
-      <code>Gateway Redirect</code> ingress table.  The intention was
-      not to actually send traffic out the distributed gateway port
-      instance on the <code>redirect-chassis</code>.  This traffic was
-      sent to the distributed gateway port instance in order for DNAT
-      and/or SNAT processing to be applied.
-    </p>
-
-    <p>
-      While UNDNAT and SNAT processing have already occurred by this
-      point, this traffic needs to be forced through egress loopback on
-      this distributed gateway port instance, in order for UNSNAT and
-      DNAT processing to be applied, and also for IP routing and ARP
-      resolution after all of the NAT processing, so that the packet can
-      be forwarded to the destination.
-    </p>
-
-    <p>
-      This table has the following flows:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          For each <code>dnat_and_snat</code> NAT rule couple in the
-          OVN Northbound database on a distributed router,
-          a priority-200 logical with match
-          <code>ip4.dst == <var>external_ip0</var> &amp;&amp;
-          ip4.src == <var>external_ip1</var></code>, has action
-          <code>next;</code>
-        </p>
-
-        <p>
-          For each NAT rule in the OVN Northbound database on a
-          distributed router, a priority-100 logical flow with match
-          <code>ip4.dst == <var>E</var> &amp;&amp;
-          outport == <var>GW</var></code>, where <var>E</var> is the
-          external IP address specified in the NAT rule, and <var>GW</var>
-          is the logical router distributed gateway port, with the
-          following actions:
-        </p>
-
-        <pre>
-clone {
-    ct_clear;
-    inport = outport;
-    outport = "";
-    flags = 0;
-    flags.loopback = 1;
-    reg0 = 0;
-    reg1 = 0;
-    ...
-    reg9 = 0;
-    REGBIT_EGRESS_LOOPBACK = 1;
-    next(pipeline=ingress, table=0);
-};
-        </pre>
-
-        <p>
-          <code>flags.loopback</code> is set since in_port is unchanged
-          and the packet may return back to that port after NAT processing.
-          <code>REGBIT_EGRESS_LOOPBACK</code> is set to indicate that
-          egress loopback has occurred, in order to skip the source IP
-          address check against the router address.
-        </p>
-      </li>
-
-      <li>
-        A priority-0 logical flow with match <code>1</code> has actions
-        <code>next;</code>.
-      </li>
-    </ul>
-
-    <h3>Egress Table 3: Delivery</h3>
-
-    <p>
-      Packets that reach this table are ready for delivery.  It contains
-      priority-100 logical flows that match packets on each enabled logical
-      router port, with action <code>output;</code>.
-    </p>
-
-</manpage>
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
deleted file mode 100644
index 77415ddd3..000000000
--- a/ovn/northd/ovn-northd.c
+++ /dev/null
@@ -1,9446 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-
-#include <getopt.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "bitmap.h"
-#include "command-line.h"
-#include "daemon.h"
-#include "dirs.h"
-#include "openvswitch/dynamic-string.h"
-#include "fatal-signal.h"
-#include "hash.h"
-#include "openvswitch/hmap.h"
-#include "openvswitch/json.h"
-#include "ovn/lex.h"
-#include "ovn/lib/chassis-index.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-nb-idl.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "ovn/actions.h"
-#include "ovn/logical-fields.h"
-#include "packets.h"
-#include "openvswitch/poll-loop.h"
-#include "smap.h"
-#include "sset.h"
-#include "svec.h"
-#include "stream.h"
-#include "stream-ssl.h"
-#include "unixctl.h"
-#include "util.h"
-#include "uuid.h"
-#include "openvswitch/vlog.h"
-
-VLOG_DEFINE_THIS_MODULE(ovn_northd);
-
-static unixctl_cb_func ovn_northd_exit;
-
-struct northd_context {
-    struct ovsdb_idl *ovnnb_idl;
-    struct ovsdb_idl *ovnsb_idl;
-    struct ovsdb_idl_txn *ovnnb_txn;
-    struct ovsdb_idl_txn *ovnsb_txn;
-    struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
-    struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp;
-    struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
-};
-
-static const char *ovnnb_db;
-static const char *ovnsb_db;
-static const char *unixctl_path;
-
-#define MAC_ADDR_SPACE 0xffffff
-
-/* MAC address management (macam) table of "struct eth_addr"s, that holds the
- * MAC addresses allocated by the OVN ipam module. */
-static struct hmap macam = HMAP_INITIALIZER(&macam);
-static struct eth_addr mac_prefix;
-
-static bool controller_event_en;
-
-#define MAX_OVN_TAGS 4096
-
-/* Pipeline stages. */
-
-/* The two pipelines in an OVN logical flow table. */
-enum ovn_pipeline {
-    P_IN,                       /* Ingress pipeline. */
-    P_OUT                       /* Egress pipeline. */
-};
-
-/* The two purposes for which ovn-northd uses OVN logical datapaths. */
-enum ovn_datapath_type {
-    DP_SWITCH,                  /* OVN logical switch. */
-    DP_ROUTER                   /* OVN logical router. */
-};
-
-/* Returns an "enum ovn_stage" built from the arguments.
- *
- * (It's better to use ovn_stage_build() for type-safety reasons, but inline
- * functions can't be used in enums or switch cases.) */
-#define OVN_STAGE_BUILD(DP_TYPE, PIPELINE, TABLE) \
-    (((DP_TYPE) << 9) | ((PIPELINE) << 8) | (TABLE))
-
-/* A stage within an OVN logical switch or router.
- *
- * An "enum ovn_stage" indicates whether the stage is part of a logical switch
- * or router, whether the stage is part of the ingress or egress pipeline, and
- * the table within that pipeline.  The first three components are combined to
- * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2,
- * S_ROUTER_OUT_DELIVERY. */
-enum ovn_stage {
-#define PIPELINE_STAGES                                                   \
-    /* Logical switch ingress stages. */                                  \
-    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_L2,    0, "ls_in_port_sec_l2")   \
-    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_IP,    1, "ls_in_port_sec_ip")   \
-    PIPELINE_STAGE(SWITCH, IN,  PORT_SEC_ND,    2, "ls_in_port_sec_nd")   \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        3, "ls_in_pre_acl")       \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         4, "ls_in_pre_lb")        \
-    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   5, "ls_in_pre_stateful")  \
-    PIPELINE_STAGE(SWITCH, IN,  ACL,            6, "ls_in_acl")           \
-    PIPELINE_STAGE(SWITCH, IN,  QOS_MARK,       7, "ls_in_qos_mark")      \
-    PIPELINE_STAGE(SWITCH, IN,  QOS_METER,      8, "ls_in_qos_meter")     \
-    PIPELINE_STAGE(SWITCH, IN,  LB,             9, "ls_in_lb")            \
-    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      10, "ls_in_stateful")      \
-    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    11, "ls_in_arp_rsp")       \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  12, "ls_in_dhcp_options")  \
-    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    14, "ls_in_dns_lookup")    \
-    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  15, "ls_in_dns_response")  \
-    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 16, "ls_in_external_port") \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       17, "ls_in_l2_lkup")       \
-                                                                          \
-    /* Logical switch egress stages. */                                   \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,      1, "ls_out_pre_acl")        \
-    PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful")   \
-    PIPELINE_STAGE(SWITCH, OUT, LB,           3, "ls_out_lb")             \
-    PIPELINE_STAGE(SWITCH, OUT, ACL,          4, "ls_out_acl")            \
-    PIPELINE_STAGE(SWITCH, OUT, QOS_MARK,     5, "ls_out_qos_mark")       \
-    PIPELINE_STAGE(SWITCH, OUT, QOS_METER,    6, "ls_out_qos_meter")      \
-    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     7, "ls_out_stateful")       \
-    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP,  8, "ls_out_port_sec_ip")    \
-    PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2,  9, "ls_out_port_sec_l2")    \
-                                                                      \
-    /* Logical router ingress stages. */                              \
-    PIPELINE_STAGE(ROUTER, IN,  ADMISSION,      0, "lr_in_admission")    \
-    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,       1, "lr_in_ip_input")     \
-    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,         2, "lr_in_defrag")       \
-    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,         3, "lr_in_unsnat")       \
-    PIPELINE_STAGE(ROUTER, IN,  DNAT,           4, "lr_in_dnat")         \
-    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,  5, "lr_in_nd_ra_options") \
-    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \
-    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,     7, "lr_in_ip_routing")   \
-    PIPELINE_STAGE(ROUTER, IN,  POLICY,         8, "lr_in_policy")       \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,    9, "lr_in_arp_resolve")  \
-    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   , 10, "lr_in_chk_pkt_len")   \
-    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,    11,"lr_in_larger_pkts")   \
-    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,    12, "lr_in_gw_redirect")  \
-    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,    13, "lr_in_arp_request")  \
-                                                                      \
-    /* Logical router egress stages. */                               \
-    PIPELINE_STAGE(ROUTER, OUT, UNDNAT,    0, "lr_out_undnat")        \
-    PIPELINE_STAGE(ROUTER, OUT, SNAT,      1, "lr_out_snat")          \
-    PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP,  2, "lr_out_egr_loop")      \
-    PIPELINE_STAGE(ROUTER, OUT, DELIVERY,  3, "lr_out_delivery")
-
-#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME)   \
-    S_##DP_TYPE##_##PIPELINE##_##STAGE                          \
-        = OVN_STAGE_BUILD(DP_##DP_TYPE, P_##PIPELINE, TABLE),
-    PIPELINE_STAGES
-#undef PIPELINE_STAGE
-};
-
-/* Due to various hard-coded priorities need to implement ACLs, the
- * northbound database supports a smaller range of ACL priorities than
- * are available to logical flows.  This value is added to an ACL
- * priority to determine the ACL's logical flow priority. */
-#define OVN_ACL_PRI_OFFSET 1000
-
-/* Register definitions specific to switches. */
-#define REGBIT_CONNTRACK_DEFRAG  "reg0[0]"
-#define REGBIT_CONNTRACK_COMMIT  "reg0[1]"
-#define REGBIT_CONNTRACK_NAT     "reg0[2]"
-#define REGBIT_DHCP_OPTS_RESULT  "reg0[3]"
-#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
-#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]"
-
-/* Register definitions for switches and routers. */
-#define REGBIT_NAT_REDIRECT     "reg9[0]"
-/* Indicate that this packet has been recirculated using egress
- * loopback.  This allows certain checks to be bypassed, such as a
- * logical router dropping packets with source IP address equals
- * one of the logical router's own IP addresses. */
-#define REGBIT_EGRESS_LOOPBACK  "reg9[1]"
-#define REGBIT_DISTRIBUTED_NAT  "reg9[2]"
-/* Register to store the result of check_pkt_larger action. */
-#define REGBIT_PKT_LARGER        "reg9[3]"
-
-/* Returns an "enum ovn_stage" built from the arguments. */
-static enum ovn_stage
-ovn_stage_build(enum ovn_datapath_type dp_type, enum ovn_pipeline pipeline,
-                uint8_t table)
-{
-    return OVN_STAGE_BUILD(dp_type, pipeline, table);
-}
-
-/* Returns the pipeline to which 'stage' belongs. */
-static enum ovn_pipeline
-ovn_stage_get_pipeline(enum ovn_stage stage)
-{
-    return (stage >> 8) & 1;
-}
-
-/* Returns the pipeline name to which 'stage' belongs. */
-static const char *
-ovn_stage_get_pipeline_name(enum ovn_stage stage)
-{
-    return ovn_stage_get_pipeline(stage) == P_IN ? "ingress" : "egress";
-}
-
-/* Returns the table to which 'stage' belongs. */
-static uint8_t
-ovn_stage_get_table(enum ovn_stage stage)
-{
-    return stage & 0xff;
-}
-
-/* Returns a string name for 'stage'. */
-static const char *
-ovn_stage_to_str(enum ovn_stage stage)
-{
-    switch (stage) {
-#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME)       \
-        case S_##DP_TYPE##_##PIPELINE##_##STAGE: return NAME;
-    PIPELINE_STAGES
-#undef PIPELINE_STAGE
-        default: return "<unknown>";
-    }
-}
-
-/* Returns the type of the datapath to which a flow with the given 'stage' may
- * be added. */
-static enum ovn_datapath_type
-ovn_stage_to_datapath_type(enum ovn_stage stage)
-{
-    switch (stage) {
-#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME)       \
-        case S_##DP_TYPE##_##PIPELINE##_##STAGE: return DP_##DP_TYPE;
-    PIPELINE_STAGES
-#undef PIPELINE_STAGE
-    default: OVS_NOT_REACHED();
-    }
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN northbound management daemon\n\
-usage: %s [OPTIONS]\n\
-\n\
-Options:\n\
-  --ovnnb-db=DATABASE       connect to ovn-nb database at DATABASE\n\
-                            (default: %s)\n\
-  --ovnsb-db=DATABASE       connect to ovn-sb database at DATABASE\n\
-                            (default: %s)\n\
-  --unixctl=SOCKET          override default control socket name\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_nb_db(), default_sb_db());
-    daemon_usage();
-    vlog_usage();
-    stream_usage("database", true, true, false);
-}
-
-struct tnlid_node {
-    struct hmap_node hmap_node;
-    uint32_t tnlid;
-};
-
-static void
-destroy_tnlids(struct hmap *tnlids)
-{
-    struct tnlid_node *node;
-    HMAP_FOR_EACH_POP (node, hmap_node, tnlids) {
-        free(node);
-    }
-    hmap_destroy(tnlids);
-}
-
-static void
-add_tnlid(struct hmap *set, uint32_t tnlid)
-{
-    struct tnlid_node *node = xmalloc(sizeof *node);
-    hmap_insert(set, &node->hmap_node, hash_int(tnlid, 0));
-    node->tnlid = tnlid;
-}
-
-static bool
-tnlid_in_use(const struct hmap *set, uint32_t tnlid)
-{
-    const struct tnlid_node *node;
-    HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash_int(tnlid, 0), set) {
-        if (node->tnlid == tnlid) {
-            return true;
-        }
-    }
-    return false;
-}
-
-static uint32_t
-next_tnlid(uint32_t tnlid, uint32_t min, uint32_t max)
-{
-    return tnlid + 1 <= max ? tnlid + 1 : min;
-}
-
-static uint32_t
-allocate_tnlid(struct hmap *set, const char *name, uint32_t min, uint32_t max,
-               uint32_t *hint)
-{
-    for (uint32_t tnlid = next_tnlid(*hint, min, max); tnlid != *hint;
-         tnlid = next_tnlid(tnlid, min, max)) {
-        if (!tnlid_in_use(set, tnlid)) {
-            add_tnlid(set, tnlid);
-            *hint = tnlid;
-            return tnlid;
-        }
-    }
-
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-    VLOG_WARN_RL(&rl, "all %s tunnel ids exhausted", name);
-    return 0;
-}
-
-struct ovn_chassis_qdisc_queues {
-    struct hmap_node key_node;
-    uint32_t queue_id;
-    struct uuid chassis_uuid;
-};
-
-static uint32_t
-hash_chassis_queue(const struct uuid *chassis_uuid, uint32_t queue_id)
-{
-    return hash_2words(uuid_hash(chassis_uuid), queue_id);
-}
-
-static void
-destroy_chassis_queues(struct hmap *set)
-{
-    struct ovn_chassis_qdisc_queues *node;
-    HMAP_FOR_EACH_POP (node, key_node, set) {
-        free(node);
-    }
-    hmap_destroy(set);
-}
-
-static void
-add_chassis_queue(struct hmap *set, struct uuid *chassis_uuid,
-                  uint32_t queue_id)
-{
-    struct ovn_chassis_qdisc_queues *node = xmalloc(sizeof *node);
-    node->queue_id = queue_id;
-    node->chassis_uuid = *chassis_uuid;
-    hmap_insert(set, &node->key_node,
-                hash_chassis_queue(chassis_uuid, queue_id));
-}
-
-static bool
-chassis_queueid_in_use(const struct hmap *set, struct uuid *chassis_uuid,
-                       uint32_t queue_id)
-{
-    const struct ovn_chassis_qdisc_queues *node;
-    HMAP_FOR_EACH_WITH_HASH (node, key_node,
-                             hash_chassis_queue(chassis_uuid, queue_id), set) {
-        if (uuid_equals(chassis_uuid, &node->chassis_uuid)
-            && node->queue_id == queue_id) {
-            return true;
-        }
-    }
-    return false;
-}
-
-static uint32_t
-allocate_chassis_queueid(struct hmap *set, struct sbrec_chassis *chassis)
-{
-    for (uint32_t queue_id = QDISC_MIN_QUEUE_ID + 1;
-         queue_id <= QDISC_MAX_QUEUE_ID;
-         queue_id++) {
-        if (!chassis_queueid_in_use(set, &chassis->header_.uuid, queue_id)) {
-            add_chassis_queue(set, &chassis->header_.uuid, queue_id);
-            return queue_id;
-        }
-    }
-
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-    VLOG_WARN_RL(&rl, "all %s queue ids exhausted", chassis->name);
-    return 0;
-}
-
-static void
-free_chassis_queueid(struct hmap *set, struct sbrec_chassis *chassis,
-                     uint32_t queue_id)
-{
-    const struct uuid *chassis_uuid = &chassis->header_.uuid;
-    struct ovn_chassis_qdisc_queues *node;
-    HMAP_FOR_EACH_WITH_HASH (node, key_node,
-                             hash_chassis_queue(chassis_uuid, queue_id), set) {
-        if (uuid_equals(chassis_uuid, &node->chassis_uuid)
-            && node->queue_id == queue_id) {
-            hmap_remove(set, &node->key_node);
-            free(node);
-            break;
-        }
-    }
-}
-
-static inline bool
-port_has_qos_params(const struct smap *opts)
-{
-    return (smap_get(opts, "qos_max_rate") ||
-            smap_get(opts, "qos_burst"));
-}
-
-
-struct ipam_info {
-    uint32_t start_ipv4;
-    size_t total_ipv4s;
-    unsigned long *allocated_ipv4s; /* A bitmap of allocated IPv4s */
-    bool ipv6_prefix_set;
-    struct in6_addr ipv6_prefix;
-    bool mac_only;
-};
-
-#define OVN_MIN_MULTICAST 32768
-#define OVN_MAX_MULTICAST OVN_MCAST_FLOOD_TUNNEL_KEY
-BUILD_ASSERT_DECL(OVN_MIN_MULTICAST < OVN_MAX_MULTICAST);
-
-#define OVN_MIN_IP_MULTICAST OVN_MIN_MULTICAST
-#define OVN_MAX_IP_MULTICAST (OVN_MCAST_UNKNOWN_TUNNEL_KEY - 1)
-BUILD_ASSERT_DECL(OVN_MAX_IP_MULTICAST >= OVN_MIN_MULTICAST);
-
-/*
- * Multicast snooping and querier per datapath configuration.
- */
-struct mcast_info {
-    bool enabled;
-    bool querier;
-    bool flood_unregistered;
-
-    int64_t table_size;
-    int64_t idle_timeout;
-    int64_t query_interval;
-    char *eth_src;
-    char *ipv4_src;
-    int64_t  query_max_response;
-
-    struct hmap group_tnlids;
-    uint32_t group_tnlid_hint;
-    uint32_t active_flows;
-};
-
-static uint32_t
-ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
-{
-    return allocate_tnlid(&mcast_info->group_tnlids, "multicast group",
-                          OVN_MIN_IP_MULTICAST, OVN_MAX_IP_MULTICAST,
-                          &mcast_info->group_tnlid_hint);
-}
-
-/* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or
- * sb->external_ids:logical-switch. */
-struct ovn_datapath {
-    struct hmap_node key_node;  /* Index on 'key'. */
-    struct uuid key;            /* (nbs/nbr)->header_.uuid. */
-
-    const struct nbrec_logical_switch *nbs;  /* May be NULL. */
-    const struct nbrec_logical_router *nbr;  /* May be NULL. */
-    const struct sbrec_datapath_binding *sb; /* May be NULL. */
-
-    struct ovs_list list;       /* In list of similar records. */
-
-    /* Logical switch data. */
-    struct ovn_port **router_ports;
-    size_t n_router_ports;
-
-    struct hmap port_tnlids;
-    uint32_t port_key_hint;
-
-    bool has_unknown;
-
-    /* IPAM data. */
-    struct ipam_info ipam_info;
-
-    /* Multicast data. */
-    struct mcast_info mcast_info;
-
-    /* OVN northd only needs to know about the logical router gateway port for
-     * NAT on a distributed router.  This "distributed gateway port" is
-     * populated only when there is a "redirect-chassis" specified for one of
-     * the ports on the logical router.  Otherwise this will be NULL. */
-    struct ovn_port *l3dgw_port;
-    /* The "derived" OVN port representing the instance of l3dgw_port on
-     * the "redirect-chassis". */
-    struct ovn_port *l3redirect_port;
-    struct ovn_port *localnet_port;
-
-    struct ovs_list lr_list; /* In list of logical router datapaths. */
-    /* The logical router group to which this datapath belongs.
-     * Valid only if it is logical router datapath. NULL otherwise. */
-    struct lrouter_group *lr_group;
-
-    /* Port groups related to the datapath, used only when nbs is NOT NULL. */
-    struct hmap nb_pgs;
-};
-
-/* A group of logical router datapaths which are connected - either
- * directly or indirectly.
- * Each logical router can belong to only one group. */
-struct lrouter_group {
-    struct ovn_datapath **router_dps;
-    int n_router_dps;
-    /* Set of ha_chassis_groups which are associated with the router dps. */
-    struct sset ha_chassis_groups;
-};
-
-struct macam_node {
-    struct hmap_node hmap_node;
-    struct eth_addr mac_addr; /* Allocated MAC address. */
-};
-
-static void
-cleanup_macam(struct hmap *macam_)
-{
-    struct macam_node *node;
-    HMAP_FOR_EACH_POP (node, hmap_node, macam_) {
-        free(node);
-    }
-}
-
-static struct ovn_datapath *
-ovn_datapath_create(struct hmap *datapaths, const struct uuid *key,
-                    const struct nbrec_logical_switch *nbs,
-                    const struct nbrec_logical_router *nbr,
-                    const struct sbrec_datapath_binding *sb)
-{
-    struct ovn_datapath *od = xzalloc(sizeof *od);
-    od->key = *key;
-    od->sb = sb;
-    od->nbs = nbs;
-    od->nbr = nbr;
-    hmap_init(&od->port_tnlids);
-    hmap_init(&od->nb_pgs);
-    od->port_key_hint = 0;
-    hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key));
-    od->lr_group = NULL;
-    return od;
-}
-
-static void ovn_ls_port_group_destroy(struct hmap *nb_pgs);
-
-static void
-ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
-{
-    if (od) {
-        /* Don't remove od->list.  It is used within build_datapaths() as a
-         * private list and once we've exited that function it is not safe to
-         * use it. */
-        hmap_remove(datapaths, &od->key_node);
-        destroy_tnlids(&od->port_tnlids);
-        bitmap_free(od->ipam_info.allocated_ipv4s);
-        free(od->router_ports);
-        ovn_ls_port_group_destroy(&od->nb_pgs);
-
-        if (od->nbs) {
-            free(od->mcast_info.eth_src);
-            free(od->mcast_info.ipv4_src);
-            destroy_tnlids(&od->mcast_info.group_tnlids);
-        }
-
-        free(od);
-    }
-}
-
-/* Returns 'od''s datapath type. */
-static enum ovn_datapath_type
-ovn_datapath_get_type(const struct ovn_datapath *od)
-{
-    return od->nbs ? DP_SWITCH : DP_ROUTER;
-}
-
-static struct ovn_datapath *
-ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid)
-{
-    struct ovn_datapath *od;
-
-    HMAP_FOR_EACH_WITH_HASH (od, key_node, uuid_hash(uuid), datapaths) {
-        if (uuid_equals(uuid, &od->key)) {
-            return od;
-        }
-    }
-    return NULL;
-}
-
-static struct ovn_datapath *
-ovn_datapath_from_sbrec(struct hmap *datapaths,
-                        const struct sbrec_datapath_binding *sb)
-{
-    struct uuid key;
-
-    if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) &&
-        !smap_get_uuid(&sb->external_ids, "logical-router", &key)) {
-        return NULL;
-    }
-    return ovn_datapath_find(datapaths, &key);
-}
-
-static bool
-lrouter_is_enabled(const struct nbrec_logical_router *lrouter)
-{
-    return !lrouter->enabled || *lrouter->enabled;
-}
-
-static void
-init_ipam_info_for_datapath(struct ovn_datapath *od)
-{
-    if (!od->nbs) {
-        return;
-    }
-
-    const char *subnet_str = smap_get(&od->nbs->other_config, "subnet");
-    const char *ipv6_prefix = smap_get(&od->nbs->other_config, "ipv6_prefix");
-
-    if (ipv6_prefix) {
-        od->ipam_info.ipv6_prefix_set = ipv6_parse(
-            ipv6_prefix, &od->ipam_info.ipv6_prefix);
-    }
-
-    if (!subnet_str) {
-        if (!ipv6_prefix) {
-            od->ipam_info.mac_only = smap_get_bool(&od->nbs->other_config,
-                                                   "mac_only", false);
-        }
-        return;
-    }
-
-    ovs_be32 subnet, mask;
-    char *error = ip_parse_masked(subnet_str, &subnet, &mask);
-    if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) {
-        static struct vlog_rate_limit rl
-            = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "bad 'subnet' %s", subnet_str);
-        free(error);
-        return;
-    }
-
-    od->ipam_info.start_ipv4 = ntohl(subnet) + 1;
-    od->ipam_info.total_ipv4s = ~ntohl(mask);
-    od->ipam_info.allocated_ipv4s =
-        bitmap_allocate(od->ipam_info.total_ipv4s);
-
-    /* Mark first IP as taken */
-    bitmap_set1(od->ipam_info.allocated_ipv4s, 0);
-
-    /* Check if there are any reserver IPs (list) to be excluded from IPAM */
-    const char *exclude_ip_list = smap_get(&od->nbs->other_config,
-                                           "exclude_ips");
-    if (!exclude_ip_list) {
-        return;
-    }
-
-    struct lexer lexer;
-    lexer_init(&lexer, exclude_ip_list);
-    /* exclude_ip_list could be in the format -
-    *  "10.0.0.4 10.0.0.10 10.0.0.20..10.0.0.50 10.0.0.100..10.0.0.110".
-    */
-    lexer_get(&lexer);
-    while (lexer.token.type != LEX_T_END) {
-        if (lexer.token.type != LEX_T_INTEGER) {
-            lexer_syntax_error(&lexer, "expecting address");
-            break;
-        }
-        uint32_t start = ntohl(lexer.token.value.ipv4);
-        lexer_get(&lexer);
-
-        uint32_t end = start + 1;
-        if (lexer_match(&lexer, LEX_T_ELLIPSIS)) {
-            if (lexer.token.type != LEX_T_INTEGER) {
-                lexer_syntax_error(&lexer, "expecting address range");
-                break;
-            }
-            end = ntohl(lexer.token.value.ipv4) + 1;
-            lexer_get(&lexer);
-        }
-
-        /* Clamp start...end to fit the subnet. */
-        start = MAX(od->ipam_info.start_ipv4, start);
-        end = MIN(od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s, end);
-        if (end > start) {
-            bitmap_set_multiple(od->ipam_info.allocated_ipv4s,
-                                start - od->ipam_info.start_ipv4,
-                                end - start, 1);
-        } else {
-            lexer_error(&lexer, "excluded addresses not in subnet");
-        }
-    }
-    if (lexer.error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "logical switch "UUID_FMT": bad exclude_ips (%s)",
-                     UUID_ARGS(&od->key), lexer.error);
-    }
-    lexer_destroy(&lexer);
-}
-
-static void
-init_mcast_info_for_datapath(struct ovn_datapath *od)
-{
-    if (!od->nbs) {
-        return;
-    }
-
-    struct mcast_info *mcast_info = &od->mcast_info;
-
-    mcast_info->enabled =
-        smap_get_bool(&od->nbs->other_config, "mcast_snoop", false);
-    mcast_info->querier =
-        smap_get_bool(&od->nbs->other_config, "mcast_querier", true);
-    mcast_info->flood_unregistered =
-        smap_get_bool(&od->nbs->other_config, "mcast_flood_unregistered",
-                      false);
-
-    mcast_info->table_size =
-        smap_get_ullong(&od->nbs->other_config, "mcast_table_size",
-                        OVN_MCAST_DEFAULT_MAX_ENTRIES);
-
-    uint32_t idle_timeout =
-        smap_get_ullong(&od->nbs->other_config, "mcast_idle_timeout",
-                        OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S);
-    if (idle_timeout < OVN_MCAST_MIN_IDLE_TIMEOUT_S) {
-        idle_timeout = OVN_MCAST_MIN_IDLE_TIMEOUT_S;
-    } else if (idle_timeout > OVN_MCAST_MAX_IDLE_TIMEOUT_S) {
-        idle_timeout = OVN_MCAST_MAX_IDLE_TIMEOUT_S;
-    }
-    mcast_info->idle_timeout = idle_timeout;
-
-    uint32_t query_interval =
-        smap_get_ullong(&od->nbs->other_config, "mcast_query_interval",
-                        mcast_info->idle_timeout / 2);
-    if (query_interval < OVN_MCAST_MIN_QUERY_INTERVAL_S) {
-        query_interval = OVN_MCAST_MIN_QUERY_INTERVAL_S;
-    } else if (query_interval > OVN_MCAST_MAX_QUERY_INTERVAL_S) {
-        query_interval = OVN_MCAST_MAX_QUERY_INTERVAL_S;
-    }
-    mcast_info->query_interval = query_interval;
-
-    mcast_info->eth_src =
-        nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_eth_src"));
-    mcast_info->ipv4_src =
-        nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip4_src"));
-
-    mcast_info->query_max_response =
-        smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response",
-                        OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S);
-
-    hmap_init(&mcast_info->group_tnlids);
-    mcast_info->group_tnlid_hint = OVN_MIN_IP_MULTICAST;
-    mcast_info->active_flows = 0;
-}
-
-static void
-store_mcast_info_for_datapath(const struct sbrec_ip_multicast *sb,
-                              struct ovn_datapath *od)
-{
-    struct mcast_info *mcast_info = &od->mcast_info;
-
-    sbrec_ip_multicast_set_datapath(sb, od->sb);
-    sbrec_ip_multicast_set_enabled(sb, &mcast_info->enabled, 1);
-    sbrec_ip_multicast_set_querier(sb, &mcast_info->querier, 1);
-    sbrec_ip_multicast_set_table_size(sb, &mcast_info->table_size, 1);
-    sbrec_ip_multicast_set_idle_timeout(sb, &mcast_info->idle_timeout, 1);
-    sbrec_ip_multicast_set_query_interval(sb,
-                                          &mcast_info->query_interval, 1);
-    sbrec_ip_multicast_set_query_max_resp(sb,
-                                          &mcast_info->query_max_response, 1);
-
-    if (mcast_info->eth_src) {
-        sbrec_ip_multicast_set_eth_src(sb, mcast_info->eth_src);
-    }
-
-    if (mcast_info->ipv4_src) {
-        sbrec_ip_multicast_set_ip4_src(sb, mcast_info->ipv4_src);
-    }
-}
-
-static void
-ovn_datapath_update_external_ids(struct ovn_datapath *od)
-{
-    /* Get the logical-switch or logical-router UUID to set in
-     * external-ids. */
-    char uuid_s[UUID_LEN + 1];
-    sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key));
-    const char *key = od->nbs ? "logical-switch" : "logical-router";
-
-    /* Get names to set in external-ids. */
-    const char *name = od->nbs ? od->nbs->name : od->nbr->name;
-    const char *name2 = (od->nbs
-                         ? smap_get(&od->nbs->external_ids,
-                                    "neutron:network_name")
-                         : smap_get(&od->nbr->external_ids,
-                                    "neutron:router_name"));
-
-    /* Set external-ids. */
-    struct smap ids = SMAP_INITIALIZER(&ids);
-    smap_add(&ids, key, uuid_s);
-    smap_add(&ids, "name", name);
-    if (name2 && name2[0]) {
-        smap_add(&ids, "name2", name2);
-    }
-    sbrec_datapath_binding_set_external_ids(od->sb, &ids);
-    smap_destroy(&ids);
-}
-
-static void
-join_datapaths(struct northd_context *ctx, struct hmap *datapaths,
-               struct ovs_list *sb_only, struct ovs_list *nb_only,
-               struct ovs_list *both, struct ovs_list *lr_list)
-{
-    ovs_list_init(sb_only);
-    ovs_list_init(nb_only);
-    ovs_list_init(both);
-
-    const struct sbrec_datapath_binding *sb, *sb_next;
-    SBREC_DATAPATH_BINDING_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) {
-        struct uuid key;
-        if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) &&
-            !smap_get_uuid(&sb->external_ids, "logical-router", &key)) {
-            ovsdb_idl_txn_add_comment(
-                ctx->ovnsb_txn,
-                "deleting Datapath_Binding "UUID_FMT" that lacks "
-                "external-ids:logical-switch and "
-                "external-ids:logical-router",
-                UUID_ARGS(&sb->header_.uuid));
-            sbrec_datapath_binding_delete(sb);
-            continue;
-        }
-
-        if (ovn_datapath_find(datapaths, &key)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_INFO_RL(
-                &rl, "deleting Datapath_Binding "UUID_FMT" with "
-                "duplicate external-ids:logical-switch/router "UUID_FMT,
-                UUID_ARGS(&sb->header_.uuid), UUID_ARGS(&key));
-            sbrec_datapath_binding_delete(sb);
-            continue;
-        }
-
-        struct ovn_datapath *od = ovn_datapath_create(datapaths, &key,
-                                                      NULL, NULL, sb);
-        ovs_list_push_back(sb_only, &od->list);
-    }
-
-    const struct nbrec_logical_switch *nbs;
-    NBREC_LOGICAL_SWITCH_FOR_EACH (nbs, ctx->ovnnb_idl) {
-        struct ovn_datapath *od = ovn_datapath_find(datapaths,
-                                                    &nbs->header_.uuid);
-        if (od) {
-            od->nbs = nbs;
-            ovs_list_remove(&od->list);
-            ovs_list_push_back(both, &od->list);
-            ovn_datapath_update_external_ids(od);
-        } else {
-            od = ovn_datapath_create(datapaths, &nbs->header_.uuid,
-                                     nbs, NULL, NULL);
-            ovs_list_push_back(nb_only, &od->list);
-        }
-
-        init_ipam_info_for_datapath(od);
-        init_mcast_info_for_datapath(od);
-    }
-
-    const struct nbrec_logical_router *nbr;
-    NBREC_LOGICAL_ROUTER_FOR_EACH (nbr, ctx->ovnnb_idl) {
-        if (!lrouter_is_enabled(nbr)) {
-            continue;
-        }
-
-        struct ovn_datapath *od = ovn_datapath_find(datapaths,
-                                                    &nbr->header_.uuid);
-        if (od) {
-            if (!od->nbs) {
-                od->nbr = nbr;
-                ovs_list_remove(&od->list);
-                ovs_list_push_back(both, &od->list);
-                ovn_datapath_update_external_ids(od);
-            } else {
-                /* Can't happen! */
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl,
-                             "duplicate UUID "UUID_FMT" in OVN_Northbound",
-                             UUID_ARGS(&nbr->header_.uuid));
-                continue;
-            }
-        } else {
-            od = ovn_datapath_create(datapaths, &nbr->header_.uuid,
-                                     NULL, nbr, NULL);
-            ovs_list_push_back(nb_only, &od->list);
-        }
-        ovs_list_push_back(lr_list, &od->lr_list);
-    }
-}
-
-static uint32_t
-ovn_datapath_allocate_key(struct hmap *dp_tnlids)
-{
-    static uint32_t hint;
-    return allocate_tnlid(dp_tnlids, "datapath", 1, (1u << 24) - 1, &hint);
-}
-
-/* Updates the southbound Datapath_Binding table so that it contains the
- * logical switches and routers specified by the northbound database.
- *
- * Initializes 'datapaths' to contain a "struct ovn_datapath" for every logical
- * switch and router. */
-static void
-build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
-                struct ovs_list *lr_list)
-{
-    struct ovs_list sb_only, nb_only, both;
-
-    join_datapaths(ctx, datapaths, &sb_only, &nb_only, &both, lr_list);
-
-    if (!ovs_list_is_empty(&nb_only)) {
-        /* First index the in-use datapath tunnel IDs. */
-        struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids);
-        struct ovn_datapath *od;
-        LIST_FOR_EACH (od, list, &both) {
-            add_tnlid(&dp_tnlids, od->sb->tunnel_key);
-        }
-
-        /* Add southbound record for each unmatched northbound record. */
-        LIST_FOR_EACH (od, list, &nb_only) {
-            uint16_t tunnel_key = ovn_datapath_allocate_key(&dp_tnlids);
-            if (!tunnel_key) {
-                break;
-            }
-
-            od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn);
-            ovn_datapath_update_external_ids(od);
-            sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key);
-        }
-        destroy_tnlids(&dp_tnlids);
-    }
-
-    /* Delete southbound records without northbound matches. */
-    struct ovn_datapath *od, *next;
-    LIST_FOR_EACH_SAFE (od, next, list, &sb_only) {
-        ovs_list_remove(&od->list);
-        sbrec_datapath_binding_delete(od->sb);
-        ovn_datapath_destroy(datapaths, od);
-    }
-}
-
-struct ovn_port {
-    struct hmap_node key_node;  /* Index on 'key'. */
-    char *key;                  /* nbs->name, nbr->name, sb->logical_port. */
-    char *json_key;             /* 'key', quoted for use in JSON. */
-
-    const struct sbrec_port_binding *sb;         /* May be NULL. */
-
-    /* Logical switch port data. */
-    const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */
-
-    struct lport_addresses *lsp_addrs;  /* Logical switch port addresses. */
-    unsigned int n_lsp_addrs;
-
-    struct lport_addresses *ps_addrs;   /* Port security addresses. */
-    unsigned int n_ps_addrs;
-
-    /* Logical router port data. */
-    const struct nbrec_logical_router_port *nbrp; /* May be NULL. */
-
-    struct lport_addresses lrp_networks;
-
-    bool derived; /* Indicates whether this is an additional port
-                   * derived from nbsp or nbrp. */
-
-    /* The port's peer:
-     *
-     *     - A switch port S of type "router" has a router port R as a peer,
-     *       and R in turn has S has its peer.
-     *
-     *     - Two connected logical router ports have each other as peer. */
-    struct ovn_port *peer;
-
-    struct ovn_datapath *od;
-
-    struct ovs_list list;       /* In list of similar records. */
-};
-
-static struct ovn_port *
-ovn_port_create(struct hmap *ports, const char *key,
-                const struct nbrec_logical_switch_port *nbsp,
-                const struct nbrec_logical_router_port *nbrp,
-                const struct sbrec_port_binding *sb)
-{
-    struct ovn_port *op = xzalloc(sizeof *op);
-
-    struct ds json_key = DS_EMPTY_INITIALIZER;
-    json_string_escape(key, &json_key);
-    op->json_key = ds_steal_cstr(&json_key);
-
-    op->key = xstrdup(key);
-    op->sb = sb;
-    op->nbsp = nbsp;
-    op->nbrp = nbrp;
-    op->derived = false;
-    hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
-    return op;
-}
-
-static void
-ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
-{
-    if (port) {
-        /* Don't remove port->list.  It is used within build_ports() as a
-         * private list and once we've exited that function it is not safe to
-         * use it. */
-        hmap_remove(ports, &port->key_node);
-
-        for (int i = 0; i < port->n_lsp_addrs; i++) {
-            destroy_lport_addresses(&port->lsp_addrs[i]);
-        }
-        free(port->lsp_addrs);
-
-        for (int i = 0; i < port->n_ps_addrs; i++) {
-            destroy_lport_addresses(&port->ps_addrs[i]);
-        }
-        free(port->ps_addrs);
-
-        destroy_lport_addresses(&port->lrp_networks);
-        free(port->json_key);
-        free(port->key);
-        free(port);
-    }
-}
-
-static struct ovn_port *
-ovn_port_find(const struct hmap *ports, const char *name)
-{
-    struct ovn_port *op;
-
-    HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) {
-        if (!strcmp(op->key, name)) {
-            return op;
-        }
-    }
-    return NULL;
-}
-
-static uint32_t
-ovn_port_allocate_key(struct ovn_datapath *od)
-{
-    return allocate_tnlid(&od->port_tnlids, "port",
-                          1, (1u << 15) - 1, &od->port_key_hint);
-}
-
-static char *
-chassis_redirect_name(const char *port_name)
-{
-    return xasprintf("cr-%s", port_name);
-}
-
-static bool
-ipam_is_duplicate_mac(struct eth_addr *ea, uint64_t mac64, bool warn)
-{
-    struct macam_node *macam_node;
-    HMAP_FOR_EACH_WITH_HASH (macam_node, hmap_node, hash_uint64(mac64),
-                             &macam) {
-        if (eth_addr_equals(*ea, macam_node->mac_addr)) {
-            if (warn) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_WARN_RL(&rl, "Duplicate MAC set: "ETH_ADDR_FMT,
-                             ETH_ADDR_ARGS(macam_node->mac_addr));
-            }
-            return true;
-        }
-    }
-    return false;
-}
-
-static void
-ipam_insert_mac(struct eth_addr *ea, bool check)
-{
-    if (!ea) {
-        return;
-    }
-
-    uint64_t mac64 = eth_addr_to_uint64(*ea);
-    uint64_t prefix = eth_addr_to_uint64(mac_prefix);
-
-    /* If the new MAC was not assigned by this address management system or
-     * check is true and the new MAC is a duplicate, do not insert it into the
-     * macam hmap. */
-    if (((mac64 ^ prefix) >> 24)
-        || (check && ipam_is_duplicate_mac(ea, mac64, true))) {
-        return;
-    }
-
-    struct macam_node *new_macam_node = xmalloc(sizeof *new_macam_node);
-    new_macam_node->mac_addr = *ea;
-    hmap_insert(&macam, &new_macam_node->hmap_node, hash_uint64(mac64));
-}
-
-static void
-ipam_insert_ip(struct ovn_datapath *od, uint32_t ip)
-{
-    if (!od || !od->ipam_info.allocated_ipv4s) {
-        return;
-    }
-
-    if (ip >= od->ipam_info.start_ipv4 &&
-        ip < (od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s)) {
-        if (bitmap_is_set(od->ipam_info.allocated_ipv4s,
-                          ip - od->ipam_info.start_ipv4)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl, "Duplicate IP set on switch %s: "IP_FMT,
-                         od->nbs->name, IP_ARGS(htonl(ip)));
-        }
-        bitmap_set1(od->ipam_info.allocated_ipv4s,
-                    ip - od->ipam_info.start_ipv4);
-    }
-}
-
-static void
-ipam_insert_lsp_addresses(struct ovn_datapath *od, struct ovn_port *op,
-                          char *address)
-{
-    if (!od || !op || !address || !strcmp(address, "unknown")
-        || !strcmp(address, "router") || is_dynamic_lsp_address(address)) {
-        return;
-    }
-
-    struct lport_addresses laddrs;
-    if (!extract_lsp_addresses(address, &laddrs)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "Extract addresses failed.");
-        return;
-    }
-    ipam_insert_mac(&laddrs.ea, true);
-
-    /* IP is only added to IPAM if the switch's subnet option
-     * is set, whereas MAC is always added to MACAM. */
-    if (!od->ipam_info.allocated_ipv4s) {
-        destroy_lport_addresses(&laddrs);
-        return;
-    }
-
-    for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
-        uint32_t ip = ntohl(laddrs.ipv4_addrs[j].addr);
-        ipam_insert_ip(od, ip);
-    }
-
-    destroy_lport_addresses(&laddrs);
-}
-
-static void
-ipam_add_port_addresses(struct ovn_datapath *od, struct ovn_port *op)
-{
-    if (!od || !op) {
-        return;
-    }
-
-    if (op->nbsp) {
-        /* Add all the port's addresses to address data structures. */
-        for (size_t i = 0; i < op->nbsp->n_addresses; i++) {
-            ipam_insert_lsp_addresses(od, op, op->nbsp->addresses[i]);
-        }
-    } else if (op->nbrp) {
-        struct lport_addresses lrp_networks;
-        if (!extract_lrp_networks(op->nbrp, &lrp_networks)) {
-            static struct vlog_rate_limit rl
-                = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl, "Extract addresses failed.");
-            return;
-        }
-        ipam_insert_mac(&lrp_networks.ea, true);
-
-        if (!op->peer || !op->peer->nbsp || !op->peer->od || !op->peer->od->nbs
-            || !smap_get(&op->peer->od->nbs->other_config, "subnet")) {
-            destroy_lport_addresses(&lrp_networks);
-            return;
-        }
-
-        for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) {
-            uint32_t ip = ntohl(lrp_networks.ipv4_addrs[i].addr);
-            ipam_insert_ip(op->peer->od, ip);
-        }
-
-        destroy_lport_addresses(&lrp_networks);
-    }
-}
-
-static uint64_t
-ipam_get_unused_mac(ovs_be32 ip)
-{
-    uint32_t mac_addr_suffix, i, base_addr = ntohl(ip) & MAC_ADDR_SPACE;
-    struct eth_addr mac;
-    uint64_t mac64;
-
-    for (i = 0; i < MAC_ADDR_SPACE - 1; i++) {
-        /* The tentative MAC's suffix will be in the interval (1, 0xfffffe). */
-        mac_addr_suffix = ((base_addr + i) % (MAC_ADDR_SPACE - 1)) + 1;
-        mac64 =  eth_addr_to_uint64(mac_prefix) | mac_addr_suffix;
-        eth_addr_from_uint64(mac64, &mac);
-        if (!ipam_is_duplicate_mac(&mac, mac64, true)) {
-            break;
-        }
-    }
-
-    if (i == MAC_ADDR_SPACE) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "MAC address space exhausted.");
-        mac64 = 0;
-    }
-
-    return mac64;
-}
-
-static uint32_t
-ipam_get_unused_ip(struct ovn_datapath *od)
-{
-    if (!od || !od->ipam_info.allocated_ipv4s) {
-        return 0;
-    }
-
-    size_t new_ip_index = bitmap_scan(od->ipam_info.allocated_ipv4s, 0, 0,
-                                      od->ipam_info.total_ipv4s - 1);
-    if (new_ip_index == od->ipam_info.total_ipv4s - 1) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL( &rl, "Subnet address space has been exhausted.");
-        return 0;
-    }
-
-    return od->ipam_info.start_ipv4 + new_ip_index;
-}
-
-enum dynamic_update_type {
-    NONE,    /* No change to the address */
-    REMOVE,  /* Address is no longer dynamic */
-    STATIC,  /* Use static address (MAC only) */
-    DYNAMIC, /* Assign a new dynamic address */
-};
-
-struct dynamic_address_update {
-    struct ovs_list node;       /* In build_ipam()'s list of updates. */
-
-    struct ovn_datapath *od;
-    struct ovn_port *op;
-
-    struct lport_addresses current_addresses;
-    struct eth_addr static_mac;
-    ovs_be32 static_ip;
-    struct in6_addr static_ipv6;
-    enum dynamic_update_type mac;
-    enum dynamic_update_type ipv4;
-    enum dynamic_update_type ipv6;
-};
-
-static enum dynamic_update_type
-dynamic_mac_changed(const char *lsp_addresses,
-                    struct dynamic_address_update *update)
-{
-   struct eth_addr ea;
-
-   if (ovs_scan(lsp_addresses, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) {
-       if (eth_addr_equals(ea, update->current_addresses.ea)) {
-           return NONE;
-       } else {
-           /* MAC is still static, but it has changed */
-           update->static_mac = ea;
-           return STATIC;
-       }
-   }
-
-   uint64_t mac64 = eth_addr_to_uint64(update->current_addresses.ea);
-   uint64_t prefix = eth_addr_to_uint64(mac_prefix);
-
-   if ((mac64 ^ prefix) >> 24) {
-       return DYNAMIC;
-   } else {
-       return NONE;
-   }
-}
-
-static enum dynamic_update_type
-dynamic_ip4_changed(const char *lsp_addrs,
-                    struct dynamic_address_update *update)
-{
-    const struct ipam_info *ipam = &update->op->od->ipam_info;
-    const struct lport_addresses *cur_addresses = &update->current_addresses;
-    bool dynamic_ip4 = ipam->allocated_ipv4s != NULL;
-
-    if (!dynamic_ip4) {
-        if (update->current_addresses.n_ipv4_addrs) {
-            return REMOVE;
-        } else {
-            return NONE;
-        }
-    }
-
-    if (!cur_addresses->n_ipv4_addrs) {
-        /* IPv4 was previously static but now is dynamic */
-        return DYNAMIC;
-    }
-
-    uint32_t ip4 = ntohl(cur_addresses->ipv4_addrs[0].addr);
-    if (ip4 < ipam->start_ipv4) {
-        return DYNAMIC;
-    }
-
-    uint32_t index = ip4 - ipam->start_ipv4;
-    if (index > ipam->total_ipv4s ||
-        bitmap_is_set(ipam->allocated_ipv4s, index)) {
-        /* Previously assigned dynamic IPv4 address can no longer be used.
-         * It's either outside the subnet, conflicts with an excluded IP,
-         * or conflicts with a statically-assigned address on the switch
-         */
-        return DYNAMIC;
-    } else {
-        char ipv6_s[IPV6_SCAN_LEN + 1];
-        ovs_be32 new_ip;
-        int n = 0;
-
-        if ((ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT"%n",
-                     IP_SCAN_ARGS(&new_ip), &n)
-             && lsp_addrs[n] == '\0') ||
-            (ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
-                      IP_SCAN_ARGS(&new_ip), ipv6_s, &n)
-             && lsp_addrs[n] == '\0')) {
-            index = ntohl(new_ip) - ipam->start_ipv4;
-            if (ntohl(new_ip) < ipam->start_ipv4 ||
-                index > ipam->total_ipv4s ||
-                bitmap_is_set(ipam->allocated_ipv4s, index)) {
-                /* new static ip is not valid */
-                return DYNAMIC;
-            } else if (cur_addresses->ipv4_addrs[0].addr != new_ip) {
-                update->ipv4 = STATIC;
-                update->static_ip = new_ip;
-                return STATIC;
-            }
-        }
-        return NONE;
-    }
-}
-
-static enum dynamic_update_type
-dynamic_ip6_changed(const char *lsp_addrs,
-                    struct dynamic_address_update *update)
-{
-    bool dynamic_ip6 = update->op->od->ipam_info.ipv6_prefix_set;
-    struct eth_addr ea;
-
-    if (!dynamic_ip6) {
-        if (update->current_addresses.n_ipv6_addrs) {
-            /* IPv6 was dynamic but now is not */
-            return REMOVE;
-        } else {
-            /* IPv6 has never been dynamic */
-            return NONE;
-        }
-    }
-
-    if (!update->current_addresses.n_ipv6_addrs ||
-        ovs_scan(lsp_addrs, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) {
-        /* IPv6 was previously static but now is dynamic */
-        return DYNAMIC;
-    }
-
-    const struct lport_addresses *cur_addresses;
-    char ipv6_s[IPV6_SCAN_LEN + 1];
-    ovs_be32 new_ip;
-    int n = 0;
-
-    if ((ovs_scan(lsp_addrs, "dynamic "IPV6_SCAN_FMT"%n",
-                  ipv6_s, &n) && lsp_addrs[n] == '\0') ||
-        (ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
-                  IP_SCAN_ARGS(&new_ip), ipv6_s, &n)
-         && lsp_addrs[n] == '\0')) {
-        struct in6_addr ipv6;
-
-        if (!ipv6_parse(ipv6_s, &ipv6)) {
-            return DYNAMIC;
-        }
-
-        struct in6_addr masked = ipv6_addr_bitand(&ipv6,
-                &update->op->od->ipam_info.ipv6_prefix);
-        if (!IN6_ARE_ADDR_EQUAL(&masked,
-                                &update->op->od->ipam_info.ipv6_prefix)) {
-            return DYNAMIC;
-        }
-
-        cur_addresses = &update->current_addresses;
-
-        if (!IN6_ARE_ADDR_EQUAL(&cur_addresses->ipv6_addrs[0].addr,
-                                &ipv6)) {
-            update->static_ipv6 = ipv6;
-            return STATIC;
-        }
-    } else if (update->mac != NONE) {
-        return DYNAMIC;
-    }
-
-    return NONE;
-}
-
-/* Check previously assigned dynamic addresses for validity. This will
- * check if the assigned addresses need to change.
- *
- * Returns true if any changes to dynamic addresses are required
- */
-static bool
-dynamic_addresses_check_for_updates(const char *lsp_addrs,
-                                    struct dynamic_address_update *update)
-{
-    update->mac = dynamic_mac_changed(lsp_addrs, update);
-    update->ipv4 = dynamic_ip4_changed(lsp_addrs, update);
-    update->ipv6 = dynamic_ip6_changed(lsp_addrs, update);
-    if (update->mac == NONE &&
-        update->ipv4 == NONE &&
-        update->ipv6 == NONE) {
-        return false;
-    } else {
-        return true;
-    }
-}
-
-/* For addresses that do not need to be updated, go ahead and insert them
- * into IPAM. This way, their addresses will be claimed and cannot be assigned
- * elsewhere later.
- */
-static void
-update_unchanged_dynamic_addresses(struct dynamic_address_update *update)
-{
-    if (update->mac == NONE) {
-        ipam_insert_mac(&update->current_addresses.ea, false);
-    }
-    if (update->ipv4 == NONE && update->current_addresses.n_ipv4_addrs) {
-        ipam_insert_ip(update->op->od,
-                       ntohl(update->current_addresses.ipv4_addrs[0].addr));
-    }
-}
-
-static void
-set_lsp_dynamic_addresses(const char *dynamic_addresses, struct ovn_port *op)
-{
-    extract_lsp_addresses(dynamic_addresses, &op->lsp_addrs[op->n_lsp_addrs]);
-    op->n_lsp_addrs++;
-}
-
-/* Determines which components (MAC, IPv4, and IPv6) of dynamic
- * addresses need to be assigned. This is used exclusively for
- * ports that do not have dynamic addresses already assigned.
- */
-static void
-set_dynamic_updates(const char *addrspec,
-                    struct dynamic_address_update *update)
-{
-    bool has_ipv4 = false, has_ipv6 = false;
-    char ipv6_s[IPV6_SCAN_LEN + 1];
-    struct eth_addr mac;
-    ovs_be32 ip;
-    int n = 0;
-    if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n",
-                 ETH_ADDR_SCAN_ARGS(mac), &n)
-        && addrspec[n] == '\0') {
-        update->mac = STATIC;
-        update->static_mac = mac;
-    } else {
-        update->mac = DYNAMIC;
-    }
-
-    if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT"%n",
-                 IP_SCAN_ARGS(&ip), &n) && addrspec[n] == '\0')) {
-        has_ipv4 = true;
-    } else if ((ovs_scan(addrspec, "dynamic "IPV6_SCAN_FMT"%n",
-                         ipv6_s, &n) && addrspec[n] == '\0')) {
-        has_ipv6 = true;
-    } else if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
-                         IP_SCAN_ARGS(&ip), ipv6_s, &n)
-               && addrspec[n] == '\0')) {
-        has_ipv4 = has_ipv6 = true;
-    }
-
-    if (has_ipv4) {
-        update->ipv4 = STATIC;
-        update->static_ip = ip;
-    } else if (update->op->od->ipam_info.allocated_ipv4s) {
-        update->ipv4 = DYNAMIC;
-    } else {
-        update->ipv4 = NONE;
-    }
-
-    if (has_ipv6 && ipv6_parse(ipv6_s, &update->static_ipv6)) {
-        update->ipv6 = STATIC;
-    } else if (update->op->od->ipam_info.ipv6_prefix_set) {
-        update->ipv6 = DYNAMIC;
-    } else {
-        update->ipv6 = NONE;
-    }
-}
-
-static void
-update_dynamic_addresses(struct dynamic_address_update *update)
-{
-    ovs_be32 ip4 = 0;
-    switch (update->ipv4) {
-    case NONE:
-        if (update->current_addresses.n_ipv4_addrs) {
-            ip4 = update->current_addresses.ipv4_addrs[0].addr;
-        }
-        break;
-    case REMOVE:
-        break;
-    case STATIC:
-        ip4 = update->static_ip;
-        break;
-    case DYNAMIC:
-        ip4 = htonl(ipam_get_unused_ip(update->od));
-    }
-
-    struct eth_addr mac;
-    switch (update->mac) {
-    case NONE:
-        mac = update->current_addresses.ea;
-        break;
-    case REMOVE:
-        OVS_NOT_REACHED();
-    case STATIC:
-        mac = update->static_mac;
-        break;
-    case DYNAMIC:
-        eth_addr_from_uint64(ipam_get_unused_mac(ip4), &mac);
-        break;
-    }
-
-    struct in6_addr ip6 = in6addr_any;
-    switch (update->ipv6) {
-    case NONE:
-        if (update->current_addresses.n_ipv6_addrs) {
-            ip6 = update->current_addresses.ipv6_addrs[0].addr;
-        }
-        break;
-    case REMOVE:
-        break;
-    case STATIC:
-        ip6 = update->static_ipv6;
-        break;
-    case DYNAMIC:
-        in6_generate_eui64(mac, &update->od->ipam_info.ipv6_prefix, &ip6);
-        break;
-    }
-
-    struct ds new_addr = DS_EMPTY_INITIALIZER;
-    ds_put_format(&new_addr, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
-    ipam_insert_mac(&mac, true);
-
-    if (ip4) {
-        ipam_insert_ip(update->od, ntohl(ip4));
-        ds_put_format(&new_addr, " "IP_FMT, IP_ARGS(ip4));
-    }
-    if (!IN6_ARE_ADDR_EQUAL(&ip6, &in6addr_any)) {
-        char ip6_s[INET6_ADDRSTRLEN + 1];
-        ipv6_string_mapped(ip6_s, &ip6);
-        ds_put_format(&new_addr, " %s", ip6_s);
-    }
-    nbrec_logical_switch_port_set_dynamic_addresses(update->op->nbsp,
-                                                    ds_cstr(&new_addr));
-    set_lsp_dynamic_addresses(ds_cstr(&new_addr), update->op);
-    ds_destroy(&new_addr);
-}
-
-static void
-build_ipam(struct hmap *datapaths, struct hmap *ports)
-{
-    /* IPAM generally stands for IP address management.  In non-virtualized
-     * world, MAC addresses come with the hardware.  But, with virtualized
-     * workloads, they need to be assigned and managed.  This function
-     * does both IP address management (ipam) and MAC address management
-     * (macam). */
-
-    /* If the switch's other_config:subnet is set, allocate new addresses for
-     * ports that have the "dynamic" keyword in their addresses column. */
-    struct ovn_datapath *od;
-    struct ovs_list updates;
-
-    ovs_list_init(&updates);
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        for (size_t i = 0; i < od->nbs->n_ports; i++) {
-            const struct nbrec_logical_switch_port *nbsp = od->nbs->ports[i];
-
-            if (!od->ipam_info.allocated_ipv4s &&
-                !od->ipam_info.ipv6_prefix_set &&
-                !od->ipam_info.mac_only) {
-                if (nbsp->dynamic_addresses) {
-                    nbrec_logical_switch_port_set_dynamic_addresses(nbsp,
-                                                                    NULL);
-                }
-                continue;
-            }
-
-            struct ovn_port *op = ovn_port_find(ports, nbsp->name);
-            if (!op || op->nbsp != nbsp || op->peer) {
-                /* Do not allocate addresses for logical switch ports that
-                 * have a peer. */
-                continue;
-            }
-
-            int num_dynamic_addresses = 0;
-            for (size_t j = 0; j < nbsp->n_addresses; j++) {
-                if (!is_dynamic_lsp_address(nbsp->addresses[j])) {
-                    continue;
-                }
-                if (num_dynamic_addresses) {
-                    static struct vlog_rate_limit rl
-                        = VLOG_RATE_LIMIT_INIT(1, 1);
-                    VLOG_WARN_RL(&rl, "More than one dynamic address "
-                                 "configured for logical switch port '%s'",
-                                 nbsp->name);
-                    continue;
-                }
-                num_dynamic_addresses++;
-                struct dynamic_address_update *update
-                    = xzalloc(sizeof *update);
-                update->op = op;
-                update->od = od;
-                if (nbsp->dynamic_addresses) {
-                    bool any_changed;
-                    extract_lsp_addresses(nbsp->dynamic_addresses,
-                                          &update->current_addresses);
-                    any_changed = dynamic_addresses_check_for_updates(
-                        nbsp->addresses[j], update);
-                    update_unchanged_dynamic_addresses(update);
-                    if (any_changed) {
-                        ovs_list_push_back(&updates, &update->node);
-                    } else {
-                        /* No changes to dynamic addresses */
-                        set_lsp_dynamic_addresses(nbsp->dynamic_addresses, op);
-                        destroy_lport_addresses(&update->current_addresses);
-                        free(update);
-                    }
-                } else {
-                    set_dynamic_updates(nbsp->addresses[j], update);
-                    ovs_list_push_back(&updates, &update->node);
-                }
-            }
-
-            if (!num_dynamic_addresses && nbsp->dynamic_addresses) {
-                nbrec_logical_switch_port_set_dynamic_addresses(nbsp, NULL);
-            }
-        }
-
-    }
-
-    /* After retaining all unchanged dynamic addresses, now assign
-     * new ones.
-     */
-    struct dynamic_address_update *update;
-    LIST_FOR_EACH_POP (update, node, &updates) {
-        update_dynamic_addresses(update);
-        destroy_lport_addresses(&update->current_addresses);
-        free(update);
-    }
-}
-
-/* Tag allocation for nested containers.
- *
- * For a logical switch port with 'parent_name' and a request to allocate tags,
- * keeps a track of all allocated tags. */
-struct tag_alloc_node {
-    struct hmap_node hmap_node;
-    char *parent_name;
-    unsigned long *allocated_tags;  /* A bitmap to track allocated tags. */
-};
-
-static void
-tag_alloc_destroy(struct hmap *tag_alloc_table)
-{
-    struct tag_alloc_node *node;
-    HMAP_FOR_EACH_POP (node, hmap_node, tag_alloc_table) {
-        bitmap_free(node->allocated_tags);
-        free(node->parent_name);
-        free(node);
-    }
-    hmap_destroy(tag_alloc_table);
-}
-
-static struct tag_alloc_node *
-tag_alloc_get_node(struct hmap *tag_alloc_table, const char *parent_name)
-{
-    /* If a node for the 'parent_name' exists, return it. */
-    struct tag_alloc_node *tag_alloc_node;
-    HMAP_FOR_EACH_WITH_HASH (tag_alloc_node, hmap_node,
-                             hash_string(parent_name, 0),
-                             tag_alloc_table) {
-        if (!strcmp(tag_alloc_node->parent_name, parent_name)) {
-            return tag_alloc_node;
-        }
-    }
-
-    /* Create a new node. */
-    tag_alloc_node = xmalloc(sizeof *tag_alloc_node);
-    tag_alloc_node->parent_name = xstrdup(parent_name);
-    tag_alloc_node->allocated_tags = bitmap_allocate(MAX_OVN_TAGS);
-    /* Tag 0 is invalid for nested containers. */
-    bitmap_set1(tag_alloc_node->allocated_tags, 0);
-    hmap_insert(tag_alloc_table, &tag_alloc_node->hmap_node,
-                hash_string(parent_name, 0));
-
-    return tag_alloc_node;
-}
-
-static void
-tag_alloc_add_existing_tags(struct hmap *tag_alloc_table,
-                            const struct nbrec_logical_switch_port *nbsp)
-{
-    /* Add the tags of already existing nested containers.  If there is no
-     * 'nbsp->parent_name' or no 'nbsp->tag' set, there is nothing to do. */
-    if (!nbsp->parent_name || !nbsp->parent_name[0] || !nbsp->tag) {
-        return;
-    }
-
-    struct tag_alloc_node *tag_alloc_node;
-    tag_alloc_node = tag_alloc_get_node(tag_alloc_table, nbsp->parent_name);
-    bitmap_set1(tag_alloc_node->allocated_tags, *nbsp->tag);
-}
-
-static void
-tag_alloc_create_new_tag(struct hmap *tag_alloc_table,
-                         const struct nbrec_logical_switch_port *nbsp)
-{
-    if (!nbsp->tag_request) {
-        return;
-    }
-
-    if (nbsp->parent_name && nbsp->parent_name[0]
-        && *nbsp->tag_request == 0) {
-        /* For nested containers that need allocation, do the allocation. */
-
-        if (nbsp->tag) {
-            /* This has already been allocated. */
-            return;
-        }
-
-        struct tag_alloc_node *tag_alloc_node;
-        int64_t tag;
-        tag_alloc_node = tag_alloc_get_node(tag_alloc_table,
-                                            nbsp->parent_name);
-        tag = bitmap_scan(tag_alloc_node->allocated_tags, 0, 1, MAX_OVN_TAGS);
-        if (tag == MAX_OVN_TAGS) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_ERR_RL(&rl, "out of vlans for logical switch ports with "
-                        "parent %s", nbsp->parent_name);
-            return;
-        }
-        bitmap_set1(tag_alloc_node->allocated_tags, tag);
-        nbrec_logical_switch_port_set_tag(nbsp, &tag, 1);
-    } else if (*nbsp->tag_request != 0) {
-        /* For everything else, copy the contents of 'tag_request' to 'tag'. */
-        nbrec_logical_switch_port_set_tag(nbsp, nbsp->tag_request, 1);
-    }
-}
-
-
-static void
-join_logical_ports(struct northd_context *ctx,
-                   struct hmap *datapaths, struct hmap *ports,
-                   struct hmap *chassis_qdisc_queues,
-                   struct hmap *tag_alloc_table, struct ovs_list *sb_only,
-                   struct ovs_list *nb_only, struct ovs_list *both)
-{
-    ovs_list_init(sb_only);
-    ovs_list_init(nb_only);
-    ovs_list_init(both);
-
-    const struct sbrec_port_binding *sb;
-    SBREC_PORT_BINDING_FOR_EACH (sb, ctx->ovnsb_idl) {
-        struct ovn_port *op = ovn_port_create(ports, sb->logical_port,
-                                              NULL, NULL, sb);
-        ovs_list_push_back(sb_only, &op->list);
-    }
-
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (od->nbs) {
-            for (size_t i = 0; i < od->nbs->n_ports; i++) {
-                const struct nbrec_logical_switch_port *nbsp
-                    = od->nbs->ports[i];
-                struct ovn_port *op = ovn_port_find(ports, nbsp->name);
-                if (op) {
-                    if (op->nbsp || op->nbrp) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(5, 1);
-                        VLOG_WARN_RL(&rl, "duplicate logical port %s",
-                                     nbsp->name);
-                        continue;
-                    }
-                    op->nbsp = nbsp;
-                    ovs_list_remove(&op->list);
-
-                    uint32_t queue_id = smap_get_int(&op->sb->options,
-                                                     "qdisc_queue_id", 0);
-                    if (queue_id && op->sb->chassis) {
-                        add_chassis_queue(
-                             chassis_qdisc_queues, &op->sb->chassis->header_.uuid,
-                             queue_id);
-                    }
-
-                    ovs_list_push_back(both, &op->list);
-
-                    /* This port exists due to a SB binding, but should
-                     * not have been initialized fully. */
-                    ovs_assert(!op->n_lsp_addrs && !op->n_ps_addrs);
-                } else {
-                    op = ovn_port_create(ports, nbsp->name, nbsp, NULL, NULL);
-                    ovs_list_push_back(nb_only, &op->list);
-                }
-
-                if (!strcmp(nbsp->type, "localnet")) {
-                   od->localnet_port = op;
-                }
-
-                op->lsp_addrs
-                    = xmalloc(sizeof *op->lsp_addrs * nbsp->n_addresses);
-                for (size_t j = 0; j < nbsp->n_addresses; j++) {
-                    if (!strcmp(nbsp->addresses[j], "unknown")
-                        || !strcmp(nbsp->addresses[j], "router")) {
-                        continue;
-                    }
-                    if (is_dynamic_lsp_address(nbsp->addresses[j])) {
-                        continue;
-                    } else if (!extract_lsp_addresses(nbsp->addresses[j],
-                                           &op->lsp_addrs[op->n_lsp_addrs])) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(1, 1);
-                        VLOG_INFO_RL(&rl, "invalid syntax '%s' in logical "
-                                          "switch port addresses. No MAC "
-                                          "address found",
-                                          op->nbsp->addresses[j]);
-                        continue;
-                    }
-                    op->n_lsp_addrs++;
-                }
-
-                op->ps_addrs
-                    = xmalloc(sizeof *op->ps_addrs * nbsp->n_port_security);
-                for (size_t j = 0; j < nbsp->n_port_security; j++) {
-                    if (!extract_lsp_addresses(nbsp->port_security[j],
-                                               &op->ps_addrs[op->n_ps_addrs])) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(1, 1);
-                        VLOG_INFO_RL(&rl, "invalid syntax '%s' in port "
-                                          "security. No MAC address found",
-                                          op->nbsp->port_security[j]);
-                        continue;
-                    }
-                    op->n_ps_addrs++;
-                }
-
-                op->od = od;
-                tag_alloc_add_existing_tags(tag_alloc_table, nbsp);
-            }
-        } else {
-            for (size_t i = 0; i < od->nbr->n_ports; i++) {
-                const struct nbrec_logical_router_port *nbrp
-                    = od->nbr->ports[i];
-
-                struct lport_addresses lrp_networks;
-                if (!extract_lrp_networks(nbrp, &lrp_networks)) {
-                    static struct vlog_rate_limit rl
-                        = VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad 'mac' %s", nbrp->mac);
-                    continue;
-                }
-
-                if (!lrp_networks.n_ipv4_addrs && !lrp_networks.n_ipv6_addrs) {
-                    continue;
-                }
-
-                struct ovn_port *op = ovn_port_find(ports, nbrp->name);
-                if (op) {
-                    if (op->nbsp || op->nbrp) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(5, 1);
-                        VLOG_WARN_RL(&rl, "duplicate logical router port %s",
-                                     nbrp->name);
-                        continue;
-                    }
-                    op->nbrp = nbrp;
-                    ovs_list_remove(&op->list);
-                    ovs_list_push_back(both, &op->list);
-
-                    /* This port exists but should not have been
-                     * initialized fully. */
-                    ovs_assert(!op->lrp_networks.n_ipv4_addrs
-                               && !op->lrp_networks.n_ipv6_addrs);
-                } else {
-                    op = ovn_port_create(ports, nbrp->name, NULL, nbrp, NULL);
-                    ovs_list_push_back(nb_only, &op->list);
-                }
-
-                op->lrp_networks = lrp_networks;
-                op->od = od;
-
-                const char *redirect_chassis = smap_get(&op->nbrp->options,
-                                                        "redirect-chassis");
-                if (op->nbrp->ha_chassis_group || redirect_chassis ||
-                    op->nbrp->n_gateway_chassis) {
-                    /* Additional "derived" ovn_port crp represents the
-                     * instance of op on the "redirect-chassis". */
-                    const char *gw_chassis = smap_get(&op->od->nbr->options,
-                                                   "chassis");
-                    if (gw_chassis) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(1, 1);
-                        VLOG_WARN_RL(&rl, "Bad configuration: "
-                                     "redirect-chassis configured on port %s "
-                                     "on L3 gateway router", nbrp->name);
-                        continue;
-                    }
-                    if (od->l3dgw_port || od->l3redirect_port) {
-                        static struct vlog_rate_limit rl
-                            = VLOG_RATE_LIMIT_INIT(1, 1);
-                        VLOG_WARN_RL(&rl, "Bad configuration: multiple ports "
-                                     "with redirect-chassis on same logical "
-                                     "router %s", od->nbr->name);
-                        continue;
-                    }
-
-                    char *redirect_name = chassis_redirect_name(nbrp->name);
-                    struct ovn_port *crp = ovn_port_find(ports, redirect_name);
-                    if (crp) {
-                        crp->derived = true;
-                        crp->nbrp = nbrp;
-                        ovs_list_remove(&crp->list);
-                        ovs_list_push_back(both, &crp->list);
-                    } else {
-                        crp = ovn_port_create(ports, redirect_name,
-                                              NULL, nbrp, NULL);
-                        crp->derived = true;
-                        ovs_list_push_back(nb_only, &crp->list);
-                    }
-                    crp->od = od;
-                    free(redirect_name);
-
-                    /* Set l3dgw_port and l3redirect_port in od, for later
-                     * use during flow creation. */
-                    od->l3dgw_port = op;
-                    od->l3redirect_port = crp;
-                }
-            }
-        }
-    }
-
-    /* Connect logical router ports, and logical switch ports of type "router",
-     * to their peers. */
-    struct ovn_port *op;
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (op->nbsp && !strcmp(op->nbsp->type, "router") && !op->derived) {
-            const char *peer_name = smap_get(&op->nbsp->options, "router-port");
-            if (!peer_name) {
-                continue;
-            }
-
-            struct ovn_port *peer = ovn_port_find(ports, peer_name);
-            if (!peer || !peer->nbrp) {
-                continue;
-            }
-
-            peer->peer = op;
-            op->peer = peer;
-            op->od->router_ports = xrealloc(
-                op->od->router_ports,
-                sizeof *op->od->router_ports * (op->od->n_router_ports + 1));
-            op->od->router_ports[op->od->n_router_ports++] = op;
-
-            /* Fill op->lsp_addrs for op->nbsp->addresses[] with
-             * contents "router", which was skipped in the loop above. */
-            for (size_t j = 0; j < op->nbsp->n_addresses; j++) {
-                if (!strcmp(op->nbsp->addresses[j], "router")) {
-                    if (extract_lrp_networks(peer->nbrp,
-                                            &op->lsp_addrs[op->n_lsp_addrs])) {
-                        op->n_lsp_addrs++;
-                    }
-                    break;
-                }
-            }
-        } else if (op->nbrp && op->nbrp->peer && !op->derived) {
-            struct ovn_port *peer = ovn_port_find(ports, op->nbrp->peer);
-            if (peer) {
-                if (peer->nbrp) {
-                    op->peer = peer;
-                } else if (peer->nbsp) {
-                    /* An ovn_port for a switch port of type "router" does have
-                     * a router port as its peer (see the case above for
-                     * "router" ports), but this is set via options:router-port
-                     * in Logical_Switch_Port and does not involve the
-                     * Logical_Router_Port's 'peer' column. */
-                    static struct vlog_rate_limit rl =
-                            VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "Bad configuration: The peer of router "
-                                 "port %s is a switch port", op->key);
-                }
-            }
-        }
-    }
-
-    /* Wait until all ports have been connected to add to IPAM since
-     * it relies on proper peers to be set
-     */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        ipam_add_port_addresses(op->od, op);
-    }
-}
-
-static void
-ip_address_and_port_from_lb_key(const char *key, char **ip_address,
-                                uint16_t *port, int *addr_family);
-
-static void
-get_router_load_balancer_ips(const struct ovn_datapath *od,
-                             struct sset *all_ips, int *addr_family)
-{
-    if (!od->nbr) {
-        return;
-    }
-
-    for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-        struct nbrec_load_balancer *lb = od->nbr->load_balancer[i];
-        struct smap *vips = &lb->vips;
-        struct smap_node *node;
-
-        SMAP_FOR_EACH (node, vips) {
-            /* node->key contains IP:port or just IP. */
-            char *ip_address = NULL;
-            uint16_t port;
-
-            ip_address_and_port_from_lb_key(node->key, &ip_address, &port,
-                                            addr_family);
-            if (!ip_address) {
-                continue;
-            }
-
-            if (!sset_contains(all_ips, ip_address)) {
-                sset_add(all_ips, ip_address);
-            }
-
-            free(ip_address);
-        }
-    }
-}
-
-/* Returns an array of strings, each consisting of a MAC address followed
- * by one or more IP addresses, and if the port is a distributed gateway
- * port, followed by 'is_chassis_resident("LPORT_NAME")', where the
- * LPORT_NAME is the name of the L3 redirect port or the name of the
- * logical_port specified in a NAT rule.  These strings include the
- * external IP addresses of all NAT rules defined on that router, and all
- * of the IP addresses used in load balancer VIPs defined on that router.
- *
- * The caller must free each of the n returned strings with free(),
- * and must free the returned array when it is no longer needed. */
-static char **
-get_nat_addresses(const struct ovn_port *op, size_t *n)
-{
-    size_t n_nats = 0;
-    struct eth_addr mac;
-    if (!op->nbrp || !op->od || !op->od->nbr
-        || (!op->od->nbr->n_nat && !op->od->nbr->n_load_balancer)
-        || !eth_addr_from_string(op->nbrp->mac, &mac)) {
-        *n = n_nats;
-        return NULL;
-    }
-
-    struct ds c_addresses = DS_EMPTY_INITIALIZER;
-    ds_put_format(&c_addresses, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
-    bool central_ip_address = false;
-
-    char **addresses;
-    addresses = xmalloc(sizeof *addresses * (op->od->nbr->n_nat + 1));
-
-    /* Get NAT IP addresses. */
-    for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-        const struct nbrec_nat *nat = op->od->nbr->nat[i];
-        ovs_be32 ip, mask;
-
-        char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-        if (error || mask != OVS_BE32_MAX) {
-            free(error);
-            continue;
-        }
-
-        /* Determine whether this NAT rule satisfies the conditions for
-         * distributed NAT processing. */
-        if (op->od->l3redirect_port && !strcmp(nat->type, "dnat_and_snat")
-            && nat->logical_port && nat->external_mac) {
-            /* Distributed NAT rule. */
-            if (eth_addr_from_string(nat->external_mac, &mac)) {
-                struct ds address = DS_EMPTY_INITIALIZER;
-                ds_put_format(&address, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
-                ds_put_format(&address, " %s", nat->external_ip);
-                ds_put_format(&address, " is_chassis_resident(\"%s\")",
-                              nat->logical_port);
-                addresses[n_nats++] = ds_steal_cstr(&address);
-            }
-        } else {
-            /* Centralized NAT rule, either on gateway router or distributed
-             * router.
-             * Check if external_ip is same as router ip. If so, then there
-             * is no need to add this to the nat_addresses. The router IPs
-             * will be added separately. */
-            bool is_router_ip = false;
-            for (size_t j = 0; j < op->lrp_networks.n_ipv4_addrs; j++) {
-                if (!strcmp(nat->external_ip,
-                            op->lrp_networks.ipv4_addrs[j].addr_s)) {
-                    is_router_ip = true;
-                    break;
-                }
-            }
-
-            if (!is_router_ip) {
-                ds_put_format(&c_addresses, " %s", nat->external_ip);
-                central_ip_address = true;
-            }
-        }
-    }
-
-    /* A set to hold all load-balancer vips. */
-    struct sset all_ips = SSET_INITIALIZER(&all_ips);
-    int addr_family;
-    get_router_load_balancer_ips(op->od, &all_ips, &addr_family);
-
-    const char *ip_address;
-    SSET_FOR_EACH (ip_address, &all_ips) {
-        ds_put_format(&c_addresses, " %s", ip_address);
-        central_ip_address = true;
-    }
-    sset_destroy(&all_ips);
-
-    if (central_ip_address) {
-        /* Gratuitous ARP for centralized NAT rules on distributed gateway
-         * ports should be restricted to the "redirect-chassis". */
-        if (op->od->l3redirect_port) {
-            ds_put_format(&c_addresses, " is_chassis_resident(%s)",
-                          op->od->l3redirect_port->json_key);
-        }
-
-        addresses[n_nats++] = ds_steal_cstr(&c_addresses);
-    }
-
-    *n = n_nats;
-
-    return addresses;
-}
-
-static bool
-sbpb_gw_chassis_needs_update(
-    const struct sbrec_port_binding *pb,
-    const struct nbrec_logical_router_port *lrp,
-    struct ovsdb_idl_index *sbrec_chassis_by_name)
-{
-    if (!lrp || !pb) {
-        return false;
-    }
-
-    if (lrp->n_gateway_chassis && !pb->ha_chassis_group) {
-        /* If there are gateway chassis in the NB DB, but there is
-         * no corresponding HA chassis group in SB DB we need to
-         * create the HA chassis group in SB DB for this lrp. */
-        return true;
-    }
-
-    if (strcmp(pb->ha_chassis_group->name, lrp->name)) {
-        /* Name doesn't match. */
-        return true;
-    }
-
-    if (lrp->n_gateway_chassis != pb->ha_chassis_group->n_ha_chassis) {
-        return true;
-    }
-
-    for (size_t i = 0; i < lrp->n_gateway_chassis; i++) {
-        struct nbrec_gateway_chassis *nbgw_ch = lrp->gateway_chassis[i];
-        bool found = false;
-        for (size_t j = 0; j < pb->ha_chassis_group->n_ha_chassis; j++) {
-            struct sbrec_ha_chassis *sbha_ch =
-                pb->ha_chassis_group->ha_chassis[j];
-            const char *chassis_name = smap_get(&sbha_ch->external_ids,
-                                                "chassis-name");
-            if (!chassis_name) {
-                return true;
-            }
-
-            if (strcmp(chassis_name, nbgw_ch->chassis_name)) {
-                continue;
-            }
-
-            found = true;
-
-            if (nbgw_ch->priority != sbha_ch->priority) {
-                return true;
-            }
-
-            if (sbha_ch->chassis &&
-                strcmp(nbgw_ch->chassis_name, sbha_ch->chassis->name)) {
-                /* sbha_ch->chassis's name is different from the one
-                 * in sbha_ch->external_ids:chassis-name. */
-                return true;
-            }
-
-            if (!sbha_ch->chassis &&
-                chassis_lookup_by_name(sbrec_chassis_by_name,
-                                       nbgw_ch->chassis_name)) {
-                /* sbha_ch->chassis is NULL, but the chassis is
-                 * present in Chassis table. */
-                return true;
-            }
-        }
-
-        if (!found) {
-            return true;
-        }
-    }
-
-    /* No need to update SB DB. Its in sync. */
-    return false;
-}
-
-static struct sbrec_ha_chassis *
-create_sb_ha_chassis(struct northd_context *ctx,
-                     const struct sbrec_chassis *chassis,
-                     const char *chassis_name, int priority)
-{
-    struct sbrec_ha_chassis *sb_ha_chassis =
-        sbrec_ha_chassis_insert(ctx->ovnsb_txn);
-    sbrec_ha_chassis_set_chassis(sb_ha_chassis, chassis);
-    sbrec_ha_chassis_set_priority(sb_ha_chassis, priority);
-    /* Store the chassis_name in external_ids. If the chassis
-     * entry doesn't exist in the Chassis table then we can
-     * figure out the chassis to which this ha_chassis
-     * maps to. */
-    const struct smap external_ids =
-        SMAP_CONST1(&external_ids, "chassis-name", chassis_name);
-    sbrec_ha_chassis_set_external_ids(sb_ha_chassis, &external_ids);
-    return sb_ha_chassis;
-}
-
-static bool
-chassis_group_list_changed(
-    const struct nbrec_ha_chassis_group *nb_ha_grp,
-    const struct sbrec_ha_chassis_group *sb_ha_grp,
-    struct ovsdb_idl_index *sbrec_chassis_by_name)
-{
-    if (nb_ha_grp->n_ha_chassis != sb_ha_grp->n_ha_chassis) {
-        return true;
-    }
-
-    struct shash nb_ha_chassis_list = SHASH_INITIALIZER(&nb_ha_chassis_list);
-    for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) {
-        shash_add(&nb_ha_chassis_list,
-                  nb_ha_grp->ha_chassis[i]->chassis_name,
-                  nb_ha_grp->ha_chassis[i]);
-    }
-
-    bool changed = false;
-    const struct sbrec_ha_chassis *sb_ha_chassis;
-    const struct nbrec_ha_chassis *nb_ha_chassis;
-    for (size_t i = 0; i < sb_ha_grp->n_ha_chassis; i++) {
-        sb_ha_chassis = sb_ha_grp->ha_chassis[i];
-        const char *chassis_name = smap_get(&sb_ha_chassis->external_ids,
-                                            "chassis-name");
-
-        if (!chassis_name) {
-            changed = true;
-            break;
-        }
-
-        nb_ha_chassis = shash_find_and_delete(&nb_ha_chassis_list,
-                                              chassis_name);
-        if (!nb_ha_chassis ||
-            nb_ha_chassis->priority != sb_ha_chassis->priority) {
-            changed = true;
-            break;
-        }
-
-        if (sb_ha_chassis->chassis &&
-            strcmp(sb_ha_chassis->chassis->name, chassis_name)) {
-            /* sb_ha_chassis->chassis's name is different from the one
-             * in sb_ha_chassis->external_ids:chassis-name. */
-            changed = true;
-            break;
-        }
-
-        if (!sb_ha_chassis->chassis &&
-            chassis_lookup_by_name(sbrec_chassis_by_name,
-                                   chassis_name)) {
-            /* sb_ha_chassis->chassis is NULL, but the chassis is
-             * present in Chassis table. */
-            changed = true;
-            break;
-        }
-    }
-
-    struct shash_node *node, *next;
-    SHASH_FOR_EACH_SAFE (node, next, &nb_ha_chassis_list) {
-        shash_delete(&nb_ha_chassis_list, node);
-        changed = true;
-    }
-    shash_destroy(&nb_ha_chassis_list);
-
-    return changed;
-}
-
-static void
-sync_ha_chassis_group_for_sbpb(struct northd_context *ctx,
-                               const struct nbrec_ha_chassis_group *nb_ha_grp,
-                               struct ovsdb_idl_index *sbrec_chassis_by_name,
-                               const struct sbrec_port_binding *pb)
-{
-    bool new_sb_chassis_group = false;
-    const struct sbrec_ha_chassis_group *sb_ha_grp =
-        ha_chassis_group_lookup_by_name(
-            ctx->sbrec_ha_chassis_grp_by_name, nb_ha_grp->name);
-
-    if (!sb_ha_grp) {
-        sb_ha_grp = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
-        sbrec_ha_chassis_group_set_name(sb_ha_grp, nb_ha_grp->name);
-        new_sb_chassis_group = true;
-    }
-
-    if (new_sb_chassis_group ||
-        chassis_group_list_changed(nb_ha_grp, sb_ha_grp,
-                                   sbrec_chassis_by_name)) {
-        struct sbrec_ha_chassis **sb_ha_chassis = NULL;
-        size_t n_ha_chassis = nb_ha_grp->n_ha_chassis;
-        sb_ha_chassis = xcalloc(n_ha_chassis, sizeof *sb_ha_chassis);
-        for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) {
-            const struct nbrec_ha_chassis *nb_ha_chassis
-                = nb_ha_grp->ha_chassis[i];
-            const struct sbrec_chassis *chassis =
-                chassis_lookup_by_name(sbrec_chassis_by_name,
-                                       nb_ha_chassis->chassis_name);
-            sb_ha_chassis[i] = sbrec_ha_chassis_insert(ctx->ovnsb_txn);
-            /* It's perfectly ok if the chassis is NULL. This could
-             * happen when ovn-controller exits and removes its row
-             * from the chassis table in OVN SB DB. */
-            sbrec_ha_chassis_set_chassis(sb_ha_chassis[i], chassis);
-            sbrec_ha_chassis_set_priority(sb_ha_chassis[i],
-                                          nb_ha_chassis->priority);
-            const struct smap external_ids =
-                SMAP_CONST1(&external_ids, "chassis-name",
-                            nb_ha_chassis->chassis_name);
-            sbrec_ha_chassis_set_external_ids(sb_ha_chassis[i], &external_ids);
-        }
-        sbrec_ha_chassis_group_set_ha_chassis(sb_ha_grp, sb_ha_chassis,
-                                              n_ha_chassis);
-        free(sb_ha_chassis);
-    }
-
-    sbrec_port_binding_set_ha_chassis_group(pb, sb_ha_grp);
-}
-
-/* This functions translates the gw chassis on the nb database
- * to HA chassis group in the sb database entries.
- */
-static void
-copy_gw_chassis_from_nbrp_to_sbpb(
-        struct northd_context *ctx,
-        struct ovsdb_idl_index *sbrec_chassis_by_name,
-        const struct nbrec_logical_router_port *lrp,
-        const struct sbrec_port_binding *port_binding)
-{
-
-    /* Make use of the new HA chassis group table to support HA
-     * for the distributed gateway router port. */
-    const struct sbrec_ha_chassis_group *sb_ha_chassis_group =
-        ha_chassis_group_lookup_by_name(
-            ctx->sbrec_ha_chassis_grp_by_name, lrp->name);
-    if (!sb_ha_chassis_group) {
-        sb_ha_chassis_group = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
-        sbrec_ha_chassis_group_set_name(sb_ha_chassis_group, lrp->name);
-    }
-
-    struct sbrec_ha_chassis **sb_ha_chassis = xcalloc(lrp->n_gateway_chassis,
-                                                      sizeof *sb_ha_chassis);
-    size_t n_sb_ha_ch = 0;
-    for (size_t n = 0; n < lrp->n_gateway_chassis; n++) {
-        struct nbrec_gateway_chassis *lrp_gwc = lrp->gateway_chassis[n];
-        if (!lrp_gwc->chassis_name) {
-            continue;
-        }
-
-        const struct sbrec_chassis *chassis =
-            chassis_lookup_by_name(sbrec_chassis_by_name,
-                                   lrp_gwc->chassis_name);
-
-        sb_ha_chassis[n_sb_ha_ch] =
-            create_sb_ha_chassis(ctx, chassis, lrp_gwc->chassis_name,
-                                 lrp_gwc->priority);
-        n_sb_ha_ch++;
-    }
-
-    sbrec_ha_chassis_group_set_ha_chassis(sb_ha_chassis_group,
-                                          sb_ha_chassis, n_sb_ha_ch);
-    sbrec_port_binding_set_ha_chassis_group(port_binding, sb_ha_chassis_group);
-    free(sb_ha_chassis);
-}
-
-static void
-ovn_port_update_sbrec(struct northd_context *ctx,
-                      struct ovsdb_idl_index *sbrec_chassis_by_name,
-                      const struct ovn_port *op,
-                      struct hmap *chassis_qdisc_queues,
-                      struct sset *active_ha_chassis_grps)
-{
-    sbrec_port_binding_set_datapath(op->sb, op->od->sb);
-    if (op->nbrp) {
-        /* If the router is for l3 gateway, it resides on a chassis
-         * and its port type is "l3gateway". */
-        const char *chassis_name = smap_get(&op->od->nbr->options, "chassis");
-        if (op->derived) {
-            sbrec_port_binding_set_type(op->sb, "chassisredirect");
-        } else if (chassis_name) {
-            sbrec_port_binding_set_type(op->sb, "l3gateway");
-        } else {
-            sbrec_port_binding_set_type(op->sb, "patch");
-        }
-
-        struct smap new;
-        smap_init(&new);
-        if (op->derived) {
-            const char *redirect_chassis = smap_get(&op->nbrp->options,
-                                                    "redirect-chassis");
-            int n_gw_options_set = 0;
-            if (op->nbrp->ha_chassis_group) {
-                n_gw_options_set++;
-            }
-            if (op->nbrp->n_gateway_chassis) {
-                n_gw_options_set++;
-            }
-            if (redirect_chassis) {
-                n_gw_options_set++;
-            }
-            if (n_gw_options_set > 1) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_WARN_RL(
-                    &rl, "Multiple gatway options set for the logical router "
-                         "port %s. The first preferred option is "
-                         "ha_chassis_group; the second is gateway_chassis; "
-                         "and the last is redirect-chassis.", op->nbrp->name);
-            }
-
-            if (op->nbrp->ha_chassis_group) {
-                /* HA Chassis group is set. Ignore 'gateway_chassis'
-                 * column and redirect-chassis option. */
-                sync_ha_chassis_group_for_sbpb(ctx, op->nbrp->ha_chassis_group,
-                                               sbrec_chassis_by_name, op->sb);
-                sset_add(active_ha_chassis_grps,
-                         op->nbrp->ha_chassis_group->name);
-            } else if (op->nbrp->n_gateway_chassis) {
-                /* Legacy gateway_chassis support.
-                 * Create ha_chassis_group for the Northbound gateway_chassis
-                 * associated with the lrp. */
-                if (sbpb_gw_chassis_needs_update(op->sb, op->nbrp,
-                                                 sbrec_chassis_by_name)) {
-                    copy_gw_chassis_from_nbrp_to_sbpb(ctx,
-                                                      sbrec_chassis_by_name,
-                                                      op->nbrp, op->sb);
-                }
-
-                sset_add(active_ha_chassis_grps, op->nbrp->name);
-            } else if (redirect_chassis) {
-                /* Handle ports that had redirect-chassis option attached
-                 * to them, and for backwards compatibility convert them
-                 * to a single HA Chassis group entry */
-                const struct sbrec_chassis *chassis =
-                    chassis_lookup_by_name(sbrec_chassis_by_name,
-                                           redirect_chassis);
-                if (chassis) {
-                    /* If we found the chassis, and the gw chassis on record
-                     * differs from what we expect go ahead and update */
-                    char *gwc_name = xasprintf("%s_%s", op->nbrp->name,
-                                chassis->name);
-                    const struct sbrec_ha_chassis_group *sb_ha_ch_grp;
-                    sb_ha_ch_grp = ha_chassis_group_lookup_by_name(
-                        ctx->sbrec_ha_chassis_grp_by_name, gwc_name);
-                    if (!sb_ha_ch_grp) {
-                        sb_ha_ch_grp =
-                            sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
-                        sbrec_ha_chassis_group_set_name(sb_ha_ch_grp,
-                                                        gwc_name);
-                    }
-
-                    if (sb_ha_ch_grp->n_ha_chassis != 1) {
-                        struct sbrec_ha_chassis **sb_ha_ch =
-                            xcalloc(1, sizeof *sb_ha_ch);
-                        sb_ha_ch[0] = create_sb_ha_chassis(ctx, chassis,
-                                                           chassis->name, 0);
-                        sbrec_ha_chassis_group_set_ha_chassis(sb_ha_ch_grp,
-                                                              sb_ha_ch, 1);
-                    }
-                    sbrec_port_binding_set_ha_chassis_group(op->sb,
-                                                            sb_ha_ch_grp);
-                    sset_add(active_ha_chassis_grps, gwc_name);
-                    free(gwc_name);
-                } else {
-                    VLOG_WARN("chassis name '%s' from redirect from logical "
-                              " router port '%s' redirect-chassis not found",
-                              redirect_chassis, op->nbrp->name);
-                    if (op->sb->ha_chassis_group) {
-                        sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
-                    }
-                }
-            } else {
-                /* Nothing is set. Clear ha_chassis_group  from pb. */
-                if (op->sb->ha_chassis_group) {
-                    sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
-                }
-            }
-
-            if (op->sb->n_gateway_chassis) {
-                /* Delete the legacy gateway_chassis from the pb. */
-                sbrec_port_binding_set_gateway_chassis(op->sb, NULL, 0);
-            }
-            smap_add(&new, "distributed-port", op->nbrp->name);
-        } else {
-            if (op->peer) {
-                smap_add(&new, "peer", op->peer->key);
-            }
-            if (chassis_name) {
-                smap_add(&new, "l3gateway-chassis", chassis_name);
-            }
-        }
-        sbrec_port_binding_set_options(op->sb, &new);
-        smap_destroy(&new);
-
-        sbrec_port_binding_set_parent_port(op->sb, NULL);
-        sbrec_port_binding_set_tag(op->sb, NULL, 0);
-
-        struct ds s = DS_EMPTY_INITIALIZER;
-        ds_put_cstr(&s, op->nbrp->mac);
-        for (int i = 0; i < op->nbrp->n_networks; ++i) {
-            ds_put_format(&s, " %s", op->nbrp->networks[i]);
-        }
-        const char *addresses = ds_cstr(&s);
-        sbrec_port_binding_set_mac(op->sb, &addresses, 1);
-        ds_destroy(&s);
-
-        struct smap ids = SMAP_INITIALIZER(&ids);
-        sbrec_port_binding_set_external_ids(op->sb, &ids);
-
-        sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
-    } else {
-        if (strcmp(op->nbsp->type, "router")) {
-            uint32_t queue_id = smap_get_int(
-                    &op->sb->options, "qdisc_queue_id", 0);
-            bool has_qos = port_has_qos_params(&op->nbsp->options);
-            struct smap options;
-
-            if (op->sb->chassis && has_qos && !queue_id) {
-                queue_id = allocate_chassis_queueid(chassis_qdisc_queues,
-                                                    op->sb->chassis);
-            } else if (!has_qos && queue_id) {
-                free_chassis_queueid(chassis_qdisc_queues,
-                                     op->sb->chassis,
-                                     queue_id);
-                queue_id = 0;
-            }
-
-            smap_clone(&options, &op->nbsp->options);
-            if (queue_id) {
-                smap_add_format(&options,
-                                "qdisc_queue_id", "%d", queue_id);
-            }
-            sbrec_port_binding_set_options(op->sb, &options);
-            smap_destroy(&options);
-            if (ovn_is_known_nb_lsp_type(op->nbsp->type)) {
-                sbrec_port_binding_set_type(op->sb, op->nbsp->type);
-            } else {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_WARN_RL(
-                    &rl, "Unknown port type '%s' set on logical switch '%s'.",
-                    op->nbsp->type, op->nbsp->name);
-            }
-
-            sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
-
-            if (!strcmp(op->nbsp->type, "external")) {
-                if (op->nbsp->ha_chassis_group) {
-                    sync_ha_chassis_group_for_sbpb(
-                        ctx, op->nbsp->ha_chassis_group,
-                        sbrec_chassis_by_name, op->sb);
-                    sset_add(active_ha_chassis_grps,
-                             op->nbsp->ha_chassis_group->name);
-                } else {
-                    sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
-                }
-            }
-        } else {
-            const char *chassis = NULL;
-            if (op->peer && op->peer->od && op->peer->od->nbr) {
-                chassis = smap_get(&op->peer->od->nbr->options, "chassis");
-            }
-
-            /* A switch port connected to a gateway router is also of
-             * type "l3gateway". */
-            if (chassis) {
-                sbrec_port_binding_set_type(op->sb, "l3gateway");
-            } else {
-                sbrec_port_binding_set_type(op->sb, "patch");
-            }
-
-            const char *router_port = smap_get(&op->nbsp->options,
-                                               "router-port");
-            if (router_port || chassis) {
-                struct smap new;
-                smap_init(&new);
-                if (router_port) {
-                    smap_add(&new, "peer", router_port);
-                }
-                if (chassis) {
-                    smap_add(&new, "l3gateway-chassis", chassis);
-                }
-                sbrec_port_binding_set_options(op->sb, &new);
-                smap_destroy(&new);
-            } else {
-                sbrec_port_binding_set_options(op->sb, NULL);
-            }
-
-            const char *nat_addresses = smap_get(&op->nbsp->options,
-                                           "nat-addresses");
-            size_t n_nats = 0;
-            char **nats = NULL;
-            if (nat_addresses && !strcmp(nat_addresses, "router")) {
-                if (op->peer && op->peer->od
-                    && (chassis || op->peer->od->l3redirect_port)) {
-                    nats = get_nat_addresses(op->peer, &n_nats);
-                }
-            /* Only accept manual specification of ethernet address
-             * followed by IPv4 addresses on type "l3gateway" ports. */
-            } else if (nat_addresses && chassis) {
-                struct lport_addresses laddrs;
-                if (!extract_lsp_addresses(nat_addresses, &laddrs)) {
-                    static struct vlog_rate_limit rl =
-                        VLOG_RATE_LIMIT_INIT(1, 1);
-                    VLOG_WARN_RL(&rl, "Error extracting nat-addresses.");
-                } else {
-                    destroy_lport_addresses(&laddrs);
-                    n_nats = 1;
-                    nats = xcalloc(1, sizeof *nats);
-                    nats[0] = xstrdup(nat_addresses);
-                }
-            }
-
-            /* Add the router mac and IPv4 addresses to
-             * Port_Binding.nat_addresses so that GARP is sent for these
-             * IPs by the ovn-controller on which the distributed gateway
-             * router port resides if:
-             *
-             * -  op->peer has 'reside-on-gateway-chassis' set and the
-             *    the logical router datapath has distributed router port.
-             *
-             * -  op->peer is distributed gateway router port.
-             *
-             * -  op->peer's router is a gateway router and op has a localnet
-             *    port.
-             *
-             * Note: Port_Binding.nat_addresses column is also used for
-             * sending the GARPs for the router port IPs.
-             * */
-            bool add_router_port_garp = false;
-            if (op->peer && op->peer->nbrp && op->peer->od->l3dgw_port &&
-                op->peer->od->l3redirect_port &&
-                (smap_get_bool(&op->peer->nbrp->options,
-                              "reside-on-redirect-chassis", false) ||
-                op->peer == op->peer->od->l3dgw_port)) {
-                add_router_port_garp = true;
-            } else if (chassis && op->od->localnet_port) {
-                add_router_port_garp = true;
-            }
-
-            if (add_router_port_garp) {
-                struct ds garp_info = DS_EMPTY_INITIALIZER;
-                ds_put_format(&garp_info, "%s", op->peer->lrp_networks.ea_s);
-                for (size_t i = 0; i < op->peer->lrp_networks.n_ipv4_addrs;
-                     i++) {
-                    ds_put_format(&garp_info, " %s",
-                                  op->peer->lrp_networks.ipv4_addrs[i].addr_s);
-                }
-
-                if (op->peer->od->l3redirect_port) {
-                    ds_put_format(&garp_info, " is_chassis_resident(%s)",
-                                  op->peer->od->l3redirect_port->json_key);
-                }
-
-                n_nats++;
-                nats = xrealloc(nats, (n_nats * sizeof *nats));
-                nats[n_nats - 1] = ds_steal_cstr(&garp_info);
-                ds_destroy(&garp_info);
-            }
-
-            sbrec_port_binding_set_nat_addresses(op->sb,
-                                                 (const char **) nats, n_nats);
-            for (size_t i = 0; i < n_nats; i++) {
-                free(nats[i]);
-            }
-            free(nats);
-        }
-
-        sbrec_port_binding_set_parent_port(op->sb, op->nbsp->parent_name);
-        sbrec_port_binding_set_tag(op->sb, op->nbsp->tag, op->nbsp->n_tag);
-        sbrec_port_binding_set_mac(op->sb, (const char **) op->nbsp->addresses,
-                                   op->nbsp->n_addresses);
-
-        struct smap ids = SMAP_INITIALIZER(&ids);
-        smap_clone(&ids, &op->nbsp->external_ids);
-        const char *name = smap_get(&ids, "neutron:port_name");
-        if (name && name[0]) {
-            smap_add(&ids, "name", name);
-        }
-        sbrec_port_binding_set_external_ids(op->sb, &ids);
-        smap_destroy(&ids);
-    }
-}
-
-/* Remove mac_binding entries that refer to logical_ports which are
- * deleted. */
-static void
-cleanup_mac_bindings(struct northd_context *ctx, struct hmap *ports)
-{
-    const struct sbrec_mac_binding *b, *n;
-    SBREC_MAC_BINDING_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) {
-        if (!ovn_port_find(ports, b->logical_port)) {
-            sbrec_mac_binding_delete(b);
-        }
-    }
-}
-
-static void
-cleanup_sb_ha_chassis_groups(struct northd_context *ctx,
-                             struct sset *active_ha_chassis_groups)
-{
-    const struct sbrec_ha_chassis_group *b, *n;
-    SBREC_HA_CHASSIS_GROUP_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) {
-        if (!sset_contains(active_ha_chassis_groups, b->name)) {
-            sbrec_ha_chassis_group_delete(b);
-        }
-    }
-}
-
-/* Updates the southbound Port_Binding table so that it contains the logical
- * switch ports specified by the northbound database.
- *
- * Initializes 'ports' to contain a "struct ovn_port" for every logical port,
- * using the "struct ovn_datapath"s in 'datapaths' to look up logical
- * datapaths. */
-static void
-build_ports(struct northd_context *ctx,
-            struct ovsdb_idl_index *sbrec_chassis_by_name,
-            struct hmap *datapaths, struct hmap *ports)
-{
-    struct ovs_list sb_only, nb_only, both;
-    struct hmap tag_alloc_table = HMAP_INITIALIZER(&tag_alloc_table);
-    struct hmap chassis_qdisc_queues = HMAP_INITIALIZER(&chassis_qdisc_queues);
-
-    /* sset which stores the set of ha chassis group names used. */
-    struct sset active_ha_chassis_grps =
-        SSET_INITIALIZER(&active_ha_chassis_grps);
-
-    join_logical_ports(ctx, datapaths, ports, &chassis_qdisc_queues,
-                       &tag_alloc_table, &sb_only, &nb_only, &both);
-
-    struct ovn_port *op, *next;
-    /* For logical ports that are in both databases, update the southbound
-     * record based on northbound data.  Also index the in-use tunnel_keys.
-     * For logical ports that are in NB database, do any tag allocation
-     * needed. */
-    LIST_FOR_EACH_SAFE (op, next, list, &both) {
-        if (op->nbsp) {
-            tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp);
-        }
-        ovn_port_update_sbrec(ctx, sbrec_chassis_by_name,
-                              op, &chassis_qdisc_queues,
-                              &active_ha_chassis_grps);
-        add_tnlid(&op->od->port_tnlids, op->sb->tunnel_key);
-        if (op->sb->tunnel_key > op->od->port_key_hint) {
-            op->od->port_key_hint = op->sb->tunnel_key;
-        }
-    }
-
-    /* Add southbound record for each unmatched northbound record. */
-    LIST_FOR_EACH_SAFE (op, next, list, &nb_only) {
-        uint16_t tunnel_key = ovn_port_allocate_key(op->od);
-        if (!tunnel_key) {
-            continue;
-        }
-
-        op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
-        ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
-                              &chassis_qdisc_queues,
-                              &active_ha_chassis_grps);
-        sbrec_port_binding_set_logical_port(op->sb, op->key);
-        sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key);
-    }
-
-    bool remove_mac_bindings = false;
-    if (!ovs_list_is_empty(&sb_only)) {
-        remove_mac_bindings = true;
-    }
-
-    /* Delete southbound records without northbound matches. */
-    LIST_FOR_EACH_SAFE(op, next, list, &sb_only) {
-        ovs_list_remove(&op->list);
-        sbrec_port_binding_delete(op->sb);
-        ovn_port_destroy(ports, op);
-    }
-    if (remove_mac_bindings) {
-        cleanup_mac_bindings(ctx, ports);
-    }
-
-    tag_alloc_destroy(&tag_alloc_table);
-    destroy_chassis_queues(&chassis_qdisc_queues);
-    cleanup_sb_ha_chassis_groups(ctx, &active_ha_chassis_grps);
-    sset_destroy(&active_ha_chassis_grps);
-}
-
-struct multicast_group {
-    const char *name;
-    uint16_t key;               /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */
-};
-
-#define MC_FLOOD "_MC_flood"
-static const struct multicast_group mc_flood =
-    { MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY };
-
-#define MC_UNKNOWN "_MC_unknown"
-static const struct multicast_group mc_unknown =
-    { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
-
-static bool
-multicast_group_equal(const struct multicast_group *a,
-                      const struct multicast_group *b)
-{
-    return !strcmp(a->name, b->name) && a->key == b->key;
-}
-
-/* Multicast group entry. */
-struct ovn_multicast {
-    struct hmap_node hmap_node; /* Index on 'datapath' and 'key'. */
-    struct ovn_datapath *datapath;
-    const struct multicast_group *group;
-
-    struct ovn_port **ports;
-    size_t n_ports, allocated_ports;
-};
-
-static uint32_t
-ovn_multicast_hash(const struct ovn_datapath *datapath,
-                   const struct multicast_group *group)
-{
-    return hash_pointer(datapath, group->key);
-}
-
-static struct ovn_multicast *
-ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath,
-                   const struct multicast_group *group)
-{
-    struct ovn_multicast *mc;
-
-    HMAP_FOR_EACH_WITH_HASH (mc, hmap_node,
-                             ovn_multicast_hash(datapath, group), mcgroups) {
-        if (mc->datapath == datapath
-            && multicast_group_equal(mc->group, group)) {
-            return mc;
-        }
-    }
-    return NULL;
-}
-
-static void
-ovn_multicast_add_ports(struct hmap *mcgroups, struct ovn_datapath *od,
-                        const struct multicast_group *group,
-                        struct ovn_port **ports, size_t n_ports)
-{
-    struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group);
-    if (!mc) {
-        mc = xmalloc(sizeof *mc);
-        hmap_insert(mcgroups, &mc->hmap_node, ovn_multicast_hash(od, group));
-        mc->datapath = od;
-        mc->group = group;
-        mc->n_ports = 0;
-        mc->allocated_ports = 4;
-        mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports);
-    }
-
-    size_t n_ports_total = mc->n_ports + n_ports;
-
-    if (n_ports_total > 2 * mc->allocated_ports) {
-        mc->allocated_ports = n_ports_total;
-        mc->ports = xrealloc(mc->ports,
-                             mc->allocated_ports * sizeof *mc->ports);
-    } else if (n_ports_total > mc->allocated_ports) {
-        mc->ports = x2nrealloc(mc->ports, &mc->allocated_ports,
-                               sizeof *mc->ports);
-    }
-
-    memcpy(&mc->ports[mc->n_ports], &ports[0], n_ports * sizeof *ports);
-    mc->n_ports += n_ports;
-}
-
-static void
-ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
-                  struct ovn_port *port)
-{
-    ovn_multicast_add_ports(mcgroups, port->od, group, &port, 1);
-}
-
-static void
-ovn_multicast_destroy(struct hmap *mcgroups, struct ovn_multicast *mc)
-{
-    if (mc) {
-        hmap_remove(mcgroups, &mc->hmap_node);
-        free(mc->ports);
-        free(mc);
-    }
-}
-
-static void
-ovn_multicast_update_sbrec(const struct ovn_multicast *mc,
-                           const struct sbrec_multicast_group *sb)
-{
-    struct sbrec_port_binding **ports = xmalloc(mc->n_ports * sizeof *ports);
-    for (size_t i = 0; i < mc->n_ports; i++) {
-        ports[i] = CONST_CAST(struct sbrec_port_binding *, mc->ports[i]->sb);
-    }
-    sbrec_multicast_group_set_ports(sb, ports, mc->n_ports);
-    free(ports);
-}
-
-/*
- * IGMP group entry (1:1 mapping to SB database).
- */
-struct ovn_igmp_group_entry {
-    struct ovs_list list_node; /* Linkage in the list of entries. */
-    const struct sbrec_igmp_group *sb;
-};
-
-/*
- * IGMP group entry (aggregate of all entries from the SB database
- * corresponding to the multicast group).
- */
-struct ovn_igmp_group {
-    struct hmap_node hmap_node; /* Index on 'datapath' and 'address'. */
-
-    struct ovn_datapath *datapath;
-    struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */
-    struct multicast_group mcgroup;
-
-    struct ovs_list sb_entries; /* List of SB entries for this group. */
-};
-
-static uint32_t
-ovn_igmp_group_hash(const struct ovn_datapath *datapath,
-                    const struct in6_addr *address)
-{
-    return hash_pointer(datapath, hash_bytes(address, sizeof *address, 0));
-}
-
-static struct ovn_igmp_group *
-ovn_igmp_group_find(struct hmap *igmp_groups,
-                    const struct ovn_datapath *datapath,
-                    const struct in6_addr *address)
-{
-    struct ovn_igmp_group *group;
-
-    HMAP_FOR_EACH_WITH_HASH (group, hmap_node,
-                             ovn_igmp_group_hash(datapath, address),
-                             igmp_groups) {
-        if (group->datapath == datapath &&
-                ipv6_addr_equals(&group->address, address)) {
-            return group;
-        }
-    }
-    return NULL;
-}
-
-static void
-ovn_igmp_group_add(struct northd_context *ctx, struct hmap *igmp_groups,
-                   struct ovn_datapath *datapath,
-                   const struct sbrec_igmp_group *sb_igmp_group)
-{
-    struct in6_addr group_address;
-    ovs_be32 ipv4;
-
-    if (ip_parse(sb_igmp_group->address, &ipv4)) {
-        group_address = in6_addr_mapped_ipv4(ipv4);
-    } else if (!ipv6_parse(sb_igmp_group->address, &group_address)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "invalid IGMP group address: %s",
-                     sb_igmp_group->address);
-        return;
-    }
-
-    struct ovn_igmp_group *igmp_group =
-        ovn_igmp_group_find(igmp_groups, datapath, &group_address);
-
-    if (!igmp_group) {
-        igmp_group = xmalloc(sizeof *igmp_group);
-
-        const struct sbrec_multicast_group *mcgroup =
-            mcast_group_lookup(ctx->sbrec_mcast_group_by_name_dp,
-                               sb_igmp_group->address, datapath->sb);
-
-        igmp_group->datapath = datapath;
-        igmp_group->address = group_address;
-        if (mcgroup) {
-            igmp_group->mcgroup.key = mcgroup->tunnel_key;
-            add_tnlid(&datapath->mcast_info.group_tnlids, mcgroup->tunnel_key);
-        } else {
-            igmp_group->mcgroup.key = 0;
-        }
-        igmp_group->mcgroup.name = sb_igmp_group->address;
-        ovs_list_init(&igmp_group->sb_entries);
-
-        hmap_insert(igmp_groups, &igmp_group->hmap_node,
-                    ovn_igmp_group_hash(datapath, &group_address));
-    }
-
-    struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry);
-
-    entry->sb = sb_igmp_group;
-    ovs_list_push_back(&igmp_group->sb_entries , &entry->list_node);
-}
-
-static void
-ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
-                               struct hmap *ovn_ports,
-                               struct hmap *mcast_groups)
-{
-    struct ovn_igmp_group_entry *entry;
-
-    LIST_FOR_EACH_POP (entry, list_node, &igmp_group->sb_entries) {
-        size_t n_oports = 0;
-        struct ovn_port **oports =
-            xmalloc(entry->sb->n_ports * sizeof *oports);
-
-        for (size_t i = 0; i < entry->sb->n_ports; i++) {
-            oports[n_oports] =
-                ovn_port_find(ovn_ports, entry->sb->ports[i]->logical_port);
-            if (oports[n_oports]) {
-                n_oports++;
-            }
-        }
-
-        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
-                                &igmp_group->mcgroup, oports, n_oports);
-        free(oports);
-        free(entry);
-    }
-}
-
-static void
-ovn_igmp_group_destroy(struct hmap *igmp_groups,
-                       struct ovn_igmp_group *igmp_group)
-{
-    if (igmp_group) {
-        struct ovn_igmp_group_entry *entry;
-
-        LIST_FOR_EACH_POP (entry, list_node, &igmp_group->sb_entries) {
-            free(entry);
-        }
-        hmap_remove(igmp_groups, &igmp_group->hmap_node);
-        free(igmp_group);
-    }
-}
-
-/* Logical flow generation.
- *
- * This code generates the Logical_Flow table in the southbound database, as a
- * function of most of the northbound database.
- */
-
-struct ovn_lflow {
-    struct hmap_node hmap_node;
-
-    struct ovn_datapath *od;
-    enum ovn_stage stage;
-    uint16_t priority;
-    char *match;
-    char *actions;
-    char *stage_hint;
-    const char *where;
-};
-
-static size_t
-ovn_lflow_hash(const struct ovn_lflow *lflow)
-{
-    return ovn_logical_flow_hash(&lflow->od->sb->header_.uuid,
-                                 ovn_stage_get_table(lflow->stage),
-                                 ovn_stage_get_pipeline_name(lflow->stage),
-                                 lflow->priority, lflow->match,
-                                 lflow->actions);
-}
-
-static bool
-ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_lflow *b)
-{
-    return (a->od == b->od
-            && a->stage == b->stage
-            && a->priority == b->priority
-            && !strcmp(a->match, b->match)
-            && !strcmp(a->actions, b->actions));
-}
-
-static void
-ovn_lflow_init(struct ovn_lflow *lflow, struct ovn_datapath *od,
-               enum ovn_stage stage, uint16_t priority,
-               char *match, char *actions, char *stage_hint,
-               const char *where)
-{
-    lflow->od = od;
-    lflow->stage = stage;
-    lflow->priority = priority;
-    lflow->match = match;
-    lflow->actions = actions;
-    lflow->stage_hint = stage_hint;
-    lflow->where = where;
-}
-
-/* Adds a row with the specified contents to the Logical_Flow table. */
-static void
-ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
-                 enum ovn_stage stage, uint16_t priority,
-                 const char *match, const char *actions,
-                 const char *stage_hint, const char *where)
-{
-    ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od));
-
-    struct ovn_lflow *lflow = xmalloc(sizeof *lflow);
-    ovn_lflow_init(lflow, od, stage, priority,
-                   xstrdup(match), xstrdup(actions),
-                   nullable_xstrdup(stage_hint), where);
-    hmap_insert(lflow_map, &lflow->hmap_node, ovn_lflow_hash(lflow));
-}
-
-/* Adds a row with the specified contents to the Logical_Flow table. */
-#define ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
-                                ACTIONS, STAGE_HINT) \
-    ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
-                     STAGE_HINT, OVS_SOURCE_LOCATOR)
-
-#define ovn_lflow_add(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \
-    ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
-                            ACTIONS, NULL)
-
-static struct ovn_lflow *
-ovn_lflow_find(struct hmap *lflows, struct ovn_datapath *od,
-               enum ovn_stage stage, uint16_t priority,
-               const char *match, const char *actions, uint32_t hash)
-{
-    struct ovn_lflow target;
-    ovn_lflow_init(&target, od, stage, priority,
-                   CONST_CAST(char *, match), CONST_CAST(char *, actions),
-                   NULL, NULL);
-
-    struct ovn_lflow *lflow;
-    HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) {
-        if (ovn_lflow_equal(lflow, &target)) {
-            return lflow;
-        }
-    }
-    return NULL;
-}
-
-static void
-ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow)
-{
-    if (lflow) {
-        hmap_remove(lflows, &lflow->hmap_node);
-        free(lflow->match);
-        free(lflow->actions);
-        free(lflow->stage_hint);
-        free(lflow);
-    }
-}
-
-/* Appends port security constraints on L2 address field 'eth_addr_field'
- * (e.g. "eth.src" or "eth.dst") to 'match'.  'ps_addrs', with 'n_ps_addrs'
- * elements, is the collection of port_security constraints from an
- * OVN_NB Logical_Switch_Port row generated by extract_lsp_addresses(). */
-static void
-build_port_security_l2(const char *eth_addr_field,
-                       struct lport_addresses *ps_addrs,
-                       unsigned int n_ps_addrs,
-                       struct ds *match)
-{
-    if (!n_ps_addrs) {
-        return;
-    }
-
-    ds_put_format(match, " && %s == {", eth_addr_field);
-
-    for (size_t i = 0; i < n_ps_addrs; i++) {
-        ds_put_format(match, "%s ", ps_addrs[i].ea_s);
-    }
-    ds_chomp(match, ' ');
-    ds_put_cstr(match, "}");
-}
-
-static void
-build_port_security_ipv6_nd_flow(
-    struct ds *match, struct eth_addr ea, struct ipv6_netaddr *ipv6_addrs,
-    int n_ipv6_addrs)
-{
-    ds_put_format(match, " && ip6 && nd && ((nd.sll == "ETH_ADDR_FMT" || "
-                  "nd.sll == "ETH_ADDR_FMT") || ((nd.tll == "ETH_ADDR_FMT" || "
-                  "nd.tll == "ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth_addr_zero),
-                  ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(eth_addr_zero),
-                  ETH_ADDR_ARGS(ea));
-    if (!n_ipv6_addrs) {
-        ds_put_cstr(match, "))");
-        return;
-    }
-
-    char ip6_str[INET6_ADDRSTRLEN + 1];
-    struct in6_addr lla;
-    in6_generate_lla(ea, &lla);
-    memset(ip6_str, 0, sizeof(ip6_str));
-    ipv6_string_mapped(ip6_str, &lla);
-    ds_put_format(match, " && (nd.target == %s", ip6_str);
-
-    for(int i = 0; i < n_ipv6_addrs; i++) {
-        memset(ip6_str, 0, sizeof(ip6_str));
-        ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr);
-        ds_put_format(match, " || nd.target == %s", ip6_str);
-    }
-
-    ds_put_format(match, ")))");
-}
-
-static void
-build_port_security_ipv6_flow(
-    enum ovn_pipeline pipeline, struct ds *match, struct eth_addr ea,
-    struct ipv6_netaddr *ipv6_addrs, int n_ipv6_addrs)
-{
-    char ip6_str[INET6_ADDRSTRLEN + 1];
-
-    ds_put_format(match, " && %s == {",
-                  pipeline == P_IN ? "ip6.src" : "ip6.dst");
-
-    /* Allow link-local address. */
-    struct in6_addr lla;
-    in6_generate_lla(ea, &lla);
-    ipv6_string_mapped(ip6_str, &lla);
-    ds_put_format(match, "%s, ", ip6_str);
-
-    /* Allow ip6.dst=ff00::/8 for multicast packets */
-    if (pipeline == P_OUT) {
-        ds_put_cstr(match, "ff00::/8, ");
-    }
-    for(int i = 0; i < n_ipv6_addrs; i++) {
-        ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr);
-        ds_put_format(match, "%s, ", ip6_str);
-    }
-    /* Replace ", " by "}". */
-    ds_chomp(match, ' ');
-    ds_chomp(match, ',');
-    ds_put_cstr(match, "}");
-}
-
-/**
- * Build port security constraints on ARP and IPv6 ND fields
- * and add logical flows to S_SWITCH_IN_PORT_SEC_ND stage.
- *
- * For each port security of the logical port, following
- * logical flows are added
- *   - If the port security has no IP (both IPv4 and IPv6) or
- *     if it has IPv4 address(es)
- *      - Priority 90 flow to allow ARP packets for known MAC addresses
- *        in the eth.src and arp.spa fields. If the port security
- *        has IPv4 addresses, allow known IPv4 addresses in the arp.tpa field.
- *
- *   - If the port security has no IP (both IPv4 and IPv6) or
- *     if it has IPv6 address(es)
- *     - Priority 90 flow to allow IPv6 ND packets for known MAC addresses
- *       in the eth.src and nd.sll/nd.tll fields. If the port security
- *       has IPv6 addresses, allow known IPv6 addresses in the nd.target field
- *       for IPv6 Neighbor Advertisement packet.
- *
- *   - Priority 80 flow to drop ARP and IPv6 ND packets.
- */
-static void
-build_port_security_nd(struct ovn_port *op, struct hmap *lflows)
-{
-    struct ds match = DS_EMPTY_INITIALIZER;
-
-    for (size_t i = 0; i < op->n_ps_addrs; i++) {
-        struct lport_addresses *ps = &op->ps_addrs[i];
-
-        bool no_ip = !(ps->n_ipv4_addrs || ps->n_ipv6_addrs);
-
-        ds_clear(&match);
-        if (ps->n_ipv4_addrs || no_ip) {
-            ds_put_format(&match,
-                          "inport == %s && eth.src == %s && arp.sha == %s",
-                          op->json_key, ps->ea_s, ps->ea_s);
-
-            if (ps->n_ipv4_addrs) {
-                ds_put_cstr(&match, " && arp.spa == {");
-                for (size_t j = 0; j < ps->n_ipv4_addrs; j++) {
-                    /* When the netmask is applied, if the host portion is
-                     * non-zero, the host can only use the specified
-                     * address in the arp.spa.  If zero, the host is allowed
-                     * to use any address in the subnet. */
-                    if (ps->ipv4_addrs[j].plen == 32
-                        || ps->ipv4_addrs[j].addr & ~ps->ipv4_addrs[j].mask) {
-                        ds_put_cstr(&match, ps->ipv4_addrs[j].addr_s);
-                    } else {
-                        ds_put_format(&match, "%s/%d",
-                                      ps->ipv4_addrs[j].network_s,
-                                      ps->ipv4_addrs[j].plen);
-                    }
-                    ds_put_cstr(&match, ", ");
-                }
-                ds_chomp(&match, ' ');
-                ds_chomp(&match, ',');
-                ds_put_cstr(&match, "}");
-            }
-            ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90,
-                          ds_cstr(&match), "next;");
-        }
-
-        if (ps->n_ipv6_addrs || no_ip) {
-            ds_clear(&match);
-            ds_put_format(&match, "inport == %s && eth.src == %s",
-                          op->json_key, ps->ea_s);
-            build_port_security_ipv6_nd_flow(&match, ps->ea, ps->ipv6_addrs,
-                                             ps->n_ipv6_addrs);
-            ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90,
-                          ds_cstr(&match), "next;");
-        }
-    }
-
-    ds_clear(&match);
-    ds_put_format(&match, "inport == %s && (arp || nd)", op->json_key);
-    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 80,
-                  ds_cstr(&match), "drop;");
-    ds_destroy(&match);
-}
-
-/**
- * Build port security constraints on IPv4 and IPv6 src and dst fields
- * and add logical flows to S_SWITCH_(IN/OUT)_PORT_SEC_IP stage.
- *
- * For each port security of the logical port, following
- * logical flows are added
- *   - If the port security has IPv4 addresses,
- *     - Priority 90 flow to allow IPv4 packets for known IPv4 addresses
- *
- *   - If the port security has IPv6 addresses,
- *     - Priority 90 flow to allow IPv6 packets for known IPv6 addresses
- *
- *   - If the port security has IPv4 addresses or IPv6 addresses or both
- *     - Priority 80 flow to drop all IPv4 and IPv6 traffic
- */
-static void
-build_port_security_ip(enum ovn_pipeline pipeline, struct ovn_port *op,
-                       struct hmap *lflows)
-{
-    char *port_direction;
-    enum ovn_stage stage;
-    if (pipeline == P_IN) {
-        port_direction = "inport";
-        stage = S_SWITCH_IN_PORT_SEC_IP;
-    } else {
-        port_direction = "outport";
-        stage = S_SWITCH_OUT_PORT_SEC_IP;
-    }
-
-    for (size_t i = 0; i < op->n_ps_addrs; i++) {
-        struct lport_addresses *ps = &op->ps_addrs[i];
-
-        if (!(ps->n_ipv4_addrs || ps->n_ipv6_addrs)) {
-            continue;
-        }
-
-        if (ps->n_ipv4_addrs) {
-            struct ds match = DS_EMPTY_INITIALIZER;
-            if (pipeline == P_IN) {
-                /* Permit use of the unspecified address for DHCP discovery */
-                struct ds dhcp_match = DS_EMPTY_INITIALIZER;
-                ds_put_format(&dhcp_match, "inport == %s"
-                              " && eth.src == %s"
-                              " && ip4.src == 0.0.0.0"
-                              " && ip4.dst == 255.255.255.255"
-                              " && udp.src == 68 && udp.dst == 67",
-                              op->json_key, ps->ea_s);
-                ovn_lflow_add(lflows, op->od, stage, 90,
-                              ds_cstr(&dhcp_match), "next;");
-                ds_destroy(&dhcp_match);
-                ds_put_format(&match, "inport == %s && eth.src == %s"
-                              " && ip4.src == {", op->json_key,
-                              ps->ea_s);
-            } else {
-                ds_put_format(&match, "outport == %s && eth.dst == %s"
-                              " && ip4.dst == {255.255.255.255, 224.0.0.0/4, ",
-                              op->json_key, ps->ea_s);
-            }
-
-            for (int j = 0; j < ps->n_ipv4_addrs; j++) {
-                ovs_be32 mask = ps->ipv4_addrs[j].mask;
-                /* When the netmask is applied, if the host portion is
-                 * non-zero, the host can only use the specified
-                 * address.  If zero, the host is allowed to use any
-                 * address in the subnet.
-                 */
-                if (ps->ipv4_addrs[j].plen == 32
-                    || ps->ipv4_addrs[j].addr & ~mask) {
-                    ds_put_format(&match, "%s", ps->ipv4_addrs[j].addr_s);
-                    if (pipeline == P_OUT && ps->ipv4_addrs[j].plen != 32) {
-                        /* Host is also allowed to receive packets to the
-                         * broadcast address in the specified subnet. */
-                        ds_put_format(&match, ", %s",
-                                      ps->ipv4_addrs[j].bcast_s);
-                    }
-                } else {
-                    /* host portion is zero */
-                    ds_put_format(&match, "%s/%d", ps->ipv4_addrs[j].network_s,
-                                  ps->ipv4_addrs[j].plen);
-                }
-                ds_put_cstr(&match, ", ");
-            }
-
-            /* Replace ", " by "}". */
-            ds_chomp(&match, ' ');
-            ds_chomp(&match, ',');
-            ds_put_cstr(&match, "}");
-            ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), "next;");
-            ds_destroy(&match);
-        }
-
-        if (ps->n_ipv6_addrs) {
-            struct ds match = DS_EMPTY_INITIALIZER;
-            if (pipeline == P_IN) {
-                /* Permit use of unspecified address for duplicate address
-                 * detection */
-                struct ds dad_match = DS_EMPTY_INITIALIZER;
-                ds_put_format(&dad_match, "inport == %s"
-                              " && eth.src == %s"
-                              " && ip6.src == ::"
-                              " && ip6.dst == ff02::/16"
-                              " && icmp6.type == {131, 135, 143}", op->json_key,
-                              ps->ea_s);
-                ovn_lflow_add(lflows, op->od, stage, 90,
-                              ds_cstr(&dad_match), "next;");
-                ds_destroy(&dad_match);
-            }
-            ds_put_format(&match, "%s == %s && %s == %s",
-                          port_direction, op->json_key,
-                          pipeline == P_IN ? "eth.src" : "eth.dst", ps->ea_s);
-            build_port_security_ipv6_flow(pipeline, &match, ps->ea,
-                                          ps->ipv6_addrs, ps->n_ipv6_addrs);
-            ovn_lflow_add(lflows, op->od, stage, 90,
-                          ds_cstr(&match), "next;");
-            ds_destroy(&match);
-        }
-
-        char *match = xasprintf("%s == %s && %s == %s && ip",
-                                port_direction, op->json_key,
-                                pipeline == P_IN ? "eth.src" : "eth.dst",
-                                ps->ea_s);
-        ovn_lflow_add(lflows, op->od, stage, 80, match, "drop;");
-        free(match);
-    }
-
-}
-
-static bool
-lsp_is_enabled(const struct nbrec_logical_switch_port *lsp)
-{
-    return !lsp->enabled || *lsp->enabled;
-}
-
-static bool
-lsp_is_up(const struct nbrec_logical_switch_port *lsp)
-{
-    return !lsp->up || *lsp->up;
-}
-
-static bool
-lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
-{
-    return !strcmp(nbsp->type, "external");
-}
-
-static bool
-build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
-                    struct ds *options_action, struct ds *response_action,
-                    struct ds *ipv4_addr_match)
-{
-    if (!op->nbsp->dhcpv4_options) {
-        /* CMS has disabled native DHCPv4 for this lport. */
-        return false;
-    }
-
-    ovs_be32 host_ip, mask;
-    char *error = ip_parse_masked(op->nbsp->dhcpv4_options->cidr, &host_ip,
-                                  &mask);
-    if (error || ((offer_ip ^ host_ip) & mask)) {
-       /* Either
-        *  - cidr defined is invalid or
-        *  - the offer ip of the logical port doesn't belong to the cidr
-        *    defined in the DHCPv4 options.
-        *  */
-        free(error);
-        return false;
-    }
-
-    const char *server_ip = smap_get(
-        &op->nbsp->dhcpv4_options->options, "server_id");
-    const char *server_mac = smap_get(
-        &op->nbsp->dhcpv4_options->options, "server_mac");
-    const char *lease_time = smap_get(
-        &op->nbsp->dhcpv4_options->options, "lease_time");
-
-    if (!(server_ip && server_mac && lease_time)) {
-        /* "server_id", "server_mac" and "lease_time" should be
-         * present in the dhcp_options. */
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Required DHCPv4 options not defined for lport - %s",
-                     op->json_key);
-        return false;
-    }
-
-    struct smap dhcpv4_options = SMAP_INITIALIZER(&dhcpv4_options);
-    smap_clone(&dhcpv4_options, &op->nbsp->dhcpv4_options->options);
-
-    /* server_mac is not DHCPv4 option, delete it from the smap. */
-    smap_remove(&dhcpv4_options, "server_mac");
-    char *netmask = xasprintf(IP_FMT, IP_ARGS(mask));
-    smap_add(&dhcpv4_options, "netmask", netmask);
-    free(netmask);
-
-    ds_put_format(options_action,
-                  REGBIT_DHCP_OPTS_RESULT" = put_dhcp_opts(offerip = "
-                  IP_FMT", ", IP_ARGS(offer_ip));
-
-    /* We're not using SMAP_FOR_EACH because we want a consistent order of the
-     * options on different architectures (big or little endian, SSE4.2) */
-    const struct smap_node **sorted_opts = smap_sort(&dhcpv4_options);
-    for (size_t i = 0; i < smap_count(&dhcpv4_options); i++) {
-        const struct smap_node *node = sorted_opts[i];
-        ds_put_format(options_action, "%s = %s, ", node->key, node->value);
-    }
-    free(sorted_opts);
-
-    ds_chomp(options_action, ' ');
-    ds_chomp(options_action, ',');
-    ds_put_cstr(options_action, "); next;");
-
-    ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; "
-                  "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; "
-                  "udp.dst = 68; outport = inport; flags.loopback = 1; "
-                  "output;",
-                  server_mac, IP_ARGS(offer_ip), server_ip);
-
-    ds_put_format(ipv4_addr_match,
-                  "ip4.src == "IP_FMT" && ip4.dst == {%s, 255.255.255.255}",
-                  IP_ARGS(offer_ip), server_ip);
-    smap_destroy(&dhcpv4_options);
-    return true;
-}
-
-static bool
-build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
-                    struct ds *options_action, struct ds *response_action)
-{
-    if (!op->nbsp->dhcpv6_options) {
-        /* CMS has disabled native DHCPv6 for this lport. */
-        return false;
-    }
-
-    struct in6_addr host_ip, mask;
-
-    char *error = ipv6_parse_masked(op->nbsp->dhcpv6_options->cidr, &host_ip,
-                                        &mask);
-    if (error) {
-        free(error);
-        return false;
-    }
-    struct in6_addr ip6_mask = ipv6_addr_bitxor(offer_ip, &host_ip);
-    ip6_mask = ipv6_addr_bitand(&ip6_mask, &mask);
-    if (!ipv6_mask_is_any(&ip6_mask)) {
-        /* offer_ip doesn't belongs to the cidr defined in lport's DHCPv6
-         * options.*/
-        return false;
-    }
-
-    const struct smap *options_map = &op->nbsp->dhcpv6_options->options;
-    /* "server_id" should be the MAC address. */
-    const char *server_mac = smap_get(options_map, "server_id");
-    struct eth_addr ea;
-    if (!server_mac || !eth_addr_from_string(server_mac, &ea)) {
-        /* "server_id" should be present in the dhcpv6_options. */
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "server_id not present in the DHCPv6 options"
-                          " for lport %s", op->json_key);
-        return false;
-    }
-
-    /* Get the link local IP of the DHCPv6 server from the server MAC. */
-    struct in6_addr lla;
-    in6_generate_lla(ea, &lla);
-
-    char server_ip[INET6_ADDRSTRLEN + 1];
-    ipv6_string_mapped(server_ip, &lla);
-
-    char ia_addr[INET6_ADDRSTRLEN + 1];
-    ipv6_string_mapped(ia_addr, offer_ip);
-
-    ds_put_format(options_action,
-                  REGBIT_DHCP_OPTS_RESULT" = put_dhcpv6_opts(");
-
-    /* Check whether the dhcpv6 options should be configured as stateful.
-     * Only reply with ia_addr option for dhcpv6 stateful address mode. */
-    if (!smap_get_bool(options_map, "dhcpv6_stateless", false)) {
-        ipv6_string_mapped(ia_addr, offer_ip);
-        ds_put_format(options_action, "ia_addr = %s, ", ia_addr);
-    }
-
-    /* We're not using SMAP_FOR_EACH because we want a consistent order of the
-     * options on different architectures (big or little endian, SSE4.2) */
-    const struct smap_node **sorted_opts = smap_sort(options_map);
-    for (size_t i = 0; i < smap_count(options_map); i++) {
-        const struct smap_node *node = sorted_opts[i];
-        if (strcmp(node->key, "dhcpv6_stateless")) {
-            ds_put_format(options_action, "%s = %s, ", node->key, node->value);
-        }
-    }
-    free(sorted_opts);
-
-    ds_chomp(options_action, ' ');
-    ds_chomp(options_action, ',');
-    ds_put_cstr(options_action, "); next;");
-
-    ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; "
-                  "ip6.dst = ip6.src; ip6.src = %s; udp.src = 547; "
-                  "udp.dst = 546; outport = inport; flags.loopback = 1; "
-                  "output;",
-                  server_mac, server_ip);
-
-    return true;
-}
-
-struct ovn_port_group_ls {
-    struct hmap_node key_node;  /* Index on 'key'. */
-    struct uuid key;            /* nb_ls->header_.uuid. */
-    const struct nbrec_logical_switch *nb_ls;
-};
-
-struct ovn_port_group {
-    struct hmap_node key_node;  /* Index on 'key'. */
-    struct uuid key;            /* nb_pg->header_.uuid. */
-    const struct nbrec_port_group *nb_pg;
-    struct hmap nb_lswitches;   /* NB lswitches related to the port group */
-};
-
-static void
-ovn_port_group_ls_add(struct ovn_port_group *pg,
-                      const struct nbrec_logical_switch *nb_ls)
-{
-    struct ovn_port_group_ls *pg_ls = xzalloc(sizeof *pg_ls);
-    pg_ls->key = nb_ls->header_.uuid;
-    pg_ls->nb_ls = nb_ls;
-    hmap_insert(&pg->nb_lswitches, &pg_ls->key_node, uuid_hash(&pg_ls->key));
-}
-
-static struct ovn_port_group_ls *
-ovn_port_group_ls_find(struct ovn_port_group *pg, const struct uuid *ls_uuid)
-{
-    struct ovn_port_group_ls *pg_ls;
-
-    HMAP_FOR_EACH_WITH_HASH (pg_ls, key_node, uuid_hash(ls_uuid),
-                             &pg->nb_lswitches) {
-        if (uuid_equals(ls_uuid, &pg_ls->key)) {
-            return pg_ls;
-        }
-    }
-    return NULL;
-}
-
-struct ovn_ls_port_group {
-    struct hmap_node key_node;  /* Index on 'key'. */
-    struct uuid key;            /* nb_pg->header_.uuid. */
-    const struct nbrec_port_group *nb_pg;
-};
-
-static void
-ovn_ls_port_group_add(struct hmap *nb_pgs,
-                      const struct nbrec_port_group *nb_pg)
-{
-    struct ovn_ls_port_group *ls_pg = xzalloc(sizeof *ls_pg);
-    ls_pg->key = nb_pg->header_.uuid;
-    ls_pg->nb_pg = nb_pg;
-    hmap_insert(nb_pgs, &ls_pg->key_node, uuid_hash(&ls_pg->key));
-}
-
-static void
-ovn_ls_port_group_destroy(struct hmap *nb_pgs)
-{
-    struct ovn_ls_port_group *ls_pg;
-    HMAP_FOR_EACH_POP (ls_pg, key_node, nb_pgs) {
-        free(ls_pg);
-    }
-    hmap_destroy(nb_pgs);
-}
-
-static bool
-has_stateful_acl(struct ovn_datapath *od)
-{
-    for (size_t i = 0; i < od->nbs->n_acls; i++) {
-        struct nbrec_acl *acl = od->nbs->acls[i];
-        if (!strcmp(acl->action, "allow-related")) {
-            return true;
-        }
-    }
-
-    struct ovn_ls_port_group *ls_pg;
-    HMAP_FOR_EACH (ls_pg, key_node, &od->nb_pgs) {
-        for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) {
-            struct nbrec_acl *acl = ls_pg->nb_pg->acls[i];
-            if (!strcmp(acl->action, "allow-related")) {
-                return true;
-            }
-        }
-    }
-
-    return false;
-}
-
-static void
-build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
-{
-    bool has_stateful = has_stateful_acl(od);
-
-    /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
-     * allowed by default. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 0, "1", "next;");
-
-    /* If there are any stateful ACL rules in this datapath, we must
-     * send all IP packets through the conntrack action, which handles
-     * defragmentation, in order to match L4 headers. */
-    if (has_stateful) {
-        for (size_t i = 0; i < od->n_router_ports; i++) {
-            struct ovn_port *op = od->router_ports[i];
-            /* Can't use ct() for router ports. Consider the
-             * following configuration: lp1(10.0.0.2) on
-             * hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a
-             * ping from lp1 to lp2, First, the response will go
-             * through ct() with a zone for lp2 in the ls2 ingress
-             * pipeline on hostB.  That ct zone knows about this
-             * connection. Next, it goes through ct() with the zone
-             * for the router port in the egress pipeline of ls2 on
-             * hostB.  This zone does not know about the connection,
-             * as the icmp request went through the logical router
-             * on hostA, not hostB. This would only work with
-             * distributed conntrack state across all chassis. */
-            struct ds match_in = DS_EMPTY_INITIALIZER;
-            struct ds match_out = DS_EMPTY_INITIALIZER;
-
-            ds_put_format(&match_in, "ip && inport == %s", op->json_key);
-            ds_put_format(&match_out, "ip && outport == %s", op->json_key);
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110,
-                          ds_cstr(&match_in), "next;");
-            ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
-                          ds_cstr(&match_out), "next;");
-
-            ds_destroy(&match_in);
-            ds_destroy(&match_out);
-        }
-        if (od->localnet_port) {
-            struct ds match_in = DS_EMPTY_INITIALIZER;
-            struct ds match_out = DS_EMPTY_INITIALIZER;
-
-            ds_put_format(&match_in, "ip && inport == %s",
-                          od->localnet_port->json_key);
-            ds_put_format(&match_out, "ip && outport == %s",
-                          od->localnet_port->json_key);
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110,
-                          ds_cstr(&match_in), "next;");
-            ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
-                          ds_cstr(&match_out), "next;");
-
-            ds_destroy(&match_in);
-            ds_destroy(&match_out);
-        }
-
-        /* Ingress and Egress Pre-ACL Table (Priority 110).
-         *
-         * Not to do conntrack on ND and ICMP destination
-         * unreachable packets. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110,
-                      "nd || nd_rs || nd_ra || icmp4.type == 3 || "
-                      "icmp6.type == 1 || (tcp && tcp.flags == 4)",
-                      "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
-                      "nd || nd_rs || nd_ra || icmp4.type == 3 || "
-                      "icmp6.type == 1 || (tcp && tcp.flags == 4)",
-                      "next;");
-
-        /* Ingress and Egress Pre-ACL Table (Priority 100).
-         *
-         * Regardless of whether the ACL is "from-lport" or "to-lport",
-         * we need rules in both the ingress and egress table, because
-         * the return traffic needs to be followed.
-         *
-         * 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send
-         * it to conntrack for tracking and defragmentation. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 100, "ip",
-                      REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
-                      REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-    }
-}
-
-/* For a 'key' of the form "IP:port" or just "IP", sets 'port' and
- * 'ip_address'.  The caller must free() the memory allocated for
- * 'ip_address'. */
-static void
-ip_address_and_port_from_lb_key(const char *key, char **ip_address,
-                                uint16_t *port, int *addr_family)
-{
-    struct sockaddr_storage ss;
-    if (!inet_parse_active(key, 0, &ss, false)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s",
-                     key);
-        return;
-    }
-
-    struct ds s = DS_EMPTY_INITIALIZER;
-    ss_format_address_nobracks(&ss, &s);
-    *ip_address = ds_steal_cstr(&s);
-
-    *port = ss_get_port(&ss);
-
-    *addr_family = ss.ss_family;
-}
-
-/*
- * Returns true if logical switch is configured with DNS records, false
- * otherwise.
- */
-static bool
-ls_has_dns_records(const struct nbrec_logical_switch *nbs)
-{
-    for (size_t i = 0; i < nbs->n_dns_records; i++) {
-        if (!smap_is_empty(&nbs->dns_records[i]->records)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-static void
-build_pre_lb(struct ovn_datapath *od, struct hmap *lflows)
-{
-    /* Do not send ND packets to conntrack */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
-                  "nd || nd_rs || nd_ra", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
-                  "nd || nd_rs || nd_ra", "next;");
-
-    /* Allow all packets to go to next tables by default. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
-
-    struct sset all_ips = SSET_INITIALIZER(&all_ips);
-    bool vip_configured = false;
-    int addr_family = AF_INET;
-    for (int i = 0; i < od->nbs->n_load_balancer; i++) {
-        struct nbrec_load_balancer *lb = od->nbs->load_balancer[i];
-        struct smap *vips = &lb->vips;
-        struct smap_node *node;
-
-        SMAP_FOR_EACH (node, vips) {
-            vip_configured = true;
-
-            /* node->key contains IP:port or just IP. */
-            char *ip_address = NULL;
-            uint16_t port;
-            ip_address_and_port_from_lb_key(node->key, &ip_address, &port,
-                                            &addr_family);
-            if (!ip_address) {
-                continue;
-            }
-
-            if (!sset_contains(&all_ips, ip_address)) {
-                sset_add(&all_ips, ip_address);
-            }
-
-            if (controller_event_en && !node->value[0]) {
-                struct ds match = DS_EMPTY_INITIALIZER;
-                char *action;
-
-                if (addr_family == AF_INET) {
-                    ds_put_format(&match, "ip4.dst == %s && %s",
-                                  ip_address, lb->protocol);
-                } else {
-                    ds_put_format(&match, "ip6.dst == %s && %s",
-                                  ip_address, lb->protocol);
-                }
-                if (port) {
-                    ds_put_format(&match, " && %s.dst == %u", lb->protocol,
-                                  port);
-                }
-                action = xasprintf("trigger_event(event = \"%s\", "
-                               "vip = \"%s\", protocol = \"%s\", "
-                               "load_balancer = \"" UUID_FMT "\");",
-                               event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS),
-                               node->key, lb->protocol,
-                               UUID_ARGS(&lb->header_.uuid));
-                ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 120,
-                              ds_cstr(&match), action);
-                ds_destroy(&match);
-                free(action);
-            }
-
-            free(ip_address);
-
-            /* Ignore L4 port information in the key because fragmented packets
-             * may not have L4 information.  The pre-stateful table will send
-             * the packet through ct() action to de-fragment. In stateful
-             * table, we will eventually look at L4 information. */
-        }
-    }
-
-    /* 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send
-     * packet to conntrack for defragmentation. */
-    const char *ip_address;
-    SSET_FOR_EACH(ip_address, &all_ips) {
-        char *match;
-
-        if (addr_family == AF_INET) {
-            match = xasprintf("ip && ip4.dst == %s", ip_address);
-        } else {
-            match = xasprintf("ip && ip6.dst == %s", ip_address);
-        }
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
-                      100, match, REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-        free(match);
-    }
-
-    sset_destroy(&all_ips);
-
-    if (vip_configured) {
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
-                      100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-    }
-}
-
-static void
-build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows)
-{
-    /* Ingress and Egress pre-stateful Table (Priority 0): Packets are
-     * allowed by default. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 0, "1", "next;");
-
-    /* If REGBIT_CONNTRACK_DEFRAG is set as 1, then the packets should be
-     * sent to conntrack for tracking and defragmentation. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 100,
-                  REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 100,
-                  REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
-}
-
-static void
-build_acl_log(struct ds *actions, const struct nbrec_acl *acl)
-{
-    if (!acl->log) {
-        return;
-    }
-
-    ds_put_cstr(actions, "log(");
-
-    if (acl->name) {
-        ds_put_format(actions, "name=\"%s\", ", acl->name);
-    }
-
-    /* If a severity level isn't specified, default to "info". */
-    if (acl->severity) {
-        ds_put_format(actions, "severity=%s, ", acl->severity);
-    } else {
-        ds_put_format(actions, "severity=info, ");
-    }
-
-    if (!strcmp(acl->action, "drop")) {
-        ds_put_cstr(actions, "verdict=drop, ");
-    } else if (!strcmp(acl->action, "reject")) {
-        ds_put_cstr(actions, "verdict=reject, ");
-    } else if (!strcmp(acl->action, "allow")
-        || !strcmp(acl->action, "allow-related")) {
-        ds_put_cstr(actions, "verdict=allow, ");
-    }
-
-    if (acl->meter) {
-        ds_put_format(actions, "meter=\"%s\", ", acl->meter);
-    }
-
-    ds_chomp(actions, ' ');
-    ds_chomp(actions, ',');
-    ds_put_cstr(actions, "); ");
-}
-
-static void
-build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows,
-                       enum ovn_stage stage, struct nbrec_acl *acl,
-                       struct ds *extra_match, struct ds *extra_actions)
-{
-    struct ds match = DS_EMPTY_INITIALIZER;
-    struct ds actions = DS_EMPTY_INITIALIZER;
-    bool ingress = (stage == S_SWITCH_IN_ACL);
-
-    /* TCP */
-    build_acl_log(&actions, acl);
-    if (extra_match->length > 0) {
-        ds_put_format(&match, "(%s) && ", extra_match->string);
-    }
-    ds_put_format(&match, "ip4 && tcp && (%s)", acl->match);
-    ds_put_format(&actions, "reg0 = 0; "
-                  "eth.dst <-> eth.src; ip4.dst <-> ip4.src; "
-                  "tcp_reset { outport <-> inport; %s };",
-                  ingress ? "output;" : "next(pipeline=ingress,table=0);");
-    ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET + 10,
-                  ds_cstr(&match), ds_cstr(&actions));
-    ds_clear(&match);
-    ds_clear(&actions);
-    build_acl_log(&actions, acl);
-    if (extra_match->length > 0) {
-        ds_put_format(&match, "(%s) && ", extra_match->string);
-    }
-    ds_put_format(&match, "ip6 && tcp && (%s)", acl->match);
-    ds_put_format(&actions, "reg0 = 0; "
-                  "eth.dst <-> eth.src; ip6.dst <-> ip6.src; "
-                  "tcp_reset { outport <-> inport; %s };",
-                  ingress ? "output;" : "next(pipeline=ingress,table=0);");
-    ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET + 10,
-                  ds_cstr(&match), ds_cstr(&actions));
-
-    /* IP traffic */
-    ds_clear(&match);
-    ds_clear(&actions);
-    build_acl_log(&actions, acl);
-    if (extra_match->length > 0) {
-        ds_put_format(&match, "(%s) && ", extra_match->string);
-    }
-    ds_put_format(&match, "ip4 && (%s)", acl->match);
-    if (extra_actions->length > 0) {
-        ds_put_format(&actions, "%s ", extra_actions->string);
-    }
-    ds_put_format(&actions, "reg0 = 0; "
-                  "eth.dst <-> eth.src; ip4.dst <-> ip4.src; "
-                  "icmp4 { outport <-> inport; %s };",
-                  ingress ? "output;" : "next(pipeline=ingress,table=0);");
-    ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET,
-                  ds_cstr(&match), ds_cstr(&actions));
-    ds_clear(&match);
-    ds_clear(&actions);
-    build_acl_log(&actions, acl);
-    if (extra_match->length > 0) {
-        ds_put_format(&match, "(%s) && ", extra_match->string);
-    }
-    ds_put_format(&match, "ip6 && (%s)", acl->match);
-    if (extra_actions->length > 0) {
-        ds_put_format(&actions, "%s ", extra_actions->string);
-    }
-    ds_put_format(&actions, "reg0 = 0; icmp6 { "
-                  "eth.dst <-> eth.src; ip6.dst <-> ip6.src; "
-                  "outport <-> inport; %s };",
-                  ingress ? "output;" : "next(pipeline=ingress,table=0);");
-    ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET,
-                  ds_cstr(&match), ds_cstr(&actions));
-
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
-static void
-consider_acl(struct hmap *lflows, struct ovn_datapath *od,
-             struct nbrec_acl *acl, bool has_stateful)
-{
-    bool ingress = !strcmp(acl->direction, "from-lport") ? true :false;
-    enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL;
-
-    char *stage_hint = xasprintf("%08x", acl->header_.uuid.parts[0]);
-    if (!strcmp(acl->action, "allow")
-        || !strcmp(acl->action, "allow-related")) {
-        /* If there are any stateful flows, we must even commit "allow"
-         * actions.  This is because, while the initiater's
-         * direction may not have any stateful rules, the server's
-         * may and then its return traffic would not have an
-         * associated conntrack entry and would return "+invalid". */
-        if (!has_stateful) {
-            struct ds actions = DS_EMPTY_INITIALIZER;
-            build_acl_log(&actions, acl);
-            ds_put_cstr(&actions, "next;");
-            ovn_lflow_add_with_hint(lflows, od, stage,
-                                    acl->priority + OVN_ACL_PRI_OFFSET,
-                                    acl->match, ds_cstr(&actions),
-                                    stage_hint);
-            ds_destroy(&actions);
-        } else {
-            struct ds match = DS_EMPTY_INITIALIZER;
-            struct ds actions = DS_EMPTY_INITIALIZER;
-
-            /* Commit the connection tracking entry if it's a new
-             * connection that matches this ACL.  After this commit,
-             * the reply traffic is allowed by a flow we create at
-             * priority 65535, defined earlier.
-             *
-             * It's also possible that a known connection was marked for
-             * deletion after a policy was deleted, but the policy was
-             * re-added while that connection is still known.  We catch
-             * that case here and un-set ct_label.blocked (which will be done
-             * by ct_commit in the "stateful" stage) to indicate that the
-             * connection should be allowed to resume.
-             */
-            ds_put_format(&match, "((ct.new && !ct.est)"
-                                  " || (!ct.new && ct.est && !ct.rpl "
-                                       "&& ct_label.blocked == 1)) "
-                                  "&& (%s)", acl->match);
-            ds_put_cstr(&actions, REGBIT_CONNTRACK_COMMIT" = 1; ");
-            build_acl_log(&actions, acl);
-            ds_put_cstr(&actions, "next;");
-            ovn_lflow_add_with_hint(lflows, od, stage,
-                                    acl->priority + OVN_ACL_PRI_OFFSET,
-                                    ds_cstr(&match),
-                                    ds_cstr(&actions),
-                                    stage_hint);
-
-            /* Match on traffic in the request direction for an established
-             * connection tracking entry that has not been marked for
-             * deletion.  There is no need to commit here, so we can just
-             * proceed to the next table. We use this to ensure that this
-             * connection is still allowed by the currently defined
-             * policy. */
-            ds_clear(&match);
-            ds_clear(&actions);
-            ds_put_format(&match,
-                          "!ct.new && ct.est && !ct.rpl"
-                          " && ct_label.blocked == 0 && (%s)",
-                          acl->match);
-
-            build_acl_log(&actions, acl);
-            ds_put_cstr(&actions, "next;");
-            ovn_lflow_add_with_hint(lflows, od, stage,
-                                    acl->priority + OVN_ACL_PRI_OFFSET,
-                                    ds_cstr(&match), ds_cstr(&actions),
-                                    stage_hint);
-
-            ds_destroy(&match);
-            ds_destroy(&actions);
-        }
-    } else if (!strcmp(acl->action, "drop")
-               || !strcmp(acl->action, "reject")) {
-        struct ds match = DS_EMPTY_INITIALIZER;
-        struct ds actions = DS_EMPTY_INITIALIZER;
-
-        /* The implementation of "drop" differs if stateful ACLs are in
-         * use for this datapath.  In that case, the actions differ
-         * depending on whether the connection was previously committed
-         * to the connection tracker with ct_commit. */
-        if (has_stateful) {
-            /* If the packet is not part of an established connection, then
-             * we can simply reject/drop it. */
-            ds_put_cstr(&match,
-                        "(!ct.est || (ct.est && ct_label.blocked == 1))");
-            if (!strcmp(acl->action, "reject")) {
-                build_reject_acl_rules(od, lflows, stage, acl, &match,
-                                       &actions);
-            } else {
-                ds_put_format(&match, " && (%s)", acl->match);
-                build_acl_log(&actions, acl);
-                ds_put_cstr(&actions, "/* drop */");
-                ovn_lflow_add(lflows, od, stage,
-                              acl->priority + OVN_ACL_PRI_OFFSET,
-                              ds_cstr(&match), ds_cstr(&actions));
-            }
-            /* For an existing connection without ct_label set, we've
-             * encountered a policy change. ACLs previously allowed
-             * this connection and we committed the connection tracking
-             * entry.  Current policy says that we should drop this
-             * connection.  First, we set bit 0 of ct_label to indicate
-             * that this connection is set for deletion.  By not
-             * specifying "next;", we implicitly drop the packet after
-             * updating conntrack state.  We would normally defer
-             * ct_commit() to the "stateful" stage, but since we're
-             * rejecting/dropping the packet, we go ahead and do it here.
-             */
-            ds_clear(&match);
-            ds_clear(&actions);
-            ds_put_cstr(&match, "ct.est && ct_label.blocked == 0");
-            ds_put_cstr(&actions, "ct_commit(ct_label=1/1); ");
-            if (!strcmp(acl->action, "reject")) {
-                build_reject_acl_rules(od, lflows, stage, acl, &match,
-                                       &actions);
-            } else {
-                ds_put_format(&match, " && (%s)", acl->match);
-                build_acl_log(&actions, acl);
-                ds_put_cstr(&actions, "/* drop */");
-                ovn_lflow_add(lflows, od, stage,
-                              acl->priority + OVN_ACL_PRI_OFFSET,
-                              ds_cstr(&match), ds_cstr(&actions));
-            }
-        } else {
-            /* There are no stateful ACLs in use on this datapath,
-             * so a "reject/drop" ACL is simply the "reject/drop"
-             * logical flow action in all cases. */
-            if (!strcmp(acl->action, "reject")) {
-                build_reject_acl_rules(od, lflows, stage, acl, &match,
-                                       &actions);
-            } else {
-                build_acl_log(&actions, acl);
-                ds_put_cstr(&actions, "/* drop */");
-                ovn_lflow_add(lflows, od, stage,
-                              acl->priority + OVN_ACL_PRI_OFFSET,
-                              acl->match, ds_cstr(&actions));
-            }
-        }
-        ds_destroy(&match);
-        ds_destroy(&actions);
-    }
-    free(stage_hint);
-}
-
-static struct ovn_port_group *
-ovn_port_group_create(struct hmap *pgs,
-                      const struct nbrec_port_group *nb_pg)
-{
-    struct ovn_port_group *pg = xzalloc(sizeof *pg);
-    pg->key = nb_pg->header_.uuid;
-    pg->nb_pg = nb_pg;
-    hmap_init(&pg->nb_lswitches);
-    hmap_insert(pgs, &pg->key_node, uuid_hash(&pg->key));
-    return pg;
-}
-
-static void
-ovn_port_group_destroy(struct hmap *pgs, struct ovn_port_group *pg)
-{
-    if (pg) {
-        hmap_remove(pgs, &pg->key_node);
-        struct ovn_port_group_ls *ls;
-        HMAP_FOR_EACH_POP (ls, key_node, &pg->nb_lswitches) {
-            free(ls);
-        }
-        hmap_destroy(&pg->nb_lswitches);
-        free(pg);
-    }
-}
-
-static void
-build_port_group_lswitches(struct northd_context *ctx, struct hmap *pgs,
-                           struct hmap *ports)
-{
-    hmap_init(pgs);
-
-    const struct nbrec_port_group *nb_pg;
-    NBREC_PORT_GROUP_FOR_EACH (nb_pg, ctx->ovnnb_idl) {
-        struct ovn_port_group *pg = ovn_port_group_create(pgs, nb_pg);
-        for (size_t i = 0; i < nb_pg->n_ports; i++) {
-            struct ovn_port *op = ovn_port_find(ports, nb_pg->ports[i]->name);
-            if (!op) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_ERR_RL(&rl, "lport %s in port group %s not found.",
-                            nb_pg->ports[i]->name,
-                            nb_pg->name);
-                continue;
-            }
-
-            if (!op->od->nbs) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-                VLOG_WARN_RL(&rl, "lport %s in port group %s has no lswitch.",
-                             nb_pg->ports[i]->name,
-                             nb_pg->name);
-                continue;
-            }
-
-            struct ovn_port_group_ls *pg_ls =
-                ovn_port_group_ls_find(pg, &op->od->nbs->header_.uuid);
-            if (!pg_ls) {
-                ovn_port_group_ls_add(pg, op->od->nbs);
-                ovn_ls_port_group_add(&op->od->nb_pgs, nb_pg);
-            }
-        }
-    }
-}
-
-static void
-build_acls(struct ovn_datapath *od, struct hmap *lflows,
-           struct hmap *port_groups)
-{
-    bool has_stateful = has_stateful_acl(od);
-
-    /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by
-     * default.  A related rule at priority 1 is added below if there
-     * are any stateful ACLs in this datapath. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;");
-
-    if (has_stateful) {
-        /* Ingress and Egress ACL Table (Priority 1).
-         *
-         * By default, traffic is allowed.  This is partially handled by
-         * the Priority 0 ACL flows added earlier, but we also need to
-         * commit IP flows.  This is because, while the initiater's
-         * direction may not have any stateful rules, the server's may
-         * and then its return traffic would not have an associated
-         * conntrack entry and would return "+invalid".
-         *
-         * We use "ct_commit" for a connection that is not already known
-         * by the connection tracker.  Once a connection is committed,
-         * subsequent packets will hit the flow at priority 0 that just
-         * uses "next;"
-         *
-         * We also check for established connections that have ct_label.blocked
-         * set on them.  That's a connection that was disallowed, but is
-         * now allowed by policy again since it hit this default-allow flow.
-         * We need to set ct_label.blocked=0 to let the connection continue,
-         * which will be done by ct_commit() in the "stateful" stage.
-         * Subsequent packets will hit the flow at priority 0 that just
-         * uses "next;". */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 1,
-                      "ip && (!ct.est || (ct.est && ct_label.blocked == 1))",
-                       REGBIT_CONNTRACK_COMMIT" = 1; next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 1,
-                      "ip && (!ct.est || (ct.est && ct_label.blocked == 1))",
-                       REGBIT_CONNTRACK_COMMIT" = 1; next;");
-
-        /* Ingress and Egress ACL Table (Priority 65535).
-         *
-         * Always drop traffic that's in an invalid state.  Also drop
-         * reply direction packets for connections that have been marked
-         * for deletion (bit 0 of ct_label is set).
-         *
-         * This is enforced at a higher priority than ACLs can be defined. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-                      "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)",
-                      "drop;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-                      "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)",
-                      "drop;");
-
-        /* Ingress and Egress ACL Table (Priority 65535).
-         *
-         * Allow reply traffic that is part of an established
-         * conntrack entry that has not been marked for deletion
-         * (bit 0 of ct_label).  We only match traffic in the
-         * reply direction because we want traffic in the request
-         * direction to hit the currently defined policy from ACLs.
-         *
-         * This is enforced at a higher priority than ACLs can be defined. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-                      "ct.est && !ct.rel && !ct.new && !ct.inv "
-                      "&& ct.rpl && ct_label.blocked == 0",
-                      "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-                      "ct.est && !ct.rel && !ct.new && !ct.inv "
-                      "&& ct.rpl && ct_label.blocked == 0",
-                      "next;");
-
-        /* Ingress and Egress ACL Table (Priority 65535).
-         *
-         * Allow traffic that is related to an existing conntrack entry that
-         * has not been marked for deletion (bit 0 of ct_label).
-         *
-         * This is enforced at a higher priority than ACLs can be defined.
-         *
-         * NOTE: This does not support related data sessions (eg,
-         * a dynamically negotiated FTP data channel), but will allow
-         * related traffic such as an ICMP Port Unreachable through
-         * that's generated from a non-listening UDP port.  */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-                      "!ct.est && ct.rel && !ct.new && !ct.inv "
-                      "&& ct_label.blocked == 0",
-                      "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-                      "!ct.est && ct.rel && !ct.new && !ct.inv "
-                      "&& ct_label.blocked == 0",
-                      "next;");
-
-        /* Ingress and Egress ACL Table (Priority 65535).
-         *
-         * Not to do conntrack on ND packets. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "nd", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "nd", "next;");
-    }
-
-    /* Ingress or Egress ACL Table (Various priorities). */
-    for (size_t i = 0; i < od->nbs->n_acls; i++) {
-        struct nbrec_acl *acl = od->nbs->acls[i];
-        consider_acl(lflows, od, acl, has_stateful);
-    }
-    struct ovn_port_group *pg;
-    HMAP_FOR_EACH (pg, key_node, port_groups) {
-        if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) {
-            for (size_t i = 0; i < pg->nb_pg->n_acls; i++) {
-                consider_acl(lflows, od, pg->nb_pg->acls[i], has_stateful);
-            }
-        }
-    }
-
-    /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all
-     * logical ports of the datapath if the CMS has configured DHCPv4 options.
-     * */
-    for (size_t i = 0; i < od->nbs->n_ports; i++) {
-        if (lsp_is_external(od->nbs->ports[i])) {
-            continue;
-        }
-
-        if (od->nbs->ports[i]->dhcpv4_options) {
-            const char *server_id = smap_get(
-                &od->nbs->ports[i]->dhcpv4_options->options, "server_id");
-            const char *server_mac = smap_get(
-                &od->nbs->ports[i]->dhcpv4_options->options, "server_mac");
-            const char *lease_time = smap_get(
-                &od->nbs->ports[i]->dhcpv4_options->options, "lease_time");
-            if (server_id && server_mac && lease_time) {
-                struct ds match = DS_EMPTY_INITIALIZER;
-                const char *actions =
-                    has_stateful ? "ct_commit; next;" : "next;";
-                ds_put_format(&match, "outport == \"%s\" && eth.src == %s "
-                              "&& ip4.src == %s && udp && udp.src == 67 "
-                              "&& udp.dst == 68", od->nbs->ports[i]->name,
-                              server_mac, server_id);
-                ovn_lflow_add(
-                    lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
-                    actions);
-                ds_destroy(&match);
-            }
-        }
-
-        if (od->nbs->ports[i]->dhcpv6_options) {
-            const char *server_mac = smap_get(
-                &od->nbs->ports[i]->dhcpv6_options->options, "server_id");
-            struct eth_addr ea;
-            if (server_mac && eth_addr_from_string(server_mac, &ea)) {
-                /* Get the link local IP of the DHCPv6 server from the
-                 * server MAC. */
-                struct in6_addr lla;
-                in6_generate_lla(ea, &lla);
-
-                char server_ip[INET6_ADDRSTRLEN + 1];
-                ipv6_string_mapped(server_ip, &lla);
-
-                struct ds match = DS_EMPTY_INITIALIZER;
-                const char *actions = has_stateful ? "ct_commit; next;" :
-                    "next;";
-                ds_put_format(&match, "outport == \"%s\" && eth.src == %s "
-                              "&& ip6.src == %s && udp && udp.src == 547 "
-                              "&& udp.dst == 546", od->nbs->ports[i]->name,
-                              server_mac, server_ip);
-                ovn_lflow_add(
-                    lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
-                    actions);
-                ds_destroy(&match);
-            }
-        }
-    }
-
-    /* Add a 34000 priority flow to advance the DNS reply from ovn-controller,
-     * if the CMS has configured DNS records for the datapath.
-     */
-    if (ls_has_dns_records(od->nbs)) {
-        const char *actions = has_stateful ? "ct_commit; next;" : "next;";
-        ovn_lflow_add(
-            lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53",
-            actions);
-    }
-}
-
-static void
-build_qos(struct ovn_datapath *od, struct hmap *lflows) {
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_MARK, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_MARK, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_METER, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_METER, 0, "1", "next;");
-
-    for (size_t i = 0; i < od->nbs->n_qos_rules; i++) {
-        struct nbrec_qos *qos = od->nbs->qos_rules[i];
-        bool ingress = !strcmp(qos->direction, "from-lport") ? true :false;
-        enum ovn_stage stage = ingress ? S_SWITCH_IN_QOS_MARK : S_SWITCH_OUT_QOS_MARK;
-        int64_t rate = 0;
-        int64_t burst = 0;
-
-        for (size_t j = 0; j < qos->n_action; j++) {
-            if (!strcmp(qos->key_action[j], "dscp")) {
-                struct ds dscp_action = DS_EMPTY_INITIALIZER;
-
-                ds_put_format(&dscp_action, "ip.dscp = %"PRId64"; next;",
-                              qos->value_action[j]);
-                ovn_lflow_add(lflows, od, stage,
-                              qos->priority,
-                              qos->match, ds_cstr(&dscp_action));
-                ds_destroy(&dscp_action);
-            }
-        }
-
-        for (size_t n = 0; n < qos->n_bandwidth; n++) {
-            if (!strcmp(qos->key_bandwidth[n], "rate")) {
-                rate = qos->value_bandwidth[n];
-            } else if (!strcmp(qos->key_bandwidth[n], "burst")) {
-                burst = qos->value_bandwidth[n];
-            }
-        }
-        if (rate) {
-            struct ds meter_action = DS_EMPTY_INITIALIZER;
-            stage = ingress ? S_SWITCH_IN_QOS_METER : S_SWITCH_OUT_QOS_METER;
-            if (burst) {
-                ds_put_format(&meter_action,
-                              "set_meter(%"PRId64", %"PRId64"); next;",
-                              rate, burst);
-            } else {
-                ds_put_format(&meter_action,
-                              "set_meter(%"PRId64"); next;",
-                              rate);
-            }
-
-            /* Ingress and Egress QoS Meter Table.
-             *
-             * We limit the bandwidth of this flow by adding a meter table.
-             */
-            ovn_lflow_add(lflows, od, stage,
-                          qos->priority,
-                          qos->match, ds_cstr(&meter_action));
-            ds_destroy(&meter_action);
-        }
-    }
-}
-
-static void
-build_lb(struct ovn_datapath *od, struct hmap *lflows)
-{
-    /* Ingress and Egress LB Table (Priority 0): Packets are allowed by
-     * default.  */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, 0, "1", "next;");
-
-    if (od->nbs->load_balancer) {
-        /* Ingress and Egress LB Table (Priority 65535).
-         *
-         * Send established traffic through conntrack for just NAT. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, UINT16_MAX,
-                      "ct.est && !ct.rel && !ct.new && !ct.inv",
-                      REGBIT_CONNTRACK_NAT" = 1; next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, UINT16_MAX,
-                      "ct.est && !ct.rel && !ct.new && !ct.inv",
-                      REGBIT_CONNTRACK_NAT" = 1; next;");
-    }
-}
-
-static void
-build_stateful(struct ovn_datapath *od, struct hmap *lflows)
-{
-    /* Ingress and Egress stateful Table (Priority 0): Packets are
-     * allowed by default. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 0, "1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 0, "1", "next;");
-
-    /* If REGBIT_CONNTRACK_COMMIT is set as 1, then the packets should be
-     * committed to conntrack. We always set ct_label.blocked to 0 here as
-     * any packet that makes it this far is part of a connection we
-     * want to allow to continue. */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-                  REGBIT_CONNTRACK_COMMIT" == 1", "ct_commit(ct_label=0/1); next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100,
-                  REGBIT_CONNTRACK_COMMIT" == 1", "ct_commit(ct_label=0/1); next;");
-
-    /* If REGBIT_CONNTRACK_NAT is set as 1, then packets should just be sent
-     * through nat (without committing).
-     *
-     * REGBIT_CONNTRACK_COMMIT is set for new connections and
-     * REGBIT_CONNTRACK_NAT is set for established connections. So they
-     * don't overlap.
-     */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-                  REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100,
-                  REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-
-    /* Load balancing rules for new connections get committed to conntrack
-     * table.  So even if REGBIT_CONNTRACK_COMMIT is set in a previous table
-     * a higher priority rule for load balancing below also commits the
-     * connection, so it is okay if we do not hit the above match on
-     * REGBIT_CONNTRACK_COMMIT. */
-    for (int i = 0; i < od->nbs->n_load_balancer; i++) {
-        struct nbrec_load_balancer *lb = od->nbs->load_balancer[i];
-        struct smap *vips = &lb->vips;
-        struct smap_node *node;
-
-        SMAP_FOR_EACH (node, vips) {
-            uint16_t port = 0;
-            int addr_family;
-
-            /* node->key contains IP:port or just IP. */
-            char *ip_address = NULL;
-            ip_address_and_port_from_lb_key(node->key, &ip_address, &port,
-                                            &addr_family);
-            if (!ip_address) {
-                continue;
-            }
-
-            /* New connections in Ingress table. */
-            char *action = xasprintf("ct_lb(%s);", node->value);
-            struct ds match = DS_EMPTY_INITIALIZER;
-            if (addr_family == AF_INET) {
-                ds_put_format(&match, "ct.new && ip4.dst == %s", ip_address);
-            } else {
-                ds_put_format(&match, "ct.new && ip6.dst == %s", ip_address);
-            }
-            if (port) {
-                if (lb->protocol && !strcmp(lb->protocol, "udp")) {
-                    ds_put_format(&match, " && udp.dst == %d", port);
-                } else {
-                    ds_put_format(&match, " && tcp.dst == %d", port);
-                }
-                ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL,
-                              120, ds_cstr(&match), action);
-            } else {
-                ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL,
-                              110, ds_cstr(&match), action);
-            }
-
-            free(ip_address);
-            ds_destroy(&match);
-            free(action);
-       }
-    }
-}
-
-static void
-build_lrouter_groups__(struct hmap *ports, struct ovn_datapath *od)
-{
-    ovs_assert((od && od->nbr && od->lr_group));
-
-    if (od->l3dgw_port && od->l3redirect_port) {
-        /* It's a logical router with gateway port. If it
-         * has HA_Chassis_Group associated to it in SB DB, then store the
-         * ha chassis group name. */
-        if (od->l3redirect_port->sb->ha_chassis_group) {
-            sset_add(&od->lr_group->ha_chassis_groups,
-                     od->l3redirect_port->sb->ha_chassis_group->name);
-        }
-    }
-
-    for (size_t i = 0; i < od->nbr->n_ports; i++) {
-        struct ovn_port *router_port =
-            ovn_port_find(ports, od->nbr->ports[i]->name);
-
-        if (!router_port || !router_port->peer) {
-            continue;
-        }
-
-        /* Get the peer logical switch/logical router datapath. */
-        struct ovn_datapath *peer_dp = router_port->peer->od;
-        if (peer_dp->nbr) {
-            if (!peer_dp->lr_group) {
-                peer_dp->lr_group = od->lr_group;
-                od->lr_group->router_dps[od->lr_group->n_router_dps++]
-                    = peer_dp;
-                build_lrouter_groups__(ports, peer_dp);
-            }
-        } else {
-            for (size_t j = 0; j < peer_dp->n_router_ports; j++) {
-                if (!peer_dp->router_ports[j]->peer) {
-                    /* If there is no peer port connecting to the
-                    * router port, ignore it. */
-                    continue;
-                }
-
-                struct ovn_datapath *router_dp;
-                router_dp = peer_dp->router_ports[j]->peer->od;
-                if (router_dp == od) {
-                    continue;
-                }
-
-                if (router_dp->lr_group == od->lr_group) {
-                    /* 'router_dp' and 'od' already belong to the same
-                    * lrouter group. Nothing to be done. */
-                    continue;
-                }
-
-                router_dp->lr_group = od->lr_group;
-                od->lr_group->router_dps[od->lr_group->n_router_dps++]
-                    = router_dp;
-                build_lrouter_groups__(ports, router_dp);
-            }
-        }
-    }
-}
-
-/* Adds each logical router into a logical router group. All the
- * logical routers which belong to a group are connected to
- * each other either directly or indirectly (via transit logical switches
- * in between).
- *
- * Suppose if 'lr_list' has lr0, lr1, lr2, lr3, lr4, lr5
- * and the topology is like
- *  sw0 <-> lr0 <-> sw1 <-> lr1 <->sw2 <-> lr2
- *  sw3 <-> lr3 <-> lr4 <-> sw5
- *  sw6 <-> lr5 <-> sw7
- * Then 3 groups are created.
- * Group 1 -> lr0, lr1 and lr2
- *            lr0, lr1 and lr2's ovn_datapath->lr_group will point to this
- *            group. This means sw0's logical ports can send packets to sw2's
- *            logical ports if proper static route's are added.
- * Group 2 -> lr3 and lr4
- *            lr3 and lr4's ovn_datapath->lr_group will point to this group.
- * Group 3 -> lr5
- *
- * Each logical router can belong to only one group.
- */
-static void
-build_lrouter_groups(struct hmap *ports, struct ovs_list *lr_list)
-{
-    struct ovn_datapath *od;
-    size_t n_router_dps = ovs_list_size(lr_list);
-
-    LIST_FOR_EACH (od, lr_list, lr_list) {
-        if (!od->lr_group) {
-            od->lr_group = xzalloc(sizeof *od->lr_group);
-            /* Each logical router group can have max
-             * 'n_router_dps'. So allocate enough memory. */
-            od->lr_group->router_dps = xcalloc(sizeof *od, n_router_dps);
-            od->lr_group->router_dps[0] = od;
-            od->lr_group->n_router_dps = 1;
-            sset_init(&od->lr_group->ha_chassis_groups);
-            build_lrouter_groups__(ports, od);
-        }
-    }
-}
-
-static void
-build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
-                    struct hmap *port_groups, struct hmap *lflows,
-                    struct hmap *mcgroups, struct hmap *igmp_groups)
-{
-    /* This flow table structure is documented in ovn-northd(8), so please
-     * update ovn-northd.8.xml if you change anything. */
-
-    struct ds match = DS_EMPTY_INITIALIZER;
-    struct ds actions = DS_EMPTY_INITIALIZER;
-
-    /* Build pre-ACL and ACL tables for both ingress and egress.
-     * Ingress tables 3 through 10.  Egress tables 0 through 7. */
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        build_pre_acls(od, lflows);
-        build_pre_lb(od, lflows);
-        build_pre_stateful(od, lflows);
-        build_acls(od, lflows, port_groups);
-        build_qos(od, lflows);
-        build_lb(od, lflows);
-        build_stateful(od, lflows);
-    }
-
-    /* Logical switch ingress table 0: Admission control framework (priority
-     * 100). */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        /* Logical VLANs not supported. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present",
-                      "drop;");
-
-        /* Broadcast/multicast source address is invalid. */
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]",
-                      "drop;");
-
-        /* Port security flows have priority 50 (see below) and will continue
-         * to the next table if packet source is acceptable. */
-    }
-
-    /* Logical switch ingress table 0: Ingress port security - L2
-     *  (priority 50).
-     *  Ingress table 1: Ingress port security - IP (priority 90 and 80)
-     *  Ingress table 2: Ingress port security - ND (priority 90 and 80)
-     */
-    struct ovn_port *op;
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-            continue;
-        }
-
-        if (!lsp_is_enabled(op->nbsp)) {
-            /* Drop packets from disabled logical ports (since logical flow
-             * tables are default-drop). */
-            continue;
-        }
-
-        if (lsp_is_external(op->nbsp)) {
-            continue;
-        }
-
-        ds_clear(&match);
-        ds_clear(&actions);
-        ds_put_format(&match, "inport == %s", op->json_key);
-        build_port_security_l2("eth.src", op->ps_addrs, op->n_ps_addrs,
-                               &match);
-
-        const char *queue_id = smap_get(&op->sb->options, "qdisc_queue_id");
-        if (queue_id) {
-            ds_put_format(&actions, "set_queue(%s); ", queue_id);
-        }
-        ds_put_cstr(&actions, "next;");
-        ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50,
-                      ds_cstr(&match), ds_cstr(&actions));
-
-        if (op->nbsp->n_port_security) {
-            build_port_security_ip(P_IN, op, lflows);
-            build_port_security_nd(op, lflows);
-        }
-    }
-
-    /* Ingress table 1 and 2: Port security - IP and ND, by default goto next.
-     * (priority 0)*/
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
-    }
-
-    /* Ingress table 11: ARP/ND responder, skip requests coming from localnet
-     * and vtep ports. (priority 100); see ovn-northd.8.xml for the
-     * rationale. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-            continue;
-        }
-
-        if ((!strcmp(op->nbsp->type, "localnet")) ||
-            (!strcmp(op->nbsp->type, "vtep"))) {
-            ds_clear(&match);
-            ds_put_format(&match, "inport == %s", op->json_key);
-            ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100,
-                          ds_cstr(&match), "next;");
-        }
-    }
-
-    /* Ingress table 11: ARP/ND responder, reply for known IPs.
-     * (priority 50). */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-            continue;
-        }
-
-        /*
-         * Add ARP/ND reply flows if either the
-         *  - port is up or
-         *  - port type is router or
-         *  - port type is localport
-         */
-        if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") &&
-            strcmp(op->nbsp->type, "localport")) {
-            continue;
-        }
-
-        if (lsp_is_external(op->nbsp)) {
-            continue;
-        }
-
-        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-                ds_clear(&match);
-                ds_put_format(&match, "arp.tpa == %s && arp.op == 1",
-                              op->lsp_addrs[i].ipv4_addrs[j].addr_s);
-                ds_clear(&actions);
-                ds_put_format(&actions,
-                    "eth.dst = eth.src; "
-                    "eth.src = %s; "
-                    "arp.op = 2; /* ARP reply */ "
-                    "arp.tha = arp.sha; "
-                    "arp.sha = %s; "
-                    "arp.tpa = arp.spa; "
-                    "arp.spa = %s; "
-                    "outport = inport; "
-                    "flags.loopback = 1; "
-                    "output;",
-                    op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s,
-                    op->lsp_addrs[i].ipv4_addrs[j].addr_s);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-
-                /* Do not reply to an ARP request from the port that owns the
-                 * address (otherwise a DHCP client that ARPs to check for a
-                 * duplicate address will fail).  Instead, forward it the usual
-                 * way.
-                 *
-                 * (Another alternative would be to simply drop the packet.  If
-                 * everything is working as it is configured, then this would
-                 * produce equivalent results, since no one should reply to the
-                 * request.  But ARPing for one's own IP address is intended to
-                 * detect situations where the network is not working as
-                 * configured, so dropping the request would frustrate that
-                 * intent.) */
-                ds_put_format(&match, " && inport == %s", op->json_key);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100,
-                              ds_cstr(&match), "next;");
-            }
-
-            /* For ND solicitations, we need to listen for both the
-             * unicast IPv6 address and its all-nodes multicast address,
-             * but always respond with the unicast IPv6 address. */
-            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-                ds_clear(&match);
-                ds_put_format(&match,
-                        "nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s);
-
-                ds_clear(&actions);
-                ds_put_format(&actions,
-                        "%s { "
-                        "eth.src = %s; "
-                        "ip6.src = %s; "
-                        "nd.target = %s; "
-                        "nd.tll = %s; "
-                        "outport = inport; "
-                        "flags.loopback = 1; "
-                        "output; "
-                        "};",
-                        !strcmp(op->nbsp->type, "router") ?
-                            "nd_na_router" : "nd_na",
-                        op->lsp_addrs[i].ea_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s,
-                        op->lsp_addrs[i].ea_s);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-
-                /* Do not reply to a solicitation from the port that owns the
-                 * address (otherwise DAD detection will fail). */
-                ds_put_format(&match, " && inport == %s", op->json_key);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100,
-                              ds_cstr(&match), "next;");
-            }
-        }
-    }
-
-    /* Ingress table 11: ARP/ND responder, by default goto next.
-     * (priority 0)*/
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;");
-    }
-
-    /* Logical switch ingress table 12 and 13: DHCP options and response
-         * priority 100 flows. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-           continue;
-        }
-
-        if (!lsp_is_enabled(op->nbsp) || !strcmp(op->nbsp->type, "router")) {
-            /* Don't add the DHCP flows if the port is not enabled or if the
-             * port is a router port. */
-            continue;
-        }
-
-        if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) {
-            /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport.
-             */
-            continue;
-        }
-
-        bool is_external = lsp_is_external(op->nbsp);
-        if (is_external && (!op->od->localnet_port ||
-                            !op->nbsp->ha_chassis_group)) {
-            /* If it's an external port and there is no localnet port
-             * and if it doesn't belong to an HA chassis group ignore it. */
-            continue;
-        }
-
-        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-                struct ds options_action = DS_EMPTY_INITIALIZER;
-                struct ds response_action = DS_EMPTY_INITIALIZER;
-                struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER;
-                if (build_dhcpv4_action(
-                        op, op->lsp_addrs[i].ipv4_addrs[j].addr,
-                        &options_action, &response_action, &ipv4_addr_match)) {
-                    ds_clear(&match);
-                    ds_put_format(
-                        &match, "inport == %s && eth.src == %s && "
-                        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
-                        "udp.src == 68 && udp.dst == 67",
-                        is_external ? op->od->localnet_port->json_key :
-                            op->json_key,
-                        op->lsp_addrs[i].ea_s);
-
-                    if (is_external) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->json_key);
-                    }
-
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
-                                  100, ds_cstr(&match),
-                                  ds_cstr(&options_action));
-                    ds_clear(&match);
-                    /* Allow ip4.src = OFFER_IP and
-                     * ip4.dst = {SERVER_IP, 255.255.255.255} for the below
-                     * cases
-                     *  -  When the client wants to renew the IP by sending
-                     *     the DHCPREQUEST to the server ip.
-                     *  -  When the client wants to renew the IP by
-                     *     broadcasting the DHCPREQUEST.
-                     */
-                    ds_put_format(
-                        &match, "inport == %s && eth.src == %s && "
-                        "%s && udp.src == 68 && udp.dst == 67",
-                        is_external ? op->od->localnet_port->json_key :
-                            op->json_key,
-                        op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match));
-
-                    if (is_external) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->json_key);
-                    }
-
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
-                                  100, ds_cstr(&match),
-                                  ds_cstr(&options_action));
-                    ds_clear(&match);
-
-                    /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
-                     * put_dhcp_opts action  is successful. */
-                    ds_put_format(
-                        &match, "inport == %s && eth.src == %s && "
-                        "ip4 && udp.src == 68 && udp.dst == 67"
-                        " && "REGBIT_DHCP_OPTS_RESULT,
-                        is_external ? op->od->localnet_port->json_key :
-                            op->json_key,
-                        op->lsp_addrs[i].ea_s);
-
-                    if (is_external) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->json_key);
-                    }
-
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE,
-                                  100, ds_cstr(&match),
-                                  ds_cstr(&response_action));
-                    ds_destroy(&options_action);
-                    ds_destroy(&response_action);
-                    ds_destroy(&ipv4_addr_match);
-                    break;
-                }
-            }
-
-            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-                struct ds options_action = DS_EMPTY_INITIALIZER;
-                struct ds response_action = DS_EMPTY_INITIALIZER;
-                if (build_dhcpv6_action(
-                        op, &op->lsp_addrs[i].ipv6_addrs[j].addr,
-                        &options_action, &response_action)) {
-                    ds_clear(&match);
-                    ds_put_format(
-                        &match, "inport == %s && eth.src == %s"
-                        " && ip6.dst == ff02::1:2 && udp.src == 546 &&"
-                        " udp.dst == 547",
-                        is_external ? op->od->localnet_port->json_key :
-                            op->json_key,
-                        op->lsp_addrs[i].ea_s);
-
-                    if (is_external) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->json_key);
-                    }
-
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100,
-                                  ds_cstr(&match), ds_cstr(&options_action));
-
-                    /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means the
-                     * put_dhcpv6_opts action is successful */
-                    ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT);
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, 100,
-                                  ds_cstr(&match), ds_cstr(&response_action));
-                    ds_destroy(&options_action);
-                    ds_destroy(&response_action);
-                    break;
-                }
-            }
-        }
-    }
-
-    /* Logical switch ingress table 14 and 15: DNS lookup and response
-     * priority 100 flows.
-     */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs || !ls_has_dns_records(od->nbs)) {
-           continue;
-        }
-
-        struct ds action = DS_EMPTY_INITIALIZER;
-
-        ds_clear(&match);
-        ds_put_cstr(&match, "udp.dst == 53");
-        ds_put_format(&action,
-                      REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
-                      ds_cstr(&match), ds_cstr(&action));
-        ds_clear(&action);
-        ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT);
-        ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst; "
-                      "udp.dst = udp.src; udp.src = 53; outport = inport; "
-                      "flags.loopback = 1; output;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
-                      ds_cstr(&match), ds_cstr(&action));
-        ds_clear(&action);
-        ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst; "
-                      "udp.dst = udp.src; udp.src = 53; outport = inport; "
-                      "flags.loopback = 1; output;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
-                      ds_cstr(&match), ds_cstr(&action));
-        ds_destroy(&action);
-    }
-
-    /* Ingress table 12 and 13: DHCP options and response, by default goto
-     * next. (priority 0).
-     * Ingress table 14 and 15: DNS lookup and response, by default goto next.
-     * (priority 0).
-     * Ingress table 16 - External port handling, by default goto next.
-     * (priority 0). */
-
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
-    }
-
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp || !lsp_is_external(op->nbsp) ||
-            !op->od->localnet_port) {
-           continue;
-        }
-
-        /* Table 16: External port. Drop ARP request for router ips from
-         * external ports  on chassis not binding those ports.
-         * This makes the router pipeline to be run only on the chassis
-         * binding the external ports. */
-
-        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-            for (size_t j = 0; j < op->od->n_router_ports; j++) {
-                struct ovn_port *rp = op->od->router_ports[j];
-                for (size_t k = 0; k < rp->n_lsp_addrs; k++) {
-                    for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs;
-                         l++) {
-                        ds_clear(&match);
-                        ds_put_format(
-                            &match, "inport == %s && eth.src == %s"
-                            " && !is_chassis_resident(%s)"
-                            " && arp.tpa == %s && arp.op == 1",
-                            op->od->localnet_port->json_key,
-                            op->lsp_addrs[i].ea_s, op->json_key,
-                            rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
-                        ovn_lflow_add(lflows, op->od,
-                                      S_SWITCH_IN_EXTERNAL_PORT, 100,
-                                      ds_cstr(&match), "drop;");
-                    }
-                    for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs;
-                         l++) {
-                        ds_clear(&match);
-                        ds_put_format(
-                            &match, "inport == %s && eth.src == %s"
-                            " && !is_chassis_resident(%s)"
-                            " && nd_ns && ip6.dst == {%s, %s} && "
-                            "nd.target == %s",
-                            op->od->localnet_port->json_key,
-                            op->lsp_addrs[i].ea_s, op->json_key,
-                            rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
-                            rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
-                            rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
-                        ovn_lflow_add(lflows, op->od,
-                                      S_SWITCH_IN_EXTERNAL_PORT, 100,
-                                      ds_cstr(&match), "drop;");
-                    }
-                }
-            }
-        }
-    }
-
-    /* Ingress table 17: Destination lookup, broadcast and multicast handling
-     * (priority 70 - 100). */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        if (od->mcast_info.enabled) {
-            /* Punt IGMP traffic to controller. */
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "ip4 && ip.proto == 2", "igmp;");
-
-            /* Flood all IP multicast traffic destined to 224.0.0.X to all
-             * ports - RFC 4541, section 2.1.2, item 2.
-             */
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85,
-                          "ip4 && ip4.dst == 224.0.0.0/24",
-                          "outport = \""MC_FLOOD"\"; output;");
-
-            /* Drop unregistered IP multicast if not allowed. */
-            if (!od->mcast_info.flood_unregistered) {
-                ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
-                              "ip4 && ip4.mcast", "drop;");
-            }
-        }
-
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast",
-                      "outport = \""MC_FLOOD"\"; output;");
-    }
-
-    /* Ingress table 17: Add IP multicast flows learnt from IGMP
-     * (priority 90). */
-    struct ovn_igmp_group *igmp_group, *next_igmp_group;
-
-    HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, igmp_groups) {
-        ds_clear(&match);
-        ds_clear(&actions);
-
-        if (!igmp_group->datapath) {
-            continue;
-        }
-
-        struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info;
-
-        if (mcast_info->active_flows >= mcast_info->table_size) {
-            continue;
-        }
-        mcast_info->active_flows++;
-
-        ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
-                      igmp_group->mcgroup.name);
-        ds_put_format(&actions, "outport = \"%s\"; output; ",
-                      igmp_group->mcgroup.name);
-
-        ovn_lflow_add(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, 90,
-                      ds_cstr(&match), ds_cstr(&actions));
-    }
-
-    /* Ingress table 17: Destination lookup, unicast handling (priority 50), */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp || lsp_is_external(op->nbsp)) {
-            continue;
-        }
-
-        for (size_t i = 0; i < op->nbsp->n_addresses; i++) {
-            /* Addresses are owned by the logical port.
-             * Ethernet address followed by zero or more IPv4
-             * or IPv6 addresses (or both). */
-            struct eth_addr mac;
-            if (ovs_scan(op->nbsp->addresses[i],
-                        ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
-                ds_clear(&match);
-                ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-                              ETH_ADDR_ARGS(mac));
-
-                ds_clear(&actions);
-                ds_put_format(&actions, "outport = %s; output;", op->json_key);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-            } else if (!strcmp(op->nbsp->addresses[i], "unknown")) {
-                if (lsp_is_enabled(op->nbsp)) {
-                    ovn_multicast_add(mcgroups, &mc_unknown, op);
-                    op->od->has_unknown = true;
-                }
-            } else if (is_dynamic_lsp_address(op->nbsp->addresses[i])) {
-                if (!op->nbsp->dynamic_addresses
-                    || !ovs_scan(op->nbsp->dynamic_addresses,
-                            ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
-                    continue;
-                }
-                ds_clear(&match);
-                ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-                              ETH_ADDR_ARGS(mac));
-
-                ds_clear(&actions);
-                ds_put_format(&actions, "outport = %s; output;", op->json_key);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-            } else if (!strcmp(op->nbsp->addresses[i], "router")) {
-                if (!op->peer || !op->peer->nbrp
-                    || !ovs_scan(op->peer->nbrp->mac,
-                            ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
-                    continue;
-                }
-                ds_clear(&match);
-                ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-                              ETH_ADDR_ARGS(mac));
-                if (op->peer->od->l3dgw_port
-                    && op->peer->od->l3redirect_port
-                    && op->od->localnet_port) {
-                    bool add_chassis_resident_check = false;
-                    if (op->peer == op->peer->od->l3dgw_port) {
-                        /* The peer of this port represents a distributed
-                         * gateway port. The destination lookup flow for the
-                         * router's distributed gateway port MAC address should
-                         * only be programmed on the "redirect-chassis". */
-                        add_chassis_resident_check = true;
-                    } else {
-                        /* Check if the option 'reside-on-redirect-chassis'
-                         * is set to true on the peer port. If set to true
-                         * and if the logical switch has a localnet port, it
-                         * means the router pipeline for the packets from
-                         * this logical switch should be run on the chassis
-                         * hosting the gateway port.
-                         */
-                        add_chassis_resident_check = smap_get_bool(
-                            &op->peer->nbrp->options,
-                            "reside-on-redirect-chassis", false);
-                    }
-
-                    if (add_chassis_resident_check) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->peer->od->l3redirect_port->json_key);
-                    }
-                }
-
-                ds_clear(&actions);
-                ds_put_format(&actions, "outport = %s; output;", op->json_key);
-                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-
-                /* Add ethernet addresses specified in NAT rules on
-                 * distributed logical routers. */
-                if (op->peer->od->l3dgw_port
-                    && op->peer == op->peer->od->l3dgw_port) {
-                    for (int j = 0; j < op->peer->od->nbr->n_nat; j++) {
-                        const struct nbrec_nat *nat
-                                                  = op->peer->od->nbr->nat[j];
-                        if (!strcmp(nat->type, "dnat_and_snat")
-                            && nat->logical_port && nat->external_mac
-                            && eth_addr_from_string(nat->external_mac, &mac)) {
-
-                            ds_clear(&match);
-                            ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT
-                                          " && is_chassis_resident(\"%s\")",
-                                          ETH_ADDR_ARGS(mac),
-                                          nat->logical_port);
-
-                            ds_clear(&actions);
-                            ds_put_format(&actions, "outport = %s; output;",
-                                          op->json_key);
-                            ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP,
-                                          50, ds_cstr(&match),
-                                          ds_cstr(&actions));
-                        }
-                    }
-                }
-            } else {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-
-                VLOG_INFO_RL(&rl,
-                             "%s: invalid syntax '%s' in addresses column",
-                             op->nbsp->name, op->nbsp->addresses[i]);
-            }
-        }
-    }
-
-    /* Ingress table 17: Destination lookup for unknown MACs (priority 0). */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        if (od->has_unknown) {
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
-                          "outport = \""MC_UNKNOWN"\"; output;");
-        }
-    }
-
-    /* Egress tables 8: Egress port security - IP (priority 0)
-     * Egress table 9: Egress port security L2 - multicast/broadcast
-     *                 (priority 100). */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast",
-                      "output;");
-    }
-
-    /* Egress table 8: Egress port security - IP (priorities 90 and 80)
-     * if port security enabled.
-     *
-     * Egress table 9: Egress port security - L2 (priorities 50 and 150).
-     *
-     * Priority 50 rules implement port security for enabled logical port.
-     *
-     * Priority 150 rules drop packets to disabled logical ports, so that they
-     * don't even receive multicast or broadcast packets. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp || lsp_is_external(op->nbsp)) {
-            continue;
-        }
-
-        ds_clear(&match);
-        ds_put_format(&match, "outport == %s", op->json_key);
-        if (lsp_is_enabled(op->nbsp)) {
-            build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs,
-                                   &match);
-            ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 50,
-                          ds_cstr(&match), "output;");
-        } else {
-            ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 150,
-                          ds_cstr(&match), "drop;");
-        }
-
-        if (op->nbsp->n_port_security) {
-            build_port_security_ip(P_OUT, op, lflows);
-        }
-    }
-
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
-static bool
-lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
-{
-    return !lrport->enabled || *lrport->enabled;
-}
-
-/* Returns a string of the IP address of the router port 'op' that
- * overlaps with 'ip_s".  If one is not found, returns NULL.
- *
- * The caller must not free the returned string. */
-static const char *
-find_lrp_member_ip(const struct ovn_port *op, const char *ip_s)
-{
-    bool is_ipv4 = strchr(ip_s, '.') ? true : false;
-
-    if (is_ipv4) {
-        ovs_be32 ip;
-
-        if (!ip_parse(ip_s, &ip)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad ip address %s", ip_s);
-            return NULL;
-        }
-
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i];
-
-            if (!((na->network ^ ip) & na->mask)) {
-                /* There should be only 1 interface that matches the
-                 * supplied IP.  Otherwise, it's a configuration error,
-                 * because subnets of a router's interfaces should NOT
-                 * overlap. */
-                return na->addr_s;
-            }
-        }
-    } else {
-        struct in6_addr ip6;
-
-        if (!ipv6_parse(ip_s, &ip6)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad ipv6 address %s", ip_s);
-            return NULL;
-        }
-
-        for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-            const struct ipv6_netaddr *na = &op->lrp_networks.ipv6_addrs[i];
-            struct in6_addr xor_addr = ipv6_addr_bitxor(&na->network, &ip6);
-            struct in6_addr and_addr = ipv6_addr_bitand(&xor_addr, &na->mask);
-
-            if (ipv6_is_zero(&and_addr)) {
-                /* There should be only 1 interface that matches the
-                 * supplied IP.  Otherwise, it's a configuration error,
-                 * because subnets of a router's interfaces should NOT
-                 * overlap. */
-                return na->addr_s;
-            }
-        }
-    }
-
-    return NULL;
-}
-
-static struct ovn_port*
-get_outport_for_routing_policy_nexthop(struct ovn_datapath *od,
-                                       struct hmap *ports,
-                                       int priority, const char *nexthop)
-{
-    if (nexthop == NULL) {
-        return NULL;
-    }
-
-    /* Find the router port matching the next hop. */
-    for (int i = 0; i < od->nbr->n_ports; i++) {
-       struct nbrec_logical_router_port *lrp = od->nbr->ports[i];
-
-       struct ovn_port *out_port = ovn_port_find(ports, lrp->name);
-       if (out_port && find_lrp_member_ip(out_port, nexthop)) {
-           return out_port;
-       }
-    }
-
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-    VLOG_WARN_RL(&rl, "No path for routing policy priority %d; next hop %s",
-                 priority, nexthop);
-    return NULL;
-}
-
-static void
-build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
-                          struct hmap *ports,
-                          const struct nbrec_logical_router_policy *rule)
-{
-    struct ds match = DS_EMPTY_INITIALIZER;
-    struct ds actions = DS_EMPTY_INITIALIZER;
-
-    if (!strcmp(rule->action, "reroute")) {
-        struct ovn_port *out_port = get_outport_for_routing_policy_nexthop(
-             od, ports, rule->priority, rule->nexthop);
-        if (!out_port) {
-            return;
-        }
-
-        const char *lrp_addr_s = find_lrp_member_ip(out_port, rule->nexthop);
-        if (!lrp_addr_s) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy "
-                         " priority %"PRId64" nexthop %s",
-                         rule->priority, rule->nexthop);
-            return;
-        }
-        bool is_ipv4 = strchr(rule->nexthop, '.') ? true : false;
-        ds_put_format(&actions, "%sreg0 = %s; "
-                      "%sreg1 = %s; "
-                      "eth.src = %s; "
-                      "outport = %s; "
-                      "flags.loopback = 1; "
-                      "next;",
-                      is_ipv4 ? "" : "xx",
-                      rule->nexthop,
-                      is_ipv4 ? "" : "xx",
-                      lrp_addr_s,
-                      out_port->lrp_networks.ea_s,
-                      out_port->json_key);
-
-    } else if (!strcmp(rule->action, "drop")) {
-        ds_put_cstr(&actions, "drop;");
-    } else if (!strcmp(rule->action, "allow")) {
-        ds_put_cstr(&actions, "next;");
-    }
-    ds_put_format(&match, "%s", rule->match);
-    ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, rule->priority,
-                  ds_cstr(&match), ds_cstr(&actions));
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
-static void
-add_distributed_nat_routes(struct hmap *lflows, const struct ovn_port *op)
-{
-    struct ds actions = DS_EMPTY_INITIALIZER;
-    struct ds match = DS_EMPTY_INITIALIZER;
-
-    if (!op->od->l3dgw_port) {
-        return;
-    }
-
-    if (!op->peer || !op->peer->od->nbs) {
-        return;
-    }
-
-    for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-        const struct nbrec_nat *nat = op->od->nbr->nat[i];
-        bool found = false;
-
-        if (strcmp(nat->type, "dnat_and_snat") ||
-            !nat->external_mac  || !nat->external_ip) {
-            continue;
-        }
-
-        const struct ovn_datapath *peer_dp = op->peer->od;
-        for (size_t j = 0; j < peer_dp->nbs->n_ports; j++) {
-            if (!strcmp(peer_dp->nbs->ports[j]->name, nat->logical_port)) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            continue;
-        }
-
-        ds_put_format(&match, "inport == %s && "
-                      "ip4.src == %s && ip4.dst == %s",
-                       op->json_key, nat->logical_ip, nat->external_ip);
-        ds_put_format(&actions, "outport = %s; eth.dst = %s; "
-                      REGBIT_DISTRIBUTED_NAT" = 1; "
-                      REGBIT_NAT_REDIRECT" = 0; next;",
-                      op->od->l3dgw_port->json_key,
-                      nat->external_mac);
-        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400,
-                      ds_cstr(&match), ds_cstr(&actions));
-        ds_clear(&match);
-        ds_clear(&actions);
-
-        for (size_t j = 0; j < op->od->nbr->n_nat; j++) {
-            const struct nbrec_nat *nat2 = op->od->nbr->nat[j];
-
-            if (nat == nat2 || strcmp(nat2->type, "dnat_and_snat") ||
-                !nat2->external_mac || !nat2->external_ip)
-                continue;
-
-            ds_put_format(&match, "inport == %s && "
-                          "ip4.src == %s && ip4.dst == %s",
-                          op->json_key, nat->logical_ip, nat2->external_ip);
-            ds_put_format(&actions, "outport = %s; "
-                          "eth.src = %s; eth.dst = %s; "
-                          "reg0 = ip4.dst; reg1 = %s; "
-                          REGBIT_DISTRIBUTED_NAT" = 1; "
-                          REGBIT_NAT_REDIRECT" = 0; next;",
-                          op->od->l3dgw_port->json_key,
-                          op->od->l3dgw_port->lrp_networks.ea_s,
-                          nat2->external_mac, nat->external_ip);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400,
-                          ds_cstr(&match), ds_cstr(&actions));
-            ds_clear(&match);
-            ds_clear(&actions);
-        }
-    }
-}
-
-static void
-add_route(struct hmap *lflows, const struct ovn_port *op,
-          const char *lrp_addr_s, const char *network_s, int plen,
-          const char *gateway, const char *policy)
-{
-    bool is_ipv4 = strchr(network_s, '.') ? true : false;
-    struct ds match = DS_EMPTY_INITIALIZER;
-    const char *dir;
-    uint16_t priority;
-
-    if (policy && !strcmp(policy, "src-ip")) {
-        dir = "src";
-        priority = plen * 2;
-    } else {
-        dir = "dst";
-        priority = (plen * 2) + 1;
-    }
-
-    /* IPv6 link-local addresses must be scoped to the local router port. */
-    if (!is_ipv4) {
-        struct in6_addr network;
-        ovs_assert(ipv6_parse(network_s, &network));
-        if (in6_is_lla(&network)) {
-            ds_put_format(&match, "inport == %s && ", op->json_key);
-        }
-    }
-    ds_put_format(&match, "ip%s.%s == %s/%d", is_ipv4 ? "4" : "6", dir,
-                  network_s, plen);
-
-    struct ds actions = DS_EMPTY_INITIALIZER;
-    ds_put_format(&actions, "ip.ttl--; %sreg0 = ", is_ipv4 ? "" : "xx");
-
-    if (gateway) {
-        ds_put_cstr(&actions, gateway);
-    } else {
-        ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6");
-    }
-    ds_put_format(&actions, "; "
-                  "%sreg1 = %s; "
-                  "eth.src = %s; "
-                  "outport = %s; "
-                  "flags.loopback = 1; "
-                  "next;",
-                  is_ipv4 ? "" : "xx",
-                  lrp_addr_s,
-                  op->lrp_networks.ea_s,
-                  op->json_key);
-
-    /* The priority here is calculated to implement longest-prefix-match
-     * routing. */
-    ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority,
-                  ds_cstr(&match), ds_cstr(&actions));
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
-static void
-build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
-                        struct hmap *ports,
-                        const struct nbrec_logical_router_static_route *route)
-{
-    ovs_be32 nexthop;
-    const char *lrp_addr_s = NULL;
-    unsigned int plen;
-    bool is_ipv4;
-
-    /* Verify that the next hop is an IP address with an all-ones mask. */
-    char *error = ip_parse_cidr(route->nexthop, &nexthop, &plen);
-    if (!error) {
-        if (plen != 32) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop);
-            return;
-        }
-        is_ipv4 = true;
-    } else {
-        free(error);
-
-        struct in6_addr ip6;
-        error = ipv6_parse_cidr(route->nexthop, &ip6, &plen);
-        if (!error) {
-            if (plen != 128) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop);
-                return;
-            }
-            is_ipv4 = false;
-        } else {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad next hop ip address %s", route->nexthop);
-            free(error);
-            return;
-        }
-    }
-
-    char *prefix_s;
-    if (is_ipv4) {
-        ovs_be32 prefix;
-        /* Verify that ip prefix is a valid IPv4 address. */
-        error = ip_parse_cidr(route->ip_prefix, &prefix, &plen);
-        if (error) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s",
-                         route->ip_prefix);
-            free(error);
-            return;
-        }
-        prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & be32_prefix_mask(plen)));
-    } else {
-        /* Verify that ip prefix is a valid IPv6 address. */
-        struct in6_addr prefix;
-        error = ipv6_parse_cidr(route->ip_prefix, &prefix, &plen);
-        if (error) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s",
-                         route->ip_prefix);
-            free(error);
-            return;
-        }
-        struct in6_addr mask = ipv6_create_mask(plen);
-        struct in6_addr network = ipv6_addr_bitand(&prefix, &mask);
-        prefix_s = xmalloc(INET6_ADDRSTRLEN);
-        inet_ntop(AF_INET6, &network, prefix_s, INET6_ADDRSTRLEN);
-    }
-
-    /* Find the outgoing port. */
-    struct ovn_port *out_port = NULL;
-    if (route->output_port) {
-        out_port = ovn_port_find(ports, route->output_port);
-        if (!out_port) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "Bad out port %s for static route %s",
-                         route->output_port, route->ip_prefix);
-            goto free_prefix_s;
-        }
-        lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
-        if (!lrp_addr_s) {
-            /* There are no IP networks configured on the router's port via
-             * which 'route->nexthop' is theoretically reachable.  But since
-             * 'out_port' has been specified, we honor it by trying to reach
-             * 'route->nexthop' via the first IP address of 'out_port'.
-             * (There are cases, e.g in GCE, where each VM gets a /32 IP
-             * address and the default gateway is still reachable from it.) */
-            if (is_ipv4) {
-                if (out_port->lrp_networks.n_ipv4_addrs) {
-                    lrp_addr_s = out_port->lrp_networks.ipv4_addrs[0].addr_s;
-                }
-            } else {
-                if (out_port->lrp_networks.n_ipv6_addrs) {
-                    lrp_addr_s = out_port->lrp_networks.ipv6_addrs[0].addr_s;
-                }
-            }
-        }
-    } else {
-        /* output_port is not specified, find the
-         * router port matching the next hop. */
-        int i;
-        for (i = 0; i < od->nbr->n_ports; i++) {
-            struct nbrec_logical_router_port *lrp = od->nbr->ports[i];
-            out_port = ovn_port_find(ports, lrp->name);
-            if (!out_port) {
-                /* This should not happen. */
-                continue;
-            }
-
-            lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
-            if (lrp_addr_s) {
-                break;
-            }
-        }
-    }
-
-    if (!out_port || !lrp_addr_s) {
-        /* There is no matched out port. */
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s",
-                     route->ip_prefix, route->nexthop);
-        goto free_prefix_s;
-    }
-
-    char *policy = route->policy ? route->policy : "dst-ip";
-    add_route(lflows, out_port, lrp_addr_s, prefix_s, plen, route->nexthop,
-              policy);
-
-free_prefix_s:
-    free(prefix_s);
-}
-
-static void
-op_put_v4_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast)
-{
-    if (!add_bcast && op->lrp_networks.n_ipv4_addrs == 1) {
-        ds_put_format(ds, "%s", op->lrp_networks.ipv4_addrs[0].addr_s);
-        return;
-    }
-
-    ds_put_cstr(ds, "{");
-    for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-        ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s);
-        if (add_bcast) {
-            ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s);
-        }
-    }
-    ds_chomp(ds, ' ');
-    ds_chomp(ds, ',');
-    ds_put_cstr(ds, "}");
-}
-
-static void
-op_put_v6_networks(struct ds *ds, const struct ovn_port *op)
-{
-    if (op->lrp_networks.n_ipv6_addrs == 1) {
-        ds_put_format(ds, "%s", op->lrp_networks.ipv6_addrs[0].addr_s);
-        return;
-    }
-
-    ds_put_cstr(ds, "{");
-    for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-        ds_put_format(ds, "%s, ", op->lrp_networks.ipv6_addrs[i].addr_s);
-    }
-    ds_chomp(ds, ' ');
-    ds_chomp(ds, ',');
-    ds_put_cstr(ds, "}");
-}
-
-static const char *
-get_force_snat_ip(struct ovn_datapath *od, const char *key_type, ovs_be32 *ip)
-{
-    char *key = xasprintf("%s_force_snat_ip", key_type);
-    const char *ip_address = smap_get(&od->nbr->options, key);
-    free(key);
-
-    if (ip_address) {
-        ovs_be32 mask;
-        char *error = ip_parse_masked(ip_address, ip, &mask);
-        if (error || mask != OVS_BE32_MAX) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
-                         ip_address, UUID_ARGS(&od->key));
-            free(error);
-            *ip = 0;
-            return NULL;
-        }
-        return ip_address;
-    }
-
-    *ip = 0;
-    return NULL;
-}
-
-static void
-add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
-                   struct ds *match, struct ds *actions, int priority,
-                   const char *lb_force_snat_ip, char *backend_ips,
-                   bool is_udp, int addr_family)
-{
-    /* A match and actions for new connections. */
-    char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
-    if (lb_force_snat_ip) {
-        char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
-                                      ds_cstr(actions));
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, new_match,
-                      new_actions);
-        free(new_actions);
-    } else {
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, new_match,
-                      ds_cstr(actions));
-    }
-
-    /* A match and actions for established connections. */
-    char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
-    if (lb_force_snat_ip) {
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, est_match,
-                      "flags.force_snat_for_lb = 1; ct_dnat;");
-    } else {
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, est_match,
-                      "ct_dnat;");
-    }
-
-    free(new_match);
-    free(est_match);
-
-    if (!od->l3dgw_port || !od->l3redirect_port || !backend_ips) {
-        return;
-    }
-
-    /* Add logical flows to UNDNAT the load balanced reverse traffic in
-     * the router egress pipleine stage - S_ROUTER_OUT_UNDNAT if the logical
-     * router has a gateway router port associated.
-     */
-    struct ds undnat_match = DS_EMPTY_INITIALIZER;
-    if (addr_family == AF_INET) {
-        ds_put_cstr(&undnat_match, "ip4 && (");
-    } else {
-        ds_put_cstr(&undnat_match, "ip6 && (");
-    }
-    char *start, *next, *ip_str;
-    start = next = xstrdup(backend_ips);
-    ip_str = strsep(&next, ",");
-    bool backend_ips_found = false;
-    while (ip_str && ip_str[0]) {
-        char *ip_address = NULL;
-        uint16_t port = 0;
-        int addr_family_;
-        ip_address_and_port_from_lb_key(ip_str, &ip_address, &port,
-                                        &addr_family_);
-        if (!ip_address) {
-            break;
-        }
-
-        if (addr_family_ == AF_INET) {
-            ds_put_format(&undnat_match, "(ip4.src == %s", ip_address);
-        } else {
-            ds_put_format(&undnat_match, "(ip6.src == %s", ip_address);
-        }
-        free(ip_address);
-        if (port) {
-            ds_put_format(&undnat_match, " && %s.src == %d) || ",
-                          is_udp ? "udp" : "tcp", port);
-        } else {
-            ds_put_cstr(&undnat_match, ") || ");
-        }
-        ip_str = strsep(&next, ",");
-        backend_ips_found = true;
-    }
-
-    free(start);
-    if (!backend_ips_found) {
-        ds_destroy(&undnat_match);
-        return;
-    }
-    ds_chomp(&undnat_match, ' ');
-    ds_chomp(&undnat_match, '|');
-    ds_chomp(&undnat_match, '|');
-    ds_chomp(&undnat_match, ' ');
-    ds_put_format(&undnat_match, ") && outport == %s && "
-                 "is_chassis_resident(%s)", od->l3dgw_port->json_key,
-                 od->l3redirect_port->json_key);
-    if (lb_force_snat_ip) {
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
-                      ds_cstr(&undnat_match),
-                      "flags.force_snat_for_lb = 1; ct_dnat;");
-    } else {
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
-                      ds_cstr(&undnat_match), "ct_dnat;");
-    }
-
-    ds_destroy(&undnat_match);
-}
-
-#define ND_RA_MAX_INTERVAL_MAX 1800
-#define ND_RA_MAX_INTERVAL_MIN 4
-
-#define ND_RA_MIN_INTERVAL_MAX(max) ((max) * 3 / 4)
-#define ND_RA_MIN_INTERVAL_MIN 3
-
-static void
-copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
-{
-    struct smap options;
-    smap_clone(&options, &op->sb->options);
-
-    smap_add(&options, "ipv6_ra_send_periodic", "true");
-    smap_add(&options, "ipv6_ra_address_mode", address_mode);
-
-    int max_interval = smap_get_int(&op->nbrp->ipv6_ra_configs,
-            "max_interval", ND_RA_MAX_INTERVAL_DEFAULT);
-    if (max_interval > ND_RA_MAX_INTERVAL_MAX) {
-        max_interval = ND_RA_MAX_INTERVAL_MAX;
-    }
-    if (max_interval < ND_RA_MAX_INTERVAL_MIN) {
-        max_interval = ND_RA_MAX_INTERVAL_MIN;
-    }
-    smap_add_format(&options, "ipv6_ra_max_interval", "%d", max_interval);
-
-    int min_interval = smap_get_int(&op->nbrp->ipv6_ra_configs,
-            "min_interval", nd_ra_min_interval_default(max_interval));
-    if (min_interval > ND_RA_MIN_INTERVAL_MAX(max_interval)) {
-        min_interval = ND_RA_MIN_INTERVAL_MAX(max_interval);
-    }
-    if (min_interval < ND_RA_MIN_INTERVAL_MIN) {
-        min_interval = ND_RA_MIN_INTERVAL_MIN;
-    }
-    smap_add_format(&options, "ipv6_ra_min_interval", "%d", min_interval);
-
-    int mtu = smap_get_int(&op->nbrp->ipv6_ra_configs, "mtu", ND_MTU_DEFAULT);
-    /* RFC 2460 requires the MTU for IPv6 to be at least 1280 */
-    if (mtu && mtu >= 1280) {
-        smap_add_format(&options, "ipv6_ra_mtu", "%d", mtu);
-    }
-
-    struct ds s = DS_EMPTY_INITIALIZER;
-    for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; ++i) {
-        struct ipv6_netaddr *addrs = &op->lrp_networks.ipv6_addrs[i];
-        if (in6_is_lla(&addrs->network)) {
-            smap_add(&options, "ipv6_ra_src_addr", addrs->addr_s);
-            continue;
-        }
-        ds_put_format(&s, "%s/%u ", addrs->network_s, addrs->plen);
-    }
-    /* Remove trailing space */
-    ds_chomp(&s, ' ');
-    smap_add(&options, "ipv6_ra_prefixes", ds_cstr(&s));
-    ds_destroy(&s);
-
-    smap_add(&options, "ipv6_ra_src_eth", op->lrp_networks.ea_s);
-
-    sbrec_port_binding_set_options(op->sb, &options);
-    smap_destroy(&options);
-}
-
-static void
-build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
-                    struct hmap *lflows)
-{
-    /* This flow table structure is documented in ovn-northd(8), so please
-     * update ovn-northd.8.xml if you change anything. */
-
-    struct ds match = DS_EMPTY_INITIALIZER;
-    struct ds actions = DS_EMPTY_INITIALIZER;
-
-    /* Logical router ingress table 0: Admission control framework. */
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        /* Logical VLANs not supported.
-         * Broadcast/multicast source address is invalid. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-                      "vlan.present || eth.src[40]", "drop;");
-    }
-
-    /* Logical router ingress table 0: match (priority 50). */
-    struct ovn_port *op;
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp) {
-            continue;
-        }
-
-        if (!lrport_is_enabled(op->nbrp)) {
-            /* Drop packets from disabled logical ports (since logical flow
-             * tables are default-drop). */
-            continue;
-        }
-
-        if (op->derived) {
-            /* No ingress packets should be received on a chassisredirect
-             * port. */
-            continue;
-        }
-
-        ds_clear(&match);
-        ds_put_format(&match, "eth.mcast && inport == %s", op->json_key);
-        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-                      ds_cstr(&match), "next;");
-
-        ds_clear(&match);
-        ds_put_format(&match, "eth.dst == %s && inport == %s",
-                      op->lrp_networks.ea_s, op->json_key);
-        if (op->od->l3dgw_port && op == op->od->l3dgw_port
-            && op->od->l3redirect_port) {
-            /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
-             * should only be received on the "redirect-chassis". */
-            ds_put_format(&match, " && is_chassis_resident(%s)",
-                          op->od->l3redirect_port->json_key);
-        }
-        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-                      ds_cstr(&match), "next;");
-    }
-
-    /* Logical router ingress table 1: IP Input. */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        /* L3 admission control: drop multicast and broadcast source, localhost
-         * source or destination, and zero network source or destination
-         * (priority 100). */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100,
-                      "ip4.mcast || "
-                      "ip4.src == 255.255.255.255 || "
-                      "ip4.src == 127.0.0.0/8 || "
-                      "ip4.dst == 127.0.0.0/8 || "
-                      "ip4.src == 0.0.0.0/8 || "
-                      "ip4.dst == 0.0.0.0/8",
-                      "drop;");
-
-        /* ARP reply handling.  Use ARP replies to populate the logical
-         * router's ARP table. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "arp.op == 2",
-                      "put_arp(inport, arp.spa, arp.sha);");
-
-        /* Drop Ethernet local broadcast.  By definition this traffic should
-         * not be forwarded.*/
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-                      "eth.bcast", "drop;");
-
-        /* TTL discard */
-        ds_clear(&match);
-        ds_put_cstr(&match, "ip4 && ip.ttl == {0, 1}");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-                      ds_cstr(&match), "drop;");
-
-        /* ND advertisement handling.  Use advertisements to populate
-         * the logical router's ARP/ND table. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na",
-                      "put_nd(inport, nd.target, nd.tll);");
-
-        /* Lean from neighbor solicitations that were not directed at
-         * us.  (A priority-90 flow will respond to requests to us and
-         * learn the sender's mac address. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns",
-                      "put_nd(inport, ip6.src, nd.sll);");
-
-        /* Pass other traffic not already handled to the next table for
-         * routing. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
-    }
-
-    /* Logical router ingress table 1: IP Input for IPv4. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp) {
-            continue;
-        }
-
-        if (op->derived) {
-            /* No ingress packets are accepted on a chassisredirect
-             * port, so no need to program flows for that port. */
-            continue;
-        }
-
-        if (op->lrp_networks.n_ipv4_addrs) {
-            /* L3 admission control: drop packets that originate from an
-             * IPv4 address owned by the router or a broadcast address
-             * known to the router (priority 100). */
-            ds_clear(&match);
-            ds_put_cstr(&match, "ip4.src == ");
-            op_put_v4_networks(&match, op, true);
-            ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-                          ds_cstr(&match), "drop;");
-
-            /* ICMP echo reply.  These flows reply to ICMP echo requests
-             * received for the router's IP address. Since packets only
-             * get here as part of the logical router datapath, the inport
-             * (i.e. the incoming locally attached net) does not matter.
-             * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-            ds_clear(&match);
-            ds_put_cstr(&match, "ip4.dst == ");
-            op_put_v4_networks(&match, op, false);
-            ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                "ip4.dst <-> ip4.src; "
-                "ip.ttl = 255; "
-                "icmp4.type = 0; "
-                "flags.loopback = 1; "
-                "next; ");
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        /* ICMP time exceeded */
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            ds_clear(&match);
-            ds_clear(&actions);
-
-            ds_put_format(&match,
-                          "inport == %s && ip4 && "
-                          "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-            ds_put_format(&actions,
-                          "icmp4 {"
-                          "eth.dst <-> eth.src; "
-                          "icmp4.type = 11; /* Time exceeded */ "
-                          "icmp4.code = 0; /* TTL exceeded in transit */ "
-                          "ip4.dst = ip4.src; "
-                          "ip4.src = %s; "
-                          "ip.ttl = 255; "
-                          "next; };",
-                          op->lrp_networks.ipv4_addrs[i].addr_s);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        /* ARP reply.  These flows reply to ARP requests for the router's own
-         * IP address. */
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            ds_clear(&match);
-            ds_put_format(&match,
-                          "inport == %s && arp.spa == %s/%u && arp.tpa == %s"
-                          " && arp.op == 1",
-                          op->json_key,
-                          op->lrp_networks.ipv4_addrs[i].network_s,
-                          op->lrp_networks.ipv4_addrs[i].plen,
-                          op->lrp_networks.ipv4_addrs[i].addr_s);
-
-            if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-                && op->peer->od->localnet_port) {
-                bool add_chassis_resident_check = false;
-                if (op == op->od->l3dgw_port) {
-                    /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-                     * should only be sent from the "redirect-chassis", so that
-                     * upstream MAC learning points to the "redirect-chassis".
-                     * Also need to avoid generation of multiple ARP responses
-                     * from different chassis. */
-                    add_chassis_resident_check = true;
-                } else {
-                    /* Check if the option 'reside-on-redirect-chassis'
-                     * is set to true on the router port. If set to true
-                     * and if peer's logical switch has a localnet port, it
-                     * means the router pipeline for the packets from
-                     * peer's logical switch is be run on the chassis
-                     * hosting the gateway port and it should reply to the
-                     * ARP requests for the router port IPs.
-                     */
-                    add_chassis_resident_check = smap_get_bool(
-                        &op->nbrp->options,
-                        "reside-on-redirect-chassis", false);
-                }
-
-                if (add_chassis_resident_check) {
-                    ds_put_format(&match, " && is_chassis_resident(%s)",
-                                  op->od->l3redirect_port->json_key);
-                }
-            }
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                "put_arp(inport, arp.spa, arp.sha); "
-                "eth.dst = eth.src; "
-                "eth.src = %s; "
-                "arp.op = 2; /* ARP reply */ "
-                "arp.tha = arp.sha; "
-                "arp.sha = %s; "
-                "arp.tpa = arp.spa; "
-                "arp.spa = %s; "
-                "outport = %s; "
-                "flags.loopback = 1; "
-                "output;",
-                op->lrp_networks.ea_s,
-                op->lrp_networks.ea_s,
-                op->lrp_networks.ipv4_addrs[i].addr_s,
-                op->json_key);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        /* Learn from ARP requests that were not directed at us. A typical
-         * use case is GARP request handling.  (A priority-90 flow will
-         * respond to request to us and learn the sender's mac address.) */
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            ds_clear(&match);
-            ds_put_format(&match,
-                          "inport == %s && arp.spa == %s/%u && arp.op == 1",
-                          op->json_key,
-                          op->lrp_networks.ipv4_addrs[i].network_s,
-                          op->lrp_networks.ipv4_addrs[i].plen);
-            if (op->od->l3dgw_port && op == op->od->l3dgw_port
-                && op->od->l3redirect_port) {
-                ds_put_format(&match, " && is_chassis_resident(%s)",
-                              op->od->l3redirect_port->json_key);
-            }
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
-                          ds_cstr(&match),
-                          "put_arp(inport, arp.spa, arp.sha);");
-
-        }
-
-        /* A set to hold all load-balancer vips that need ARP responses. */
-        struct sset all_ips = SSET_INITIALIZER(&all_ips);
-        int addr_family;
-        get_router_load_balancer_ips(op->od, &all_ips, &addr_family);
-
-        const char *ip_address;
-        SSET_FOR_EACH(ip_address, &all_ips) {
-            ds_clear(&match);
-            if (addr_family == AF_INET) {
-                ds_put_format(&match,
-                              "inport == %s && arp.tpa == %s && arp.op == 1",
-                              op->json_key, ip_address);
-            } else {
-                ds_put_format(&match,
-                              "inport == %s && nd_ns && nd.target == %s",
-                              op->json_key, ip_address);
-            }
-
-            ds_clear(&actions);
-            if (addr_family == AF_INET) {
-                ds_put_format(&actions,
-                "eth.dst = eth.src; "
-                "eth.src = %s; "
-                "arp.op = 2; /* ARP reply */ "
-                "arp.tha = arp.sha; "
-                "arp.sha = %s; "
-                "arp.tpa = arp.spa; "
-                "arp.spa = %s; "
-                "outport = %s; "
-                "flags.loopback = 1; "
-                "output;",
-                op->lrp_networks.ea_s,
-                op->lrp_networks.ea_s,
-                ip_address,
-                op->json_key);
-            } else {
-                ds_put_format(&actions,
-                "nd_na { "
-                "eth.src = %s; "
-                "ip6.src = %s; "
-                "nd.target = %s; "
-                "nd.tll = %s; "
-                "outport = inport; "
-                "flags.loopback = 1; "
-                "output; "
-                "};",
-                op->lrp_networks.ea_s,
-                ip_address,
-                ip_address,
-                op->lrp_networks.ea_s);
-            }
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        sset_destroy(&all_ips);
-
-        /* A gateway router can have 2 SNAT IP addresses to force DNATed and
-         * LBed traffic respectively to be SNATed.  In addition, there can be
-         * a number of SNAT rules in the NAT table. */
-        ovs_be32 *snat_ips = xmalloc(sizeof *snat_ips *
-                                     (op->od->nbr->n_nat + 2));
-        size_t n_snat_ips = 0;
-
-        ovs_be32 snat_ip;
-        const char *dnat_force_snat_ip = get_force_snat_ip(op->od, "dnat",
-                                                           &snat_ip);
-        if (dnat_force_snat_ip) {
-            snat_ips[n_snat_ips++] = snat_ip;
-        }
-
-        const char *lb_force_snat_ip = get_force_snat_ip(op->od, "lb",
-                                                         &snat_ip);
-        if (lb_force_snat_ip) {
-            snat_ips[n_snat_ips++] = snat_ip;
-        }
-
-        for (int i = 0; i < op->od->nbr->n_nat; i++) {
-            const struct nbrec_nat *nat;
-
-            nat = op->od->nbr->nat[i];
-
-            ovs_be32 ip;
-            if (!ip_parse(nat->external_ip, &ip) || !ip) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration "
-                             "for router %s", nat->external_ip, op->key);
-                continue;
-            }
-
-            if (!strcmp(nat->type, "snat")) {
-                snat_ips[n_snat_ips++] = ip;
-                continue;
-            }
-
-            /* ARP handling for external IP addresses.
-             *
-             * DNAT IP addresses are external IP addresses that need ARP
-             * handling. */
-            ds_clear(&match);
-            ds_put_format(&match,
-                          "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1",
-                          op->json_key, IP_ARGS(ip));
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                "eth.dst = eth.src; "
-                "arp.op = 2; /* ARP reply */ "
-                "arp.tha = arp.sha; ");
-
-            if (op->od->l3dgw_port && op == op->od->l3dgw_port) {
-                struct eth_addr mac;
-                if (nat->external_mac &&
-                    eth_addr_from_string(nat->external_mac, &mac)
-                    && nat->logical_port) {
-                    /* distributed NAT case, use nat->external_mac */
-                    ds_put_format(&actions,
-                        "eth.src = "ETH_ADDR_FMT"; "
-                        "arp.sha = "ETH_ADDR_FMT"; ",
-                        ETH_ADDR_ARGS(mac),
-                        ETH_ADDR_ARGS(mac));
-                    /* Traffic with eth.src = nat->external_mac should only be
-                     * sent from the chassis where nat->logical_port is
-                     * resident, so that upstream MAC learning points to the
-                     * correct chassis.  Also need to avoid generation of
-                     * multiple ARP responses from different chassis. */
-                    ds_put_format(&match, " && is_chassis_resident(\"%s\")",
-                                  nat->logical_port);
-                } else {
-                    ds_put_format(&actions,
-                        "eth.src = %s; "
-                        "arp.sha = %s; ",
-                        op->lrp_networks.ea_s,
-                        op->lrp_networks.ea_s);
-                    /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-                     * should only be sent from the "redirect-chassis", so that
-                     * upstream MAC learning points to the "redirect-chassis".
-                     * Also need to avoid generation of multiple ARP responses
-                     * from different chassis. */
-                    if (op->od->l3redirect_port) {
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      op->od->l3redirect_port->json_key);
-                    }
-                }
-            } else {
-                ds_put_format(&actions,
-                    "eth.src = %s; "
-                    "arp.sha = %s; ",
-                    op->lrp_networks.ea_s,
-                    op->lrp_networks.ea_s);
-            }
-            ds_put_format(&actions,
-                "arp.tpa = arp.spa; "
-                "arp.spa = "IP_FMT"; "
-                "outport = %s; "
-                "flags.loopback = 1; "
-                "output;",
-                IP_ARGS(ip),
-                op->json_key);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        if (!smap_get(&op->od->nbr->options, "chassis")
-            && !op->od->l3dgw_port) {
-            /* UDP/TCP port unreachable. */
-            for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-                              op->lrp_networks.ipv4_addrs[i].addr_s);
-                const char *action = "icmp4 {"
-                                     "eth.dst <-> eth.src; "
-                                     "ip4.dst <-> ip4.src; "
-                                     "ip.ttl = 255; "
-                                     "icmp4.type = 3; "
-                                     "icmp4.code = 3; "
-                                     "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
-                              ds_cstr(&match), action);
-
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-                              op->lrp_networks.ipv4_addrs[i].addr_s);
-                action = "tcp_reset {"
-                         "eth.dst <-> eth.src; "
-                         "ip4.dst <-> ip4.src; "
-                         "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
-                              ds_cstr(&match), action);
-
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip4 && ip4.dst == %s && !ip.later_frag",
-                              op->lrp_networks.ipv4_addrs[i].addr_s);
-                action = "icmp4 {"
-                         "eth.dst <-> eth.src; "
-                         "ip4.dst <-> ip4.src; "
-                         "ip.ttl = 255; "
-                         "icmp4.type = 3; "
-                         "icmp4.code = 2; "
-                         "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 70,
-                              ds_cstr(&match), action);
-            }
-        }
-
-        ds_clear(&match);
-        ds_put_cstr(&match, "ip4.dst == {");
-        bool has_drop_ips = false;
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            bool snat_ip_is_router_ip = false;
-            for (int j = 0; j < n_snat_ips; j++) {
-                /* Packets to SNAT IPs should not be dropped. */
-                if (op->lrp_networks.ipv4_addrs[i].addr == snat_ips[j]) {
-                    snat_ip_is_router_ip = true;
-                    break;
-                }
-            }
-            if (snat_ip_is_router_ip) {
-                continue;
-            }
-            ds_put_format(&match, "%s, ",
-                          op->lrp_networks.ipv4_addrs[i].addr_s);
-            has_drop_ips = true;
-        }
-        ds_chomp(&match, ' ');
-        ds_chomp(&match, ',');
-        ds_put_cstr(&match, "}");
-
-        if (has_drop_ips) {
-            /* Drop IP traffic to this router. */
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60,
-                          ds_cstr(&match), "drop;");
-        }
-
-        free(snat_ips);
-    }
-
-    /* Logical router ingress table 1: IP Input for IPv6. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp) {
-            continue;
-        }
-
-        if (op->derived) {
-            /* No ingress packets are accepted on a chassisredirect
-             * port, so no need to program flows for that port. */
-            continue;
-        }
-
-        if (op->lrp_networks.n_ipv6_addrs) {
-            /* L3 admission control: drop packets that originate from an
-             * IPv6 address owned by the router (priority 100). */
-            ds_clear(&match);
-            ds_put_cstr(&match, "ip6.src == ");
-            op_put_v6_networks(&match, op);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-                          ds_cstr(&match), "drop;");
-
-            /* ICMPv6 echo reply.  These flows reply to echo requests
-             * received for the router's IP address. */
-            ds_clear(&match);
-            ds_put_cstr(&match, "ip6.dst == ");
-            op_put_v6_networks(&match, op);
-            ds_put_cstr(&match, " && icmp6.type == 128 && icmp6.code == 0");
-
-            ds_clear(&actions);
-            ds_put_cstr(&actions,
-                        "ip6.dst <-> ip6.src; "
-                        "ip.ttl = 255; "
-                        "icmp6.type = 129; "
-                        "flags.loopback = 1; "
-                        "next; ");
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-
-            /* Drop IPv6 traffic to this router. */
-            ds_clear(&match);
-            ds_put_cstr(&match, "ip6.dst == ");
-            op_put_v6_networks(&match, op);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60,
-                          ds_cstr(&match), "drop;");
-        }
-
-        /* ND reply.  These flows reply to ND solicitations for the
-         * router's own IP address. */
-        for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-            ds_clear(&match);
-            ds_put_format(&match,
-                    "inport == %s && nd_ns && ip6.dst == {%s, %s} "
-                    "&& nd.target == %s",
-                    op->json_key,
-                    op->lrp_networks.ipv6_addrs[i].addr_s,
-                    op->lrp_networks.ipv6_addrs[i].sn_addr_s,
-                    op->lrp_networks.ipv6_addrs[i].addr_s);
-            if (op->od->l3dgw_port && op == op->od->l3dgw_port
-                && op->od->l3redirect_port) {
-                /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-                 * should only be sent from the "redirect-chassis", so that
-                 * upstream MAC learning points to the "redirect-chassis".
-                 * Also need to avoid generation of multiple ND replies
-                 * from different chassis. */
-                ds_put_format(&match, " && is_chassis_resident(%s)",
-                              op->od->l3redirect_port->json_key);
-            }
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                          "put_nd(inport, ip6.src, nd.sll); "
-                          "nd_na_router { "
-                          "eth.src = %s; "
-                          "ip6.src = %s; "
-                          "nd.target = %s; "
-                          "nd.tll = %s; "
-                          "outport = inport; "
-                          "flags.loopback = 1; "
-                          "output; "
-                          "};",
-                          op->lrp_networks.ea_s,
-                          op->lrp_networks.ipv6_addrs[i].addr_s,
-                          op->lrp_networks.ipv6_addrs[i].addr_s,
-                          op->lrp_networks.ea_s);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        /* UDP/TCP port unreachable */
-        if (!smap_get(&op->od->nbr->options, "chassis")
-            && !op->od->l3dgw_port) {
-            for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip6 && ip6.dst == %s && !ip.later_frag && tcp",
-                              op->lrp_networks.ipv6_addrs[i].addr_s);
-                const char *action = "tcp_reset {"
-                                     "eth.dst <-> eth.src; "
-                                     "ip6.dst <-> ip6.src; "
-                                     "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
-                          ds_cstr(&match), action);
-
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip6 && ip6.dst == %s && !ip.later_frag && udp",
-                              op->lrp_networks.ipv6_addrs[i].addr_s);
-                action = "icmp6 {"
-                         "eth.dst <-> eth.src; "
-                         "ip6.dst <-> ip6.src; "
-                         "ip.ttl = 255; "
-                         "icmp6.type = 1; "
-                         "icmp6.code = 4; "
-                         "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
-                              ds_cstr(&match), action);
-
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "ip6 && ip6.dst == %s && !ip.later_frag",
-                              op->lrp_networks.ipv6_addrs[i].addr_s);
-                action = "icmp6 {"
-                         "eth.dst <-> eth.src; "
-                         "ip6.dst <-> ip6.src; "
-                         "ip.ttl = 255; "
-                         "icmp6.type = 1; "
-                         "icmp6.code = 3; "
-                         "next; };";
-                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 70,
-                              ds_cstr(&match), action);
-            }
-        }
-
-        /* ICMPv6 time exceeded */
-        for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-            /* skip link-local address */
-            if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-                continue;
-            }
-
-            ds_clear(&match);
-            ds_clear(&actions);
-
-            ds_put_format(&match,
-                          "inport == %s && ip6 && "
-                          "ip6.src == %s/%d && "
-                          "ip.ttl == {0, 1} && !ip.later_frag",
-                          op->json_key,
-                          op->lrp_networks.ipv6_addrs[i].network_s,
-                          op->lrp_networks.ipv6_addrs[i].plen);
-            ds_put_format(&actions,
-                          "icmp6 {"
-                          "eth.dst <-> eth.src; "
-                          "ip6.dst = ip6.src; "
-                          "ip6.src = %s; "
-                          "ip.ttl = 255; "
-                          "icmp6.type = 3; /* Time exceeded */ "
-                          "icmp6.code = 0; /* TTL exceeded in transit */ "
-                          "next; };",
-                          op->lrp_networks.ipv6_addrs[i].addr_s);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-    }
-
-    /* NAT, Defrag and load balancing. */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        /* Packets are allowed by default. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-
-        /* NAT rules are only valid on Gateway routers and routers with
-         * l3dgw_port (router has a port with "redirect-chassis"
-         * specified). */
-        if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-            continue;
-        }
-
-        ovs_be32 snat_ip;
-        const char *dnat_force_snat_ip = get_force_snat_ip(od, "dnat",
-                                                           &snat_ip);
-        const char *lb_force_snat_ip = get_force_snat_ip(od, "lb",
-                                                         &snat_ip);
-
-        for (int i = 0; i < od->nbr->n_nat; i++) {
-            const struct nbrec_nat *nat;
-
-            nat = od->nbr->nat[i];
-
-            ovs_be32 ip, mask;
-
-            char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-            if (error || mask != OVS_BE32_MAX) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-                             nat->external_ip);
-                free(error);
-                continue;
-            }
-
-            /* Check the validity of nat->logical_ip. 'logical_ip' can
-             * be a subnet when the type is "snat". */
-            error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-            if (!strcmp(nat->type, "snat")) {
-                if (error) {
-                    static struct vlog_rate_limit rl =
-                        VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-                                 "in router "UUID_FMT"",
-                                 nat->logical_ip, UUID_ARGS(&od->key));
-                    free(error);
-                    continue;
-                }
-            } else {
-                if (error || mask != OVS_BE32_MAX) {
-                    static struct vlog_rate_limit rl =
-                        VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-                        ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-                    free(error);
-                    continue;
-                }
-            }
-
-            /* For distributed router NAT, determine whether this NAT rule
-             * satisfies the conditions for distributed NAT processing. */
-            bool distributed = false;
-            struct eth_addr mac;
-            if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-                nat->logical_port && nat->external_mac) {
-                if (eth_addr_from_string(nat->external_mac, &mac)) {
-                    distributed = true;
-                } else {
-                    static struct vlog_rate_limit rl =
-                        VLOG_RATE_LIMIT_INIT(5, 1);
-                    VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-                        ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-                    continue;
-                }
-            }
-
-            /* Ingress UNSNAT table: It is for already established connections'
-             * reverse traffic. i.e., SNAT has already been done in egress
-             * pipeline and now the packet has entered the ingress pipeline as
-             * part of a reply. We undo the SNAT here.
-             *
-             * Undoing SNAT has to happen before DNAT processing.  This is
-             * because when the packet was DNATed in ingress pipeline, it did
-             * not know about the possibility of eventual additional SNAT in
-             * egress pipeline. */
-            if (!strcmp(nat->type, "snat")
-                || !strcmp(nat->type, "dnat_and_snat")) {
-                if (!od->l3dgw_port) {
-                    /* Gateway router. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s",
-                                  nat->external_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
-                                  ds_cstr(&match), "ct_snat;");
-                } else {
-                    /* Distributed router. */
-
-                    /* Traffic received on l3dgw_port is subject to NAT. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s"
-                                          " && inport == %s",
-                                  nat->external_ip,
-                                  od->l3dgw_port->json_key);
-                    if (!distributed && od->l3redirect_port) {
-                        /* Flows for NAT rules that are centralized are only
-                         * programmed on the "redirect-chassis". */
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      od->l3redirect_port->json_key);
-                    }
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
-                                  ds_cstr(&match), "ct_snat;");
-
-                    /* Traffic received on other router ports must be
-                     * redirected to the central instance of the l3dgw_port
-                     * for NAT processing. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s",
-                                  nat->external_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 50,
-                                  ds_cstr(&match),
-                                  REGBIT_NAT_REDIRECT" = 1; next;");
-                }
-            }
-
-            /* Ingress DNAT table: Packets enter the pipeline with destination
-             * IP address that needs to be DNATted from a external IP address
-             * to a logical IP address. */
-            if (!strcmp(nat->type, "dnat")
-                || !strcmp(nat->type, "dnat_and_snat")) {
-                if (!od->l3dgw_port) {
-                    /* Gateway router. */
-                    /* Packet when it goes from the initiator to destination.
-                     * We need to set flags.loopback because the router can
-                     * send the packet back through the same interface. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s",
-                                  nat->external_ip);
-                    ds_clear(&actions);
-                    if (dnat_force_snat_ip) {
-                        /* Indicate to the future tables that a DNAT has taken
-                         * place and a force SNAT needs to be done in the
-                         * Egress SNAT table. */
-                        ds_put_format(&actions,
-                                      "flags.force_snat_for_dnat = 1; ");
-                    }
-                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
-                                  nat->logical_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
-                                  ds_cstr(&match), ds_cstr(&actions));
-                } else {
-                    /* Distributed router. */
-
-                    /* Traffic received on l3dgw_port is subject to NAT. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s"
-                                          " && inport == %s",
-                                  nat->external_ip,
-                                  od->l3dgw_port->json_key);
-                    if (!distributed && od->l3redirect_port) {
-                        /* Flows for NAT rules that are centralized are only
-                         * programmed on the "redirect-chassis". */
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      od->l3redirect_port->json_key);
-                    }
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "ct_dnat(%s);",
-                                  nat->logical_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
-                                  ds_cstr(&match), ds_cstr(&actions));
-
-                    /* Traffic received on other router ports must be
-                     * redirected to the central instance of the l3dgw_port
-                     * for NAT processing. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.dst == %s",
-                                  nat->external_ip);
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-                                  ds_cstr(&match),
-                                  REGBIT_NAT_REDIRECT" = 1; next;");
-                }
-            }
-
-            /* Egress UNDNAT table: It is for already established connections'
-             * reverse traffic. i.e., DNAT has already been done in ingress
-             * pipeline and now the packet has entered the egress pipeline as
-             * part of a reply. We undo the DNAT here.
-             *
-             * Note that this only applies for NAT on a distributed router.
-             * Undo DNAT on a gateway router is done in the ingress DNAT
-             * pipeline stage. */
-            if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-                || !strcmp(nat->type, "dnat_and_snat"))) {
-                ds_clear(&match);
-                ds_put_format(&match, "ip && ip4.src == %s"
-                                      " && outport == %s",
-                              nat->logical_ip,
-                              od->l3dgw_port->json_key);
-                if (!distributed && od->l3redirect_port) {
-                    /* Flows for NAT rules that are centralized are only
-                     * programmed on the "redirect-chassis". */
-                    ds_put_format(&match, " && is_chassis_resident(%s)",
-                                  od->l3redirect_port->json_key);
-                }
-                ds_clear(&actions);
-                if (distributed) {
-                    ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-                                  ETH_ADDR_ARGS(mac));
-                }
-                ds_put_format(&actions, "ct_dnat;");
-                ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-                              ds_cstr(&match), ds_cstr(&actions));
-            }
-
-            /* Egress SNAT table: Packets enter the egress pipeline with
-             * source ip address that needs to be SNATted to a external ip
-             * address. */
-            if (!strcmp(nat->type, "snat")
-                || !strcmp(nat->type, "dnat_and_snat")) {
-                if (!od->l3dgw_port) {
-                    /* Gateway router. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.src == %s",
-                                  nat->logical_ip);
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
-
-                    /* The priority here is calculated such that the
-                     * nat->logical_ip with the longest mask gets a higher
-                     * priority. */
-                    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT,
-                                  count_1bits(ntohl(mask)) + 1,
-                                  ds_cstr(&match), ds_cstr(&actions));
-                } else {
-                    uint16_t priority = count_1bits(ntohl(mask)) + 1;
-
-                    /* Distributed router. */
-                    ds_clear(&match);
-                    ds_put_format(&match, "ip && ip4.src == %s"
-                                          " && outport == %s",
-                                  nat->logical_ip,
-                                  od->l3dgw_port->json_key);
-                    if (!distributed && od->l3redirect_port) {
-                        /* Flows for NAT rules that are centralized are only
-                         * programmed on the "redirect-chassis". */
-                        priority += 128;
-                        ds_put_format(&match, " && is_chassis_resident(%s)",
-                                      od->l3redirect_port->json_key);
-                    }
-                    ds_clear(&actions);
-                    if (distributed) {
-                        ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-                                      ETH_ADDR_ARGS(mac));
-                    }
-                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
-
-                    /* The priority here is calculated such that the
-                     * nat->logical_ip with the longest mask gets a higher
-                     * priority. */
-                    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT,
-                                  priority, ds_cstr(&match),
-                                  ds_cstr(&actions));
-                }
-            }
-
-            /* Logical router ingress table 0:
-             * For NAT on a distributed router, add rules allowing
-             * ingress traffic with eth.dst matching nat->external_mac
-             * on the l3dgw_port instance where nat->logical_port is
-             * resident. */
-            if (distributed) {
-                ds_clear(&match);
-                ds_put_format(&match,
-                              "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-                              " && is_chassis_resident(\"%s\")",
-                              ETH_ADDR_ARGS(mac),
-                              od->l3dgw_port->json_key,
-                              nat->logical_port);
-                ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-                              ds_cstr(&match), "next;");
-            }
-
-            /* Ingress Gateway Redirect Table: For NAT on a distributed
-             * router, add flows that are specific to a NAT rule.  These
-             * flows indicate the presence of an applicable NAT rule that
-             * can be applied in a distributed manner. */
-            if (distributed) {
-                ds_clear(&match);
-                ds_put_format(&match, "ip4.src == %s && outport == %s",
-                              nat->logical_ip,
-                              od->l3dgw_port->json_key);
-                ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 100,
-                              ds_cstr(&match), "next;");
-            }
-
-            /* Egress Loopback table: For NAT on a distributed router.
-             * If packets in the egress pipeline on the distributed
-             * gateway port have ip.dst matching a NAT external IP, then
-             * loop a clone of the packet back to the beginning of the
-             * ingress pipeline with inport = outport. */
-            if (od->l3dgw_port) {
-                /* Distributed router. */
-                if (!strcmp(nat->type, "dnat_and_snat") &&
-                    nat->external_mac && nat->external_ip) {
-                    for (int j = 0; j < od->nbr->n_nat; j++) {
-                        const struct nbrec_nat *nat2 = od->nbr->nat[j];
-
-                        if (nat2 == nat ||
-                            strcmp(nat2->type, "dnat_and_snat") ||
-                            !nat2->external_mac || !nat2->external_ip) {
-                            continue;
-                        }
-
-                        ds_clear(&match);
-                        ds_put_format(&match, "is_chassis_resident(\"%s\") && "
-                                      "ip4.src == %s && ip4.dst == %s",
-                                      nat->logical_port, nat2->external_ip,
-                                      nat->external_ip);
-                        ds_clear(&actions);
-                        ds_put_format(&actions,
-                                      "inport = outport; outport = \"\"; "
-                                      "flags = 0; flags.loopback = 1; "
-                                      REGBIT_EGRESS_LOOPBACK" = 1; "
-                                      "next(pipeline=ingress, table=0); ");
-                        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 300,
-                                      ds_cstr(&match),  ds_cstr(&actions));
-
-                        ds_clear(&match);
-                        ds_put_format(&match,
-                                      "ip4.src == %s && ip4.dst == %s",
-                                      nat2->external_ip, nat->external_ip);
-                        ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 200,
-                                      ds_cstr(&match), "next;");
-                        ds_clear(&match);
-                    }
-                }
-
-                ds_clear(&match);
-                ds_put_format(&match, "ip4.dst == %s && outport == %s",
-                              nat->external_ip,
-                              od->l3dgw_port->json_key);
-                ds_clear(&actions);
-                ds_put_format(&actions,
-                              "clone { ct_clear; "
-                              "inport = outport; outport = \"\"; "
-                              "flags = 0; flags.loopback = 1; ");
-                for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-                    ds_put_format(&actions, "reg%d = 0; ", j);
-                }
-                ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-                              "next(pipeline=ingress, table=0); };");
-                ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-                              ds_cstr(&match), ds_cstr(&actions));
-            }
-        }
-
-        /* Handle force SNAT options set in the gateway router. */
-        if (dnat_force_snat_ip && !od->l3dgw_port) {
-            /* If a packet with destination IP address as that of the
-             * gateway router (as set in options:dnat_force_snat_ip) is seen,
-             * UNSNAT it. */
-            ds_clear(&match);
-            ds_put_format(&match, "ip && ip4.dst == %s", dnat_force_snat_ip);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 110,
-                          ds_cstr(&match), "ct_snat;");
-
-            /* Higher priority rules to force SNAT with the IP addresses
-             * configured in the Gateway router.  This only takes effect
-             * when the packet has already been DNATed once. */
-            ds_clear(&match);
-            ds_put_format(&match, "flags.force_snat_for_dnat == 1 && ip");
-            ds_clear(&actions);
-            ds_put_format(&actions, "ct_snat(%s);", dnat_force_snat_ip);
-            ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-        if (lb_force_snat_ip && !od->l3dgw_port) {
-            /* If a packet with destination IP address as that of the
-             * gateway router (as set in options:lb_force_snat_ip) is seen,
-             * UNSNAT it. */
-            ds_clear(&match);
-            ds_put_format(&match, "ip && ip4.dst == %s", lb_force_snat_ip);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
-                          ds_cstr(&match), "ct_snat;");
-
-            /* Load balanced traffic will have flags.force_snat_for_lb set.
-             * Force SNAT it. */
-            ds_clear(&match);
-            ds_put_format(&match, "flags.force_snat_for_lb == 1 && ip");
-            ds_clear(&actions);
-            ds_put_format(&actions, "ct_snat(%s);", lb_force_snat_ip);
-            ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        if (!od->l3dgw_port) {
-            /* For gateway router, re-circulate every packet through
-            * the DNAT zone.  This helps with the following.
-            *
-            * Any packet that needs to be unDNATed in the reverse
-            * direction gets unDNATed. Ideally this could be done in
-            * the egress pipeline. But since the gateway router
-            * does not have any feature that depends on the source
-            * ip address being external IP address for IP routing,
-            * we can do it here, saving a future re-circulation. */
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-                          "ip", "flags.loopback = 1; ct_dnat;");
-        } else {
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 400,
-                          REGBIT_DISTRIBUTED_NAT" == 1", "next;");
-
-            /* For NAT on a distributed router, add flows to Ingress
-             * IP Routing table, Ingress ARP Resolution table, and
-             * Ingress Gateway Redirect Table that are not specific to a
-             * NAT rule. */
-
-            /* The highest priority IN_IP_ROUTING rule matches packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages),
-             * with action "ip.ttl--; next;".  The IN_GW_REDIRECT table
-             * will take care of setting the outport. */
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 300,
-                          REGBIT_NAT_REDIRECT" == 1", "ip.ttl--; next;");
-
-            /* The highest priority IN_ARP_RESOLVE rule matches packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages),
-             * then sets eth.dst to the distributed gateway port's
-             * ethernet address. */
-            ds_clear(&actions);
-            ds_put_format(&actions, "eth.dst = %s; next;",
-                          od->l3dgw_port->lrp_networks.ea_s);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 200,
-                          REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions));
-
-            /* The highest priority IN_GW_REDIRECT rule redirects packets
-             * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages) to
-             * the central instance of the l3dgw_port for NAT processing. */
-            ds_clear(&actions);
-            ds_put_format(&actions, "outport = %s; next;",
-                          od->l3redirect_port->json_key);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 200,
-                          REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions));
-        }
-
-        /* Load balancing and packet defrag are only valid on
-         * Gateway routers or router with gateway port. */
-        if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-            continue;
-        }
-
-        /* A set to hold all ips that need defragmentation and tracking. */
-        struct sset all_ips = SSET_INITIALIZER(&all_ips);
-
-        for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-            struct nbrec_load_balancer *lb = od->nbr->load_balancer[i];
-            struct smap *vips = &lb->vips;
-            struct smap_node *node;
-
-            SMAP_FOR_EACH (node, vips) {
-                uint16_t port = 0;
-                int addr_family;
-
-                /* node->key contains IP:port or just IP. */
-                char *ip_address = NULL;
-                ip_address_and_port_from_lb_key(node->key, &ip_address, &port,
-                        &addr_family);
-                if (!ip_address) {
-                    continue;
-                }
-
-                if (!sset_contains(&all_ips, ip_address)) {
-                    sset_add(&all_ips, ip_address);
-                    /* If there are any load balancing rules, we should send
-                     * the packet to conntrack for defragmentation and
-                     * tracking.  This helps with two things.
-                     *
-                     * 1. With tracking, we can send only new connections to
-                     *    pick a DNAT ip address from a group.
-                     * 2. If there are L4 ports in load balancing rules, we
-                     *    need the defragmentation to match on L4 ports. */
-                    ds_clear(&match);
-                    if (addr_family == AF_INET) {
-                        ds_put_format(&match, "ip && ip4.dst == %s",
-                                      ip_address);
-                    } else {
-                        ds_put_format(&match, "ip && ip6.dst == %s",
-                                      ip_address);
-                    }
-                    ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG,
-                                  100, ds_cstr(&match), "ct_next;");
-                }
-
-                /* Higher priority rules are added for load-balancing in DNAT
-                 * table.  For every match (on a VIP[:port]), we add two flows
-                 * via add_router_lb_flow().  One flow is for specific matching
-                 * on ct.new with an action of "ct_lb($targets);".  The other
-                 * flow is for ct.est with an action of "ct_dnat;". */
-                ds_clear(&actions);
-                ds_put_format(&actions, "ct_lb(%s);", node->value);
-
-                ds_clear(&match);
-                if (addr_family == AF_INET) {
-                    ds_put_format(&match, "ip && ip4.dst == %s",
-                                ip_address);
-                } else {
-                    ds_put_format(&match, "ip && ip6.dst == %s",
-                                ip_address);
-                }
-                free(ip_address);
-
-                int prio = 110;
-                bool is_udp = lb->protocol && !strcmp(lb->protocol, "udp") ?
-                    true : false;
-                if (port) {
-                    if (is_udp) {
-                        ds_put_format(&match, " && udp && udp.dst == %d",
-                                      port);
-                    } else {
-                        ds_put_format(&match, " && tcp && tcp.dst == %d",
-                                      port);
-                    }
-                    prio = 120;
-                }
-
-                if (od->l3redirect_port) {
-                    ds_put_format(&match, " && is_chassis_resident(%s)",
-                                  od->l3redirect_port->json_key);
-                }
-                add_router_lb_flow(lflows, od, &match, &actions, prio,
-                                   lb_force_snat_ip, node->value, is_udp,
-                                   addr_family);
-            }
-        }
-        sset_destroy(&all_ips);
-    }
-
-    /* Logical router ingress table 5 and 6: IPv6 Router Adv (RA) options and
-     * response. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp || op->nbrp->peer || !op->peer) {
-            continue;
-        }
-
-        if (!op->lrp_networks.n_ipv6_addrs) {
-            continue;
-        }
-
-        const char *address_mode = smap_get(
-            &op->nbrp->ipv6_ra_configs, "address_mode");
-
-        if (!address_mode) {
-            continue;
-        }
-        if (strcmp(address_mode, "slaac") &&
-            strcmp(address_mode, "dhcpv6_stateful") &&
-            strcmp(address_mode, "dhcpv6_stateless")) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
-                         address_mode);
-            continue;
-        }
-
-        if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
-                          false)) {
-            copy_ra_to_sb(op, address_mode);
-        }
-
-        ds_clear(&match);
-        ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
-                              op->json_key);
-        ds_clear(&actions);
-
-        const char *mtu_s = smap_get(
-            &op->nbrp->ipv6_ra_configs, "mtu");
-
-        /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
-        uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
-
-        ds_put_format(&actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
-                      "addr_mode = \"%s\", slla = %s",
-                      address_mode, op->lrp_networks.ea_s);
-        if (mtu > 0) {
-            ds_put_format(&actions, ", mtu = %u", mtu);
-        }
-
-        bool add_rs_response_flow = false;
-
-        for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-            if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-                continue;
-            }
-
-            ds_put_format(&actions, ", prefix = %s/%u",
-                          op->lrp_networks.ipv6_addrs[i].network_s,
-                          op->lrp_networks.ipv6_addrs[i].plen);
-
-            add_rs_response_flow = true;
-        }
-
-        if (add_rs_response_flow) {
-            ds_put_cstr(&actions, "); next;");
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50,
-                          ds_cstr(&match), ds_cstr(&actions));
-            ds_clear(&actions);
-            ds_clear(&match);
-            ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && "
-                          "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
-
-            char ip6_str[INET6_ADDRSTRLEN + 1];
-            struct in6_addr lla;
-            in6_generate_lla(op->lrp_networks.ea, &lla);
-            memset(ip6_str, 0, sizeof(ip6_str));
-            ipv6_string_mapped(ip6_str, &lla);
-            ds_put_format(&actions, "eth.dst = eth.src; eth.src = %s; "
-                          "ip6.dst = ip6.src; ip6.src = %s; "
-                          "outport = inport; flags.loopback = 1; "
-                          "output;",
-                          op->lrp_networks.ea_s, ip6_str);
-            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_RESPONSE, 50,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-    }
-
-    /* Logical router ingress table 5, 6: RS responder, by default goto next.
-     * (priority 0)*/
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
-    }
-
-    /* Logical router ingress table 7: IP Routing.
-     *
-     * A packet that arrives at this table is an IP packet that should be
-     * routed to the address in 'ip[46].dst'. This table sets outport to
-     * the correct output port, eth.src to the output port's MAC
-     * address, and '[xx]reg0' to the next-hop IP address (leaving
-     * 'ip[46].dst', the packet’s final destination, unchanged), and
-     * advances to the next table for ARP/ND resolution. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp) {
-            continue;
-        }
-
-        /* create logical flows for DVR floating IPs */
-        add_distributed_nat_routes(lflows, op);
-
-        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-            add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
-                      op->lrp_networks.ipv4_addrs[i].network_s,
-                      op->lrp_networks.ipv4_addrs[i].plen, NULL, NULL);
-        }
-
-        for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-            add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s,
-                      op->lrp_networks.ipv6_addrs[i].network_s,
-                      op->lrp_networks.ipv6_addrs[i].plen, NULL, NULL);
-        }
-    }
-
-    /* Convert the static routes to flows. */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        for (int i = 0; i < od->nbr->n_static_routes; i++) {
-            const struct nbrec_logical_router_static_route *route;
-
-            route = od->nbr->static_routes[i];
-            build_static_route_flow(lflows, od, ports, route);
-        }
-    }
-
-    /* Logical router ingress table 8: Policy.
-     *
-     * A packet that arrives at this table is an IP packet that should be
-     * permitted/denied/rerouted to the address in the rule's nexthop.
-     * This table sets outport to the correct out_port,
-     * eth.src to the output port's MAC address,
-     * and '[xx]reg0' to the next-hop IP address (leaving
-     * 'ip[46].dst', the packet’s final destination, unchanged), and
-     * advances to the next table for ARP/ND resolution. */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-        /* This is a catch-all rule. It has the lowest priority (0)
-         * does a match-all("1") and pass-through (next) */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;");
-
-        /* Convert routing policies to flows. */
-        for (int i = 0; i < od->nbr->n_policies; i++) {
-            const struct nbrec_logical_router_policy *rule
-                = od->nbr->policies[i];
-            build_routing_policy_flow(lflows, od, ports, rule);
-        }
-    }
-
-
-    /* XXX destination unreachable */
-
-    /* Local router ingress table 9: ARP Resolution.
-     *
-     * Any packet that reaches this table is an IP packet whose next-hop IP
-     * address is in reg0. (ip4.dst is the final destination.) This table
-     * resolves the IP address in reg0 into an output port in outport and an
-     * Ethernet address in eth.dst. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
-            continue;
-        }
-
-        if (op->nbrp) {
-            /* This is a logical router port. If next-hop IP address in
-             * '[xx]reg0' matches IP address of this router port, then
-             * the packet is intended to eventually be sent to this
-             * logical port. Set the destination mac address using this
-             * port's mac address.
-             *
-             * The packet is still in peer's logical pipeline. So the match
-             * should be on peer's outport. */
-            if (op->peer && op->nbrp->peer) {
-                if (op->lrp_networks.n_ipv4_addrs) {
-                    ds_clear(&match);
-                    ds_put_format(&match, "outport == %s && reg0 == ",
-                                  op->peer->json_key);
-                    op_put_v4_networks(&match, op, false);
-
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "eth.dst = %s; next;",
-                                  op->lrp_networks.ea_s);
-                    ovn_lflow_add(lflows, op->peer->od, S_ROUTER_IN_ARP_RESOLVE,
-                                  100, ds_cstr(&match), ds_cstr(&actions));
-                }
-
-                if (op->lrp_networks.n_ipv6_addrs) {
-                    ds_clear(&match);
-                    ds_put_format(&match, "outport == %s && xxreg0 == ",
-                                  op->peer->json_key);
-                    op_put_v6_networks(&match, op);
-
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "eth.dst = %s; next;",
-                                  op->lrp_networks.ea_s);
-                    ovn_lflow_add(lflows, op->peer->od, S_ROUTER_IN_ARP_RESOLVE,
-                                  100, ds_cstr(&match), ds_cstr(&actions));
-                }
-            }
-        } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")) {
-            /* This is a logical switch port that backs a VM or a container.
-             * Extract its addresses. For each of the address, go through all
-             * the router ports attached to the switch (to which this port
-             * connects) and if the address in question is reachable from the
-             * router port, add an ARP/ND entry in that router's pipeline. */
-
-            for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-                const char *ea_s = op->lsp_addrs[i].ea_s;
-                for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-                    const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
-                    for (size_t k = 0; k < op->od->n_router_ports; k++) {
-                        /* Get the Logical_Router_Port that the
-                         * Logical_Switch_Port is connected to, as
-                         * 'peer'. */
-                        const char *peer_name = smap_get(
-                            &op->od->router_ports[k]->nbsp->options,
-                            "router-port");
-                        if (!peer_name) {
-                            continue;
-                        }
-
-                        struct ovn_port *peer = ovn_port_find(ports, peer_name);
-                        if (!peer || !peer->nbrp) {
-                            continue;
-                        }
-
-                        if (!find_lrp_member_ip(peer, ip_s)) {
-                            continue;
-                        }
-
-                        ds_clear(&match);
-                        ds_put_format(&match, "outport == %s && reg0 == %s",
-                                      peer->json_key, ip_s);
-
-                        ds_clear(&actions);
-                        ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
-                        ovn_lflow_add(lflows, peer->od,
-                                      S_ROUTER_IN_ARP_RESOLVE, 100,
-                                      ds_cstr(&match), ds_cstr(&actions));
-                    }
-                }
-
-                for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-                    const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
-                    for (size_t k = 0; k < op->od->n_router_ports; k++) {
-                        /* Get the Logical_Router_Port that the
-                         * Logical_Switch_Port is connected to, as
-                         * 'peer'. */
-                        const char *peer_name = smap_get(
-                            &op->od->router_ports[k]->nbsp->options,
-                            "router-port");
-                        if (!peer_name) {
-                            continue;
-                        }
-
-                        struct ovn_port *peer = ovn_port_find(ports, peer_name);
-                        if (!peer || !peer->nbrp) {
-                            continue;
-                        }
-
-                        if (!find_lrp_member_ip(peer, ip_s)) {
-                            continue;
-                        }
-
-                        ds_clear(&match);
-                        ds_put_format(&match, "outport == %s && xxreg0 == %s",
-                                      peer->json_key, ip_s);
-
-                        ds_clear(&actions);
-                        ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
-                        ovn_lflow_add(lflows, peer->od,
-                                      S_ROUTER_IN_ARP_RESOLVE, 100,
-                                      ds_cstr(&match), ds_cstr(&actions));
-                    }
-                }
-            }
-        } else if (!strcmp(op->nbsp->type, "router")) {
-            /* This is a logical switch port that connects to a router. */
-
-            /* The peer of this switch port is the router port for which
-             * we need to add logical flows such that it can resolve
-             * ARP entries for all the other router ports connected to
-             * the switch in question. */
-
-            const char *peer_name = smap_get(&op->nbsp->options,
-                                             "router-port");
-            if (!peer_name) {
-                continue;
-            }
-
-            struct ovn_port *peer = ovn_port_find(ports, peer_name);
-            if (!peer || !peer->nbrp) {
-                continue;
-            }
-
-            for (size_t i = 0; i < op->od->n_router_ports; i++) {
-                const char *router_port_name = smap_get(
-                                    &op->od->router_ports[i]->nbsp->options,
-                                    "router-port");
-                struct ovn_port *router_port = ovn_port_find(ports,
-                                                             router_port_name);
-                if (!router_port || !router_port->nbrp) {
-                    continue;
-                }
-
-                /* Skip the router port under consideration. */
-                if (router_port == peer) {
-                   continue;
-                }
-
-                if (router_port->lrp_networks.n_ipv4_addrs) {
-                    ds_clear(&match);
-                    ds_put_format(&match, "outport == %s && reg0 == ",
-                                  peer->json_key);
-                    op_put_v4_networks(&match, router_port, false);
-
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "eth.dst = %s; next;",
-                                              router_port->lrp_networks.ea_s);
-                    ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
-                                  100, ds_cstr(&match), ds_cstr(&actions));
-                }
-
-                if (router_port->lrp_networks.n_ipv6_addrs) {
-                    ds_clear(&match);
-                    ds_put_format(&match, "outport == %s && xxreg0 == ",
-                                  peer->json_key);
-                    op_put_v6_networks(&match, router_port);
-
-                    ds_clear(&actions);
-                    ds_put_format(&actions, "eth.dst = %s; next;",
-                                  router_port->lrp_networks.ea_s);
-                    ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
-                                  100, ds_cstr(&match), ds_cstr(&actions));
-                }
-            }
-        }
-    }
-
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
-                      "get_arp(outport, reg0); next;");
-
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
-                      "get_nd(outport, xxreg0); next;");
-    }
-
-    /* Local router ingress table 10: Check packet length.
-     *
-     * Any IPv4 packet with outport set to the distributed gateway
-     * router port, check the packet length and store the result in the
-     * 'REGBIT_PKT_LARGER' register bit.
-     *
-     * Local router ingress table 11: Handle larger packets.
-     *
-     * Any IPv4 packet with outport set to the distributed gateway
-     * router port and the 'REGBIT_PKT_LARGER' register bit is set,
-     * generate ICMPv4 packet with type 3 (Destination Unreachable) and
-     * code 4 (Fragmentation needed).
-     * */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        /* Packets are allowed by default. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1",
-                      "next;");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
-                      "next;");
-
-        if (od->l3dgw_port && od->l3redirect_port) {
-            int gw_mtu = 0;
-            if (od->l3dgw_port->nbrp) {
-                 gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
-                                       "gateway_mtu", 0);
-            }
-            /* Add the flows only if gateway_mtu is configured. */
-            if (gw_mtu <= 0) {
-                continue;
-            }
-
-            ds_clear(&match);
-            ds_put_format(&match, "outport == %s && ip4",
-                          od->l3dgw_port->json_key);
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                          REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-                          " next;", gw_mtu);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
-                          ds_cstr(&match), ds_cstr(&actions));
-
-            for (size_t i = 0; i < od->nbr->n_ports; i++) {
-                struct ovn_port *rp = ovn_port_find(ports,
-                                                    od->nbr->ports[i]->name);
-                if (!rp || rp == od->l3dgw_port) {
-                    continue;
-                }
-                ds_clear(&match);
-                ds_put_format(&match, "inport == %s && outport == %s && ip4 "
-                              "&& "REGBIT_PKT_LARGER,
-                              rp->json_key, od->l3dgw_port->json_key);
-
-                ds_clear(&actions);
-                /* Set icmp4.frag_mtu to gw_mtu - 58. 58 is the Geneve tunnel
-                 * overhead. */
-                ds_put_format(&actions,
-                    "icmp4_error {"
-                    REGBIT_EGRESS_LOOPBACK" = 1; "
-                    "eth.dst = %s; "
-                    "ip4.dst = ip4.src; "
-                    "ip4.src = %s; "
-                    "ip.ttl = 255; "
-                    "icmp4.type = 3; /* Destination Unreachable. */ "
-                    "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-                    "icmp4.frag_mtu = %d; "
-                    "next(pipeline=ingress, table=0); };",
-                    rp->lrp_networks.ea_s,
-                    rp->lrp_networks.ipv4_addrs[0].addr_s,
-                    gw_mtu - 18);
-                ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 50,
-                              ds_cstr(&match), ds_cstr(&actions));
-            }
-        }
-    }
-
-    /* Logical router ingress table 12: Gateway redirect.
-     *
-     * For traffic with outport equal to the l3dgw_port
-     * on a distributed router, this table redirects a subset
-     * of the traffic to the l3redirect_port which represents
-     * the central instance of the l3dgw_port.
-     */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-        if (od->l3dgw_port && od->l3redirect_port) {
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 300,
-                          REGBIT_DISTRIBUTED_NAT" == 1", "next;");
-
-            /* For traffic with outport == l3dgw_port, if the
-             * packet did not match any higher priority redirect
-             * rule, then the traffic is redirected to the central
-             * instance of the l3dgw_port. */
-            ds_clear(&match);
-            ds_put_format(&match, "outport == %s",
-                          od->l3dgw_port->json_key);
-            ds_clear(&actions);
-            ds_put_format(&actions, "outport = %s; next;",
-                          od->l3redirect_port->json_key);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
-                          ds_cstr(&match), ds_cstr(&actions));
-
-            /* If the Ethernet destination has not been resolved,
-             * redirect to the central instance of the l3dgw_port.
-             * Such traffic will be replaced by an ARP request or ND
-             * Neighbor Solicitation in the ARP request ingress
-             * table, before being redirected to the central instance.
-             */
-            ds_put_format(&match, " && eth.dst == 00:00:00:00:00:00");
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 150,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        /* Packets are allowed by default. */
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
-    }
-
-    /* Local router ingress table 13: ARP request.
-     *
-     * In the common case where the Ethernet destination has been resolved,
-     * this table outputs the packet (priority 0).  Otherwise, it composes
-     * and sends an ARP/IPv6 NA request (priority 100). */
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        for (int i = 0; i < od->nbr->n_static_routes; i++) {
-            const struct nbrec_logical_router_static_route *route;
-
-            route = od->nbr->static_routes[i];
-            struct in6_addr gw_ip6;
-            unsigned int plen;
-            char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
-            if (error || plen != 128) {
-                free(error);
-                continue;
-            }
-
-            ds_clear(&match);
-            ds_put_format(&match, "eth.dst == 00:00:00:00:00:00 && "
-                          "ip6 && xxreg0 == %s", route->nexthop);
-            struct in6_addr sn_addr;
-            struct eth_addr eth_dst;
-            in6_addr_solicited_node(&sn_addr, &gw_ip6);
-            ipv6_multicast_to_ethernet(&eth_dst, &sn_addr);
-
-            char sn_addr_s[INET6_ADDRSTRLEN + 1];
-            ipv6_string_mapped(sn_addr_s, &sn_addr);
-
-            ds_clear(&actions);
-            ds_put_format(&actions,
-                          "nd_ns { "
-                          "eth.dst = "ETH_ADDR_FMT"; "
-                          "ip6.dst = %s; "
-                          "nd.target = %s; "
-                          "output; "
-                          "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
-                          route->nexthop);
-            ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
-                          ds_cstr(&match), ds_cstr(&actions));
-        }
-
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-                      "eth.dst == 00:00:00:00:00:00",
-                      "arp { "
-                      "eth.dst = ff:ff:ff:ff:ff:ff; "
-                      "arp.spa = reg1; "
-                      "arp.tpa = reg0; "
-                      "arp.op = 1; " /* ARP request */
-                      "output; "
-                      "};");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-                      "eth.dst == 00:00:00:00:00:00",
-                      "nd_ns { "
-                      "nd.target = xxreg0; "
-                      "output; "
-                      "};");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
-    }
-
-    /* Logical router egress table 1: Delivery (priority 100).
-     *
-     * Priority 100 rules deliver packets to enabled logical ports. */
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbrp) {
-            continue;
-        }
-
-        if (!lrport_is_enabled(op->nbrp)) {
-            /* Drop packets to disabled logical ports (since logical flow
-             * tables are default-drop). */
-            continue;
-        }
-
-        if (op->derived) {
-            /* No egress packets should be processed in the context of
-             * a chassisredirect port.  The chassisredirect port should
-             * be replaced by the l3dgw port in the local output
-             * pipeline stage before egress processing. */
-            continue;
-        }
-
-        ds_clear(&match);
-        ds_put_format(&match, "outport == %s", op->json_key);
-        ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100,
-                      ds_cstr(&match), "output;");
-    }
-
-    ds_destroy(&match);
-    ds_destroy(&actions);
-}
-
-/* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database,
- * constructing their contents based on the OVN_NB database. */
-static void
-build_lflows(struct northd_context *ctx, struct hmap *datapaths,
-             struct hmap *ports, struct hmap *port_groups,
-             struct hmap *mcgroups, struct hmap *igmp_groups)
-{
-    struct hmap lflows = HMAP_INITIALIZER(&lflows);
-
-    build_lswitch_flows(datapaths, ports, port_groups, &lflows, mcgroups,
-                        igmp_groups);
-    build_lrouter_flows(datapaths, ports, &lflows);
-
-    /* Push changes to the Logical_Flow table to database. */
-    const struct sbrec_logical_flow *sbflow, *next_sbflow;
-    SBREC_LOGICAL_FLOW_FOR_EACH_SAFE (sbflow, next_sbflow, ctx->ovnsb_idl) {
-        struct ovn_datapath *od
-            = ovn_datapath_from_sbrec(datapaths, sbflow->logical_datapath);
-        if (!od) {
-            sbrec_logical_flow_delete(sbflow);
-            continue;
-        }
-
-        enum ovn_datapath_type dp_type = od->nbs ? DP_SWITCH : DP_ROUTER;
-        enum ovn_pipeline pipeline
-            = !strcmp(sbflow->pipeline, "ingress") ? P_IN : P_OUT;
-        struct ovn_lflow *lflow = ovn_lflow_find(
-            &lflows, od, ovn_stage_build(dp_type, pipeline, sbflow->table_id),
-            sbflow->priority, sbflow->match, sbflow->actions, sbflow->hash);
-        if (lflow) {
-            ovn_lflow_destroy(&lflows, lflow);
-        } else {
-            sbrec_logical_flow_delete(sbflow);
-        }
-    }
-    struct ovn_lflow *lflow, *next_lflow;
-    HMAP_FOR_EACH_SAFE (lflow, next_lflow, hmap_node, &lflows) {
-        const char *pipeline = ovn_stage_get_pipeline_name(lflow->stage);
-        uint8_t table = ovn_stage_get_table(lflow->stage);
-
-        sbflow = sbrec_logical_flow_insert(ctx->ovnsb_txn);
-        sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->sb);
-        sbrec_logical_flow_set_pipeline(sbflow, pipeline);
-        sbrec_logical_flow_set_table_id(sbflow, table);
-        sbrec_logical_flow_set_priority(sbflow, lflow->priority);
-        sbrec_logical_flow_set_match(sbflow, lflow->match);
-        sbrec_logical_flow_set_actions(sbflow, lflow->actions);
-
-        /* Trim the source locator lflow->where, which looks something like
-         * "ovn/northd/ovn-northd.c:1234", down to just the part following the
-         * last slash, e.g. "ovn-northd.c:1234". */
-        const char *slash = strrchr(lflow->where, '/');
-#if _WIN32
-        const char *backslash = strrchr(lflow->where, '\\');
-        if (!slash || backslash > slash) {
-            slash = backslash;
-        }
-#endif
-        const char *where = slash ? slash + 1 : lflow->where;
-
-        struct smap ids = SMAP_INITIALIZER(&ids);
-        smap_add(&ids, "stage-name", ovn_stage_to_str(lflow->stage));
-        smap_add(&ids, "source", where);
-        if (lflow->stage_hint) {
-            smap_add(&ids, "stage-hint", lflow->stage_hint);
-        }
-        sbrec_logical_flow_set_external_ids(sbflow, &ids);
-        smap_destroy(&ids);
-
-        ovn_lflow_destroy(&lflows, lflow);
-    }
-    hmap_destroy(&lflows);
-
-    /* Push changes to the Multicast_Group table to database. */
-    const struct sbrec_multicast_group *sbmc, *next_sbmc;
-    SBREC_MULTICAST_GROUP_FOR_EACH_SAFE (sbmc, next_sbmc, ctx->ovnsb_idl) {
-        struct ovn_datapath *od = ovn_datapath_from_sbrec(datapaths,
-                                                          sbmc->datapath);
-        if (!od) {
-            sbrec_multicast_group_delete(sbmc);
-            continue;
-        }
-
-        struct multicast_group group = { .name = sbmc->name,
-                                         .key = sbmc->tunnel_key };
-        struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, &group);
-        if (mc) {
-            ovn_multicast_update_sbrec(mc, sbmc);
-            ovn_multicast_destroy(mcgroups, mc);
-        } else {
-            sbrec_multicast_group_delete(sbmc);
-        }
-    }
-    struct ovn_multicast *mc, *next_mc;
-    HMAP_FOR_EACH_SAFE (mc, next_mc, hmap_node, mcgroups) {
-        if (!mc->datapath) {
-            ovn_multicast_destroy(mcgroups, mc);
-            continue;
-        }
-        sbmc = sbrec_multicast_group_insert(ctx->ovnsb_txn);
-        sbrec_multicast_group_set_datapath(sbmc, mc->datapath->sb);
-        sbrec_multicast_group_set_name(sbmc, mc->group->name);
-        sbrec_multicast_group_set_tunnel_key(sbmc, mc->group->key);
-        ovn_multicast_update_sbrec(mc, sbmc);
-        ovn_multicast_destroy(mcgroups, mc);
-    }
-}
-
-static void
-sync_address_set(struct northd_context *ctx, const char *name,
-                 const char **addrs, size_t n_addrs,
-                 struct shash *sb_address_sets)
-{
-    const struct sbrec_address_set *sb_address_set;
-    sb_address_set = shash_find_and_delete(sb_address_sets,
-                                           name);
-    if (!sb_address_set) {
-        sb_address_set = sbrec_address_set_insert(ctx->ovnsb_txn);
-        sbrec_address_set_set_name(sb_address_set, name);
-    }
-
-    sbrec_address_set_set_addresses(sb_address_set,
-                                    addrs, n_addrs);
-}
-
-/* Go through 'addresses' and add found IPv4 addresses to 'ipv4_addrs' and IPv6
- * addresses to 'ipv6_addrs'.
- */
-static void
-split_addresses(const char *addresses, struct svec *ipv4_addrs,
-                struct svec *ipv6_addrs)
-{
-    struct lport_addresses laddrs;
-    extract_lsp_addresses(addresses, &laddrs);
-    for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) {
-        svec_add(ipv4_addrs, laddrs.ipv4_addrs[k].addr_s);
-    }
-    for (size_t k = 0; k < laddrs.n_ipv6_addrs; k++) {
-        svec_add(ipv6_addrs, laddrs.ipv6_addrs[k].addr_s);
-    }
-    destroy_lport_addresses(&laddrs);
-}
-
-/* OVN_Southbound Address_Set table contains same records as in north
- * bound, plus the records generated from Port_Group table in north bound.
- *
- * There are 2 records generated from each port group, one for IPv4, and
- * one for IPv6, named in the format: <port group name>_ip4 and
- * <port group name>_ip6 respectively. MAC addresses are ignored.
- *
- * We always update OVN_Southbound to match the Address_Set and Port_Group
- * in OVN_Northbound, so that the address sets used in Logical_Flows in
- * OVN_Southbound is checked against the proper set.*/
-static void
-sync_address_sets(struct northd_context *ctx)
-{
-    struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
-
-    const struct sbrec_address_set *sb_address_set;
-    SBREC_ADDRESS_SET_FOR_EACH (sb_address_set, ctx->ovnsb_idl) {
-        shash_add(&sb_address_sets, sb_address_set->name, sb_address_set);
-    }
-
-    /* sync port group generated address sets first */
-    const struct nbrec_port_group *nb_port_group;
-    NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
-        struct svec ipv4_addrs = SVEC_EMPTY_INITIALIZER;
-        struct svec ipv6_addrs = SVEC_EMPTY_INITIALIZER;
-        for (size_t i = 0; i < nb_port_group->n_ports; i++) {
-            for (size_t j = 0; j < nb_port_group->ports[i]->n_addresses; j++) {
-                const char *addrs = nb_port_group->ports[i]->addresses[j];
-                if (!is_dynamic_lsp_address(addrs)) {
-                    split_addresses(addrs, &ipv4_addrs, &ipv6_addrs);
-                }
-            }
-            if (nb_port_group->ports[i]->dynamic_addresses) {
-                split_addresses(nb_port_group->ports[i]->dynamic_addresses,
-                                &ipv4_addrs, &ipv6_addrs);
-            }
-        }
-        char *ipv4_addrs_name = xasprintf("%s_ip4", nb_port_group->name);
-        char *ipv6_addrs_name = xasprintf("%s_ip6", nb_port_group->name);
-        sync_address_set(ctx, ipv4_addrs_name,
-                         /* "char **" is not compatible with "const char **" */
-                         (const char **)ipv4_addrs.names,
-                         ipv4_addrs.n, &sb_address_sets);
-        sync_address_set(ctx, ipv6_addrs_name,
-                         /* "char **" is not compatible with "const char **" */
-                         (const char **)ipv6_addrs.names,
-                         ipv6_addrs.n, &sb_address_sets);
-        free(ipv4_addrs_name);
-        free(ipv6_addrs_name);
-        svec_destroy(&ipv4_addrs);
-        svec_destroy(&ipv6_addrs);
-    }
-
-    /* sync user defined address sets, which may overwrite port group
-     * generated address sets if same name is used */
-    const struct nbrec_address_set *nb_address_set;
-    NBREC_ADDRESS_SET_FOR_EACH (nb_address_set, ctx->ovnnb_idl) {
-        sync_address_set(ctx, nb_address_set->name,
-            /* "char **" is not compatible with "const char **" */
-            (const char **)nb_address_set->addresses,
-            nb_address_set->n_addresses, &sb_address_sets);
-    }
-
-    struct shash_node *node, *next;
-    SHASH_FOR_EACH_SAFE (node, next, &sb_address_sets) {
-        sbrec_address_set_delete(node->data);
-        shash_delete(&sb_address_sets, node);
-    }
-    shash_destroy(&sb_address_sets);
-}
-
-/* Each port group in Port_Group table in OVN_Northbound has a corresponding
- * entry in Port_Group table in OVN_Southbound. In OVN_Northbound the entries
- * contains lport uuids, while in OVN_Southbound we store the lport names.
- */
-static void
-sync_port_groups(struct northd_context *ctx)
-{
-    struct shash sb_port_groups = SHASH_INITIALIZER(&sb_port_groups);
-
-    const struct sbrec_port_group *sb_port_group;
-    SBREC_PORT_GROUP_FOR_EACH (sb_port_group, ctx->ovnsb_idl) {
-        shash_add(&sb_port_groups, sb_port_group->name, sb_port_group);
-    }
-
-    const struct nbrec_port_group *nb_port_group;
-    NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
-        sb_port_group = shash_find_and_delete(&sb_port_groups,
-                                               nb_port_group->name);
-        if (!sb_port_group) {
-            sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
-            sbrec_port_group_set_name(sb_port_group, nb_port_group->name);
-        }
-
-        const char **nb_port_names = xcalloc(nb_port_group->n_ports,
-                                             sizeof *nb_port_names);
-        int i;
-        for (i = 0; i < nb_port_group->n_ports; i++) {
-            nb_port_names[i] = nb_port_group->ports[i]->name;
-        }
-        sbrec_port_group_set_ports(sb_port_group,
-                                   nb_port_names,
-                                   nb_port_group->n_ports);
-        free(nb_port_names);
-    }
-
-    struct shash_node *node, *next;
-    SHASH_FOR_EACH_SAFE (node, next, &sb_port_groups) {
-        sbrec_port_group_delete(node->data);
-        shash_delete(&sb_port_groups, node);
-    }
-    shash_destroy(&sb_port_groups);
-}
-
-struct band_entry {
-    int64_t rate;
-    int64_t burst_size;
-    const char *action;
-};
-
-static int
-band_cmp(const void *band1_, const void *band2_)
-{
-    const struct band_entry *band1p = band1_;
-    const struct band_entry *band2p = band2_;
-
-    if (band1p->rate != band2p->rate) {
-        return band1p->rate > band2p->rate ? -1 : 1;
-    } else if (band1p->burst_size != band2p->burst_size) {
-        return band1p->burst_size > band2p->burst_size ? -1 : 1;
-    } else {
-        return strcmp(band1p->action, band2p->action);
-    }
-}
-
-static bool
-bands_need_update(const struct nbrec_meter *nb_meter,
-                  const struct sbrec_meter *sb_meter)
-{
-    if (nb_meter->n_bands != sb_meter->n_bands) {
-        return true;
-    }
-
-    /* A single band is the most common scenario, so speed up that
-     * check. */
-    if (nb_meter->n_bands == 1) {
-        struct nbrec_meter_band *nb_band = nb_meter->bands[0];
-        struct sbrec_meter_band *sb_band = sb_meter->bands[0];
-
-        return !(nb_band->rate == sb_band->rate
-                 && nb_band->burst_size == sb_band->burst_size
-                 && !strcmp(sb_band->action, nb_band->action));
-    }
-
-    /* Place the Northbound entries in sorted order. */
-    struct band_entry *nb_bands;
-    nb_bands = xmalloc(sizeof *nb_bands * nb_meter->n_bands);
-    for (size_t i = 0; i < nb_meter->n_bands; i++) {
-        struct nbrec_meter_band *nb_band = nb_meter->bands[i];
-
-        nb_bands[i].rate = nb_band->rate;
-        nb_bands[i].burst_size = nb_band->burst_size;
-        nb_bands[i].action = nb_band->action;
-    }
-    qsort(nb_bands, nb_meter->n_bands, sizeof *nb_bands, band_cmp);
-
-    /* Place the Southbound entries in sorted order. */
-    struct band_entry *sb_bands;
-    sb_bands = xmalloc(sizeof *sb_bands * sb_meter->n_bands);
-    for (size_t i = 0; i < sb_meter->n_bands; i++) {
-        struct sbrec_meter_band *sb_band = sb_meter->bands[i];
-
-        sb_bands[i].rate = sb_band->rate;
-        sb_bands[i].burst_size = sb_band->burst_size;
-        sb_bands[i].action = sb_band->action;
-    }
-    qsort(sb_bands, sb_meter->n_bands, sizeof *sb_bands, band_cmp);
-
-    bool need_update = false;
-    for (size_t i = 0; i < nb_meter->n_bands; i++) {
-        if (nb_bands[i].rate != sb_bands[i].rate
-            || nb_bands[i].burst_size != sb_bands[i].burst_size
-            || strcmp(nb_bands[i].action, sb_bands[i].action)) {
-            need_update = true;
-            goto done;
-        }
-    }
-
-done:
-    free(nb_bands);
-    free(sb_bands);
-
-    return need_update;
-}
-
-/* Each entry in the Meter and Meter_Band tables in OVN_Northbound have
- * a corresponding entries in the Meter and Meter_Band tables in
- * OVN_Southbound.
- */
-static void
-sync_meters(struct northd_context *ctx)
-{
-    struct shash sb_meters = SHASH_INITIALIZER(&sb_meters);
-
-    const struct sbrec_meter *sb_meter;
-    SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) {
-        shash_add(&sb_meters, sb_meter->name, sb_meter);
-    }
-
-    const struct nbrec_meter *nb_meter;
-    NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) {
-        bool new_sb_meter = false;
-
-        sb_meter = shash_find_and_delete(&sb_meters, nb_meter->name);
-        if (!sb_meter) {
-            sb_meter = sbrec_meter_insert(ctx->ovnsb_txn);
-            sbrec_meter_set_name(sb_meter, nb_meter->name);
-            new_sb_meter = true;
-        }
-
-        if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) {
-            struct sbrec_meter_band **sb_bands;
-            sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands);
-            for (size_t i = 0; i < nb_meter->n_bands; i++) {
-                const struct nbrec_meter_band *nb_band = nb_meter->bands[i];
-
-                sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn);
-
-                sbrec_meter_band_set_action(sb_bands[i], nb_band->action);
-                sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate);
-                sbrec_meter_band_set_burst_size(sb_bands[i],
-                                                nb_band->burst_size);
-            }
-            sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands);
-            free(sb_bands);
-        }
-
-        sbrec_meter_set_unit(sb_meter, nb_meter->unit);
-    }
-
-    struct shash_node *node, *next;
-    SHASH_FOR_EACH_SAFE (node, next, &sb_meters) {
-        sbrec_meter_delete(node->data);
-        shash_delete(&sb_meters, node);
-    }
-    shash_destroy(&sb_meters);
-}
-
-/*
- * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
- * and Southbound db.
- */
-struct dns_info {
-    struct hmap_node hmap_node;
-    const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */
-    const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */
-
-    /* Datapaths to which the DNS entry is associated with it. */
-    const struct sbrec_datapath_binding **sbs;
-    size_t n_sbs;
-};
-
-static inline struct dns_info *
-get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid)
-{
-    struct dns_info *dns_info;
-    size_t hash = uuid_hash(uuid);
-    HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) {
-        if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) {
-            return dns_info;
-        }
-    }
-
-    return NULL;
-}
-
-static void
-sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths)
-{
-    struct hmap dns_map = HMAP_INITIALIZER(&dns_map);
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs || !od->nbs->n_dns_records) {
-            continue;
-        }
-
-        for (size_t i = 0; i < od->nbs->n_dns_records; i++) {
-            struct dns_info *dns_info = get_dns_info_from_hmap(
-                &dns_map, &od->nbs->dns_records[i]->header_.uuid);
-            if (!dns_info) {
-                size_t hash = uuid_hash(
-                    &od->nbs->dns_records[i]->header_.uuid);
-                dns_info = xzalloc(sizeof *dns_info);;
-                dns_info->nb_dns = od->nbs->dns_records[i];
-                hmap_insert(&dns_map, &dns_info->hmap_node, hash);
-            }
-
-            dns_info->n_sbs++;
-            dns_info->sbs = xrealloc(dns_info->sbs,
-                                     dns_info->n_sbs * sizeof *dns_info->sbs);
-            dns_info->sbs[dns_info->n_sbs - 1] = od->sb;
-        }
-    }
-
-    const struct sbrec_dns *sbrec_dns, *next;
-    SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) {
-        const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids, "dns_id");
-        struct uuid dns_uuid;
-        if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) {
-            sbrec_dns_delete(sbrec_dns);
-            continue;
-        }
-
-        struct dns_info *dns_info =
-            get_dns_info_from_hmap(&dns_map, &dns_uuid);
-        if (dns_info) {
-            dns_info->sb_dns = sbrec_dns;
-        } else {
-            sbrec_dns_delete(sbrec_dns);
-        }
-    }
-
-    struct dns_info *dns_info;
-    HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) {
-        if (!dns_info->sb_dns) {
-            sbrec_dns = sbrec_dns_insert(ctx->ovnsb_txn);
-            dns_info->sb_dns = sbrec_dns;
-            char *dns_id = xasprintf(
-                UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid));
-            const struct smap external_ids =
-                SMAP_CONST1(&external_ids, "dns_id", dns_id);
-            sbrec_dns_set_external_ids(sbrec_dns, &external_ids);
-            free(dns_id);
-        }
-
-        /* Set the datapaths and records. If nothing has changed, then
-         * this will be a no-op.
-         */
-        sbrec_dns_set_datapaths(
-            dns_info->sb_dns,
-            (struct sbrec_datapath_binding **)dns_info->sbs,
-            dns_info->n_sbs);
-        sbrec_dns_set_records(dns_info->sb_dns, &dns_info->nb_dns->records);
-        free(dns_info->sbs);
-        free(dns_info);
-    }
-    hmap_destroy(&dns_map);
-}
-
-static void
-destroy_datapaths_and_ports(struct hmap *datapaths, struct hmap *ports,
-                            struct ovs_list *lr_list)
-{
-    struct ovn_datapath *router_dp;
-    LIST_FOR_EACH_POP (router_dp, lr_list, lr_list) {
-        if (router_dp->lr_group) {
-            struct lrouter_group *lr_group = router_dp->lr_group;
-
-            for (size_t i = 0; i < lr_group->n_router_dps; i++) {
-                lr_group->router_dps[i]->lr_group = NULL;
-            }
-
-            free(lr_group->router_dps);
-            sset_destroy(&lr_group->ha_chassis_groups);
-            free(lr_group);
-        }
-    }
-
-    struct ovn_datapath *dp, *next_dp;
-    HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, datapaths) {
-        ovn_datapath_destroy(datapaths, dp);
-    }
-    hmap_destroy(datapaths);
-
-    struct ovn_port *port, *next_port;
-    HMAP_FOR_EACH_SAFE (port, next_port, key_node, ports) {
-        ovn_port_destroy(ports, port);
-    }
-    hmap_destroy(ports);
-}
-
-static void
-build_ip_mcast(struct northd_context *ctx, struct hmap *datapaths)
-{
-    struct ovn_datapath *od;
-
-    HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs) {
-            continue;
-        }
-
-        const struct sbrec_ip_multicast *ip_mcast =
-            ip_mcast_lookup(ctx->sbrec_ip_mcast_by_dp, od->sb);
-
-        if (!ip_mcast) {
-            ip_mcast = sbrec_ip_multicast_insert(ctx->ovnsb_txn);
-        }
-        store_mcast_info_for_datapath(ip_mcast, od);
-    }
-
-    /* Delete southbound records without northbound matches. */
-    const struct sbrec_ip_multicast *sb, *sb_next;
-
-    SBREC_IP_MULTICAST_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) {
-        if (!sb->datapath ||
-                !ovn_datapath_from_sbrec(datapaths, sb->datapath)) {
-            sbrec_ip_multicast_delete(sb);
-        }
-    }
-}
-
-static void
-build_mcast_groups(struct northd_context *ctx,
-                   struct hmap *datapaths, struct hmap *ports,
-                   struct hmap *mcast_groups,
-                   struct hmap *igmp_groups)
-{
-    struct ovn_port *op;
-
-    hmap_init(mcast_groups);
-    hmap_init(igmp_groups);
-
-    HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-            continue;
-        }
-
-        if (lsp_is_enabled(op->nbsp)) {
-            ovn_multicast_add(mcast_groups, &mc_flood, op);
-        }
-    }
-
-    const struct sbrec_igmp_group *sb_igmp, *sb_igmp_next;
-
-    SBREC_IGMP_GROUP_FOR_EACH_SAFE (sb_igmp, sb_igmp_next, ctx->ovnsb_idl) {
-        /* If this is a stale group (e.g., controller had crashed,
-         * purge it).
-         */
-        if (!sb_igmp->chassis || !sb_igmp->datapath) {
-            sbrec_igmp_group_delete(sb_igmp);
-            continue;
-        }
-
-        /* If the datapath value is stale, purge the group. */
-        struct ovn_datapath *od =
-            ovn_datapath_from_sbrec(datapaths, sb_igmp->datapath);
-        if (!od) {
-            sbrec_igmp_group_delete(sb_igmp);
-            continue;
-        }
-
-        /* Add the IGMP group entry. Will also try to allocate an ID for it
-         * if the multicast group already exists.
-         */
-        ovn_igmp_group_add(ctx, igmp_groups, od, sb_igmp);
-    }
-
-    /* Walk the aggregated IGMP groups and allocate IDs for new entries.
-     * Then store the ports in the associated multicast group.
-     */
-    struct ovn_igmp_group *igmp_group, *igmp_group_next;
-    HMAP_FOR_EACH_SAFE (igmp_group, igmp_group_next, hmap_node, igmp_groups) {
-        if (igmp_group->mcgroup.key == 0) {
-            struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info;
-            igmp_group->mcgroup.key = ovn_mcast_group_allocate_key(mcast_info);
-        }
-
-        /* If we ran out of keys just destroy the entry. */
-        if (igmp_group->mcgroup.key == 0) {
-            ovn_igmp_group_destroy(igmp_groups, igmp_group);
-            continue;
-        }
-
-        /* Aggregate the ports from all SB entries corresponding to this
-         * group.
-         */
-        ovn_igmp_group_aggregate_ports(igmp_group, ports, mcast_groups);
-    }
-}
-
-static void
-ovnnb_db_run(struct northd_context *ctx,
-             struct ovsdb_idl_index *sbrec_chassis_by_name,
-             struct ovsdb_idl_loop *sb_loop,
-             struct hmap *datapaths, struct hmap *ports,
-             struct ovs_list *lr_list)
-{
-    if (!ctx->ovnsb_txn || !ctx->ovnnb_txn) {
-        return;
-    }
-    struct hmap port_groups;
-    struct hmap mcast_groups;
-    struct hmap igmp_groups;
-
-    build_datapaths(ctx, datapaths, lr_list);
-    build_ports(ctx, sbrec_chassis_by_name, datapaths, ports);
-    build_ipam(datapaths, ports);
-    build_port_group_lswitches(ctx, &port_groups, ports);
-    build_lrouter_groups(ports, lr_list);
-    build_ip_mcast(ctx, datapaths);
-    build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
-    build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
-                 &igmp_groups);
-
-    sync_address_sets(ctx);
-    sync_port_groups(ctx);
-    sync_meters(ctx);
-    sync_dns_entries(ctx, datapaths);
-
-    struct ovn_igmp_group *igmp_group, *next_igmp_group;
-
-    HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, &igmp_groups) {
-        ovn_igmp_group_destroy(&igmp_groups, igmp_group);
-    }
-
-    struct ovn_port_group *pg, *next_pg;
-    HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) {
-        ovn_port_group_destroy(&port_groups, pg);
-    }
-    hmap_destroy(&igmp_groups);
-    hmap_destroy(&mcast_groups);
-    hmap_destroy(&port_groups);
-
-    /* Sync ipsec configuration.
-     * Copy nb_cfg from northbound to southbound database.
-     * Also set up to update sb_cfg once our southbound transaction commits. */
-    const struct nbrec_nb_global *nb = nbrec_nb_global_first(ctx->ovnnb_idl);
-    if (!nb) {
-        nb = nbrec_nb_global_insert(ctx->ovnnb_txn);
-    }
-    const struct sbrec_sb_global *sb = sbrec_sb_global_first(ctx->ovnsb_idl);
-    if (!sb) {
-        sb = sbrec_sb_global_insert(ctx->ovnsb_txn);
-    }
-    if (nb->ipsec != sb->ipsec) {
-        sbrec_sb_global_set_ipsec(sb, nb->ipsec);
-    }
-    sbrec_sb_global_set_nb_cfg(sb, nb->nb_cfg);
-    sbrec_sb_global_set_options(sb, &nb->options);
-    sb_loop->next_cfg = nb->nb_cfg;
-
-    const char *mac_addr_prefix = smap_get(&nb->options, "mac_prefix");
-    if (mac_addr_prefix) {
-        struct eth_addr addr;
-
-        memset(&addr, 0, sizeof addr);
-        if (ovs_scan(mac_addr_prefix, "%"SCNx8":%"SCNx8":%"SCNx8,
-                     &addr.ea[0], &addr.ea[1], &addr.ea[2])) {
-            mac_prefix = addr;
-        }
-    } else {
-        struct smap options;
-
-        smap_clone(&options, &nb->options);
-        eth_addr_random(&mac_prefix);
-        memset(&mac_prefix.ea[3], 0, 3);
-
-        smap_add_format(&options, "mac_prefix",
-                        "%02"PRIx8":%02"PRIx8":%02"PRIx8,
-                        mac_prefix.ea[0], mac_prefix.ea[1], mac_prefix.ea[2]);
-        nbrec_nb_global_verify_options(nb);
-        nbrec_nb_global_set_options(nb, &options);
-
-        smap_destroy(&options);
-    }
-
-    controller_event_en = smap_get_bool(&nb->options,
-                                        "controller_event", false);
-
-    cleanup_macam(&macam);
-}
-
-/* Stores the list of chassis which references an ha_chassis_group.
- */
-struct ha_ref_chassis_info {
-    const struct sbrec_ha_chassis_group *ha_chassis_group;
-    struct sbrec_chassis **ref_chassis;
-    size_t n_ref_chassis;
-    size_t free_slots;
-};
-
-static void
-add_to_ha_ref_chassis_info(struct ha_ref_chassis_info *ref_ch_info,
-                           const struct sbrec_chassis *chassis)
-{
-    for (size_t j = 0; j < ref_ch_info->n_ref_chassis; j++) {
-        if (ref_ch_info->ref_chassis[j] == chassis) {
-           return;
-        }
-    }
-
-    /* Allocate space for 3 chassis at a time. */
-    if (!ref_ch_info->free_slots) {
-        ref_ch_info->ref_chassis =
-            xrealloc(ref_ch_info->ref_chassis,
-                     sizeof *ref_ch_info->ref_chassis *
-                     (ref_ch_info->n_ref_chassis + 3));
-        ref_ch_info->free_slots = 3;
-    }
-
-    ref_ch_info->ref_chassis[ref_ch_info->n_ref_chassis] =
-        CONST_CAST(struct sbrec_chassis *, chassis);
-    ref_ch_info->n_ref_chassis++;
-    ref_ch_info->free_slots--;
-}
-
-static void
-update_sb_ha_group_ref_chassis(struct shash *ha_ref_chassis_map)
-{
-    struct shash_node *node, *next;
-    SHASH_FOR_EACH_SAFE (node, next, ha_ref_chassis_map) {
-        struct ha_ref_chassis_info *ha_ref_info = node->data;
-        sbrec_ha_chassis_group_set_ref_chassis(ha_ref_info->ha_chassis_group,
-                                               ha_ref_info->ref_chassis,
-                                               ha_ref_info->n_ref_chassis);
-        free(ha_ref_info->ref_chassis);
-        free(ha_ref_info);
-        shash_delete(ha_ref_chassis_map, node);
-    }
-}
-
-/* This function checks if the port binding 'sb' references
- * a HA chassis group.
- * Eg. Suppose a distributed logical router port - lr0-public
- * uses an HA chassis group - hagrp1 and if hagrp1 has 3 ha
- * chassis - gw1, gw2 and gw3.
- * Or
- * If the distributed logical router port - lr0-public has
- * 3 gateway chassis - gw1, gw2 and gw3.
- * ovn-northd creates ha chassis group - hagrp1 in SB DB
- * and adds gw1, gw2 and gw3 to its ha_chassis list.
- *
- * If port binding 'sb' represents a logical switch port 'p1'
- * and its logical switch is connected to the logical router
- * 'lr0' directly or indirectly (i.e p1's logical switch is
- *  connected to a router 'lr1' and 'lr1' has a path to lr0 via
- *  transit logical switches) and 'sb' is claimed by chassis - 'c1' then
- * this function adds c1 to the list of the reference chassis
- *  - 'ref_chassis' of hagrp1.
- */
-static void
-build_ha_chassis_group_ref_chassis(struct northd_context *ctx,
-                                   const struct sbrec_port_binding *sb,
-                                   struct ovn_port *op,
-                                   struct shash *ha_ref_chassis_map)
-{
-    struct lrouter_group *lr_group = NULL;
-    for (size_t i = 0; i < op->od->n_router_ports; i++) {
-        if (!op->od->router_ports[i]->peer) {
-            continue;
-        }
-
-        lr_group = op->od->router_ports[i]->peer->od->lr_group;
-        /* If a logical switch has multiple router ports, then
-         * all the logical routers belong to the same logical
-         * router group. */
-        break;
-    }
-
-    if (!lr_group) {
-        return;
-    }
-
-    const char *ha_group_name;
-    SSET_FOR_EACH (ha_group_name, &lr_group->ha_chassis_groups) {
-        const struct sbrec_ha_chassis_group *sb_ha_chassis_grp;
-        sb_ha_chassis_grp = ha_chassis_group_lookup_by_name(
-            ctx->sbrec_ha_chassis_grp_by_name, ha_group_name);
-
-        if (sb_ha_chassis_grp) {
-            struct ha_ref_chassis_info *ref_ch_info =
-            shash_find_data(ha_ref_chassis_map, sb_ha_chassis_grp->name);
-            ovs_assert(ref_ch_info);
-            add_to_ha_ref_chassis_info(ref_ch_info, sb->chassis);
-        }
-    }
-}
-
-/* Handle changes to the 'chassis' column of the 'Port_Binding' table.  When
- * this column is not empty, it means we need to set the corresponding logical
- * port as 'up' in the northbound DB. */
-static void
-handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports,
-                            struct shash *ha_ref_chassis_map)
-{
-    const struct sbrec_port_binding *sb;
-    bool build_ha_chassis_ref = false;
-    if (ctx->ovnsb_txn) {
-        const struct sbrec_ha_chassis_group *ha_ch_grp;
-        SBREC_HA_CHASSIS_GROUP_FOR_EACH (ha_ch_grp, ctx->ovnsb_idl) {
-            struct ha_ref_chassis_info *ref_ch_info =
-                xzalloc(sizeof *ref_ch_info);
-            ref_ch_info->ha_chassis_group = ha_ch_grp;
-            build_ha_chassis_ref = true;
-            shash_add(ha_ref_chassis_map, ha_ch_grp->name, ref_ch_info);
-        }
-    }
-
-    SBREC_PORT_BINDING_FOR_EACH(sb, ctx->ovnsb_idl) {
-        struct ovn_port *op = ovn_port_find(ports, sb->logical_port);
-
-        if (!op || !op->nbsp) {
-            /* The logical port doesn't exist for this port binding.  This can
-             * happen under normal circumstances when ovn-northd hasn't gotten
-             * around to pruning the Port_Binding yet. */
-            continue;
-        }
-
-        bool up = (sb->chassis || !strcmp(op->nbsp->type, "router"));
-        if (!op->nbsp->up || *op->nbsp->up != up) {
-            nbrec_logical_switch_port_set_up(op->nbsp, &up, 1);
-        }
-
-        if (build_ha_chassis_ref && ctx->ovnsb_txn && sb->chassis) {
-            /* Check and add the chassis which has claimed this 'sb'
-             * to the ha chassis group's ref_chassis if required. */
-            build_ha_chassis_group_ref_chassis(ctx, sb, op,
-                                               ha_ref_chassis_map);
-        }
-    }
-}
-
-static struct gen_opts_map supported_dhcp_opts[] = {
-    OFFERIP,
-    DHCP_OPT_NETMASK,
-    DHCP_OPT_ROUTER,
-    DHCP_OPT_DNS_SERVER,
-    DHCP_OPT_LOG_SERVER,
-    DHCP_OPT_LPR_SERVER,
-    DHCP_OPT_SWAP_SERVER,
-    DHCP_OPT_POLICY_FILTER,
-    DHCP_OPT_ROUTER_SOLICITATION,
-    DHCP_OPT_NIS_SERVER,
-    DHCP_OPT_NTP_SERVER,
-    DHCP_OPT_SERVER_ID,
-    DHCP_OPT_TFTP_SERVER,
-    DHCP_OPT_CLASSLESS_STATIC_ROUTE,
-    DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE,
-    DHCP_OPT_IP_FORWARD_ENABLE,
-    DHCP_OPT_ROUTER_DISCOVERY,
-    DHCP_OPT_ETHERNET_ENCAP,
-    DHCP_OPT_DEFAULT_TTL,
-    DHCP_OPT_TCP_TTL,
-    DHCP_OPT_MTU,
-    DHCP_OPT_LEASE_TIME,
-    DHCP_OPT_T1,
-    DHCP_OPT_T2,
-    DHCP_OPT_WPAD,
-    DHCP_OPT_BOOTFILE,
-    DHCP_OPT_PATH_PREFIX,
-    DHCP_OPT_TFTP_SERVER_ADDRESS,
-    DHCP_OPT_DOMAIN_NAME,
-};
-
-static struct gen_opts_map supported_dhcpv6_opts[] = {
-    DHCPV6_OPT_IA_ADDR,
-    DHCPV6_OPT_SERVER_ID,
-    DHCPV6_OPT_DOMAIN_SEARCH,
-    DHCPV6_OPT_DNS_SERVER
-};
-
-static void
-check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
-{
-    struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add);
-    for (size_t i = 0; (i < sizeof(supported_dhcp_opts) /
-                            sizeof(supported_dhcp_opts[0])); i++) {
-        hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node,
-                    dhcp_opt_hash(supported_dhcp_opts[i].name));
-    }
-
-    const struct sbrec_dhcp_options *opt_row, *opt_row_next;
-    SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
-        struct gen_opts_map *dhcp_opt =
-            dhcp_opts_find(&dhcp_opts_to_add, opt_row->name);
-        if (dhcp_opt) {
-            hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
-        } else {
-            sbrec_dhcp_options_delete(opt_row);
-        }
-    }
-
-    struct gen_opts_map *opt;
-    HMAP_FOR_EACH (opt, hmap_node, &dhcp_opts_to_add) {
-        struct sbrec_dhcp_options *sbrec_dhcp_option =
-            sbrec_dhcp_options_insert(ctx->ovnsb_txn);
-        sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name);
-        sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code);
-        sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type);
-    }
-
-    hmap_destroy(&dhcp_opts_to_add);
-}
-
-static void
-check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx)
-{
-    struct hmap dhcpv6_opts_to_add = HMAP_INITIALIZER(&dhcpv6_opts_to_add);
-    for (size_t i = 0; (i < sizeof(supported_dhcpv6_opts) /
-                            sizeof(supported_dhcpv6_opts[0])); i++) {
-        hmap_insert(&dhcpv6_opts_to_add, &supported_dhcpv6_opts[i].hmap_node,
-                    dhcp_opt_hash(supported_dhcpv6_opts[i].name));
-    }
-
-    const struct sbrec_dhcpv6_options *opt_row, *opt_row_next;
-    SBREC_DHCPV6_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
-        struct gen_opts_map *dhcp_opt =
-            dhcp_opts_find(&dhcpv6_opts_to_add, opt_row->name);
-        if (dhcp_opt) {
-            hmap_remove(&dhcpv6_opts_to_add, &dhcp_opt->hmap_node);
-        } else {
-            sbrec_dhcpv6_options_delete(opt_row);
-        }
-    }
-
-    struct gen_opts_map *opt;
-    HMAP_FOR_EACH(opt, hmap_node, &dhcpv6_opts_to_add) {
-        struct sbrec_dhcpv6_options *sbrec_dhcpv6_option =
-            sbrec_dhcpv6_options_insert(ctx->ovnsb_txn);
-        sbrec_dhcpv6_options_set_name(sbrec_dhcpv6_option, opt->name);
-        sbrec_dhcpv6_options_set_code(sbrec_dhcpv6_option, opt->code);
-        sbrec_dhcpv6_options_set_type(sbrec_dhcpv6_option, opt->type);
-    }
-
-    hmap_destroy(&dhcpv6_opts_to_add);
-}
-
-static const char *rbac_chassis_auth[] =
-    {"name"};
-static const char *rbac_chassis_update[] =
-    {"nb_cfg", "external_ids", "encaps", "vtep_logical_switches"};
-
-static const char *rbac_encap_auth[] =
-    {"chassis_name"};
-static const char *rbac_encap_update[] =
-    {"type", "options", "ip"};
-
-static const char *rbac_port_binding_auth[] =
-    {""};
-static const char *rbac_port_binding_update[] =
-    {"chassis"};
-
-static const char *rbac_mac_binding_auth[] =
-    {""};
-static const char *rbac_mac_binding_update[] =
-    {"logical_port", "ip", "mac", "datapath"};
-
-static struct rbac_perm_cfg {
-    const char *table;
-    const char **auth;
-    int n_auth;
-    bool insdel;
-    const char **update;
-    int n_update;
-    const struct sbrec_rbac_permission *row;
-} rbac_perm_cfg[] = {
-    {
-        .table = "Chassis",
-        .auth = rbac_chassis_auth,
-        .n_auth = ARRAY_SIZE(rbac_chassis_auth),
-        .insdel = true,
-        .update = rbac_chassis_update,
-        .n_update = ARRAY_SIZE(rbac_chassis_update),
-        .row = NULL
-    },{
-        .table = "Encap",
-        .auth = rbac_encap_auth,
-        .n_auth = ARRAY_SIZE(rbac_encap_auth),
-        .insdel = true,
-        .update = rbac_encap_update,
-        .n_update = ARRAY_SIZE(rbac_encap_update),
-        .row = NULL
-    },{
-        .table = "Port_Binding",
-        .auth = rbac_port_binding_auth,
-        .n_auth = ARRAY_SIZE(rbac_port_binding_auth),
-        .insdel = false,
-        .update = rbac_port_binding_update,
-        .n_update = ARRAY_SIZE(rbac_port_binding_update),
-        .row = NULL
-    },{
-        .table = "MAC_Binding",
-        .auth = rbac_mac_binding_auth,
-        .n_auth = ARRAY_SIZE(rbac_mac_binding_auth),
-        .insdel = true,
-        .update = rbac_mac_binding_update,
-        .n_update = ARRAY_SIZE(rbac_mac_binding_update),
-        .row = NULL
-    },{
-        .table = NULL,
-        .auth = NULL,
-        .n_auth = 0,
-        .insdel = false,
-        .update = NULL,
-        .n_update = 0,
-        .row = NULL
-    }
-};
-
-static bool
-ovn_rbac_validate_perm(const struct sbrec_rbac_permission *perm)
-{
-    struct rbac_perm_cfg *pcfg;
-    int i, j, n_found;
-
-    for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
-        if (!strcmp(perm->table, pcfg->table)) {
-            break;
-        }
-    }
-    if (!pcfg->table) {
-        return false;
-    }
-    if (perm->n_authorization != pcfg->n_auth ||
-        perm->n_update != pcfg->n_update) {
-        return false;
-    }
-    if (perm->insert_delete != pcfg->insdel) {
-        return false;
-    }
-    /* verify perm->authorization vs. pcfg->auth */
-    n_found = 0;
-    for (i = 0; i < pcfg->n_auth; i++) {
-        for (j = 0; j < perm->n_authorization; j++) {
-            if (!strcmp(pcfg->auth[i], perm->authorization[j])) {
-                n_found++;
-                break;
-            }
-        }
-    }
-    if (n_found != pcfg->n_auth) {
-        return false;
-    }
-
-    /* verify perm->update vs. pcfg->update */
-    n_found = 0;
-    for (i = 0; i < pcfg->n_update; i++) {
-        for (j = 0; j < perm->n_update; j++) {
-            if (!strcmp(pcfg->update[i], perm->update[j])) {
-                n_found++;
-                break;
-            }
-        }
-    }
-    if (n_found != pcfg->n_update) {
-        return false;
-    }
-
-    /* Success, db state matches expected state */
-    pcfg->row = perm;
-    return true;
-}
-
-static void
-ovn_rbac_create_perm(struct rbac_perm_cfg *pcfg,
-                     struct northd_context *ctx,
-                     const struct sbrec_rbac_role *rbac_role)
-{
-    struct sbrec_rbac_permission *rbac_perm;
-
-    rbac_perm = sbrec_rbac_permission_insert(ctx->ovnsb_txn);
-    sbrec_rbac_permission_set_table(rbac_perm, pcfg->table);
-    sbrec_rbac_permission_set_authorization(rbac_perm,
-                                            pcfg->auth,
-                                            pcfg->n_auth);
-    sbrec_rbac_permission_set_insert_delete(rbac_perm, pcfg->insdel);
-    sbrec_rbac_permission_set_update(rbac_perm,
-                                     pcfg->update,
-                                     pcfg->n_update);
-    sbrec_rbac_role_update_permissions_setkey(rbac_role, pcfg->table,
-                                              rbac_perm);
-}
-
-static void
-check_and_update_rbac(struct northd_context *ctx)
-{
-    const struct sbrec_rbac_role *rbac_role = NULL;
-    const struct sbrec_rbac_permission *perm_row, *perm_next;
-    const struct sbrec_rbac_role *role_row, *role_row_next;
-    struct rbac_perm_cfg *pcfg;
-
-    for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
-        pcfg->row = NULL;
-    }
-
-    SBREC_RBAC_PERMISSION_FOR_EACH_SAFE (perm_row, perm_next, ctx->ovnsb_idl) {
-        if (!ovn_rbac_validate_perm(perm_row)) {
-            sbrec_rbac_permission_delete(perm_row);
-        }
-    }
-    SBREC_RBAC_ROLE_FOR_EACH_SAFE (role_row, role_row_next, ctx->ovnsb_idl) {
-        if (strcmp(role_row->name, "ovn-controller")) {
-            sbrec_rbac_role_delete(role_row);
-        } else {
-            rbac_role = role_row;
-        }
-    }
-
-    if (!rbac_role) {
-        rbac_role = sbrec_rbac_role_insert(ctx->ovnsb_txn);
-        sbrec_rbac_role_set_name(rbac_role, "ovn-controller");
-    }
-
-    for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
-        if (!pcfg->row) {
-            ovn_rbac_create_perm(pcfg, ctx, rbac_role);
-        }
-    }
-}
-
-/* Updates the sb_cfg and hv_cfg columns in the northbound NB_Global table. */
-static void
-update_northbound_cfg(struct northd_context *ctx,
-                      struct ovsdb_idl_loop *sb_loop)
-{
-    /* Update northbound sb_cfg if appropriate. */
-    const struct nbrec_nb_global *nbg = nbrec_nb_global_first(ctx->ovnnb_idl);
-    int64_t sb_cfg = sb_loop->cur_cfg;
-    if (nbg && sb_cfg && nbg->sb_cfg != sb_cfg) {
-        nbrec_nb_global_set_sb_cfg(nbg, sb_cfg);
-    }
-
-    /* Update northbound hv_cfg if appropriate. */
-    if (nbg) {
-        /* Find minimum nb_cfg among all chassis. */
-        const struct sbrec_chassis *chassis;
-        int64_t hv_cfg = nbg->nb_cfg;
-        SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) {
-            if (chassis->nb_cfg < hv_cfg) {
-                hv_cfg = chassis->nb_cfg;
-            }
-        }
-
-        /* Update hv_cfg. */
-        if (nbg->hv_cfg != hv_cfg) {
-            nbrec_nb_global_set_hv_cfg(nbg, hv_cfg);
-        }
-    }
-}
-
-/* Handle a fairly small set of changes in the southbound database. */
-static void
-ovnsb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop,
-             struct hmap *ports)
-{
-    if (!ctx->ovnnb_txn || !ovsdb_idl_has_ever_connected(ctx->ovnsb_idl)) {
-        return;
-    }
-
-    struct shash ha_ref_chassis_map = SHASH_INITIALIZER(&ha_ref_chassis_map);
-    handle_port_binding_changes(ctx, ports, &ha_ref_chassis_map);
-    update_northbound_cfg(ctx, sb_loop);
-    if (ctx->ovnsb_txn) {
-        update_sb_ha_group_ref_chassis(&ha_ref_chassis_map);
-    }
-    shash_destroy(&ha_ref_chassis_map);
-}
-
-static void
-ovn_db_run(struct northd_context *ctx,
-           struct ovsdb_idl_index *sbrec_chassis_by_name,
-           struct ovsdb_idl_loop *ovnsb_idl_loop)
-{
-    struct hmap datapaths, ports;
-    struct ovs_list lr_list;
-    ovs_list_init(&lr_list);
-    hmap_init(&datapaths);
-    hmap_init(&ports);
-    ovnnb_db_run(ctx, sbrec_chassis_by_name, ovnsb_idl_loop,
-                 &datapaths, &ports, &lr_list);
-    ovnsb_db_run(ctx, ovnsb_idl_loop, &ports);
-    destroy_datapaths_and_ports(&datapaths, &ports, &lr_list);
-}
-
-static void
-parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
-{
-    enum {
-        DAEMON_OPTION_ENUMS,
-        VLOG_OPTION_ENUMS,
-        SSL_OPTION_ENUMS,
-    };
-    static const struct option long_options[] = {
-        {"ovnsb-db", required_argument, NULL, 'd'},
-        {"ovnnb-db", required_argument, NULL, 'D'},
-        {"unixctl", required_argument, NULL, 'u'},
-        {"help", no_argument, NULL, 'h'},
-        {"options", no_argument, NULL, 'o'},
-        {"version", no_argument, NULL, 'V'},
-        DAEMON_LONG_OPTIONS,
-        VLOG_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {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) {
-        DAEMON_OPTION_HANDLERS;
-        VLOG_OPTION_HANDLERS;
-        STREAM_SSL_OPTION_HANDLERS;
-
-        case 'd':
-            ovnsb_db = optarg;
-            break;
-
-        case 'D':
-            ovnnb_db = optarg;
-            break;
-
-        case 'u':
-            unixctl_path = optarg;
-            break;
-
-        case 'h':
-            usage();
-            exit(EXIT_SUCCESS);
-
-        case 'o':
-            ovs_cmdl_print_options(long_options);
-            exit(EXIT_SUCCESS);
-
-        case 'V':
-            ovs_print_version(0, 0);
-            exit(EXIT_SUCCESS);
-
-        default:
-            break;
-        }
-    }
-
-    if (!ovnsb_db) {
-        ovnsb_db = default_sb_db();
-    }
-
-    if (!ovnnb_db) {
-        ovnnb_db = default_nb_db();
-    }
-
-    free(short_options);
-}
-
-static void
-add_column_noalert(struct ovsdb_idl *idl,
-                   const struct ovsdb_idl_column *column)
-{
-    ovsdb_idl_add_column(idl, column);
-    ovsdb_idl_omit_alert(idl, column);
-}
-
-int
-main(int argc, char *argv[])
-{
-    int res = EXIT_SUCCESS;
-    struct unixctl_server *unixctl;
-    int retval;
-    bool exiting;
-
-    fatal_ignore_sigpipe();
-    ovs_cmdl_proctitle_init(argc, argv);
-    set_program_name(argv[0]);
-    service_start(&argc, &argv);
-    parse_options(argc, argv);
-
-    daemonize_start(false);
-
-    retval = unixctl_server_create(unixctl_path, &unixctl);
-    if (retval) {
-        exit(EXIT_FAILURE);
-    }
-    unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting);
-
-    daemonize_complete();
-
-    /* We want to detect (almost) all changes to the ovn-nb db. */
-    struct ovsdb_idl_loop ovnnb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create(ovnnb_db, &nbrec_idl_class, true, true));
-    ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_sb_cfg);
-    ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_hv_cfg);
-
-    /* We want to detect only selected changes to the ovn-sb db. */
-    struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-        ovsdb_idl_create(ovnsb_db, &sbrec_idl_class, false, true));
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_sb_global);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_nb_cfg);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_options);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_ipsec);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_logical_flow);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_logical_flow_col_logical_datapath);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_pipeline);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_table_id);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_priority);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_match);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_actions);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_multicast_group);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_multicast_group_col_datapath);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_multicast_group_col_tunnel_key);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_multicast_group_col_name);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_multicast_group_col_ports);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_datapath_binding);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_datapath_binding_col_tunnel_key);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_datapath_binding_col_external_ids);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_binding);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_datapath);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_port_binding_col_logical_port);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_port_binding_col_tunnel_key);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_port_binding_col_parent_port);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_tag);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_type);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_port_binding_col_nat_addresses);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_port_binding_col_gateway_chassis);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_port_binding_col_ha_chassis_group);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_gateway_chassis_col_chassis);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_gateway_chassis_col_priority);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_gateway_chassis_col_external_ids);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-                         &sbrec_gateway_chassis_col_options);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_port_binding_col_external_ids);
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_mac_binding);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_datapath);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_ip);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_mac);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_mac_binding_col_logical_port);
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name);
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcpv6_options);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_code);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_type);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_name);
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses);
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_group);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_group_col_name);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_group_col_ports);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_role);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_name);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_permissions);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_permission);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_rbac_permission_col_table);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_rbac_permission_col_authorization);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_rbac_permission_col_insert_delete);
-    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_permission_col_update);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_name);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_unit);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_bands);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter_band);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_action);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_rate);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_burst_size);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_name);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ha_chassis);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_col_chassis);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_col_priority);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_col_external_ids);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ha_chassis_group);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_group_col_name);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_group_col_ha_chassis);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_group_col_external_ids);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ha_chassis_group_col_ref_chassis);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_igmp_group);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_address);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_datapath);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_chassis);
-    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_ports);
-
-    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ip_multicast);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_datapath);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_enabled);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_querier);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_eth_src);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_ip4_src);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_table_size);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_idle_timeout);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_query_interval);
-    add_column_noalert(ovnsb_idl_loop.idl,
-                       &sbrec_ip_multicast_col_query_max_resp);
-
-    struct ovsdb_idl_index *sbrec_chassis_by_name
-        = chassis_index_create(ovnsb_idl_loop.idl);
-
-    struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name
-        = ha_chassis_group_index_create(ovnsb_idl_loop.idl);
-
-    struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp
-        = mcast_group_index_create(ovnsb_idl_loop.idl);
-
-    struct ovsdb_idl_index *sbrec_ip_mcast_by_dp
-        = ip_mcast_index_create(ovnsb_idl_loop.idl);
-
-    /* Ensure that only a single ovn-northd is active in the deployment by
-     * acquiring a lock called "ovn_northd" on the southbound database
-     * and then only performing DB transactions if the lock is held. */
-    ovsdb_idl_set_lock(ovnsb_idl_loop.idl, "ovn_northd");
-    bool had_lock = false;
-
-    /* Main loop. */
-    exiting = false;
-    while (!exiting) {
-        struct northd_context ctx = {
-            .ovnnb_idl = ovnnb_idl_loop.idl,
-            .ovnnb_txn = ovsdb_idl_loop_run(&ovnnb_idl_loop),
-            .ovnsb_idl = ovnsb_idl_loop.idl,
-            .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
-            .sbrec_ha_chassis_grp_by_name = sbrec_ha_chassis_grp_by_name,
-            .sbrec_mcast_group_by_name_dp = sbrec_mcast_group_by_name_dp,
-            .sbrec_ip_mcast_by_dp = sbrec_ip_mcast_by_dp,
-        };
-
-        if (!had_lock && ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) {
-            VLOG_INFO("ovn-northd lock acquired. "
-                      "This ovn-northd instance is now active.");
-            had_lock = true;
-        } else if (had_lock && !ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) {
-            VLOG_INFO("ovn-northd lock lost. "
-                      "This ovn-northd instance is now on standby.");
-            had_lock = false;
-        }
-
-        if (ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) {
-            ovn_db_run(&ctx, sbrec_chassis_by_name, &ovnsb_idl_loop);
-            if (ctx.ovnsb_txn) {
-                check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
-                check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx);
-                check_and_update_rbac(&ctx);
-            }
-        }
-
-        unixctl_server_run(unixctl);
-        unixctl_server_wait(unixctl);
-        if (exiting) {
-            poll_immediate_wake();
-        }
-        ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop);
-        ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-
-        poll_block();
-        if (should_service_stop()) {
-            exiting = true;
-        }
-    }
-
-    unixctl_server_destroy(unixctl);
-    ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
-    ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
-    service_stop();
-
-    exit(res);
-}
-
-static void
-ovn_northd_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/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
deleted file mode 100644
index c4099f25a..000000000
--- a/ovn/ovn-architecture.7.xml
+++ /dev/null
@@ -1,2074 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-architecture" section="7" title="OVN Architecture">
-  <h1>Name</h1>
-  <p>ovn-architecture -- Open Virtual Network architecture</p>
-
-  <h1>Description</h1>
-
-  <p>
-    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.  Services such as DHCP are also desirable
-    features.  Just like OVS, OVN's design goal is to have a production-quality
-    implementation that can operate at significant scale.
-  </p>
-
-  <p>
-    An OVN deployment consists of several components:
-  </p>
-
-  <ul>
-    <li>
-      <p>
-        A <dfn>Cloud Management System</dfn> (<dfn>CMS</dfn>), which is
-        OVN's ultimate client (via its users and administrators).  OVN
-        integration requires installing a CMS-specific plugin and
-        related software (see below).  OVN initially targets OpenStack
-        as CMS.
-      </p>
-
-      <p>
-        We generally speak of ``the'' CMS, but one can imagine scenarios in
-        which multiple CMSes manage different parts of an OVN deployment.
-      </p>
-    </li>
-
-    <li>
-      An OVN Database physical or virtual node (or, eventually, cluster)
-      installed in a central location.
-    </li>
-
-    <li>
-      One or more (usually many) <dfn>hypervisors</dfn>.  Hypervisors must run
-      Open vSwitch and implement the interface described in
-      <code>IntegrationGuide.rst</code> in the OVS source tree.  Any hypervisor
-      platform supported by Open vSwitch is acceptable.
-    </li>
-
-    <li>
-      <p>
-        Zero or more <dfn>gateways</dfn>.  A gateway extends a tunnel-based
-        logical network into a physical network by bidirectionally forwarding
-        packets between tunnels and a physical Ethernet port.  This allows
-        non-virtualized machines to participate in logical networks.  A gateway
-        may be a physical host, a virtual machine, or an ASIC-based hardware
-        switch that supports the <code>vtep</code>(5) schema.
-      </p>
-
-      <p>
-        Hypervisors and gateways are together called <dfn>transport node</dfn>
-        or <dfn>chassis</dfn>.
-      </p>
-    </li>
-  </ul>
-
-  <p>
-    The diagram below shows how the major components of OVN and related
-    software interact.  Starting at the top of the diagram, we have:
-  </p>
-
-  <ul>
-    <li>
-      The Cloud Management System, as defined above.
-    </li>
-
-    <li>
-      <p>
-        The <dfn>OVN/CMS Plugin</dfn> is the component of the CMS that
-        interfaces to OVN.  In OpenStack, this is a Neutron plugin.
-        The plugin's main purpose is to translate the CMS's notion of logical
-        network configuration, stored in the CMS's configuration database in a
-        CMS-specific format, into an intermediate representation understood by
-        OVN.
-      </p>
-
-      <p>
-        This component is necessarily CMS-specific, so a new plugin needs to be
-        developed for each CMS that is integrated with OVN.  All of the
-        components below this one in the diagram are CMS-independent.
-      </p>
-    </li>
-
-    <li>
-      <p>
-        The <dfn>OVN Northbound Database</dfn> receives the intermediate
-        representation of logical network configuration passed down by the
-        OVN/CMS Plugin.  The database schema is meant to be ``impedance
-        matched'' with the concepts used in a CMS, so that it directly supports
-        notions of logical switches, routers, ACLs, and so on.  See
-        <code>ovn-nb</code>(5) for details.
-      </p>
-
-      <p>
-        The OVN Northbound Database has only two clients: the OVN/CMS Plugin
-        above it and <code>ovn-northd</code> below it.
-      </p>
-    </li>
-
-    <li>
-      <code>ovn-northd</code>(8) connects to the OVN Northbound Database
-      above it and the OVN Southbound Database below it.  It translates the
-      logical network configuration in terms of conventional network
-      concepts, taken from the OVN Northbound Database, into logical
-      datapath flows in the OVN Southbound Database below it.
-    </li>
-
-    <li>
-      <p>
-    The <dfn>OVN Southbound Database</dfn> is the center of the system.
-    Its clients are <code>ovn-northd</code>(8) above it and
-    <code>ovn-controller</code>(8) on every transport node below it.
-      </p>
-
-      <p>
-        The OVN Southbound Database contains three kinds of data: <dfn>Physical
-        Network</dfn> (PN) tables that specify how to reach hypervisor and
-        other nodes, <dfn>Logical Network</dfn> (LN) tables that describe the
-        logical network in terms of ``logical datapath flows,'' and
-        <dfn>Binding</dfn> tables that link logical network components'
-        locations to the physical network.  The hypervisors populate the PN and
-        Port_Binding tables, whereas <code>ovn-northd</code>(8) populates the
-        LN tables.
-      </p>
-
-      <p>
-    OVN Southbound Database performance must scale with the number of
-    transport nodes.  This will likely require some work on
-    <code>ovsdb-server</code>(1) as we encounter bottlenecks.
-    Clustering for availability may be needed.
-      </p>
-    </li>
-  </ul>
-
-  <p>
-    The remaining components are replicated onto each hypervisor:
-  </p>
-
-  <ul>
-    <li>
-      <code>ovn-controller</code>(8) is OVN's agent on each hypervisor and
-      software gateway.  Northbound, it connects to the OVN Southbound
-      Database to learn about OVN configuration and status and to
-      populate the PN table and the <code>Chassis</code> column in
-      <code>Binding</code> table with the hypervisor's status.
-      Southbound, it connects to <code>ovs-vswitchd</code>(8) as an
-      OpenFlow controller, for control over network traffic, and to the
-      local <code>ovsdb-server</code>(1) to allow it to monitor and
-      control Open vSwitch configuration.
-    </li>
-
-    <li>
-      <code>ovs-vswitchd</code>(8) and <code>ovsdb-server</code>(1) are
-      conventional components of Open vSwitch.
-    </li>
-  </ul>
-
-  <pre fixed="yes">
-                                  CMS
-                                   |
-                                   |
-                       +-----------|-----------+
-                       |           |           |
-                       |     OVN/CMS Plugin    |
-                       |           |           |
-                       |           |           |
-                       |   OVN Northbound DB   |
-                       |           |           |
-                       |           |           |
-                       |       ovn-northd      |
-                       |           |           |
-                       +-----------|-----------+
-                                   |
-                                   |
-                         +-------------------+
-                         | OVN Southbound DB |
-                         +-------------------+
-                                   |
-                                   |
-                +------------------+------------------+
-                |                  |                  |
-  HV 1          |                  |    HV n          |
-+---------------|---------------+  .  +---------------|---------------+
-|               |               |  .  |               |               |
-|        ovn-controller         |  .  |        ovn-controller         |
-|         |          |          |  .  |         |          |          |
-|         |          |          |     |         |          |          |
-|  ovs-vswitchd   ovsdb-server  |     |  ovs-vswitchd   ovsdb-server  |
-|                               |     |                               |
-+-------------------------------+     +-------------------------------+
-  </pre>
-
-  <h2>Information Flow in OVN</h2>
-
-  <p>
-    Configuration data in OVN flows from north to south.  The CMS, through its
-    OVN/CMS plugin, passes the logical network configuration to
-    <code>ovn-northd</code> via the northbound database.  In turn,
-    <code>ovn-northd</code> compiles the configuration into a lower-level form
-    and passes it to all of the chassis via the southbound database.
-  </p>
-
-  <p>
-    Status information in OVN flows from south to north.  OVN currently
-    provides only a few forms of status information.  First,
-    <code>ovn-northd</code> populates the <code>up</code> column in the
-    northbound <code>Logical_Switch_Port</code> table: if a logical port's
-    <code>chassis</code> column in the southbound <code>Port_Binding</code>
-    table is nonempty, it sets <code>up</code> to <code>true</code>, otherwise
-    to <code>false</code>.  This allows the CMS to detect when a VM's
-    networking has come up.
-  </p>
-
-  <p>
-    Second, OVN provides feedback to the CMS on the realization of its
-    configuration, that is, whether the configuration provided by the CMS has
-    taken effect.  This feature requires the CMS to participate in a sequence
-    number protocol, which works the following way:
-  </p>
-
-  <ol>
-    <li>
-      When the CMS updates the configuration in the northbound database, as
-      part of the same transaction, it increments the value of the
-      <code>nb_cfg</code> column in the <code>NB_Global</code> table.  (This is
-      only necessary if the CMS wants to know when the configuration has been
-      realized.)
-    </li>
-
-    <li>
-      When <code>ovn-northd</code> updates the southbound database based on a
-      given snapshot of the northbound database, it copies <code>nb_cfg</code>
-      from northbound <code>NB_Global</code> into the southbound database
-      <code>SB_Global</code> table, as part of the same transaction.  (Thus, an
-      observer monitoring both databases can determine when the southbound
-      database is caught up with the northbound.)
-    </li>
-
-    <li>
-      After <code>ovn-northd</code> receives confirmation from the southbound
-      database server that its changes have committed, it updates
-      <code>sb_cfg</code> in the northbound <code>NB_Global</code> table to the
-      <code>nb_cfg</code> version that was pushed down.  (Thus, the CMS or
-      another observer can determine when the southbound database is caught up
-      without a connection to the southbound database.)
-    </li>
-
-    <li>
-      The <code>ovn-controller</code> process on each chassis receives the
-      updated southbound database, with the updated <code>nb_cfg</code>.  This
-      process in turn updates the physical flows installed in the chassis's
-      Open vSwitch instances.  When it receives confirmation from Open vSwitch
-      that the physical flows have been updated, it updates <code>nb_cfg</code>
-      in its own <code>Chassis</code> record in the southbound database.
-    </li>
-
-    <li>
-      <code>ovn-northd</code> monitors the <code>nb_cfg</code> column in all of
-      the <code>Chassis</code> records in the southbound database.  It keeps
-      track of the minimum value among all the records and copies it into the
-      <code>hv_cfg</code> column in the northbound <code>NB_Global</code>
-      table.  (Thus, the CMS or another observer can determine when all of the
-      hypervisors have caught up to the northbound configuration.)
-    </li>
-  </ol>
-
-  <h2>Chassis Setup</h2>
-
-  <p>
-    Each chassis in an OVN deployment must be configured with an Open vSwitch
-    bridge dedicated for OVN's use, called the <dfn>integration bridge</dfn>.
-    System startup scripts may create this bridge prior to starting
-    <code>ovn-controller</code> if desired.  If this bridge does not exist when
-    ovn-controller starts, it will be created automatically with the default
-    configuration suggested below.  The ports on the integration bridge include:
-  </p>
-
-  <ul>
-    <li>
-      On any chassis, tunnel ports that OVN uses to maintain logical network
-      connectivity.  <code>ovn-controller</code> adds, updates, and removes
-      these tunnel ports.
-    </li>
-
-    <li>
-      On a hypervisor, any VIFs that are to be attached to logical networks.
-      The hypervisor itself, or the integration between Open vSwitch and the
-      hypervisor (described in <code>IntegrationGuide.rst</code>) takes care of
-      this.  (This is not part of OVN or new to OVN; this is pre-existing
-      integration work that has already been done on hypervisors that support
-      OVS.)
-    </li>
-
-    <li>
-      On a gateway, the physical port used for logical network connectivity.
-      System startup scripts add this port to the bridge prior to starting
-      <code>ovn-controller</code>.  This can be a patch port to another bridge,
-      instead of a physical port, in more sophisticated setups.
-    </li>
-  </ul>
-
-  <p>
-    Other ports should not be attached to the integration bridge.  In
-    particular, physical ports attached to the underlay network (as opposed to
-    gateway ports, which are physical ports attached to logical networks) must
-    not be attached to the integration bridge.  Underlay physical ports should
-    instead be attached to a separate Open vSwitch bridge (they need not be
-    attached to any bridge at all, in fact).
-  </p>
-
-  <p>
-    The integration bridge should be configured as described below.
-    The effect of each of these settings is documented in
-    <code>ovs-vswitchd.conf.db</code>(5):
-  </p>
-
-  <!-- Keep the following in sync with create_br_int() in
-       ovn/controller/ovn-controller.c. -->
-  <dl>
-    <dt><code>fail-mode=secure</code></dt>
-    <dd>
-      Avoids switching packets between isolated logical networks before
-      <code>ovn-controller</code> starts up.  See <code>Controller Failure
-      Settings</code> in <code>ovs-vsctl</code>(8) for more information.
-    </dd>
-
-    <dt><code>other-config:disable-in-band=true</code></dt>
-    <dd>
-      Suppresses in-band control flows for the integration bridge.  It would be
-      unusual for such flows to show up anyway, because OVN uses a local
-      controller (over a Unix domain socket) instead of a remote controller.
-      It's possible, however, for some other bridge in the same system to have
-      an in-band remote controller, and in that case this suppresses the flows
-      that in-band control would ordinarily set up.  Refer to the documentation
-      for more information.
-    </dd>
-  </dl>
-
-  <p>
-    The customary name for the integration bridge is <code>br-int</code>, but
-    another name may be used.
-  </p>
-
-  <h2>Logical Networks</h2>
-
-  <p>
-    A <dfn>logical network</dfn> implements the same concepts as physical
-    networks, but they are insulated from the physical network with tunnels or
-    other encapsulations.  This allows logical networks to have separate IP and
-    other address spaces that overlap, without conflicting, with those used for
-    physical networks.  Logical network topologies can be arranged without
-    regard for the topologies of the physical networks on which they run.
-  </p>
-
-  <p>
-    Logical network concepts in OVN include:
-  </p>
-
-  <ul>
-    <li>
-      <dfn>Logical switches</dfn>, the logical version of Ethernet switches.
-    </li>
-
-    <li>
-      <dfn>Logical routers</dfn>, the logical version of IP routers.  Logical
-      switches and routers can be connected into sophisticated topologies.
-    </li>
-
-    <li>
-      <dfn>Logical datapaths</dfn> are the logical version of an OpenFlow
-      switch.  Logical switches and routers are both implemented as logical
-      datapaths.
-    </li>
-
-    <li>
-      <p>
-        <dfn>Logical ports</dfn> represent the points of connectivity in and
-        out of logical switches and logical routers.  Some common types of
-        logical ports are:
-      </p>
-
-      <ul>
-        <li>
-          Logical ports representing VIFs.
-        </li>
-
-        <li>
-          <dfn>Localnet ports</dfn> represent the points of connectivity
-          between logical switches and the physical network.  They are
-          implemented as OVS patch ports between the integration bridge
-          and the separate Open vSwitch bridge that underlay physical
-          ports attach to.
-        </li>
-
-        <li>
-          <dfn>Logical patch ports</dfn> represent the points of
-          connectivity between logical switches and logical routers, and
-          in some cases between peer logical routers.  There is a pair of
-          logical patch ports at each such point of connectivity, one on
-          each side.
-        </li>
-        <li>
-          <dfn>Localport ports</dfn> represent the points of local
-          connectivity between logical switches and VIFs. These ports are
-          present in every chassis (not bound to any particular one) and
-          traffic from them will never go through a tunnel. A
-          <code>localport</code> is expected to only generate traffic destined
-          for a local destination, typically in response to a request it
-          received.
-          One use case is how OpenStack Neutron uses a <code>localport</code>
-          port for serving metadata to VM's residing on every hypervisor. A
-          metadata proxy process is attached to this port on every host and all
-          VM's within the same network will reach it at the same IP/MAC address
-          without any traffic being sent over a tunnel. Further details can be
-          seen at https://docs.openstack.org/developer/networking-ovn/design/metadata_api.html.
-        </li>
-      </ul>
-    </li>
-  </ul>
-
-  <h2>Life Cycle of a VIF</h2>
-
-  <p>
-    Tables and their schemas presented in isolation are difficult to
-    understand.  Here's an example.
-  </p>
-
-  <p>
-    A VIF on a hypervisor is a virtual network interface attached either
-    to a VM or a container running directly on that hypervisor (This is
-    different from the interface of a container running inside a VM).
-  </p>
-
-  <p>
-    The steps in this example refer often to details of the OVN and OVN
-    Northbound database schemas.  Please see <code>ovn-sb</code>(5) and
-    <code>ovn-nb</code>(5), respectively, for the full story on these
-    databases.
-  </p>
-
-  <ol>
-    <li>
-      A VIF's life cycle begins when a CMS administrator creates a new VIF
-      using the CMS user interface or API and adds it to a switch (one
-      implemented by OVN as a logical switch).  The CMS updates its own
-      configuration.  This includes associating unique, persistent identifier
-      <var>vif-id</var> and Ethernet address <var>mac</var> with the VIF.
-    </li>
-
-    <li>
-      The CMS plugin updates the OVN Northbound database to include the new
-      VIF, by adding a row to the <code>Logical_Switch_Port</code>
-      table.  In the new row, <code>name</code> is <var>vif-id</var>,
-      <code>mac</code> is <var>mac</var>, <code>switch</code> points to
-      the OVN logical switch's Logical_Switch record, and other columns
-      are initialized appropriately.
-    </li>
-
-    <li>
-      <code>ovn-northd</code> receives the OVN Northbound database update.  In
-      turn, it makes the corresponding updates to the OVN Southbound database,
-      by adding rows to the OVN Southbound database <code>Logical_Flow</code>
-      table to reflect the new port, e.g. add a flow to recognize that packets
-      destined to the new port's MAC address should be delivered to it, and
-      update the flow that delivers broadcast and multicast packets to include
-      the new port.  It also creates a record in the <code>Binding</code> table
-      and populates all its columns except the column that identifies the
-      <code>chassis</code>.
-    </li>
-
-    <li>
-      On every hypervisor, <code>ovn-controller</code> receives the
-      <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made
-      in the previous step.  As long as the VM that owns the VIF is powered
-      off, <code>ovn-controller</code> cannot do much; it cannot, for example,
-      arrange to send packets to or receive packets from the VIF, because the
-      VIF does not actually exist anywhere.
-    </li>
-
-    <li>
-      Eventually, a user powers on the VM that owns the VIF.  On the hypervisor
-      where the VM is powered on, the integration between the hypervisor and
-      Open vSwitch (described in <code>IntegrationGuide.rst</code>) adds the VIF
-      to the OVN integration bridge and stores <var>vif-id</var> in
-      <code>external_ids</code>:<code>iface-id</code> to indicate that the
-      interface is an instantiation of the new VIF.  (None of this code is new
-      in OVN; this is pre-existing integration work that has already been done
-      on hypervisors that support OVS.)
-    </li>
-
-    <li>
-      On the hypervisor where the VM is powered on, <code>ovn-controller</code>
-      notices <code>external_ids</code>:<code>iface-id</code> in the new
-      Interface. In response, in the OVN Southbound DB, it updates the
-      <code>Binding</code> table's <code>chassis</code> column for the
-      row that links the logical port from <code>external_ids</code>:<code>
-      iface-id</code> to the hypervisor. Afterward, <code>ovn-controller</code>
-      updates the local hypervisor's OpenFlow tables so that packets to and from
-      the VIF are properly handled.
-    </li>
-
-    <li>
-      Some CMS systems, including OpenStack, fully start a VM only when its
-      networking is ready.  To support this, <code>ovn-northd</code> notices
-      the <code>chassis</code> column updated for the row in
-      <code>Binding</code> table and pushes this upward by updating the
-      <ref column="up" table="Logical_Switch_Port" db="OVN_NB"/> column
-      in the OVN Northbound database's <ref table="Logical_Switch_Port"
-      db="OVN_NB"/> table to indicate that the VIF is now up.  The CMS,
-      if it uses this feature, can then react by allowing the VM's
-      execution to proceed.
-    </li>
-
-    <li>
-      On every hypervisor but the one where the VIF resides,
-      <code>ovn-controller</code> notices the completely populated row in the
-      <code>Binding</code> table.  This provides <code>ovn-controller</code>
-      the physical location of the logical port, so each instance updates the
-      OpenFlow tables of its switch (based on logical datapath flows in the OVN
-      DB <code>Logical_Flow</code> table) so that packets to and from the VIF
-      can be properly handled via tunnels.
-    </li>
-
-    <li>
-      Eventually, a user powers off the VM that owns the VIF.  On the
-      hypervisor where the VM was powered off, the VIF is deleted from the OVN
-      integration bridge.
-    </li>
-
-    <li>
-      On the hypervisor where the VM was powered off,
-      <code>ovn-controller</code> notices that the VIF was deleted.  In
-      response, it removes the <code>Chassis</code> column content in the
-      <code>Binding</code> table for the logical port.
-    </li>
-
-    <li>
-      On every hypervisor, <code>ovn-controller</code> notices the empty
-      <code>Chassis</code> column in the <code>Binding</code> table's row
-      for the logical port.  This means that <code>ovn-controller</code> no
-      longer knows the physical location of the logical port, so each instance
-      updates its OpenFlow table to reflect that.
-    </li>
-
-    <li>
-      Eventually, when the VIF (or its entire VM) is no longer needed by
-      anyone, an administrator deletes the VIF using the CMS user interface or
-      API.  The CMS updates its own configuration.
-    </li>
-
-    <li>
-      The CMS plugin removes the VIF from the OVN Northbound database,
-      by deleting its row in the <code>Logical_Switch_Port</code> table.
-    </li>
-
-    <li>
-      <code>ovn-northd</code> receives the OVN Northbound update and in turn
-      updates the OVN Southbound database accordingly, by removing or updating
-      the rows from the OVN Southbound database <code>Logical_Flow</code> table
-      and <code>Binding</code> table that were related to the now-destroyed
-      VIF.
-    </li>
-
-    <li>
-      On every hypervisor, <code>ovn-controller</code> receives the
-      <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made
-      in the previous step.  <code>ovn-controller</code> updates OpenFlow
-      tables to reflect the update, although there may not be much to do, since
-      the VIF had already become unreachable when it was removed from the
-      <code>Binding</code> table in a previous step.
-    </li>
-  </ol>
-
-  <h2>Life Cycle of a Container Interface Inside a VM</h2>
-
-  <p>
-    OVN provides virtual network abstractions by converting information
-    written in OVN_NB database to OpenFlow flows in each hypervisor.  Secure
-    virtual networking for multi-tenants can only be provided if OVN controller
-    is the only entity that can modify flows in Open vSwitch.  When the
-    Open vSwitch integration bridge resides in the hypervisor, it is a
-    fair assumption to make that tenant workloads running inside VMs cannot
-    make any changes to Open vSwitch flows.
-  </p>
-
-  <p>
-    If the infrastructure provider trusts the applications inside the
-    containers not to break out and modify the Open vSwitch flows, then
-    containers can be run in hypervisors.  This is also the case when
-    containers are run inside the VMs and Open vSwitch integration bridge
-    with flows added by OVN controller resides in the same VM.  For both
-    the above cases, the workflow is the same as explained with an example
-    in the previous section ("Life Cycle of a VIF").
-  </p>
-
-  <p>
-    This section talks about the life cycle of a container interface (CIF)
-    when containers are created in the VMs and the Open vSwitch integration
-    bridge resides inside the hypervisor.  In this case, even if a container
-    application breaks out, other tenants are not affected because the
-    containers running inside the VMs cannot modify the flows in the
-    Open vSwitch integration bridge.
-  </p>
-
-  <p>
-    When multiple containers are created inside a VM, there are multiple
-    CIFs associated with them.  The network traffic associated with these
-    CIFs need to reach the Open vSwitch integration bridge running in the
-    hypervisor for OVN to support virtual network abstractions.  OVN should
-    also be able to distinguish network traffic coming from different CIFs.
-    There are two ways to distinguish network traffic of CIFs.
-  </p>
-
-  <p>
-    One way is to provide one VIF for every CIF (1:1 model).  This means that
-    there could be a lot of network devices in the hypervisor.  This would slow
-    down OVS because of all the additional CPU cycles needed for the management
-    of all the VIFs.  It would also mean that the entity creating the
-    containers in a VM should also be able to create the corresponding VIFs in
-    the hypervisor.
-  </p>
-
-  <p>
-    The second way is to provide a single VIF for all the CIFs (1:many model).
-    OVN could then distinguish network traffic coming from different CIFs via
-    a tag written in every packet.  OVN uses this mechanism and uses VLAN as
-    the tagging mechanism.
-  </p>
-
-  <ol>
-    <li>
-      A CIF's life cycle begins when a container is spawned inside a VM by
-      the either the same CMS that created the VM or a tenant that owns that VM
-      or even a container Orchestration System that is different than the CMS
-      that initially created the VM.  Whoever the entity is, it will need to
-      know the <var>vif-id</var> that is associated with the network interface
-      of the VM through which the container interface's network traffic is
-      expected to go through.  The entity that creates the container interface
-      will also need to choose an unused VLAN inside that VM.
-    </li>
-
-    <li>
-      The container spawning entity (either directly or through the CMS that
-      manages the underlying infrastructure) updates the OVN Northbound
-      database to include the new CIF, by adding a row to the
-      <code>Logical_Switch_Port</code> table.  In the new row,
-      <code>name</code> is any unique identifier,
-      <code>parent_name</code> is the <var>vif-id</var> of the VM
-      through which the CIF's network traffic is expected to go through
-      and the <code>tag</code> is the VLAN tag that identifies the
-      network traffic of that CIF.
-    </li>
-
-    <li>
-      <code>ovn-northd</code> receives the OVN Northbound database update.  In
-      turn, it makes the corresponding updates to the OVN Southbound database,
-      by adding rows to the OVN Southbound database's <code>Logical_Flow</code>
-      table to reflect the new port and also by creating a new row in the
-      <code>Binding</code> table and populating all its columns except the
-      column that identifies the <code>chassis</code>.
-    </li>
-
-    <li>
-      On every hypervisor, <code>ovn-controller</code> subscribes to the
-      changes in the <code>Binding</code> table.  When a new row is created
-      by <code>ovn-northd</code> that includes a value in
-      <code>parent_port</code> column of <code>Binding</code> table, the
-      <code>ovn-controller</code> in the hypervisor whose OVN integration bridge
-      has that same value in <var>vif-id</var> in
-      <code>external_ids</code>:<code>iface-id</code>
-      updates the local hypervisor's OpenFlow tables so that packets to and
-      from the VIF with the particular VLAN <code>tag</code> are properly
-      handled.  Afterward it updates the <code>chassis</code> column of
-      the <code>Binding</code> to reflect the physical location.
-    </li>
-
-    <li>
-      One can only start the application inside the container after the
-      underlying network is ready.  To support this, <code>ovn-northd</code>
-      notices the updated <code>chassis</code> column in <code>Binding</code>
-      table and updates the <ref column="up" table="Logical_Switch_Port"
-      db="OVN_NB"/> column in the OVN Northbound database's
-      <ref table="Logical_Switch_Port" db="OVN_NB"/> table to indicate that the
-      CIF is now up.  The entity responsible to start the container application
-      queries this value and starts the application.
-    </li>
-
-    <li>
-      Eventually the entity that created and started the container, stops it.
-      The entity, through the CMS (or directly) deletes its row in the
-      <code>Logical_Switch_Port</code> table.
-    </li>
-
-    <li>
-      <code>ovn-northd</code> receives the OVN Northbound update and in turn
-      updates the OVN Southbound database accordingly, by removing or updating
-      the rows from the OVN Southbound database <code>Logical_Flow</code> table
-      that were related to the now-destroyed CIF.  It also deletes the row in
-      the <code>Binding</code> table for that CIF.
-    </li>
-
-    <li>
-      On every hypervisor, <code>ovn-controller</code> receives the
-      <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made
-      in the previous step.  <code>ovn-controller</code> updates OpenFlow
-      tables to reflect the update.
-    </li>
-  </ol>
-
-  <h2>Architectural Physical Life Cycle of a Packet</h2>
-
-  <p>
-    This section describes how a packet travels from one virtual machine or
-    container to another through OVN.  This description focuses on the physical
-    treatment of a packet; for a description of the logical life cycle of a
-    packet, please refer to the <code>Logical_Flow</code> table in
-    <code>ovn-sb</code>(5).
-  </p>
-
-  <p>
-    This section mentions several data and metadata fields, for clarity
-    summarized here:
-  </p>
-
-  <dl>
-    <dt>tunnel key</dt>
-    <dd>
-      When OVN encapsulates a packet in Geneve or another tunnel, it attaches
-      extra data to it to allow the receiving OVN instance to process it
-      correctly.  This takes different forms depending on the particular
-      encapsulation, but in each case we refer to it here as the ``tunnel
-      key.''  See <code>Tunnel Encapsulations</code>, below, for details.
-    </dd>
-
-    <dt>logical datapath field</dt>
-    <dd>
-      A field that denotes the logical datapath through which a packet is being
-      processed.
-      <!-- Keep the following in sync with MFF_LOG_DATAPATH in
-           ovn/lib/logical-fields.h. -->
-      OVN uses the field that OpenFlow 1.1+ simply (and confusingly) calls
-      ``metadata'' to store the logical datapath.  (This field is passed across
-      tunnels as part of the tunnel key.)
-    </dd>
-
-    <dt>logical input port field</dt>
-    <dd>
-      <p>
-        A field that denotes the logical port from which the packet
-        entered the logical datapath.
-        <!-- Keep the following in sync with MFF_LOG_INPORT in
-             ovn/lib/logical-fields.h. -->
-        OVN stores this in Open vSwitch extension register number 14.
-      </p>
-
-      <p>
-        Geneve and STT tunnels pass this field as part of the tunnel key.
-        Although VXLAN tunnels do not explicitly carry a logical input port,
-        OVN only uses VXLAN to communicate with gateways that from OVN's
-        perspective consist of only a single logical port, so that OVN can set
-        the logical input port field to this one on ingress to the OVN logical
-        pipeline.
-      </p>
-    </dd>
-
-    <dt>logical output port field</dt>
-    <dd>
-      <p>
-        A field that denotes the logical port from which the packet will
-        leave the logical datapath.  This is initialized to 0 at the
-        beginning of the logical ingress pipeline.
-        <!-- Keep the following in sync with MFF_LOG_OUTPORT in
-             ovn/lib/logical-fields.h. -->
-        OVN stores this in Open vSwitch extension register number 15.
-      </p>
-
-      <p>
-        Geneve and STT tunnels pass this field as part of the tunnel key.
-        VXLAN tunnels do not transmit the logical output port field.
-        Since VXLAN tunnels do not carry a logical output port field in
-        the tunnel key, when a packet is received from VXLAN tunnel by
-        an OVN hypervisor, the packet is resubmitted to table 8 to
-        determine the output port(s);  when the packet reaches table 32,
-        these packets are resubmitted to table 33 for local delivery by
-        checking a MLF_RCV_FROM_VXLAN flag, which is set when the packet
-        arrives from a VXLAN tunnel.
-      </p>
-    </dd>
-
-    <dt>conntrack zone field for logical ports</dt>
-    <dd>
-      A field that denotes the connection tracking zone for logical ports.
-      The value only has local significance and is not meaningful between
-      chassis.  This is initialized to 0 at the beginning of the logical
-        <!-- Keep the following in sync with MFF_LOG_CT_ZONE in
-             ovn/lib/logical-fields.h. -->
-      ingress pipeline.  OVN stores this in Open vSwitch extension register
-      number 13.
-    </dd>
-
-    <dt>conntrack zone fields for routers</dt>
-    <dd>
-      Fields that denote the connection tracking zones for routers.  These
-      values only have local significance and are not meaningful between
-      chassis.  OVN stores the zone information for DNATting in Open vSwitch
-        <!-- Keep the following in sync with MFF_LOG_DNAT_ZONE and
-        MFF_LOG_SNAT_ZONE in ovn/lib/logical-fields.h. -->
-      extension register number 11 and zone information for SNATing in
-      Open vSwitch extension register number 12.
-    </dd>
-
-    <dt>logical flow flags</dt>
-    <dd>
-      The logical flags are intended to handle keeping context between
-      tables in order to decide which rules in subsequent tables are
-      matched.  These values only have local significance and are not
-      meaningful between chassis.  OVN stores the logical flags in
-        <!-- Keep the following in sync with MFF_LOG_FLAGS in
-             ovn/lib/logical-fields.h. -->
-      Open vSwitch extension register number 10.
-    </dd>
-
-    <dt>VLAN ID</dt>
-    <dd>
-      The VLAN ID is used as an interface between OVN and containers nested
-      inside a VM (see <code>Life Cycle of a container interface inside a
-      VM</code>, above, for more information).
-    </dd>
-  </dl>
-
-  <p>
-    Initially, a VM or container on the ingress hypervisor sends a packet on a
-    port attached to the OVN integration bridge.  Then:
-  </p>
-
-  <ol>
-    <li>
-      <p>
-        OpenFlow table 0 performs physical-to-logical translation.  It matches
-        the packet's ingress port.  Its actions annotate the packet with
-        logical metadata, by setting the logical datapath field to identify the
-        logical datapath that the packet is traversing and the logical input
-        port field to identify the ingress port.  Then it resubmits to table 8
-        to enter the logical ingress pipeline.
-      </p>
-
-      <p>
-        Packets that originate from a container nested within a VM are treated
-        in a slightly different way.  The originating container can be
-        distinguished based on the VIF-specific VLAN ID, so the
-        physical-to-logical translation flows additionally match on VLAN ID and
-        the actions strip the VLAN header.  Following this step, OVN treats
-        packets from containers just like any other packets.
-      </p>
-
-      <p>
-        Table 0 also processes packets that arrive from other chassis.  It
-        distinguishes them from other packets by ingress port, which is a
-        tunnel.  As with packets just entering the OVN pipeline, the actions
-        annotate these packets with logical datapath and logical ingress port
-        metadata.  In addition, the actions set the logical output port field,
-        which is available because in OVN tunneling occurs after the logical
-        output port is known.  These three pieces of information are obtained
-        from the tunnel encapsulation metadata (see <code>Tunnel
-        Encapsulations</code> for encoding details).  Then the actions resubmit
-        to table 33 to enter the logical egress pipeline.
-      </p>
-    </li>
-
-    <li>
-      <p>
-        OpenFlow tables 8 through 31 execute the logical ingress pipeline from
-        the <code>Logical_Flow</code> table in the OVN Southbound database.
-        These tables are expressed entirely in terms of logical concepts like
-        logical ports and logical datapaths.  A big part of
-        <code>ovn-controller</code>'s job is to translate them into equivalent
-        OpenFlow (in particular it translates the table numbers:
-        <code>Logical_Flow</code> tables 0 through 23 become OpenFlow tables 8
-        through 31).
-      </p>
-
-      <p>
-        Each logical flow maps to one or more OpenFlow flows.  An actual packet
-        ordinarily matches only one of these, although in some cases it can
-        match more than one of these flows (which is not a problem because all
-        of them have the same actions).  <code>ovn-controller</code> uses the
-        first 32 bits of the logical flow's UUID as the cookie for its OpenFlow
-        flow or flows.  (This is not necessarily unique, since the first 32
-        bits of a logical flow's UUID is not necessarily unique.)
-      </p>
-
-      <p>
-        Some logical flows can map to the Open vSwitch ``conjunctive match''
-        extension (see <code>ovs-fields</code>(7)).  Flows with a
-        <code>conjunction</code> action use an OpenFlow cookie of 0, because
-        they can correspond to multiple logical flows.  The OpenFlow flow for a
-        conjunctive match includes a match on <code>conj_id</code>.
-      </p>
-
-      <p>
-        Some logical flows may not be represented in the OpenFlow tables on a
-        given hypervisor, if they could not be used on that hypervisor.  For
-        example, if no VIF in a logical switch resides on a given hypervisor,
-        and the logical switch is not otherwise reachable on that hypervisor
-        (e.g. over a series of hops through logical switches and routers
-        starting from a VIF on the hypervisor), then the logical flow may not
-        be represented there.
-      </p>
-
-      <p>
-        Most OVN actions have fairly obvious implementations in OpenFlow (with
-        OVS extensions), e.g. <code>next;</code> is implemented as
-        <code>resubmit</code>, <code><var>field</var> =
-        <var>constant</var>;</code> as <code>set_field</code>.  A few are worth
-        describing in more detail:
-      </p>
-
-      <dl>
-        <dt><code>output:</code></dt>
-        <dd>
-          Implemented by resubmitting the packet to table 32.  If the pipeline
-          executes more than one <code>output</code> action, then each one is
-          separately resubmitted to table 32.  This can be used to send
-          multiple copies of the packet to multiple ports.  (If the packet was
-          not modified between the <code>output</code> actions, and some of the
-          copies are destined to the same hypervisor, then using a logical
-          multicast output port would save bandwidth between hypervisors.)
-        </dd>
-
-        <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
-        <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
-        <dd>
-          <p>
-            Implemented by storing arguments into OpenFlow fields, then
-            resubmitting to table 66, which <code>ovn-controller</code>
-            populates with flows generated from the <code>MAC_Binding</code>
-            table in the OVN Southbound database.  If there is a match in table
-            66, then its actions store the bound MAC in the Ethernet
-            destination address field.
-          </p>
-
-          <p>
-            (The OpenFlow actions save and restore the OpenFlow fields used for
-            the arguments, so that the OVN actions do not have to be aware of
-            this temporary use.)
-          </p>
-        </dd>
-
-        <dt><code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt>
-        <dt><code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt>
-        <dd>
-          <p>
-            Implemented by storing the arguments into OpenFlow fields, then
-            outputting a packet to <code>ovn-controller</code>, which updates
-            the <code>MAC_Binding</code> table.
-          </p>
-
-          <p>
-            (The OpenFlow actions save and restore the OpenFlow fields used for
-            the arguments, so that the OVN actions do not have to be aware of
-            this temporary use.)
-          </p>
-        </dd>
-      </dl>
-    </li>
-
-    <li>
-      <p>
-        OpenFlow tables 32 through 47 implement the <code>output</code> action
-        in the logical ingress pipeline.  Specifically, table 32 handles
-        packets to remote hypervisors, table 33 handles packets to the local
-        hypervisor, and table 34 checks whether packets whose logical ingress
-        and egress port are the same should be discarded.
-      </p>
-
-      <p>
-        Logical patch ports are a special case.  Logical patch ports do not
-        have a physical location and effectively reside on every hypervisor.
-        Thus, flow table 33, for output to ports on the local hypervisor,
-        naturally implements output to unicast logical patch ports too.
-        However, applying the same logic to a logical patch port that is part
-        of a logical multicast group yields packet duplication, because each
-        hypervisor that contains a logical port in the multicast group will
-        also output the packet to the logical patch port.  Thus, multicast
-        groups implement output to logical patch ports in table 32.
-      </p>
-
-      <p>
-        Each flow in table 32 matches on a logical output port for unicast or
-        multicast logical ports that include a logical port on a remote
-        hypervisor.  Each flow's actions implement sending a packet to the port
-        it matches.  For unicast logical output ports on remote hypervisors,
-        the actions set the tunnel key to the correct value, then send the
-        packet on the tunnel port to the correct hypervisor.  (When the remote
-        hypervisor receives the packet, table 0 there will recognize it as a
-        tunneled packet and pass it along to table 33.)  For multicast logical
-        output ports, the actions send one copy of the packet to each remote
-        hypervisor, in the same way as for unicast destinations.  If a
-        multicast group includes a logical port or ports on the local
-        hypervisor, then its actions also resubmit to table 33.  Table 32 also
-        includes:
-      </p>
-
-      <ul>
-        <li>
-          A higher-priority rule to match packets received from VXLAN tunnels,
-          based on flag MLF_RCV_FROM_VXLAN, and resubmit these packets to table
-          33 for local delivery.  Packets received from VXLAN tunnels reach
-          here because of a lack of logical output port field in the tunnel key
-          and thus these packets needed to be submitted to table 8 to
-          determine the output port.
-        </li>
-        <li>
-          A higher-priority rule to match packets received from ports of type
-          <code>localport</code>, based on the logical input port, and resubmit
-          these packets to table 33 for local delivery.  Ports of type
-          <code>localport</code> exist on every hypervisor and by definition
-          their traffic should never go out through a tunnel.
-        </li>
-        <li>
-          A higher-priority rule to match packets that have the MLF_LOCAL_ONLY
-          logical flow flag set, and whose destination is a multicast address.
-          This flag indicates that the packet should not be delivered to remote
-          hypervisors, even if the multicast destination includes ports on
-          remote hypervisors. This flag is used when
-          <code>ovn-controller</code> is the originator of the multicast packet.
-          Since each <code>ovn-controller</code> instance is originating these
-          packets, the packets only need to be delivered to local ports.
-        </li>
-        <li>
-          A fallback flow that resubmits to table 33 if there is no other
-          match.
-        </li>
-      </ul>
-
-      <p>
-        Flows in table 33 resemble those in table 32 but for logical ports that
-        reside locally rather than remotely.  For unicast logical output ports
-        on the local hypervisor, the actions just resubmit to table 34.  For
-        multicast output ports that include one or more logical ports on the
-        local hypervisor, for each such logical port <var>P</var>, the actions
-        change the logical output port to <var>P</var>, then resubmit to table
-        34.
-      </p>
-
-      <p>
-        A special case is that when a localnet port exists on the datapath,
-        remote port is connected by switching to the localnet port. In this
-        case, instead of adding a flow in table 32 to reach the remote port, a
-        flow is added in table 33 to switch the logical outport to the localnet
-        port, and resubmit to table 33 as if it were unicasted to a logical
-        port on the local hypervisor.
-      </p>
-
-      <p>
-        Table 34 matches and drops packets for which the logical input and
-        output ports are the same and the MLF_ALLOW_LOOPBACK flag is not
-        set.  It resubmits other packets to table 40.
-      </p>
-    </li>
-
-    <li>
-      <p>
-        OpenFlow tables 40 through 63 execute the logical egress pipeline from
-        the <code>Logical_Flow</code> table in the OVN Southbound database.
-        The egress pipeline can perform a final stage of validation before
-        packet delivery.  Eventually, it may execute an <code>output</code>
-        action, which <code>ovn-controller</code> implements by resubmitting to
-        table 64.  A packet for which the pipeline never executes
-        <code>output</code> is effectively dropped (although it may have been
-        transmitted through a tunnel across a physical network).
-      </p>
-
-      <p>
-        The egress pipeline cannot change the logical output port or cause
-        further tunneling.
-      </p>
-    </li>
-
-    <li>
-     <p>
-       Table 64 bypasses OpenFlow loopback when MLF_ALLOW_LOOPBACK is set.
-       Logical loopback was handled in table 34, but OpenFlow by default also
-       prevents loopback to the OpenFlow ingress port.  Thus, when
-       MLF_ALLOW_LOOPBACK is set, OpenFlow table 64 saves the OpenFlow ingress
-       port, sets it to zero, resubmits to table 65 for logical-to-physical
-       transformation, and then restores the OpenFlow ingress port,
-       effectively disabling OpenFlow loopback prevents.  When
-       MLF_ALLOW_LOOPBACK is unset, table 64 flow simply resubmits to table
-       65.
-     </p>
-    </li>
-
-    <li>
-      <p>
-        OpenFlow table 65 performs logical-to-physical translation, the
-        opposite of table 0.  It matches the packet's logical egress port.  Its
-        actions output the packet to the port attached to the OVN integration
-        bridge that represents that logical port.  If the logical egress port
-        is a container nested with a VM, then before sending the packet the
-        actions push on a VLAN header with an appropriate VLAN ID.
-      </p>
-    </li>
-  </ol>
-
-  <h2>Logical Routers and Logical Patch Ports</h2>
-
-  <p>
-    Typically logical routers and logical patch ports do not have a
-    physical location and effectively reside on every hypervisor.  This is
-    the case for logical patch ports between logical routers and logical
-    switches behind those logical routers, to which VMs (and VIFs) attach.
-  </p>
-
-  <p>
-    Consider a packet sent from one virtual machine or container to another
-    VM or container that resides on a different subnet.  The packet will
-    traverse tables 0 to 65 as described in the previous section
-    <code>Architectural Physical Life Cycle of a Packet</code>, using the
-    logical datapath representing the logical switch that the sender is
-    attached to.  At table 32, the packet will use the fallback flow that
-    resubmits locally to table 33 on the same hypervisor.  In this case,
-    all of the processing from table 0 to table 65 occurs on the hypervisor
-    where the sender resides.
-  </p>
-
-  <p>
-    When the packet reaches table 65, the logical egress port is a logical
-    patch port.  The implementation in table 65 differs depending on the OVS
-    version, although the observed behavior is meant to be the same:
-  </p>
-
-  <ul>
-    <li>
-      In OVS versions 2.6 and earlier, table 65 outputs to an OVS patch
-      port that represents the logical patch port.  The packet re-enters
-      the OpenFlow flow table from the OVS patch port's peer in table 0,
-      which identifies the logical datapath and logical input port based
-      on the OVS patch port's OpenFlow port number.
-    </li>
-
-    <li>
-      In OVS versions 2.7 and later, the packet is cloned and resubmitted
-      directly to the first OpenFlow flow table in the ingress pipeline,
-      setting the logical ingress port to the peer logical patch port, and
-      using the peer logical patch port's logical datapath (that
-      represents the logical router).
-    </li>
-  </ul>
-
-  <p>
-    The packet re-enters the ingress pipeline in order to traverse tables
-    8 to 65 again, this time using the logical datapath representing the
-    logical router.  The processing continues as described in the previous
-    section <code>Architectural Physical Life Cycle of a Packet</code>.
-    When the packet reachs table 65, the logical egress port will once
-    again be a logical patch port.  In the same manner as described above,
-    this logical patch port will cause the packet to be resubmitted to
-    OpenFlow tables 8 to 65, this time using the logical datapath
-    representing the logical switch that the destination VM or container
-    is attached to.
-  </p>
-
-  <p>
-    The packet traverses tables 8 to 65 a third and final time.  If the
-    destination VM or container resides on a remote hypervisor, then table
-    32 will send the packet on a tunnel port from the sender's hypervisor
-    to the remote hypervisor.  Finally table 65 will output the packet
-    directly to the destination VM or container.
-  </p>
-
-  <p>
-    The following sections describe two exceptions, where logical routers
-    and/or logical patch ports are associated with a physical location.
-  </p>
-
-  <h3>Gateway Routers</h3>
-
-  <p>
-    A <dfn>gateway router</dfn> is a logical router that is bound to a
-    physical location.  This includes all of the logical patch ports of
-    the logical router, as well as all of the peer logical patch ports on
-    logical switches.  In the OVN Southbound database, the
-    <code>Port_Binding</code> entries for these logical patch ports use
-    the type <code>l3gateway</code> rather than <code>patch</code>, in
-    order to distinguish that these logical patch ports are bound to a
-    chassis.
-  </p>
-
-  <p>
-    When a hypervisor processes a packet on a logical datapath
-    representing a logical switch, and the logical egress port is a
-    <code>l3gateway</code> port representing connectivity to a gateway
-    router, the packet will match a flow in table 32 that sends the
-    packet on a tunnel port to the chassis where the gateway router
-    resides.  This processing in table 32 is done in the same manner as
-    for VIFs.
-  </p>
-
-  <p>
-    Gateway routers are typically used in between distributed logical
-    routers and physical networks.  The distributed logical router and
-    the logical switches behind it, to which VMs and containers attach,
-    effectively reside on each hypervisor.  The distributed router and
-    the gateway router are connected by another logical switch, sometimes
-    referred to as a <code>join</code> logical switch.  On the other
-    side, the gateway router connects to another logical switch that has
-    a localnet port connecting to the physical network.
-  </p>
-
-  <p>
-    When using gateway routers, DNAT and SNAT rules are associated with
-    the gateway router, which provides a central location that can handle
-    one-to-many SNAT (aka IP masquerading).
-  </p>
-
-  <h3>Distributed Gateway Ports</h3>
-
-  <p>
-    <dfn>Distributed gateway ports</dfn> are logical router patch ports
-    that directly connect distributed logical routers to logical
-    switches with localnet ports.
-  </p>
-
-  <p>
-    The primary design goal of distributed gateway ports is to allow as
-    much traffic as possible to be handled locally on the hypervisor
-    where a VM or container resides.  Whenever possible, packets from
-    the VM or container to the outside world should be processed
-    completely on that VM's or container's hypervisor, eventually
-    traversing a localnet port instance on that hypervisor to the
-    physical network.  Whenever possible, packets from the outside
-    world to a VM or container should be directed through the physical
-    network directly to the VM's or container's hypervisor, where the
-    packet will enter the integration bridge through a localnet port.
-  </p>
-
-  <p>
-    In order to allow for the distributed processing of packets
-    described in the paragraph above, distributed gateway ports need to
-    be logical patch ports that effectively reside on every hypervisor,
-    rather than <code>l3gateway</code> ports that are bound to a
-    particular chassis.  However, the flows associated with distributed
-    gateway ports often need to be associated with physical locations,
-    for the following reasons:
-  </p>
-
-  <ul>
-    <li>
-      <p>
-        The physical network that the localnet port is attached to
-        typically uses L2 learning.  Any Ethernet address used over the
-        distributed gateway port must be restricted to a single physical
-        location so that upstream L2 learning is not confused.  Traffic
-        sent out the distributed gateway port towards the localnet port
-        with a specific Ethernet address must be sent out one specific
-        instance of the distributed gateway port on one specific
-        chassis.  Traffic received from the localnet port (or from a VIF
-        on the same logical switch as the localnet port) with a specific
-        Ethernet address must be directed to the logical switch's patch
-        port instance on that specific chassis.
-      </p>
-
-      <p>
-        Due to the implications of L2 learning, the Ethernet address and
-        IP address of the distributed gateway port need to be restricted
-        to a single physical location.  For this reason, the user must
-        specify one chassis associated with the distributed gateway
-        port.  Note that traffic traversing the distributed gateway port
-        using other Ethernet addresses and IP addresses (e.g. one-to-one
-        NAT) is not restricted to this chassis.
-      </p>
-
-      <p>
-        Replies to ARP and ND requests must be restricted to a single
-        physical location, where the Ethernet address in the reply
-        resides.  This includes ARP and ND replies for the IP address
-        of the distributed gateway port, which are restricted to the
-        chassis that the user associated with the distributed gateway
-        port.
-      </p>
-    </li>
-
-    <li>
-      In order to support one-to-many SNAT (aka IP masquerading), where
-      multiple logical IP addresses spread across multiple chassis are
-      mapped to a single external IP address, it will be necessary to
-      handle some of the logical router processing on a specific chassis
-      in a centralized manner.  Since the SNAT external IP address is
-      typically the distributed gateway port IP address, and for
-      simplicity, the same chassis associated with the distributed
-      gateway port is used.
-    </li>
-  </ul>
-
-  <p>
-    The details of flow restrictions to specific chassis are described
-    in the <code>ovn-northd</code> documentation.
-  </p>
-
-  <p>
-    While most of the physical location dependent aspects of distributed
-    gateway ports can be handled by restricting some flows to specific
-    chassis, one additional mechanism is required.  When a packet
-    leaves the ingress pipeline and the logical egress port is the
-    distributed gateway port, one of two different sets of actions is
-    required at table 32:
-  </p>
-
-  <ul>
-    <li>
-      If the packet can be handled locally on the sender's hypervisor
-      (e.g. one-to-one NAT traffic), then the packet should just be
-      resubmitted locally to table 33, in the normal manner for
-      distributed logical patch ports.
-    </li>
-
-    <li>
-      However, if the packet needs to be handled on the chassis
-      associated with the distributed gateway port (e.g. one-to-many
-      SNAT traffic or non-NAT traffic), then table 32 must send the
-      packet on a tunnel port to that chassis.
-    </li>
-  </ul>
-
-  <p>
-    In order to trigger the second set of actions, the
-    <code>chassisredirect</code> type of southbound
-    <code>Port_Binding</code> has been added.  Setting the logical
-    egress port to the type <code>chassisredirect</code> logical port is
-    simply a way to indicate that although the packet is destined for
-    the distributed gateway port, it needs to be redirected to a
-    different chassis.  At table 32, packets with this logical egress
-    port are sent to a specific chassis, in the same way that table 32
-    directs packets whose logical egress port is a VIF or a type
-    <code>l3gateway</code> port to different chassis.  Once the packet
-    arrives at that chassis, table 33 resets the logical egress port to
-    the value representing the distributed gateway port.  For each
-    distributed gateway port, there is one type
-    <code>chassisredirect</code> port, in addition to the distributed
-    logical patch port representing the distributed gateway port.
-  </p>
-
-  <h3>High Availability for Distributed Gateway Ports</h3>
-
-  <p>
-    OVN allows you to specify a prioritized list of chassis for a distributed
-    gateway port.  This is done by associating multiple
-    <code>Gateway_Chassis</code> rows with a <code>Logical_Router_Port</code>
-    in the <code>OVN_Northbound</code> database.
-  </p>
-
-  <p>
-    When multiple chassis have been specified for a gateway, all chassis that
-    may send packets to that gateway will enable BFD on tunnels to all
-    configured gateway chassis.  The current master chassis for the gateway
-    is the highest priority gateway chassis that is currently viewed as
-    active based on BFD status.
-  </p>
-
-  <p>
-    For more information on L3 gateway high availability, please refer to
-    http://docs.openvswitch.org/en/latest/topics/high-availability.
-  </p>
-
-  <h2>Multiple localnet logical switches connected to a Logical Router</h2>
-
-  <p>
-    It is possible to have multiple logical switches each with a localnet port
-    (representing physical networks) connected to a logical router, in which
-    one localnet logical switch may provide the external connectivity via a
-    distributed gateway port and rest of the localnet logical switches use
-    VLAN tagging in the physical network. It is expected that
-    <code>ovn-bridge-mappings</code> is configured appropriately on the
-    chassis for all these localnet networks.
-  </p>
-
-  <h3>East West routing</h3>
-  <p>
-    East-West routing between these localnet VLAN tagged logical switches
-    work almost the same way as normal logical switches. When the VM sends
-    such a packet, then:
-  </p>
-  <ol>
-    <li>
-      It first enters the ingress pipeline, and then egress pipeline of the
-      source localnet logical switch datapath. It then enters the ingress
-      pipeline of the logical router datapath via the logical router port in
-      the source chassis.
-    </li>
-
-    <li>
-      Routing decision is taken.
-    </li>
-
-    <li>
-      <p>
-        From the router datapath, packet enters the ingress pipeline and then
-        egress pipeline of the destination localnet logical switch datapath
-        and goes out of the integration bridge to the provider bridge (
-        belonging to the destination logical switch) via the localnet port.
-        While sending the packet to provider bridge, we also replace router
-        port MAC as source MAC with a chassis unique MAC.
-      </p>
-
-      <p>
-        This chassis unique MAC is configured as global ovs config on each
-        chassis (eg. via "<code>ovs-vsctl set open . external-ids:
-        ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"</code>").  For more
-        details, see <code>ovn-controller</code>(8).
-      </p>
-
-      <p>
-        If the above is not configured, then source MAC would be the router
-        port MAC.  This could create problem if we have more than one chassis.
-        This is because, since the router port is distributed, the same
-        (MAC,VLAN) tuple will seen by physical network from other chassis as
-        well, which could cause these issues:
-      </p>
-      
-      <ul>
-        <li>
-          Continuous MAC moves in top-of-rack switch (ToR).
-        </li>
-        <li>
-          ToR dropping the traffic, which is causing continuous MAC moves.
-        </li>
-        <li>
-          ToR blocking the ports from which MAC moves are happening.
-        </li>
-      </ul>
-    </li>
-
-    <li>
-      The destination chassis receives the packet via the localnet port and
-      sends it to the integration bridge. The packet enters the
-      ingress pipeline and then egress pipeline of the destination localnet
-      logical switch and finally gets delivered to the destination VM port.
-    </li>
-  </ol>
-
-  <h3>External traffic</h3>
-
-  <p>
-    The following happens when a VM sends an external traffic (which requires
-    NATting) and the chassis hosting the VM doesn't have a distributed gateway
-    port.
-  </p>
-
-  <ol>
-    <li>
-      The packet first enters the ingress pipeline, and then egress pipeline of
-      the source localnet logical switch datapath. It then enters the ingress
-      pipeline of the logical router datapath via the logical router port in
-      the source chassis.
-    </li>
-
-    <li>
-      Routing decision is taken. Since the gateway router or the distributed
-      gateway port doesn't reside in the source chassis, the traffic is
-      redirected to the gateway chassis via the tunnel port.
-    </li>
-
-    <li>
-      The gateway chassis receives the packet via the tunnel port and the
-      packet enters the egress pipeline of the logical router datapath. NAT
-      rules are applied here. The packet then enters the ingress pipeline and
-      then egress pipeline of the localnet logical switch datapath which
-      provides external connectivity and finally goes out via the localnet
-      port of the logical switch which provides external connectivity.
-    </li>
-  </ol>
-
-  <p>
-    Although this works, the VM traffic is tunnelled when sent from the compute
-    chassis to the gateway chassis. In order for it to work properly, the MTU
-    of the localnet logical switches must be lowered to account for the tunnel
-    encapsulation.
-  </p>
-
-  <h2>
-    Centralized routing for localnet VLAN tagged logical switches connected
-    to a Logical Router
-  </h2>
-
-  <p>
-    To overcome the tunnel encapsulation problem described in the previous
-    section, <code>OVN</code> supports the option of enabling centralized
-    routing for localnet VLAN tagged logical switches. CMS can configure the
-    option <ref column="options:reside-on-redirect-chassis"
-    table="Logical_Router_Port" db="OVN_NB"/> to <code>true</code> for each
-    <ref table="Logical_Router_Port" db="OVN_NB"/> which connects to the
-    localnet VLAN tagged logical switches. This causes the gateway
-    chassis (hosting the distributed gateway port) to handle all the
-    routing for these networks, making it centralized. It will reply to
-    the ARP requests for the logical router port IPs.
-  </p>
-
-  <p>
-    If the logical router doesn't have a distributed gateway port connecting
-    to the localnet logical switch which provides external connectivity,
-    then this option is ignored by <code>OVN</code>.
-  </p>
-
-  <p>
-    The following happens when a VM sends an east-west traffic which needs to
-    be routed:
-  </p>
-
-  <ol>
-    <li>
-      The packet first enters the ingress pipeline, and then egress pipeline of
-      the source localnet logical switch datapath and is sent out via the
-      localnet port of the source localnet logical switch (instead of sending
-      it to router pipeline).
-    </li>
-
-    <li>
-      The gateway chassis receives the packet via the localnet port of the
-      source localnet logical switch and sends it to the integration bridge.
-      The packet then enters the ingress pipeline, and then egress pipeline of
-      the source localnet logical switch datapath and enters the ingress
-      pipeline of the logical router datapath.
-    </li>
-
-    <li>
-      Routing decision is taken.
-    </li>
-
-    <li>
-      From the router datapath, packet enters the ingress pipeline and then
-      egress pipeline of the destination localnet logical switch datapath.
-      It then goes out of the integration bridge to the provider bridge (
-      belonging to the destination logical switch) via the localnet port.
-    </li>
-
-    <li>
-      The destination chassis receives the packet via the localnet port and
-      sends it to the integration bridge. The packet enters the
-      ingress pipeline and then egress pipeline of the destination localnet
-      logical switch and finally delivered to the destination VM port.
-    </li>
-  </ol>
-
-  <p>
-    The following happens when a VM sends an external traffic which requires
-    NATting:
-  </p>
-
-  <ol>
-    <li>
-      The packet first enters the ingress pipeline, and then egress pipeline of
-      the source localnet logical switch datapath and is sent out via the
-      localnet port of the source localnet logical switch (instead of sending
-      it to router pipeline).
-    </li>
-
-    <li>
-      The gateway chassis receives the packet via the localnet port of the
-      source localnet logical switch and sends it to the integration bridge.
-      The packet then enters the ingress pipeline, and then egress pipeline of
-      the source localnet logical switch datapath and enters the ingress
-      pipeline of the logical router datapath.
-    </li>
-
-    <li>
-      Routing decision is taken and NAT rules are applied.
-    </li>
-
-    <li>
-      From the router datapath, packet enters the ingress pipeline and then
-      egress pipeline of the localnet logical switch datapath which provides
-      external connectivity. It then goes out of the integration bridge to the
-      provider bridge (belonging to the logical switch which provides external
-      connectivity) via the localnet port.
-    </li>
-  </ol>
-
-  <p>
-    The following happens for the reverse external traffic.
-  </p>
-
-  <ol>
-    <li>
-      The gateway chassis receives the packet from the localnet port of
-      the logical switch which provides external connectivity. The packet then
-      enters the ingress pipeline and then egress pipeline of the localnet
-      logical switch (which provides external connectivity). The packet then
-      enters the ingress pipeline of the logical router datapath.
-    </li>
-
-    <li>
-      The ingress pipeline of the logical router datapath applies the unNATting
-      rules. The packet then enters the ingress pipeline and then egress
-      pipeline of the source localnet logical switch. Since the source VM
-      doesn't reside in the gateway chassis, the packet is sent out via the
-      localnet port of the source logical switch.
-    </li>
-
-    <li>
-      The source chassis receives the packet via the localnet port and
-      sends it to the integration bridge. The packet enters the
-      ingress pipeline and then egress pipeline of the source localnet
-      logical switch and finally gets delivered to the source VM port.
-    </li>
-  </ol>
-
-  <h2>Life Cycle of a VTEP gateway</h2>
-
-  <p>
-    A gateway is a chassis that forwards traffic between the OVN-managed
-    part of a logical network and a physical VLAN,  extending a
-    tunnel-based logical network into a physical network.
-  </p>
-
-  <p>
-    The steps below refer often to details of the OVN and VTEP database
-    schemas.  Please see <code>ovn-sb</code>(5), <code>ovn-nb</code>(5)
-    and <code>vtep</code>(5), respectively, for the full story on these
-    databases.
-  </p>
-
-  <ol>
-    <li>
-      A VTEP gateway's life cycle begins with the administrator registering
-      the VTEP gateway as a <code>Physical_Switch</code> table entry in the
-      <code>VTEP</code> database.  The <code>ovn-controller-vtep</code>
-      connected to this VTEP database, will recognize the new VTEP gateway
-      and create a new <code>Chassis</code> table entry for it in the
-      <code>OVN_Southbound</code> database.
-    </li>
-
-    <li>
-      The administrator can then create a new <code>Logical_Switch</code>
-      table entry, and bind a particular vlan on a VTEP gateway's port to
-      any VTEP logical switch.  Once a VTEP logical switch is bound to
-      a VTEP gateway, the <code>ovn-controller-vtep</code> will detect
-      it and add its name to the <var>vtep_logical_switches</var>
-      column of the <code>Chassis</code> table in the <code>
-      OVN_Southbound</code> database.  Note, the <var>tunnel_key</var>
-      column of VTEP logical switch is not filled at creation.  The
-      <code>ovn-controller-vtep</code> will set the column when the
-      correponding vtep logical switch is bound to an OVN logical network.
-    </li>
-
-    <li>
-      Now, the administrator can use the CMS to add a VTEP logical switch
-      to the OVN logical network.  To do that, the CMS must first create a
-      new <code>Logical_Switch_Port</code> table entry in the <code>
-      OVN_Northbound</code> database.  Then, the <var>type</var> column
-      of this entry must be set to "vtep".  Next, the <var>
-      vtep-logical-switch</var> and <var>vtep-physical-switch</var> keys
-      in the <var>options</var> column must also be specified, since
-      multiple VTEP gateways can attach to the same VTEP logical switch.
-    </li>
-
-    <li>
-      The newly created logical port in the <code>OVN_Northbound</code>
-      database and its configuration will be passed down to the <code>
-      OVN_Southbound</code> database as a new <code>Port_Binding</code>
-      table entry.  The <code>ovn-controller-vtep</code> will recognize the
-      change and bind the logical port to the corresponding VTEP gateway
-      chassis.  Configuration of binding the same VTEP logical switch to
-      a different OVN logical networks is not allowed and a warning will be
-      generated in the log.
-    </li>
-
-    <li>
-      Beside binding to the VTEP gateway chassis, the <code>
-      ovn-controller-vtep</code> will update the <var>tunnel_key</var>
-      column of the VTEP logical switch to the corresponding <code>
-      Datapath_Binding</code> table entry's <var>tunnel_key</var> for the
-      bound OVN logical network.
-    </li>
-
-    <li>
-      Next, the <code>ovn-controller-vtep</code> will keep reacting to the
-      configuration change in the <code>Port_Binding</code> in the
-      <code>OVN_Northbound</code> database, and updating the
-      <code>Ucast_Macs_Remote</code> table in the <code>VTEP</code> database.
-      This allows the VTEP gateway to understand where to forward the unicast
-      traffic coming from the extended external network.
-    </li>
-
-    <li>
-      Eventually, the VTEP gateway's life cycle ends when the administrator
-      unregisters the VTEP gateway from the <code>VTEP</code> database.
-      The <code>ovn-controller-vtep</code> will recognize the event and
-      remove all related configurations (<code>Chassis</code> table entry
-      and port bindings) in the <code>OVN_Southbound</code> database.
-    </li>
-
-    <li>
-      When the <code>ovn-controller-vtep</code> is terminated, all related
-      configurations in the <code>OVN_Southbound</code> database and
-      the <code>VTEP</code> database will be cleaned, including
-      <code>Chassis</code> table entries for all registered VTEP gateways
-      and their port bindings, and all <code>Ucast_Macs_Remote</code> table
-      entries and the <code>Logical_Switch</code> tunnel keys.
-    </li>
-  </ol>
-
-  <h2>Native OVN services for external logical ports</h2>
-
-  <p>
-    To support OVN native services (like DHCP/IPv6 RA/DNS lookup) to the
-    cloud resources which are external, OVN supports <code>external</code>
-    logical ports.
-  </p>
-
-  <p>
-    Below are some of the use cases where <code>external</code> ports can be
-    used.
-  </p>
-
-  <ul>
-    <li>
-      VMs connected to SR-IOV nics - Traffic from these VMs by passes the
-      kernel stack and local <code>ovn-controller</code> do not bind these
-      ports and cannot serve the native services.
-    </li>
-    <li>
-      When CMS supports provisioning baremetal servers.
-    </li>
-  </ul>
-
-  <p>
-    OVN will provide the native services if CMS has done the below
-    configuration in the <dfn>OVN Northbound Database</dfn>.
-  </p>
-
-  <ul>
-    <li>
-      A row is created in <code>Logical_Switch_Port</code>, configuring the
-      <ref column="addresses" table="Logical_Switch_Port" db="OVN_NB"/> column
-      and setting the <ref column="type" table="Logical_Switch_Port"
-      db="OVN_NB"/> to <code>external</code>.
-    </li>
-
-    <li>
-      <ref column="ha_chassis_group" table="Logical_Switch_Port"
-      db="OVN_NB"/> column is configured.
-    </li>
-
-    <li>
-      The HA chassis which belongs to the HA chassis group has the
-      <code>ovn-bridge-mappings</code> configured and has proper L2
-      connectivity so that it can receive the DHCP and other related request
-      packets from these external resources.
-    </li>
-
-    <li>
-      The Logical_Switch of this port has a <code>localnet</code> port.
-    </li>
-
-    <li>
-      Native OVN services are enabled by configuring the DHCP and other
-      options like the way it is done for the normal logical ports.
-    </li>
-  </ul>
-
-  <p>
-    It is recommended to use the same HA chassis group for all the external
-    ports of a logical switch. Otherwise, the physical switch might see MAC
-    flap issue when different chassis provide the native services. For
-    example when supporting native DHCPv4 service, DHCPv4 server mac
-    (configured in <ref column="options:server_mac" table="DHCP_Options"
-    db="OVN_NB"/> column in table <ref table="DHCP_Options"/>) originating
-    from different ports can cause MAC flap issue.
-    The MAC of the logical router IP(s) can also flap if the same HA chassis
-    group is not set for all the external ports of a logical switch.
-  </p>
-
-  <h1>Security</h1>
-
-  <h2>Role-Based Access Controls for the Soutbound DB</h2>
-  <p>
-    In order to provide additional security against the possibility of an OVN
-    chassis becoming compromised in such a way as to allow rogue software to
-    make arbitrary modifications to the southbound database state and thus
-    disrupt the OVN network, role-based access controls (see
-    <code>ovsdb-server(1)</code> for additional details) are provided for the
-    southbound database.
-  </p>
-
-  <p>
-    The implementation of role-based access controls (RBAC) requires the
-    addition of two tables to an OVSDB schema: the <code>RBAC_Role</code>
-    table, which is indexed by role name and maps the the names of the various
-    tables that may be modifiable for a given role to individual rows in a
-    permissions table containing detailed permission information for that role,
-    and the permission table itself which consists of rows containing the
-    following information:
-  </p>
-  <dl>
-    <dt><code>Table Name</code></dt>
-    <dd>
-      The name of the associated table. This column exists primarily as an
-      aid for humans reading the contents of this table.
-    </dd>
-
-    <dt><code>Auth Criteria</code></dt>
-    <dd>
-      A set of strings containing the names of columns (or column:key pairs
-      for columns containing string:string maps).  The contents of at least
-      one of the columns or column:key values in a row to be modified,
-      inserted, or deleted must be equal to the ID of the client attempting
-      to act on the row in order for the authorization check to pass. If the
-      authorization criteria is empty, authorization checking is disabled and
-      all clients for the role will be treated as authorized.
-    </dd>
-
-    <dt><code>Insert/Delete</code></dt>
-    <dd>
-       Row insertion/deletion permission; boolean value indicating whether
-       insertion and deletion of rows is allowed for the associated table.
-       If true, insertion and deletion of rows is allowed for authorized
-       clients.
-    </dd>
-
-    <dt><code>Updatable Columns</code></dt>
-    <dd>
-      A set of strings containing the names of columns or column:key pairs
-      that may be updated or mutated by authorized clients. Modifications to
-      columns within a row are only permitted when the authorization check
-      for the client passes and all columns to be modified are included in
-      this set of modifiable columns.
-    </dd>
-  </dl>
-
-  <p>
-    RBAC configuration for the OVN southbound database is maintained by
-    ovn-northd. With RBAC enabled, modifications are only permitted for the
-    <code>Chassis</code>, <code>Encap</code>, <code>Port_Binding</code>, and
-    <code>MAC_Binding</code> tables, and are resstricted as follows:
-  </p>
-  <dl>
-    <dt><code>Chassis</code></dt>
-    <dd>
-      <p>
-       <code>Authorization</code>: client ID must match the chassis name.
-      </p>
-      <p>
-        <code>Insert/Delete</code>: authorized row insertion and deletion
-        are permitted.
-      </p>
-      <p>
-        <code>Update</code>: The columns <code>nb_cfg</code>,
-        <code>external_ids</code>, <code>encaps</code>, and
-        <code>vtep_logical_switches</code> may be modified when authorized.
-      </p>
-    </dd>
-
-    <dt><code>Encap</code></dt>
-    <dd>
-      <p>
-        <code>Authorization</code>: client ID must match the chassis name.
-      </p>
-      <p>
-        <code>Insert/Delete</code>: row insertion and row deletion
-        are permitted.
-      </p>
-      <p>
-        <code>Update</code>: The columns <code>type</code>,
-        <code>options</code>, and <code>ip</code> can be modified.
-      </p>
-    </dd>
-
-    <dt><code>Port_Binding</code></dt>
-    <dd>
-      <p>
-        <code>Authorization</code>: disabled (all clients are considered
-        authorized. A future enhancement may add columns (or keys to
-        <code>external_ids</code>) in order to control which chassis are
-        allowed to bind each port.
-      </p>
-      <p>
-        <code>Insert/Delete</code>: row insertion/deletion are not permitted
-        (ovn-northd maintains rows in this table.
-      </p>
-      <p>
-        <code>Update</code>: Only modifications to the <code>chassis</code>
-        column are permitted.
-      </p>
-    </dd>
-
-    <dt><code>MAC_Binding</code></dt>
-    <dd>
-      <p>
-        <code>Authorization</code>: disabled (all clients are considered
-        to be authorized).
-      </p>
-      <p>
-        <code>Insert/Delete</code>: row insertion/deletion are permitted.
-      </p>
-      <p>
-        <code>Update</code>: The columns <code>logical_port</code>,
-        <code>ip</code>, <code>mac</code>, and <code>datapath</code> may be
-        modified by ovn-controller.
-      </p>
-    </dd>
-  </dl>
-
-  <p>
-    Enabling RBAC for ovn-controller connections to the southbound database
-    requires the following steps:
-  </p>
-
-  <ol>
-    <li>
-      Creating SSL certificates for each chassis with the certificate CN field
-      set to the chassis name (e.g. for a chassis with
-      <code>external-ids:system-id=chassis-1</code>, via the command
-      "<code>ovs-pki -u req+sign chassis-1 switch</code>").
-    </li>
-    <li>
-      Configuring each ovn-controller to use SSL when connecting to the
-      southbound database (e.g. via "<code>ovs-vsctl set open .
-      external-ids:ovn-remote=ssl:x.x.x.x:6642</code>").
-    </li>
-    <li>
-      Configuring a southbound database SSL remote with "ovn-controller" role
-      (e.g. via "<code>ovn-sbctl set-connection role=ovn-controller
-      pssl:6642</code>").
-    </li>
-  </ol>
-
-  <h2>Encrypt Tunnel Traffic with IPsec</h2>
-  <p>
-    OVN tunnel traffic goes through physical routers and switches. These
-    physical devices could be untrusted (devices in public network) or might be
-    compromised. Enabling encryption to the tunnel traffic can prevent the
-    traffic data from being monitored and manipulated.
-  </p>
-  <p>
-    The tunnel traffic is encrypted with IPsec. The CMS sets the
-    <code>ipsec</code> column in the northbound <code>NB_Global</code> table to
-    enable or disable IPsec encrytion. If <code>ipsec</code> is true, all OVN
-    tunnels will be encrypted. If <code>ipsec</code> is false, no OVN tunnels
-    will be encrypted.
-  </p>
-  <p>
-    When CMS updates the <code>ipsec</code> column in the northbound
-    <code>NB_Global</code> table, <code>ovn-northd</code> copies the value to
-    the <code>ipsec</code> column in the southbound <code>SB_Global</code>
-    table. <code>ovn-controller</code> in each chassis monitors the southbound
-    database and sets the options of the OVS tunnel interface accordingly. OVS
-    tunnel interface options are monitored by the
-    <code>ovs-monitor-ipsec</code> daemon which configures IKE daemon to set up
-    IPsec connections.
-  </p>
-  <p>
-    Chassis authenticates each other by using certificate. The authentication
-    succeeds if the other end in tunnel presents a certificate signed by a
-    trusted CA and the common name (CN) matches the expected chassis name.  The
-    SSL certificates used in role-based access controls (RBAC) can be used in
-    IPsec. Or use <code>ovs-pki</code> to create different certificates. The
-    certificate is required to be x.509 version 3, and with CN field and
-    subjectAltName field being set to the chassis name.
-  </p>
-  <p>
-    The CA certificate, chassis certificate and private key are required to be
-    installed in each chassis before enabling IPsec. Please see
-    <code>ovs-vswitchd.conf.db</code>(5) for setting up CA based IPsec
-    authentication.
-  </p>
-  <h1>Design Decisions</h1>
-
-  <h2>Tunnel Encapsulations</h2>
-
-  <p>
-    OVN annotates logical network packets that it sends from one hypervisor to
-    another with the following three pieces of metadata, which are encoded in
-    an encapsulation-specific fashion:
-  </p>
-
-  <ul>
-    <li>
-      24-bit logical datapath identifier, from the <code>tunnel_key</code>
-      column in the OVN Southbound <code>Datapath_Binding</code> table.
-    </li>
-
-    <li>
-      15-bit logical ingress port identifier.  ID 0 is reserved for internal
-      use within OVN.  IDs 1 through 32767, inclusive, may be assigned to
-      logical ports (see the <code>tunnel_key</code> column in the OVN
-      Southbound <code>Port_Binding</code> table).
-    </li>
-
-    <li>
-      16-bit logical egress port identifier.  IDs 0 through 32767 have the same
-      meaning as for logical ingress ports.  IDs 32768 through 65535,
-      inclusive, may be assigned to logical multicast groups (see the
-      <code>tunnel_key</code> column in the OVN Southbound
-      <code>Multicast_Group</code> table).
-    </li>
-  </ul>
-
-  <p>
-    For hypervisor-to-hypervisor traffic, OVN supports only Geneve and STT
-    encapsulations, for the following reasons:
-  </p>
-
-  <ul>
-    <li>
-      Only STT and Geneve support the large amounts of metadata (over 32 bits
-      per packet) that OVN uses (as described above).
-    </li>
-
-    <li>
-      STT and Geneve use randomized UDP or TCP source ports that allows
-      efficient distribution among multiple paths in environments that use ECMP
-      in their underlay.
-    </li>
-
-    <li>
-      NICs are available to offload STT and Geneve encapsulation and
-      decapsulation.
-    </li>
-  </ul>
-
-  <p>
-    Due to its flexibility, the preferred encapsulation between hypervisors is
-    Geneve.  For Geneve encapsulation, OVN transmits the logical datapath
-    identifier in the Geneve VNI.
-
-    <!-- Keep the following in sync with ovn/controller/physical.h. -->
-    OVN transmits the logical ingress and logical egress ports in a TLV with
-    class 0x0102, type 0x80, and a 32-bit value encoded as follows, from MSB to
-    LSB:
-  </p>
-
-  <diagram>
-    <header name="">
-      <bits name="rsv" above="1" below="0" width=".25"/>
-      <bits name="ingress port" above="15" width=".75"/>
-      <bits name="egress port" above="16" width=".75"/>
-    </header>
-  </diagram>
-
-  <p>
-    Environments whose NICs lack Geneve offload may prefer STT encapsulation
-    for performance reasons.  For STT encapsulation, OVN encodes all three
-    pieces of logical metadata in the STT 64-bit tunnel ID as follows, from MSB
-    to LSB:
-  </p>
-
-  <diagram>
-    <header name="">
-      <bits name="reserved" above="9" below="0" width=".5"/>
-      <bits name="ingress port" above="15" width=".75"/>
-      <bits name="egress port" above="16" width=".75"/>
-      <bits name="datapath" above="24" width="1.25"/>
-    </header>
-  </diagram>
-
-  <p>
-    For connecting to gateways, in addition to Geneve and STT, OVN supports
-    VXLAN, because only VXLAN support is common on top-of-rack (ToR) switches.
-    Currently, gateways have a feature set that matches the capabilities as
-    defined by the VTEP schema, so fewer bits of metadata are necessary.  In
-    the future, gateways that do not support encapsulations with large amounts
-    of metadata may continue to have a reduced feature set.
-  </p>
-</manpage>
diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk
index e8c59a2eb..c2c3b7d5c 100644
--- a/ovn/utilities/automake.mk
+++ b/ovn/utilities/automake.mk
@@ -1,43 +1,16 @@
-scripts_SCRIPTS += \
-    ovn/utilities/ovn-ctl \
-    ovn/utilities/ovndb-servers.ocf
-
 man_MANS += \
-    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
+    ovn/utilities/ovn-sbctl.8
 
 MAN_ROOTS += \
-    ovn/utilities/ovn-sbctl.8.in \
-    ovn/utilities/ovn-detrace.1.in
-
-# Docker drivers
-bin_SCRIPTS += \
-    ovn/utilities/ovn-docker-overlay-driver \
-    ovn/utilities/ovn-docker-underlay-driver \
-    ovn/utilities/ovn-detrace
+    ovn/utilities/ovn-sbctl.8.in
 
 EXTRA_DIST += \
-    ovn/utilities/ovn-ctl \
-    ovn/utilities/ovn-ctl.8.xml \
-    ovn/utilities/ovn-docker-overlay-driver.in \
-    ovn/utilities/ovn-docker-underlay-driver.in \
-    ovn/utilities/ovn-nbctl.8.xml \
-    ovn/utilities/ovn-trace.8.xml \
-    ovn/utilities/ovn-detrace.in \
-    ovn/utilities/ovndb-servers.ocf
+    ovn/utilities/ovn-nbctl.8.xml
 
 CLEANFILES += \
-    ovn/utilities/ovn-ctl.8 \
-    ovn/utilities/ovn-docker-overlay-driver \
-    ovn/utilities/ovn-docker-underlay-driver \
     ovn/utilities/ovn-nbctl.8 \
-    ovn/utilities/ovn-sbctl.8 \
-    ovn/utilities/ovn-trace.8 \
-    ovn/utilities/ovn-detrace.1 \
-    ovn/utilities/ovn-detrace
+    ovn/utilities/ovn-sbctl.8
 
 # ovn-nbctl
 bin_PROGRAMS += ovn/utilities/ovn-nbctl
@@ -48,10 +21,3 @@ ovn_utilities_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenv
 bin_PROGRAMS += ovn/utilities/ovn-sbctl
 ovn_utilities_ovn_sbctl_SOURCES = ovn/utilities/ovn-sbctl.c
 ovn_utilities_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
-
-# ovn-trace
-bin_PROGRAMS += ovn/utilities/ovn-trace
-ovn_utilities_ovn_trace_SOURCES = ovn/utilities/ovn-trace.c
-ovn_utilities_ovn_trace_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la
-
-include ovn/utilities/bugtool/automake.mk
diff --git a/ovn/utilities/bugtool/automake.mk b/ovn/utilities/bugtool/automake.mk
deleted file mode 100644
index 8582074a7..000000000
--- a/ovn/utilities/bugtool/automake.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-if HAVE_PYTHON2
-bugtool_plugins += \
-	ovn/utilities/bugtool/plugins/network-status/ovn.xml
-
-bugtool_scripts += \
-	ovn/utilities/bugtool/ovn-bugtool-nbctl-show \
-	ovn/utilities/bugtool/ovn-bugtool-sbctl-show \
-	ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list
-endif
diff --git a/ovn/utilities/bugtool/ovn-bugtool-nbctl-show b/ovn/utilities/bugtool/ovn-bugtool-nbctl-show
deleted file mode 100644
index 927252745..000000000
--- a/ovn/utilities/bugtool/ovn-bugtool-nbctl-show
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /bin/sh
-
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of version 2.1 of the GNU Lesser General
-# Public License as published by the Free Software Foundation.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-# USA
-#
-# Copyright (C) 2016 Nicira, Inc.
-
-ovn-nbctl --timeout=3 show
diff --git a/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list b/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list
deleted file mode 100644
index 33a15d7d5..000000000
--- a/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /bin/sh
-
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of version 2.1 of the GNU Lesser General
-# Public License as published by the Free Software Foundation.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-# USA
-#
-# Copyright (C) 2016 Nicira, Inc.
-
-ovn-sbctl --timeout=3 lflow-list
diff --git a/ovn/utilities/bugtool/ovn-bugtool-sbctl-show b/ovn/utilities/bugtool/ovn-bugtool-sbctl-show
deleted file mode 100644
index b6741bcc2..000000000
--- a/ovn/utilities/bugtool/ovn-bugtool-sbctl-show
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /bin/sh
-
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of version 2.1 of the GNU Lesser General
-# Public License as published by the Free Software Foundation.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-# USA
-#
-# Copyright (C) 2016 Nicira, Inc.
-
-ovn-sbctl --timeout=3 show
diff --git a/ovn/utilities/bugtool/plugins/network-status/ovn.xml b/ovn/utilities/bugtool/plugins/network-status/ovn.xml
deleted file mode 100644
index 3b399feb3..000000000
--- a/ovn/utilities/bugtool/plugins/network-status/ovn.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
- This library is free software; you can redistribute it and/or modify
- it under the terms of version 2.1 of the GNU Lesser General Public
- License as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
- USA.
-
- Copyright (C) 2016 Nicira, Inc.
--->
-
-<collect>
-  <command label="ovn-nbctl-show" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-nbctl-show</command>
-  <command label="ovn-sbctl-show" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-sbctl-show</command>
-  <command label="ovn-sbctl-lflow-list" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-sbctl-lflow-list</command>
-</collect>
diff --git a/ovn/utilities/ovn-ctl b/ovn/utilities/ovn-ctl
deleted file mode 100755
index 7e5cd469c..000000000
--- a/ovn/utilities/ovn-ctl
+++ /dev/null
@@ -1,822 +0,0 @@
-#!/bin/sh
-#
-# 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.
-
-case $0 in
-    */*) dir0=`echo "$0" | sed 's,/[^/]*$,,'` ;;
-    *) dir0=./ ;;
-esac
-. "$dir0/ovs-lib" || exit 1
-
-for dir in "$sbindir" "$bindir" /sbin /bin /usr/sbin /usr/bin; do
-    case :$PATH: in
-        *:$dir:*) ;;
-        *) PATH=$PATH:$dir ;;
-    esac
-done
-
-
-ovnnb_active_conf_file="$etcdir/ovnnb-active.conf"
-ovnsb_active_conf_file="$etcdir/ovnsb-active.conf"
-ovn_northd_db_conf_file="$etcdir/ovn-northd-db-params.conf"
-## ----- ##
-## start ##
-## ----- ##
-
-pidfile_is_running () {
-    pidfile=$1
-    test -e "$pidfile" && pid=`cat "$pidfile"` && pid_exists "$pid"
-} >/dev/null 2>&1
-
-stop_nb_ovsdb() {
-    if pidfile_is_running $DB_NB_PID; then
-        ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl exit
-    fi
-}
-
-stop_sb_ovsdb() {
-    if pidfile_is_running $DB_SB_PID; then
-        ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl exit
-    fi
-}
-
-stop_ovsdb () {
-    stop_nb_ovsdb
-    stop_sb_ovsdb
-}
-
-demote_ovnnb() {
-    if test ! -z "$DB_NB_SYNC_FROM_ADDR"; then
-        echo "$DB_NB_SYNC_FROM_PROTO:$DB_NB_SYNC_FROM_ADDR:$DB_NB_SYNC_FROM_PORT" > $ovnnb_active_conf_file
-    fi
-
-    if test -e $ovnnb_active_conf_file; then
-        ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/set-active-ovsdb-server `cat $ovnnb_active_conf_file`
-        ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/connect-active-ovsdb-server
-    else
-        echo >&2 "$0: active server details not set"
-        exit 1
-    fi
-}
-
-demote_ovnsb() {
-    if test ! -z "$DB_SB_SYNC_FROM_ADDR"; then
-        echo "$DB_SB_SYNC_FROM_PROTO:$DB_SB_SYNC_FROM_ADDR:$DB_SB_SYNC_FROM_PORT" > $ovnsb_active_conf_file
-    fi
-
-    if test -e $ovnsb_active_conf_file; then
-        ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/set-active-ovsdb-server `cat $ovnsb_active_conf_file`
-        ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/connect-active-ovsdb-server
-    else
-        echo >&2 "$0: active server details not set"
-        exit 1
-    fi
-}
-
-promote_ovnnb() {
-    rm -f $ovnnb_active_conf_file
-    ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/disconnect-active-ovsdb-server
-}
-
-promote_ovnsb() {
-    rm -f $ovnsb_active_conf_file
-    ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/disconnect-active-ovsdb-server
-}
-
-start_ovsdb__() {
-    local DB=$1 db=$2 schema_name=$3 table_name=$4
-    local db_pid_file
-    local cluster_local_addr
-    local cluster_local_port
-    local cluster_local_proto
-    local cluster_remote_addr
-    local cluster_remote_port
-    local cluster_remote_proto
-    local sync_from_proto
-    local sync_from_addr
-    local sync_from_port
-    local file
-    local schema
-    local logfile
-    local log
-    local sock
-    local detach
-    local create_insecure_remote
-    local port
-    local addr
-    local active_conf_file
-    local use_remote_in_db
-    local ovn_db_ssl_key
-    local ovn_db_ssl_cert
-    local ovn_db_ssl_cacert
-    eval db_pid_file=\$DB_${DB}_PID
-    eval cluster_local_addr=\$DB_${DB}_CLUSTER_LOCAL_ADDR
-    eval cluster_local_port=\$DB_${DB}_CLUSTER_LOCAL_PORT
-    eval cluster_local_proto=\$DB_${DB}_CLUSTER_LOCAL_PROTO
-    eval cluster_remote_addr=\$DB_${DB}_CLUSTER_REMOTE_ADDR
-    eval cluster_remote_port=\$DB_${DB}_CLUSTER_REMOTE_PORT
-    eval cluster_remote_proto=\$DB_${DB}_CLUSTER_REMOTE_PROTO
-    eval sync_from_proto=\$DB_${DB}_SYNC_FROM_PROTO
-    eval sync_from_addr=\$DB_${DB}_SYNC_FROM_ADDR
-    eval sync_from_port=\$DB_${DB}_SYNC_FROM_PORT
-    eval file=\$DB_${DB}_FILE
-    eval schema=\$DB_${DB}_SCHEMA
-    eval logfile=\$OVN_${DB}_LOGFILE
-    eval log=\$OVN_${DB}_LOG
-    eval sock=\$DB_${DB}_SOCK
-    eval detach=\$DB_${DB}_DETACH
-    eval create_insecure_remote=\$DB_${DB}_CREATE_INSECURE_REMOTE
-    eval port=\$DB_${DB}_PORT
-    eval addr=\$DB_${DB}_ADDR
-    eval active_conf_file=\$ovn${db}_active_conf_file
-    eval use_remote_in_db=\$DB_${DB}_USE_REMOTE_IN_DB
-    eval ovn_db_ssl_key=\$OVN_${DB}_DB_SSL_KEY
-    eval ovn_db_ssl_cert=\$OVN_${DB}_DB_SSL_CERT
-    eval ovn_db_ssl_cacert=\$OVN_${DB}_DB_SSL_CA_CERT
-
-    install_dir "$OVN_RUNDIR"
-    # Check and eventually start ovsdb-server for DB
-    if pidfile_is_running $db_pid_file; then
-        return
-    fi
-
-    if test ! -z "$cluster_local_addr"; then
-        mode=cluster
-    elif test ! -z "$sync_from_addr"; then
-        mode=active_passive
-        echo "$sync_from_proto:$sync_from_addr:\
-$sync_from_port" > $active_conf_file
-    else
-        mode=standalone
-    fi
-
-    if test $mode = cluster; then
-        local local=$cluster_local_proto:$cluster_local_addr:\
-$cluster_local_port
-        local remote=$cluster_remote_proto:$cluster_remote_addr:\
-$cluster_remote_port
-        if test -n "$cluster_remote_addr"; then
-            join_cluster "$file" "$schema_name" "$local" "$remote"
-        else
-            create_cluster "$file" "$schema" "$local"
-        fi
-    else
-        upgrade_db "$file" "$schema"
-    fi
-
-    set ovsdb-server
-    set "$@" $log --log-file=$logfile
-    set "$@" --remote=punix:$sock --pidfile=$db_pid_file
-    set "$@" --unixctl=ovn${db}_db.ctl
-
-    [ "$OVS_USER" != "" ] && set "$@" --user "$OVS_USER"
-
-    if test X"$detach" != Xno; then
-        set "$@" --detach --monitor
-    else
-        set exec "$@"
-    fi
-
-    if test X"$use_remote_in_db" != Xno; then
-        set "$@" --remote=db:$schema_name,$table_name,connections
-    fi
-
-    if test X"$ovn_db_ssl_key" != X; then
-        set "$@" --private-key=$ovn_db_ssl_key
-    else
-        set "$@" --private-key=db:$schema_name,SSL,private_key
-    fi
-    if test X"$ovn_db_ssl_cert" != X; then
-        set "$@" --certificate=$ovn_db_ssl_cert
-    else
-        set "$@" --certificate=db:$schema_name,SSL,certificate
-    fi
-    if test X"$ovn_db_ssl_cacert" != X; then
-        set "$@" --ca-cert=$ovn_db_ssl_cacert
-    else
-        set "$@" --ca-cert=db:$schema_name,SSL,ca_cert
-    fi
-
-    set "$@" --ssl-protocols=db:$schema_name,SSL,ssl_protocols
-    set "$@" --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers
-
-    if test X"$create_insecure_remote" = Xyes; then
-        set "$@" --remote=ptcp:$port:$addr
-    fi
-
-    if test $mode = active_passive; then
-        set "$@" --sync-from=`cat $active_conf_file`
-    fi
-
-    "$@" "$file"
-
-    # Initialize the database if it's running standalone,
-    # active-passive, or is the first server in a cluster.
-    if test -z "$cluster_remote_addr"; then
-        ovn-${db}ctl init
-    fi
-
-    if test $mode = cluster; then
-        upgrade_cluster "$schema" "unix:$sock"
-    fi
-}
-
-start_nb_ovsdb() {
-    start_ovsdb__ NB nb OVN_Northbound NB_Global
-}
-
-start_sb_ovsdb() {
-    # Increase the limit on the number of open file descriptors, because
-    # SB DB may connect to large number of chassises, on top of connections
-    # for cluster members, northd, and serveral local unix sockets.
-    MAXFD=8192
-    if [ $(ulimit -n) -lt $MAXFD ]; then
-        ulimit -n $MAXFD
-    fi
-
-    start_ovsdb__ SB sb OVN_Southbound SB_Global
-}
-
-start_ovsdb () {
-    start_nb_ovsdb
-    start_sb_ovsdb
-}
-
-sync_status() {
-    ovs-appctl -t $OVN_RUNDIR/ovn${1}_db.ctl ovsdb-server/sync-status | awk '{if(NR==1) print $2}'
-}
-
-status_ovnnb() {
-    if ! pidfile_is_running $DB_NB_PID; then
-        echo "not-running"
-    else
-        echo "running/$(sync_status nb)"
-    fi
-}
-
-status_ovnsb() {
-    if ! pidfile_is_running $DB_SB_PID; then
-        echo "not-running"
-    else
-        echo "running/$(sync_status sb)"
-    fi
-}
-
-status_ovsdb () {
-  if ! pidfile_is_running $DB_NB_PID; then
-      log_success_msg "OVN Northbound DB is not running"
-  else
-      log_success_msg "OVN Northbound DB is running"
-  fi
-
-  if ! pidfile_is_running $DB_SB_PID; then
-      log_success_msg "OVN Southbound DB is not running"
-  else
-      log_success_msg "OVN Southbound DB is running"
-  fi
-}
-
-run_nb_ovsdb() {
-    DB_NB_DETACH=no
-    start_nb_ovsdb
-}
-
-run_sb_ovsdb() {
-    DB_SB_DETACH=no
-    start_sb_ovsdb
-}
-
-start_northd () {
-    if [ ! -e $ovn_northd_db_conf_file ]; then
-        if test X"$OVN_MANAGE_OVSDB" = Xyes; then
-            start_ovsdb
-
-            if ! pidfile_is_running $DB_NB_PID; then
-                log_failure_msg "OVN Northbound DB is not running"
-                exit
-            fi
-            if ! pidfile_is_running $DB_SB_PID; then
-                log_failure_msg "OVN Southbound DB is not running"
-                exit
-            fi
-        fi
-        ovn_northd_params="--ovnnb-db=$OVN_NORTHD_NB_DB \
-        --ovnsb-db=$OVN_NORTHD_SB_DB"
-    else
-        ovn_northd_params="`cat $ovn_northd_db_conf_file`"
-    fi
-
-    if daemon_is_running ovn-northd; then
-        log_success_msg "ovn-northd is already running"
-    else
-        set ovn-northd
-        if test X"$OVN_NORTHD_LOGFILE" != X; then
-            set "$@" --log-file=$OVN_NORTHD_LOGFILE
-        fi
-
-        [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER"
-
-        set "$@" $OVN_NORTHD_LOG $ovn_northd_params
-
-        OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_NORTHD_PRIORITY" "$OVN_NORTHD_WRAPPER" "$@"
-    fi
-}
-
-start_controller () {
-    set ovn-controller "unix:$DB_SOCK"
-    set "$@" $OVN_CONTROLLER_LOG
-    if test X"$OVN_CONTROLLER_SSL_KEY" != X; then
-        set "$@" --private-key=$OVN_CONTROLLER_SSL_KEY
-    fi
-    if test X"$OVN_CONTROLLER_SSL_CERT" != X; then
-        set "$@" --certificate=$OVN_CONTROLLER_SSL_CERT
-    fi
-    if test X"$OVN_CONTROLLER_SSL_CA_CERT" != X; then
-        set "$@" --ca-cert=$OVN_CONTROLLER_SSL_CA_CERT
-    fi
-    if test X"$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT" != X; then
-        set "$@" --bootstrap-ca-cert=$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT
-    fi
-
-    [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER"
-
-    OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_CONTROLLER_PRIORITY" "$OVN_CONTROLLER_WRAPPER" "$@"
-}
-
-start_controller_vtep () {
-    set ovn-controller-vtep
-    set "$@" -vconsole:emer -vsyslog:err -vfile:info
-    if test X"$OVN_CONTROLLER_SSL_KEY" != X; then
-        set "$@" --private-key=$OVN_CONTROLLER_SSL_KEY
-    fi
-    if test X"$OVN_CONTROLLER_SSL_CERT" != X; then
-        set "$@" --certificate=$OVN_CONTROLLER_SSL_CERT
-    fi
-    if test X"$OVN_CONTROLLER_SSL_CA_CERT" != X; then
-        set "$@" --ca-cert=$OVN_CONTROLLER_SSL_CA_CERT
-    fi
-    if test X"$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT" != X; then
-        set "$@" --bootstrap-ca-cert=$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT
-    fi
-    if test X"$DB_SOCK" != X; then
-        set "$@" --vtep-db=$DB_SOCK
-    fi
-    if test X"$DB_SB_SOCK" != X; then
-        set "$@" --ovnsb-db=$DB_SB_SOCK
-    fi
-
-    [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER"
-
-    OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_CONTROLLER_PRIORITY" "$OVN_CONTROLLER_WRAPPER" "$@"
-}
-
-## ---- ##
-## stop ##
-## ---- ##
-
-stop_northd () {
-    OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-northd
-
-    if [ ! -e $ovn_northd_db_conf_file ]; then
-        if test X"$OVN_MANAGE_OVSDB" = Xyes; then
-            stop_ovsdb
-        fi
-    fi
-}
-
-stop_controller () {
-    OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-controller "$@"
-}
-
-stop_controller_vtep () {
-    OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-controller-vtep
-}
-
-## ------- ##
-## restart ##
-## ------- ##
-
-restart_northd () {
-    stop_northd
-    start_northd
-}
-
-restart_controller () {
-    stop_controller --restart
-    start_controller
-}
-
-restart_controller_vtep () {
-    stop_controller_vtep
-    start_controller_vtep
-}
-
-restart_ovsdb () {
-    stop_ovsdb
-    start_ovsdb
-}
-
-restart_nb_ovsdb () {
-    stop_nb_ovsdb
-    start_nb_ovsdb
-}
-
-restart_sb_ovsdb () {
-    stop_sb_ovsdb
-    start_sb_ovsdb
-}
-
-## ---- ##
-## main ##
-## ---- ##
-
-set_defaults () {
-    OVN_MANAGE_OVSDB=yes
-
-    OVS_RUNDIR=${OVS_RUNDIR:-${rundir}}
-    OVN_RUNDIR=${OVN_RUNDIR:-${OVS_RUNDIR}}
-
-    DB_NB_SOCK=$OVN_RUNDIR/ovnnb_db.sock
-    DB_NB_PID=$OVN_RUNDIR/ovnnb_db.pid
-    DB_NB_FILE=$dbdir/ovnnb_db.db
-    DB_NB_ADDR=0.0.0.0
-    DB_NB_PORT=6641
-    DB_NB_SYNC_FROM_PROTO=tcp
-    DB_NB_SYNC_FROM_ADDR=
-    DB_NB_SYNC_FROM_PORT=6641
-
-    DB_SB_SOCK=$OVN_RUNDIR/ovnsb_db.sock
-    DB_SB_PID=$OVN_RUNDIR/ovnsb_db.pid
-    DB_SB_FILE=$dbdir/ovnsb_db.db
-    DB_SB_ADDR=0.0.0.0
-    DB_SB_PORT=6642
-    DB_SB_SYNC_FROM_PROTO=tcp
-    DB_SB_SYNC_FROM_ADDR=
-    DB_SB_SYNC_FROM_PORT=6642
-
-    DB_NB_SCHEMA=$datadir/ovn-nb.ovsschema
-    DB_SB_SCHEMA=$datadir/ovn-sb.ovsschema
-
-    DB_SOCK=$OVN_RUNDIR/db.sock
-    DB_CONF_FILE=$dbdir/conf.db
-
-    OVN_NORTHD_PRIORITY=-10
-    OVN_NORTHD_WRAPPER=
-    OVN_CONTROLLER_PRIORITY=-10
-    OVN_CONTROLLER_WRAPPER=
-
-    OVN_USER=
-    OVS_USER=
-
-    OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info"
-    OVN_NORTHD_LOG="-vconsole:emer -vsyslog:err -vfile:info"
-    OVN_NORTHD_LOGFILE=""
-    OVN_NB_LOG="-vconsole:off -vfile:info"
-    OVN_SB_LOG="-vconsole:off -vfile:info"
-    OVN_NB_LOGFILE="$logdir/ovsdb-server-nb.log"
-    OVN_SB_LOGFILE="$logdir/ovsdb-server-sb.log"
-
-    OVN_CONTROLLER_SSL_KEY=""
-    OVN_CONTROLLER_SSL_CERT=""
-    OVN_CONTROLLER_SSL_CA_CERT=""
-    OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT=""
-
-    DB_SB_CREATE_INSECURE_REMOTE="no"
-    DB_NB_CREATE_INSECURE_REMOTE="no"
-
-    MONITOR="yes"
-
-    DB_NB_DETACH="yes"
-    DB_SB_DETACH="yes"
-
-    DB_NB_CLUSTER_LOCAL_ADDR=""
-    DB_NB_CLUSTER_LOCAL_PROTO="tcp"
-    DB_NB_CLUSTER_LOCAL_PORT=6643
-    DB_NB_CLUSTER_REMOTE_ADDR=""
-    DB_NB_CLUSTER_REMOTE_PROTO="tcp"
-    DB_NB_CLUSTER_REMOTE_PORT=6643
-
-    DB_SB_CLUSTER_LOCAL_ADDR=""
-    DB_SB_CLUSTER_LOCAL_PROTO="tcp"
-    DB_SB_CLUSTER_LOCAL_PORT=6644
-    DB_SB_CLUSTER_REMOTE_ADDR=""
-    DB_SB_CLUSTER_REMOTE_PROTO="tcp"
-    DB_SB_CLUSTER_REMOTE_PORT=6644
-
-    OVN_NORTHD_NB_DB="unix:$DB_NB_SOCK"
-    OVN_NORTHD_SB_DB="unix:$DB_SB_SOCK"
-    DB_NB_USE_REMOTE_IN_DB="yes"
-    DB_SB_USE_REMOTE_IN_DB="yes"
-
-    OVN_NB_DB_SSL_KEY=""
-    OVN_NB_DB_SSL_CERT=""
-    OVN_NB_DB_SSL_CA_CERT=""
-
-    OVN_SB_DB_SSL_KEY=""
-    OVN_SB_DB_SSL_CERT=""
-    OVN_SB_DB_SSL_CA_CERT=""
-
-}
-
-set_option () {
-    var=`echo "$option" | tr abcdefghijklmnopqrstuvwxyz- ABCDEFGHIJKLMNOPQRSTUVWXYZ_`
-    eval set=\${$var+yes}
-    eval old_value=\$$var
-    if test X$set = X || \
-        (test $type = bool && \
-        test X"$old_value" != Xno && test X"$old_value" != Xyes); then
-        echo >&2 "$0: unknown option \"$arg\" (use --help for help)"
-        return
-    fi
-    eval $var=\$value
-}
-
-usage () {
-    set_defaults
-    cat << EOF
-$0: controls Open Virtual Network daemons
-usage: $0 [OPTIONS] COMMAND
-
-This program is intended to be invoked internally by Open Virtual Network
-startup scripts.  System administrators should not normally invoke it directly.
-
-Commands:
-  start_northd                start ovn-northd
-  start_ovsdb                 start ovn related ovsdb-server processes
-  start_nb_ovsdb              start ovn northbound db ovsdb-server process
-  start_sb_ovsdb              start ovn southbound db ovsdb-server process
-  start_controller            start ovn-controller
-  start_controller_vtep       start ovn-controller-vtep
-  stop_northd                 stop ovn-northd
-  stop_ovsdb                  stop ovn related ovsdb-server processes
-  stop_nb_ovsdb               stop ovn northbound db ovsdb-server process
-  stop_sb_ovsdb               stop ovn southbound db ovsdb-server process
-  stop_controller             stop ovn-controller
-  stop_controller_vtep        stop ovn-controller-vtep
-  restart_northd              restart ovn-northd
-  restart_ovsdb               restart ovn related ovsdb-server processes
-  restart_nb_ovsdb            restart ovn northbound db ovsdb-server process
-  restart_sb_ovsdb            restart ovn southbound db ovsdb-server process
-  restart_controller          restart ovn-controller
-  restart_controller_vtep     restart ovn-controller-vtep
-  status_northd               status ovs-northd
-  status_ovsdb                status related ovsdb-server processes
-  status_controller           status ovn-controller
-  status_controller_vtep      status ovn-controller-vtep
-  promote_ovnnb               promote ovn northbound db backup server to active
-  promote_ovnsb               promote ovn southbound db backup server to active
-  demote_ovnnb                demote ovn northbound db active server to backup
-  demote_ovnsb                demote ovn southbound db active server to backup
-  run_nb_ovsdb                run ovn northbound db ovsdb-server process
-  run_sb_ovsdb                run ovn southbound db ovsdb-server process
-
-Options:
-  --ovn-northd-priority=NICE     set ovn-northd's niceness (default: $OVN_NORTHD_PRIORITY)
-  --ovn-northd-wrapper=WRAPPER   run with a wrapper like valgrind for debugging
-  --ovn-controller-priority=NICE     set ovn-controller's niceness (default: $OVN_CONTROLLER_PRIORITY)
-  --ovn-controller-wrapper=WRAPPER   run with a wrapper like valgrind for debugging
-  --ovn-controller-ssl-key=KEY OVN Southbound SSL private key file
-  --ovn-controller-ssl-cert=CERT OVN Southbound SSL certificate file
-  --ovn-controller-ssl-ca-cert=CERT OVN Southbound SSL CA certificate file
-  --ovn-controller-ssl-bootstrap-ca-cert=CERT Bootstrapped OVN Southbound SSL CA certificate file
-  --ovn-nb-db-ssl-key=KEY OVN Northbound DB SSL private key file
-  --ovn-nb-db-ssl-cert=CERT OVN Northbound DB SSL certificate file
-  --ovn-nb-db-ssl-ca-cert=CERT OVN Northbound DB SSL CA certificate file
-  --ovn-sb-db-ssl-key=KEY OVN Southbound DB SSL private key file
-  --ovn-sb-db-ssl-cert=CERT OVN Southbound DB SSL certificate file
-  --ovn-sb-db-ssl-ca-cert=CERT OVN Southbound DB SSL CA certificate file
-  --ovn-manage-ovsdb=yes|no        Whether or not the OVN databases should be
-                                   automatically started and stopped along
-                                   with ovn-northd. The default is "yes". If
-                                   this is set to "no", the "start_ovsdb" and
-                                   "stop_ovsdb" commands must be used to start
-                                   and stop the OVN databases.
-  --ovn-controller-log=STRING        ovn controller process logging params (default: $OVN_CONTROLLER_LOG)
-  --ovn-northd-log=STRING            ovn northd process logging params (default: $OVN_NORTHD_LOG)
-  --ovn-northd-logfile=STRING        ovn northd process log file (default: $OVN_NORTHD_LOGFILE)
-  --ovn-nb-log=STRING             ovn NB ovsdb-server processes logging params (default: $OVN_NB_LOG)
-  --ovn-sb-log=STRING             ovn SB ovsdb-server processes logging params (default: $OVN_SB_LOG)
-  --ovn-user="user[:group]"      pass the --user flag to the ovn daemons
-  --ovs-user="user[:group]"      pass the --user flag to ovs daemons
-  -h, --help                     display this help message
-
-File location options:
-  --db-sock=SOCKET     JSON-RPC socket name (default: $DB_SOCK)
-  --db-nb-sock=SOCKET  OVN_Northbound db socket (default: $DB_NB_SOCK)
-  --db-sb-scok=SOCKET  OVN_Southbound db socket (default: $DB_SB_SOCK)
-  --db-nb-file=FILE    OVN_Northbound db file (default: $DB_NB_FILE)
-  --db-sb-file=FILE    OVN_Southbound db file (default: $DB_SB_FILE)
-  --db-nb-schema=FILE  OVN_Northbound db file (default: $DB_NB_SCHEMA)
-  --db-sb-schema=FILE  OVN_Southbound db file (default: $DB_SB_SCHEMA)
-  --db-nb-addr=ADDR    OVN Northbound db ptcp address (default: $DB_NB_ADDR)
-  --db-nb-port=PORT    OVN Northbound db ptcp port (default: $DB_NB_PORT)
-  --db-sb-addr=ADDR    OVN Southbound db ptcp address (default: $DB_SB_ADDR)
-  --db-sb-port=PORT    OVN Southbound db ptcp port (default: $DB_SB_PORT)
-  --ovn-nb-logfile=FILE OVN Northbound log file (default: $OVN_NB_LOGFILE)
-  --ovn-sb-logfile=FILE OVN Southbound log file (default: $OVN_SB_LOGFILE)
-  --db-nb-sync-from-addr=ADDR OVN Northbound active db tcp address (default: $DB_NB_SYNC_FROM_ADDR)
-  --db-nb-sync-from-port=PORT OVN Northbound active db tcp port (default: $DB_NB_SYNC_FROM_PORT)
-  --db-nb-sync-from-proto=PROTO OVN Northbound active db transport (default: $DB_NB_SYNC_FROM_PROTO)
-  --db-nb-create-insecure-remote=yes|no Create ptcp OVN Northbound remote (default: $DB_NB_CREATE_INSECURE_REMOTE)
-  --db-sb-sync-from-addr=ADDR OVN Southbound active db tcp address (default: $DB_SB_SYNC_FROM_ADDR)
-  --db-sb-sync-from-port=ADDR OVN Southbound active db tcp port (default: $DB_SB_SYNC_FROM_PORT)
-  --db-sb-sync-from-proto=PROTO OVN Southbound active db transport (default: $DB_SB_SYNC_FROM_PROTO)
-  --db-sb-create-insecure-remote=yes|no Create ptcp OVN Southbound remote (default: $DB_SB_CREATE_INSECURE_REMOTE)
-  --db-nb-cluster-local-addr=ADDR OVN_Northbound cluster local address \
-  (default: $DB_NB_CLUSTER_LOCAL_ADDR)
-  --db-nb-cluster-local-port=PORT OVN_Northbound cluster local tcp port \
-  (default: $DB_NB_CLUSTER_LOCAL_PORT)
-  --db-nb-cluster-local-proto=PROTO OVN_Northbound cluster local db transport \
-  (default: $DB_NB_CLUSTER_LOCAL_PROTO)
-  --db-nb-cluster-remote-addr=ADDR OVN_Northbound cluster remote address \
-  (default: $DB_NB_CLUSTER_REMOTE_ADDR)
-  --db-nb-cluster-remote-port=PORT OVN_Northbound cluster remote tcp port \
-  (default: $DB_NB_CLUSTER_REMOTE_PORT)
-  --db-nb-cluster-remote-proto=PROTO OVN_Northbound cluster remote db \
-  transport (default: $DB_NB_CLUSTER_REMOTE_PROTO)
-  --db-sb-cluster-local-addr=ADDR OVN_Southbound cluster local address \
-  (default: $DB_SB_CLUSTER_LOCAL_ADDR)
-  --db-sb-cluster-local-port=PORT OVN_Southbound cluster local tcp port \
-  (default: $DB_SB_CLUSTER_LOCAL_PORT)
-  --db-sb-cluster-local-proto=PROTO OVN_Southbound cluster local db transport \
-  (default: $DB_SB_CLUSTER_LOCAL_PROTO)
-  --db-sb-cluster-remote-addr=ADDR OVN_Southbound cluster remote address \
-  (default: $DB_SB_CLUSTER_REMOTE_ADDR)
-  --db-sb-cluster-remote-port=PORT OVN_Southbound cluster remote tcp port \
-  (default: $DB_SB_CLUSTER_REMOTE_PORT)
-  --db-sb-cluster-remote-proto=PROTO OVN_Southbound cluster remote db \
-  transport (default: $DB_SB_CLUSTER_REMOTE_PROTO)
-  --ovn-northd-nb-db=NB DB address(es) (default: $OVN_NORTHD_NB_DB)
-  --ovn-northd-sb-db=SB DB address(es) (default: $OVN_NORTHD_SB_DB)
-  --db-nb-use-remote-in-db=yes|no OVN_Northbound db listen on target connection table (default: $DB_NB_USE_REMOTE_IN_DB)
-  --db-sb-use-remote-in-db=yes|no OVN_Southbound db listen on target connection table (default: $DB_SB_USE_REMOTE_IN_DB)
-
-Default directories with "configure" option and environment variable override:
-  logs: /usr/local/var/log/openvswitch (--with-logdir, OVS_LOGDIR)
-  pidfiles and sockets: /usr/local/var/run/openvswitch (--with-rundir, OVS_RUNDIR)
-  ovn-nb.db: /usr/local/etc/openvswitch (--with-dbdir, OVS_DBDIR)
-  ovn-sb.db: /usr/local/etc/openvswitch (--with-dbdir, OVS_DBDIR)
-  system configuration: /usr/local/etc (--sysconfdir, OVS_SYSCONFDIR)
-  data files: /usr/local/share/openvswitch (--pkgdatadir, OVS_PKGDATADIR)
-  user binaries: /usr/local/bin (--bindir, OVS_BINDIR)
-  system binaries: /usr/local/sbin (--sbindir, OVS_SBINDIR)
-EOF
-}
-
-set_defaults
-command=
-for arg
-do
-    case $arg in
-        -h | --help)
-            usage
-            ;;
-        --[a-z]*=*)
-            option=`expr X"$arg" : 'X--\([^=]*\)'`
-            value=`expr X"$arg" : 'X[^=]*=\(.*\)'`
-            type=string
-            set_option
-            ;;
-        --no-[a-z]*)
-            option=`expr X"$arg" : 'X--no-\(.*\)'`
-            value=no
-            type=bool
-            set_option
-            ;;
-        --[a-z]*)
-            option=`expr X"$arg" : 'X--\(.*\)'`
-            value=yes
-            type=bool
-            set_option
-            ;;
-        -*)
-            echo >&2 "$0: unknown option \"$arg\" (use --help for help)"
-            exit 1
-            ;;
-        *)
-            if test X"$command" = X; then
-                command=$arg
-            else
-                echo >&2 "$0: exactly one non-option argument required (use --help for help)"
-                exit 1
-            fi
-            ;;
-    esac
-done
-case $command in
-    start_northd)
-        start_northd
-        ;;
-    start_ovsdb)
-        start_ovsdb
-        ;;
-    start_nb_ovsdb)
-        start_nb_ovsdb
-        ;;
-    start_sb_ovsdb)
-        start_sb_ovsdb
-        ;;
-    start_controller)
-        start_controller
-        ;;
-    start_controller_vtep)
-        start_controller_vtep
-        ;;
-    stop_northd)
-        stop_northd
-        ;;
-    stop_ovsdb)
-       stop_ovsdb
-        ;;
-    stop_nb_ovsdb)
-       stop_nb_ovsdb
-        ;;
-    stop_sb_ovsdb)
-       stop_sb_ovsdb
-        ;;
-    stop_controller)
-        stop_controller
-        ;;
-    stop_controller_vtep)
-        stop_controller_vtep
-        ;;
-    restart_northd)
-        restart_northd
-        ;;
-    restart_ovsdb)
-        restart_ovsdb
-        ;;
-    restart_nb_ovsdb)
-        restart_nb_ovsdb
-        ;;
-    restart_sb_ovsdb)
-        restart_sb_ovsdb
-        ;;
-    restart_controller)
-        restart_controller
-        ;;
-    restart_controller_vtep)
-        restart_controller_vtep
-        ;;
-    status_northd)
-        daemon_status ovn-northd || exit 1
-        ;;
-    status_ovsdb)
-        status_ovsdb
-        ;;
-    status_controller)
-        daemon_status ovn-controller || exit 1
-        ;;
-    status_controller_vtep)
-        daemon_status ovn-controller-vtep || exit 1
-        ;;
-    promote_ovnnb)
-        promote_ovnnb
-        ;;
-    promote_ovnsb)
-        promote_ovnsb
-        ;;
-    demote_ovnnb)
-        demote_ovnnb
-        ;;
-    demote_ovnsb)
-        demote_ovnsb
-        ;;
-    status_ovnnb)
-        status_ovnnb
-        ;;
-    status_ovnsb)
-        status_ovnsb
-        ;;
-    run_nb_ovsdb)
-        run_nb_ovsdb
-        ;;
-    run_sb_ovsdb)
-        run_sb_ovsdb
-        ;;
-    help)
-        usage
-        ;;
-    preheat)
-        echo >&2 "$0: preheating ovn to 350 degrees F."
-        exit 1
-        ;;
-    '')
-        echo >&2 "$0: missing command name (use --help for help)"
-        exit 1
-        ;;
-    *)
-        echo >&2 "$0: unknown command \"$command\" (use --help for help)"
-        exit 1
-        ;;
-esac
diff --git a/ovn/utilities/ovn-ctl.8.xml b/ovn/utilities/ovn-ctl.8.xml
deleted file mode 100644
index c5294d794..000000000
--- a/ovn/utilities/ovn-ctl.8.xml
+++ /dev/null
@@ -1,215 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-ctl" section="8" title="ovn-ctl">
-    <h1>Name</h1>
-    <p>ovn-ctl -- Open Virtual Network northbound daemon lifecycle utility</p>
-
-    <h1>Synopsis</h1>
-    <p><code>ovn-ctl</code> [<var>options</var>] <var>command</var></p>
-
-    <h1>Description</h1>
-    <p>This program is intended to be invoked internally by Open Virtual Network
-    startup scripts.  System administrators should not normally invoke it directly.</p>
-
-    <h1>Commands</h1>
-
-    <dl>
-      <dt><code>start_northd</code></dt>
-      <dt><code>start_controller</code></dt>
-      <dt><code>start_controller_vtep</code></dt>
-      <dt><code>stop_northd</code></dt>
-      <dt><code>stop_controller</code></dt>
-      <dt><code>stop_controller_vtep</code></dt>
-      <dt><code>restart_northd</code></dt>
-      <dt><code>restart_controller</code></dt>
-      <dt><code>restart_controller_vtep</code></dt>
-      <dt><code>promote_ovnnb</code></dt>
-      <dt><code>promote_ovnsb</code></dt>
-      <dt><code>demote_ovnnb</code></dt>
-      <dt><code>demote_ovnsb</code></dt>
-      <dt><code>status_ovnnb</code></dt>
-      <dt><code>status_ovnsb</code></dt>
-      <dt><code>start_ovsdb</code></dt>
-      <dt><code>start_nb_ovsdb</code></dt>
-      <dt><code>start_sb_ovsdb</code></dt>
-      <dt><code>stop_ovsdb</code></dt>
-      <dt><code>stop_nb_ovsdb</code></dt>
-      <dt><code>stop_sb_ovsdb</code></dt>
-      <dt><code>restart_ovsdb</code></dt>
-      <dt><code>run_nb_ovsdb</code></dt>
-      <dt><code>run_sb_ovsdb</code></dt>
-    </dl>
-
-    <h1>Options</h1>
-    <p><code>--ovn-northd-priority=<var>NICE</var></code></p>
-    <p><code>--ovn-northd-wrapper=<var>WRAPPER</var></code></p>
-    <p><code>--ovn-controller-priority=<var>NICE</var></code></p>
-    <p><code>--ovn-controller-wrapper=<var>WRAPPER</var></code></p>
-    <p><code>--ovn-user=<var>USER:GROUP</var></code></p>
-    <p><code>--ovs-user=<var>USER:GROUP</var></code></p>
-    <p><code>-h</code> | <code>--help</code></p>
-
-    <h1>File location options</h1>
-    <p><code>--db-sock=<var>SOCKET</var></code></p>
-    <p><code>--db-nb-file=<var>FILE</var></code></p>
-    <p><code>--db-sb-file=<var>FILE</var></code></p>
-    <p><code>--db-nb-schema=<var>FILE</var></code></p>
-    <p><code>--db-sb-schema=<var>FILE</var></code></p>
-    <p><code>--db-sb-create-insecure-remote=<var>yes|no</var></code></p>
-    <p><code>--db-nb-create-insecure-remote=<var>yes|no</var></code></p>
-    <p><code>--ovn-controller-ssl-key=<var>KEY</var></code></p>
-    <p><code>--ovn-controller-ssl-cert=<var>CERT</var></code></p>
-    <p><code>--ovn-controller-ssl-ca-cert=<var>CERT</var></code></p>
-    <p><code>--ovn-controller-ssl-bootstrap-ca-cert=<var>CERT</var></code></p>
-
-    <h1>Address and port options</h1>
-    <p><code>--db-nb-sync-from-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-nb-sync-from-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-nb-sync-from-proto=<var>PROTO</var></code></p>
-    <p><code>--db-sb-sync-from-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-sb-sync-from-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-sb-sync-from-proto=<var>PROTO</var></code></p>
-    <p>
-      <code>
-        --ovn-northd-nb-db=<var>PROTO</var>:<var>IP ADDRESS</var>:
-        <var>PORT</var>..
-      </code>
-    </p>
-    <p>
-      <code>
-        --ovn-northd-sb-db=<var>PROTO</var>:<var>IP ADDRESS</var>:
-        <var>PORT</var>..
-      </code>
-    </p>
-    <h1> Clustering options </h1>
-    <p><code>--db-nb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-nb-cluster-local-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-nb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
-    <p><code>--db-nb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-nb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-nb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
-    <p><code>--db-sb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-sb-cluster-local-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-sb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
-    <p><code>--db-sb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
-    <p><code>--db-sb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
-    <p><code>--db-sb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
-
-    <h1>Configuration files</h1>
-    <p>Following are the optional configuration files. If present, it should be located in the etc dir</p>
-
-    <h2>ovnnb-active.conf</h2>
-    <p>
-    If present, this file should hold the url to connect to the active
-    Northbound DB server
-    </p>
-    <p><code>tcp:x.x.x.x:6641</code></p>
-
-    <h2>ovnsb-active.conf</h2>
-    <p>
-    If present, this file should hold the url to connect to the active
-    Southbound DB server
-    </p>
-    <p><code>tcp:x.x.x.x:6642</code></p>
-
-    <h2>ovn-northd-db-params.conf</h2>
-    <p>
-    If present, start_northd will not start the DB server even if
-    <code>--ovn-manage-ovsdb=yes</code>. This file should hold the database url
-    parameters to be passed to ovn-northd.
-    </p>
-    <p><code>--ovnnb-db=tcp:x.x.x.x:6641 --ovnsb-db=tcp:x.x.x.x:6642</code></p>
-
-    <h1> Running OVN db servers without detaching </h1>
-    <p><code># ovn-ctl run_nb_ovsdb</code></p>
-    <p>
-      This command runs the OVN nb ovsdb-server without passing the
-      <code>detach</code> option, making it to block until ovsdb-server exits.
-      This command will be useful for starting the OVN nb ovsdb-server in a
-      container.
-    </p>
-    <p><code># ovn-ctl run_sb_ovsdb</code></p>
-    <p>
-      This command runs the OVN sb ovsdb-server without passing the
-      <code>detach</code> option, making it to block until ovsdb-server exits.
-      This command will be useful for starting the OVN sb ovsdb-server in a
-      container.
-    </p>
-
-    <h1>Example Usage</h1>
-    <h2>Run ovn-controller on a host already running OVS</h2>
-    <p><code># ovn-ctl start_controller</code></p>
-
-    <h2>Run ovn-northd on a host already running OVS</h2>
-    <p><code># ovn-ctl start_northd</code></p>
-
-    <h2>All-in-one OVS+OVN for testing</h2>
-    <p><code># ovs-ctl start --system-id="random"</code></p>
-    <p><code># ovn-ctl start_northd</code></p>
-    <p><code># ovn-ctl start_controller</code></p>
-
-    <h2>Promote and demote ovsdb servers</h2>
-    <p><code># ovn-ctl promote_ovnnb</code></p>
-    <p><code># ovn-ctl promote_ovnsb</code></p>
-    <p><code># ovn-ctl --db-nb-sync-from-addr=x.x.x.x --db-nb-sync-from-port=6641 demote_ovnnb</code></p>
-    <p><code># ovn-ctl --db-sb-sync-from-addr=x.x.x.x --db-sb-sync-from-port=6642 demote_ovnsb</code></p>
-
-    <h2>Creating a clustered db on 3 nodes with IPs x.x.x.x, y.y.y.y and z.z.z.z</h2>
-    <h3>Starting OVN ovsdb servers and ovn-northd on the node with IP x.x.x.x</h3>
-    <p>
-      <code>
-        # ovn-ctl --db-nb-addr=x.x.x.x --db-nb-create-insecure-remote=yes
-        --db-sb-addr=x.x.x.x --db-sb-create-insecure-remote=yes
-        --db-nb-cluster-local-addr=x.x.x.x
-        --db-sb-cluster-local-addr=x.x.x.x
-        --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641
-        --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642
-        start_northd
-      </code>
-    </p>
-
-    <h3>Starting OVN ovsdb-servers and ovn-northd on the node with IP y.y.y.y and joining the cluster started at x.x.x.x</h3>
-    <p>
-      <code>
-        # ovn-ctl --db-nb-addr=y.y.y.y --db-nb-create-insecure-remote=yes
-        --db-sb-addr=y.y.y.y --db-sb-create-insecure-remote=yes
-        --db-nb-cluster-local-addr=y.y.y.y
-        --db-sb-cluster-local-addr=y.y.y.y
-        --db-nb-cluster-remote-addr=x.x.x.x
-        --db-sb-cluster-remote-addr=x.x.x.x
-        --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641
-        --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642
-        start_northd
-      </code>
-    </p>
-
-    <h3>Starting OVN ovsdb-servers and ovn-northd on the node with IP z.z.z.z and joining the cluster started at x.x.x.x</h3>
-    <p>
-      <code>
-        # ovn-ctl --db-nb-addr=z.z.z.z
-          --db-nb-create-insecure-remote=yes
-          --db-nb-cluster-local-addr=z.z.z.z
-          --db-sb-addr=z.z.z.z
-          --db-sb-create-insecure-remote=yes
-          --db-sb-cluster-local-addr=z.z.z.z
-          --db-nb-cluster-remote-addr=x.x.x.x
-          --db-sb-cluster-remote-addr=x.x.x.x
-          --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641
-          --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642
-          start_northd
-      </code>
-    </p>
-
-    <h2>Passing ssl keys when starting OVN dbs will supercede the default ssl values in db</h2>
-    <h3>Starting standalone ovn db server passing SSL certificates</h3>
-    <p>
-      <code>
-        # ovn-ctl --ovn-nb-db-ssl-key=/etc/openvswitch/ovnnb-privkey.pem
-          --ovn-nb-db-ssl-cert=/etc/openvswitch/ovnnb-cert.pem
-          --ovn-nb-db-ssl-ca-cert=/etc/openvswitch/cacert.pem
-          --ovn-sb-db-ssl-key=/etc/openvswitch/ovnsb-privkey.pem
-          --ovn-sb-db-ssl-cert=/etc/openvswitch/ovnsb-cert.pem
-          --ovn-sb-db-ssl-ca-cert=/etc/openvswitch/cacert.pem
-           start_northd
-      </code>
-    </p>
-</manpage>
diff --git a/ovn/utilities/ovn-detrace.1.in b/ovn/utilities/ovn-detrace.1.in
deleted file mode 100644
index 2f662d4fe..000000000
--- a/ovn/utilities/ovn-detrace.1.in
+++ /dev/null
@@ -1,38 +0,0 @@
-.so lib/ovs.tmac
-.TH ovn\-detrace 1 "@VERSION@" "Open vSwitch" "Open vSwitch Manual"
-.
-.SH NAME
-ovn\-detrace \- convert ``ovs\-appctl ofproto/trace'' output to combine
-OVN logical flow information.
-.
-.SH SYNOPSIS
-\fBovn\-detrace < \fIfile\fR
-.so lib/common-syn.man
-.
-.SH DESCRIPTION
-The \fBovn\-detrace\fR program reads \fBovs\-appctl ofproto/trace\fR output on
-stdin, looking for flow cookies, and expand each cookie with corresponding OVN
-logical flows. It expands logical flow further with the north-bound information
-e.g. the ACL that generated the logical flow, when relevant.
-.PP
-.
-.SH "OPTIONS"
-.so lib/common.man
-.
-.IP "\fB\-\-ovnsb=\fIserver\fR"
-The OVN Southbound DB remote to contact.  If the \fBOVN_SB_DB\fR
-environment variable is set, its value is used as the default.
-Otherwise, the default is \fBunix:@RUNDIR@/ovnsb_db.sock\fR, but this
-default is unlikely to be useful outside of single-machine OVN test
-environments.
-.
-.IP "\fB\-\-ovnnb=\fIserver\fR"
-The OVN Northbound DB remote to contact.  If the \fBOVN_NB_DB\fR
-environment variable is set, its value is used as the default.
-Otherwise, the default is \fBunix:@RUNDIR@/ovnnb_db.sock\fR, but this
-default is unlikely to be useful outside of single-machine OVN test
-environments.
-.
-.SH "SEE ALSO"
-.
-.BR ovs\-appctl (8), ovn\-sbctl (8), ovn-\-nbctl (8), ovn\-trace (8)
diff --git a/ovn/utilities/ovn-detrace.in b/ovn/utilities/ovn-detrace.in
deleted file mode 100755
index c842adc32..000000000
--- a/ovn/utilities/ovn-detrace.in
+++ /dev/null
@@ -1,215 +0,0 @@
-#! @PYTHON@
-#
-# Copyright (c) 2017 eBay 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.
-
-import getopt
-import os
-import re
-import sys
-import time
-
-try:
-    from ovs.db import idl
-    from ovs import jsonrpc
-    from ovs.poller import Poller
-    from ovs.stream import Stream
-except Exception:
-    print("ERROR: Please install the correct Open vSwitch python support")
-    print("       libraries (@VERSION@).")
-    print("       Alternatively, check that your PYTHONPATH is pointing to")
-    print("       the correct location.")
-    sys.exit(1)
-
-
-argv0 = sys.argv[0]
-
-
-def usage():
-    print """\
-%(argv0)s:
-usage: %(argv0)s < FILE
-where FILE is output from ovs-appctl ofproto/trace.
-
-The following options are also available:
-  -h, --help                  display this help message
-  -V, --version               display version information
-  --ovnsb=DATABASE            use DATABASE as southbound DB
-  --ovnnb=DATABASE            use DATABASE as northbound DB\
-""" % {'argv0': argv0}
-    sys.exit(0)
-
-
-class OVSDB(object):
-    @staticmethod
-    def wait_for_db_change(idl):
-        seq = idl.change_seqno
-        stop = time.time() + 10
-        while idl.change_seqno == seq and not idl.run():
-            poller = Poller()
-            idl.wait(poller)
-            poller.block()
-            if time.time() >= stop:
-                raise Exception('Retry Timeout')
-
-    def __init__(self, db_sock, schema_name):
-        self._db_sock = db_sock
-        self._txn = None
-        schema = self._get_schema(schema_name)
-        schema.register_all()
-        self._idl_conn = idl.Idl(db_sock, schema)
-        OVSDB.wait_for_db_change(self._idl_conn)  # Initial Sync with DB
-
-    def _get_schema(self, schema_name):
-        error, strm = Stream.open_block(Stream.open(self._db_sock))
-        if error:
-            raise Exception("Unable to connect to %s" % self._db_sock)
-        rpc = jsonrpc.Connection(strm)
-        req = jsonrpc.Message.create_request('get_schema', [schema_name])
-        error, resp = rpc.transact_block(req)
-        rpc.close()
-
-        if error or resp.error:
-            raise Exception('Unable to retrieve schema.')
-        return idl.SchemaHelper(None, resp.result)
-
-    def get_table(self, table_name):
-        return self._idl_conn.tables[table_name]
-
-    def _find_row(self, table_name, find):
-        return next(
-            (row for row in self.get_table(table_name).rows.values()
-             if find(row)), None)
-
-    def _find_row_by_name(self, table_name, value):
-        return self._find_row(table_name, lambda row: row.name == value)
-
-    def find_row_by_partial_uuid(self, table_name, value):
-        return self._find_row(table_name, lambda row: value in str(row.uuid))
-
-
-def get_lflow_from_cookie(ovnsb_db, cookie):
-    return ovnsb_db.find_row_by_partial_uuid('Logical_Flow', cookie)
-
-
-def print_lflow(lflow, prefix):
-    ldp_uuid = lflow.logical_datapath.uuid
-    ldp_name = str(lflow.logical_datapath.external_ids.get('name'))
-
-    print '%sLogical datapath: "%s" (%s) [%s]' % (prefix,
-                                                  ldp_name,
-                                                  ldp_uuid,
-                                                  lflow.pipeline)
-    print "%sLogical flow: table=%s (%s), priority=%s, " \
-          "match=(%s), actions=(%s)" % (prefix,
-                                        lflow.table_id,
-                                        lflow.external_ids.get('stage-name'),
-                                        lflow.priority,
-                                        str(lflow.match).strip('"'),
-                                        str(lflow.actions).strip('"'))
-
-
-def print_lflow_nb_hint(lflow, prefix, ovnnb_db):
-    external_ids = lflow.external_ids
-    if external_ids.get('stage-name') in ['ls_in_acl',
-                                          'ls_out_acl']:
-        acl_hint = external_ids.get('stage-hint')
-        if not acl_hint:
-            return
-        acl = ovnnb_db.find_row_by_partial_uuid('ACL', acl_hint)
-        if not acl:
-            return
-        output = "%sACL: %s, priority=%s, " \
-                 "match=(%s), %s" % (prefix,
-                                     acl.direction,
-                                     acl.priority,
-                                     acl.match.strip('"'),
-                                     acl.action)
-        if acl.log:
-            output += ' (log)'
-        print output
-
-
-def main():
-    try:
-        options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
-                                          ['help', 'version', 'ovnsb=', 'ovnnb='])
-    except getopt.GetoptError, geo:
-        sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
-        sys.exit(1)
-
-    ovnsb_db = None
-    ovnnb_db = None
-
-    for key, value in options:
-        if key in ['-h', '--help']:
-            usage()
-        elif key in ['-V', '--version']:
-            print "%s (Open vSwitch) @VERSION@" % argv0
-        elif key in ['--ovnsb']:
-            ovnsb_db = value
-        elif key in ['--ovnnb']:
-            ovnnb_db = value
-        else:
-            sys.exit(0)
-
-    if len(args) != 0:
-        sys.stderr.write("%s: non-option argument not supported "
-                         "(use --help for help)\n" % argv0)
-        sys.exit(1)
-
-    ovs_rundir = os.getenv('OVS_RUNDIR', '@RUNDIR@')
-    if not ovnsb_db:
-        ovnsb_db = os.getenv('OVN_SB_DB')
-        if not ovnsb_db:
-            ovnsb_db = 'unix:%s/ovnsb_db.sock' % ovs_rundir
-
-    if not ovnnb_db:
-        ovnnb_db = os.getenv('OVN_NB_DB')
-        if not ovnnb_db:
-            ovnnb_db = 'unix:%s/ovnnb_db.sock' % ovs_rundir
-
-    ovsdb_ovnsb = OVSDB(ovnsb_db, 'OVN_Southbound')
-    ovsdb_ovnnb = OVSDB(ovnnb_db, 'OVN_Northbound')
-
-    regex_cookie = re.compile(r'^.*cookie 0x([0-9a-fA-F]+)')
-    regex_table_id = re.compile(r'^[0-9]+\.')
-    cookie = None
-    while True:
-        line = sys.stdin.readline()
-        if cookie:
-            # print lflow info when the current flow block ends
-            if regex_table_id.match(line) or line.strip() == '':
-                lflow = get_lflow_from_cookie(ovsdb_ovnsb, cookie)
-                print_lflow(lflow, "  * ")
-                print_lflow_nb_hint(lflow, "    * ", ovsdb_ovnnb)
-                cookie = None
-
-        print line.strip()
-        if line == "":
-            break
-
-        m = regex_cookie.match(line)
-        if not m:
-            continue
-        cookie = m.group(1)
-
-
-if __name__ == "__main__":
-    main()
-
-
-# Local variables:
-# mode: python
-# End:
diff --git a/ovn/utilities/ovn-docker-overlay-driver.in b/ovn/utilities/ovn-docker-overlay-driver.in
deleted file mode 100755
index 65edfcd9d..000000000
--- a/ovn/utilities/ovn-docker-overlay-driver.in
+++ /dev/null
@@ -1,442 +0,0 @@
-#! @PYTHON@
-# 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.
-
-import argparse
-import ast
-import atexit
-import json
-import os
-import random
-import re
-import shlex
-import subprocess
-import sys
-
-import ovs.dirs
-import ovs.util
-import ovs.daemon
-import ovs.vlog
-
-from flask import Flask, jsonify
-from flask import request, abort
-
-app = Flask(__name__)
-vlog = ovs.vlog.Vlog("ovn-docker-overlay-driver")
-
-OVN_BRIDGE = "br-int"
-OVN_NB = ""
-PLUGIN_DIR = "/etc/docker/plugins"
-PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
-
-
-def call_popen(cmd):
-    child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-    output = child.communicate()
-    if child.returncode:
-        raise RuntimeError("Fatal error executing %s" % (cmd))
-    if len(output) == 0 or output[0] == None:
-        output = ""
-    else:
-        output = output[0].strip()
-    return output
-
-
-def call_prog(prog, args_list):
-    cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
-    return call_popen(cmd)
-
-
-def ovs_vsctl(*args):
-    return call_prog("ovs-vsctl", list(args))
-
-
-def ovn_nbctl(*args):
-    args_list = list(args)
-    database_option = "%s=%s" % ("--db", OVN_NB)
-    args_list.insert(0, database_option)
-    return call_prog("ovn-nbctl", args_list)
-
-
-def cleanup():
-    if os.path.isfile(PLUGIN_FILE):
-        os.remove(PLUGIN_FILE)
-
-
-def ovn_init_overlay():
-    br_list = ovs_vsctl("list-br").split()
-    if OVN_BRIDGE not in br_list:
-        ovs_vsctl("--", "--may-exist", "add-br", OVN_BRIDGE,
-                  "--", "set", "bridge", OVN_BRIDGE,
-                  "external_ids:bridge-id=" + OVN_BRIDGE,
-                  "other-config:disable-in-band=true", "fail-mode=secure")
-
-    global OVN_NB
-    OVN_NB = ovs_vsctl("get", "Open_vSwitch", ".",
-                           "external_ids:ovn-nb").strip('"')
-    if not OVN_NB:
-        sys.exit("OVN central database's ip address not set")
-
-    ovs_vsctl("set", "open_vswitch", ".",
-              "external_ids:ovn-bridge=" + OVN_BRIDGE)
-
-
-def prepare():
-    parser = argparse.ArgumentParser()
-
-    ovs.vlog.add_args(parser)
-    ovs.daemon.add_args(parser)
-    args = parser.parse_args()
-    ovs.vlog.handle_args(args)
-    ovs.daemon.handle_args(args)
-    ovn_init_overlay()
-
-    if not os.path.isdir(PLUGIN_DIR):
-        os.makedirs(PLUGIN_DIR)
-
-    ovs.daemon.daemonize()
-    try:
-        fo = open(PLUGIN_FILE, "w")
-        fo.write("tcp://0.0.0.0:5000")
-        fo.close()
-    except Exception as e:
-        ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
-                           vlog)
-
-    atexit.register(cleanup)
-
-
-@app.route('/Plugin.Activate', methods=['POST'])
-def plugin_activate():
-    return jsonify({"Implements": ["NetworkDriver"]})
-
-
-@app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
-def get_capability():
-    return jsonify({"Scope": "global"})
-
-
-@app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
-def new_discovery():
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
-def delete_discovery():
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
-def create_network():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    # NetworkID will have docker generated network uuid and it
-    # becomes 'name' in a OVN Logical switch record.
-    network = data.get("NetworkID", "")
-    if not network:
-        abort(400)
-
-    # Limit subnet handling to ipv4 till ipv6 usecase is clear.
-    ipv4_data = data.get("IPv4Data", "")
-    if not ipv4_data:
-        error = "create_network: No ipv4 subnet provided"
-        return jsonify({'Err': error})
-
-    subnet = ipv4_data[0].get("Pool", "")
-    if not subnet:
-        error = "create_network: no subnet in ipv4 data from libnetwork"
-        return jsonify({'Err': error})
-
-    gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
-    if not gateway_ip:
-        error = "create_network: no gateway in ipv4 data from libnetwork"
-        return jsonify({'Err': error})
-
-    try:
-        ovn_nbctl("ls-add", network, "--", "set", "Logical_Switch",
-                  network, "external_ids:subnet=" + subnet,
-                  "external_ids:gateway_ip=" + gateway_ip)
-    except Exception as e:
-        error = "create_network: ls-add %s" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
-def delete_network():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    try:
-        ovn_nbctl("ls-del", nid)
-    except Exception as e:
-        error = "delete_network: ls-del %s" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
-def create_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    interface = data.get("Interface", "")
-    if not interface:
-        error = "create_endpoint: no interfaces structure supplied by " \
-                "libnetwork"
-        return jsonify({'Err': error})
-
-    ip_address_and_mask = interface.get("Address", "")
-    if not ip_address_and_mask:
-        error = "create_endpoint: ip address not provided by libnetwork"
-        return jsonify({'Err': error})
-
-    ip_address = ip_address_and_mask.rsplit('/', 1)[0]
-    mac_address_input = interface.get("MacAddress", "")
-    mac_address_output = ""
-
-    try:
-        ovn_nbctl("lsp-add", nid, eid)
-    except Exception as e:
-        error = "create_endpoint: lsp-add (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    if not mac_address_input:
-        mac_address = "02:%02x:%02x:%02x:%02x:%02x" % (random.randint(0, 255),
-                                                       random.randint(0, 255),
-                                                       random.randint(0, 255),
-                                                       random.randint(0, 255),
-                                                       random.randint(0, 255))
-    else:
-        mac_address = mac_address_input
-
-    try:
-        ovn_nbctl("lsp-set-addresses", eid,
-                  mac_address + " " + ip_address)
-    except Exception as e:
-        error = "create_endpoint: lsp-set-addresses (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    # Only return a mac address if one did not come as request.
-    mac_address_output = ""
-    if not mac_address_input:
-        mac_address_output = mac_address
-
-    return jsonify({"Interface": {
-                                    "Address": "",
-                                    "AddressIPv6": "",
-                                    "MacAddress": mac_address_output
-                                    }})
-
-
-def get_lsp_addresses(eid):
-    ret = ovn_nbctl("--if-exists", "get", "Logical_Switch_Port", eid,
-                    "addresses")
-    if not ret:
-        error = "endpoint not found in OVN database"
-        return (None, None, error)
-    addresses = ast.literal_eval(ret)
-    if len(addresses) == 0:
-        error = "unexpected return while fetching addresses"
-        return (None, None, error)
-    (mac_address, ip_address) = addresses[0].split()
-    return (mac_address, ip_address, None)
-
-
-@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
-def show_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    try:
-        (mac_address, ip_address, error) = get_lsp_addresses(eid)
-        if error:
-            jsonify({'Err': error})
-    except Exception as e:
-        error = "show_endpoint: get Logical_Switch_Port addresses. (%s)" \
-                % (str(e))
-        return jsonify({'Err': error})
-
-    veth_outside = eid[0:15]
-    return jsonify({"Value": {"ip_address": ip_address,
-                              "mac_address": mac_address,
-                              "veth_outside": veth_outside
-                              }})
-
-
-@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
-def delete_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    try:
-        ovn_nbctl("lsp-del", eid)
-    except Exception as e:
-        error = "delete_endpoint: lsp-del %s" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.Join', methods=['POST'])
-def network_join():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    sboxkey = data.get("SandboxKey", "")
-    if not sboxkey:
-        abort(400)
-
-    # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
-    vm_id = sboxkey.rsplit('/')[-1]
-
-    try:
-        (mac_address, ip_address, error) = get_lsp_addresses(eid)
-        if error:
-            jsonify({'Err': error})
-    except Exception as e:
-        error = "network_join: %s" % (str(e))
-        return jsonify({'Err': error})
-
-    veth_outside = eid[0:15]
-    veth_inside = eid[0:13] + "_c"
-    command = "ip link add %s type veth peer name %s" \
-              % (veth_inside, veth_outside)
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to create veth pair (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    command = "ip link set dev %s address %s" \
-              % (veth_inside, mac_address)
-
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to set veth mac address (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    command = "ip link set %s up" % (veth_outside)
-
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to up the veth interface (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ovs_vsctl("add-port", OVN_BRIDGE, veth_outside)
-        ovs_vsctl("set", "interface", veth_outside,
-                  "external_ids:attached-mac=" + mac_address,
-                  "external_ids:iface-id=" + eid,
-                  "external_ids:vm-id=" + vm_id,
-                  "external_ids:iface-status=active")
-    except Exception as e:
-        error = "network_join: failed to create a port (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({"InterfaceName": {
-                                        "SrcName": veth_inside,
-                                        "DstPrefix": "eth"
-                                     },
-                    "Gateway": "",
-                    "GatewayIPv6": ""})
-
-
-@app.route('/NetworkDriver.Leave', methods=['POST'])
-def network_leave():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    veth_outside = eid[0:15]
-    command = "ip link delete %s" % (veth_outside)
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_leave: failed to delete veth pair (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ovs_vsctl("--if-exists", "del-port", veth_outside)
-    except Exception as e:
-        error = "network_leave: failed to delete port (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-if __name__ == '__main__':
-    prepare()
-    app.run(host='0.0.0.0')
diff --git a/ovn/utilities/ovn-docker-underlay-driver.in b/ovn/utilities/ovn-docker-underlay-driver.in
deleted file mode 100755
index d91ce9fca..000000000
--- a/ovn/utilities/ovn-docker-underlay-driver.in
+++ /dev/null
@@ -1,677 +0,0 @@
-#! @PYTHON@
-# 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.
-
-import argparse
-import atexit
-import getpass
-import json
-import os
-import re
-import shlex
-import subprocess
-import sys
-import time
-import uuid
-
-import ovs.dirs
-import ovs.util
-import ovs.daemon
-import ovs.unixctl.server
-import ovs.vlog
-
-from neutronclient.v2_0 import client
-from flask import Flask, jsonify
-from flask import request, abort
-
-app = Flask(__name__)
-vlog = ovs.vlog.Vlog("ovn-docker-underlay-driver")
-
-AUTH_STRATEGY = ""
-AUTH_URL = ""
-ENDPOINT_URL = ""
-OVN_BRIDGE = ""
-PASSWORD = ""
-PLUGIN_DIR = "/etc/docker/plugins"
-PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
-TENANT_ID = ""
-USERNAME = ""
-VIF_ID = ""
-
-
-def call_popen(cmd):
-    child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-    output = child.communicate()
-    if child.returncode:
-        raise RuntimeError("Fatal error executing %s" % (cmd))
-    if len(output) == 0 or output[0] == None:
-        output = ""
-    else:
-        output = output[0].strip()
-    return output
-
-
-def call_prog(prog, args_list):
-    cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
-    return call_popen(cmd)
-
-
-def ovs_vsctl(*args):
-    return call_prog("ovs-vsctl", list(args))
-
-
-def cleanup():
-    if os.path.isfile(PLUGIN_FILE):
-        os.remove(PLUGIN_FILE)
-
-
-def ovn_init_underlay(args):
-    global USERNAME, PASSWORD, TENANT_ID, AUTH_URL, AUTH_STRATEGY, VIF_ID
-    global OVN_BRIDGE
-
-    if not args.bridge:
-        sys.exit("OVS bridge name not provided")
-    OVN_BRIDGE = args.bridge
-
-    VIF_ID = os.environ.get('OS_VIF_ID', '')
-    if not VIF_ID:
-        sys.exit("env OS_VIF_ID not set")
-    USERNAME = os.environ.get('OS_USERNAME', '')
-    if not USERNAME:
-        sys.exit("env OS_USERNAME not set")
-    TENANT_ID = os.environ.get('OS_TENANT_ID', '')
-    if not TENANT_ID:
-        sys.exit("env OS_TENANT_ID not set")
-    AUTH_URL = os.environ.get('OS_AUTH_URL', '')
-    if not AUTH_URL:
-        sys.exit("env OS_AUTH_URL not set")
-    AUTH_STRATEGY = "keystone"
-
-    PASSWORD = os.environ.get('OS_PASSWORD', '')
-    if not PASSWORD:
-        PASSWORD = getpass.getpass()
-
-
-def prepare():
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--bridge', help="The Bridge to which containers "
-                        "interfaces connect to.")
-
-    ovs.vlog.add_args(parser)
-    ovs.daemon.add_args(parser)
-    args = parser.parse_args()
-    ovs.vlog.handle_args(args)
-    ovs.daemon.handle_args(args)
-    ovn_init_underlay(args)
-
-    if not os.path.isdir(PLUGIN_DIR):
-        os.makedirs(PLUGIN_DIR)
-
-    ovs.daemon.daemonize()
-    try:
-        fo = open(PLUGIN_FILE, "w")
-        fo.write("tcp://127.0.0.1:5000")
-        fo.close()
-    except Exception as e:
-        ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
-                           vlog)
-
-    atexit.register(cleanup)
-
-
-@app.route('/Plugin.Activate', methods=['POST'])
-def plugin_activate():
-    return jsonify({"Implements": ["NetworkDriver"]})
-
-
-@app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
-def get_capability():
-    return jsonify({"Scope": "global"})
-
-
-@app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
-def new_discovery():
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
-def delete_discovery():
-    return jsonify({})
-
-
-def neutron_login():
-    try:
-        neutron = client.Client(username=USERNAME,
-                                password=PASSWORD,
-                                tenant_id=TENANT_ID,
-                                auth_url=AUTH_URL,
-                                endpoint_url=ENDPOINT_URL,
-                                auth_strategy=AUTH_STRATEGY)
-    except Exception as e:
-        raise RuntimeError("Failed to login into Neutron(%s)" % str(e))
-    return neutron
-
-
-def get_networkuuid_by_name(neutron, name):
-    param = {'fields': 'id', 'name': name}
-    ret = neutron.list_networks(**param)
-    if len(ret['networks']) > 1:
-        raise RuntimeError("More than one network for the given name")
-    elif len(ret['networks']) == 0:
-        network = None
-    else:
-        network = ret['networks'][0]['id']
-    return network
-
-
-def get_subnetuuid_by_name(neutron, name):
-    param = {'fields': 'id', 'name': name}
-    ret = neutron.list_subnets(**param)
-    if len(ret['subnets']) > 1:
-        raise RuntimeError("More than one subnet for the given name")
-    elif len(ret['subnets']) == 0:
-        subnet = None
-    else:
-        subnet = ret['subnets'][0]['id']
-    return subnet
-
-
-@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
-def create_network():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    # NetworkID will have docker generated network uuid and it
-    # becomes 'name' in a neutron network record.
-    network = data.get("NetworkID", "")
-    if not network:
-        abort(400)
-
-    # Limit subnet handling to ipv4 till ipv6 usecase is clear.
-    ipv4_data = data.get("IPv4Data", "")
-    if not ipv4_data:
-        error = "create_network: No ipv4 subnet provided"
-        return jsonify({'Err': error})
-
-    subnet = ipv4_data[0].get("Pool", "")
-    if not subnet:
-        error = "create_network: no subnet in ipv4 data from libnetwork"
-        return jsonify({'Err': error})
-
-    gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
-    if not gateway_ip:
-        error = "create_network: no gateway in ipv4 data from libnetwork"
-        return jsonify({'Err': error})
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "create_network: neutron login. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        if get_networkuuid_by_name(neutron, network):
-            error = "create_network: network has already been created"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "create_network: neutron network uuid by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        body = {'network': {'name': network, 'admin_state_up': True}}
-        ret = neutron.create_network(body)
-        network_id = ret['network']['id']
-    except Exception as e:
-        error = "create_network: neutron net-create call. (%s)" % str(e)
-        return jsonify({'Err': error})
-
-    subnet_name = "docker-%s" % (network)
-
-    try:
-        body = {'subnet': {'network_id': network_id,
-                           'ip_version': 4,
-                           'cidr': subnet,
-                           'gateway_ip': gateway_ip,
-                           'name': subnet_name}}
-        created_subnet = neutron.create_subnet(body)
-    except Exception as e:
-        error = "create_network: neutron subnet-create call. (%s)" % str(e)
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
-def delete_network():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "delete_network: neutron login. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        network = get_networkuuid_by_name(neutron, nid)
-        if not network:
-            error = "delete_network: failed in network by name. (%s)" % (nid)
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "delete_network: network uuid by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        neutron.delete_network(network)
-    except Exception as e:
-        error = "delete_network: neutron net-delete. (%s)" % str(e)
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-def reserve_vlan():
-    reserved_vlan = 0
-    vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
-                      "external_ids:vlans").strip('"')
-    if not vlans:
-        reserved_vlan = 1
-        ovs_vsctl("set", "Open_vSwitch", ".",
-                  "external_ids:vlans=" + str(reserved_vlan))
-        return reserved_vlan
-
-    vlan_set = str(vlans).split(',')
-
-    for vlan in range(1, 4095):
-        if str(vlan) not in vlan_set:
-            vlan_set.append(str(vlan))
-            reserved_vlan = vlan
-            vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
-            ovs_vsctl("set", "Open_vSwitch", ".",
-                      "external_ids:vlans=" + vlans)
-            return reserved_vlan
-
-    if not reserved_vlan:
-        raise RuntimeError("No more vlans available on this host")
-
-
-def unreserve_vlan(reserved_vlan):
-    vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
-                      "external_ids:vlans").strip('"')
-    if not vlans:
-        return
-
-    vlan_set = str(vlans).split(',')
-    if str(reserved_vlan) not in vlan_set:
-        return
-
-    vlan_set.remove(str(reserved_vlan))
-    vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
-    if vlans:
-        ovs_vsctl("set", "Open_vSwitch", ".", "external_ids:vlans=" + vlans)
-    else:
-        ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids", "vlans")
-
-
-def create_port_underlay(neutron, network, eid, ip_address, mac_address):
-    reserved_vlan = reserve_vlan()
-    if mac_address:
-        body = {'port': {'network_id': network,
-                         'binding:profile': {'parent_name': VIF_ID,
-                                             'tag': int(reserved_vlan)},
-                         'mac_address': mac_address,
-                         'fixed_ips': [{'ip_address': ip_address}],
-                         'name': eid,
-                         'admin_state_up': True}}
-    else:
-        body = {'port': {'network_id': network,
-                         'binding:profile': {'parent_name': VIF_ID,
-                                             'tag': int(reserved_vlan)},
-                         'fixed_ips': [{'ip_address': ip_address}],
-                         'name': eid,
-                         'admin_state_up': True}}
-
-    try:
-        ret = neutron.create_port(body)
-        mac_address = ret['port']['mac_address']
-    except Exception as e:
-        unreserve_vlan(reserved_vlan)
-        raise RuntimeError("Failed in creation of neutron port (%s)." % str(e))
-
-    ovs_vsctl("set", "Open_vSwitch", ".",
-              "external_ids:" + eid + "_vlan=" + str(reserved_vlan))
-
-    return mac_address
-
-
-def get_endpointuuid_by_name(neutron, name):
-    param = {'fields': 'id', 'name': name}
-    ret = neutron.list_ports(**param)
-    if len(ret['ports']) > 1:
-        raise RuntimeError("More than one endpoint for the given name")
-    elif len(ret['ports']) == 0:
-        endpoint = None
-    else:
-        endpoint = ret['ports'][0]['id']
-    return endpoint
-
-
-@app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
-def create_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    interface = data.get("Interface", "")
-    if not interface:
-        error = "create_endpoint: no interfaces supplied by libnetwork"
-        return jsonify({'Err': error})
-
-    ip_address_and_mask = interface.get("Address", "")
-    if not ip_address_and_mask:
-        error = "create_endpoint: ip address not provided by libnetwork"
-        return jsonify({'Err': error})
-
-    ip_address = ip_address_and_mask.rsplit('/', 1)[0]
-    mac_address_input = interface.get("MacAddress", "")
-    mac_address_output = ""
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "create_endpoint: neutron login. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        endpoint = get_endpointuuid_by_name(neutron, eid)
-        if endpoint:
-            error = "create_endpoint: Endpoint has already been created"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "create_endpoint: endpoint uuid by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        network = get_networkuuid_by_name(neutron, nid)
-        if not network:
-            error = "Failed to get neutron network record for (%s)" % (nid)
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "create_endpoint: network uuid by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        mac_address = create_port_underlay(neutron, network, eid, ip_address,
-                                           mac_address_input)
-    except Exception as e:
-        error = "create_endpoint: neutron port-create (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    if not mac_address_input:
-        mac_address_output = mac_address
-
-    return jsonify({"Interface": {
-                                    "Address": "",
-                                    "AddressIPv6": "",
-                                    "MacAddress": mac_address_output
-                                    }})
-
-
-@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
-def show_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "%s" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        endpoint = get_endpointuuid_by_name(neutron, eid)
-        if not endpoint:
-            error = "show_endpoint: Failed to get endpoint by name"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "show_endpoint: get endpoint by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ret = neutron.show_port(endpoint)
-        mac_address = ret['port']['mac_address']
-        ip_address = ret['port']['fixed_ips'][0]['ip_address']
-    except Exception as e:
-        error = "show_endpoint: show port (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    veth_outside = eid[0:15]
-    return jsonify({"Value": {"ip_address": ip_address,
-                              "mac_address": mac_address,
-                              "veth_outside": veth_outside
-                              }})
-
-
-@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
-def delete_endpoint():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "delete_endpoint: neutron login (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    endpoint = get_endpointuuid_by_name(neutron, eid)
-    if not endpoint:
-        return jsonify({})
-
-    reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
-                              "external_ids:" + eid + "_vlan").strip('"')
-    if reserved_vlan:
-        unreserve_vlan(reserved_vlan)
-        ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids",
-                  eid + "_vlan")
-
-    try:
-        neutron.delete_port(endpoint)
-    except Exception as e:
-        error = "delete_endpoint: neutron port-delete. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-
-@app.route('/NetworkDriver.Join', methods=['POST'])
-def network_join():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    sboxkey = data.get("SandboxKey", "")
-    if not sboxkey:
-        abort(400)
-
-    # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
-    vm_id = sboxkey.rsplit('/')[-1]
-
-    try:
-        neutron = neutron_login()
-    except Exception as e:
-        error = "network_join: neutron login. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    subnet_name = "docker-%s" % (nid)
-    try:
-        subnet = get_subnetuuid_by_name(neutron, subnet_name)
-        if not subnet:
-            error = "network_join: can't find subnet in neutron"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "network_join: subnet uuid by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ret = neutron.show_subnet(subnet)
-        gateway_ip = ret['subnet']['gateway_ip']
-        if not gateway_ip:
-            error = "network_join: no gateway_ip for the subnet"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "network_join: neutron show subnet. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        endpoint = get_endpointuuid_by_name(neutron, eid)
-        if not endpoint:
-            error = "network_join: Failed to get endpoint by name"
-            return jsonify({'Err': error})
-    except Exception as e:
-        error = "network_join: neutron endpoint by name. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ret = neutron.show_port(endpoint)
-        mac_address = ret['port']['mac_address']
-    except Exception as e:
-        error = "network_join: neutron show port. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    veth_outside = eid[0:15]
-    veth_inside = eid[0:13] + "_c"
-    command = "ip link add %s type veth peer name %s" \
-              % (veth_inside, veth_outside)
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to create veth pair. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    command = "ip link set dev %s address %s" \
-              % (veth_inside, mac_address)
-
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to set veth mac address. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    command = "ip link set %s up" % (veth_outside)
-
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_join: failed to up the veth iface. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
-                                  "external_ids:" + eid + "_vlan").strip('"')
-        if not reserved_vlan:
-            error = "network_join: no reserved vlan for this endpoint"
-            return jsonify({'Err': error})
-        ovs_vsctl("add-port", OVN_BRIDGE, veth_outside, "tag=" + reserved_vlan)
-    except Exception as e:
-        error = "network_join: failed to create a OVS port. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({"InterfaceName": {
-                                        "SrcName": veth_inside,
-                                        "DstPrefix": "eth"
-                                     },
-                    "Gateway": gateway_ip,
-                    "GatewayIPv6": ""})
-
-
-@app.route('/NetworkDriver.Leave', methods=['POST'])
-def network_leave():
-    if not request.data:
-        abort(400)
-
-    data = json.loads(request.data)
-
-    nid = data.get("NetworkID", "")
-    if not nid:
-        abort(400)
-
-    eid = data.get("EndpointID", "")
-    if not eid:
-        abort(400)
-
-    veth_outside = eid[0:15]
-    command = "ip link delete %s" % (veth_outside)
-    try:
-        call_popen(shlex.split(command))
-    except Exception as e:
-        error = "network_leave: failed to delete veth pair. (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    try:
-        ovs_vsctl("--if-exists", "del-port", veth_outside)
-    except Exception as e:
-        error = "network_leave: Failed to delete port (%s)" % (str(e))
-        return jsonify({'Err': error})
-
-    return jsonify({})
-
-if __name__ == '__main__':
-    prepare()
-    app.run(host='127.0.0.1')
diff --git a/ovn/utilities/ovn-trace.8.xml b/ovn/utilities/ovn-trace.8.xml
deleted file mode 100644
index 01e741119..000000000
--- a/ovn/utilities/ovn-trace.8.xml
+++ /dev/null
@@ -1,485 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manpage program="ovn-trace" section="8" title="ovn-trace">
-  <h1>Name</h1>
-  <p>ovn-trace -- Open Virtual Network logical network tracing utility</p>
-
-  <h1>Synopsis</h1>
-  <p><code>ovn-trace</code> [<var>options</var>] <var>datapath</var> <var>microflow</var></p>
-  <p><code>ovn-trace</code> [<var>options</var>] <code>--detach</code></p>
-  
-  <h1>Description</h1>
-  <p>
-    This utility simulates packet forwarding within an OVN logical network.
-    It can be used to run through ``what-if'' scenarios: if a packet
-    originates at a logical port, what will happen to it and where will it
-    ultimately end up?  Users already familiar with the Open vSwitch
-    <code>ofproto/trace</code> command described in
-    <code>ovs-vswitch</code>(8) will find <code>ovn-trace</code> to be a
-    similar tool for logical networks.
-  </p>
-
-  <p>
-    <code>ovn-trace</code> works by reading the <code>Logical_Flow</code> and
-    other tables from the OVN southbound database (see
-    <code>ovn-sb</code>(5)).  It simulates a packet's path through logical
-    networks by repeatedly looking it up in the logical flow table, following
-    the entire tree of possibilities.
-  </p>
-
-  <p>
-    <code>ovn-trace</code> simulates only the OVN logical network.  It does
-    not simulate the physical elements on which the logical network is
-    layered.  This means that, for example, it is unimportant how VMs are
-    distributed among hypervisors, or whether their hypervisors are
-    functioning and reachable, so <code>ovn-trace</code> will yield the same
-    results regardless.  There is one important exception:
-    <code>ovn-northd</code>, the daemon that generates the logical flows that
-    <code>ovn-trace</code> simulates, treats logical ports differently based
-    on whether they are up or down.  Thus, if you see surprising results,
-    ensure that the ports involved in a simulation are up.
-  </p>
-
-  <p>
-    The simplest way to use <code>ovn-trace</code> is to provide
-    <var>datapath</var> and <var>microflow</var> arguments on the command
-    line.  In this case, it simulates the behavior of a single packet and
-    exits.  For an alternate usage model, see <code>Daemon Mode</code> below.
-  </p>
-
-  <p>
-    The <var>datapath</var> argument specifies the name of a logical
-    datapath.  Acceptable names are the <code>name</code> from the northbound
-    <code>Logical_Switch</code> or <code>Logical_Router</code> table, the
-    UUID of a record from one of those tables, or the UUID of a record from
-    the southbound <code>Datapath_Binding</code> table.
-  </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>
-
-  <p>
-    For reasonable L2 behavior, the microflow should include at least
-    <code>inport</code> and <code>eth.dst</code>, plus <code>eth.src</code>
-    if port security is enabled.  For example:
-  </p>
-  <pre>
-    inport == "lp11" &amp;&amp; eth.src == 00:01:02:03:04:05 &amp;&amp; eth.dst == ff:ff:ff:ff:ff:ff
-  </pre>
-
-  <p>
-    For reasonable L3 behavior, <var>microflow</var> should also include
-    <code>ip4.src</code> and <code>ip4.dst</code> (or <code>ip6.src</code>
-    and <code>ip6.dst</code>) and <code>ip.ttl</code>.  For example:
-  </p>
-  <pre>
-    inport == "lp111" &amp;&amp; eth.src == f0:00:00:00:01:11 &amp;&amp; eth.dst == 00:00:00:00:ff:11
-    &amp;&amp; ip4.src == 192.168.11.1 &amp;&amp; ip4.dst == 192.168.22.2 &amp;&amp; ip.ttl == 64
-  </pre>
-
-  <p>Here's an ARP microflow example:</p>
-  <pre>
-    inport == "lp123"
-    &amp;&amp; eth.dst == ff:ff:ff:ff:ff:ff &amp;&amp; eth.src == f0:00:00:00:01:11
-    &amp;&amp; arp.op == 1 &amp;&amp; arp.sha == f0:00:00:00:01:11 &amp;&amp; arp.spa == 192.168.1.11
-    &amp;&amp; arp.tha == ff:ff:ff:ff:ff:ff &amp;&amp; arp.tpa == 192.168.2.22
-  </pre>
-
-  <p>
-    <code>ovn-trace</code> will reject erroneous microflow expressions, which
-    beyond syntax errors fall into two categories.  First, they can be
-    ambiguous.  For example, <code>tcp.src == 80</code> is ambiguous because
-    it does not state IPv4 or IPv6 as the Ethernet type.  <code>ip4
-    &amp;&amp; tcp.src > 1024</code> is also ambiguous because it does not
-    constrain bits of <code>tcp.src</code> to particular values.  Second,
-    they can be contradictory, e.g. <code>ip4 &amp;&amp; ip6</code>.
-  </p>
-
-  <h1>Output</h1>
-
-  <p>
-    <code>ovn-trace</code> supports the three different forms of output, each
-    described in a separate section below.  Regardless of the selected output
-    format, <code>ovn-trace</code> starts the output with a line that shows
-    the microflow being traced in OpenFlow syntax.
-  </p>
-
-  <h2>Detailed Output</h2>
-
-  <p>
-    The detailed form of output is also the default form.  This form groups
-    output into sections headed up by the ingress or egress pipeline being
-    traversed.  Each pipeline lists each table that was visited (by number and
-    name), the <code>ovn-northd</code> source file and line number of the code
-    that added the flow, the match expression and priority of the logical flow
-    that was matched, and the actions that were executed.
-  </p>
-
-  <p>
-    The execution of OVN logical actions naturally forms a ``control stack''
-    that resembles that of a program in conventional programming languages
-    such as C or Java.  Because the <code>next</code> action that calls into
-    another logical flow table for a lookup is a recursive construct, OVN
-    ``programs'' in practice tend to form deep control stacks that, displayed
-    in the obvious way using additional indentation for each level, quickly
-    use up the horizontal space on all but the widest displays.  To make
-    detailed output more readable, without loss of generality,
-    <code>ovn-trace</code> omits indentation for ``tail recursion,'' that is,
-    when <code>next</code> is the last action in a logical flow, it does not
-    indent details of the next table lookup more deeply.  Output still uses
-    indentation when it is needed for clarity.
-  </p>
-
-  <p>
-    OVN ``programs'' traces also tend to encounter long strings of logical
-    flows with match expression <code>1</code> (which matches every packet)
-    and the single action <code>next;</code>.  These are uninteresting
-    and merely clutter output, so <code>ovn-trace</code> omits them
-    entirely even from detailed output.
-  </p>
-
-  <p>
-    The following excerpt from detailed <code>ovn-trace</code> output shows a
-    section for a packet traversing the ingress pipeline of logical datapath
-    <code>ls1</code> with ingress logical port <code>lp111</code>.  The
-    packet matches a logical flow in table 0 (aka
-    <code>ls_in_port_sec_l2</code>) with priority 50 and executes
-    <code>next(1);</code> to pass to table 1.  Tables 1 through 11 are
-    trivial and omitted.  In table 12 (aka <code>ls_in_l2_lkup</code>), the
-    packet matches a flow with priority 50 based on its Ethernet destination
-    address and the flow's actions output the packet to the
-    <code>lrp11-attachement</code> logical port.
-  </p>
-
-  <pre fixed="yes">
-    ingress(dp="ls1", inport="lp111")
-    ---------------------------------
-    0. ls_in_port_sec_l2: inport == "lp111", priority 50
-    next(1);
-    12. ls_in_l2_lkup: eth.dst == 00:00:00:00:ff:11, priority 50
-    outport = "lrp11-attachment";
-    output;
-  </pre>
-
-  <h2>Summary Output</h2>
-
-  <p>
-    Summary output includes the logical pipelines visited by a packet and the
-    logical actions executed on it.  Compared to the detailed output,
-    however, it removes details of tables and logical flows traversed by a
-    packet.  It uses a format closer to that of a programming language and
-    does not attempt to avoid indentation.  The summary output equivalent to
-    the above detailed output fragment is:
-  </p>
-
-  <pre fixed="yes">
-    ingress(dp="ls1", inport="lp111") {
-    outport = "lrp11-attachment";
-    output;
-    ...
-    };
-  </pre>
-
-  <h2>Minimal Output</h2>
-
-  <p>
-    Minimal output includes only actions that modify packet data (not
-    including OVN registers or metadata such as <code>outport</code>) and
-    <code>output</code> actions that actually deliver a packet to a logical
-    port (excluding patch ports).  The operands of actions that modify packet
-    data are displayed reduced to constants, e.g. <code>ip4.dst =
-    reg0;</code> might be show as <code>ip4.dst = 192.168.0.1;</code> if that
-    was the value actually loaded.  This yields output even simpler than the
-    summary format.  (Users familiar with Open vSwitch may recognize this as
-    similar in spirit to the datapath actions listed at the bottom of
-    <code>ofproto/trace</code> output.)
-  </p>
-
-  <p>
-    The minimal output format reflects the externally seen behavior of the
-    logical networks more than it does the implementation.  This makes this
-    output format the most suitable for use in regression tests, because it
-    is least likely to change when logical flow tables are rearranged without
-    semantic change.
-  </p>
-
-  <h1>Stateful Actions</h1>
-
-  <p>
-    Some OVN logical actions use or update state that is not available in the
-    southbound database.  <code>ovn-trace</code> handles these actions as
-    described below:
-  </p>
-
-  <dl>
-    <dt><code>ct_next</code></dt>
-    <dd>
-      By default <code>ovn-trace</code> treats flows as ``tracked'' and
-      ``established.''  See the description of the <code>--ct</code> option for
-      a way to override this behavior.
-    </dd>
-
-    <dt><code>ct_dnat</code> (without an argument)</dt>
-    <dd>
-      Forks the pipeline.  In one fork, advances to the next table as if
-      <code>next;</code> were executed.  The packet is not changed, on the
-      assumption that no NAT state was available.  In the other fork, the
-      pipeline continues without change after the <code>ct_dnat</code> action.
-    </dd>
-
-    <dt><code>ct_snat</code> (without an argument)</dt>
-    <dd>
-      This action distinguishes between gateway routers and distributed
-      routers.  A gateway router is defined as a logical datapath that contains
-      an <code>l3gateway</code> port; any other logical datapath is a
-      distributed router.  On a gateway router, <code>ct_snat;</code> is
-      treated as a no-op.  On a distributed router, it is treated the same way
-      as <code>ct_dnat;</code>.
-    </dd>
-
-    <dt><code>ct_dnat(<var>ip</var>)</code></dt>
-    <dt><code>ct_snat(<var>ip</var>)</code></dt>
-    <dd>
-      Forks the pipeline.  In one fork, sets <code>ip4.dst</code> (or
-      <code>ip4.src</code>) to <var>ip</var> and <code>ct.dnat</code> (or
-      <code>ct.snat</code>) to 1 and advances to the next table as if
-      <code>next;</code> were executed.  In the other fork, the pipeline
-      continues without change after the <code>ct_dnat</code> (or
-      <code>ct_snat</code>) action.
-    </dd>
-
-    <dt><code>ct_lb;</code></dt>
-    <dt><code>ct_lb(<var>ip</var></code>[<code>:<var>port</var></code>]...<code>);</code></dt>
-    <dd>
-      Forks the pipeline. In one fork, sets <code>ip4.dst</code> (or
-      <code>ip6.dst</code>) to one of the load-balancer addresses and the
-      destination port to its associated port, if any, and sets
-      <code>ct.dnat</code> to 1.  With one or more arguments, gives preference
-      to the address specified on <code>--lb-dst</code>, if any; without
-      arguments, uses the address and port specified on <code>--lb-dst</code>.
-      In the other fork, the pipeline continues without change after the
-      <code>ct_lb</code> action.
-    </dd>
-
-    <dt><code>ct_commit</code></dt>
-    <dt><code>put_arp</code></dt>
-    <dt><code>put_nd</code></dt>
-    <dd>
-      These actions are treated as no-ops.
-    </dd>
-  </dl>
-
-  <h1>Daemon Mode</h1>
-
-  <p>
-    If <code>ovn-trace</code> is invoked with the <code>--detach</code> option
-    (see <code>Daemon Options</code>, below), it runs in the background as a
-    daemon and accepts commands from <code>ovs-appctl</code> (or another
-    JSON-RPC client) indefinitely.  The currently supported commands are
-    described below.
-  </p>
-
-  <p>
-    
-  </p>
-
-  <dl>
-    <dt><code>trace</code> [<var>options</var>] <var>datapath</var> <var>microflow</var></dt>
-    <dd>
-      Traces <var>microflow</var> through <var>datapath</var> and replies with
-      the results of the trace.  Accepts the <var>options</var> described under
-      <code>Trace Options</code> below.
-    </dd>
-
-    <dt><code>exit</code></dt>
-    <dd>Causes <code>ovn-trace</code> to gracefully terminate.</dd>
-  </dl>
-
-  <h1>Options</h1>
-  
-  <h2>Trace Options</h2>
-
-  <dl>
-    <dt><code>--detailed</code></dt>
-    <dt><code>--summary</code></dt>
-    <dt><code>--minimal</code></dt>
-    <dd>
-      These options control the form and level of detail in
-      <code>ovn-trace</code> output.  If more than one of these options is
-      specified, all of the selected forms are output, in the order listed
-      above, each headed by a banner line.  If none of these options is
-      given, <code>--detailed</code> is the default.  See
-      <code>Output</code>, above, for a description of each kind of output.
-    </dd>
-
-    <dt><code>--all</code></dt>
-    <dd>
-      Selects all three forms of output.
-    </dd>
-
-    <dt><code>--ovs</code>[<code>=</code><var>remote</var>]</dt>
-    <dd>
-      <p>
-        Makes <code>ovn-trace</code> attempt to obtain and display the OpenFlow
-        flows that correspond to each OVN logical flow.  To do so,
-        <code>ovn-trace</code> connects to <var>remote</var> (by default,
-        <code>unix:@RUNDIR@/br-int.mgmt</code>) over OpenFlow and retrieves the
-        flows.  If <var>remote</var> is specified, it must be an active
-        OpenFlow connection method described in <code>ovsdb</code>(7).
-      </p>
-
-      <p>
-        To make the best use of the output, it is important to understand the
-        relationship between logical flows and OpenFlow flows.
-        <code>ovn-architecture</code>(7), under <em>Architectural Physical Life
-        Cycle of a Packet</em>, describes this relationship.  Keep in mind the
-        following points:
-      </p>
-
-      <ul>
-        <li>
-          <code>ovn-trace</code> currently shows all the OpenFlow flows to
-          which a logical flow corresponds, even though an actual packet
-          ordinarily matches only one of these.
-        </li>
-
-        <li>
-          Some logical flows can map to the Open vSwitch ``conjunctive match''
-          extension (see <code>ovs-fields</code>(7)).  Currently
-          <code>ovn-trace</code> cannot display the flows with
-          <code>conjunction</code> actions that effectively produce the
-          <code>conj_id</code> match.
-        </li>
-
-        <li>
-          Some logical flows may not be represented in the OpenFlow tables on a
-          given hypervisor, if they could not be used on that hypervisor.
-        </li>
-
-        <li>
-          Some OpenFlow flows do not correspond to logical flows, such as
-          OpenFlow flows that map between physical and logical ports.  These
-          flows will never show up in a trace.
-        </li>
-
-        <li>
-          When <code>ovn-trace</code> omits uninteresting logical flows from
-          output, it does not look up the corresponding OpenFlow flows.
-        </li>
-      </ul>
-    </dd>
-
-    <dt><code>--ct=<var>flags</var></code></dt>
-    <dd>
-      <p>
-        This option sets the <code>ct_state</code> flags that a
-        <code>ct_next</code> logical action will report.  The <var>flags</var>
-        must be a comma- or space-separated list of the following connection
-        tracking flags:
-      </p>
-
-      <ul>
-        <li>
-          <code>trk</code>: Include to indicate connection tracking has taken
-          place.  (This bit is set automatically even if not listed in
-          <var>flags</var>.
-        </li>
-        <li><code>new</code>: Include to indicate a new flow.</li>
-        <li><code>est</code>: Include to indicate an established flow.</li>
-        <li><code>rel</code>: Include to indicate a related flow.</li>
-        <li><code>rpl</code>: Include to indicate a reply flow.</li>
-        <li><code>inv</code>: Include to indicate a connection entry in a
-        bad state.</li>
-        <li><code>dnat</code>: Include to indicate a packet whose
-        destination IP address has been changed.</li>
-        <li><code>snat</code>: Include to indicate a packet whose source IP
-        address has been changed.</li>
-      </ul>
-
-      <p>
-        The <code>ct_next</code> action is used to implement the OVN
-        distributed firewall.  For testing, useful flag combinations include:
-      </p>
-
-      <ul>
-        <li><code>trk,new</code>: A packet in a flow in either direction
-        through a firewall that has not yet been committed (with
-        <code>ct_commit</code>).</li>
-        <li><code>trk,est</code>: A packet in an established flow going out
-        through a firewall.</li>
-        <li><code>trk,rpl</code>: A packet coming in through a firewall in
-        reply to an established flow.</li>
-        <li><code>trk,inv</code>: An invalid packet in either direction.</li>
-      </ul>
-
-      <p>
-        A packet might pass through the connection tracker twice in one trip
-        through OVN: once following egress from a VM as it passes outward
-        through a firewall, and once preceding ingress to a second VM as it
-        passes inward through a firewall.  Use multiple <code>--ct</code>
-        options to specify the flags for multiple <code>ct_next</code> actions.
-      </p>
-
-      <p>
-        When <code>--ct</code> is unspecified, or when there are fewer
-        <code>--ct</code> options than <code>ct_next</code> actions, the
-        <var>flags</var> default to <code>trk,est</code>.
-      </p>
-    </dd>
-
-    <dt><code>--lb-dst=</code><var>ip</var>[<code>:<var>port</var></code>]</dt>
-    <dd>
-      Sets the IP from VIP pool to use as destination of the packet.
-      <code>--lb-dst</code> is not available in daemon mode.
-    </dd>
-
-    <dt><code>--friendly-names</code></dt>
-    <dt><code>--no-friendly-names</code></dt>
-    <dd>
-      When cloud management systems such as OpenStack are layered on top of
-      OVN, they often use long, human-unfriendly names for ports and datapaths,
-      for example, ones that include entire UUIDs.  They do usually include
-      friendlier names, but the long, hard-to-read names are the ones that
-      appear in matches and actions.  By default, or with
-      <code>--friendly-names</code>, <code>ovn-trace</code> substitutes these
-      friendlier names for the long names in its output.  Use
-      <code>--no-friendly-names</code> to disable this behavior; this option
-      might be useful, for example, if a program is going to parse
-      <code>ovn-trace</code> output.
-    </dd>
-  </dl>
-
-  <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 to use SSL for the connection to the
-    database (and the switch, if <code>--ovs</code> is specified).
-  </p>
-  <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-  <h2>Other Options</h2>
-
-  <dl>
-    <dt><code>--db</code> <var>database</var></dt>
-    <dd>
-      The OVSDB database remote to contact.  If the <env>OVN_SB_DB</env>
-      environment variable is set, its value is used as the default.
-      Otherwise, the default is <code>unix:@RUNDIR@/db.sock</code>, but this
-      default is unlikely to be useful outside of single-machine OVN test
-      environments.
-    </dd>
-  </dl>
-  
-  <xi:include href="lib/common.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
-
-</manpage>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
deleted file mode 100644
index 044eb1cc2..000000000
--- a/ovn/utilities/ovn-trace.c
+++ /dev/null
@@ -1,2373 +0,0 @@
-/*
- * Copyright (c) 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 <getopt.h>
-
-#include "command-line.h"
-#include "compiler.h"
-#include "daemon.h"
-#include "dirs.h"
-#include "fatal-signal.h"
-#include "flow.h"
-#include "nx-match.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
-#include "openvswitch/ofp-actions.h"
-#include "openvswitch/ofp-flow.h"
-#include "openvswitch/ofp-print.h"
-#include "openvswitch/vconn.h"
-#include "openvswitch/vlog.h"
-#include "ovn/actions.h"
-#include "ovn/expr.h"
-#include "ovn/lex.h"
-#include "ovn/logical-fields.h"
-#include "ovn/lib/acl-log.h"
-#include "ovn/lib/ovn-l7.h"
-#include "ovn/lib/ovn-sb-idl.h"
-#include "ovn/lib/ovn-util.h"
-#include "ovsdb-idl.h"
-#include "openvswitch/poll-loop.h"
-#include "stream-ssl.h"
-#include "stream.h"
-#include "unixctl.h"
-#include "util.h"
-#include "random.h"
-
-VLOG_DEFINE_THIS_MODULE(ovntrace);
-
-/* --db: The database server to contact. */
-static const char *db;
-
-/* --unixctl-path: Path to use for unixctl server, for "monitor" and "snoop"
-     commands. */
-static char *unixctl_path;
-
-/* The southbound database. */
-static struct ovsdb_idl *ovnsb_idl;
-
-/* --detailed: Show a detailed, table-by-table trace. */
-static bool detailed;
-
-/* --summary: Show a trace that omits table information. */
-static bool summary;
-
-/* --minimal: Show a trace with only minimal information. */
-static bool minimal;
-
-/* --ovs: OVS instance to contact to get OpenFlow flows. */
-static const char *ovs;
-static struct vconn *vconn;
-
-/* --ct: Connection tracking state to use for ct_next() actions. */
-static uint32_t *ct_states;
-static size_t n_ct_states;
-static size_t ct_state_idx;
-
-/* --lb-dst: load balancer destination info. */
-static struct ovnact_ct_lb_dst lb_dst;
-
-/* --friendly-names, --no-friendly-names: Whether to substitute human-friendly
- * port and datapath names for the awkward UUIDs typically used in the actual
- * logical flows. */
-static bool use_friendly_names = true;
-
-OVS_NO_RETURN static void usage(void);
-static void parse_options(int argc, char *argv[]);
-static char *trace(const char *datapath, const char *flow);
-static void read_db(void);
-static unixctl_cb_func ovntrace_exit;
-static unixctl_cb_func ovntrace_trace;
-
-int
-main(int argc, char *argv[])
-{
-    set_program_name(argv[0]);
-    service_start(&argc, &argv);
-    fatal_ignore_sigpipe();
-    vlog_set_levels_from_string_assert("reconnect:warn");
-
-    /* Parse command line. */
-    parse_options(argc, argv);
-    argc -= optind;
-    argv += optind;
-
-    if (get_detach()) {
-        if (argc != 0) {
-            ovs_fatal(0, "non-option arguments not supported with --detach "
-                      "(use --help for help)");
-        }
-    } else {
-        if (argc != 2) {
-            ovs_fatal(0, "exactly two non-option arguments are required "
-                      "(use --help for help)");
-        }
-    }
-
-    struct unixctl_server *server = NULL;
-    bool exiting = false;
-    if (get_detach()) {
-        daemonize_start(false);
-        int error = unixctl_server_create(unixctl_path, &server);
-        if (error) {
-            ovs_fatal(error, "failed to create unixctl server");
-        }
-        unixctl_command_register("exit", "", 0, 0, ovntrace_exit, &exiting);
-        unixctl_command_register("trace", "[OPTIONS] DATAPATH MICROFLOW",
-                                 2, INT_MAX, ovntrace_trace, NULL);
-    }
-    ovnsb_idl = ovsdb_idl_create(db, &sbrec_idl_class, true, false);
-
-    bool already_read = false;
-    for (;;) {
-        ovsdb_idl_run(ovnsb_idl);
-        unixctl_server_run(server);
-        if (!ovsdb_idl_is_alive(ovnsb_idl)) {
-            int retval = ovsdb_idl_get_last_error(ovnsb_idl);
-            ovs_fatal(0, "%s: database connection failed (%s)",
-                      db, ovs_retval_to_string(retval));
-        }
-
-        if (ovsdb_idl_has_ever_connected(ovnsb_idl)) {
-            if (!already_read) {
-                already_read = true;
-                read_db();
-            }
-
-            daemonize_complete();
-            if (!get_detach()) {
-                char *output = trace(argv[0], argv[1]);
-                fputs(output, stdout);
-                free(output);
-                return 0;
-            }
-        }
-
-        if (exiting) {
-            break;
-        }
-        ovsdb_idl_wait(ovnsb_idl);
-        unixctl_server_wait(server);
-        poll_block();
-    }
-}
-
-static char *
-default_ovs(void)
-{
-    return xasprintf("unix:%s/br-int.mgmt", ovs_rundir());
-}
-
-static void
-parse_ct_option(const char *state_s_)
-{
-    uint32_t state;
-    struct ds ds = DS_EMPTY_INITIALIZER;
-
-    if (!parse_ct_state(state_s_, CS_TRACKED, &state, &ds)) {
-        ovs_fatal(0, "%s", ds_cstr(&ds));
-    }
-    if (!validate_ct_state(state, &ds)) {
-        VLOG_WARN("%s", ds_cstr(&ds));
-    }
-    ds_destroy(&ds);
-
-    ct_states = xrealloc(ct_states, (n_ct_states + 1) * sizeof *ct_states);
-    ct_states[n_ct_states++] = state;
-}
-
-static void
-parse_lb_option(const char *s)
-{
-    struct sockaddr_storage ss;
-    if (!inet_parse_active(s, 0, &ss, false)) {
-        ovs_fatal(0, "%s: bad address", s);
-    }
-
-    lb_dst.family = ss.ss_family;
-    struct in6_addr a = ss_get_address(&ss);
-    if (ss.ss_family == AF_INET) {
-        lb_dst.ipv4 = in6_addr_get_mapped_ipv4(&a);
-    } else {
-        lb_dst.ipv6 = a;
-    }
-    lb_dst.port = ss_get_port(&ss);
-}
-
-static void
-parse_options(int argc, char *argv[])
-{
-    enum {
-        OPT_DB = UCHAR_MAX + 1,
-        OPT_UNIXCTL,
-        OPT_DETAILED,
-        OPT_SUMMARY,
-        OPT_MINIMAL,
-        OPT_ALL,
-        OPT_OVS,
-        OPT_CT,
-        OPT_FRIENDLY_NAMES,
-        OPT_NO_FRIENDLY_NAMES,
-        DAEMON_OPTION_ENUMS,
-        SSL_OPTION_ENUMS,
-        VLOG_OPTION_ENUMS,
-        OPT_LB_DST
-    };
-    static const struct option long_options[] = {
-        {"db", required_argument, NULL, OPT_DB},
-        {"unixctl", required_argument, NULL, OPT_UNIXCTL},
-        {"detailed", no_argument, NULL, OPT_DETAILED},
-        {"summary", no_argument, NULL, OPT_SUMMARY},
-        {"minimal", no_argument, NULL, OPT_MINIMAL},
-        {"all", no_argument, NULL, OPT_ALL},
-        {"ovs", optional_argument, NULL, OPT_OVS},
-        {"ct", required_argument, NULL, OPT_CT},
-        {"friendly-names", no_argument, NULL, OPT_FRIENDLY_NAMES},
-        {"no-friendly-names", no_argument, NULL, OPT_NO_FRIENDLY_NAMES},
-        {"help", no_argument, NULL, 'h'},
-        {"version", no_argument, NULL, 'V'},
-        {"lb-dst", required_argument, NULL, OPT_LB_DST},
-        DAEMON_LONG_OPTIONS,
-        VLOG_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {NULL, 0, NULL, 0},
-    };
-    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
-
-    for (;;) {
-        int idx;
-        int c;
-
-        c = getopt_long(argc, argv, short_options, long_options, &idx);
-        if (c == -1) {
-            break;
-        }
-
-        switch (c) {
-        case OPT_DB:
-            db = optarg;
-            break;
-
-        case OPT_UNIXCTL:
-            unixctl_path = optarg;
-            break;
-
-        case OPT_DETAILED:
-            detailed = true;
-            break;
-
-        case OPT_SUMMARY:
-            summary = true;
-            break;
-
-        case OPT_MINIMAL:
-            minimal = true;
-            break;
-
-        case OPT_ALL:
-            detailed = summary = minimal = true;
-            break;
-
-        case OPT_OVS:
-            ovs = optarg ? optarg : default_ovs();
-            break;
-
-        case OPT_CT:
-            parse_ct_option(optarg);
-            break;
-
-        case OPT_FRIENDLY_NAMES:
-            use_friendly_names = true;
-            break;
-
-        case OPT_NO_FRIENDLY_NAMES:
-            use_friendly_names = false;
-            break;
-
-        case OPT_LB_DST:
-            parse_lb_option(optarg);
-            break;
-
-        case 'h':
-            usage();
-
-        case 'V':
-            ovs_print_version(0, 0);
-            printf("DB Schema %s\n", sbrec_get_db_version());
-            exit(EXIT_SUCCESS);
-
-        DAEMON_OPTION_HANDLERS
-        VLOG_OPTION_HANDLERS
-        STREAM_SSL_OPTION_HANDLERS
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            abort();
-        }
-    }
-    free(short_options);
-
-    if (!db) {
-        db = default_sb_db();
-    }
-
-    if (!detailed && !summary && !minimal) {
-        detailed = true;
-    }
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN trace utility\n\
-usage: %s [OPTIONS] DATAPATH MICROFLOW\n\
-       %s [OPTIONS] --detach\n\
-\n\
-Output format options:\n\
-  --detailed              table-by-table \"backtrace\" (default)\n\
-  --summary               less detailed, more parseable\n\
-  --minimal               minimum to explain externally visible behavior\n\
-  --all                   provide all forms of output\n\
-Output style options:\n\
-  --no-friendly-names     do not substitute human friendly names for UUIDs\n",
-           program_name, program_name, program_name);
-    daemon_usage();
-    vlog_usage();
-    printf("\n\
-Other options:\n\
-  --db=DATABASE           connect to DATABASE\n\
-                          (default: %s)\n\
-  --ovs[=REMOTE]          obtain corresponding OpenFlow flows from REMOTE\n\
-                          (default: %s)\n\
-  --unixctl=SOCKET        set control socket name\n\
-  -h, --help              display this help message\n\
-  -V, --version           display version information\n",
-           default_sb_db(), default_ovs());
-    stream_usage("database", true, true, false);
-    vconn_usage(true, false, false);
-    exit(EXIT_SUCCESS);
-}
-
-struct ovntrace_datapath {
-    struct hmap_node sb_uuid_node;
-    struct uuid sb_uuid;
-    struct uuid nb_uuid;
-    char *name;
-    char *name2;
-    char *friendly_name;
-    uint32_t tunnel_key;
-
-    struct ovs_list mcgroups;   /* Contains "struct ovntrace_mcgroup"s. */
-
-    struct ovntrace_flow **flows;
-    size_t n_flows, allocated_flows;
-
-    struct hmap mac_bindings;   /* Contains "struct ovntrace_mac_binding"s. */
-
-    bool has_local_l3gateway;
-};
-
-struct ovntrace_port {
-    struct ovntrace_datapath *dp;
-    struct uuid uuid;
-    char *name;
-    char *name2;
-    const char *friendly_name;
-    char *type;
-    uint16_t tunnel_key;
-    struct ovntrace_port *peer; /* Patch ports only. */
-    struct ovntrace_port *distributed_port; /* chassisredirect ports only. */
-};
-
-struct ovntrace_mcgroup {
-    struct ovs_list list_node;  /* In struct ovntrace_datapath's 'mcgroups'. */
-
-    struct ovntrace_datapath *dp;
-    char *name;
-
-    uint16_t tunnel_key;
-
-    struct ovntrace_port **ports;
-    size_t n_ports;
-};
-
-struct ovntrace_flow {
-    struct uuid uuid;
-    enum ovnact_pipeline pipeline;
-    int table_id;
-    char *stage_name;
-    char *source;
-    int priority;
-    char *match_s;
-    struct expr *match;
-    struct ovnact *ovnacts;
-    size_t ovnacts_len;
-};
-
-struct ovntrace_mac_binding {
-    struct hmap_node node;
-    uint16_t port_key;
-    struct in6_addr ip;
-    struct eth_addr mac;
-};
-
-static inline uint32_t
-hash_mac_binding(uint16_t port_key, const struct in6_addr *ip)
-{
-    return hash_bytes(ip, sizeof *ip, port_key);
-}
-
-/* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */
-static struct hmap datapaths;
-
-/* Every ovntrace_port, by name. */
-static struct shash ports;
-
-/* Symbol table for expressions and actions. */
-static struct shash symtab;
-
-/* Address sets. */
-static struct shash address_sets;
-
-/* Port groups. */
-static struct shash port_groups;
-
-/* DHCP options. */
-static struct hmap dhcp_opts;   /* Contains "struct gen_opts_map"s. */
-static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */
-static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */
-
-static struct ovntrace_datapath *
-ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid)
-{
-    struct ovntrace_datapath *dp;
-    HMAP_FOR_EACH_WITH_HASH (dp, sb_uuid_node, uuid_hash(sb_uuid),
-                             &datapaths) {
-        if (uuid_equals(&dp->sb_uuid, sb_uuid)) {
-            return dp;
-        }
-    }
-    return NULL;
-}
-
-static const struct ovntrace_datapath *
-ovntrace_datapath_find_by_name(const char *name)
-{
-    struct ovntrace_datapath *dp;
-    HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) {
-        if (!strcmp(name, dp->name)
-            || (dp->name2 && !strcmp(name, dp->name2))) {
-            return dp;
-        }
-    }
-
-    struct ovntrace_datapath *match = NULL;
-    HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) {
-        if (uuid_is_partial_match(&dp->sb_uuid, name) >= 4 ||
-            uuid_is_partial_match(&dp->nb_uuid, name) >= 4) {
-            if (match) {
-                VLOG_WARN("name \"%s\" matches multiple datapaths", name);
-                return NULL;
-            }
-            match = dp;
-        }
-    }
-    return match;
-}
-
-static const struct ovntrace_port *
-ovntrace_port_find_by_key(const struct ovntrace_datapath *dp,
-                          uint16_t tunnel_key)
-{
-    const struct shash_node *node;
-    SHASH_FOR_EACH (node, &ports) {
-        const struct ovntrace_port *port = node->data;
-        if (port->dp == dp && port->tunnel_key == tunnel_key) {
-            return port;
-        }
-    }
-    return NULL;
-}
-
-static const char *
-ovntrace_port_key_to_name(const struct ovntrace_datapath *dp,
-                          uint16_t key)
-{
-    const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key);
-    return (port ? port->friendly_name
-            : !key ? ""
-            : "(unnamed)");
-}
-
-static const struct ovntrace_mcgroup *
-ovntrace_mcgroup_find_by_key(const struct ovntrace_datapath *dp,
-                             uint16_t tunnel_key)
-{
-    const struct ovntrace_mcgroup *mcgroup;
-    LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) {
-        if (mcgroup->tunnel_key == tunnel_key) {
-            return mcgroup;
-        }
-    }
-    return NULL;
-}
-
-static const struct ovntrace_mcgroup *
-ovntrace_mcgroup_find_by_name(const struct ovntrace_datapath *dp,
-                              const char *name)
-{
-    const struct ovntrace_mcgroup *mcgroup;
-    LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) {
-        if (!strcmp(mcgroup->name, name)) {
-            return mcgroup;
-        }
-    }
-    return NULL;
-}
-
-static const struct ovntrace_mac_binding *
-ovntrace_mac_binding_find(const struct ovntrace_datapath *dp,
-                          uint16_t port_key, const struct in6_addr *ip)
-{
-    const struct ovntrace_mac_binding *bind;
-    HMAP_FOR_EACH_WITH_HASH (bind, node, hash_mac_binding(port_key, ip),
-                             &dp->mac_bindings) {
-        if (bind->port_key == port_key && ipv6_addr_equals(ip, &bind->ip)) {
-            return bind;
-        }
-    }
-    return NULL;
-}
-
-/* If 's' ends with a UUID, returns a copy of it with the UUID truncated to
- * just the first 6 characters; otherwise, returns a copy of 's'. */
-static char *
-shorten_uuid(const char *s)
-{
-    size_t len = strlen(s);
-    return (len >= UUID_LEN && uuid_is_partial_string(s + (len - UUID_LEN))
-            ? xmemdup0(s, len - (UUID_LEN - 6))
-            : xstrdup(s));
-}
-
-static void
-read_datapaths(void)
-{
-    hmap_init(&datapaths);
-    const struct sbrec_datapath_binding *sbdb;
-    SBREC_DATAPATH_BINDING_FOR_EACH (sbdb, ovnsb_idl) {
-        struct ovntrace_datapath *dp = xzalloc(sizeof *dp);
-        const struct smap *ids = &sbdb->external_ids;
-
-        dp->sb_uuid = sbdb->header_.uuid;
-        if (!smap_get_uuid(ids, "logical-switch", &dp->nb_uuid) &&
-            !smap_get_uuid(ids, "logical-router", &dp->nb_uuid)) {
-            dp->nb_uuid = dp->sb_uuid;
-        }
-
-        const char *name = smap_get(ids, "name");
-        dp->name = (name
-                    ? xstrdup(name)
-                    : xasprintf(UUID_FMT, UUID_ARGS(&dp->nb_uuid)));
-
-        dp->name2 = nullable_xstrdup(smap_get(ids, "name2"));
-        dp->friendly_name = (!use_friendly_names
-                             ? xasprintf(UUID_FMT, UUID_ARGS(&dp->nb_uuid))
-                             : shorten_uuid(dp->name2 ? dp->name2 : dp->name));
-
-        dp->tunnel_key = sbdb->tunnel_key;
-
-        ovs_list_init(&dp->mcgroups);
-        hmap_init(&dp->mac_bindings);
-
-        hmap_insert(&datapaths, &dp->sb_uuid_node, uuid_hash(&dp->sb_uuid));
-    }
-}
-
-static void
-read_ports(void)
-{
-    shash_init(&ports);
-    const struct sbrec_port_binding *sbpb;
-    SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) {
-        const char *port_name = sbpb->logical_port;
-        struct ovntrace_datapath *dp
-            = ovntrace_datapath_find_by_sb_uuid(&sbpb->datapath->header_.uuid);
-        if (!dp) {
-            VLOG_WARN("logical port %s missing datapath", port_name);
-            continue;
-        }
-
-        struct ovntrace_port *port = xzalloc(sizeof *port);
-        if (!shash_add_once(&ports, port_name, port)) {
-            VLOG_WARN("duplicate logical port name %s", port_name);
-            free(port);
-            continue;
-        }
-        port->dp = dp;
-        port->uuid = sbpb->header_.uuid;
-        port->name = xstrdup(port_name);
-        port->type = xstrdup(sbpb->type);
-        port->tunnel_key = sbpb->tunnel_key;
-
-        port->name2 = nullable_xstrdup(smap_get(&sbpb->external_ids, "name"));
-        port->friendly_name = (!use_friendly_names ? xstrdup(port->name)
-                               : shorten_uuid(port->name2
-                                              ? port->name2 : port->name));
-
-        if (!strcmp(sbpb->type, "patch")) {
-            const char *peer_name = smap_get(&sbpb->options, "peer");
-            if (peer_name) {
-                struct ovntrace_port *peer
-                    = shash_find_data(&ports, peer_name);
-                if (peer) {
-                    port->peer = peer;
-                    port->peer->peer = port;
-                }
-            }
-        } else if (!strcmp(sbpb->type, "l3gateway")) {
-            /* Treat all gateways as local for our purposes. */
-            dp->has_local_l3gateway = true;
-            const char *peer_name = smap_get(&sbpb->options, "peer");
-            if (peer_name) {
-                struct ovntrace_port *peer
-                    = shash_find_data(&ports, peer_name);
-                if (peer) {
-                    port->peer = peer;
-                    port->peer->peer = port;
-                }
-            }
-        }
-    }
-
-    SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) {
-        if (!strcmp(sbpb->type, "chassisredirect")) {
-            struct ovntrace_port *port
-                = shash_find_data(&ports, sbpb->logical_port);
-            if (port) {
-                const char *distributed_name = smap_get(&sbpb->options,
-                                                       "distributed-port");
-                if (distributed_name) {
-                    struct ovntrace_port *distributed_port
-                        = shash_find_data(&ports, distributed_name);
-                    if (distributed_port && distributed_port->dp == port->dp) {
-                        port->distributed_port = distributed_port;
-                    }
-                }
-            }
-        }
-    }
-}
-
-static int
-compare_port(const void *a_, const void *b_)
-{
-    struct ovntrace_port *const *ap = a_;
-    struct ovntrace_port *const *bp = b_;
-    const struct ovntrace_port *a = *ap;
-    const struct ovntrace_port *b = *bp;
-
-    return strcmp(a->name, b->name);
-}
-
-static void
-read_mcgroups(void)
-{
-    const struct sbrec_multicast_group *sbmg;
-    SBREC_MULTICAST_GROUP_FOR_EACH (sbmg, ovnsb_idl) {
-        struct ovntrace_datapath *dp
-            = ovntrace_datapath_find_by_sb_uuid(&sbmg->datapath->header_.uuid);
-        if (!dp) {
-            VLOG_WARN("logical multicast group %s missing datapath",
-                      sbmg->name);
-            continue;
-        }
-
-        struct ovntrace_mcgroup *mcgroup = xzalloc(sizeof *mcgroup);
-        ovs_list_push_back(&dp->mcgroups, &mcgroup->list_node);
-        mcgroup->dp = dp;
-        mcgroup->tunnel_key = sbmg->tunnel_key;
-        mcgroup->name = xstrdup(sbmg->name);
-        mcgroup->ports = xmalloc(sbmg->n_ports * sizeof *mcgroup->ports);
-        for (size_t i = 0; i < sbmg->n_ports; i++) {
-            const char *port_name = sbmg->ports[i]->logical_port;
-            struct ovntrace_port *p = shash_find_data(&ports, port_name);
-            if (!p) {
-                VLOG_WARN("missing port %s", port_name);
-                continue;
-            }
-            if (!uuid_equals(&sbmg->ports[i]->datapath->header_.uuid,
-                             &p->dp->sb_uuid)) {
-                VLOG_WARN("multicast group %s in datapath %s contains "
-                          "port %s outside that datapath",
-                          mcgroup->name, mcgroup->dp->name, port_name);
-                continue;
-            }
-            mcgroup->ports[mcgroup->n_ports++] = p;
-        }
-
-        /* Sort the ports in alphabetical order to make output more
-         * predictable. */
-        qsort(mcgroup->ports, mcgroup->n_ports, sizeof *mcgroup->ports,
-              compare_port);
-    }
-}
-
-static void
-read_address_sets(void)
-{
-    shash_init(&address_sets);
-
-    const struct sbrec_address_set *sbas;
-    SBREC_ADDRESS_SET_FOR_EACH (sbas, ovnsb_idl) {
-        expr_const_sets_add(&address_sets, sbas->name,
-                           (const char *const *) sbas->addresses,
-                           sbas->n_addresses, true);
-    }
-}
-
-static void
-read_port_groups(void)
-{
-    shash_init(&port_groups);
-
-    const struct sbrec_port_group *sbpg;
-    SBREC_PORT_GROUP_FOR_EACH (sbpg, ovnsb_idl) {
-        expr_const_sets_add(&port_groups, sbpg->name,
-                           (const char *const *) sbpg->ports,
-                           sbpg->n_ports, false);
-    }
-}
-
-static bool
-ovntrace_is_chassis_resident(const void *aux OVS_UNUSED,
-                             const char *port_name)
-{
-    if (port_name[0] == '\0') {
-        return true;
-    }
-
-    const struct ovntrace_port *port = shash_find_data(&ports, port_name);
-    if (port) {
-        /* Since ovntrace is not chassis specific, assume any port
-         * that exists is resident. */
-        return true;
-    }
-
-    VLOG_WARN("%s: unknown logical port\n", port_name);
-    return false;
-}
-
-static int
-compare_flow(const void *a_, const void *b_)
-{
-    struct ovntrace_flow *const *ap = a_;
-    struct ovntrace_flow *const *bp = b_;
-    const struct ovntrace_flow *a = *ap;
-    const struct ovntrace_flow *b = *bp;
-
-    if (a->pipeline != b->pipeline) {
-        /* Sort OVNACT_P_INGRESS before OVNACT_P_EGRESS. */
-        return a->pipeline == OVNACT_P_EGRESS ? 1 : -1;
-    } else if (a->table_id != b->table_id) {
-        /* Sort in increasing order of table_id. */
-        return a->table_id > b->table_id ? 1 : -1;
-    } else if (a->priority != b->priority) {
-        /* Sort in decreasing order of priority. */
-        return a->priority > b->priority ? -1 : 1;
-    } else {
-        /* Otherwise who cares. */
-        return 0;
-    }
-}
-
-static char *
-ovntrace_make_names_friendly(const char *in)
-{
-    if (!use_friendly_names) {
-        return xstrdup(in);
-    }
-
-    struct ds out = DS_EMPTY_INITIALIZER;
-    while (*in) {
-        struct lex_token token;
-        const char *start;
-        const char *next;
-
-        next = lex_token_parse(&token, in, &start);
-        if (token.type == LEX_T_STRING) {
-            const struct ovntrace_port *port = shash_find_data(&ports,
-                                                               token.s);
-            if (port) {
-                ds_put_buffer(&out, in, start - in);
-                json_string_escape(port->friendly_name, &out);
-            } else {
-                ds_put_buffer(&out, in, next - in);
-            }
-        } else if (token.type != LEX_T_END) {
-            ds_put_buffer(&out, in, next - in);
-        } else {
-            break;
-        }
-        lex_token_destroy(&token);
-        in = next;
-    }
-    return ds_steal_cstr(&out);
-}
-
-static void
-read_flows(void)
-{
-    ovn_init_symtab(&symtab);
-
-    const struct sbrec_logical_flow *sblf;
-    SBREC_LOGICAL_FLOW_FOR_EACH (sblf, ovnsb_idl) {
-        const struct sbrec_datapath_binding *sbdb = sblf->logical_datapath;
-        struct ovntrace_datapath *dp
-            = ovntrace_datapath_find_by_sb_uuid(&sbdb->header_.uuid);
-        if (!dp) {
-            VLOG_WARN("logical flow missing datapath");
-            continue;
-        }
-
-        char *error;
-        struct expr *match;
-        match = expr_parse_string(sblf->match, &symtab, &address_sets,
-                                  &port_groups, NULL, &error);
-        if (error) {
-            VLOG_WARN("%s: parsing expression failed (%s)",
-                      sblf->match, error);
-            free(error);
-            continue;
-        }
-
-        struct ovnact_parse_params pp = {
-            .symtab = &symtab,
-            .dhcp_opts = &dhcp_opts,
-            .dhcpv6_opts = &dhcpv6_opts,
-            .nd_ra_opts = &nd_ra_opts,
-            .pipeline = (!strcmp(sblf->pipeline, "ingress")
-                         ? OVNACT_P_INGRESS
-                         : OVNACT_P_EGRESS),
-            .n_tables = 24,
-            .cur_ltable = sblf->table_id,
-        };
-        uint64_t stub[1024 / 8];
-        struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(stub);
-        struct expr *prereqs;
-        error = ovnacts_parse_string(sblf->actions, &pp, &ovnacts, &prereqs);
-        if (error) {
-            VLOG_WARN("%s: parsing actions failed (%s)", sblf->actions, error);
-            free(error);
-            expr_destroy(match);
-            continue;
-        }
-
-        match = expr_combine(EXPR_T_AND, match, prereqs);
-        match = expr_annotate(match, &symtab, &error);
-        if (error) {
-            VLOG_WARN("match annotation failed (%s)", error);
-            free(error);
-            expr_destroy(match);
-            ovnacts_free(ovnacts.data, ovnacts.size);
-            ofpbuf_uninit(&ovnacts);
-            continue;
-        }
-        if (match) {
-            match = expr_simplify(match, ovntrace_is_chassis_resident, NULL);
-        }
-
-        struct ovntrace_flow *flow = xzalloc(sizeof *flow);
-        flow->uuid = sblf->header_.uuid;
-        flow->pipeline = (!strcmp(sblf->pipeline, "ingress")
-                          ? OVNACT_P_INGRESS
-                          : OVNACT_P_EGRESS);
-        flow->table_id = sblf->table_id;
-        flow->stage_name = nullable_xstrdup(smap_get(&sblf->external_ids,
-                                                     "stage-name"));
-        flow->source = nullable_xstrdup(smap_get(&sblf->external_ids,
-                                                 "source"));
-        flow->priority = sblf->priority;
-        flow->match_s = ovntrace_make_names_friendly(sblf->match);
-        flow->match = match;
-        flow->ovnacts_len = ovnacts.size;
-        flow->ovnacts = ofpbuf_steal_data(&ovnacts);
-
-        if (dp->n_flows >= dp->allocated_flows) {
-            dp->flows = x2nrealloc(dp->flows, &dp->allocated_flows,
-                                   sizeof *dp->flows);
-        }
-        dp->flows[dp->n_flows++] = flow;
-    }
-
-    const struct ovntrace_datapath *dp;
-    HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) {
-        qsort(dp->flows, dp->n_flows, sizeof *dp->flows, compare_flow);
-    }
-}
-
-static void
-read_gen_opts(void)
-{
-    hmap_init(&dhcp_opts);
-    const struct sbrec_dhcp_options *sdo;
-    SBREC_DHCP_OPTIONS_FOR_EACH (sdo, ovnsb_idl) {
-        dhcp_opt_add(&dhcp_opts, sdo->name, sdo->code, sdo->type);
-    }
-
-
-    hmap_init(&dhcpv6_opts);
-    const struct sbrec_dhcpv6_options *sdo6;
-    SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) {
-       dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type);
-    }
-
-    hmap_init(&nd_ra_opts);
-    nd_ra_opts_init(&nd_ra_opts);
-}
-
-static void
-read_mac_bindings(void)
-{
-    const struct sbrec_mac_binding *sbmb;
-    SBREC_MAC_BINDING_FOR_EACH (sbmb, ovnsb_idl) {
-        const struct ovntrace_port *port = shash_find_data(
-            &ports, sbmb->logical_port);
-        if (!port) {
-            VLOG_WARN("missing port %s", sbmb->logical_port);
-            continue;
-        }
-
-        if (!uuid_equals(&port->dp->sb_uuid, &sbmb->datapath->header_.uuid)) {
-            VLOG_WARN("port %s is in wrong datapath", sbmb->logical_port);
-            continue;
-        }
-
-        struct in6_addr ip6;
-        ovs_be32 ip4;
-        if (ip_parse(sbmb->ip, &ip4)) {
-            ip6 = in6_addr_mapped_ipv4(ip4);
-        } else if (!ipv6_parse(sbmb->ip, &ip6)) {
-            VLOG_WARN("%s: bad IP address", sbmb->ip);
-            continue;
-        }
-
-        struct eth_addr mac;
-        if (!eth_addr_from_string(sbmb->mac, &mac)) {
-            VLOG_WARN("%s: bad Ethernet address", sbmb->mac);
-            continue;
-        }
-
-        struct ovntrace_mac_binding *binding = xmalloc(sizeof *binding);
-        binding->port_key = port->tunnel_key;
-        binding->ip = ip6;
-        binding->mac = mac;
-        hmap_insert(&port->dp->mac_bindings, &binding->node,
-                    hash_mac_binding(binding->port_key, &ip6));
-    }
-}
-
-static void
-read_db(void)
-{
-    read_datapaths();
-    read_ports();
-    read_mcgroups();
-    read_address_sets();
-    read_port_groups();
-    read_gen_opts();
-    read_flows();
-    read_mac_bindings();
-}
-
-static const struct ovntrace_port *
-ovntrace_port_lookup_by_name(const char *name)
-{
-    const struct ovntrace_port *port = shash_find_data(&ports, name);
-    if (port) {
-        return port;
-    }
-
-    const struct ovntrace_port *match = NULL;
-
-    struct shash_node *node;
-    SHASH_FOR_EACH (node, &ports) {
-        port = node->data;
-
-        if (port->name2 && !strcmp(port->name2, name)) {
-            if (match) {
-                VLOG_WARN("name \"%s\" matches multiple ports", name);
-                return NULL;
-            }
-            match = port;
-        }
-    }
-
-    if (uuid_is_partial_string(name) >= 4) {
-        SHASH_FOR_EACH (node, &ports) {
-            port = node->data;
-
-            struct uuid name_uuid;
-            if (uuid_is_partial_match(&port->uuid, name)
-                || (uuid_from_string(&name_uuid, port->name)
-                    && uuid_is_partial_match(&name_uuid, name))) {
-                if (match && match != port) {
-                    VLOG_WARN("name \"%s\" matches multiple ports", name);
-                    return NULL;
-                }
-                match = port;
-            }
-        }
-    }
-
-    return match;
-}
-
-static bool
-ovntrace_lookup_port(const void *dp_, const char *port_name,
-                     unsigned int *portp)
-{
-    const struct ovntrace_datapath *dp = dp_;
-
-    if (port_name[0] == '\0') {
-        *portp = 0;
-        return true;
-    }
-
-    const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name);
-    if (port) {
-        if (port->dp == dp) {
-            *portp = port->tunnel_key;
-            return true;
-        }
-        /* The port is found but not in this datapath. It happens when port
-         * group is used in match conditions. */
-        return false;
-    }
-
-    const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_name(dp, port_name);
-    if (mcgroup) {
-        *portp = mcgroup->tunnel_key;
-        return true;
-    }
-
-    VLOG_WARN("%s: unknown logical port", port_name);
-    return false;
-}
-
-static const struct ovntrace_flow *
-ovntrace_flow_lookup(const struct ovntrace_datapath *dp,
-                     const struct flow *uflow,
-                     uint8_t table_id, enum ovnact_pipeline pipeline)
-{
-    for (size_t i = 0; i < dp->n_flows; i++) {
-        const struct ovntrace_flow *flow = dp->flows[i];
-        if (flow->pipeline == pipeline &&
-            flow->table_id == table_id &&
-            expr_evaluate(flow->match, uflow, ovntrace_lookup_port, dp)) {
-            return flow;
-        }
-    }
-    return NULL;
-}
-
-static char *
-ovntrace_stage_name(const struct ovntrace_datapath *dp,
-                    uint8_t table_id, enum ovnact_pipeline pipeline)
-{
-    for (size_t i = 0; i < dp->n_flows; i++) {
-        const struct ovntrace_flow *flow = dp->flows[i];
-        if (flow->pipeline == pipeline && flow->table_id == table_id) {
-            return xstrdup(flow->stage_name);;
-        }
-    }
-    return NULL;
-}
-
-/* Type of a node within a trace. */
-enum ovntrace_node_type {
-    /* Nodes that may have children (nonterminal nodes). */
-    OVNTRACE_NODE_OUTPUT,
-    OVNTRACE_NODE_MODIFY,
-    OVNTRACE_NODE_ACTION,
-    OVNTRACE_NODE_ERROR,
-
-    /* Nodes that never have children (terminal nodes). */
-    OVNTRACE_NODE_PIPELINE,
-    OVNTRACE_NODE_TABLE,
-    OVNTRACE_NODE_TRANSFORMATION
-};
-
-static bool
-ovntrace_node_type_is_terminal(enum ovntrace_node_type type)
-{
-    switch (type) {
-    case OVNTRACE_NODE_OUTPUT:
-    case OVNTRACE_NODE_MODIFY:
-    case OVNTRACE_NODE_ACTION:
-    case OVNTRACE_NODE_ERROR:
-        return true;
-
-    case OVNTRACE_NODE_PIPELINE:
-    case OVNTRACE_NODE_TABLE:
-    case OVNTRACE_NODE_TRANSFORMATION:
-        return false;
-    }
-
-    OVS_NOT_REACHED();
-}
-
-struct ovntrace_node {
-    struct ovs_list node;       /* In parent. */
-
-    enum ovntrace_node_type type;
-    char *name;
-    bool always_indent;
-    struct ovs_list subs;       /* List of children. */
-};
-
-static void ovntrace_node_destroy(struct ovntrace_node *);
-
-static struct ovntrace_node * OVS_PRINTF_FORMAT(3, 4)
-ovntrace_node_append(struct ovs_list *super, enum ovntrace_node_type type,
-                     const char *format, ...)
-{
-    va_list args;
-    va_start(args, format);
-    char *s = xvasprintf(format, args);
-    va_end(args);
-
-    struct ovntrace_node *node = xmalloc(sizeof *node);
-    ovs_list_push_back(super, &node->node);
-    node->type = type;
-    node->name = s;
-    node->always_indent = false;
-    ovs_list_init(&node->subs);
-
-    return node;
-}
-
-static void
-ovntrace_node_clone(const struct ovs_list *old, struct ovs_list *new)
-{
-    const struct ovntrace_node *osub;
-    LIST_FOR_EACH (osub, node, old) {
-        struct ovntrace_node *nsub = ovntrace_node_append(new, osub->type,
-                                                          "%s", osub->name);
-        nsub->always_indent = osub->always_indent;
-        ovntrace_node_clone(&osub->subs, &nsub->subs);
-    }
-}
-
-static void
-ovntrace_node_list_destroy(struct ovs_list *list)
-{
-    if (list) {
-        struct ovntrace_node *node, *next;
-
-        LIST_FOR_EACH_SAFE (node, next, node, list) {
-            ovs_list_remove(&node->node);
-            ovntrace_node_destroy(node);
-        }
-    }
-}
-
-static void
-ovntrace_node_destroy(struct ovntrace_node *node)
-{
-    if (node) {
-        ovntrace_node_list_destroy(&node->subs);
-        free(node->name);
-        free(node);
-    }
-}
-
-static void
-ovntrace_node_print_details(struct ds *output,
-                            const struct ovs_list *nodes, int level)
-{
-    const struct ovntrace_node *sub;
-    LIST_FOR_EACH (sub, node, nodes) {
-        if (sub->type == OVNTRACE_NODE_MODIFY) {
-            continue;
-        }
-
-        bool more = (sub->node.next != nodes
-                     || sub->always_indent
-                     || ovntrace_node_type_is_terminal(sub->type));
-        bool title = (sub->type == OVNTRACE_NODE_PIPELINE ||
-                      sub->type == OVNTRACE_NODE_TRANSFORMATION);
-        if (title) {
-            ds_put_char(output, '\n');
-        }
-        ds_put_char_multiple(output, ' ', (level + more) * 4);
-        ds_put_format(output, "%s\n", sub->name);
-        if (title) {
-            ds_put_char_multiple(output, ' ', (level + more) * 4);
-            ds_put_char_multiple(output, '-', strlen(sub->name));
-            ds_put_char(output, '\n');
-        }
-
-        ovntrace_node_print_details(output, &sub->subs, level + more + more);
-    }
-}
-
-static void
-ovntrace_node_prune_summary(struct ovs_list *nodes)
-{
-    struct ovntrace_node *sub, *next;
-    LIST_FOR_EACH_SAFE (sub, next, node, nodes) {
-        ovntrace_node_prune_summary(&sub->subs);
-        if (sub->type == OVNTRACE_NODE_MODIFY ||
-            sub->type == OVNTRACE_NODE_TABLE) {
-            /* Replace 'sub' by its children, if any, */
-            ovs_list_remove(&sub->node);
-            ovs_list_splice(&next->node, sub->subs.next, &sub->subs);
-            ovntrace_node_destroy(sub);
-        }
-    }
-}
-
-static void
-ovntrace_node_print_summary(struct ds *output, const struct ovs_list *nodes,
-                            int level)
-{
-    const struct ovntrace_node *sub;
-    LIST_FOR_EACH (sub, node, nodes) {
-        if (sub->type == OVNTRACE_NODE_ACTION
-            && !strncmp(sub->name, "next(", 5)) {
-            continue;
-        }
-
-        ds_put_char_multiple(output, ' ', level * 4);
-        ds_put_cstr(output, sub->name);
-        if (!ovs_list_is_empty(&sub->subs)) {
-            ds_put_cstr(output, " {\n");
-            ovntrace_node_print_summary(output, &sub->subs, level + 1);
-            ds_put_char_multiple(output, ' ', level * 4);
-            ds_put_char(output, '}');
-        }
-        if (sub->type != OVNTRACE_NODE_ACTION) {
-            ds_put_char(output, ';');
-        }
-        ds_put_char(output, '\n');
-    }
-}
-
-static void
-ovntrace_node_prune_hard(struct ovs_list *nodes)
-{
-    struct ovntrace_node *sub, *next;
-    LIST_FOR_EACH_SAFE (sub, next, node, nodes) {
-        ovntrace_node_prune_hard(&sub->subs);
-        if (sub->type == OVNTRACE_NODE_ACTION ||
-            sub->type == OVNTRACE_NODE_PIPELINE ||
-            sub->type == OVNTRACE_NODE_TABLE ||
-            sub->type == OVNTRACE_NODE_OUTPUT) {
-            /* Replace 'sub' by its children, if any, */
-            ovs_list_remove(&sub->node);
-            ovs_list_splice(&next->node, sub->subs.next, &sub->subs);
-            ovntrace_node_destroy(sub);
-        }
-    }
-}
-
-static void
-execute_load(const struct ovnact_load *load,
-             const struct ovntrace_datapath *dp, struct flow *uflow,
-             struct ovs_list *super OVS_UNUSED)
-{
-    const struct ovnact_encode_params ep = {
-        .lookup_port = ovntrace_lookup_port,
-        .aux = dp,
-    };
-    uint64_t stub[512 / 8];
-    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
-
-    ovnacts_encode(&load->ovnact, sizeof *load, &ep, &ofpacts);
-
-    struct ofpact *a;
-    OFPACT_FOR_EACH (a, ofpacts.data, ofpacts.size) {
-        struct ofpact_set_field *sf = ofpact_get_SET_FIELD(a);
-
-        if (!mf_is_register(sf->field->id)) {
-            struct ds s = DS_EMPTY_INITIALIZER;
-            ovnacts_format(&load->ovnact, OVNACT_LOAD_SIZE, &s);
-            ds_chomp(&s, ';');
-
-            char *friendly = ovntrace_make_names_friendly(ds_cstr(&s));
-            ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", friendly);
-            free(friendly);
-
-            ds_destroy(&s);
-        }
-
-        if (mf_are_prereqs_ok(sf->field, uflow, NULL)) {
-            mf_set_flow_value_masked(sf->field, sf->value,
-                                     ofpact_set_field_mask(sf), uflow);
-        }
-    }
-    ofpbuf_uninit(&ofpacts);
-}
-
-static void
-summarize_move(const struct mf_subfield *rsrc,
-               const struct expr_field *dst, const struct mf_subfield *rdst,
-               const struct flow *uflow, struct ovs_list *super OVS_UNUSED)
-{
-    if (!mf_is_register(rdst->field->id)) {
-        struct ds s = DS_EMPTY_INITIALIZER;
-        expr_field_format(dst, &s);
-        ds_put_cstr(&s, " = ");
-
-        if (rsrc->ofs == 0 && rsrc->n_bits >= rsrc->field->n_bits) {
-            union mf_value value;
-            mf_get_value(rsrc->field, uflow, &value);
-            mf_format(rsrc->field, &value, NULL, NULL, &s);
-        } else {
-            union mf_subvalue cst;
-            mf_read_subfield(rsrc, uflow, &cst);
-            ds_put_hex(&s, &cst, sizeof cst);
-        }
-
-        ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", ds_cstr(&s));
-
-        ds_destroy(&s);
-    }
-}
-
-static void
-execute_move(const struct ovnact_move *move, struct flow *uflow,
-             struct ovs_list *super)
-{
-    struct mf_subfield dst = expr_resolve_field(&move->lhs);
-    struct mf_subfield src = expr_resolve_field(&move->rhs);
-    summarize_move(&src, &move->lhs, &dst, uflow, super);
-    mf_subfield_copy(&src, &dst, uflow, NULL);
-}
-
-static void
-execute_exchange(const struct ovnact_move *move, struct flow *uflow,
-             struct ovs_list *super)
-{
-    struct mf_subfield a = expr_resolve_field(&move->lhs);
-    struct mf_subfield b = expr_resolve_field(&move->rhs);
-    summarize_move(&b, &move->lhs, &a, uflow, super);
-    summarize_move(&a, &move->rhs, &b, uflow, super);
-    mf_subfield_swap(&a, &b, uflow, NULL);
-}
-
-static void
-trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
-        uint8_t table_id, enum ovnact_pipeline pipeline,
-        struct ovs_list *super);
-
-static void
-trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
-              const struct ovntrace_datapath *dp, struct flow *uflow,
-              uint8_t table_id, enum ovnact_pipeline pipeline,
-              struct ovs_list *super);
-static void
-execute_output(const struct ovntrace_datapath *dp, struct flow *uflow,
-               enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    uint16_t key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0];
-    if (!key) {
-        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                             "*** output to null logical port");
-        return;
-    }
-
-    const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key);
-    const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_key(dp,
-                                                                          key);
-    const char *out_name = (port ? port->friendly_name
-                            : mcgroup ? mcgroup->name
-                            : "(unnamed)");
-    if (!port && !mcgroup) {
-        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                             "*** unknown port or multicast group %"PRIu16,
-                             key);
-    }
-
-    if (pipeline == OVNACT_P_EGRESS) {
-        ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT,
-                             "/* output to \"%s\", type \"%s\" */",
-                             out_name, port ? port->type : "");
-        if (port && port->peer) {
-            const struct ovntrace_port *peer = port->peer;
-
-            struct ovntrace_node *node = ovntrace_node_append(
-                super, OVNTRACE_NODE_PIPELINE,
-                "ingress(dp=\"%s\", inport=\"%s\")",
-                peer->dp->friendly_name, peer->friendly_name);
-
-            struct flow new_uflow = *uflow;
-            new_uflow.regs[MFF_LOG_INPORT - MFF_REG0] = peer->tunnel_key;
-            new_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = 0;
-            trace__(peer->dp, &new_uflow, 0, OVNACT_P_INGRESS, &node->subs);
-        } else {
-            ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
-                                 "output(\"%s\")", out_name);
-
-        }
-        return;
-    }
-
-    struct flow egress_uflow = *uflow;
-    for (int i = 0; i < FLOW_N_REGS; i++) {
-        if (i != MFF_LOG_INPORT - MFF_REG0 &&
-            i != MFF_LOG_OUTPORT - MFF_REG0) {
-            egress_uflow.regs[i] = 0;
-        }
-    }
-
-    uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0];
-    const char *inport_name = ovntrace_port_key_to_name(dp, in_key);
-    uint32_t flags = uflow->regs[MFF_LOG_FLAGS - MFF_REG0];
-    bool allow_loopback = (flags & MLF_ALLOW_LOOPBACK) != 0;
-
-    if (mcgroup) {
-        struct ovntrace_node *mcnode = ovntrace_node_append(
-            super, OVNTRACE_NODE_PIPELINE,
-            "multicast(dp=\"%s\", mcgroup=\"%s\")",
-            dp->friendly_name, mcgroup->name);
-        for (size_t i = 0; i < mcgroup->n_ports; i++) {
-            const struct ovntrace_port *p = mcgroup->ports[i];
-
-            struct ovntrace_node *node = ovntrace_node_append(
-                &mcnode->subs, OVNTRACE_NODE_PIPELINE,
-                "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")",
-                dp->friendly_name, inport_name, p->friendly_name);
-
-            if (p->tunnel_key != in_key || allow_loopback) {
-                node->always_indent = true;
-
-                egress_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = p->tunnel_key;
-                trace__(dp, &egress_uflow, 0, OVNACT_P_EGRESS, &node->subs);
-            } else {
-                ovntrace_node_append(&node->subs, OVNTRACE_NODE_OUTPUT,
-                                     "/* omitting output because inport == outport && !flags.loopback */");
-            }
-        }
-        return;
-    }
-
-    if (port && !strcmp(port->type, "chassisredirect")) {
-        if (port->distributed_port) {
-            ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT,
-                                 "/* Replacing type \"%s\" outport \"%s\""
-                                 " with distributed port \"%s\". */",
-                                 port->type, port->friendly_name,
-                                 port->distributed_port->friendly_name);
-            port = port->distributed_port;
-            out_name = port->friendly_name;
-            egress_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = port->tunnel_key;
-        } else {
-            ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                                 "*** output to type \"%s\" port \"%s\""
-                                 " with no or invalid distributed port",
-                                 port->type, out_name);
-            return;
-        }
-    }
-
-    if ((port && port->tunnel_key != in_key) || allow_loopback) {
-        struct ovntrace_node *node = ovntrace_node_append(
-            super, OVNTRACE_NODE_PIPELINE,
-            "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")",
-            dp->friendly_name, inport_name, out_name);
-
-        trace__(dp, &egress_uflow, 0, OVNACT_P_EGRESS, &node->subs);
-    } else {
-        ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT,
-                             "/* omitting output because inport == outport && !flags.loopback */");
-    }
-}
-
-static void
-execute_clone(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
-              const struct flow *uflow, uint8_t table_id,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow cloned_flow = *uflow;
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "clone");
-
-    trace_actions(on->nested, on->nested_len, dp, &cloned_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_arp(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
-            const struct flow *uflow, uint8_t table_id,
-            enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow arp_flow = *uflow;
-
-    /* Zero fields that are no longer relevant. */
-    arp_flow.nw_frag = 0;
-    arp_flow.nw_tos = 0;
-    arp_flow.nw_ttl = 0;
-    arp_flow.tcp_flags = 0;
-
-    /* Update fields for ARP. */
-    arp_flow.dl_type = htons(ETH_TYPE_ARP);
-    arp_flow.nw_proto = ARP_OP_REQUEST;
-    arp_flow.arp_sha = arp_flow.dl_src;
-    arp_flow.arp_tha = eth_addr_zero;
-    /* ARP SPA is already in arp_flow.nw_src. */
-    /* ARP TPA is already in arp_flow.nw_dst. */
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "arp");
-
-    trace_actions(on->nested, on->nested_len, dp, &arp_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_nd_na(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
-              const struct flow *uflow, uint8_t table_id,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow na_flow = *uflow;
-
-    /* Update fields for NA. */
-    na_flow.dl_src = uflow->dl_dst;
-    na_flow.dl_dst = uflow->dl_src;
-    na_flow.ipv6_dst = uflow->ipv6_src;
-    na_flow.ipv6_src = uflow->nd_target;
-    na_flow.tp_src = htons(136);
-    na_flow.arp_sha = eth_addr_zero;
-    na_flow.arp_tha = uflow->dl_dst;
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "nd_na");
-
-    trace_actions(on->nested, on->nested_len, dp, &na_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_nd_ns(const struct ovnact_nest *on, const struct ovntrace_datapath *dp,
-              const struct flow *uflow, uint8_t table_id,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow na_flow = *uflow;
-
-    /* Update fields for NA. */
-    na_flow.dl_src = uflow->dl_src;
-    na_flow.ipv6_src = uflow->ipv6_src;
-    na_flow.ipv6_dst = uflow->ipv6_dst;
-    struct in6_addr sn_addr;
-    in6_addr_solicited_node(&sn_addr, &uflow->ipv6_dst);
-    ipv6_multicast_to_ethernet(&na_flow.dl_dst, &sn_addr);
-    na_flow.tp_src = htons(135);
-    na_flow.arp_sha = eth_addr_zero;
-    na_flow.arp_tha = uflow->dl_dst;
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "nd_ns");
-
-    trace_actions(on->nested, on->nested_len, dp, &na_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_icmp4(const struct ovnact_nest *on,
-              const struct ovntrace_datapath *dp,
-              const struct flow *uflow, uint8_t table_id,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow icmp4_flow = *uflow;
-
-    /* Update fields for ICMP. */
-    icmp4_flow.dl_dst = uflow->dl_dst;
-    icmp4_flow.dl_src = uflow->dl_src;
-    icmp4_flow.nw_dst = uflow->nw_dst;
-    icmp4_flow.nw_src = uflow->nw_src;
-    icmp4_flow.nw_proto = IPPROTO_ICMP;
-    icmp4_flow.nw_ttl = 255;
-    icmp4_flow.tp_src = htons(ICMP4_DST_UNREACH); /* icmp type */
-    icmp4_flow.tp_dst = htons(1); /* icmp code */
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "icmp4");
-
-    trace_actions(on->nested, on->nested_len, dp, &icmp4_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_icmp6(const struct ovnact_nest *on,
-              const struct ovntrace_datapath *dp,
-              const struct flow *uflow, uint8_t table_id,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow icmp6_flow = *uflow;
-
-    /* Update fields for ICMPv6. */
-    icmp6_flow.dl_dst = uflow->dl_dst;
-    icmp6_flow.dl_src = uflow->dl_src;
-    icmp6_flow.ipv6_dst = uflow->ipv6_dst;
-    icmp6_flow.ipv6_src = uflow->ipv6_src;
-    icmp6_flow.nw_proto = IPPROTO_ICMPV6;
-    icmp6_flow.nw_ttl = 255;
-    icmp6_flow.tp_src = htons(ICMP6_DST_UNREACH); /* icmp type */
-    icmp6_flow.tp_dst = htons(1); /* icmp code */
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "icmp6");
-
-    trace_actions(on->nested, on->nested_len, dp, &icmp6_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_tcp_reset(const struct ovnact_nest *on,
-                  const struct ovntrace_datapath *dp,
-                  const struct flow *uflow, uint8_t table_id,
-                  enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow tcp_flow = *uflow;
-
-    /* Update fields for TCP segment. */
-    tcp_flow.dl_dst = uflow->dl_dst;
-    tcp_flow.dl_src = uflow->dl_src;
-    tcp_flow.nw_dst = uflow->nw_dst;
-    tcp_flow.nw_src = uflow->nw_src;
-    tcp_flow.nw_proto = IPPROTO_TCP;
-    tcp_flow.nw_ttl = 255;
-    tcp_flow.tp_src = uflow->tp_src;
-    tcp_flow.tp_dst = uflow->tp_dst;
-    tcp_flow.tcp_flags = htons(TCP_RST);
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "tcp_reset");
-
-    trace_actions(on->nested, on->nested_len, dp, &tcp_flow,
-                  table_id, pipeline, &node->subs);
-}
-
-static void
-execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
-                     const struct ovntrace_datapath *dp,
-                     struct flow *uflow, struct ovs_list *super)
-{
-    /* Get logical port number.*/
-    struct mf_subfield port_sf = expr_resolve_field(&bind->port);
-    ovs_assert(port_sf.n_bits == 32);
-    uint32_t port_key = mf_get_subfield(&port_sf, uflow);
-
-    /* Get IP address. */
-    struct mf_subfield ip_sf = expr_resolve_field(&bind->ip);
-    ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128);
-    union mf_subvalue ip_sv;
-    mf_read_subfield(&ip_sf, uflow, &ip_sv);
-    struct in6_addr ip = (ip_sf.n_bits == 32
-                          ? in6_addr_mapped_ipv4(ip_sv.ipv4)
-                          : ip_sv.ipv6);
-
-    const struct ovntrace_mac_binding *binding
-        = ovntrace_mac_binding_find(dp, port_key, &ip);
-
-    uflow->dl_dst = binding ? binding->mac : eth_addr_zero;
-    if (binding) {
-        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
-                             "/* MAC binding to "ETH_ADDR_FMT". */",
-                             ETH_ADDR_ARGS(uflow->dl_dst));
-    } else {
-        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
-                             "/* No MAC binding. */");
-    }
-    ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
-                         "eth.dst = "ETH_ADDR_FMT,
-                         ETH_ADDR_ARGS(uflow->dl_dst));
-}
-
-static void
-execute_put_opts(const struct ovnact_put_opts *po,
-                 const char *name, struct flow *uflow,
-                 struct ovs_list *super)
-{
-    /* Format the put_dhcp_opts action. */
-    struct ds s = DS_EMPTY_INITIALIZER;
-    for (const struct ovnact_gen_option *o = po->options;
-         o < &po->options[po->n_options]; o++) {
-        if (o != po->options) {
-            ds_put_cstr(&s, ", ");
-        }
-        ds_put_format(&s, "%s = ", o->option->name);
-        expr_constant_set_format(&o->value, &s);
-    }
-    ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s(%s)",
-                         name, ds_cstr(&s));
-
-    struct mf_subfield dst = expr_resolve_field(&po->dst);
-    if (!mf_is_register(dst.field->id)) {
-        /* Format assignment. */
-        ds_clear(&s);
-        expr_field_format(&po->dst, &s);
-        ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
-                             "%s = 1", ds_cstr(&s));
-    }
-    ds_destroy(&s);
-
-    struct mf_subfield sf = expr_resolve_field(&po->dst);
-    union mf_subvalue sv = { .u8_val = 1 };
-    mf_write_subfield_flow(&sf, &sv, uflow);
-}
-
-static void
-execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
-                      const char *name, struct flow *uflow,
-                      struct ovs_list *super)
-{
-    ovntrace_node_append(
-        super, OVNTRACE_NODE_ERROR,
-        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
-    execute_put_opts(pdo, name, uflow, super);
-}
-
-static void
-execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
-                       const char *name, struct flow *uflow,
-                       struct ovs_list *super)
-{
-    execute_put_opts(pdo, name, uflow, super);
-}
-
-static void
-execute_next(const struct ovnact_next *next,
-             const struct ovntrace_datapath *dp, struct flow *uflow,
-             enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    if (pipeline != next->pipeline) {
-        ovs_assert(next->pipeline == OVNACT_P_INGRESS);
-
-        uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0];
-        struct ovntrace_node *node = ovntrace_node_append(
-            super, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")",
-            dp->friendly_name, ovntrace_port_key_to_name(dp, in_key));
-        super = &node->subs;
-    }
-    trace__(dp, uflow, next->ltable, next->pipeline, super);
-}
-
-
-static void
-execute_dns_lookup(const struct ovnact_dns_lookup *dl, struct flow *uflow,
-                   struct ovs_list *super)
-{
-    struct mf_subfield sf = expr_resolve_field(&dl->dst);
-    union mf_subvalue sv = { .u8_val = 0 };
-    mf_write_subfield_flow(&sf, &sv, uflow);
-    ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                         "*** dns_lookup action not implemented");
-}
-
-static void
-execute_ct_next(const struct ovnact_ct_next *ct_next,
-                const struct ovntrace_datapath *dp, struct flow *uflow,
-                enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    /* Figure out ct_state. */
-    uint32_t state;
-    const char *comment;
-    if (ct_state_idx < n_ct_states) {
-        state = ct_states[ct_state_idx++];
-        comment = "";
-    } else {
-        state = CS_ESTABLISHED | CS_TRACKED;
-        comment = " /* default (use --ct to customize) */";
-    }
-
-    /* Make a sub-node for attaching the next table. */
-    struct ds s = DS_EMPTY_INITIALIZER;
-    format_flags(&s, ct_state_to_string, state, '|');
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "ct_next(ct_state=%s%s)",
-        ds_cstr(&s), comment);
-    ds_destroy(&s);
-
-    /* Trace the actions in the next table. */
-    struct flow ct_flow = *uflow;
-    ct_flow.ct_state = state;
-    trace__(dp, &ct_flow, ct_next->ltable, pipeline, &node->subs);
-
-    /* Upon return, we will trace the actions following the ct action in the
-     * original table.  The pipeline forked, so we're using the original
-     * flow, not ct_flow. */
-}
-
-static void
-execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
-               const struct ovntrace_datapath *dp, struct flow *uflow,
-               enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    bool is_dst = ct_nat->ovnact.type == OVNACT_CT_DNAT;
-    if (!is_dst && dp->has_local_l3gateway && !ct_nat->ip) {
-        /* "ct_snat;" has no visible effect in a gateway router. */
-        return;
-    }
-    const char *direction = is_dst ? "dst" : "src";
-
-    /* Make a sub-node for attaching the next table,
-     * and figure out the changes if any. */
-    struct flow ct_flow = *uflow;
-    struct ds s = DS_EMPTY_INITIALIZER;
-    ds_put_format(&s, "ct_%cnat", direction[0]);
-    if (ct_nat->ip) {
-        ds_put_format(&s, "(ip4.%s="IP_FMT")", direction, IP_ARGS(ct_nat->ip));
-        ovs_be32 *ip = is_dst ? &ct_flow.nw_dst : &ct_flow.nw_src;
-        *ip = ct_nat->ip;
-
-        uint8_t state = is_dst ? CS_DST_NAT : CS_SRC_NAT;
-        ct_flow.ct_state |= state;
-    } else {
-        ds_put_format(&s, " /* assuming no un-%cnat entry, so no change */",
-                      direction[0]);
-    }
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "%s", ds_cstr(&s));
-    ds_destroy(&s);
-
-    /* Trace the actions in the next table. */
-    trace__(dp, &ct_flow, ct_nat->ltable, pipeline, &node->subs);
-
-    /* Upon return, we will trace the actions following the ct action in the
-     * original table.  The pipeline forked, so we're using the original
-     * flow, not ct_flow. */
-}
-
-static void
-execute_ct_lb(const struct ovnact_ct_lb *ct_lb,
-              const struct ovntrace_datapath *dp, struct flow *uflow,
-              enum ovnact_pipeline pipeline, struct ovs_list *super)
-{
-    struct flow ct_lb_flow = *uflow;
-
-    int family = (ct_lb_flow.dl_type == htons(ETH_TYPE_IP) ? AF_INET
-                  : ct_lb_flow.dl_type == htons(ETH_TYPE_IPV6) ? AF_INET6
-                  : AF_UNSPEC);
-    if (family != AF_UNSPEC) {
-        const struct ovnact_ct_lb_dst *dst = NULL;
-        if (ct_lb->n_dsts) {
-            /* For ct_lb with addresses, choose one of the addresses. */
-            int n = 0;
-            for (int i = 0; i < ct_lb->n_dsts; i++) {
-                const struct ovnact_ct_lb_dst *d = &ct_lb->dsts[i];
-                if (d->family != family) {
-                    continue;
-                }
-
-                /* Check for the destination specified by --lb-dst, if any. */
-                if (lb_dst.family == family
-                    && (family == AF_INET
-                        ? d->ipv4 == lb_dst.ipv4
-                        : ipv6_addr_equals(&d->ipv6, &lb_dst.ipv6))) {
-                    lb_dst.family = AF_UNSPEC;
-                    dst = d;
-                    break;
-                }
-
-                /* Select a random destination as a fallback. */
-                if (!random_range(++n)) {
-                    dst = d;
-                }
-            }
-
-            if (!dst) {
-                ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                                     "*** no load balancing destination "
-                                     "(use --lb-dst)");
-            }
-        } else if (lb_dst.family == family) {
-            /* For ct_lb without addresses, use user-specified address. */
-            dst = &lb_dst;
-        }
-
-        if (dst) {
-            if (family == AF_INET6) {
-                ct_lb_flow.ipv6_dst = dst->ipv6;
-            } else {
-                ct_lb_flow.nw_dst = dst->ipv4;
-            }
-            if (dst->port) {
-                ct_lb_flow.tp_dst = htons(dst->port);
-            }
-            ct_lb_flow.ct_state |= CS_DST_NAT;
-        }
-    }
-
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TRANSFORMATION, "ct_lb");
-    trace__(dp, &ct_lb_flow, ct_lb->ltable, pipeline, &node->subs);
-}
-
-static void
-execute_log(const struct ovnact_log *log, struct flow *uflow,
-            struct ovs_list *super)
-{
-    char *packet_str = flow_to_string(uflow, NULL);
-    ovntrace_node_append(super, OVNTRACE_NODE_TRANSFORMATION,
-                    "LOG: ACL name=%s, verdict=%s, severity=%s, packet=\"%s\"",
-                    log->name ? log->name : "<unnamed>",
-                    log_verdict_to_string(log->verdict),
-                    log_severity_to_string(log->severity),
-                    packet_str);
-    free(packet_str);
-}
-
-static void
-execute_ovnfield_load(const struct ovnact_load *load,
-                      struct ovs_list *super)
-{
-    const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name);
-    switch (f->id) {
-    case OVN_ICMP4_FRAG_MTU: {
-        ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
-                             "icmp4.frag_mtu = %u",
-                             ntohs(load->imm.value.be16_int));
-        break;
-    }
-
-    case OVN_FIELD_N_IDS:
-    default:
-        OVS_NOT_REACHED();
-    }
-}
-
-static void
-trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
-              const struct ovntrace_datapath *dp, struct flow *uflow,
-              uint8_t table_id, enum ovnact_pipeline pipeline,
-              struct ovs_list *super)
-{
-    if (!ovnacts_len) {
-        ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "drop;");
-        return;
-    }
-
-    struct ds s = DS_EMPTY_INITIALIZER;
-    const struct ovnact *a;
-    OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-        ds_clear(&s);
-        ovnacts_format(a, sizeof *a * (ovnact_next(a) - a), &s);
-        char *friendly = ovntrace_make_names_friendly(ds_cstr(&s));
-        ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", friendly);
-        free(friendly);
-
-        switch (a->type) {
-        case OVNACT_OUTPUT:
-            execute_output(dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_NEXT:
-            execute_next(ovnact_get_NEXT(a), dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_LOAD:
-            execute_load(ovnact_get_LOAD(a), dp, uflow, super);
-            break;
-
-        case OVNACT_MOVE:
-            execute_move(ovnact_get_MOVE(a), uflow, super);
-            break;
-
-        case OVNACT_EXCHANGE:
-            execute_exchange(ovnact_get_EXCHANGE(a), uflow, super);
-            break;
-
-        case OVNACT_DEC_TTL:
-            if (is_ip_any(uflow)) {
-                if (uflow->nw_ttl) {
-                    uflow->nw_ttl--;
-                    ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
-                                         "ip.ttl--");
-                } else {
-                    ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                                         "*** TTL underflow");
-                }
-            } else {
-                ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                                     "*** TTL decrement of non-IP packet");
-            }
-            break;
-
-        case OVNACT_CT_NEXT:
-            execute_ct_next(ovnact_get_CT_NEXT(a), dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_CT_COMMIT:
-            /* Nothing to do. */
-            break;
-
-        case OVNACT_CT_DNAT:
-            execute_ct_nat(ovnact_get_CT_DNAT(a), dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_CT_SNAT:
-            execute_ct_nat(ovnact_get_CT_SNAT(a), dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_CT_LB:
-            execute_ct_lb(ovnact_get_CT_LB(a), dp, uflow, pipeline, super);
-            break;
-
-        case OVNACT_CT_CLEAR:
-            flow_clear_conntrack(uflow);
-            break;
-
-        case OVNACT_CLONE:
-            execute_clone(ovnact_get_CLONE(a), dp, uflow, table_id, pipeline,
-                          super);
-            break;
-
-        case OVNACT_ARP:
-            execute_arp(ovnact_get_ARP(a), dp, uflow, table_id, pipeline,
-                        super);
-            break;
-
-        case OVNACT_ND_NA:
-        case OVNACT_ND_NA_ROUTER:
-            execute_nd_na(ovnact_get_ND_NA(a), dp, uflow, table_id, pipeline,
-                          super);
-            break;
-
-        case OVNACT_ND_NS:
-            execute_nd_ns(ovnact_get_ND_NS(a), dp, uflow, table_id, pipeline,
-                          super);
-            break;
-
-        case OVNACT_GET_ARP:
-            execute_get_mac_bind(ovnact_get_GET_ARP(a), dp, uflow, super);
-            break;
-
-        case OVNACT_GET_ND:
-            execute_get_mac_bind(ovnact_get_GET_ND(a), dp, uflow, super);
-            break;
-
-        case OVNACT_PUT_ARP:
-        case OVNACT_PUT_ND:
-            /* Nothing to do for tracing. */
-            break;
-
-        case OVNACT_PUT_DHCPV4_OPTS:
-            execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a),
-                                  "put_dhcp_opts", uflow, super);
-            break;
-
-        case OVNACT_PUT_DHCPV6_OPTS:
-            execute_put_dhcp_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
-                                  "put_dhcpv6_opts", uflow, super);
-            break;
-
-        case OVNACT_PUT_ND_RA_OPTS:
-            execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
-                                   "put_nd_ra_opts", uflow, super);
-            break;
-
-        case OVNACT_SET_QUEUE:
-            /* The set_queue action is slippery from a logical perspective.  It
-             * has no visible effect as long as the packet remains on the same
-             * chassis: it can bounce from one logical datapath to another
-             * retaining the queue and even end up at a VM on the same chassis.
-             * Without taking the physical arrangement into account, we can't
-             * do anything with this action other than just to note that it
-             * happened.  If we ever add some physical knowledge to ovn-trace,
-             * though, it would be easy enough to track the queue information
-             * by adjusting uflow->skb_priority. */
-            break;
-
-        case OVNACT_DNS_LOOKUP:
-            execute_dns_lookup(ovnact_get_DNS_LOOKUP(a), uflow, super);
-            break;
-
-        case OVNACT_LOG:
-            execute_log(ovnact_get_LOG(a), uflow, super);
-            break;
-
-        case OVNACT_SET_METER:
-            /* Nothing to do. */
-            break;
-
-        case OVNACT_ICMP4:
-            execute_icmp4(ovnact_get_ICMP4(a), dp, uflow, table_id, pipeline,
-                          super);
-            break;
-
-        case OVNACT_ICMP4_ERROR:
-            execute_icmp4(ovnact_get_ICMP4_ERROR(a), dp, uflow, table_id,
-                          pipeline, super);
-            break;
-
-        case OVNACT_ICMP6:
-            execute_icmp6(ovnact_get_ICMP6(a), dp, uflow, table_id, pipeline,
-                          super);
-            break;
-
-        case OVNACT_IGMP:
-            /* Nothing to do for tracing. */
-            break;
-
-        case OVNACT_TCP_RESET:
-            execute_tcp_reset(ovnact_get_TCP_RESET(a), dp, uflow, table_id,
-                              pipeline, super);
-            break;
-
-        case OVNACT_OVNFIELD_LOAD:
-            execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super);
-            break;
-
-        case OVNACT_TRIGGER_EVENT:
-            break;
-
-        case OVNACT_CHECK_PKT_LARGER:
-            break;
-        }
-    }
-    ds_destroy(&s);
-}
-
-static bool
-may_omit_stage(const struct ovntrace_flow *f, uint8_t table_id)
-{
-    return (f
-            && f->match->type == EXPR_T_BOOLEAN && f->match->boolean
-            && f->ovnacts_len == OVNACT_NEXT_SIZE
-            && f->ovnacts->type == OVNACT_NEXT
-            && ovnact_get_NEXT(f->ovnacts)->ltable == table_id + 1);
-}
-
-static void
-trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super)
-{
-    struct ofputil_flow_stats_request fsr = {
-        .cookie = htonll(f->uuid.parts[0]),
-        .cookie_mask = OVS_BE64_MAX,
-        .out_port = OFPP_ANY,
-        .out_group = OFPG_ANY,
-        .table_id = OFPTT_ALL,
-    };
-
-    struct ofputil_flow_stats *fses;
-    size_t n_fses;
-    int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM,
-                                 &fses, &n_fses);
-    if (error) {
-        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                             "*** error obtaining flow stats (%s)",
-                             ovs_strerror(error));
-        VLOG_WARN("%s: error obtaining flow stats (%s)",
-                  ovs, ovs_strerror(error));
-        return;
-    }
-
-    if (n_fses) {
-        struct ds s = DS_EMPTY_INITIALIZER;
-        for (size_t i = 0; i < n_fses; i++) {
-            ds_clear(&s);
-            ofputil_flow_stats_format(&s, &fses[i], NULL, NULL, true);
-            ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
-                                 "%s", ds_cstr(&s));
-        }
-        ds_destroy(&s);
-    } else {
-        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
-                             "*** no OpenFlow flows");
-    }
-
-    for (size_t i = 0; i < n_fses; i++) {
-        free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
-    }
-    free(fses);
-}
-
-static void
-trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
-        uint8_t table_id, enum ovnact_pipeline pipeline,
-        struct ovs_list *super)
-{
-    const struct ovntrace_flow *f;
-    for (;;) {
-        f = ovntrace_flow_lookup(dp, uflow, table_id, pipeline);
-        if (!may_omit_stage(f, table_id)) {
-            break;
-        }
-        table_id++;
-    }
-
-    struct ds s = DS_EMPTY_INITIALIZER;
-    ds_put_format(&s, "%2d. ", table_id);
-    if (f) {
-        if (f->stage_name && f->source) {
-            ds_put_format(&s, "%s (%s): ", f->stage_name, f->source);
-        } else if (f->stage_name) {
-            ds_put_format(&s, "%s: ", f->stage_name);
-        } else if (f->source) {
-            ds_put_format(&s, "(%s): ", f->source);
-        }
-        ds_put_format(&s, "%s, priority %d, uuid %08x",
-                      f->match_s, f->priority, f->uuid.parts[0]);
-    } else {
-        char *stage_name = ovntrace_stage_name(dp, table_id, pipeline);
-        ds_put_format(&s, "%s%sno match (implicit drop)",
-                      stage_name ? stage_name : "",
-                      stage_name ? ": " : "");
-        free(stage_name);
-    }
-    struct ovntrace_node *node = ovntrace_node_append(
-        super, OVNTRACE_NODE_TABLE, "%s", ds_cstr(&s));
-    ds_destroy(&s);
-
-    if (f) {
-        if (vconn) {
-            trace_openflow(f, &node->subs);
-        }
-        trace_actions(f->ovnacts, f->ovnacts_len, dp, uflow, table_id,
-                      pipeline, &node->subs);
-    }
-}
-
-static char *
-trace(const char *dp_s, const char *flow_s)
-{
-    const struct ovntrace_datapath *dp = ovntrace_datapath_find_by_name(dp_s);
-    if (!dp) {
-        return xasprintf("unknown datapath \"%s\"\n", dp_s);
-    }
-
-    struct flow uflow;
-    char *error = expr_parse_microflow(flow_s, &symtab, &address_sets,
-                                       &port_groups, ovntrace_lookup_port,
-                                       dp, &uflow);
-    if (error) {
-        char *s = xasprintf("error parsing flow: %s\n", error);
-        free(error);
-        return s;
-    }
-
-    uint32_t in_key = uflow.regs[MFF_LOG_INPORT - MFF_REG0];
-    if (!in_key) {
-        VLOG_WARN("microflow does not specify ingress port");
-    }
-    const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key);
-    const char *inport_name = inport ? inport->friendly_name : "(unnamed)";
-
-    struct ds output = DS_EMPTY_INITIALIZER;
-
-    ds_put_cstr(&output, "# ");
-    flow_format(&output, &uflow, NULL);
-    ds_put_char(&output, '\n');
-
-    if (ovs) {
-        int retval = vconn_open_block(ovs, 1 << OFP13_VERSION, 0, -1, &vconn);
-        if (retval) {
-            VLOG_WARN("%s: connection failed (%s)", ovs, ovs_strerror(retval));
-        }
-    }
-
-    struct ovs_list root = OVS_LIST_INITIALIZER(&root);
-    struct ovntrace_node *node = ovntrace_node_append(
-        &root, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")",
-        dp->friendly_name, inport_name);
-    trace__(dp, &uflow, 0, OVNACT_P_INGRESS, &node->subs);
-
-    bool multiple = (detailed + summary + minimal) > 1;
-    if (detailed) {
-        if (multiple) {
-            ds_put_cstr(&output, "# Detailed trace.\n");
-        }
-        ovntrace_node_print_details(&output, &root, 0);
-    }
-
-    if (summary) {
-        if (multiple) {
-            ds_put_cstr(&output, "# Summary trace.\n");
-        }
-        struct ovs_list clone = OVS_LIST_INITIALIZER(&clone);
-        ovntrace_node_clone(&root, &clone);
-        ovntrace_node_prune_summary(&clone);
-        ovntrace_node_print_summary(&output, &clone, 0);
-        ovntrace_node_list_destroy(&clone);
-    }
-
-    if (minimal) {
-        if (multiple) {
-            ds_put_cstr(&output, "# Minimal trace.\n");
-        }
-        ovntrace_node_prune_hard(&root);
-        ovntrace_node_print_summary(&output, &root, 0);
-    }
-
-    ovntrace_node_list_destroy(&root);
-
-    vconn_close(vconn);
-
-    return ds_steal_cstr(&output);
-}
-
-static void
-ovntrace_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
-              const char *argv[] OVS_UNUSED, void *exiting_)
-{
-    bool *exiting = exiting_;
-    *exiting = true;
-    unixctl_command_reply(conn, NULL);
-}
-
-static void
-ovntrace_trace(struct unixctl_conn *conn, int argc,
-               const char *argv[], void *aux OVS_UNUSED)
-{
-    detailed = summary = minimal = false;
-    while (argc > 1 && argv[1][0] == '-') {
-        if (!strcmp(argv[1], "--detailed")) {
-            detailed = true;
-        } else if (!strcmp(argv[1], "--summary")) {
-            summary = true;
-        } else if (!strcmp(argv[1], "--minimal")) {
-            minimal = true;
-        } else if (!strcmp(argv[1], "--all")) {
-            detailed = summary = minimal = true;
-        } else {
-            unixctl_command_reply_error(conn, "unknown option");
-            return;
-        }
-        argc--;
-        argv++;
-    }
-    if (!detailed && !summary && !minimal) {
-        detailed = true;
-    }
-
-    if (argc != 3) {
-        unixctl_command_reply_error(
-            conn, "exactly 2 non-option arguments are required");
-        return;
-    }
-
-    char *output = trace(argv[1], argv[2]);
-    unixctl_command_reply(conn, output);
-    free(output);
-}
diff --git a/ovn/utilities/ovndb-servers.ocf b/ovn/utilities/ovndb-servers.ocf
deleted file mode 100755
index cd4742668..000000000
--- a/ovn/utilities/ovndb-servers.ocf
+++ /dev/null
@@ -1,642 +0,0 @@
-#!/bin/bash
-
-: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
-. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
-: ${OVN_CTL_DEFAULT="/usr/share/openvswitch/scripts/ovn-ctl"}
-: ${NB_MASTER_PORT_DEFAULT="6641"}
-: ${NB_MASTER_PROTO_DEFAULT="tcp"}
-: ${SB_MASTER_PORT_DEFAULT="6642"}
-: ${SB_MASTER_PROTO_DEFAULT="tcp"}
-: ${MANAGE_NORTHD_DEFAULT="no"}
-: ${INACTIVE_PROBE_DEFAULT="5000"}
-: ${LISTEN_ON_MASTER_IP_ONLY_DEFAULT="yes"}
-: ${NB_SSL_KEY_DEFAULT="/etc/openvswitch/ovnnb-privkey.pem"}
-: ${NB_SSL_CERT_DEFAULT="/etc/openvswitch/ovnnb-cert.pem"}
-: ${NB_SSL_CACERT_DEFAULT="/etc/openvswitch/cacert.pem"}
-: ${SB_SSL_KEY_DEFAULT="/etc/openvswitch/ovnsb-privkey.pem"}
-: ${SB_SSL_CERT_DEFAULT="/etc/openvswitch/ovnsb-cert.pem"}
-: ${SB_SSL_CACERT_DEFAULT="/etc/openvswitch/cacert.pem"}
-
-CRM_MASTER="${HA_SBIN_DIR}/crm_master -l reboot"
-CRM_ATTR_REPL_INFO="${HA_SBIN_DIR}/crm_attribute --type crm_config --name OVN_REPL_INFO -s ovn_ovsdb_master_server"
-OVN_CTL=${OCF_RESKEY_ovn_ctl:-${OVN_CTL_DEFAULT}}
-MASTER_IP=${OCF_RESKEY_master_ip}
-NB_MASTER_PORT=${OCF_RESKEY_nb_master_port:-${NB_MASTER_PORT_DEFAULT}}
-NB_MASTER_PROTO=${OCF_RESKEY_nb_master_protocol:-${NB_MASTER_PROTO_DEFAULT}}
-SB_MASTER_PORT=${OCF_RESKEY_sb_master_port:-${SB_MASTER_PORT_DEFAULT}}
-SB_MASTER_PROTO=${OCF_RESKEY_sb_master_protocol:-${SB_MASTER_PROTO_DEFAULT}}
-MANAGE_NORTHD=${OCF_RESKEY_manage_northd:-${MANAGE_NORTHD_DEFAULT}}
-INACTIVE_PROBE=${OCF_RESKEY_inactive_probe_interval:-${INACTIVE_PROBE_DEFAULT}}
-NB_PRIVKEY=${OCF_RESKEY_ovn_nb_db_privkey:-${NB_SSL_KEY_DEFAULT}}
-NB_CERT=${OCF_RESKEY_ovn_nb_db_cert:-${NB_SSL_CERT_DEFAULT}}
-NB_CACERT=${OCF_RESKEY_ovn_nb_db_cacert:-${NB_SSL_CACERT_DEFAULT}}
-SB_PRIVKEY=${OCF_RESKEY_ovn_sb_db_privkey:-${SB_SSL_KEY_DEFAULT}}
-SB_CERT=${OCF_RESKEY_ovn_sb_db_cert:-${SB_SSL_CERT_DEFAULT}}
-SB_CACERT=${OCF_RESKEY_ovn_sb_db_cacert:-${SB_SSL_CACERT_DEFAULT}}
-
-
-# In order for pacemaker to work with LB, we can set LISTEN_ON_MASTER_IP_ONLY
-# to false and pass LB vip IP while creating pcs resource.
-LISTEN_ON_MASTER_IP_ONLY=${OCF_RESKEY_listen_on_master_ip_only:-${LISTEN_ON_MASTER_IP_ONLY_DEFAULT}}
-
-# Invalid IP address is an address that can never exist in the network, as
-# mentioned in rfc-5737. The ovsdb servers connects to this IP address till
-# a master is promoted and the IPAddr2 resource is started.
-INVALID_IP_ADDRESS=192.0.2.254
-
-host_name=$(ocf_attribute_target)
-if [ "x$host_name" = "x" ]; then
-    # function ocf_attribute_target may not be available if the pacemaker
-    # version is old. Fall back to ocf_local_nodename.
-    host_name=$(ocf_local_nodename)
-fi
-: ${slave_score=5}
-: ${master_score=10}
-
-ovsdb_server_metadata() {
-    cat <<END
-<?xml version="1.0"?>
-<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
-<resource-agent name="ovsdb-server">
-  <version>1.0</version>
-
-  <longdesc lang="en">
-    This resource manages ovsdb-server.
-  </longdesc>
-
-  <shortdesc lang="en">
-    Manages ovsdb-server.
-  </shortdesc>
-
-  <parameters>
-
-  <parameter name="ovn_ctl" unique="1">
-  <longdesc lang="en">
-  Location to the ovn-ctl script file
-  </longdesc>
-  <shortdesc lang="en">ovn-ctl script</shortdesc>
-  <content type="string" default="${OVN_CTL_DEFAULT}" />
-  </parameter>
-
-  <parameter name="master_ip" unique="1">
-  <longdesc lang="en">
-  The IP address resource which will be available on the master ovsdb server
-  </longdesc>
-  <shortdesc lang="en">master ip address</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="nb_master_port" unique="1">
-  <longdesc lang="en">
-  The port which the master Northbound database server is listening
-  </longdesc>
-  <shortdesc lang="en">master Northbound database port</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="nb_master_protocol" unique="1">
-  <longdesc lang="en">
-  The protocol which the master Northbound database server used, 'tcp' or 'ssl'.
-  </longdesc>
-  <shortdesc lang="en">master Northbound database protocol</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="sb_master_port" unique="1">
-  <longdesc lang="en">
-  The port which the master Southbound database server is listening
-  </longdesc>
-  <shortdesc lang="en">master Southbound database port</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="sb_master_protocol" unique="1">
-  <longdesc lang="en">
-  The protocol which the master Southbound database server used, 'tcp' or 'ssl'.
-  </longdesc>
-  <shortdesc lang="en">master Southbound database protocol</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="manage_northd" unique="1">
-  <longdesc lang="en">
-  If set to yes, manages ovn-northd service. ovn-northd will be started in
-  the master node.
-  </longdesc>
-  <shortdesc lang="en">manage ovn-northd service</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="inactive_probe_interval" unique="1">
-  <longdesc lang="en">
-  Inactive probe interval to set for ovsdb-server.
-  </longdesc>
-  <shortdesc lang="en">Set inactive probe interval</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="listen_on_master_ip_only" unique="1">
-  <longdesc lang="en">
-  If set to yes, the OVNDBs will listen on master IP. Otherwise, it will
-  listen on 0.0.0.0. Set to yes when using pacemaker managed vip resource
-  as MASTER_IP; set to no when using external LB VIP.
-  </longdesc>
-  <shortdesc lang="en">Listen on master IP or 0.0.0.0</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_nb_db_privkey" unique="1">
-  <longdesc lang="en">
-  OVN NB DB private key absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN NB DB private key file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_nb_db_cert" unique="1">
-  <longdesc lang="en">
-  OVN NB DB certificate absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN NB DB cert file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_nb_db_cacert" unique="1">
-  <longdesc lang="en">
-  OVN NB DB CA certificate absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN NB DB cacert file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_sb_db_privkey" unique="1">
-  <longdesc lang="en">
-  OVN SB DB private key absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN SB DB private key file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_sb_db_cert" unique="1">
-  <longdesc lang="en">
-  OVN SB DB certificate absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN SB DB cert file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  <parameter name="ovn_sb_db_cacert" unique="1">
-  <longdesc lang="en">
-  OVN SB DB CA certificate absolute path for ssl setup.
-  </longdesc>
-  <shortdesc lang="en">OVN SB DB cacert file</shortdesc>
-  <content type="string" />
-  </parameter>
-
-  </parameters>
-
-  <actions>
-    <action name="notify"       timeout="20s" />
-    <action name="start"        timeout="30s" />
-    <action name="stop"         timeout="20s" />
-    <action name="promote"      timeout="50s" />
-    <action name="demote"       timeout="50s" />
-    <action name="monitor"      timeout="20s"  depth="0" interval="10s"
-     role="Master" />
-    <action name="monitor"      timeout="20s"  depth="0" interval="30s"
-     role="Slave"/>
-    <action name="meta-data"    timeout="5s" />
-    <action name="validate-all" timeout="20s" />
-  </actions>
-</resource-agent>
-END
-    exit $OCF_SUCCESS
-}
-
-ovsdb_server_notify() {
-    # requires the notify=true meta resource attribute
-    local type_op="${OCF_RESKEY_CRM_meta_notify_type}-${OCF_RESKEY_CRM_meta_notify_operation}"
-
-    if [ "$type_op" != "post-promote" ]; then
-        # We are only interested in specific events
-        return $OCF_SUCCESS
-    fi
-
-    ocf_log debug "ovndb_server: notified of event $type_op"
-    if [ "x$(ovsdb_server_last_known_master)" = "x${host_name}" ]; then
-        # Record ourselves so that the agent has a better chance of doing
-        # the right thing at startup
-        ocf_log debug "ovndb_server: $host_name is the master"
-        ${CRM_ATTR_REPL_INFO} -v "$host_name"
-        if [ "$MANAGE_NORTHD" = "yes" ]; then
-            # Startup ovn-northd service
-            ${OVN_CTL} --ovn-manage-ovsdb=no start_northd
-        fi
-
-        # In order to over-ride inactivity_probe for LB use case, we need to
-        # create connection entry to listen on 0.0.0.0 for master node.
-        if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then
-           LISTON_ON_IP="0.0.0.0"
-        else
-           LISTON_ON_IP=${MASTER_IP}
-        fi
-        conn=`ovn-nbctl get NB_global . connections`
-        if [ "$conn" == "[]" ]
-        then
-            ovn-nbctl -- --id=@conn_uuid create Connection \
-target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTON_ON_IP}" \
-inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid
-        fi
-
-        conn=`ovn-sbctl get SB_global . connections`
-        if [ "$conn" == "[]" ]
-        then
-            ovn-sbctl -- --id=@conn_uuid create Connection \
-target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTON_ON_IP}" \
-inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid
-        fi
-
-    else
-        if [ "$MANAGE_NORTHD" = "yes" ]; then
-            # Stop ovn-northd service. Set --ovn-manage-ovsdb=no so that
-            # ovn-ctl doesn't stop ovsdb-servers.
-            ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd
-        fi
-        # Synchronize with the new master
-        ocf_log debug "ovndb_server: Connecting to the new master ${OCF_RESKEY_CRM_meta_notify_promote_uname}"
-        ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${MASTER_IP} \
-                                --db-nb-sync-from-port=${NB_MASTER_PORT} \
-                                --db-nb-sync-from-proto=${NB_MASTER_PROTO}
-        ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${MASTER_IP} \
-                                --db-sb-sync-from-port=${SB_MASTER_PORT} \
-                                --db-sb-sync-from-proto=${SB_MASTER_PROTO}
-    fi
-}
-
-ovsdb_server_usage() {
-    cat <<END
-usage: $0 {start|stop|status|monitor|notify|validate-all|meta-data}
-
-Expects to have a fully populated OCF RA-compliant environment set.
-END
-    exit $1
-}
-
-ovsdb_server_find_active_master() {
-    # Operation sequence is Demote -> Stop -> Start -> Promote
-    # At the point this is run, the only active masters will be
-    # previous masters minus any that were scheduled to be demoted
-
-    for master in ${OCF_RESKEY_CRM_meta_notify_master_uname}; do
-        found=0
-        for old in ${OCF_RESKEY_CRM_meta_notify_demote_uname}; do
-            if [ $master = $old ]; then
-                found=1
-            fi
-        done
-        if [ $found = 0 ]; then
-            # Rely on master-max=1
-            # Pacemaker will demote any additional ones it finds before starting new copies
-            echo "$master"
-            return
-        fi
-    done
-
-    local expected_master=$($CRM_ATTR_REPL_INFO --query  -q 2>/dev/null)
-    case "x${OCF_RESKEY_CRM_meta_notify_start_uname}x" in
-        *${expected_master}*) echo "${expected_master}";; # The previous master is expected to start
-    esac
-}
-
-ovsdb_server_last_known_master()
-{
-    if [ -z "$MASTER_HOST" ]; then
-        MASTER_HOST="$(${CRM_ATTR_REPL_INFO} --query  -q  2>/dev/null)"
-    fi
-    echo "$MASTER_HOST"
-}
-
-ovsdb_server_master_update() {
-    case $1 in
-        $OCF_SUCCESS)
-        $CRM_MASTER -N $host_name -v ${slave_score};;
-        $OCF_RUNNING_MASTER)
-            $CRM_MASTER -N $host_name -v ${master_score};;
-        #*) $CRM_MASTER -D;;
-    esac
-}
-
-ovsdb_server_monitor() {
-    ovsdb_server_check_status $@
-    rc=$?
-
-    ovsdb_server_master_update $rc
-    return $rc
-}
-
-ovsdb_server_check_status() {
-    local sb_status=`${OVN_CTL} status_ovnsb`
-    local nb_status=`${OVN_CTL} status_ovnnb`
-
-    if [[ $sb_status == "running/backup" && $nb_status == "running/backup" ]]; then
-        return $OCF_SUCCESS
-    fi
-
-    check_northd="no"
-    if [ "$MANAGE_NORTHD" == "yes" ] && [ "$1" != "ignore_northd" ]; then
-        check_northd="yes"
-    fi
-
-    if [[ $sb_status == "running/active" && $nb_status == "running/active" ]]; then
-        if [ "$check_northd" == "yes" ]; then
-            # Verify if ovn-northd is running or not.
-            ${OVN_CTL} status_northd
-            if [ "$?" == "0" ] ; then
-                return $OCF_RUNNING_MASTER
-            fi
-        else
-            return $OCF_RUNNING_MASTER
-        fi
-    fi
-
-    # TODO: What about service running but not in either state above?
-    # Eg. a transient state where one db is "active" and the other
-    # "backup"
-
-    return $OCF_NOT_RUNNING
-}
-
-ovsdb_server_start() {
-    ovsdb_server_check_status
-    local status=$?
-    # If not in stopped state, return
-    if [ $status -ne $OCF_NOT_RUNNING ]; then
-        return $status
-    fi
-
-    local present_master=$(ovsdb_server_find_active_master)
-
-    set ${OVN_CTL}
-
-    if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then
-        set $@ --db-nb-port=${NB_MASTER_PORT}
-        set $@ --db-sb-port=${SB_MASTER_PORT}
-
-    else
-       set $@ --db-nb-addr=${MASTER_IP} --db-nb-port=${NB_MASTER_PORT}
-       set $@ --db-sb-addr=${MASTER_IP} --db-sb-port=${SB_MASTER_PORT}
-    fi
-
-    if [ "x${NB_MASTER_PROTO}" = xssl ]; then
-            set $@ --ovn-nb-db-ssl-key=${NB_PRIVKEY}
-            set $@ --ovn-nb-db-ssl-cert=${NB_CERT}
-            set $@ --ovn-nb-db-ssl-ca-cert=${NB_CACERT}
-    fi
-    if [ "x${SB_MASTER_PROTO}" = xssl ]; then
-            set $@ --ovn-sb-db-ssl-key=${SB_PRIVKEY}
-            set $@ --ovn-sb-db-ssl-cert=${SB_CERT}
-            set $@ --ovn-sb-db-ssl-ca-cert=${SB_CACERT}
-    fi
-    if [ "x${present_master}" = x ]; then
-        # No master detected, or the previous master is not among the
-        # set starting.
-        #
-        # Force all copies to come up as slaves by pointing them into
-        # space and let pacemaker pick one to promote:
-        #
-        if [ "x${NB_MASTER_PROTO}" = xtcp ]; then
-            set $@ --db-nb-create-insecure-remote=yes
-        fi
-
-        if [ "x${SB_MASTER_PROTO}" = xtcp ]; then
-            set $@ --db-sb-create-insecure-remote=yes
-        fi
-        set $@ --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} --db-sb-sync-from-addr=${INVALID_IP_ADDRESS}
-
-    elif [ ${present_master} != ${host_name} ]; then
-        if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xyes ]; then
-            if [ "x${NB_MASTER_PROTO}" = xtcp ]; then
-                set $@ --db-nb-create-insecure-remote=yes
-            fi
-
-            if [ "x${SB_MASTER_PROTO}" = xtcp ]; then
-                set $@ --db-sb-create-insecure-remote=yes
-            fi
-        fi
-        # An existing master is active, connect to it
-        set $@ --db-nb-sync-from-addr=${MASTER_IP} --db-sb-sync-from-addr=${MASTER_IP}
-        set $@ --db-nb-sync-from-port=${NB_MASTER_PORT}
-        set $@ --db-nb-sync-from-proto=${NB_MASTER_PROTO}
-        set $@ --db-sb-sync-from-port=${SB_MASTER_PORT}
-        set $@ --db-sb-sync-from-proto=${SB_MASTER_PROTO}
-        if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then
-            set $@ --db-sb-use-remote-in-db="no"
-            set $@ --db-nb-use-remote-in-db="no"
-        fi
-    fi
-
-    $@ start_ovsdb
-
-    while [ 1 = 1 ]; do
-        # It is important that we don't return until we're in a functional
-        # state. When checking the status of the ovsdb-server's ignore northd.
-        # It is possible that when the resource is restarted ovsdb-server's
-        # can be started as masters and ovn-northd would not have been started.
-        # ovn-northd will be started once a node is promoted to master and
-        # 'manage_northd' is set to yes.
-        ovsdb_server_monitor ignore_northd
-        rc=$?
-        case $rc in
-            $OCF_SUCCESS)        return $rc;;
-            $OCF_RUNNING_MASTER)
-                # When a slave node is promoted as master, the action would be
-                # STOP -> START -> PROMOTE.
-                # When the start action is called, it is possible for the
-                # ovsdb-server's to be started as active. This could happen
-                # if the node owns the $MASTER_IP. At this point, pacemaker
-                # has not promoted this node yet. Demote it and check for
-                # status again.
-                # Let pacemaker promote it in subsequent actions.
-                # As per the OCF guidelines, only monitor action should return
-                # OCF_RUNNING_MASTER.
-                # http://www.linux-ha.org/doc/dev-guides/_literal_ocf_running_master_literal_8.html
-                ${OVN_CTL} demote_ovnnb \
-                --db-nb-sync-from-addr=${INVALID_IP_ADDRESS}
-                ${OVN_CTL} demote_ovnsb \
-                --db-sb-sync-from-addr=${INVALID_IP_ADDRESS}
-                ;;
-            $OCF_ERR_GENERIC)    return $rc;;
-            # Otherwise loop, waiting for the service to start, until
-            # the cluster times the operation out
-        esac
-        ocf_log warn "ovndb_servers: After starting ovsdb, status is $rc. Checking the status again"
-    done
-}
-
-ovsdb_server_stop() {
-    if [ "$MANAGE_NORTHD" = "yes" ]; then
-        # Stop ovn-northd service in case it was running. This is required
-        # when the master is demoted. For other cases, it would be a no-op.
-        # Set --ovn-manage-ovsdb=no so that ovn-ctl doesn't stop ovsdb-servers.
-        ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd
-    fi
-
-    ovsdb_server_check_status ignore_northd
-    case $? in
-        $OCF_NOT_RUNNING)
-          # Even if one server is down, check_status returns NOT_RUNNING.
-          # So before returning call stop_ovsdb to be sure.
-          ${OVN_CTL} stop_ovsdb
-          return ${OCF_SUCCESS};;
-    esac
-
-    ${OVN_CTL} stop_ovsdb
-    ovsdb_server_master_update ${OCF_NOT_RUNNING}
-
-    while [ 1 = 1 ]; do
-        # It is important that we don't return until we're stopped
-        ovsdb_server_check_status ignore_northd
-        rc=$?
-        case $rc in
-        $OCF_SUCCESS)
-            # Loop, waiting for the service to stop, until the
-            # cluster times the operation out
-            ocf_log warn "ovndb_servers: Even after stopping, the servers seems to be running"
-            ;;
-        $OCF_NOT_RUNNING)
-            return $OCF_SUCCESS
-            ;;
-        *)
-            return $rc
-            ;;
-        esac
-    done
-
-    return $OCF_ERR_GENERIC
-}
-
-ovsdb_server_promote() {
-    local state
-
-    ovsdb_server_check_status ignore_northd
-    rc=$?
-    case $rc in
-        ${OCF_SUCCESS}) ;;
-        ${OCF_RUNNING_MASTER}) ;;
-        *)
-            ovsdb_server_master_update $OCF_RUNNING_MASTER
-            return ${rc}
-            ;;
-    esac
-
-    # Restart ovs so that new master can listen on tcp port
-    if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then
-        ${OVN_CTL} stop_ovsdb
-        ovsdb_server_start
-    fi
-    ${OVN_CTL} promote_ovnnb
-    ${OVN_CTL} promote_ovnsb
-
-    if [ "$MANAGE_NORTHD" = "yes" ]; then
-        # Startup ovn-northd service
-        ${OVN_CTL} --ovn-manage-ovsdb=no start_northd
-    fi
-
-    ocf_log debug "ovndb_servers: Waiting for promotion $host_name as master to complete"
-    ovsdb_server_check_status
-    state=$?
-    while [ "$state" != "$OCF_RUNNING_MASTER" ]; do
-      sleep 1
-      ovsdb_server_check_status
-      state=$?
-    done
-    ocf_log debug "ovndb_servers: Promotion of $host_name as the master completed"
-    # Record ourselves so that the agent has a better chance of doing
-    # the right thing at startup
-    ${CRM_ATTR_REPL_INFO} -v "$host_name"
-    ovsdb_server_master_update $OCF_RUNNING_MASTER
-    return $OCF_SUCCESS
-}
-
-ovsdb_server_demote() {
-    # While demoting, check the status of ovn_northd.
-    # In case ovn_northd is not running, we should return OCF_NOT_RUNNING.
-    ovsdb_server_check_status
-    if [ $? = $OCF_NOT_RUNNING ]; then
-        return $OCF_NOT_RUNNING
-    fi
-
-    local present_master=$(ovsdb_server_find_active_master)
-    local recorded_master=$($CRM_ATTR_REPL_INFO --query  -q 2>/dev/null)
-
-    ocf_log debug "ovndb_servers: Demoting $host_name, present master ${present_master}, recorded master ${recorded_master}"
-    if [ "x${recorded_master}" = "x${host_name}" -a "x${present_master}" = x ]; then
-        # We are the one and only master
-        # This should be the "normal" case
-        # The only way to be demoted is to call demote_ovn*
-        #
-        # The local database is only reset once we successfully
-        # connect to the peer.  So specify one that doesn't exist.
-        #
-        # Eventually a new master will be promoted and we'll resync
-        # using the logic in ovsdb_server_notify()
-        ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS}
-        ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS}
-
-    elif [ "x${present_master}" = "x${host_name}" ]; then
-        # Safety check, should never be called
-        #
-        # Never allow sync'ing from ourselves, its a great way to
-        # erase the local DB
-        ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS}
-        ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS}
-
-    elif [ "x${present_master}" != x ]; then
-        # There are too many masters and we're an extra one that is
-        # being demoted. Sync to the surviving one
-        ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${MASTER_IP} \
-                                --db-nb-sync-from-port=${NB_MASTER_PORT} \
-                                --db-nb-sync-from-proto=${NB_MASTER_PROTO}
-        ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${MASTER_IP} \
-                                --db-sb-sync-from-port=${SB_MASTER_PORT} \
-                                --db-sb-sync-from-proto=${SB_MASTER_PROTO}
-
-    else
-        # For completeness, should never be called
-        #
-        # Something unexpected happened, perhaps CRM_ATTR_REPL_INFO is incorrect
-        ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS}
-        ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS}
-    fi
-
-    if [ "$MANAGE_NORTHD" = "yes" ]; then
-        # Stop ovn-northd service
-        ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd
-    fi
-    ovsdb_server_master_update $OCF_SUCCESS
-    return $OCF_SUCCESS
-}
-
-ovsdb_server_validate() {
-    if [ ! -e ${OVN_CTL} ]; then
-        return $OCF_ERR_INSTALLED
-    fi
-    return $OCF_SUCCESS
-}
-
-
-case $__OCF_ACTION in
-start)          ovsdb_server_start;;
-stop)           ovsdb_server_stop;;
-promote)        ovsdb_server_promote;;
-demote)         ovsdb_server_demote;;
-notify)         ovsdb_server_notify;;
-meta-data)      ovsdb_server_metadata;;
-validate-all)   ovsdb_server_validate;;
-status|monitor) ovsdb_server_monitor;;
-usage|help)     ovsdb_server_usage $OCF_SUCCESS;;
-*)              ovsdb_server_usage $OCF_ERR_UNIMPLEMENTED ;;
-esac
-
-rc=$?
-exit $rc
diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in
index ec85e14c4..6fdb4b5a5 100644
--- a/ovsdb/ovsdb-tool.1.in
+++ b/ovsdb/ovsdb-tool.1.in
@@ -91,18 +91,17 @@ both its schema and data.)
 .
 .IP "\fBcreate\-cluster\fI db contents local"
 Use this command to initialize the first server in a high-availability
-cluster of 3 (or more) database servers, e.g. for an OVN northbound or
-southbound database in an environment that cannot tolerate a single
-point of failure.  It creates clustered database file \fIdb\fR and
-configures the server to listen on \fIlocal\fR, which must take the
-form \fIprotocol\fB:\fIip\fB:\fIport\fR, where \fIprotocol\fR is
-\fBtcp\fR or \fBssl\fR, \fIip\fR is the server's IP (either an IPv4
-address or an IPv6 address enclosed in square brackets), and
-\fIport\fR is a TCP port number.  Only one address is specified, for
-the first server in the cluster, ordinarily the one for the server
-running \fBcreate\-cluster\fR.  The address is used for communication
-within the cluster, not for communicating with OVSDB clients, and must
-not use the same port used for the OVSDB protocol.
+cluster of 3 (or more) database servers, e.g. for a database in an
+environment that cannot tolerate a single point of failure.  It creates
+clustered database file \fIdb\fR and configures the server to listen on
+\fIlocal\fR, which must take the form \fIprotocol\fB:\fIip\fB:\fIport\fR,
+where \fIprotocol\fR is \fBtcp\fR or \fBssl\fR, \fIip\fR is the server's
+IP (either an IPv4 address or an IPv6 address enclosed in square
+brackets), and \fIport\fR is a TCP port number.  Only one address is
+specified, for the first server in the cluster, ordinarily the one for
+the server running \fBcreate\-cluster\fR.  The address is used for
+communication within the cluster, not for communicating with OVSDB
+clients, and must not use the same port used for the OVSDB protocol.
 .IP
 The new database is initialized with \fIcontents\fR, which must name a
 file that contains either an OVSDB schema in JSON format or a
diff --git a/rhel/automake.mk b/rhel/automake.mk
index 1c5bf153c..c75406e05 100644
--- a/rhel/automake.mk
+++ b/rhel/automake.mk
@@ -23,8 +23,6 @@ EXTRA_DIST += \
 	rhel/openvswitch.spec.in \
 	rhel/openvswitch-fedora.spec \
 	rhel/openvswitch-fedora.spec.in \
-	rhel/ovn-fedora.spec \
-	rhel/ovn-fedora.spec.in \
 	rhel/usr_share_openvswitch_scripts_ovs-systemd-reload \
 	rhel/usr_share_openvswitch_scripts_sysconfig.template \
 	rhel/usr_share_openvswitch_scripts_systemd_sysconfig.template \
@@ -34,12 +32,7 @@ EXTRA_DIST += \
 	rhel/usr_lib_systemd_system_ovsdb-server.service \
 	rhel/usr_lib_systemd_system_ovs-vswitchd.service.in \
 	rhel/usr_lib_systemd_system_ovs-delete-transient-ports.service \
-	rhel/usr_lib_systemd_system_ovn-controller.service \
-	rhel/usr_lib_systemd_system_ovn-controller-vtep.service \
-	rhel/usr_lib_systemd_system_ovn-northd.service \
-	rhel/usr_lib_systemd_system_openvswitch-ipsec.service \
-	rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
-	rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml
+	rhel/usr_lib_systemd_system_openvswitch-ipsec.service
 
 DISTCLEANFILES += rhel/usr_lib_systemd_system_ovs-vswitchd.service
 
@@ -74,13 +67,6 @@ rpm-fedora: dist $(srcdir)/rhel/openvswitch-fedora.spec
                  -D "_topdir ${RPMBUILD_TOP}" \
                  -ba $(srcdir)/rhel/openvswitch-fedora.spec
 
-rpm-fedora-ovn: dist $(srcdir)/rhel/ovn-fedora.spec
-	${MKDIR_P} ${RPMBUILD_TOP}/SOURCES
-	cp ${DIST_ARCHIVES} ${RPMBUILD_TOP}/SOURCES
-	rpmbuild ${RPMBUILD_OPT} \
-                 -D "_topdir ${RPMBUILD_TOP}" \
-                 -ba $(srcdir)/rhel/ovn-fedora.spec
-
 # Build kernel datapath RPM
 rpm-fedora-kmod: dist $(srcdir)/rhel/openvswitch-kmod-fedora.spec
 	${MKDIR_P} ${RPMBUILD_TOP}/SOURCES
diff --git a/rhel/ovn-fedora.spec.in b/rhel/ovn-fedora.spec.in
deleted file mode 100644
index 2ecc629f2..000000000
--- a/rhel/ovn-fedora.spec.in
+++ /dev/null
@@ -1,432 +0,0 @@
-# Spec file for Open Virtual Network (OVN).
-
-# Copyright (C) 2018 Red Hat, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved.  This file is offered as-is,
-# without warranty of any kind.
-#
-# If tests have to be skipped while building, specify the '--without check'
-# option. For example:
-#     rpmbuild -bb --without check rhel/ovn-fedora.spec
-#
-
-# If libcap-ng isn't available and there is no need for running OVS
-# as regular user, specify the '--without libcapng'
-%bcond_without libcapng
-
-# Enable Python 3 by specifying '--with build_python3'.
-# This is enabled by default for versions of the distribution that
-# have Python 3 by default (Fedora > 22).
-%bcond_with build_python3
-
-# Enable PIE, bz#955181
-%global _hardened_build 1
-
-# some distros (e.g: RHEL-7) don't define _rundir macro yet
-# Fedora 15 onwards uses /run as _rundir
-%if 0%{!?_rundir:1}
-%define _rundir /run
-%endif
-
-# define the python package prefix based on distribution version so that we can
-# simultaneously support RHEL-based and later Fedora versions in this spec file.
-%if 0%{?fedora} >= 25
-%define _py2 python2
-%endif
-
-%if 0%{?rhel} || 0%{?fedora} < 25
-%define _py2 python
-%endif
-
-Name: ovn
-Summary: Open Virtual Network support
-Group: System Environment/Daemons
-URL: http://www.openvswitch.org/
-Version: @VERSION@
-Obsoletes: openvswitch-ovn-common < %{?epoch:%{epoch}:}%{version}-%{release}
-Provides: openvswitch-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
-
-# Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
-# lib/sflow*.[ch] files are SISSL
-License: ASL 2.0 and LGPLv2+ and SISSL
-Release: 1%{?dist}
-Source: http://openvswitch.org/releases/openvswitch-%{version}.tar.gz
-
-BuildRequires: gcc gcc-c++
-BuildRequires: autoconf automake libtool
-BuildRequires: systemd-units openssl openssl-devel
-BuildRequires: %{_py2}-devel
-%if 0%{?fedora} > 22 || %{with build_python3}
-BuildRequires: python3-devel
-%endif
-BuildRequires: desktop-file-utils
-BuildRequires: groff graphviz
-BuildRequires: checkpolicy, selinux-policy-devel
-BuildRequires: /usr/bin/sphinx-build
-# make check dependencies
-BuildRequires: %{_py2}-twisted%{?rhel:-core} %{_py2}-zope-interface %{_py2}-six
-BuildRequires: procps-ng
-%if %{with libcapng}
-BuildRequires: libcap-ng libcap-ng-devel
-%endif
-BuildRequires: unbound unbound-devel
-
-Requires: openssl hostname iproute module-init-tools openvswitch
-
-Requires(post): systemd-units
-Requires(preun): systemd-units
-Requires(postun): systemd-units
-
-# to skip running checks, pass --without check
-%bcond_without check
-
-%description
-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.
-
-%package central
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: ovn
-Requires: firewalld-filesystem
-Obsoletes: openvswitch-ovn-central
-Provides: openvswitch-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release}
-
-%description central
-OVN DB servers and ovn-northd running on a central node.
-
-%package host
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: ovn
-Requires: firewalld-filesystem
-Obsoletes: openvswitch-ovn-host
-Provides: openvswitch-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release}
-
-%description host
-OVN controller running on each host.
-
-%package vtep
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: ovn
-Obsoletes: openvswitch-ovn-vtep
-Provides: openvswitch-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release}
-
-%description vtep
-OVN vtep controller
-
-%package docker
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: ovn %{_py2}-openvswitch
-Obsoletes: openvswitch-ovn-docker
-Provides: openvswitch-ovn-docker = %{?epoch:%{epoch}:}%{version}-%{release}
-
-%description docker
-Docker network plugins for OVN.
-
-%prep
-%setup -n openvswitch-%{version}
-
-%build
-%configure \
-%if %{with libcapng}
-        --enable-libcapng \
-%else
-        --disable-libcapng \
-%endif
-        --enable-ssl \
-        --with-pkidir=%{_sharedstatedir}/openvswitch/pki \
-%if 0%{?fedora} > 22 || %{with build_python3}
-        PYTHON3=%{__python3} \
-        PYTHON=%{__python2}
-%else
-        PYTHON=%{__python}
-%endif
-
-make %{?_smp_mflags}
-
-%install
-rm -rf $RPM_BUILD_ROOT
-make install DESTDIR=$RPM_BUILD_ROOT
-
-for service in ovn-controller ovn-controller-vtep ovn-northd; do
-        install -p -D -m 0644 \
-                        rhel/usr_lib_systemd_system_${service}.service \
-                        $RPM_BUILD_ROOT%{_unitdir}/${service}.service
-done
-
-rm -rf $RPM_BUILD_ROOT/%{_datadir}/openvswitch/python/
-
-install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/openvswitch
-
-install -d $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
-        $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/ovn-central-firewall-service.xml
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
-        $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/ovn-host-firewall-service.xml
-
-install -d -m 0755 $RPM_BUILD_ROOT%{_prefix}/lib/ocf/resource.d/ovn
-ln -s %{_datadir}/openvswitch/scripts/ovndb-servers.ocf \
-      $RPM_BUILD_ROOT%{_prefix}/lib/ocf/resource.d/ovn/ovndb-servers
-
-# remove OVS unpackages files
-rm -f $RPM_BUILD_ROOT%{_bindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl
-rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep*
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/ovs*
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/vswitch.ovsschema
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/vtep.ovsschema
-rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs*
-rm -rf $RPM_BUILD_ROOT%{_datadir}/openvswitch/bugtool-plugins
-rm -f $RPM_BUILD_ROOT%{_includedir}/openvswitch/*
-rm -f $RPM_BUILD_ROOT%{_includedir}/openflow/*
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
-rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc
-rm -f $RPM_BUILD_ROOT%{_includedir}/openvswitch/*
-rm -f $RPM_BUILD_ROOT%{_includedir}/openflow/*
-rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
-rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch
-
-%check
-%if %{with check}
-    touch resolv.conf
-    export OVS_RESOLV_CONF=$(pwd)/resolv.conf
-    if make check TESTSUITEFLAGS='%{_smp_mflags}' RECHECK=yes; then :;
-    else
-        cat tests/testsuite.log
-        exit 1
-    fi
-%endif
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-%pre central
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-northd.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-central > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-northd service is running which means old openvswitch-ovn-central
-        # is already installed and it will be cleaned up. So start ovn-northd
-        # service when posttrans central is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-northd
-    fi
-fi
-
-%pre host
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-controller.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-host > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-controller service is running which means old
-        # openvswitch-ovn-host is installed and it will be cleaned up. So
-        # start ovn-controller service when posttrans host is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-controller
-    fi
-fi
-
-%pre vtep
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-controller-vtep.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-vtep > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-controller-vtep service is running which means old
-        # openvswitch-ovn-vtep is installed and it will be cleaned up. So
-        # start ovn-controller-vtep service when posttrans host is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
-    fi
-fi
-
-%preun central
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-northd.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%preun host
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-controller.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%preun vtep
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-controller-vtep.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%post central
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-northd.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%post host
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-controller.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%post vtep
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-controller-vtep.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%postun
-
-%postun central
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-northd.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-    # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%postun host
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-controller.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-        # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%postun vtep
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-controller-vtep.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-        # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%posttrans central
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-northd
-        /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || :
-    fi
-fi
-
-
-%posttrans host
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-controller
-        /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || :
-    fi
-fi
-
-%posttrans vtep
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
-        /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-fi
-
-%files
-%{_bindir}/ovn-nbctl
-%{_bindir}/ovn-sbctl
-%{_bindir}/ovn-trace
-%{_bindir}/ovn-detrace
-%{_datadir}/openvswitch/scripts/ovn-ctl
-%{_datadir}/openvswitch/scripts/ovndb-servers.ocf
-%{_datadir}/openvswitch/scripts/ovn-bugtool-nbctl-show
-%{_datadir}/openvswitch/scripts/ovn-bugtool-sbctl-lflow-list
-%{_datadir}/openvswitch/scripts/ovn-bugtool-sbctl-show
-%{_mandir}/man8/ovn-ctl.8*
-%{_mandir}/man8/ovn-nbctl.8*
-%{_mandir}/man8/ovn-trace.8*
-%{_mandir}/man1/ovn-detrace.1*
-%{_mandir}/man7/ovn-architecture.7*
-%{_mandir}/man8/ovn-sbctl.8*
-%{_mandir}/man5/ovn-nb.5*
-%{_mandir}/man5/ovn-sb.5*
-%{_prefix}/lib/ocf/resource.d/ovn/ovndb-servers
-
-%files docker
-%{_bindir}/ovn-docker-overlay-driver
-%{_bindir}/ovn-docker-underlay-driver
-
-%files central
-%{_bindir}/ovn-northd
-%{_mandir}/man8/ovn-northd.8*
-%config %{_datadir}/openvswitch/ovn-nb.ovsschema
-%config %{_datadir}/openvswitch/ovn-sb.ovsschema
-%{_unitdir}/ovn-northd.service
-%{_prefix}/lib/firewalld/services/ovn-central-firewall-service.xml
-
-%files host
-%{_bindir}/ovn-controller
-%{_mandir}/man8/ovn-controller.8*
-%{_unitdir}/ovn-controller.service
-%{_prefix}/lib/firewalld/services/ovn-host-firewall-service.xml
-
-%files vtep
-%{_bindir}/ovn-controller-vtep
-%{_mandir}/man8/ovn-controller-vtep.8*
-%{_unitdir}/ovn-controller-vtep.service
-
-%changelog
-* Thu Dec 20 2018 Numan Siddique <nusiddiq@redhat.com>
-- OVS/OVN split.
diff --git a/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml b/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml
deleted file mode 100644
index a005f325c..000000000
--- a/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<service>
-  <short>ovn-central-firewall-service</short>
-  <description>Firewall service for ovn central</description>
-  <port protocol="tcp" port="6641"/>
-  <port protocol="tcp" port="6642"/>
-</service>
diff --git a/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml b/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml
deleted file mode 100644
index f606890c3..000000000
--- a/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<service>
-  <short>ovn-host-firewall-service</short>
-  <description>Firewall service for ovn host</description>
-  <port protocol="udp" port="6081"/>
-</service>
diff --git a/rhel/usr_lib_systemd_system_ovn-controller-vtep.service b/rhel/usr_lib_systemd_system_ovn-controller-vtep.service
deleted file mode 100644
index b1e239f57..000000000
--- a/rhel/usr_lib_systemd_system_ovn-controller-vtep.service
+++ /dev/null
@@ -1,50 +0,0 @@
-# See ovn-controller-vtep(8) for details about ovn-controller-vtep.
-#
-# You may override the following variables to customize ovn-controller-vtep
-# behavior:
-#
-#   OVN_DB - Set this variable to the location of the ovsdb server that is
-#            serving the OVN_Southbound database.  See the manpage for
-#            ovn-controller-vtep for more details on the format for the db
-#            location.
-#
-#   VTEP_DB - Set this variable to the location of the ovsdb server that is
-#             serving the hardware_vtep database.  See the manpage for
-#             ovn-controller-vtep for more details on the format for the db
-#             location.
-#
-# To override these variables, you may create a configuration file
-# in the /etc/systemd/system/ovn-controller-vtep.d/ directory.  For example,
-# you could place the following contents in
-# /etc/systemd/system/ovn-controller-vtep.d/local.conf:
-#
-#   [System]
-#   Environment="OVN_DB=unix:/usr/local/var/run/openvswitch/db.sock" "VTEP_DB=unix:/usr/local/var/run/openvswitch/vtep.sock"
-#
-# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-controller-vtep:
-#
-#   OVN_DB="unix:/usr/local/var/run/openvswitch/db.sock"
-#   VTEP_DB="unix:/usr/local/var/run/openvswitch/vtep.sock"
-
-[Unit]
-Description=OVN VTEP gateway controller daemon
-After=syslog.target
-Requires=openvswitch.service
-After=openvswitch.service
-
-[Service]
-Type=forking
-PIDFile=/var/run/openvswitch/ovn-controller-vtep.pid
-Restart=on-failure
-Environment=OVN_DB=unix:%t/openvswitch/ovnsb_db.sock
-Environment=VTEP_DB=unix:%t/openvswitch/db.sock
-EnvironmentFile=-/etc/sysconfig/ovn-controller-vtep
-EnvironmentFile=/run/openvswitch.useropts
-ExecStart=/usr/share/openvswitch/scripts/ovn-ctl \
-          --db-sb-sock=${OVN_DB} --db-sock=${VTEP_DB} \
-          --ovn-user=${OVS_USER_ID} \
-          start_controller_vtep
-ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_controller_vtep
-
-[Install]
-WantedBy=multi-user.target
diff --git a/rhel/usr_lib_systemd_system_ovn-controller.service b/rhel/usr_lib_systemd_system_ovn-controller.service
deleted file mode 100644
index 335cd5a52..000000000
--- a/rhel/usr_lib_systemd_system_ovn-controller.service
+++ /dev/null
@@ -1,34 +0,0 @@
-# See ovn-controller(8) for details about ovn-controller.
-#
-# To customize the ovn-controller service, you may create a configuration file
-# in the /etc/systemd/system/ovn-controller.d/ directory.  For example, to specify
-# additional options to be passed to the "ovn-ctl start_controller" command, you
-# could place the following contents in
-# /etc/systemd/system/ovn-controller.d/local.conf:
-#
-#   [System]
-#   Environment="OVN_CONTROLLER_OPTS=--ovn-controller-log=-vconsole:emer -vsyslog:err -vfile:info"
-#
-# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-controller:
-#
-#   OVN_CONTROLLER_OPTS="--ovn-controller-log=-vconsole:emer -vsyslog:err -vfile:info"
-
-[Unit]
-Description=OVN controller daemon
-After=syslog.target
-Requires=openvswitch.service
-After=openvswitch.service
-
-[Service]
-Type=forking
-PIDFile=/var/run/openvswitch/ovn-controller.pid
-Restart=on-failure
-EnvironmentFile=-/etc/sysconfig/ovn-controller
-EnvironmentFile=/run/openvswitch.useropts
-ExecStart=/usr/share/openvswitch/scripts/ovn-ctl --no-monitor \
-          --ovn-user=${OVS_USER_ID} \
-          start_controller $OVN_CONTROLLER_OPTS
-ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_controller
-
-[Install]
-WantedBy=multi-user.target
diff --git a/rhel/usr_lib_systemd_system_ovn-northd.service b/rhel/usr_lib_systemd_system_ovn-northd.service
deleted file mode 100644
index ea8c191e3..000000000
--- a/rhel/usr_lib_systemd_system_ovn-northd.service
+++ /dev/null
@@ -1,35 +0,0 @@
-# See ovn-northd(8) for details about ovn-northd.
-#
-# To customize the ovn-northd service, you may create a configuration file
-# in the /etc/systemd/system/ovn-northd.d/ directory.  For example, to specify
-# additional options to be passed to the "ovn-ctl start_northd" command, you
-# could place the following contents in
-# /etc/systemd/system/ovn-northd.d/local.conf:
-#
-#   [System]
-#   Environment="OVN_NORTHD_OPTS=--db-nb-sock=/usr/local/var/run/openvswitch/ovnnb_db.sock --db-sb-sock=/usr/local/var/run/openvswitch/ovnsb_db.sock"
-#
-# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-northd:
-#
-#   OVN_NORTHD_OPTS="--db-nb-sock=/usr/local/var/run/openvswitch/ovnnb_db.sock --db-sb-sock=/usr/local/var/run/openvswitch/ovnsb_db.sock"
-
-[Unit]
-Description=OVN northd management daemon
-After=syslog.target
-Requires=openvswitch.service
-After=openvswitch.service
-
-[Service]
-Type=oneshot
-RemainAfterExit=yes
-Environment=OVS_RUNDIR=%t/openvswitch OVS_DBDIR=/var/lib/openvswitch
-EnvironmentFile=-/etc/sysconfig/ovn-northd
-EnvironmentFile=/run/openvswitch.useropts
-ExecStartPre=-/usr/bin/chown -R ${OVS_USER_ID} ${OVS_DBDIR}
-ExecStart=/usr/share/openvswitch/scripts/ovn-ctl \
-          --ovs-user=${OVS_USER_ID} --ovn-user=${OVS_USER_ID} \
-          start_northd $OVN_NORTHD_OPTS
-ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_northd
-
-[Install]
-WantedBy=multi-user.target
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 2e565d788..556f8681c 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -217,10 +217,6 @@ unset HTTPS_PROXY
 unset FTP_PROXY
 unset NO_PROXY
 
-# Avoid OVN environment variables leaking in from external environment.
-unset OVN_NB_DB
-unset OVN_SB_DB
-
 # Prevent logging to syslog during tests.
 OVS_SYSLOG_METHOD=null
 export OVS_SYSLOG_METHOD
diff --git a/tests/automake.mk b/tests/automake.mk
index d6ab51732..d8a4b87d0 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -23,8 +23,7 @@ EXTRA_DIST += \
 COMMON_MACROS_AT = \
 	tests/ovsdb-macros.at \
 	tests/ovs-macros.at \
-	tests/ofproto-macros.at \
-	tests/ovn-macros.at
+	tests/ofproto-macros.at
 
 TESTSUITE_AT = \
 	tests/testsuite.at \
@@ -104,16 +103,9 @@ TESTSUITE_AT = \
 	tests/vlog.at \
 	tests/vtep-ctl.at \
 	tests/auto-attach.at \
-	tests/ovn.at \
-	tests/ovn-northd.at \
-	tests/ovn-nbctl.at \
-	tests/ovn-sbctl.at \
-	tests/ovn-controller.at \
-	tests/ovn-controller-vtep.at \
 	tests/mcast-snooping.at \
 	tests/packet-type-aware.at \
-	tests/nsh.at \
-	tests/ovn-performance.at
+	tests/nsh.at
 
 EXTRA_DIST += $(FUZZ_REGRESSION_TESTS)
 FUZZ_REGRESSION_TESTS = \
@@ -158,7 +150,6 @@ SYSTEM_KMOD_TESTSUITE_AT = \
 
 SYSTEM_USERSPACE_TESTSUITE_AT = \
 	tests/system-userspace-testsuite.at \
-	tests/system-ovn.at \
 	tests/system-userspace-macros.at \
 	tests/system-userspace-packet-type-aware.at
 
@@ -169,7 +160,6 @@ SYSTEM_AFXDP_TESTSUITE_AT = \
 
 SYSTEM_TESTSUITE_AT = \
 	tests/system-common-macros.at \
-	tests/system-ovn.at \
 	tests/system-layer3-tunnels.at \
 	tests/system-traffic.at \
 	tests/system-interface.at
@@ -197,7 +187,7 @@ SYSTEM_DPDK_TESTSUITE = $(srcdir)/tests/system-dpdk-testsuite
 OVSDB_CLUSTER_TESTSUITE = $(srcdir)/tests/ovsdb-cluster-testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 
-AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):ovn/controller-vtep:ovn/northd:ovn/utilities:ovn/controller
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):ovn/utilities
 
 check-local:
 	set $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH); \
@@ -238,9 +228,7 @@ check-lcov: all $(check_DATA) clean-lcov
 # valgrind support
 
 valgrind_wrappers = \
-	tests/valgrind/ovn-controller \
 	tests/valgrind/ovn-nbctl \
-	tests/valgrind/ovn-northd \
 	tests/valgrind/ovn-sbctl \
 	tests/valgrind/ovs-appctl \
 	tests/valgrind/ovs-ofctl \
@@ -446,7 +434,6 @@ tests_ovstest_SOURCES = \
 	tests/test-netflow.c \
 	tests/test-odp.c \
 	tests/test-ofpbuf.c \
-	tests/test-ovn.c \
 	tests/test-packets.c \
 	tests/test-random.c \
 	tests/test-rcu.c \
@@ -474,7 +461,7 @@ tests_ovstest_SOURCES += \
 	tests/test-netlink-conntrack.c
 endif
 
-tests_ovstest_LDADD = lib/libopenvswitch.la ovn/lib/libovn.la
+tests_ovstest_LDADD = lib/libopenvswitch.la
 
 noinst_PROGRAMS += tests/test-stream
 tests_test_stream_SOURCES = tests/test-stream.c
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index db0cd5108..04d4ed7e2 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -214,7 +214,7 @@ check_logs () {
 
     # We most notably ignore 'Broken pipe' warnings.  These often and
     # intermittently appear in ovsdb-server.log, because *ctl commands
-    # (e.g. ovs-vsctl, ovn-nbctl) exit right after committing a change to the
+    # (e.g. ovs-vsctl) exit right after committing a change to the
     # database.  However, in reaction, some daemon may immediately update the
     # database, and this later update may cause database sending update back to
     # *ctl command if *ctl has not exited yet.  If *ctl command exits before
diff --git a/tests/oss-fuzz/automake.mk b/tests/oss-fuzz/automake.mk
index 5bf7d0d7c..2b116e7a5 100644
--- a/tests/oss-fuzz/automake.mk
+++ b/tests/oss-fuzz/automake.mk
@@ -2,7 +2,6 @@ OSS_FUZZ_TARGETS = \
 	tests/oss-fuzz/flow_extract_target \
 	tests/oss-fuzz/json_parser_target \
 	tests/oss-fuzz/ofp_print_target \
-	tests/oss-fuzz/expr_parse_target \
 	tests/oss-fuzz/odp_target \
 	tests/oss-fuzz/miniflow_target \
 	tests/oss-fuzz/ofctl_parse_target
@@ -27,13 +26,6 @@ tests_oss_fuzz_ofp_print_target_SOURCES = \
 tests_oss_fuzz_ofp_print_target_LDADD = lib/libopenvswitch.la
 tests_oss_fuzz_ofp_print_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++
 
-tests_oss_fuzz_expr_parse_target_SOURCES = \
-        tests/oss-fuzz/expr_parse_target.c \
-        tests/oss-fuzz/fuzzer.h
-tests_oss_fuzz_expr_parse_target_LDADD = lib/libopenvswitch.la \
-                                         ovn/lib/libovn.la
-tests_oss_fuzz_expr_parse_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++
-
 tests_oss_fuzz_odp_target_SOURCES = \
         tests/oss-fuzz/odp_target.c \
         tests/oss-fuzz/fuzzer.h
@@ -56,11 +48,9 @@ EXTRA_DIST += \
 	tests/oss-fuzz/config/flow_extract_target.options \
 	tests/oss-fuzz/config/json_parser_target.options \
 	tests/oss-fuzz/config/ofp_print_target.options \
-	tests/oss-fuzz/config/expr_parse_target.options \
 	tests/oss-fuzz/config/odp_target.options \
 	tests/oss-fuzz/config/miniflow_target.options \
         tests/oss-fuzz/config/ofctl_parse_target.options \
 	tests/oss-fuzz/config/ovs.dict \
-	tests/oss-fuzz/config/expr.dict \
 	tests/oss-fuzz/config/odp.dict \
 	tests/oss-fuzz/config/ofp-flow.dict
diff --git a/tests/oss-fuzz/config/expr.dict b/tests/oss-fuzz/config/expr.dict
deleted file mode 100644
index 03741ad7d..000000000
--- a/tests/oss-fuzz/config/expr.dict
+++ /dev/null
@@ -1,120 +0,0 @@
-" = "
-" = ("
-" = dns_lookup();"
-" { "
-" };"
-"!"
-"!="
-"$"
-"&&"
-"("
-"()"
-")"
-"),commit,table=,zone=NXM_NX_REG)"
-");"
-", "
-", meter=\"\""
-","
-",bucket=bucket_id=,weight:100,actions=ct(nat(dst="
-"--"
-".."
-"/"
-"/%"
-"0"
-":"
-"<"
-"<->"
-"<="
-"="
-"=="
-"=r"
-">"
-">="
-"["
-"\x00"
-"\x28"
-"]"
-"]:"
-"allow"
-"arp"
-"bool"
-"bswap "
-"bswap %0"
-"cc"
-"clone"
-"ct_clear"
-"ct_clear;"
-"ct_commit"
-"ct_commit("
-"ct_dnat"
-"ct_label"
-"ct_label="
-"ct_lb"
-"ct_mark"
-"ct_mark="
-"ct_mark=%#x"
-"ct_next"
-"ct_next;"
-"ct_snat"
-"decimal"
-"dhcpv6_stateful"
-"dhcpv6_stateless"
-"dns_lookup"
-"drop"
-"drop;"
-"egress"
-"error("
-"get_arp"
-"get_nd"
-"hexadecimal"
-"icmp4"
-"icmp6"
-"ingress"
-"ip.ttl"
-"ip.ttl--;"
-"ipv4"
-"ipv6"
-"log"
-"log("
-"mac"
-"meter"
-"name"
-"name=\"\", "
-"nd_na"
-"nd_na_router"
-"nd_ns"
-"next"
-"next();"
-"next(pipeline=, table=);"
-"next;"
-"output"
-"output;"
-"pipeline"
-"put_arp"
-"put_dhcp_opts"
-"put_dhcpv6_opts"
-"put_nd"
-"put_nd_ra_opts"
-"reject"
-"set_meter"
-"set_meter();"
-"set_meter(, );"
-"set_meter(,);"
-"set_queue"
-"set_queue();"
-"severity"
-"severity="
-"slaac"
-"static_routes"
-"str"
-"table"
-"tcp_reset"
-"type=select,selection_method=dp_hash"
-"uint16"
-"uint32"
-"uint8"
-"verdict"
-"verdict=, "
-"{"
-"||"
-"}"
diff --git a/tests/oss-fuzz/config/expr_parse_target.options b/tests/oss-fuzz/config/expr_parse_target.options
deleted file mode 100644
index fc254e84f..000000000
--- a/tests/oss-fuzz/config/expr_parse_target.options
+++ /dev/null
@@ -1,3 +0,0 @@
-[libfuzzer]
-dict = expr.dict
-close_fd_mask = 3
diff --git a/tests/oss-fuzz/expr_parse_target.c b/tests/oss-fuzz/expr_parse_target.c
deleted file mode 100644
index 170186d9d..000000000
--- a/tests/oss-fuzz/expr_parse_target.c
+++ /dev/null
@@ -1,464 +0,0 @@
-#include <config.h>
-#include "fuzzer.h"
-#include <errno.h>
-#include <getopt.h>
-#include <sys/wait.h>
-
-#include "command-line.h"
-#include "dp-packet.h"
-#include "fatal-signal.h"
-#include "flow.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/match.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/logical-fields.h"
-#include "ovn/lib/ovn-l7.h"
-#include "ovn/lib/extend-table.h"
-#include "openvswitch/shash.h"
-#include "simap.h"
-#include "util.h"
-
-static void
-compare_token(const struct lex_token *a, const struct lex_token *b)
-{
-    if (a->type != b->type) {
-        fprintf(stderr, "type differs: %d -> %d\n", a->type, b->type);
-        return;
-    }
-
-    if (!((a->s && b->s && !strcmp(a->s, b->s))
-          || (!a->s && !b->s))) {
-        fprintf(stderr, "string differs: %s -> %s\n",
-                a->s ? a->s : "(null)",
-                b->s ? b->s : "(null)");
-        return;
-    }
-
-    if (a->type == LEX_T_INTEGER || a->type == LEX_T_MASKED_INTEGER) {
-        if (memcmp(&a->value, &b->value, sizeof a->value)) {
-            fprintf(stderr, "value differs\n");
-            return;
-        }
-
-        if (a->type == LEX_T_MASKED_INTEGER
-            && memcmp(&a->mask, &b->mask, sizeof a->mask)) {
-            fprintf(stderr, "mask differs\n");
-            return;
-        }
-
-        if (a->format != b->format
-            && !(a->format == LEX_F_HEXADECIMAL
-                 && b->format == LEX_F_DECIMAL
-                 && a->value.integer == 0)) {
-            fprintf(stderr, "format differs: %d -> %d\n",
-                    a->format, b->format);
-        }
-    }
-}
-
-static void
-test_lex(const char *input)
-{
-    struct ds output;
-
-    ds_init(&output);
-    struct lexer lexer;
-
-    lexer_init(&lexer, input);
-    ds_clear(&output);
-    while (lexer_get(&lexer) != LEX_T_END) {
-        size_t len = output.length;
-        lex_token_format(&lexer.token, &output);
-
-        /* Check that the formatted version can really be parsed back
-         * losslessly. */
-        if (lexer.token.type != LEX_T_ERROR) {
-            const char *s = ds_cstr(&output) + len;
-            struct lexer l2;
-
-            lexer_init(&l2, s);
-            lexer_get(&l2);
-            compare_token(&lexer.token, &l2.token);
-            lexer_destroy(&l2);
-        }
-        ds_put_char(&output, ' ');
-    }
-    lexer_destroy(&lexer);
-
-    ds_chomp(&output, ' ');
-    puts(ds_cstr(&output));
-    ds_destroy(&output);
-}
-
-static void
-create_symtab(struct shash *symtab)
-{
-    ovn_init_symtab(symtab);
-
-    /* For negative testing. */
-    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
-    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
-                          "self_recurse != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
-                          "mutual_recurse_2 != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
-                          "mutual_recurse_1 != 0", false);
-    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
-}
-
-static void
-create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
-                struct hmap *nd_ra_opts)
-{
-    hmap_init(dhcp_opts);
-    dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
-    dhcp_opt_add(dhcp_opts, "netmask", 1, "ipv4");
-    dhcp_opt_add(dhcp_opts, "router",  3, "ipv4");
-    dhcp_opt_add(dhcp_opts, "dns_server", 6, "ipv4");
-    dhcp_opt_add(dhcp_opts, "log_server", 7, "ipv4");
-    dhcp_opt_add(dhcp_opts, "lpr_server",  9, "ipv4");
-    dhcp_opt_add(dhcp_opts, "domain_name", 15, "str");
-    dhcp_opt_add(dhcp_opts, "swap_server", 16, "ipv4");
-    dhcp_opt_add(dhcp_opts, "policy_filter", 21, "ipv4");
-    dhcp_opt_add(dhcp_opts, "router_solicitation",  32, "ipv4");
-    dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4");
-    dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4");
-    dhcp_opt_add(dhcp_opts, "server_id",  54, "ipv4");
-    dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4");
-    dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes");
-    dhcp_opt_add(dhcp_opts, "ip_forward_enable",  19, "bool");
-    dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool");
-    dhcp_opt_add(dhcp_opts, "ethernet_encap", 36, "bool");
-    dhcp_opt_add(dhcp_opts, "default_ttl",  23, "uint8");
-    dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8");
-    dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16");
-    dhcp_opt_add(dhcp_opts, "lease_time",  51, "uint32");
-    dhcp_opt_add(dhcp_opts, "wpad", 252, "str");
-
-    /* DHCPv6 options. */
-    hmap_init(dhcpv6_opts);
-    dhcp_opt_add(dhcpv6_opts, "server_id",  2, "mac");
-    dhcp_opt_add(dhcpv6_opts, "ia_addr",  5, "ipv6");
-    dhcp_opt_add(dhcpv6_opts, "dns_server",  23, "ipv6");
-    dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
-
-    /* IPv6 ND RA options. */
-    hmap_init(nd_ra_opts);
-    nd_ra_opts_init(nd_ra_opts);
-}
-
-static void
-create_addr_sets(struct shash *addr_sets)
-{
-    shash_init(addr_sets);
-
-    static const char *const addrs1[] = {
-        "10.0.0.1", "10.0.0.2", "10.0.0.3",
-    };
-    static const char *const addrs2[] = {
-        "::1", "::2", "::3",
-    };
-    static const char *const addrs3[] = {
-        "00:00:00:00:00:01", "00:00:00:00:00:02", "00:00:00:00:00:03",
-    };
-    static const char *const addrs4[] = { NULL };
-
-    expr_const_sets_add(addr_sets, "set1", addrs1, 3, true);
-    expr_const_sets_add(addr_sets, "set2", addrs2, 3, true);
-    expr_const_sets_add(addr_sets, "set3", addrs3, 3, true);
-    expr_const_sets_add(addr_sets, "set4", addrs4, 0, true);
-}
-
-static void
-create_port_groups(struct shash *port_groups)
-{
-    shash_init(port_groups);
-
-    static const char *const pg1[] = {
-        "lsp1", "lsp2", "lsp3",
-    };
-    static const char *const pg2[] = { NULL };
-
-    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
-    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
-}
-
-static bool
-lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp)
-{
-    const struct simap *ports = ports_;
-    const struct simap_node *node = simap_find(ports, port_name);
-    if (!node) {
-        return false;
-    }
-    *portp = node->data;
-    return true;
-}
-
-static bool
-is_chassis_resident_cb(const void *ports_, const char *port_name)
-{
-    const struct simap *ports = ports_;
-    const struct simap_node *node = simap_find(ports, port_name);
-    if (node) {
-        return true;
-    }
-    return false;
-}
-
-static void
-test_parse_actions(const char *input)
-{
-    struct shash symtab;
-    struct hmap dhcp_opts;
-    struct hmap dhcpv6_opts;
-    struct hmap nd_ra_opts;
-    struct simap ports;
-
-    create_symtab(&symtab);
-    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
-
-    /* Initialize group ids. */
-    struct ovn_extend_table group_table;
-    ovn_extend_table_init(&group_table);
-
-    /* Initialize meter ids for QoS. */
-    struct ovn_extend_table meter_table;
-    ovn_extend_table_init(&meter_table);
-
-    simap_init(&ports);
-    simap_put(&ports, "eth0", 5);
-    simap_put(&ports, "eth1", 6);
-    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
-
-    struct ofpbuf ovnacts;
-    struct expr *prereqs;
-    char *error;
-
-    puts(input);
-
-    ofpbuf_init(&ovnacts, 0);
-
-    const struct ovnact_parse_params pp = {
-        .symtab = &symtab,
-        .dhcp_opts = &dhcp_opts,
-        .dhcpv6_opts = &dhcpv6_opts,
-        .nd_ra_opts = &nd_ra_opts,
-        .n_tables = 24,
-        .cur_ltable = 10,
-    };
-    error = ovnacts_parse_string(input, &pp, &ovnacts, &prereqs);
-    if (!error) {
-        /* Convert the parsed representation back to a string and print it,
-         * if it's different from the input. */
-        struct ds ovnacts_s = DS_EMPTY_INITIALIZER;
-        ovnacts_format(ovnacts.data, ovnacts.size, &ovnacts_s);
-        if (strcmp(input, ds_cstr(&ovnacts_s))) {
-            printf("    formats as %s\n", ds_cstr(&ovnacts_s));
-        }
-
-        /* Encode the actions into OpenFlow and print. */
-        const struct ovnact_encode_params ep = {
-            .lookup_port = lookup_port_cb,
-            .aux = &ports,
-            .is_switch = true,
-            .group_table = &group_table,
-            .meter_table = &meter_table,
-
-            .pipeline = OVNACT_P_INGRESS,
-            .ingress_ptable = 8,
-            .egress_ptable = 40,
-            .output_ptable = 64,
-            .mac_bind_ptable = 65,
-        };
-        struct ofpbuf ofpacts;
-        ofpbuf_init(&ofpacts, 0);
-        ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
-        struct ds ofpacts_s = DS_EMPTY_INITIALIZER;
-        struct ofpact_format_params fp = { .s = &ofpacts_s };
-        ofpacts_format(ofpacts.data, ofpacts.size, &fp);
-        printf("    encodes as %s\n", ds_cstr(&ofpacts_s));
-        ds_destroy(&ofpacts_s);
-        ofpbuf_uninit(&ofpacts);
-
-        /* Print prerequisites if any. */
-        if (prereqs) {
-            struct ds prereqs_s = DS_EMPTY_INITIALIZER;
-            expr_format(prereqs, &prereqs_s);
-            printf("    has prereqs %s\n", ds_cstr(&prereqs_s));
-            ds_destroy(&prereqs_s);
-        }
-
-        /* Now re-parse and re-format the string to verify that it's
-         * round-trippable. */
-        struct ofpbuf ovnacts2;
-        struct expr *prereqs2;
-        ofpbuf_init(&ovnacts2, 0);
-        error = ovnacts_parse_string(ds_cstr(&ovnacts_s), &pp, &ovnacts2,
-                                     &prereqs2);
-        if (!error) {
-            struct ds ovnacts2_s = DS_EMPTY_INITIALIZER;
-            ovnacts_format(ovnacts2.data, ovnacts2.size, &ovnacts2_s);
-            if (strcmp(ds_cstr(&ovnacts_s), ds_cstr(&ovnacts2_s))) {
-                printf("    bad reformat: %s\n", ds_cstr(&ovnacts2_s));
-            }
-            ds_destroy(&ovnacts2_s);
-        } else {
-            printf("    reparse error: %s\n", error);
-            free(error);
-        }
-        expr_destroy(prereqs2);
-
-        ovnacts_free(ovnacts2.data, ovnacts2.size);
-        ofpbuf_uninit(&ovnacts2);
-        ds_destroy(&ovnacts_s);
-    } else {
-        printf("    %s\n", error);
-        free(error);
-    }
-
-    expr_destroy(prereqs);
-    ovnacts_free(ovnacts.data, ovnacts.size);
-    ofpbuf_uninit(&ovnacts);
-
-    simap_destroy(&ports);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-    dhcp_opts_destroy(&dhcp_opts);
-    dhcp_opts_destroy(&dhcpv6_opts);
-    nd_ra_opts_destroy(&nd_ra_opts);
-    ovn_extend_table_destroy(&group_table);
-    ovn_extend_table_destroy(&meter_table);
-}
-
-static void
-test_parse_expr(const char *input)
-{
-    struct shash symtab;
-    struct shash addr_sets;
-    struct shash port_groups;
-    struct simap ports;
-    struct expr *expr;
-    char *error;
-
-    create_symtab(&symtab);
-    create_addr_sets(&addr_sets);
-    create_port_groups(&port_groups);
-
-    simap_init(&ports);
-    simap_put(&ports, "eth0", 5);
-    simap_put(&ports, "eth1", 6);
-    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
-    simap_put(&ports, "lsp1", 0x11);
-    simap_put(&ports, "lsp2", 0x12);
-    simap_put(&ports, "lsp3", 0x13);
-
-    expr = expr_parse_string(input, &symtab, &addr_sets,
-                             &port_groups, NULL, &error);
-    if (!error) {
-        expr = expr_annotate(expr, &symtab, &error);
-    }
-    if (!error) {
-        expr = expr_simplify(expr, is_chassis_resident_cb, &ports);
-        expr = expr_normalize(expr);
-        ovs_assert(expr_is_normalized(expr));
-    }
-    if (!error) {
-        struct hmap matches;
-
-        expr_to_matches(expr, lookup_port_cb, &ports, &matches);
-        expr_matches_print(&matches, stdout);
-        expr_matches_destroy(&matches);
-    } else {
-        puts(error);
-        free(error);
-    }
-    expr_destroy(expr);
-    simap_destroy(&ports);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-    expr_const_sets_destroy(&addr_sets);
-    shash_destroy(&addr_sets);
-    expr_const_sets_destroy(&port_groups);
-    shash_destroy(&port_groups);
-}
-
-static bool
-lookup_atoi_cb(const void *aux OVS_UNUSED, const char *port_name,
-               unsigned int *portp)
-{
-    *portp = atoi(port_name);
-    return true;
-}
-
-static void
-test_expr_to_packets(const char *input)
-{
-    struct shash symtab;
-    create_symtab(&symtab);
-
-    struct flow uflow;
-    char *error = expr_parse_microflow(input, &symtab, NULL, NULL,
-                                       lookup_atoi_cb, NULL, &uflow);
-    if (error) {
-        puts(error);
-        free(error);
-        expr_symtab_destroy(&symtab);
-        shash_destroy(&symtab);
-        return;
-    }
-
-    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);
-
-    struct ds output = DS_EMPTY_INITIALIZER;
-    const uint8_t *buf = dp_packet_data(&packet);
-    for (int i = 0; i < dp_packet_size(&packet); i++) {
-        uint8_t val = buf[i];
-        ds_put_format(&output, "%02"PRIx8, val);
-    }
-    puts(ds_cstr(&output));
-    ds_destroy(&output);
-    dp_packet_uninit(&packet);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-int
-LLVMFuzzerTestOneInput(const uint8_t *input_, size_t size)
-{
-    /* Bail out if we cannot construct at least a 1 char string. */
-    const char *input = (const char *) input_;
-    if (size < 2 || input[size - 1] != '\0' || strchr(input, '\n') ||
-        strlen(input) != size - 1) {
-        return 0;
-    }
-
-    /* Disable logging to avoid write to disk. */
-    static bool isInit = false;
-    if (!isInit) {
-        vlog_set_verbosity("off");
-        isInit = true;
-    }
-
-    /* Parse, annotate, simplify, normalize expr and convert to flows. */
-    test_parse_expr(input);
-
-    /* Parse actions. */
-    test_parse_actions(input);
-
-    /* Test OVN lexer. */
-    test_lex(input);
-
-    /* Expr to packets. */
-    test_expr_to_packets(input);
-
-    return 0;
-}
diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
deleted file mode 100644
index a3fe8cb88..000000000
--- a/tests/ovn-controller-vtep.at
+++ /dev/null
@@ -1,467 +0,0 @@
-AT_BANNER([ovn_controller_vtep])
-
-# OVN_CONTROLLER_VTEP_START
-#
-# Starts the test with a setup with vtep device.  Each test case must first
-# call this macro.
-#
-# Uses vtep-ovs to simulate the vtep switch 'br-vtep' with two physical ports
-# 'p0', 'p1'.
-#
-# Configures ovn-nb with a logical switch 'br-test'.
-#
-#
-m4_define([OVN_CONTROLLER_VTEP_START],
-  [
-   AT_KEYWORDS([ovn])
-   # this will cause skip when 'make check' using Windows setup.
-   AT_SKIP_IF([test $HAVE_PYTHON = no])
-
-   dnl Create databases (ovn-nb, ovn-sb, vtep).
-   AT_CHECK([ovsdb-tool create vswitchd.db $abs_top_srcdir/vswitchd/vswitch.ovsschema])
-   for daemon in ovn-nb ovn-sb vtep; do
-      AT_CHECK([ovsdb-tool create $daemon.db $abs_top_srcdir/${daemon%%-*}/${daemon}.ovsschema])
-   done
-
-   dnl Start ovsdb-server.
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/db.sock vswitchd.db vtep.db], [0], [], [stderr])
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovsdb-nb-server.pid --log-file=ovsdb-nb-server.log --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovsdb-sb-server.pid --log-file=ovsdb-sb-server.log --remote=punix:$OVS_RUNDIR/ovnsb_db.sock ovn-sb.db ovn-sb.db], [0], [], [stderr])
-   on_exit "kill `cat ovsdb-server.pid` `cat ovsdb-nb-server.pid` `cat ovsdb-sb-server.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d
-/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
-   AT_CAPTURE_FILE([ovsdb-server.log])
-
-   dnl Start ovs-vswitchd.
-   AT_CHECK([ovs-vswitchd --enable-dummy=system --disable-system --detach --no-chdir --pidfile --log-file -vvconn -vofproto_dpif], [0], [], [stderr])
-   AT_CAPTURE_FILE([ovs-vswitchd.log])
-   on_exit "kill `cat ovs-vswitchd.pid`"
-   AT_CHECK([[sed < stderr '
-/ovs_numa|INFO|Discovered /d
-/vlog|INFO|opened log file/d
-/vswitchd|INFO|ovs-vswitchd (Open vSwitch)/d
-/reconnect|INFO|/d
-/ofproto|INFO|using datapath ID/d
-/netlink_socket|INFO|netlink: could not enable listening to all nsid/d
-/ofproto|INFO|datapath ID changed to fedcba9876543210/d']])
-   AT_CHECK([ovs-vsctl -- add-br br-vtep \
-              -- set bridge br-vtep datapath-type=dummy other-config:datapath-id=fedcba9876543210 other-config:hwaddr=aa:55:aa:55:00:00 protocols=[[OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13,OpenFlow14,OpenFlow15]] fail-mode=secure \
-              -- add-port br-vtep p0 -- set Interface p0 type=dummy ofport_request=1 \
-              -- add-port br-vtep p1 -- set Interface p1 type=dummy ofport_request=2])
-
-   dnl Start ovs-vtep.
-   AT_CHECK([vtep-ctl add-ps br-vtep -- set Physical_Switch br-vtep tunnel_ips=1.2.3.4])
-   AT_CHECK([ovs-vtep --log-file=ovs-vtep.log --pidfile=ovs-vtep.pid --detach --no-chdir br-vtep \], [0], [], [stderr])
-   on_exit "kill `cat ovs-vtep.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d']])
-   # waits until ovs-vtep starts up.
-   OVS_WAIT_UNTIL([test -n "`vtep-ctl show | grep Physical_Port`"])
-
-   dnl Start ovn-northd.
-   AT_CHECK([ovn-nbctl ls-add br-test])
-   AT_CHECK([ovn-northd --detach --no-chdir --pidfile --log-file], [0], [], [stderr])
-   on_exit "kill `cat ovn-northd.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d']])
-   AT_CAPTURE_FILE([ovn-northd.log])
-
-   dnl Start ovn-controllger-vtep.
-   AT_CHECK([ovn-controller-vtep --detach --no-chdir --pidfile --log-file --vtep-db=unix:$OVS_RUNDIR/db.sock --ovnsb-db=unix:$OVS_RUNDIR/ovnsb_db.sock], [0], [], [stderr])
-   AT_CAPTURE_FILE([ovn-controller-vtep.log])
-   on_exit "kill `cat ovn-controller-vtep.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d
-/reconnect|INFO|/d']])
-])
-
-# OVN_CONTROLLER_VTEP_STOP
-#
-# So many exits... Yeah, we started a lot daemons~
-#
-m4_define([OVN_CONTROLLER_VTEP_STOP],
-  [AT_CHECK([check_logs "$1"])
-   OVS_APP_EXIT_AND_WAIT([ovs-vtep])
-   OVS_APP_EXIT_AND_WAIT([ovn-northd])
-   OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep])
-   OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-   OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])])
-
-# Adds logical port for a vtep gateway chassis in ovn-nb database.
-#
-# $1: logical switch name in ovn-nb database
-# $2: logical port name
-# $3: physical vtep gateway name
-# $4: logical switch name on vtep gateway chassis
-m4_define([OVN_NB_ADD_VTEP_PORT], [
-AT_CHECK([ovn-nbctl lsp-add $1 $2])
-
-AT_CHECK([ovn-nbctl lsp-set-type $2 vtep])
-AT_CHECK([ovn-nbctl lsp-set-options $2 vtep-physical-switch=$3 vtep-logical-switch=$4])
-])
-
-##############################################
-
-# tests chassis related updates.
-AT_SETUP([ovn-controller-vtep - chassis])
-OVN_CONTROLLER_VTEP_START
-
-# verifies the initial ovn-sb db configuration.
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl show | grep Chassis`"])
-AT_CHECK([ovn-sbctl show], [0], [dnl
-Chassis br-vtep
-    Encap vxlan
-        ip: "1.2.3.4"
-        options: {csum="false"}
-])
-
-# deletes the chassis via ovn-sbctl and check that it is readded back
-# with the log.
-AT_CHECK([ovn-sbctl chassis-del br-vtep])
-OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"])
-AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log], [0], [dnl
-|WARN|Chassis for VTEP physical switch (br-vtep) disappears, maybe deleted by ovn-sbctl, adding it back
-])
-
-# changes the tunnel_ip on physical switch, watches the update of chassis's
-# encap.
-AT_CHECK([vtep-ctl set Physical_Switch br-vtep tunnel_ips=1.2.3.5])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl show | grep 1\.2\.3\.5`"])
-AT_CHECK([ovn-sbctl --columns=ip list Encap | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-"1.2.3.5"
-])
-
-# adds vlan_bindings to physical ports.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p0 200 lswitch0 -- bind-ls br-vtep p1 300 lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch0`"])
-AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl
-[[lswitch0]]
-])
-
-# adds another logical switch and new vlan_bindings.
-AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 300 lswitch1])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch1`"])
-AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-[[lswitch0,lswitch1]]
-])
-
-# unbinds one port from lswitch0, nothing should change.
-AT_CHECK([vtep-ctl unbind-ls br-vtep p0 200])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=vlan_bindings list physical_port p0 | grep -- '200='`"])
-AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl
-[[lswitch0,lswitch1]]
-])
-
-# unbinds all ports from lswitch0.
-AT_CHECK([vtep-ctl unbind-ls br-vtep p0 100 -- unbind-ls br-vtep p1 300])
-OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch0`"])
-AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl
-[[lswitch1]]
-])
-
-# unbinds all ports from lswitch1.
-AT_CHECK([vtep-ctl unbind-ls br-vtep p0 300])
-OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch1`"])
-AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-[[]]
-])
-
-OVN_CONTROLLER_VTEP_STOP([/Chassis for VTEP physical switch (br-vtep) disappears/d])
-AT_CLEANUP
-
-
-# Tests binding updates.
-AT_SETUP([ovn-controller-vtep - binding 1])
-OVN_CONTROLLER_VTEP_START
-
-# adds logical switch 'lswitch0' and vlan_bindings.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p1 300 lswitch0])
-# adds logical switch port in ovn-nb database, and sets the type and options.
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
-ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]'
-# should see one binding, associated to chassis of 'br-vtep'.
-chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ')
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-${chassis_uuid}
-])
-
-# adds another logical switch 'lswitch1' and vlan_bindings.
-AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1])
-# adds logical switch port in ovn-nb database for lswitch1.
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1])
-ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]'
-# This is allowed, but not recommended, to have two vlan_bindings (to different vtep logical switches)
-# from one vtep gateway physical port in one ovn-nb logical swithch.
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl
-
-${chassis_uuid}
-${chassis_uuid}
-])
-
-# adds another logical switch port in ovn-nb database for lswitch0.
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0])
-ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0_dup chassis!='[[]]'
-# it is not allowed to have more than one ovn-nb logical port for the same
-# vtep logical switch on a vtep gateway chassis, so should still see only
-# two port_binding entries bound.
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort | sort -d], [0], [dnl
-
-
-[[]]
-${chassis_uuid}
-${chassis_uuid}
-])
-# confirms the warning log.
-AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g' | uniq], [0], [dnl
-|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical port (), ignore logical port ()
-])
-
-# deletes physical ports from vtep.
-AT_CHECK([ovs-vsctl del-port p0 -- del-port p1])
-OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p1`"])
-# should see empty chassis column in both binding entries.
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl
-
-
-[[]]
-[[]]
-[[]]
-])
-
-OVN_CONTROLLER_VTEP_STOP([/has already been associated with logical port/d])
-AT_CLEANUP
-
-
-# Tests corner case: Binding the vtep logical switch from two different
-# datapath.
-AT_SETUP([ovn-controller-vtep - binding 2])
-OVN_CONTROLLER_VTEP_START
-
-# adds logical switch 'lswitch0' and vlan_bindings.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0])
-# adds logical switch port in ovn-nb database, and sets the type and options.
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
-ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]'
-
-# adds another lswitch 'br-void' in ovn-nb database.
-AT_CHECK([ovn-nbctl ls-add br-void])
-# adds another vtep pswitch 'br-vtep-void' in vtep database.
-AT_CHECK([vtep-ctl add-ps br-vtep-void -- add-port br-vtep-void p0-void -- bind-ls br-vtep-void p0-void 100 lswitch0])
-# adds a conflicting logical port (both br-vtep_lswitch0 and br-vtep-void_lswitch0
-# are bound to the same logical switch, but they are on different datapath).
-OVN_NB_ADD_VTEP_PORT([br-void], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0])
-ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0
-OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"])
-# confirms the warning log.
-AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g;s/(with tunnel key [[0-9]][[0-9]]*)/()/g' | uniq], [0], [dnl
-|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical datapath (), ignore logical port () which belongs to logical datapath ()
-])
-
-# then deletes 'br-void' and 'br-vtep-void', should see 'br-vtep_lswitch0'
-# bound correctly.
-AT_CHECK([ovn-nbctl ls-del br-void])
-# adds another vtep pswitch 'br-vtep-void' in vtep database.
-AT_CHECK([vtep-ctl del-ps br-vtep-void])
-OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Port_Binding | grep br-vtep-void_lswitch0`"])
-chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ')
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-${chassis_uuid}
-])
-
-OVN_CONTROLLER_VTEP_STOP([/has already been associated with logical datapath/d])
-AT_CLEANUP
-
-
-# Tests vtep module vtep logical switch tunnel key update.
-AT_SETUP([ovn-controller-vtep - vtep-lswitch])
-OVN_CONTROLLER_VTEP_START
-
-# creates the logical switch in vtep and adds the corresponding logical
-# port to 'br-test'.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0])
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep -- br-vtep_lswitch0`"])
-
-# retrieves the expected tunnel key.
-datapath_uuid=$(ovn-sbctl --columns=datapath list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' ')
-tunnel_key=$(ovn-sbctl --columns=tunnel_key list Datapath_Binding ${datapath_uuid} | cut -d ':' -f2 | tr -d ' ')
-OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=tunnel_key list Logical_Switch | grep 0`"])
-# checks the vtep logical switch tunnel key configuration.
-AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-${tunnel_key}
-])
-
-# creates a second physical switch in vtep database, and binds its p0 vlan-100
-# to the same logical switch 'lswitch0'.
-AT_CHECK([vtep-ctl add-ps br-vtep-void -- add-port br-vtep-void p0 -- bind-ls br-vtep-void p0 100 lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl --columns=name list Chassis  | grep -- br-vtep-void`"])
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep -- br-vtep-void_lswitch0`"])
-
-# checks the vtep logical switch tunnel key configuration.
-AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-${tunnel_key}
-])
-
-# now, deletes br-vtep-void.
-AT_CHECK([vtep-ctl del-ps br-vtep-void])
-OVS_WAIT_UNTIL([test -z "`ovn-sbctl --columns=name list Chassis  | grep -- br-vtep-void`"])
-# checks the vtep logical switch tunnel key configuration.
-AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-${tunnel_key}
-])
-
-# changes the ovn-nb logical port type so that it is no longer
-# vtep port.
-AT_CHECK([ovn-nbctl lsp-set-type br-vtep_lswitch0 ""])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=tunnel_key list Logical_Switch | grep 1`"])
-# now should see the tunnel key reset.
-AT_CHECK([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-0
-])
-
-OVN_CONTROLLER_VTEP_STOP
-AT_CLEANUP
-
-
-# Tests vtep module 'Ucast_Macs_Remote's.
-AT_SETUP([ovn-controller-vtep - vtep-macs 1])
-OVN_CONTROLLER_VTEP_START
-
-# creates a simple logical network with the vtep device and a fake hv chassis
-# 'ch0'.
-AT_CHECK([ovn-nbctl lsp-add br-test vif0])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync])
-AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5])
-AT_CHECK([ovn-sbctl lsp-bind vif0 ch0])
-
-# creates the logical switch in vtep and adds the corresponding logical
-# port to 'br-test'.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0])
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-vtep_lswitch0`"])
-
-# adds another lswitch 'br-void' in ovn-nb database.
-AT_CHECK([ovn-nbctl ls-add br-void])
-# adds fake hv chassis 'ch1'.
-AT_CHECK([ovn-nbctl lsp-add br-void vif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync])
-AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6])
-AT_CHECK([ovn-sbctl lsp-bind vif1 ch1])
-
-# checks Ucast_Macs_Remote creation.
-OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep _uuid`"])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' '], [0], [dnl
-"f0:ab:cd:ef:01:02"
-])
-
-# checks physical locator creation.
-OVS_WAIT_UNTIL([test -n "`vtep-ctl list Physical_Locator | grep _uuid`"])
-AT_CHECK([vtep-ctl --columns=dst_ip list Physical_Locator | cut -d ':' -f2 | tr -d ' ' | grep -v 1.2.3.4 | sed '/^$/d'], [0], [dnl
-"1.2.3.5"
-])
-
-# checks tunnel creation by ovs-vtep.
-OVS_WAIT_UNTIL([test -n "`ovs-vsctl list Interface bfd1.2.3.5`"])
-AT_CHECK([ovs-vsctl --columns=options list Interface bfd1.2.3.5 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
-{remote_ip="1.2.3.5"}
-])
-
-# adds another mac to logical switch port.
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02 f0:ab:cd:ef:01:03])
-OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep 03`"])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
-
-"f0:ab:cd:ef:01:02"
-"f0:ab:cd:ef:01:03"
-])
-
-# removes one mac to logical switch port.
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:03])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 02`"])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
-"f0:ab:cd:ef:01:03"
-])
-
-# migrates mac to logical switch port vif1 on 'br-void'.
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:03])
-OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 03`"])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
-])
-
-OVN_CONTROLLER_VTEP_STOP
-AT_CLEANUP
-
-
-# Tests vtep module 'Ucast_Macs_Remote's (corner cases).
-AT_SETUP([ovn-controller-vtep - vtep-macs 2])
-OVN_CONTROLLER_VTEP_START
-
-# creates a simple logical network with the vtep device and a fake hv chassis
-# 'ch0'.
-AT_CHECK([ovn-nbctl lsp-add br-test vif0])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync])
-AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5])
-AT_CHECK([ovn-sbctl lsp-bind vif0 ch0])
-
-# creates another vif in the same logical switch with duplicate mac.
-AT_CHECK([ovn-nbctl lsp-add br-test vif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync])
-AT_CHECK([ovn-sbctl lsp-bind vif1 ch0])
-
-# creates the logical switch in vtep and adds the corresponding logical
-# port to 'br-test'.
-AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0])
-OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-vtep_lswitch0`"])
-
-# checks Ucast_Macs_Remote creation.  Should still only be one entry, since duplicate
-# mac in the same logical switch is not allowed.
-OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep _uuid`"])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' '], [0], [dnl
-"f0:ab:cd:ef:01:02"
-])
-# confirms the warning log.
-OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"])
-AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_:0-9a-z]][[-_:0-9a-z]]*)/()/g' | uniq], [0], [dnl
-|WARN|MAC address () has already been known to be on logical port () in the same logical datapath, so just ignore this logical port ()
-])
-
-# deletes vif1.
-AT_CHECK([ovn-nbctl lsp-del vif1])
-
-# adds another lswitch 'br-void' in ovn-nb database.
-AT_CHECK([ovn-nbctl ls-add br-void])
-# adds fake hv chassis 'ch1' and vif1 with same mac address as vif0.
-AT_CHECK([ovn-nbctl lsp-add br-void vif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync])
-AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6])
-AT_CHECK([ovn-sbctl lsp-bind vif1 ch1])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep vif1`"])
-
-# creates another logical switch in vtep and adds the corresponding logical
-# port to 'br-void'.
-AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1])
-OVN_NB_ADD_VTEP_PORT([br-void], [br-void_lswitch1], [br-vtep], [lswitch1])
-OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-void_lswitch1`"])
-
-# checks Ucast_Macs_Remote creation.  Should see two entries since it is allowed
-# to have duplicate macs in different logical switches.
-OVS_WAIT_UNTIL([test `vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 02 | wc -l` -gt 1])
-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
-
-"f0:ab:cd:ef:01:02"
-"f0:ab:cd:ef:01:02"
-])
-
-OVN_CONTROLLER_VTEP_STOP([/has already been known to be on logical port/d])
-AT_CLEANUP
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
deleted file mode 100644
index 343c2abed..000000000
--- a/tests/ovn-controller.at
+++ /dev/null
@@ -1,294 +0,0 @@
-AT_BANNER([ovn-controller])
-
-AT_SETUP([ovn-controller - ovn-bridge-mappings])
-AT_KEYWORDS([ovn])
-ovn_init_db ovn-sb
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0 \
-    -- add-br br-eth1 \
-    -- add-br br-eth2
-ovn_attach n1 br-phys 192.168.0.1
-
-# Waits until the OVS database contains exactly the specified patch ports.
-# Each argument should be of the form BRIDGE PORT PEER.
-check_patches () {
-    # Generate code to check that the set of patch ports is exactly as
-    # specified.
-    echo 'ovs-vsctl -f csv -d bare --no-headings --columns=name find Interface type=patch | sort' > query
-    for patch
-    do
-        echo $patch
-    done | cut -d' ' -f 2 | sort > expout
-
-    # Generate code to verify that the configuration of each patch
-    # port is correct.
-    for patch
-    do
-        set $patch; bridge=$1 port=$2 peer=$3
-        echo >>query "ovs-vsctl iface-to-br $port -- get Interface $port type options"
-        echo >>expout "$bridge
-patch
-{peer=$peer}"
-    done
-
-    # Run the query until we get the expected result (or until a timeout).
-    #
-    # (We use sed to drop all "s from output because ovs-vsctl quotes some
-    # of the port names but not others.)
-    AT_CAPTURE_FILE([query])
-    AT_CAPTURE_FILE([expout])
-    AT_CAPTURE_FILE([stdout])
-    OVS_WAIT_UNTIL([. ./query | sed 's/"//g' > stdout #"
-                    diff -u stdout expout >/dev/null])
-}
-
-# Make sure that the configured bridge mappings in the Open_vSwitch db
-# is mirrored into the Chassis record in the OVN_Southbound db.
-check_bridge_mappings () {
-    local_mappings=$1
-    sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
-    OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sysid} external_ids:ovn-bridge-mappings | sed -e 's/\"//g')])
-}
-
-# Initially there should be no patch ports.
-check_patches
-
-# Configure two ovn-bridge mappings, but no patch ports should be created yet
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1])
-check_bridge_mappings "physnet1:br-eth0,physnet2:br-eth1"
-check_patches
-
-# Create a localnet port, but we should still have no patch ports, as they
-# won't be created until there's a localnet port on a logical switch with
-# another logical port bound to this chassis.
-ovn-sbctl \
-    -- --id=@dp101 create Datapath_Binding tunnel_key=101 \
-    -- create Port_Binding datapath=@dp101 logical_port=localnet1 tunnel_key=1 \
-        type=localnet options:network_name=physnet1
-check_patches
-
-# Create a localnet port on a logical switch with a port bound to this chassis.
-# Now we should get some patch ports created.
-ovn-sbctl \
-    -- --id=@dp102 create Datapath_Binding tunnel_key=102 \
-    -- create Port_Binding datapath=@dp102 logical_port=localnet2 tunnel_key=1 \
-        type=localnet options:network_name=physnet1 \
-    -- create Port_Binding datapath=@dp102 logical_port=localvif2 tunnel_key=2
-ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2
-check_patches \
-    'br-int  patch-br-int-to-localnet2 patch-localnet2-to-br-int' \
-    'br-eth0 patch-localnet2-to-br-int patch-br-int-to-localnet2'
-
-# Add logical patch ports to connect new logical datapath.
-#
-# OVN no longer uses OVS patch ports to implement logical patch ports, so
-# the set of OVS patch ports doesn't change.
-AT_CHECK([ovn-sbctl \
-    -- --id=@dp1 create Datapath_Binding tunnel_key=1 \
-    -- --id=@dp2 create Datapath_Binding tunnel_key=2 \
-    -- create Port_Binding datapath=@dp1 logical_port=foo tunnel_key=1 type=patch options:peer=bar \
-    -- create Port_Binding datapath=@dp2 logical_port=bar tunnel_key=2 type=patch options:peer=foo \
-    -- create Port_Binding datapath=@dp1 logical_port=dp1vif tunnel_key=3 \
-| uuidfilt], [0], [<0>
-<1>
-<2>
-<3>
-<4>
-])
-ovs-vsctl add-port br-int dp1vif -- set Interface dp1vif external_ids:iface-id=dp1vif
-check_patches \
-    'br-int  patch-br-int-to-localnet2 patch-localnet2-to-br-int' \
-    'br-eth0 patch-localnet2-to-br-int patch-br-int-to-localnet2'
-
-# Delete the mapping and the ovn-bridge-mapping patch ports should go away.
-AT_CHECK([ovs-vsctl remove Open_vSwitch . external-ids ovn-bridge-mappings])
-check_bridge_mappings
-check_patches
-
-# Gracefully terminate daemons
-OVN_CLEANUP_SBOX([hv])
-OVN_CLEANUP_VSWITCH([main])
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-AT_CLEANUP
-
-# Checks that ovn-controller populates datapath-type and iface-types
-# correctly in the Chassis external-ids column.
-AT_SETUP([ovn-controller - Chassis external_ids])
-AT_KEYWORDS([ovn])
-ovn_init_db ovn-sb
-
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0 \
-    -- add-br br-eth1 \
-    -- add-br br-eth2
-ovn_attach n1 br-phys 192.168.0.1
-
-sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
-
-# Make sure that the datapath_type set in the Bridge table
-# is mirrored into the Chassis record in the OVN_Southbound db.
-check_datapath_type () {
-    datapath_type=$1
-    chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} external_ids:datapath-type | sed -e 's/"//g') #"
-    test "${datapath_type}" = "${chassis_datapath_type}"
-}
-
-OVS_WAIT_UNTIL([check_datapath_type ""])
-
-ovs-vsctl set Bridge br-int datapath-type=foo
-OVS_WAIT_UNTIL([check_datapath_type foo])
-
-# Change "ovn-bridge-mappings" value. It should not change the "datapath-type".
-ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings=foo-mapping
-check_datapath_type foo
-
-ovs-vsctl set Bridge br-int datapath-type=bar
-OVS_WAIT_UNTIL([check_datapath_type bar])
-
-ovs-vsctl set Bridge br-int datapath-type=\"\"
-OVS_WAIT_UNTIL([check_datapath_type ""])
-
-# Set the datapath_type in external_ids:ovn-bridge-datapath-type.
-ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foo
-OVS_WAIT_UNTIL([check_datapath_type foo])
-
-# Change the br-int's datapath type to bar.
-# It should be reset to foo since ovn-bridge-datapath-type is configured.
-ovs-vsctl set Bridge br-int datapath-type=bar
-OVS_WAIT_UNTIL([test foo=`ovs-vsctl get Bridge br-int datapath-type`])
-OVS_WAIT_UNTIL([check_datapath_type foo])
-
-ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foobar
-OVS_WAIT_UNTIL([test foobar=`ovs-vsctl get Bridge br-int datapath-type`])
-OVS_WAIT_UNTIL([check_datapath_type foobar])
-
-expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""')
-echo "expected_iface_types = ${expected_iface_types}"
-chassis_iface_types=$(ovn-sbctl get Chassis ${sysid} external_ids:iface-types | sed -e 's/\"//g')
-echo "chassis_iface_types = ${chassis_iface_types}"
-AT_CHECK([test "${expected_iface_types}" = "${chassis_iface_types}"])
-
-# Change the value of external_ids:iface-types using ovn-sbctl.
-# ovn-controller should again set it back to proper one.
-ovn-sbctl set Chassis ${sysid} external_ids:iface-types="foo"
-OVS_WAIT_UNTIL([
-    chassis_iface_types=$(ovn-sbctl get Chassis ${sysid} external_ids:iface-types | sed -e 's/\"//g')
-    echo "chassis_iface_types = ${chassis_iface_types}"
-    test "${expected_iface_types}" = "${chassis_iface_types}"
-])
-
-# Change the value of external_ids:system-id and make sure it's mirrored
-# in the Chassis record in the OVN_Southbound database.
-sysid=${sysid}-foo
-ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}"
-OVS_WAIT_UNTIL([
-    chassis_id=$(ovn-sbctl get Chassis "${sysid}" name)
-    test "${sysid}" = "${chassis_id}"
-])
-
-# Gracefully terminate daemons
-OVN_CLEANUP_SBOX([hv])
-OVN_CLEANUP_VSWITCH([main])
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-AT_CLEANUP
-
-# Checks that ovn-controller correctly maintains the mapping from the Encap
-# table in the Southbound database to OVS in the face of changes on both sides
-AT_SETUP([ovn-controller - change Encap properties])
-AT_KEYWORDS([ovn])
-ovn_init_db ovn-sb
-
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0 \
-    -- add-br br-eth1 \
-    -- add-br br-eth2
-ovn_attach n1 br-phys 192.168.0.1
-
-check_tunnel_property () {
-    test "`ovs-vsctl get interface ovn-fakech-0 $1`" = "$2"
-}
-
-# Start off with a remote chassis supporting STT
-ovn-sbctl chassis-add fakechassis stt 192.168.0.2
-OVS_WAIT_UNTIL([check_tunnel_property type stt])
-
-# See if we switch to Geneve as the first choice when it is available
-# With multi-VTEP support we support tunnels with different IPs to the
-# same chassis, and hence use the IP to annotate the tunnel (along with
-# the chassis-id in ovn-chassis-id); if we supply a different IP here
-# we won't be able to co-relate this to the tunnel port that was created
-# in the previous step and, as a result, will end up creating another tunnel,
-# ie. we can't just lookup using "ovn-fakech-0". So, need to use the same IP
-# as above, i.e 192.168.0.2, here.
-encap_uuid=$(ovn-sbctl add chassis fakechassis encaps @encap -- --id=@encap create encap type=geneve ip="192.168.0.2")
-OVS_WAIT_UNTIL([check_tunnel_property type geneve])
-
-# Check that changes within an encap row are propagated
-ovn-sbctl set encap ${encap_uuid} ip=192.168.0.2
-OVS_WAIT_UNTIL([check_tunnel_property options:remote_ip "\"192.168.0.2\""])
-
-# Change the type on the OVS side and check than OVN fixes it
-ovs-vsctl set interface ovn-fakech-0 type=vxlan
-OVS_WAIT_UNTIL([check_tunnel_property type geneve])
-
-# Delete the port entirely and it should be resurrected
-ovs-vsctl del-port ovn-fakech-0
-OVS_WAIT_UNTIL([check_tunnel_property type geneve])
-
-# Gracefully terminate daemons
-OVN_CLEANUP_SBOX([hv])
-OVN_CLEANUP_VSWITCH([main])
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-AT_CLEANUP
-
-# Check ovn-controller connection status to Southbound database
-AT_SETUP([ovn-controller - check sbdb connection])
-AT_KEYWORDS([ovn])
-ovn_init_db ovn-sb
-
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0 \
-    -- add-br br-eth1 \
-    -- add-br br-eth2
-ovn_attach n1 br-phys 192.168.0.1
-
-check_sbdb_connection () {
-    test "$(ovs-appctl -t ovn-controller connection-status)" = "$1"
-}
-
-OVS_WAIT_UNTIL([check_sbdb_connection connected])
-
-ovs-vsctl set open . external_ids:ovn-remote=tcp:192.168.0.10:6642
-OVS_WAIT_UNTIL([check_sbdb_connection 'not connected'])
-
-# reset the remote for clean-up
-ovs-vsctl set open . external_ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock
-# Gracefully terminate daemons
-OVN_CLEANUP_SBOX([hv])
-OVN_CLEANUP_VSWITCH([main])
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-AT_CLEANUP
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
deleted file mode 100644
index 7dba42c99..000000000
--- a/tests/ovn-macros.at
+++ /dev/null
@@ -1,180 +0,0 @@
-# OVN_CLEANUP_VSWITCH(sim)
-#
-# Gracefully terminate vswitch daemons in the
-# specified sandbox.
-m4_define([OVN_CLEANUP_VSWITCH],[
-    as $1
-    OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
-    OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-])
-
-# OVN_CLEANUP_SBOX(sbox)
-#
-# Gracefully terminate OVN daemons in the specified
-# sandbox instance. The sandbox name "vtep" is treated
-# as a special case, and is assumed to have ovn-controller-vtep
-# and ovs-vtep daemons running instead of ovn-controller.
-m4_define([OVN_CLEANUP_SBOX],[
-    as $1
-    if test "$1" = "vtep"; then
-        OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep])
-        OVS_APP_EXIT_AND_WAIT([ovs-vtep])
-    else
-        OVS_APP_EXIT_AND_WAIT([ovn-controller])
-    fi
-    OVN_CLEANUP_VSWITCH([$1])
-])
-
-# OVN_CLEANUP(sim [, sim ...])
-#
-# Gracefully terminate all OVN daemons, including those in the
-# specified sandbox instances.
-m4_define([OVN_CLEANUP],[
-    m4_foreach([sbox], [$@], [
-        OVN_CLEANUP_SBOX([sbox])
-    ])
-    as ovn-sb
-    OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-    as ovn-nb
-    OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-    as northd
-    OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-    as northd-backup
-    OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-    OVN_CLEANUP_VSWITCH([main])
-])
-
-m4_divert_push([PREPARE_TESTS])
-
-# ovn_init_db DATABASE
-#
-# Creates and initializes the given DATABASE (one of "ovn-sb" or "ovn-nb"),
-# starts its ovsdb-server instance, and sets the appropriate environment
-# variable (OVN_SB_DB or OVN_NB_DB) so that ovn-sbctl or ovn-nbctl uses the
-# database by default.
-#
-# Usually invoked from ovn_start.
-ovn_init_db () {
-    echo "creating $1 database"
-    local d=$ovs_base/$1
-    mkdir "$d" || return 1
-    : > "$d"/.$1.db.~lock~
-    as $1 ovsdb-tool create "$d"/$1.db "$abs_top_srcdir"/ovn/$1.ovsschema
-    as $1 start_daemon ovsdb-server --remote=punix:"$d"/$1.sock "$d"/$1.db
-    local var=`echo $1_db | tr a-z- A-Z_`
-    AS_VAR_SET([$var], [unix:$ovs_base/$1/$1.sock]); export $var
-}
-
-# ovn_start
-#
-# Creates and initializes ovn-sb and ovn-nb databases and starts their
-# ovsdb-server instance, sets appropriate environment variables so that
-# ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running
-# against them.
-ovn_start () {
-    ovn_init_db ovn-sb; ovn-sbctl init
-    ovn_init_db ovn-nb; ovn-nbctl init
-
-    echo "starting ovn-northd"
-    mkdir "$ovs_base"/northd
-    as northd start_daemon ovn-northd -v \
-               --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \
-               --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-
-    echo "starting backup ovn-northd"
-    mkdir "$ovs_base"/northd-backup
-    as northd-backup start_daemon ovn-northd -v \
-               --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \
-               --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-}
-
-# Interconnection networks.
-#
-# When multiple sandboxed Open vSwitch instances exist, one will inevitably
-# want to connect them together.  These commands allow for that.  Conceptually,
-# an interconnection network is a switch for which these functions make it easy
-# to plug into other switches in other sandboxed Open vSwitch instances.
-# Interconnection networks are implemented as bridges in a switch named "main",
-# so to use interconnection networks please avoid working with that switch
-# directly.
-
-# net_add NETWORK
-#
-# Creates a new interconnection network named NETWORK.
-net_add () {
-    test -d "$ovs_base"/main || sim_add main || return 1
-    as main ovs-vsctl add-br "$1"
-}
-
-# net_attach NETWORK BRIDGE
-#
-# Adds a new port to BRIDGE in the default sandbox (as set with as()) and plugs
-# it into the NETWORK interconnection network.  NETWORK must already have been
-# created by a previous invocation of net_add.  The default sandbox must not be
-# "main".
-net_attach () {
-    local net=$1 bridge=$2
-
-    local port=${sandbox}_$bridge
-    as main ovs-vsctl \
-        -- add-port $net $port \
-        -- set Interface $port options:pstream="punix:$ovs_base/main/$port.sock" options:rxq_pcap="$ovs_base/main/$port-rx.pcap" options:tx_pcap="$ovs_base/main/$port-tx.pcap" \
-        || return 1
-
-    ovs-vsctl \
-        -- set Interface $bridge options:tx_pcap="$ovs_base/$sandbox/$bridge-tx.pcap" options:rxq_pcap="$ovs_base/$sandbox/$bridge-rx.pcap" \
-        -- add-port $bridge ${bridge}_$net \
-        -- set Interface ${bridge}_$net options:stream="unix:$ovs_base/main/$port.sock" options:rxq_pcap="$ovs_base/$sandbox/${bridge}_$net-rx.pcap" options:tx_pcap="$ovs_base/$sandbox/${bridge}_$net-tx.pcap" \
-        || return 1
-}
-
-# 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.
-ovn_attach() {
-    local net=$1 bridge=$2 ip=$3 masklen=${4-24}
-    net_attach $net $bridge || return 1
-
-    mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
-    arp_table="$arp_table $sandbox,$bridge,$ip,$mac"
-    ovs-appctl netdev-dummy/ip4addr $bridge $ip/$masklen >/dev/null || return 1
-    ovs-appctl ovs/route/add $ip/$masklen $bridge >/dev/null || return 1
-    ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=$sandbox \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve,vxlan \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip \
-        -- add-br br-int \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
-        || return 1
-    start_daemon ovn-controller || return 1
-}
-
-# OVN_POPULATE_ARP
-#
-# This pre-populates the ARP tables of all of the OVN instances that have been
-# started with ovn_attach().  That means that packets sent from one hypervisor
-# to another never get dropped or delayed by ARP resolution, which makes
-# testing easier.
-ovn_populate_arp__() {
-    for e1 in $arp_table; do
-        set `echo $e1 | sed 's/,/ /g'`; sb1=$1 br1=$2 ip=$3 mac=$4
-        for e2 in $arp_table; do
-            set `echo $e2 | sed 's/,/ /g'`; sb2=$1 br2=$2
-            if test $sb1,$br1 != $sb2,$br2; then
-                as $sb2 ovs-appctl tnl/neigh/set $br2 $ip $mac || return 1
-            fi
-        done
-    done
-}
-m4_divert_pop([PREPARE_TESTS])
-
-m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
deleted file mode 100644
index d99d3af9b..000000000
--- a/tests/ovn-nbctl.at
+++ /dev/null
@@ -1,1660 +0,0 @@
-AT_BANNER([ovn-nbctl])
-
-OVS_START_SHELL_HELPERS
-# OVN_NBCTL_TEST_START
-m4_define([OVN_NBCTL_TEST_START],
-  [AT_KEYWORDS([ovn])
-   AT_CAPTURE_FILE([ovsdb-server.log])
-   ovn_nbctl_test_start $1])
-ovn_nbctl_test_start() {
-   dnl Create ovn-nb database.
-   AT_CHECK([ovsdb-tool create ovn-nb.db $abs_top_srcdir/ovn/ovn-nb.ovsschema])
-
-   dnl Start ovsdb-server.
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
-   on_exit "kill `cat ovsdb-server.pid`"
-   AS_CASE([$1],
-     [daemon],
-       [export OVN_NB_DAEMON=$(ovn-nbctl --pidfile --detach --no-chdir --log-file -vsocket_util:off)
-        on_exit "kill `cat ovn-nbctl.pid`"],
-     [direct], [],
-     [*], [AT_FAIL_IF(:)])
-   AT_CHECK([ovn-nbctl init])
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d
-/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
-}
-
-# OVN_NBCTL_TEST_STOP
-m4_define([OVN_NBCTL_TEST_STOP], [ovn_nbctl_test_stop])
-ovn_nbctl_test_stop() {
-   AT_CHECK([check_logs "$1"])
-   OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-}
-OVS_END_SHELL_HELPERS
-
-# OVN_NBCTL_TEST(NAME, TITLE, COMMANDS)
-m4_define([OVN_NBCTL_TEST],
-   [OVS_START_SHELL_HELPERS
-    $1() {
-      $3
-    }
-    OVS_END_SHELL_HELPERS
-
-    AT_SETUP([ovn-nbctl - $2 - direct])
-    OVN_NBCTL_TEST_START direct
-    $1
-    OVN_NBCTL_TEST_STOP
-    AT_CLEANUP
-
-    AT_SETUP([ovn-nbctl - $2 - daemon])
-    OVN_NBCTL_TEST_START daemon
-    $1
-    OVN_NBCTL_TEST_STOP
-    AT_CLEANUP])
-
-OVN_NBCTL_TEST([ovn_nbctl_basic_switch], [basic switch commands], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl
-<0> (ls0)
-])
-
-AT_CHECK([ovn-nbctl ls-add ls1])
-AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl
-<0> (ls0)
-<1> (ls1)
-])
-
-AT_CHECK([ovn-nbctl ls-del ls0])
-AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl
-<0> (ls1)
-])
-
-AT_CHECK([ovn-nbctl show ls0])
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl show ls0 | uuidfilt], [0],
-  [switch <0> (ls0)
-])
-AT_CHECK([ovn-nbctl ls-add ls0], [1], [],
-  [ovn-nbctl: ls0: a switch with this name already exists
-])
-AT_CHECK([ovn-nbctl --may-exist ls-add ls0])
-AT_CHECK([ovn-nbctl show ls0 | uuidfilt], [0],
-  [switch <0> (ls0)
-])
-AT_CHECK([ovn-nbctl --add-duplicate ls-add ls0])
-AT_CHECK([ovn-nbctl --may-exist --add-duplicate ls-add ls0], [1], [],
-  [ovn-nbctl: --may-exist and --add-duplicate may not be used together
-])
-AT_CHECK([ovn-nbctl ls-del ls0], [1], [],
-  [ovn-nbctl: Multiple logical switches named 'ls0'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl ls-del ls2], [1], [],
-  [ovn-nbctl: ls2: switch name not found
-])
-AT_CHECK([ovn-nbctl --if-exists ls-del ls2])
-
-AT_CHECK([ovn-nbctl ls-add])
-AT_CHECK([ovn-nbctl ls-add])
-AT_CHECK([ovn-nbctl --add-duplicate ls-add], [1], [],
-  [ovn-nbctl: --add-duplicate requires specifying a name
-])
-AT_CHECK([ovn-nbctl --may-exist ls-add], [1], [],
-  [ovn-nbctl: --may-exist requires specifying a name
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_basic_lsp], [basic logical switch port commands], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0], [1], [],
-  [ovn-nbctl: lp0: a port with this name already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp0])
-AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl
-<0> (lp0)
-])
-
-AT_CHECK([ovn-nbctl lsp-add ls0 lp1])
-AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl
-<0> (lp0)
-<1> (lp1)
-])
-
-AT_CHECK([ovn-nbctl ls-add ls1])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp1], [1], [],
-  [ovn-nbctl: lp1: a port with this name already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls1 lp1], [1], [],
-  [ovn-nbctl: lp1: port already exists but in switch ls0
-])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp1 lp0 5], [1], [],
-  [ovn-nbctl: lp1: port already exists but has no parent
-])
-
-AT_CHECK([ovn-nbctl lsp-del lp1])
-AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl
-<0> (lp0)
-])
-
-AT_CHECK([ovn-nbctl lsp-add ls0 lp2 lp3 5])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp4 5], [1], [],
-  [ovn-nbctl: lp2: port already exists with different parent lp3
-])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp3 10], [1], [],
-  [ovn-nbctl: lp2: port already exists with different tag_request 5
-])
-AT_CHECK([ovn-nbctl clear Logical_Switch_Port lp2 tag_request])
-AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp3 5], [1], [],
-  [ovn-nbctl: lp2: port already exists but has no tag_request
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lsp_get_ls], [lsp get ls], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-
-AT_CHECK([ovn-nbctl lsp-get-ls lp0 | uuidfilt], [0], [dnl
-<0> (ls0)
-])
-
-AT_CHECK([ovn-nbctl lsp-get-ls lp1], [1], [],
-  [ovn-nbctl: lp1: port name not found
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lport_addresses], [lport addresses], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl
-])
-
-AT_CHECK([ovn-nbctl lsp-set-addresses lp0 00:11:22:33:44:55 unknown])
-AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl
-00:11:22:33:44:55
-unknown
-])
-
-AT_CHECK([ovn-nbctl lsp-set-addresses lp0])
-AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_port_security], [port security], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl
-])
-
-AT_CHECK([ovn-nbctl lsp-set-port-security lp0 aa:bb:cc:dd:ee:ff 00:11:22:33:44:55])
-AT_CHECK([ovn-nbctl lsp-get-port-security lp0], [0], [dnl
-00:11:22:33:44:55
-aa:bb:cc:dd:ee:ff
-])
-
-AT_CHECK([ovn-nbctl lsp-set-port-security lp0])
-AT_CHECK([ovn-nbctl lsp-get-port-security lp0], [0], [dnl
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_acls], [ACLs], [
-ovn_nbctl_test_acl() {
-   AT_CHECK([ovn-nbctl $2 --log acl-add $1 from-lport 600 udp drop])
-   AT_CHECK([ovn-nbctl $2 --log --name=test --severity=info acl-add $1 to-lport 500 udp drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 400 tcp drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 300 tcp drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 200 ip drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 100 ip drop])
-   dnl Add duplicated ACL
-   AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 100 ip drop], [1], [], [stderr])
-   AT_CHECK([grep 'already existed' stderr], [0], [ignore])
-   AT_CHECK([ovn-nbctl $2 --may-exist acl-add $1 to-lport 100 ip drop])
-
-   AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl
-from-lport   600 (udp) drop log()
-from-lport   400 (tcp) drop
-from-lport   200 (ip) drop
-  to-lport   500 (udp) drop log(name=test,severity=info)
-  to-lport   300 (tcp) drop
-  to-lport   100 (ip) drop
-])
-
-   dnl Delete in one direction.
-   AT_CHECK([ovn-nbctl $2 acl-del $1 to-lport])
-   AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl
-from-lport   600 (udp) drop log()
-from-lport   400 (tcp) drop
-from-lport   200 (ip) drop
-])
-
-   dnl Delete all ACLs.
-   AT_CHECK([ovn-nbctl $2 acl-del $1])
-   AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl
-])
-
-   AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 600 udp drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 400 tcp drop])
-   AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 200 ip drop])
-
-   dnl Delete a single flow.
-   AT_CHECK([ovn-nbctl $2 acl-del $1 from-lport 400 tcp])
-   AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl
-from-lport   600 (udp) drop
-from-lport   200 (ip) drop
-])
-}
-
-AT_CHECK([ovn-nbctl ls-add ls0])
-ovn_nbctl_test_acl ls0
-AT_CHECK([ovn-nbctl ls-add ls1])
-ovn_nbctl_test_acl ls1 --type=switch
-AT_CHECK([ovn-nbctl create port_group name=pg0], [0], [ignore])
-ovn_nbctl_test_acl pg0 --type=port-group
-
-dnl Test when port group doesn't exist
-AT_CHECK([ovn-nbctl --type=port-group acl-add pg1 to-lport 100 ip drop], [1], [], [dnl
-ovn-nbctl: pg1: port group name not found
-])
-
-dnl Test when same name exists in logical switches and portgroups
-AT_CHECK([ovn-nbctl create port_group name=ls0], [0], [ignore])
-AT_CHECK([ovn-nbctl acl-add ls0 to-lport 100 ip drop], [1], [], [stderr])
-AT_CHECK([grep 'exists in both' stderr], [0], [ignore])
-AT_CHECK([ovn-nbctl --type=port-group acl-add ls0 to-lport 100 ip drop], [0], [ignore])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_qos], [QoS], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 tcp dscp=63])
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 500 udp rate=100 burst=1000])
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=0 rate=300 burst=3000])
-AT_CHECK([ovn-nbctl qos-add ls0 to-lport 300 tcp dscp=48])
-AT_CHECK([ovn-nbctl qos-add ls0 to-lport 200 ip rate=101])
-AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=13 rate=301 burst=30000])
-
-dnl Add duplicated qos
-AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002], [1], [], [stderr])
-AT_CHECK([grep 'already existed' stderr], [0], [ignore])
-AT_CHECK([ovn-nbctl --may-exist qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002])
-
-AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
-from-lport   600 (tcp) dscp=63
-from-lport   500 (udp) rate=100 burst=1000
-from-lport   400 (tcp) rate=300 burst=3000 dscp=0
-  to-lport   300 (tcp) dscp=48
-  to-lport   200 (ip) rate=101
-  to-lport   100 (ip4) rate=301 burst=30000 dscp=13
-])
-
-dnl Delete in one direction.
-AT_CHECK([ovn-nbctl qos-del ls0 to-lport])
-AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
-from-lport   600 (tcp) dscp=63
-from-lport   500 (udp) rate=100 burst=1000
-from-lport   400 (tcp) rate=300 burst=3000 dscp=0
-])
-
-dnl Delete all qos_rules.
-AT_CHECK([ovn-nbctl qos-del ls0])
-AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=1000101])
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=44])
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 200 ip burst=1000102 rate=301 dscp=19])
-
-dnl Delete a single flow.
-AT_CHECK([ovn-nbctl qos-del ls0 from-lport 400 tcp])
-AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl
-from-lport   600 (ip) rate=1000101
-from-lport   200 (ip) rate=301 burst=1000102 dscp=19
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=100010111111], [1], [],
-[ovn-nbctl: 100010111111: rate must be in the range 1...4294967295
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=100010111112 rate=100010], [1], [],
-[ovn-nbctl: 100010111112: burst must be in the range 1...4294967295
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscp=-1], [1], [],
-[ovn-nbctl: -1: dscp must be in the range 0...63
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscpa=-1], [1], [],
-[ovn-nbctl: dscpa=-1: supported arguments are "dscp=", "rate=", and "burst="
-])
-
-AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=123], [1], [],
-[ovn-nbctl: Either "rate" and/or "dscp" must be specified
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_meters], [meters], [
-AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps])
-AT_CHECK([ovn-nbctl meter-add meter2 drop 3 kbps 2])
-AT_CHECK([ovn-nbctl meter-add meter3 drop 100 kbps 200])
-AT_CHECK([ovn-nbctl meter-add meter4 drop 10 pktps 30])
-
-dnl Add duplicate meter name
-AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps], [1], [], [stderr])
-AT_CHECK([grep 'already exists' stderr], [0], [ignore])
-
-dnl Add reserved meter name
-AT_CHECK([ovn-nbctl meter-add __meter1 drop 10 kbps], [1], [], [stderr])
-AT_CHECK([grep 'reserved' stderr], [0], [ignore])
-
-dnl Add meter with invalid rates
-AT_CHECK([ovn-nbctl meter-add meter5 drop 100010111111 kbps], [1], [],
-[ovn-nbctl: rate must be in the range 1...4294967295
-])
-
-dnl Add meter with invalid rates
-AT_CHECK([ovn-nbctl meter-add meter5 drop 100010111111 foo], [1], [],
-[ovn-nbctl: rate must be in the range 1...4294967295
-])
-
-AT_CHECK([ovn-nbctl meter-add meter5 drop 0 kbps], [1], [],
-[ovn-nbctl: rate must be in the range 1...4294967295
-])
-
-dnl Add meter with invalid burst
-AT_CHECK([ovn-nbctl meter-add meter5 drop 10 100010111111 kbps], [1], [],
-[ovn-nbctl: unit must be "kbps" or "pktps"
-])
-
-AT_CHECK([ovn-nbctl meter-list], [0], [dnl
-meter1: bands:
-  drop: 10 kbps
-meter2: bands:
-  drop: 3 kbps, 2 kb burst
-meter3: bands:
-  drop: 100 kbps, 200 kb burst
-meter4: bands:
-  drop: 10 pktps, 30 packet burst
-])
-
-dnl Delete a single meter.
-AT_CHECK([ovn-nbctl meter-del meter2])
-AT_CHECK([ovn-nbctl meter-list], [0], [dnl
-meter1: bands:
-  drop: 10 kbps
-meter3: bands:
-  drop: 100 kbps, 200 kb burst
-meter4: bands:
-  drop: 10 pktps, 30 packet burst
-])
-
-dnl Delete all meters.
-AT_CHECK([ovn-nbctl meter-del])
-AT_CHECK([ovn-nbctl meter-list], [0], [dnl
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
-[ovn-nbctl: snatt: type must be one of "dnat", "snat" and "dnat_and_snat".
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2a 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.2a: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2/24 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.2/24: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2:80 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.2:80: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2a], [1], [],
-[ovn-nbctl: 192.168.1.2a: should be an IPv4 address or network.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1], [1], [],
-[ovn-nbctl: 192.168.1: should be an IPv4 address or network.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2:80], [1], [],
-[ovn-nbctl: 192.168.1.2:80: should be an IPv4 address or network.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2/a], [1], [],
-[ovn-nbctl: 192.168.1.2/a: should be an IPv4 address or network.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2a], [1], [],
-[ovn-nbctl: 192.168.1.2a: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1], [1], [],
-[ovn-nbctl: 192.168.1: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2:80], [1], [],
-[ovn-nbctl: 192.168.1.2:80: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2/24], [1], [],
-[ovn-nbctl: 192.168.1.2/24: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2/24], [1], [],
-[ovn-nbctl: 192.168.1.2/24: should be an IPv4 address.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0], [1], [],
-[ovn-nbctl: lr-nat-add with logical_port must also specify external_mac.
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [],
-[ovn-nbctl: logical_port and external_mac are only valid when type is "dnat_and_snat".
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [],
-[ovn-nbctl: logical_port and external_mac are only valid when type is "dnat_and_snat".
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [],
-[ovn-nbctl: lp0: port name not found
-])
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02], [1], [],
-[ovn-nbctl: invalid mac address 00:00:00:01:02.
-])
-
-dnl Add snat and dnat
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:01:02:03])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.2           192.168.1.3           00:00:00:01:02:03    lp0
-snat             30.0.0.1           192.168.1.0/24
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.10/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.0/24], [1], [],
-[ovn-nbctl: a NAT with this type (snat) and logical_ip (192.168.1.0/24) already exists
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.3], [1], [],
-[ovn-nbctl: a NAT with this type (dnat) and external_ip (30.0.0.1) already exists
-])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.3], [1], [],
-[ovn-nbctl: a NAT with this type (dnat_and_snat) and external_ip (30.0.0.1) already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:04:05:06])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.2           192.168.1.3           00:00:00:04:05:06    lp0
-snat             30.0.0.1           192.168.1.0/24
-])
-AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.2           192.168.1.3
-snat             30.0.0.1           192.168.1.0/24
-])
-
-dnl Deletes the NATs
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat_and_snat) and external_ip (30.0.0.3)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.2], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat) and external_ip (30.0.0.2)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 192.168.10.0/24], [1], [],
-[ovn-nbctl: no matching NAT with the type (snat) and logical_ip (192.168.10.0/24)
-])
-AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 192.168.10.0/24])
-
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.1])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat             30.0.0.1           192.168.1.2
-dnat_and_snat    30.0.0.2           192.168.1.3
-snat             30.0.0.1           192.168.1.0/24
-])
-
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE             EXTERNAL_IP        LOGICAL_IP            EXTERNAL_MAC         LOGICAL_PORT
-dnat_and_snat    30.0.0.2           192.168.1.3
-snat             30.0.0.1           192.168.1.0/24
-])
-
-AT_CHECK([ovn-nbctl lr-nat-del lr0])
-AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [])
-AT_CHECK([ovn-nbctl lr-nat-del lr0])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lbs], [LBs], [
-dnl Add two LBs.
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80a 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
-[ovn-nbctl: 30.0.0.10:80a: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:a80 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
-[ovn-nbctl: 30.0.0.10:a80: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20 tcp], [1], [],
-[ovn-nbctl: 192.168.10.20: should be an IP address and a port number with : as a separator.
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.1a 192.168.10.10:80,192.168.10.20:80], [1], [],
-[ovn-nbctl: 30.0.0.1a: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0 192.168.10.10:80,192.168.10.20:80], [1], [],
-[ovn-nbctl: 30.0.0: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10,192.168.10.20:80], [1], [],
-[ovn-nbctl: 192.168.10.20:80: should be an IP address.
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:a80], [1], [],
-[ovn-nbctl: 192.168.10.10:a80: should be an IP address.
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:], [1], [],
-[ovn-nbctl: 192.168.10.10:: should be an IP address.
-])
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.1a], [1], [],
-[ovn-nbctl: 192.168.10.1a: should be an IP address.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10: 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:900 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-dnl Add ips to lb
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 ,,,192.168.10.10:80,,,,,])
-AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 ,,,192.168.10.10:80,,,,192.168.10.20:80,,,,])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80
-<1>    lb1                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-])
-AT_CHECK([ovn-nbctl lb-del lb0])
-AT_CHECK([ovn-nbctl lb-del lb1])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80])
-AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 tcp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-])
-
-dnl Update the VIP of the lb1.
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:8080
-])
-
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 udp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:8080
-])
-
-dnl Config lb1 with another VIP.
-AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.20:80 192.168.10.10:80 udp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:8080
-                                                            udp        30.0.0.20:80    192.168.10.10:80
-])
-
-AT_CHECK([ovn-nbctl lb-del lb1 30.0.0.20:80])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:8080
-])
-
-dnl Add LBs whose vip is just an IP address.
-AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.30 192.168.10.10])
-AT_CHECK([ovn-nbctl lb-add lb3 30.0.0.30 192.168.10.10])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:8080
-<2>    lb2                 tcp/udp    30.0.0.30       192.168.10.10
-<3>    lb3                 tcp/udp    30.0.0.30       192.168.10.10
-])
-AT_CHECK([ovn-nbctl lb-del lb2 30.0.0.30])
-AT_CHECK([ovn-nbctl lb-del lb3 30.0.0.30])
-
-AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp])
-AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP               IPs
-<0>    lb0                 tcp        30.0.0.10:80      192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80      192.168.10.10:80,192.168.10.20:8080
-<2>    lb2                 tcp        30.0.0.10:8080    192.168.10.10:80,192.168.10.20:80
-<3>    lb2                 tcp        30.0.0.10:8080    192.168.10.10:80,192.168.10.20:80
-])
-
-dnl If there are multiple load balancers with the same name, use a UUID to update/delete.
-AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl lb-del lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:8080,192.168.10.20:8080 udp])
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:8080 192.168.10.10:8080,192.168.10.20:8080 udp])
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:9090 192.168.10.10:8080,192.168.10.20:8080 udp])
-AT_CHECK([ovn-nbctl lb-del lb0 30.0.0.10:80])
-AT_CHECK([ovn-nbctl lb-del lb1])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP               IPs
-<0>    lb2                 tcp        30.0.0.10:8080    192.168.10.10:80,192.168.10.20:80
-<1>    lb2                 tcp        30.0.0.10:8080    192.168.10.10:80,192.168.10.20:80
-])
-
-dnl Add load balancer to logical switch.
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80])
-AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 udp])
-AT_CHECK([ovn-nbctl lb-add lb3 30.0.0.10 192.168.10.10,192.168.10.20])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl --may-exist ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3])
-
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<2>    lb3                 tcp/udp    30.0.0.10       192.168.10.10,192.168.10.20
-])
-
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb3                 tcp/udp    30.0.0.10       192.168.10.10,192.168.10.20
-])
-
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb3])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])
-AT_CHECK([ovn-nbctl --if-exists ls-lb-del ls0 lb1])
-
-dnl Remove all load balancers from logical switch.
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3])
-AT_CHECK([ovn-nbctl ls-lb-del ls0])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])
-
-dnl Add load balancer to logical router.
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3])
-
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb0                 tcp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<2>    lb3                 tcp/udp    30.0.0.10       192.168.10.10,192.168.10.20
-])
-
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP             IPs
-<0>    lb1                 udp        30.0.0.10:80    192.168.10.10:80,192.168.10.20:80
-<1>    lb3                 tcp/udp    30.0.0.10       192.168.10.10,192.168.10.20
-])
-
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb3])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])
-AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb1])
-
-dnl Remove all load balancers from logical router.
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3])
-AT_CHECK([ovn-nbctl lr-lb-del lr0])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])
-
-dnl Remove load balancers after adding them to a logical router/switch.
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl lb-del lb0])
-AT_CHECK([ovn-nbctl lb-del lb1])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lbs_ipv6], [LBs IPv6], [
-dnl A bunch of commands that should fail
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80a [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
-[ovn-nbctl: [[ae0f::10]]:80a: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:a80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
-[ovn-nbctl: [[ae0f::10]]:a80: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
-[ovn-nbctl: fd0f::20: should be an IP address and a port number with : as a separator.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10fff [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
-[ovn-nbctl: ae0f::10fff: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:80,[[fd0f::20]]:80], [1], [],
-[ovn-nbctl: [[fd0f::10]]:80: should be an IP address.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::10,[[fd0f::20]]:80], [1], [],
-[ovn-nbctl: [[fd0f::20]]:80: should be an IP address.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:a80], [1], [],
-[ovn-nbctl: [[fd0f::10]]:a80: should be an IP address.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:], [1], [],
-[ovn-nbctl: [[fd0f::10]]:: should be an IP address.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::1001a], [1], [],
-[ovn-nbctl: fd0f::1001a: should be an IP address.
-])
-
-
-AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]: [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:900 tcp], [1], [],
-[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [],
-[ovn-nbctl: 192.168.10.10: IP address family is different from VIP ae0f::10.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [],
-[ovn-nbctl: 192.168.10.10: IP address family is different from VIP ae0f::10.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 192.168.10.10:80], [1], [],
-[ovn-nbctl: 192.168.10.10:80: IP address family is different from VIP [[ae0f::10]]:80.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 ae0f::10], [1], [],
-[ovn-nbctl: ae0f::10: IP address family is different from VIP 30.0.0.10.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 [[ae0f::10]]:80], [1], [],
-[ovn-nbctl: [[ae0f::10]]:80: IP address family is different from VIP 30.0.0.10:80.
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10])
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f:0000:0000:0000:0000:0000:0000:0010 fd0f::20],
-[1], [], [ovn-nbctl: lb0: a load balancer with this vip (ae0f::10) already exists
-])
-
-AT_CHECK([ovn-nbctl lb-del lb0])
-
-dnl Add ips to lb
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 ,,,[[fd0f::10]]:80,,,,,])
-AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 ,,,[[fd0f::10]]:80,,,,[[fd0f::20]]:80,,,,])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80
-<1>    lb1                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-])
-AT_CHECK([ovn-nbctl lb-del lb0])
-AT_CHECK([ovn-nbctl lb-del lb1])
-
-
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80])
-AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-])
-
-dnl Update the VIP of the lb1.
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:8080
-])
-
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 udp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:8080
-])
-
-dnl Config lb1 with another VIP.
-AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::20]]:80 [[fd0f::10]]:80 udp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:8080
-                                                            udp        [[ae0f::20]]:80    [[fd0f::10]]:80
-])
-
-AT_CHECK([ovn-nbctl lb-del lb1 [[ae0f::20]]:80])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:8080
-])
-
-dnl Add LBs whose vip is just an IP address.
-AT_CHECK([ovn-nbctl lb-add lb2 ae0f::30 fd0f::10])
-AT_CHECK([ovn-nbctl lb-add lb3 ae0f::30 fd0f::10])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:8080
-<2>    lb2                 tcp/udp    ae0f::30         fd0f::10
-<3>    lb3                 tcp/udp    ae0f::30         fd0f::10
-])
-AT_CHECK([ovn-nbctl lb-del lb2 ae0f::30])
-AT_CHECK([ovn-nbctl lb-del lb3 ae0f::30])
-
-AT_CHECK([ovn-nbctl lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp])
-AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP                IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80      [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80      [[fd0f::10]]:80,[[fd0f::20]]:8080
-<2>    lb2                 tcp        [[ae0f::10]]:8080    [[fd0f::10]]:80,[[fd0f::20]]:80
-<3>    lb2                 tcp        [[ae0f::10]]:8080    [[fd0f::10]]:80,[[fd0f::20]]:80
-])
-
-dnl If there are multiple load balancers with the same name, use a UUID to update/delete.
-AT_CHECK([ovn-nbctl lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl lb-del lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp])
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:8080 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp])
-AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:9090 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp])
-AT_CHECK([ovn-nbctl lb-del lb0 [[ae0f::10]]:80])
-AT_CHECK([ovn-nbctl lb-del lb1])
-AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP                IPs
-<0>    lb2                 tcp        [[ae0f::10]]:8080    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb2                 tcp        [[ae0f::10]]:8080    [[fd0f::10]]:80,[[fd0f::20]]:80
-])
-
-dnl Add load balancer to logical switch.
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80])
-AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 udp])
-AT_CHECK([ovn-nbctl lb-add lb3 ae0f::10 fd0f::10,fd0f::20])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl --may-exist ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3])
-
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<2>    lb3                 tcp/udp    ae0f::10         fd0f::10,fd0f::20
-])
-
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb3                 tcp/udp    ae0f::10         fd0f::10,fd0f::20
-])
-
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-del ls0 lb3])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])
-AT_CHECK([ovn-nbctl --if-exists ls-lb-del ls0 lb1])
-
-dnl Remove all load balancers from logical switch.
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
-AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3])
-AT_CHECK([ovn-nbctl ls-lb-del ls0])
-AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])
-
-dnl Add load balancer to logical router.
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb2], [1], [],
-[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
-])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3])
-
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb0                 tcp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<2>    lb3                 tcp/udp    ae0f::10         fd0f::10,fd0f::20
-])
-
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl
-UUID                                    LB                  PROTO      VIP              IPs
-<0>    lb1                 udp        [[ae0f::10]]:80    [[fd0f::10]]:80,[[fd0f::20]]:80
-<1>    lb3                 tcp/udp    ae0f::10         fd0f::10,fd0f::20
-])
-
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-del lr0 lb3])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])
-AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb1])
-
-dnl Remove all load balancers from logical router.
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3])
-AT_CHECK([ovn-nbctl lr-lb-del lr0])
-AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_basic_lr], [basic logical router commands], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl
-<0> (lr0)
-])
-
-AT_CHECK([ovn-nbctl lr-add lr1])
-AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl
-<0> (lr0)
-<1> (lr1)
-])
-
-AT_CHECK([ovn-nbctl lr-del lr0])
-AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl
-<0> (lr1)
-])
-
-AT_CHECK([ovn-nbctl show lr0])
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0],
-  [router <0> (lr0)
-])
-AT_CHECK([ovn-nbctl lr-add lr0], [1], [],
-  [ovn-nbctl: lr0: a router with this name already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lr-add lr0])
-AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0],
-  [router <0> (lr0)
-])
-AT_CHECK([ovn-nbctl --add-duplicate lr-add lr0])
-AT_CHECK([ovn-nbctl --may-exist --add-duplicate lr-add lr0], [1], [],
-  [ovn-nbctl: --may-exist and --add-duplicate may not be used together
-])
-AT_CHECK([ovn-nbctl lr-del lr0], [1], [],
-  [ovn-nbctl: Multiple logical routers named 'lr0'.  Use a UUID.
-])
-
-AT_CHECK([ovn-nbctl lr-del lr2], [1], [],
-  [ovn-nbctl: lr2: router name not found
-])
-AT_CHECK([ovn-nbctl --if-exists lr-del lr2])
-
-AT_CHECK([ovn-nbctl lr-add])
-AT_CHECK([ovn-nbctl lr-add])
-AT_CHECK([ovn-nbctl --add-duplicate lr-add], [1], [],
-  [ovn-nbctl: --add-duplicate requires specifying a name
-])
-AT_CHECK([ovn-nbctl --may-exist lr-add], [1], [],
-  [ovn-nbctl: --may-exist requires specifying a name
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_basic_lrp], [basic logical router port commands], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp0: invalid mac address 00:00:00:01:02
-])
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03:04 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp0: invalid mac address 00:00:00:01:02:03:04
-])
-
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24])
-
-AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0], [dnl
-router <0> (lr0)
-    port lrp0
-        mac: "00:00:00:01:02:03"
-        networks: [["192.168.1.1/24"]]
-])
-
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp0: a port with this name already exists
-])
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24])
-AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl
-<0> (lrp0)
-])
-
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer])
-AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl
-<0> (lrp0)
-<1> (lrp1)
-])
-
-AT_CHECK([ovn-nbctl lr-add lr1])
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp1: a port with this name already exists
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr1 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp1: port already exists but in router lr0
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:04:05:06 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp1: port already exists with mac 00:00:00:01:02:03
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [],
-  [ovn-nbctl: lrp1: port already exists with mismatching peer
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 peer=lrp1-peer], [1], [],
-  [ovn-nbctl: lrp1: port already exists with different network
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer])
-
-AT_CHECK([ovn-nbctl lrp-del lrp1])
-AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl
-<0> (lrp0)
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 10.0.0.1/24 peer=lrp1-peer])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 172.16.0.1/24 peer=lrp1-peer], [1], [],
-  [ovn-nbctl: lrp1: port already exists with different network
-])
-
-AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 192.168.1.1/24 peer=lrp1-peer])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lrp_gw_chassi], [logical router port gateway chassis], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24])
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [])
-
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lp0 chassis1], [1], [],
-[ovn-nbctl: lp0: port name not found
-])
-
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lp0], [1], [],
-[ovn-nbctl: lp0: port name not found
-])
-
-AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lp0 chassis1], [1], [],
-[ovn-nbctl: lp0: port name not found
-])
-
-AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis1], [1], [],
-[ovn-nbctl: chassis chassis1 is not added to logical port lrp0
-])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1])
-
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis1     0
-])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 10])
-
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis1    10
-])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 20])
-
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis1    20
-])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis2 5])
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis1    20
-lrp0-chassis2     5
-])
-
-AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis1])
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis2     5
-])
-
-AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis2])
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0])
-
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 1])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis2 10])
-AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis3 5])
-AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl
-lrp0-chassis2    10
-lrp0-chassis3     5
-lrp0-chassis1     1
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lrp_enable], [logical router port enable and disable], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24])
-AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [enabled
-])
-
-AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 disabled])
-AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [disabled
-])
-
-AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 enabled])
-AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [enabled
-])
-
-AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 xyzzy], [1], [],
-  [ovn-nbctl: xyzzy: state must be "enabled" or "disabled"
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_routes], [routes], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-
-dnl Check IPv4 routes
-AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.0/24 11.0.1.1 lp0])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.2])
-
-dnl Add overlapping route with 10.0.0.1/24
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1], [1], [],
-  [ovn-nbctl: duplicate prefix: 10.0.0.0/24
-])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111a/24 11.0.0.1], [1], [],
-  [ovn-nbctl: bad prefix argument: 10.0.0.111a/24
-])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24a 11.0.0.1], [1], [],
-  [ovn-nbctl: bad prefix argument: 10.0.0.111/24a
-])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1a], [1], [],
-  [ovn-nbctl: bad next hop argument: 11.0.0.1a
-])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1/24], [1], [],
-  [ovn-nbctl: bad IPv4 nexthop argument: 11.0.0.1/24
-])
-AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1/64], [1], [],
-  [ovn-nbctl: bad IPv6 nexthop argument: 2001:0db8:0:f103::1/64
-])
-
-AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1])
-AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 9.16.1.0/24 11.0.0.1])
-
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv4 Routes
-              10.0.0.0/24                  11.0.0.1 dst-ip
-              10.0.1.0/24                  11.0.1.1 dst-ip lp0
-              9.16.1.0/24                  11.0.0.1 src-ip
-                0.0.0.0/0               192.168.0.1 dst-ip
-])
-
-AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1])
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv4 Routes
-              10.0.0.0/24                  11.0.0.1 dst-ip lp1
-              10.0.1.0/24                  11.0.1.1 dst-ip lp0
-              9.16.1.0/24                  11.0.0.1 src-ip
-                0.0.0.0/0               192.168.0.1 dst-ip
-])
-
-dnl Delete non-existent prefix
-AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.2.1/24], [1], [],
-  [ovn-nbctl: no matching prefix: 10.0.2.0/24
-])
-AT_CHECK([ovn-nbctl --if-exists lr-route-del lr0 10.0.2.1/24])
-
-AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.1.1/24])
-AT_CHECK([ovn-nbctl lr-route-del lr0 9.16.1.0/24])
-
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv4 Routes
-              10.0.0.0/24                  11.0.0.1 dst-ip lp1
-                0.0.0.0/0               192.168.0.1 dst-ip
-])
-
-AT_CHECK([ovn-nbctl lr-route-del lr0])
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-])
-
-dnl Check IPv6 routes
-AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1])
-AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0])
-AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
-
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv6 Routes
-            2001:db8::/64        2001:db8:0:f102::1 dst-ip lp0
-          2001:db8:1::/64        2001:db8:0:f103::1 dst-ip
-                     ::/0        2001:db8:0:f101::1 dst-ip
-])
-
-AT_CHECK([ovn-nbctl lr-route-del lr0 2001:0db8:0::/64])
-
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv6 Routes
-          2001:db8:1::/64        2001:db8:0:f103::1 dst-ip
-                     ::/0        2001:db8:0:f101::1 dst-ip
-])
-
-AT_CHECK([ovn-nbctl lr-route-del lr0])
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-])
-
-dnl Check IPv4 and IPv6 routes
-AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0])
-AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1])
-AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1])
-AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0])
-AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
-
-AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
-IPv4 Routes
-              10.0.0.0/24                  11.0.0.1 dst-ip
-              10.0.1.0/24                  11.0.1.1 dst-ip lp0
-                0.0.0.0/0               192.168.0.1 dst-ip
-
-IPv6 Routes
-            2001:db8::/64        2001:db8:0:f102::1 dst-ip lp0
-          2001:db8:1::/64        2001:db8:0:f103::1 dst-ip
-                     ::/0        2001:db8:0:f101::1 dst-ip
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_policies], [policies], [
-AT_CHECK([ovn-nbctl lr-add lr0])
-
-dnl Add policies with allow and drop actions
-AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop])
-AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.2.0/24" allow])
-AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip4.src == 2.1.1.0/24" allow])
-AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip4.src == 2.1.2.0/24" drop])
-AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip6.src == 2002::/64" drop])
-
-dnl Add duplicated policy
-AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop], [1], [],
-  [ovn-nbctl: Same routing policy already existed on the logical router lr0.
-])
-
-dnl Add duplicated policy
-AT_CHECK([ovn-nbctl lr-policy-add lr0 103 "ip4.src == 1.1.1.0/24" deny], [1], [],
-  [ovn-nbctl: deny: action must be one of "allow", "drop", and "reroute"
-])
-
-dnl Delete by priority and match string
-AT_CHECK([ovn-nbctl lr-policy-del lr0 100 "ip4.src == 1.1.1.0/24"])
-AT_CHECK([ovn-nbctl lr-policy-list lr0], [0], [dnl
-Routing Policies
-       101                              ip4.src == 2.1.1.0/24           allow
-       101                              ip4.src == 2.1.2.0/24            drop
-       101                               ip6.src == 2002::/64            drop
-       100                              ip4.src == 1.1.2.0/24           allow
-])
-
-dnl Delete all policies for given priority
-AT_CHECK([ovn-nbctl lr-policy-del lr0 101])
-AT_CHECK([ovn-nbctl lr-policy-list lr0], [0], [dnl
-Routing Policies
-       100                              ip4.src == 1.1.2.0/24           allow
-])
-
-dnl Add policy with reroute action
-AT_CHECK([ovn-nbctl lr-policy-add lr0 102 "ip4.src == 3.1.2.0/24" reroute 3.3.3.3])
-
-dnl Add policy with invalid reroute ip
-AT_CHECK([ovn-nbctl lr-policy-add lr0 103 "ip4.src == 3.1.2.0/24" reroute 3.3.3.x], [1], [],
-  [ovn-nbctl: bad next hop argument: 3.3.3.x
-])
-
-dnl Add policy with reroute action
-AT_CHECK([ovn-nbctl lr-policy-add lr0 104 "ip6.src == 2001::/64" reroute 2002::5])
-
-dnl Add policy with invalid reroute ip
-AT_CHECK([ovn-nbctl lr-policy-add lr0 105 "ip6.src == 2001::/64" reroute 2002::x], [1], [],
-  [ovn-nbctl: bad next hop argument: 2002::x
-])
-
-])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_lsp_types], [lsp types], [
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 lp0])
-
-dnl switchport type defaults to empty
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-
-])
-
-dnl The following are the valid entries for
-dnl switchport type
-AT_CHECK([ovn-nbctl lsp-set-type lp0 l2gateway])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-l2gateway
-])
-
-AT_CHECK([ovn-nbctl lsp-set-type lp0 router])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-router
-])
-
-AT_CHECK([ovn-nbctl lsp-set-type lp0 localnet])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-localnet
-])
-
-AT_CHECK([ovn-nbctl lsp-set-type lp0 localport])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-localport
-])
-
-AT_CHECK([ovn-nbctl lsp-set-type lp0 vtep])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-vtep
-])
-
-dnl All of these are valid southbound port types but
-dnl should be rejected for northbound logical switch
-dnl ports.
-AT_CHECK([ovn-nbctl lsp-set-type lp0 l3gateway], [1], [], [dnl
-ovn-nbctl: Logical switch port type 'l3gateway' is unrecognized. Not setting type.
-])
-AT_CHECK([ovn-nbctl lsp-set-type lp0 patch], [1], [], [dnl
-ovn-nbctl: Logical switch port type 'patch' is unrecognized. Not setting type.
-])
-AT_CHECK([ovn-nbctl lsp-set-type lp0 chassisredirect], [1], [], [dnl
-ovn-nbctl: Logical switch port type 'chassisredirect' is unrecognized. Not setting type.
-])
-
-dnl switch port type should still be "vtep" since previous
-dnl commands failed.
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-vtep
-])
-
-dnl Attempt a nonsense type
-AT_CHECK([ovn-nbctl lsp-set-type lp0 eggs], [1], [], [dnl
-ovn-nbctl: Logical switch port type 'eggs' is unrecognized. Not setting type.
-])
-
-dnl Empty string should work too
-AT_CHECK([ovn-nbctl lsp-set-type lp0 ""])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
-
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_connection], [connection], [
-AT_CHECK([ovn-nbctl --inactivity-probe=30000 set-connection ptcp:6641:127.0.0.1 punix:$OVS_RUNDIR/ovnnb_db.sock])
-AT_CHECK([ovn-nbctl list connection | grep inactivity_probe], [0], [dnl
-inactivity_probe    : 30000
-inactivity_probe    : 30000
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_dry_run_mode], [dry run mode], [
-dnl Check that dry run has no permanent effect.
-AT_CHECK([ovn-nbctl --dry-run ls-add ls0 -- ls-list | uuidfilt], [0], [dnl
-<0> (ls0)
-])
-AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl
-])
-
-dnl Check that dry-run mode is not sticky.
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl
-<0> (ls0)
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_oneline_output], [oneline output], [
-AT_CHECK([ovn-nbctl ls-add ls0 -- ls-add ls1])
-
-dnl Expect one line for one command.
-AT_CHECK([ovn-nbctl --oneline ls-list | uuidfilt], [0], [dnl
-<0> (ls0)\n<1> (ls1)
-])
-
-dnl Expect lines for two commands.
-AT_CHECK([ovn-nbctl --oneline ls-list -- ls-list | uuidfilt], [0], [dnl
-<0> (ls0)\n<1> (ls1)
-<0> (ls0)\n<1> (ls1)
-])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_error_paths], [commands parser error paths], [
-dnl FIXME: Duplicate options are allowed when passed with global options.
-dnl        For example: ovn-nbctl --if-exists --if-exists list Logical_Switch
-
-dnl Duplicate option
-AT_CHECK([ovn-nbctl -- --if-exists --if-exists list Logical_Switch], [1], [], [stderr])
-AT_CHECK([grep 'option specified multiple times' stderr], [0], [ignore])
-
-dnl Missing command
-AT_CHECK([ovn-nbctl], [1], [], [stderr])
-AT_CHECK([grep 'missing command name' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl --if-exists], [1], [], [stderr])
-AT_CHECK([grep 'missing command name' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl --], [1], [], [stderr])
-AT_CHECK([grep 'missing command name' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- --if-exists], [1], [], [stderr])
-AT_CHECK([grep 'missing command name' stderr], [0], [ignore])
-
-dnl Unknown command
-AT_CHECK([ovn-nbctl foo], [1], [], [stderr])
-AT_CHECK([grep 'unknown command' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- foo], [1], [], [stderr])
-AT_CHECK([grep 'unknown command' stderr], [0], [ignore])
-
-dnl Unknown option
-AT_CHECK([ovn-nbctl --foo list Logical_Switch], [1], [], [stderr])
-AT_CHECK([grep 'unrecognized option' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- --foo list Logical_Switch], [1], [], [stderr])
-AT_CHECK([grep 'command has no .* option' stderr], [0], [ignore])
-
-dnl Missing option argument
-AT_CHECK([ovn-nbctl --columns], [1], [], [stderr])
-AT_CHECK([grep 'option .* requires an argument' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- --columns list Logical_Switch], [1], [], [stderr])
-AT_CHECK([grep 'missing argument to .* option' stderr], [0], [ignore])
-
-dnl Unexpected option argument
-AT_CHECK([ovn-nbctl --if-exists=foo list Logical_Switch], [1], [], [stderr])
-AT_CHECK([egrep 'option .* doesn'\''t allow an argument|option .* requires an argument' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- --if-exists=foo list Logical_Switch], [1], [], [stderr])
-AT_CHECK([grep 'option on .* does not accept an argument' stderr], [0], [ignore])
-
-dnl Not enough arguments
-AT_CHECK([ovn-nbctl list], [1], [], [stderr])
-AT_CHECK([grep 'command requires at least .* arguments' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- list], [1], [], [stderr])
-AT_CHECK([grep 'command requires at least .* arguments' stderr], [0], [ignore])
-
-dnl Too many arguments
-AT_CHECK([ovn-nbctl show foo bar], [1], [], [stderr])
-AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- show foo bar], [1], [], [stderr])
-AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl show foo --bar], [1], [], [stderr])
-AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore])
-
-AT_CHECK([ovn-nbctl -- show foo --bar], [1], [], [stderr])
-AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore])])
-
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_port_groups], [port groups], [
-dnl Check that port group can be looked up by name
-AT_CHECK([ovn-nbctl create Port_Group name=pg0], [0], [ignore])
-AT_CHECK([ovn-nbctl get Port_Group pg0 name], [0], [dnl
-pg0
-])])
-
-OVN_NBCTL_TEST([ovn_nbctl_extra_newlines], [extra newlines], [
-dnl This test addresses a specific issue seen when running ovn-nbctl in
-dnl daemon mode. All we have to do is ensure that each time we list database
-dnl information, there is not an extra newline at the beginning of the output.
-AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore])
-AT_CHECK([ovn-nbctl --columns=name list logical_switch sw1], [0], [dnl
-name                : sw1
-])
-AT_CHECK([ovn-nbctl --columns=name list logical_switch sw1], [0], [dnl
-name                : sw1
-])])
-
-OVN_NBCTL_TEST([ovn_nbctl_table_formatting], [table formatting], [
-dnl This test addresses a specific issue seen when running ovn-nbctl in
-dnl daemon mode. We need to ensure that table formatting options are honored
-dnl when listing database information.
-AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore])
-AT_CHECK([ovn-nbctl --bare --columns=name list logical_switch sw1], [0], [dnl
-sw1
-])])
-dnl ---------------------------------------------------------------------
-
-OVN_NBCTL_TEST([ovn_nbctl_port_group_commands], [port group commands], [
-AT_CHECK([ovn-nbctl pg-add pg1], [0], [ignore])
-AT_CHECK([ovn-nbctl --bare --columns=name list port_group pg1], [0],
-[pg1
-])
-
-AT_CHECK([ovn-nbctl pg-del pg1], [0], [ignore])
-AT_CHECK([ovn-nbctl list port_group], [0], [])
-
-AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore])
-AT_CHECK([ovn-nbctl lsp-add sw1 sw1-p1], [0], [ignore])
-SW1P1=$(ovn-nbctl --bare --columns=_uuid list logical_switch_port sw1-p1)
-AT_CHECK([ovn-nbctl lsp-add sw1 sw1-p2], [0], [ignore])
-SW1P2=$(ovn-nbctl --bare --columns=_uuid list logical_switch_port sw1-p2)
-
-AT_CHECK([ovn-nbctl pg-add pg1 sw1-p1], [0], [ignore])
-AT_CHECK([ovn-nbctl --bare --columns=name list port_group pg1], [0],[dnl
-pg1
-])
-AT_CHECK_UNQUOTED([ovn-nbctl --bare --columns=ports list port_group pg1], [0], [dnl
-$SW1P1
-])
-
-AT_CHECK([ovn-nbctl pg-set-ports pg1 sw1-p2], [0], [ignore])
-AT_CHECK_UNQUOTED([ovn-nbctl --bare --columns=ports list port_group pg1], [0], [dnl
-$SW1P2
-])
-
-AT_CHECK([ovn-nbctl pg-del pg1], [0], [ignore])
-AT_CHECK([ovn-nbctl list port_group], [0], [])
-])
-
-AT_SETUP([ovn-nbctl - daemon retry connection])
-OVN_NBCTL_TEST_START daemon
-AT_CHECK([kill `cat ovsdb-server.pid`])
-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
-AT_CHECK([ovn-nbctl show], [0], [ignore])
-OVN_NBCTL_TEST_STOP /Terminated/d
-AT_CLEANUP
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
deleted file mode 100644
index 62e58fd0e..000000000
--- a/tests/ovn-northd.at
+++ /dev/null
@@ -1,900 +0,0 @@
-AT_BANNER([OVN northd])
-AT_SETUP([ovn -- check   from NBDB to SBDB])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl create Logical_Router name=R1
-ovn-sbctl chassis-add gw1 geneve 127.0.0.1
-ovn-sbctl chassis-add gw2 geneve 1.2.4.8
-
-# Connect alice to R1 as distributed router gateway port on hv2
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24
-
-ovn-nbctl --wait=sb \
-    --id=@gc0 create Gateway_Chassis name=alice_gw1 \
-                                     chassis_name=gw1 \
-                                     priority=20 -- \
-    --id=@gc1 create Gateway_Chassis name=alice_gw2 \
-                                     chassis_name=gw2 \
-                                     priority=10 -- \
-    set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]'
-
-nb_gwc1_uuid=`ovn-nbctl --bare --columns _uuid find Gateway_Chassis name="alice_gw1"`
-
-# With the new ha_chassis_group table added, there should be no rows in
-# gateway_chassis table in SB DB.
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-# There should be one ha_chassis_group with the name "alice"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="alice"`
-
-AT_CHECK([test $ha_chassi_grp_name = alice])
-
-ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice`
-
-AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \
-logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1
-])
-
-# There should be one ha_chassis_group with the name "alice"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="alice"`
-
-AT_CHECK([test $ha_chassi_grp_name = alice])
-
-ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice`
-
-AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \
-logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1
-])
-
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-# Trim the spaces.
-ha_ch=`echo $ha_ch | sed 's/ //g'`
-
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
-
-# Delete chassis - gw2 in SB DB.
-# ovn-northd should not recreate ha_chassis rows
-# repeatedly when gw2 is deleted.
-ovn-sbctl chassis-del gw2
-
-ha_ch_list_1=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_1="$ha_ch_list_1 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'`
-
-ha_ch_list_2=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_2="$ha_ch_list_2 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"])
-
-# Add back the gw2 chassis
-ovn-sbctl chassis-add gw2 geneve 1.2.4.8
-
-# delete the 2nd Gateway_Chassis on NBDB for alice port
-gw_ch=`ovn-sbctl --bare --columns gateway_chassis find port_binding \
-logical_port="cr-alice"`
-AT_CHECK([test "$gw_ch" = ""])
-
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-ha_ch=`echo $ha_ch | sed 's/ //g'`
-# Trim the spaces.
-echo "ha ch in grp = $ha_ch"
-
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
-
-# delete the 2nd Gateway_Chassis on NBDB for alice port
-ovn-nbctl --wait=sb set Logical_Router_Port alice gateway_chassis=${nb_gwc1_uuid}
-
-# There should be only 1 row in ha_chassis SB DB table.
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1
-])
-
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-# There should be only 1 row in ha_chassis SB DB table.
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1
-])
-
-# delete all the gateway_chassis on NBDB for alice port
-
-ovn-nbctl --wait=sb clear Logical_Router_Port alice gateway_chassis
-
-# expect that the ha_chassis doesn't exist anymore
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0
-])
-
-# expect that the ha_chassis doesn't exist anymore
-AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0
-])
-AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check Gateway_Chassis propagation from NBDB to SBDB backwards compatibility])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl create Logical_Router name=R1
-ovn-sbctl chassis-add gw1 geneve 127.0.0.1
-ovn-sbctl chassis-add gw2 geneve 1.2.4.8
-
-ovn-nbctl --wait=sb lrp-add R1 bob 00:00:02:01:02:03 172.16.1.1/24 \
-    -- set Logical_Router_Port bob options:redirect-chassis="gw1"
-
-
-# It should be converted to ha_chassis_group entries in SBDB, and
-# still redirect-chassis is kept for backwards compatibility
-
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1
-])
-
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis_group | wc -l], [0], [1
-])
-
-# There should be one ha_chassis_group with the name "bob_gw1"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="bob_gw1"`
-
-AT_CHECK([test $ha_chassi_grp_name = bob_gw1])
-
-ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=bob_gw1`
-
-AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \
-logical_port="cr-bob" | grep $ha_chgrp_uuid | wc -l], [0], [1
-])
-
-ovn-nbctl --wait=sb remove Logical_Router_Port bob options redirect-chassis
-
-# expect that the ha_chassis/ha_chassis_group doesn't exist anymore
-
-AT_CHECK([ovn-sbctl find Gateway_Chassis name=bob_gw1], [0], [])
-AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check up state of VIF LSP])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add S1
-ovn-nbctl --wait=sb lsp-add S1 S1-vm1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xdown])
-
-ovn-sbctl chassis-add hv1 geneve 127.0.0.1
-ovn-sbctl lsp-bind S1-vm1 hv1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xup])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check up state of router LSP linked to a distributed LR])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl lr-add R1
-ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
-
-ovn-nbctl ls-add S1
-ovn-nbctl lsp-add S1 S1-R1
-ovn-nbctl lsp-set-type S1-R1 router
-ovn-nbctl lsp-set-addresses S1-R1 02:ac:10:01:00:01
-ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check up state of router LSP linked to a gateway LR])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-sbctl chassis-add gw1 geneve 127.0.0.1
-
-ovn-nbctl create Logical_Router name=R1 options:chassis=gw1
-ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
-
-ovn-nbctl ls-add S1
-ovn-nbctl lsp-add S1 S1-R1
-ovn-nbctl lsp-set-type S1-R1 router
-ovn-nbctl lsp-set-addresses S1-R1 02:ac:10:01:00:01
-ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
-
-ovn-sbctl lsp-bind S1-R1 gw1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check up state of router LSP linked to an LRP with set Gateway Chassis])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-sbctl chassis-add gw1 geneve 127.0.0.1
-
-ovn-nbctl lr-add R1
-ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
-ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
-
-ovn-nbctl ls-add S1
-ovn-nbctl lsp-add S1 S1-R1
-ovn-nbctl lsp-set-type S1-R1 router
-ovn-nbctl lsp-set-addresses S1-R1 router
-ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check IPv6 RA config propagation to SBDB])
-ovn_start
-
-ovn-nbctl lr-add ro
-ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64
-ovn-nbctl ls-add sw
-ovn-nbctl lsp-add sw sw-ro
-ovn-nbctl lsp-set-type sw-ro router
-ovn-nbctl lsp-set-options sw-ro router-port=ro-sw
-ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:send_periodic=true
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=slaac
-ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:mtu=1280
-
-uuid=$(ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=ro-sw)
-
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_send_periodic],
-[0], ["true"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_address_mode],
-[0], [slaac
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[0], ["600"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[0], ["200"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_mtu],
-[0], ["1280"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_eth],
-[0], ["00:00:00:00:00:01"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_addr],
-[0], ["fe80::200:ff:fe00:1"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_prefixes],
-[0], ["aef0::/64"
-])
-
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300
-ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=600
-
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[0], ["300"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[0], ["225"
-])
-
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300
-ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=250
-
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[0], ["300"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[0], ["225"
-])
-
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=0
-ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=0
-
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[0], ["4"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[0], ["3"
-])
-
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=3600
-ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=2400
-
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[0], ["1800"
-])
-AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[0], ["1350"
-])
-
-ovn-nbctl --wait=sb set Logical_Router_port ro-sw ipv6_ra_configs:send_periodic=false
-
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_send_periodic],
-[1], [], [ovn-sbctl: no key "ipv6_ra_send_periodic" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval],
-[1], [], [ovn-sbctl: no key "ipv6_ra_max_interval" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval],
-[1], [], [ovn-sbctl: no key "ipv6_ra_min_interval" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_mtu],
-[1], [], [ovn-sbctl: no key "ipv6_ra_mtu" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_address_mode],
-[1], [], [ovn-sbctl: no key "ipv6_ra_address_mode" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_eth],
-[1], [], [ovn-sbctl: no key "ipv6_ra_src_eth" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_addr],
-[1], [], [ovn-sbctl: no key "ipv6_ra_src_addr" in Port_Binding record "${uuid}" column options
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_prefixes],
-[1], [], [ovn-sbctl: no key "ipv6_ra_prefixes" in Port_Binding record "${uuid}" column options
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- test unixctl])
-ovn_init_db ovn-sb; ovn-sbctl init
-ovn_init_db ovn-nb; ovn-nbctl init
-
-# test unixctl option
-mkdir "$ovs_base"/northd
-as northd start_daemon ovn-northd --unixctl="$ovs_base"/northd/ovn-northd.ctl --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-ovn-nbctl ls-add sw
-ovn-nbctl --wait=sb lsp-add sw p1
-# northd created with unixctl option successfully created port_binding entry
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p1" | wc -l], [0], [1
-])
-AT_CHECK([ovn-nbctl --wait=sb lsp-del p1])
-
-# ovs-appctl exit with unixctl option
-OVS_APP_EXIT_AND_WAIT_BY_TARGET(["$ovs_base"/northd/ovn-northd.ctl], ["$ovs_base"/northd/ovn-northd.pid])
-
-# Check no port_binding entry for new port as ovn-northd is not running
-ovn-nbctl lsp-add sw p2
-ovn-nbctl --timeout=10 --wait=sb sync
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p2" | wc -l], [0], [0
-])
-
-# test default unixctl path
-as northd start_daemon ovn-northd --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-ovn-nbctl --wait=sb lsp-add sw p3
-# northd created with default unixctl path successfully created port_binding entry
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p3" | wc -l], [0], [1
-])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- check HA_Chassis_Group propagation from NBDB to SBDB])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
-
-# ovn-northd should not create HA chassis group and HA chassis rows
-# unless the HA chassis group in OVN NB DB is associated to
-# a logical router port or logical port of type external.
-AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \
-| wc -l], [0], [0
-])
-
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
-
-# There should be no HA_Chassis rows in SB DB.
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l ], [0], [0
-])
-
-# Add chassis ch1.
-ovn-sbctl chassis-add ch1 geneve 127.0.0.2
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list chassis | grep ch1 | wc -l`])
-
-# There should be no HA_Chassis rows
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l ], [0], [0
-])
-
-# Create a logical router port and attach ha chassis group.
-ovn-nbctl lr-add lr0
-ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-
-hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1`
-ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Make sure that ovn-northd doesn't recreate the ha_chassis
-# records if the chassis record is missing in SB DB.
-
-ha_ch_list_1=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_1="$ha_ch_list_1 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'`
-
-ha_ch_list_2=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_2="$ha_ch_list_2 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"])
-
-# 2 HA chassis should be created with 'chassis' column empty because
-# we have not added hv1 and hv2 chassis to the SB DB.
-AT_CHECK([test 2 = `ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l`])
-
-# We should have 1 ha chassis with 'chassis' column set for hv1
-AT_CHECK([test 1 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | awk '{print $3}' \
-| grep '-' | wc -l`])
-
-# Create another logical router port and associate to the same ha_chasis_group
-ovn-nbctl lr-add lr1
-ovn-nbctl lrp-add lr1 lr1-public 00:00:20:20:12:14 182.168.0.100/24
-
-ovn-nbctl set logical_router_port lr1-public ha_chassis_group=$hagrp1_uuid
-
-# We should still have 1 HA chassis group and 3 HA chassis in SB DB.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Change the priority of ch1 - ha chassis in NB DB. It should get
-# reflected in SB DB.
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 100
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns priority find \
-ha_chassis | grep 100 | wc -l`])
-
-# Delete ch1 HA chassis in NB DB.
-ovn-nbctl --wait=sb ha-chassis-group-remove-chassis hagrp1 ch1
-
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Add back the ha chassis
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 40
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Delete lr0-public. We should still have 1 HA chassis group and
-# 3 HA chassis in SB DB.
-ovn-nbctl --wait=sb lrp-del lr0-public
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Delete lr1-public. There should be no HA chassis group in SB DB.
-ovn-nbctl --wait=sb lrp-del lr1-public
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
-
-# Add lr0-public again
-ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Create a Gateway chassis. ovn-northd should ignore this.
-ovn-nbctl lrp-set-gateway-chassis lr0-public ch-1 20
-
-# There should be only 1 HA chassis group in SB DB with the
-# name hagrp1.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Now delete HA chassis group. ovn-northd should create HA chassis group
-# with the Gateway chassis name
-ovn-nbctl clear logical_router_port lr0-public ha_chassis_group
-ovn-nbctl ha-chassis-group-del hagrp1
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \
-find ha_chassis | wc -l`])
-
-ovn-nbctl lrp-set-gateway-chassis lr0-public ch2 10
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
-
-ovn-sbctl --bare --columns _uuid find ha_chassis
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Test if 'ref_chassis' column is properly set or not in
-# SB DB ha_chassis_group.
-ovn-nbctl ls-add sw0
-ovn-nbctl lsp-add sw0 sw0-p1
-
-ovn-sbctl chassis-add ch2 geneve 127.0.0.3
-ovn-sbctl chassis-add ch3 geneve 127.0.0.4
-ovn-sbctl chassis-add comp1 geneve 127.0.0.5
-ovn-sbctl chassis-add comp2 geneve 127.0.0.6
-
-ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24
-ovn-nbctl lsp-add sw0 sw0-lr0
-ovn-nbctl lsp-set-type sw0-lr0 router
-ovn-nbctl lsp-set-addresses sw0-lr0 router
-ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-
-ovn-sbctl lsp-bind sw0-p1 comp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xup])
-
-comp1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"`
-comp2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp2"`
-ch2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"`
-
-echo "comp1_ch_uuid = $comp1_ch_uuid"
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp1_ch_uuid" = "$ref_ch_list"])
-
-# unbind sw0-p1
-ovn-sbctl lsp-unbind sw0-p1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "" = "$ref_ch_list"])
-
-# Bind sw0-p1 in comp2
-ovn-sbctl lsp-bind sw0-p1 comp2
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
-
-ovn-nbctl ls-add sw1
-ovn-nbctl lsp-add sw1 sw1-p1
-ovn-nbctl lr-add lr1
-ovn-nbctl lrp-add lr1 lr1-sw1 00:00:20:20:12:15 20.0.0.1/24
-ovn-nbctl lsp-add sw1 sw1-lr1
-ovn-nbctl lsp-set-type sw1-lr1 router
-ovn-nbctl lsp-set-addresses sw1-lr1 router
-ovn-nbctl lsp-set-options sw1-lr1 router-port=lr1-sw1
-
-# Bind sw1-p1 in comp1.
-ovn-sbctl lsp-bind sw1-p1 comp1
-# Wait until sw1-p1 is up
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xup])
-
-# sw1-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
-
-# Now attach sw0 to lr1
-ovn-nbctl lrp-add lr1 lr1-sw0 00:00:20:20:12:16 10.0.0.10/24
-ovn-nbctl lsp-add sw0 sw0-lr1
-ovn-nbctl lsp-set-type sw0-lr1 router
-ovn-nbctl lsp-set-addresses sw0-lr1 router
-ovn-nbctl lsp-set-options sw0-lr1 router-port=lr1-sw0
-
-# Both comp1 and comp2 should be in 'ref_chassis' as sw1 is indirectly
-# connected to lr0
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $comp1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $comp2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# Unind sw1-p1. comp2 should not be in the ref_chassis.
-ovn-sbctl lsp-unbind sw1-p1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
-
-# Create sw2 and attach it to lr2
-ovn-nbctl ls-add sw2
-ovn-nbctl lsp-add sw2 sw2-p1
-ovn-nbctl lr-add lr2
-ovn-nbctl lrp-add lr2 lr2-sw2 00:00:20:20:12:17 30.0.0.1/24
-ovn-nbctl lsp-add sw2 sw2-lr2
-ovn-nbctl lsp-set-type sw2-lr2 router
-ovn-nbctl lsp-set-addresses sw2-lr2 router
-ovn-nbctl lsp-set-options sw2-lr2 router-port=lr2-sw2
-
-# Bind sw2-p1 to comp1
-ovn-sbctl lsp-bind sw2-p1 comp1
-# Wait until sw2-p1 is up
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw2-p1` = xup])
-
-# sw2-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
-
-# Now attach sw1 to lr2. With this sw2-p1 is indirectly connected to lr0.
-ovn-nbctl lrp-add lr2 lr2-sw1 00:00:20:20:12:18 20.0.0.10/24
-ovn-nbctl lsp-add sw1 sw1-lr2
-ovn-nbctl lsp-set-type sw1-lr2 router
-ovn-nbctl lsp-set-addresses sw1-lr2 router
-ovn-nbctl lsp-set-options sw1-lr2 router-port=lr2-sw1
-
-# sw2-p1 is indirectly connected to lr0. So comp1 (and comp2) should be in
-# 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# Create sw0-p2 and bind it to comp1
-ovn-nbctl lsp-add sw0 sw0-p2
-ovn-sbctl lsp-bind sw0-p2 comp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xup])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# unbind sw0-p2
-ovn-sbctl lsp-unbind sw0-p2
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# Delete lr1-sw0. comp1 should be deleted from ref_chassis as there is no link
-# from sw1 and sw2 to lr0.
-ovn-nbctl lrp-del lr1-sw0
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
-
-# Set redirect-chassis option to lr0-public. It should be ignored.
-ovn-nbctl set logical_router_port lr0-public options:redirect-chassis=ch1
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
-
-ovn-sbctl --bare --columns _uuid find ha_chassis
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Delete the gateway chassis. HA chassis group should be created in SB DB
-# for the redirect-chassis option.
-ovn-nbctl clear logical_router_port lr0-public gateway_chassis
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group | wc -l`])
-
-ovn-sbctl list ha_chassis_group
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public_ch1" | wc -l`])
-
-ovn-sbctl --bare --columns _uuid find ha_chassis
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list ha_chassis | grep chassis |
-grep -v chassis-name | wc -l`])
-
-# Clear the redirect-chassis option.
-ovn-nbctl clear logical_router_port lr0-public options
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
-
-# Delete old sw0.
-ovn-nbctl ls-del sw0
-
-# Create external logical ports and associate ha_chassis_group
-ovn-nbctl ls-add sw0
-ovn-nbctl lsp-add sw0 sw0-pext1
-ovn-nbctl lsp-add sw0 sw0-pext2
-ovn-nbctl lsp-add sw0 sw0-p1
-
-ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3"
-ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4"
-ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5"
-
-ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
-
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
-
-# ovn-northd should not create HA chassis group and HA chassis rows
-# unless the HA chassis group in OVN NB DB is associated to
-# a logical router port or logical port of type external.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
-
-hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \
-name=hagrp1`
-
-# The type of the lsp - sw0-pext1 is still not set to external.
-# So ha_chassis_group should be ignored.
-ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
-
-# Set the type of sw0-pext1 to external
-ovn-nbctl lsp-set-type sw0-pext1 external
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
-name=hagrp1`
-
-AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext1`])
-
-# Set the type of sw0-pext2 to external and associate ha_chassis_group
-ovn-nbctl lsp-set-type sw0-pext2 external
-ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis |
-grep -v chassis-name | wc -l`])
-AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext1`])
-
-OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext2`])
-
-# sw0-p1 is a normal port. So ha_chassis_group should not be set
-# in port_binding.
-ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \
-ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-p1) = x], [0], [])
-
-# Clear ha_chassis_group for sw0-pext1
-ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group
-
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-pext1) = x], [0], [])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-# Clear ha_chassis_group for sw0-pext2
-ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group
-
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-pext2) = x], [0], [])
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-AT_CLEANUP
diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
deleted file mode 100644
index a8a15f8fe..000000000
--- a/tests/ovn-performance.at
+++ /dev/null
@@ -1,424 +0,0 @@
-#
-# Tests targeting performance of OVN components.
-#
-
-m4_divert_push([PREPARE_TESTS])
-
-# vec_cmp VALUE_VEC OP-VALUE_VEC
-#
-# Compares each value from VALUE_VEC to the operator-value pair from the
-# OP-VALUE_VEC.
-#
-# VALUE_VEC must be a list of values separated by a character from $IFS.
-# OP-VALUE_VEC must be a list of operator-value expressions separated by a
-# character from $IFS.  Operator-value expressions cannot contain any characters
-# from $IFS like spaces. '=' is treated as an equality operator ('==') for
-# conciseness.
-#
-# Returns the result of each comparison as a list of boolean values (0 or 1)
-# separated by a new-line character.
-vec_cmp() {
-    local a b i j
-
-    i=0
-    for a in $1; do
-        j=0
-        for b in $2; do
-            if test $i -eq $j; then
-                # Replace assignment '=' with equality comparison '=='
-                case "$b" in
-                =[[0-9]]*) b="=$b" ;;
-                esac
-
-                echo $(($a $b))
-                break
-            fi
-            j=$((j + 1))
-        done
-        i=$((i + 1))
-    done
-}
-
-# vec_sub VEC_A VEC_B
-#
-# Subtracts two vectors:
-#
-#     VEC_A = [a1, a2, ...]
-#     VEC_B = [b1, b2, ...]
-#     OUT = [(a1 - b1), (a2 - b2), ...]
-#
-# VEC_A and VEC_B must be lists of values separated by a character from $IFS.
-vec_sub() {
-    local a b i j
-
-    i=0
-    for a in $1; do
-        j=0
-        for b in $2; do
-            if test $i -eq $j; then
-                echo $((a - b))
-                break
-            fi
-            j=$((j + 1))
-        done
-        i=$((i + 1))
-    done
-}
-
-# vec_fold VEC OP
-#
-# Reduces a vector to a single value by applying the binary operator OP (i.e.,
-# one that requires two arguments) cumulatively to all vector elements from left
-# to right:
-#
-#     VEC = [e1, e2, e3 ...]
-#     OUT = (...((e1 OP e2) OP e3) OP ...)
-#
-# VEC must be a list of values separated by a character from $IFS.
-vec_fold() {
-    local first op prod
-
-    first=1
-    op=$2
-    for a in $1; do
-        if test $first -eq 1; then
-            prod=$a
-            first=0
-        else
-            prod=$((prod $op a))
-        fi
-    done
-    echo $prod
-}
-
-# read_counters SANDBOXES TARGET COUNTER
-#
-# Prints out the coverage COUNTER for the TARGET in each of the SANDBOXES.
-#
-# SANDBOXES must be a list of strings separated by a character from $IFS.
-read_counters() {
-    local sims="$1" target="$2" counter="$3"
-
-    for sim in $sims; do
-        as $sim ovs-appctl -t "$target" coverage/read-counter "$counter" || return 1
-    done
-}
-
-# counter_delta_ SANDBOXES TARGET COUNTER COMMAND
-#
-# Runs the COMMAND and reports the COUNTER change registered during the command
-# run for the given TARGET in each of the SANDBOXES.
-counter_delta_() {
-    local sims="$1" target="$2" counter="$3" cmd="$4"
-    local before after
-
-    before=$(read_counters "$sims" "$target" "$counter") || return 1
-    eval "$cmd" >/dev/null || return 1
-    after=$(read_counters "$sims" "$target" "$counter") || return 1
-
-    vec_sub "$after" "$before"
-}
-
-# counter_delta SANDBOXES TARGET COUNTER COMMAND
-#
-# Same as counter_delta_ but also prints the COUNTER values together with the
-# COMMAND to standard error.
-counter_delta() {
-    local cmd="$4"
-    local v
-
-    v=$(counter_delta_ "$@") || return 1
-
-    # Dump the counters and the command for troubleshooting
-    echo "$v" | tr '\n' '\t' >&2
-    echo "$cmd" >&2
-
-    echo "$v"
-}
-
-# vec_cmp_counter_delta SANDBOXES TARGET COUNTER CONDS COMMAND
-#
-# Check if COUNTER change in the TARGET app in each of the SANDBOXES after
-# running the COMMAND meets the conditions listed as operator-value pairs in
-# CONDS.
-vec_cmp_counter_delta() {
-    local v
-
-    v=$(counter_delta "$1" "$2" "$3" "$5") || return 1
-    v=$(vec_cmp "$v" "$4") || return 1
-    v=$(vec_fold "$v" "&&") || return 1
-
-    echo "$v"
-}
-
-# cmp_counter_delta SANDBOXES TARGET COUNTER COND COMMAND
-#
-# Check if COUNTER change in the TARGET app in each of the SANDBOXES after
-# running the COMMAND meets the COND condition given as a operator-value pair.
-cmp_counter_delta() {
-    local conds=""
-
-    # Use the same condition for each sandbox
-    for _ in $1; do
-        conds="$conds $4"
-    done
-
-    vec_cmp_counter_delta "$1" "$2" "$3" "$conds" "$5"
-}
-
-m4_divert_pop([PREPARE_TESTS])
-
-# CHECK_COUNTER_DELTA_IS_ZERO SANDBOXES TARGET COUNTER COMMAND
-#
-# Runs the COMMAND and checks if the COUNTER value for the TARGET in all of
-# the SANDBOXES did not change.
-m4_define([CHECK_COUNTER_DELTA_IS_ZERO],[
-    rv=$(cmp_counter_delta "$1" "$2" "$3" "=0" "$4")
-    rc=$?
-    AT_CHECK([test $rc -eq 0 -a $rv -eq 1])
-])
-
-# CHECK_COUNTER_DELTA_IS_NOT_ZERO SANDBOXES TARGET COUNTER COMMAND
-#
-# Runs the COMMAND and checks if the COUNTER value for the TARGET in
-# all of the SANDBOXES has changed.
-m4_define([CHECK_COUNTER_DELTA_IS_NOT_ZERO],[
-    rv=$(cmp_counter_delta "$1" "$2" "$3" ">0" "$4")
-    rc=$?
-    AT_CHECK([test $rc -eq 0 -a $rv -eq 1])
-])
-
-# CHECK_COUNTER_DELTA_COND SANDBOXES TARGET COUNTER CONDS COMMAND
-#
-# Runs the COMMAND and checks if the COUNTER value for the TARGET in all of the
-# SANDBOXES satisfies the conditions listed in CONDS.
-m4_define([CHECK_COUNTER_DELTA_COND],[
-    rv=$(vec_cmp_counter_delta "$1" "$2" "$3" "$4" "$5")
-    rc=$?
-    AT_CHECK([test $rc -eq 0 -a $rv -eq 1])
-])
-
-# OVN_CONTROLLER_EXPECT_HIT SANDBOXES COUNTER COMMAND
-#
-# Checks if the COUNTER value has changed for any of the ovn-controller
-# processes in the SANDBOXES when the COMMAND was run.
-m4_define([OVN_CONTROLLER_EXPECT_HIT],[
-    CHECK_COUNTER_DELTA_IS_NOT_ZERO([$1], [ovn-controller], [$2], [$3])
-])
-
-# OVN_CONTROLLER_EXPECT_NO_HIT SANDBOXES COUNTER COMMAND
-#
-# Checks if the COUNTER value has not changed for any of the ovn-controller
-# processes in the SANDBOXES when the COMMAND was run.
-m4_define([OVN_CONTROLLER_EXPECT_NO_HIT],[
-    CHECK_COUNTER_DELTA_IS_ZERO([$1], [ovn-controller], [$2], [$3])
-])
-
-# OVN_CONTROLLER_EXPECT_HIT_COND SANDBOXES COUNTER CONDS COMMAND
-#
-# Checks if the change of the COUNTER value, when the COMMAND was run, of the
-# ovn-controller process in each of the SANDBOXES meets the conditions in
-# CONDS. CONDS must be a list of operator-value pairs, for example "[>0 =0]",
-# following the same order as SANDBOXES.
-m4_define([OVN_CONTROLLER_EXPECT_HIT_COND],[
-    CHECK_COUNTER_DELTA_COND([$1], [ovn-controller], [$2], [$3], [$4])
-])
-
-AT_SETUP([ovn -- ovn-controller incremental processing])
-# Check which operations the trigger full logical flow processing.
-#
-# Create and destroy logical routers, switches, ports, address sets and ACLs
-# while counting calls to lflow_run() in ovn-controller.
-
-ovn_start
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-done
-
-# Add router lr1
-OVN_CONTROLLER_EXPECT_HIT(
-    [hv1 hv2], [lflow_run],
-    [ovn-nbctl --wait=hv lr-add lr1]
-)
-
-for i in 1 2; do
-    ls=ls$i
-    lsp=$ls-lr1
-    lrp=lr1-$ls
-
-    # Add switch $ls
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv ls-add $ls]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv add Logical_Switch $ls other_config subnet=10.0.$i.0/24]
-    )
-
-    # Add router port to $ls
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lrp-add lr1 $lrp 02:00:00:00:0$i:01 10.0.$i.1/24]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-add $ls $lsp]
-    )
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-set-type $lsp router]
-    )
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-set-options $lsp router-port=$lrp]
-    )
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-set-addresses $lsp router]
-    )
-done
-
-get_lsp_uuid () {
-    ovn-nbctl lsp-list ls${1#lp} | grep $1 | awk '{ print $1 }'
-}
-
-pg_ports=
-
-for i in 1 2; do
-    j=$((i%2 + 1))
-    as=as$i
-    ls=ls$i
-    lp=lp$i
-    vif=vif$i
-
-    # Add port $lp
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-add $ls $lp]
-    )
-
-    pg_ports="$pg_port `get_lsp_uuid $lp`"
-
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv lsp-set-addresses $lp "dynamic"]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl wait-until Logical_Switch_Port $lp dynamic_addresses!=[[]] &&
-         ovn-nbctl --wait=hv sync]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl get Logical_Switch_Port $lp dynamic_addresses &&
-         ovn-nbctl --wait=hv sync]
-    )
-
-    # Add address set $as
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv create Address_Set name="$as"]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv add Address_Set "$as" addresses "10.0.$i.10"]
-    )
-
-    # Add ACLs for port $lp
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv acl-add $ls to-lport 1001 'outport == \"$lp\" && ip4.src == \$$as' allow]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv acl-add $ls to-lport 1000 'outport == \"$lp\"' drop]
-    )
-
-    # Bind port $lp and wait for it to come up
-    OVN_CONTROLLER_EXPECT_HIT_COND(
-        [hv$i hv$j], [lflow_run], [>0 =0],
-        [as hv$i ovs-vsctl add-port br-int $vif -- set Interface $vif external-ids:iface-id=$lp &&
-         ovn-nbctl wait-until Logical_Switch_Port $lp 'up=true' &&
-         ovn-nbctl --wait=hv sync]
-    )
-done
-
-for i in 1 2; do
-    j=$((i%2 + 1))
-    as=as$i
-    ls=ls$i
-    lp=lp$i
-
-    # Delete ACLs for port $lp
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv acl-del $ls to-lport 1001 'outport == \"$lp\" && ip4.src == \$$as']
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv acl-del $ls to-lport 1000 'outport == \"$lp\"']
-    )
-
-    # Delete address set $as
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv remove Address_Set "$as" addresses "10.0.$i.10"]
-    )
-    OVN_CONTROLLER_EXPECT_NO_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv destroy Address_Set "$as"]
-    )
-done
-
-OVN_CONTROLLER_EXPECT_NO_HIT(
-    [hv1 hv2], [lflow_run],
-    [ovn-nbctl --wait=hv create Port_Group name=pg1 ports=\"$pg_ports\"]
-)
-
-# Add ACLs for port group pg1
-OVN_CONTROLLER_EXPECT_NO_HIT(
-    [hv1 hv2], [lflow_run],
-    [ovn-nbctl --wait=hv acl-add pg1 to-lport 1001 'outport == @pg1 && ip4.src == $pg1_ip4' allow]
-)
-
-for i in 1 2; do
-    j=$((i%2 + 1))
-    lp=lp$i
-
-    # Delete port $lp
-    OVN_CONTROLLER_EXPECT_HIT_COND(
-        [hv$i hv$j], [lflow_run], [>0 =0],
-        [ovn-nbctl --wait=hv lsp-del $lp]
-    )
-done
-
-# Delete port group pg1
-OVN_CONTROLLER_EXPECT_NO_HIT(
-    [hv1 hv2], [lflow_run],
-    [ovn-nbctl --wait=hv destroy Port_Group pg1]
-)
-
-for i in 1 2; do
-    ls=ls$i
-
-    # Delete switch $ls
-    OVN_CONTROLLER_EXPECT_HIT(
-        [hv1 hv2], [lflow_run],
-        [ovn-nbctl --wait=hv ls-del $ls]
-    )
-done
-
-# Delete router lr1
-OVN_CONTROLLER_EXPECT_HIT(
-    [hv1 hv2], [lflow_run],
-    [ovn-nbctl --wait=hv lr-del lr1]
-)
-
-OVN_CLEANUP([hv1], [hv2])
-
-AT_CLEANUP
diff --git a/tests/ovn-sbctl.at b/tests/ovn-sbctl.at
deleted file mode 100644
index 650e357da..000000000
--- a/tests/ovn-sbctl.at
+++ /dev/null
@@ -1,150 +0,0 @@
-AT_BANNER([ovn-sbctl])
-
-# OVN_SBCTL_TEST_START
-m4_define([OVN_SBCTL_TEST_START],
-  [dnl Create databases (ovn-nb, ovn-sb).
-   AT_KEYWORDS([ovn])
-   for daemon in ovn-nb ovn-sb; do
-      AT_CHECK([ovsdb-tool create $daemon.db $abs_top_srcdir/${daemon%%-*}/${daemon}.ovsschema])
-   done
-
-   dnl Start ovsdb-servers.
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovnnb_db.pid --unixctl=$OVS_RUNDIR/ovnnb_db.ctl --log-file=ovsdb_nb.log --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db ], [0], [], [stderr])
-   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovnsb_db.pid --unixctl=$OVS_RUNDIR/ovnsb_db.ctl --log-file=ovsdb_sb.log --remote=punix:$OVS_RUNDIR/ovnsb_db.sock ovn-sb.db], [0], [], [stderr])
-   on_exit "kill `cat ovnnb_db.pid` `cat ovnsb_db.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d
-/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
-   AT_CAPTURE_FILE([ovsdb-server.log])
-
-   dnl Start ovn-northd.
-   AT_CHECK([ovn-northd --detach --no-chdir --pidfile --log-file --ovnnb-db=unix:$OVS_RUNDIR/ovnnb_db.sock --ovnsb-db=unix:$OVS_RUNDIR/ovnsb_db.sock], [0], [], [stderr])
-   on_exit "kill `cat ovn-northd.pid`"
-   AT_CHECK([[sed < stderr '
-/vlog|INFO|opened log file/d']])
-   AT_CAPTURE_FILE([ovn-northd.log])
-])
-
-# OVN_SBCTL_TEST_STOP
-m4_define([OVN_SBCTL_TEST_STOP],
-  [AT_CHECK([check_logs "$1"])
-   OVS_APP_EXIT_AND_WAIT([ovn-northd])
-   OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnnb_db.ctl], [$OVS_RUNDIR/ovnnb_db.pid])
-   OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnsb_db.ctl], [$OVS_RUNDIR/ovnsb_db.pid])])
-
-dnl ---------------------------------------------------------------------
-
-AT_SETUP([ovn-sbctl - chassis commands])
-OVN_SBCTL_TEST_START
-ovn_init_db ovn-sb
-
-AT_CHECK([ovn-sbctl chassis-add ch0 geneve 1.2.3.4])
-AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort],
-         [0], [dnl
-1.2.3.4,geneve
-])
-
-AT_CHECK([ovn-sbctl chassis-add ch1 stt,geneve,vxlan 1.2.3.5])
-AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort],
-         [0], [dnl
-1.2.3.4,geneve
-1.2.3.5,geneve
-1.2.3.5,stt
-1.2.3.5,vxlan
-])
-
-AT_CHECK([ovn-sbctl chassis-del ch0])
-AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort],
-         [0], [dnl
-1.2.3.5,geneve
-1.2.3.5,stt
-1.2.3.5,vxlan
-])
-
-OVN_SBCTL_TEST_STOP
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-AT_CLEANUP
-
-dnl ---------------------------------------------------------------------
-
-AT_SETUP([ovn-sbctl])
-OVN_SBCTL_TEST_START
-
-AT_CHECK([ovn-nbctl ls-add br-test])
-AT_CHECK([ovn-nbctl lsp-add br-test vif0])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02])
-AT_CHECK([ovn-sbctl chassis-add ch0 stt 1.2.3.5])
-AT_CHECK([ovn-nbctl --wait=sb sync])
-AT_CHECK([ovn-sbctl lsp-bind vif0 ch0])
-
-AT_CHECK([ovn-sbctl show], [0], [dnl
-Chassis ch0
-    Encap stt
-        ip: "1.2.3.5"
-        options: {csum="true"}
-    Port_Binding vif0
-])
-
-# adds another 'vif1'
-AT_CHECK([ovn-nbctl --wait=sb lsp-add br-test vif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:03])
-AT_CHECK([ovn-sbctl lsp-bind vif1 ch0])
-
-AT_CHECK([ovn-sbctl show | sed 's/vif[[0-9]]/vif/'], [0], [dnl
-Chassis ch0
-    Encap stt
-        ip: "1.2.3.5"
-        options: {csum="true"}
-    Port_Binding vif
-    Port_Binding vif
-])
-
-# deletes 'vif1'
-AT_CHECK([ovn-nbctl lsp-del vif1])
-AT_CHECK([ovn-nbctl --wait=sb sync])
-
-AT_CHECK([ovn-sbctl show], [0], [dnl
-Chassis ch0
-    Encap stt
-        ip: "1.2.3.5"
-        options: {csum="true"}
-    Port_Binding vif0
-])
-
-uuid=$(ovn-sbctl --columns=_uuid list Chassis ch0 | cut -d ':' -f2 | tr -d ' ')
-AT_CHECK_UNQUOTED([ovn-sbctl --columns=logical_port,mac,chassis list Port_Binding], [0], [dnl
-logical_port        : vif0
-mac                 : [["f0:ab:cd:ef:01:02"]]
-chassis             : ${uuid}
-])
-
-# test the passing down of logical port type and options.
-AT_CHECK([ovn-nbctl --wait=sb lsp-add br-test vtep0])
-AT_CHECK([ovn-nbctl lsp-set-type vtep0 vtep])
-AT_CHECK([ovn-nbctl lsp-set-options vtep0 vtep_physical_switch=p0 vtep_logical_switch=l0])
-
-AT_CHECK([ovn-sbctl --timeout=10 wait-until Port_Binding vtep0 options!={}])
-AT_CHECK([ovn-sbctl --columns=logical_port,mac,type,options list Port_Binding vtep0], [0], [dnl
-logical_port        : vtep0
-mac                 : [[]]
-type                : vtep
-options             : {vtep_logical_switch=l0, vtep_physical_switch=p0}
-])
-
-OVN_SBCTL_TEST_STOP
-AT_CLEANUP
-
-dnl ---------------------------------------------------------------------
-
-AT_SETUP([ovn-sbctl - connection])
-OVN_SBCTL_TEST_START
-
-AT_CHECK([ovn-sbctl --inactivity-probe=30000 set-connection ptcp:6641:127.0.0.1 punix:$OVS_RUNDIR/ovnsb_db.sock])
-AT_CHECK([ovn-sbctl list connection | grep inactivity_probe], [0], [dnl
-inactivity_probe    : 30000
-inactivity_probe    : 30000
-])
-
-OVN_SBCTL_TEST_STOP
-AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
deleted file mode 100644
index cb380d275..000000000
--- a/tests/ovn.at
+++ /dev/null
@@ -1,14702 +0,0 @@
-# OVN_CHECK_PACKETS([PCAP], [EXPECTED])
-#
-# This compares packets read from PCAP, in pcap format, to those read
-# from EXPECTED, which is a text file containing packets as hex
-# strings, one per line.  If PCAP contains fewer packets than
-# EXPECTED, it waits up to 10 seconds for more packets to appear.
-#
-# The implementation is an m4 macro that is mostly implemented in
-# terms of a shell function.  This reduces the size of the generated
-# testsuite file since the shell function is only emitted once even
-# when this macro is invoked many times.
-m4_divert_text([PREPARE_TESTS],
-  [ovn_check_packets__ () {
-     echo
-     echo "checking packets in $1 against $2:"
-     rcv_pcap=$1
-     rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'`
-     exp_text=$2
-     exp_n=`wc -l < "$exp_text"`
-     OVS_WAIT_UNTIL(
-       [$PYTHON "$top_srcdir/utilities/ovs-pcap.in" $rcv_pcap > $rcv_text
-        rcv_n=`wc -l < "$rcv_text"`
-        echo "rcv_n=$rcv_n exp_n=$exp_n"
-        test $rcv_n -ge $exp_n])
-     sort $exp_text > expout
-   }
-])
-m4_define([OVN_CHECK_PACKETS],
-  [ovn_check_packets__ "$1" "$2"
-   AT_CHECK([sort $rcv_text], [0], [expout])])
-
-AT_BANNER([OVN components])
-
-AT_SETUP([ovn -- lexer])
-dnl For lines without =>, input and expected output are identical.
-dnl For lines with =>, input precedes => and expected output follows =>.
-AT_DATA([test-cases.txt], [dnl
-foo bar baz quuxquuxquux _abcd_ a.b.c.d a123_.456
-"abc\u0020def" => "abc def"
-" => error("Input ends inside quoted string.")dnl "
-
-$foo $bar $baz $quuxquuxquux $_abcd_ $a.b.c.d $a123_.456
-$1 => error("`$' must be followed by a valid identifier.") 1
-
-a/*b*/c => a c
-a//b c => a
-a/**/b => a b
-a/*/b => a error("`/*' without matching `*/'.")
-a/*/**/b => a b
-a/b => a error("`/' is only valid as part of `//' or `/*'.") b
-
-0 1 12345 18446744073709551615
-18446744073709551616 => error("Decimal constants must be less than 2**64.")
-9999999999999999999999 => error("Decimal constants must be less than 2**64.")
-01 => error("Decimal constants must not have leading zeros.")
-
-0/0
-0/1
-1/0 => error("Value contains unmasked 1-bits.")
-1/1
-128/384
-1/3
-1/ => error("Integer constant expected.")
-
-1/0x123 => error("Value and mask have incompatible formats.")
-
-0x1234
-0x01234 => 0x1234
-0x0 => 0
-0x000 => 0
-0xfedcba9876543210
-0XFEDCBA9876543210 => 0xfedcba9876543210
-0xfedcba9876543210fedcba9876543210
-0x0000fedcba9876543210fedcba9876543210 => 0xfedcba9876543210fedcba9876543210
-0x => error("Hex digits expected following 0x.")
-0X => error("Hex digits expected following 0X.")
-0x0/0x0 => 0/0
-0x0/0x1 => 0/0x1
-0x1/0x0 => error("Value contains unmasked 1-bits.")
-0xffff/0x1ffff
-0x. => error("Invalid syntax in hexadecimal constant.")
-
-192.168.128.1 1.2.3.4 255.255.255.255 0.0.0.0
-256.1.2.3 => error("Invalid numeric constant.")
-192.168.0.0/16
-192.168.0.0/255.255.0.0 => 192.168.0.0/16
-192.168.0.0/255.255.255.0 => 192.168.0.0/24
-192.168.0.0/255.255.0.255
-192.168.0.0/255.0.0.0 => error("Value contains unmasked 1-bits.")
-192.168.0.0/32
-192.168.0.0/255.255.255.255 => 192.168.0.0/32
-1.2.3.4:5 => 1.2.3.4 : 5
-
-::
-::1
-ff00::1234 => ff00::1234
-2001:db8:85a3::8a2e:370:7334
-2001:db8:85a3:0:0:8a2e:370:7334 => 2001:db8:85a3::8a2e:370:7334
-2001:0db8:85a3:0000:0000:8a2e:0370:7334 => 2001:db8:85a3::8a2e:370:7334
-::ffff:192.0.2.128
-::ffff:c000:0280 => ::ffff:192.0.2.128
-::1/::1
-::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => ::1/128
-::1/128
-ff00::/8
-ff00::/ff00:: => ff00::/8
-
-01:23:45:67:ab:cd
-01:23:45:67:AB:CD => 01:23:45:67:ab:cd
-fe:dc:ba:98:76:54
-FE:DC:ba:98:76:54 => fe:dc:ba:98:76:54
-01:00:00:00:00:00/01:00:00:00:00:00
-ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff
-fe:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff
-ff:ff:ff:ff:ff:ff/fe:ff:ff:ff:ff:ff => error("Value contains unmasked 1-bits.")
-fe:x => error("Invalid numeric constant.")
-00:01:02:03:04:x => error("Invalid numeric constant.")
-
-# Test that operators are tokenized as expected, even without white space.
-(){}[[]]==!=<<=>>=!&&||..,;=<->--: => ( ) { } [[ ]] == != < <= > >= ! && || .. , ; = <-> -- :
-& => error("`&' is only valid as part of `&&'.")
-| => error("`|' is only valid as part of `||'.")
-- => error("`-' is only valid as part of `--'.")
-
-^ => error("Invalid character `^' in input.")
-])
-AT_CAPTURE_FILE([input.txt])
-sed 's/ =>.*//' test-cases.txt > input.txt
-sed 's/.* => //' test-cases.txt > expout
-AT_CHECK([ovstest test-ovn lex < input.txt], [0], [expout])
-AT_CLEANUP
-
-dnl The OVN expression parser needs to know what fields overlap with one
-dnl another.  This test therefore verifies that all the smaller registers
-dnl are defined as terms of subfields of the larger ones.
-dnl
-dnl When we add or remove registers this test needs to be updated, of course.
-AT_SETUP([ovn -- registers])
-AT_CHECK([ovstest test-ovn dump-symtab | grep reg | sort], [0],
-[[reg0 = xxreg0[96..127]
-reg1 = xxreg0[64..95]
-reg2 = xxreg0[32..63]
-reg3 = xxreg0[0..31]
-reg4 = xxreg1[96..127]
-reg5 = xxreg1[64..95]
-reg6 = xxreg1[32..63]
-reg7 = xxreg1[0..31]
-reg8 = xreg4[32..63]
-reg9 = xreg4[0..31]
-xreg0 = xxreg0[64..127]
-xreg1 = xxreg0[0..63]
-xreg2 = xxreg1[64..127]
-xreg3 = xxreg1[0..63]
-xreg4 = OXM_OF_PKT_REG4
-xxreg0 = NXM_NX_XXREG0
-xxreg1 = NXM_NX_XXREG1
-]])
-AT_CLEANUP
-
-dnl Check that the OVN conntrack field definitions are correct.
-AT_SETUP([ovn -- conntrack fields])
-AT_CHECK([ovstest test-ovn dump-symtab | grep ^ct | sort], [0],
-[[ct.dnat = ct_state[7]
-ct.est = ct_state[1]
-ct.inv = ct_state[4]
-ct.new = ct_state[0]
-ct.rel = ct_state[2]
-ct.rpl = ct_state[3]
-ct.snat = ct_state[6]
-ct.trk = ct_state[5]
-ct_label = NXM_NX_CT_LABEL
-ct_label.blocked = ct_label[0]
-ct_mark = NXM_NX_CT_MARK
-ct_state = NXM_NX_CT_STATE
-]])
-AT_CLEANUP
-
-AT_SETUP([ovn -- composition])
-AT_CHECK([ovstest test-ovn composition 2], [0], [ignore])
-AT_CLEANUP
-
-AT_SETUP([ovn -- expression parser])
-dnl For lines without =>, input and expected output are identical.
-dnl For lines with =>, input precedes => and expected output follows =>.
-AT_DATA([test-cases.txt], [[
-eth.type == 0x800
-eth.type==0x800 => eth.type == 0x800
-eth.type[0..15] == 0x800 => eth.type == 0x800
-
-vlan.present
-vlan.present == 1 => vlan.present
-!(vlan.present == 0) => vlan.present
-!(vlan.present != 1) => vlan.present
-!vlan.present
-vlan.present == 0 => !vlan.present
-vlan.present != 1 => !vlan.present
-!(vlan.present == 1) => !vlan.present
-!(vlan.present != 0) => !vlan.present
-
-eth.dst[0]
-eth.dst[0] == 1 => eth.dst[0]
-eth.dst[0] != 0 => eth.dst[0]
-!(eth.dst[0] == 0) => eth.dst[0]
-!(eth.dst[0] != 1) => eth.dst[0]
-
-!eth.dst[0]
-eth.dst[0] == 0 => !eth.dst[0]
-eth.dst[0] != 1 => !eth.dst[0]
-!(eth.dst[0] == 1) => !eth.dst[0]
-!(eth.dst[0] != 0) => !eth.dst[0]
-
-vlan.tci[12..15] == 0x3
-vlan.tci == 0x3000/0xf000 => vlan.tci[12..15] == 0x3
-vlan.tci[12..15] != 0x3
-vlan.tci != 0x3000/0xf000 => vlan.tci[12..15] != 0x3
-
-!vlan.pcp => vlan.pcp == 0
-!(vlan.pcp) => vlan.pcp == 0
-vlan.pcp == 0x4
-vlan.pcp != 0x4
-vlan.pcp > 0x4
-vlan.pcp >= 0x4
-vlan.pcp < 0x4
-vlan.pcp <= 0x4
-!(vlan.pcp != 0x4) => vlan.pcp == 0x4
-!(vlan.pcp == 0x4) => vlan.pcp != 0x4
-!(vlan.pcp <= 0x4) => vlan.pcp > 0x4
-!(vlan.pcp < 0x4) => vlan.pcp >= 0x4
-!(vlan.pcp >= 0x4) => vlan.pcp < 0x4
-!(vlan.pcp > 0x4) => vlan.pcp <= 0x4
-0x4 == vlan.pcp => vlan.pcp == 0x4
-0x4 != vlan.pcp => vlan.pcp != 0x4
-0x4 < vlan.pcp => vlan.pcp > 0x4
-0x4 <= vlan.pcp => vlan.pcp >= 0x4
-0x4 > vlan.pcp => vlan.pcp < 0x4
-0x4 >= vlan.pcp => vlan.pcp <= 0x4
-!(0x4 != vlan.pcp) => vlan.pcp == 0x4
-!(0x4 == vlan.pcp) => vlan.pcp != 0x4
-!(0x4 >= vlan.pcp) => vlan.pcp > 0x4
-!(0x4 > vlan.pcp) => vlan.pcp >= 0x4
-!(0x4 <= vlan.pcp) => vlan.pcp < 0x4
-!(0x4 < vlan.pcp) => vlan.pcp <= 0x4
-
-1 < vlan.pcp < 4 => vlan.pcp > 0x1 && vlan.pcp < 0x4
-1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4
-1 < vlan.pcp <= 4 => vlan.pcp > 0x1 && vlan.pcp <= 0x4
-1 <= vlan.pcp < 4 => vlan.pcp >= 0x1 && vlan.pcp < 0x4
-1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4
-4 > vlan.pcp > 1 => vlan.pcp < 0x4 && vlan.pcp > 0x1
-4 >= vlan.pcp > 1 => vlan.pcp <= 0x4 && vlan.pcp > 0x1
-4 > vlan.pcp >= 1 => vlan.pcp < 0x4 && vlan.pcp >= 0x1
-4 >= vlan.pcp >= 1 => vlan.pcp <= 0x4 && vlan.pcp >= 0x1
-!(1 < vlan.pcp < 4) => vlan.pcp <= 0x1 || vlan.pcp >= 0x4
-!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4
-!(1 < vlan.pcp <= 4) => vlan.pcp <= 0x1 || vlan.pcp > 0x4
-!(1 <= vlan.pcp < 4) => vlan.pcp < 0x1 || vlan.pcp >= 0x4
-!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4
-!(4 > vlan.pcp > 1) => vlan.pcp >= 0x4 || vlan.pcp <= 0x1
-!(4 >= vlan.pcp > 1) => vlan.pcp > 0x4 || vlan.pcp <= 0x1
-!(4 > vlan.pcp >= 1) => vlan.pcp >= 0x4 || vlan.pcp < 0x1
-!(4 >= vlan.pcp >= 1) => vlan.pcp > 0x4 || vlan.pcp < 0x1
-
-vlan.pcp == {1, 2, 3, 4} => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4
-vlan.pcp == 1 || ((vlan.pcp == 2 || vlan.pcp == 3) || vlan.pcp == 4) => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4
-
-vlan.pcp != {1, 2, 3, 4} => vlan.pcp != 0x1 && vlan.pcp != 0x2 && vlan.pcp != 0x3 && vlan.pcp != 0x4
-vlan.pcp == 1 && ((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && vlan.pcp == 0x2 && vlan.pcp == 0x3 && vlan.pcp == 0x4
-
-vlan.pcp == 1 && !((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3 || vlan.pcp != 0x4)
-vlan.pcp == 1 && (!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3) && vlan.pcp == 0x4
-vlan.pcp == 1 && !(!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && ((vlan.pcp == 0x2 && vlan.pcp == 0x3) || vlan.pcp != 0x4)
-
-ip4.src == {10.0.0.0/8, 192.168.0.0/16, 172.16.20.0/24, 8.8.8.8} => ip4.src[24..31] == 0xa || ip4.src[16..31] == 0xc0a8 || ip4.src[8..31] == 0xac1014 || ip4.src == 0x8080808
-ip6.src == ::1 => ip6.src == 0x1
-
-ip4.src == 1.2.3.4 => ip4.src == 0x1020304
-ip4.src == ::1.2.3.4/::ffff:ffff => ip4.src == 0x1020304
-ip6.src == ::1 => ip6.src == 0x1
-
-1
-0
-!1 => 0
-!0 => 1
-
-inport == "eth0"
-!(inport != "eth0") => inport == "eth0"
-
-(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((0))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) => 0
-
-ip4.src == "eth0" => Integer field ip4.src is not compatible with string constant.
-inport == 1 => String field inport is not compatible with integer constant.
-ip4.src = 1.2.3.4 => Syntax error at `=' expecting relational operator.
-
-ip4.src > {1, 2, 3} => Only == and != operators may be used with value sets.
-eth.type > 0x800 => Only == and != operators may be used with nominal field eth.type.
-vlan.present > 0 => Only == and != operators may be used with Boolean field vlan.present.
-
-inport != "eth0" => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account).
-!(inport == "eth0") => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account).
-eth.type != 0x800 => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account).
-!(eth.type == 0x800) => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account).
-inport = "eth0" => Syntax error at `=' expecting relational operator.
-
-123 == 123 => Syntax error at `123' expecting field name.
-
-$name => Syntax error at `$name' expecting address set name.
-@name => Syntax error at `@name' expecting port group name.
-
-123 == xyzzy => Syntax error at `xyzzy' expecting field name.
-xyzzy == 1 => Syntax error at `xyzzy' expecting field name.
-
-inport[1] == 1 => Cannot select subfield of string field inport.
-
-eth.type[] == 1 => Syntax error at `@:>@' expecting small integer.
-eth.type[::1] == 1 => Syntax error at `::1' expecting small integer.
-eth.type[18446744073709551615] == 1 => Syntax error at `18446744073709551615' expecting small integer.
-
-eth.type[5!] => Syntax error at `!' expecting `@:>@'.
-
-eth.type[5..1] => Invalid bit range 5 to 1.
-
-eth.type[12..16] => Cannot select bits 12 to 16 of 16-bit field eth.type.
-
-eth.type[10] == 1 => Cannot select subfield of nominal field eth.type.
-
-eth.type => Explicit `!= 0' is required for inequality test of multibit field against 0.
-
-!(!(vlan.pcp)) => Explicit `!= 0' is required for inequality test of multibit field against 0.
-
-123 => Syntax error at end of input expecting relational operator.
-
-123 x => Syntax error at `x' expecting relational operator.
-
-{1, "eth0"} => Syntax error at `"eth0"' expecting integer.
-
-eth.type == xyzzy => Syntax error at `xyzzy' expecting constant.
-
-(1 x) => Syntax error at `x' expecting `)'.
-
-!0x800 != eth.type => Missing parentheses around operand of !.
-
-eth.type == 0x800 || eth.type == 0x86dd && ip.proto == 17 => && and || must be parenthesized when used together.
-
-eth.dst == {} => Syntax error at `}' expecting constant.
-
-eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff => Only == and != operators may be used with masked constants.  Consider using subfields instead (e.g. eth.src[0..15] > 0x1111 in place of eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff).
-
-ip4.src == ::1 => 128-bit constant is not compatible with 32-bit field ip4.src.
-
-1 == eth.type == 2 => Range expressions must have the form `x < field < y' or `x > field > y', with each `<' optionally replaced by `<=' or `>' by `>=').
-
-eth.dst[40] x => Syntax error at `x' expecting end of input.
-
-ip4.src == {1.2.3.4, $set1, $unknownset} => Syntax error at `$unknownset' expecting address set name.
-eth.src == {$set3, badmac, 00:00:00:00:00:01} => Syntax error at `badmac' expecting constant.
-
-((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) => Parentheses nested too deeply.
-
-ct_label > $set4 => Only == and != operators may be used to compare a field against an empty value set.
-]])
-sed 's/ =>.*//' test-cases.txt > input.txt
-sed 's/.* => //' test-cases.txt > expout
-AT_CHECK([ovstest test-ovn parse-expr < input.txt], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([ovn -- expression annotation])
-dnl Input precedes =>, expected output follows =>.
-dnl Empty lines and lines starting with # are ignored.
-AT_DATA([test-cases.txt], [[
-ip4.src == 1.2.3.4 => ip4.src == 0x1020304 && eth.type == 0x800
-ip4.src != 1.2.3.4 => ip4.src != 0x1020304 && eth.type == 0x800
-ip.proto == 123 => ip.proto == 0x7b && (eth.type == 0x800 || eth.type == 0x86dd)
-ip.proto == {123, 234} => (ip.proto == 0x7b || ip.proto == 0xea) && (eth.type == 0x800 || eth.type == 0x86dd)
-ip4.src == 1.2.3.4 && ip4.dst == 5.6.7.8 => ip4.src == 0x1020304 && eth.type == 0x800 && ip4.dst == 0x5060708 && eth.type == 0x800
-
-# Nested expressions over a single symbol should be annotated with symbol's
-# prerequisites only once, at the top level.
-tcp.dst == 1 || (tcp.dst >= 2 && tcp.dst <= 3) => (tcp.dst == 0x1 || (tcp.dst >= 0x2 && tcp.dst <= 0x3)) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-
-ip => eth.type == 0x800 || eth.type == 0x86dd
-ip == 1 => eth.type == 0x800 || eth.type == 0x86dd
-ip[0] == 1 => eth.type == 0x800 || eth.type == 0x86dd
-ip > 0 => Only == and != operators may be used with nominal field ip.
-!ip => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'.
-ip == 0 => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'.
-
-vlan.present => vlan.tci[12]
-!vlan.present => !vlan.tci[12]
-
-!vlan.pcp => vlan.tci[13..15] == 0 && vlan.tci[12]
-vlan.pcp == 1 && vlan.vid == 2 => vlan.tci[13..15] == 0x1 && vlan.tci[12] && vlan.tci[0..11] == 0x2 && vlan.tci[12]
-!reg0 && !reg1 && !reg2 && !reg3 => xxreg0[96..127] == 0 && xxreg0[64..95] == 0 && xxreg0[32..63] == 0 && xxreg0[0..31] == 0
-
-ip.first_frag => ip.frag[0] && (eth.type == 0x800 || eth.type == 0x86dd) && (!ip.frag[1] || (eth.type != 0x800 && eth.type != 0x86dd))
-!ip.first_frag => !ip.frag[0] || (eth.type != 0x800 && eth.type != 0x86dd) || (ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd))
-ip.later_frag => ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd)
-
-bad_prereq != 0 => Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name.
-self_recurse != 0 => Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'.
-mutual_recurse_1 != 0 => Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_1'.
-mutual_recurse_2 != 0 => Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_2'.
-]])
-sed 's/ =>.*//' test-cases.txt > input.txt
-sed 's/.* => //' test-cases.txt > expout
-AT_CHECK([ovstest test-ovn annotate-expr < input.txt], [0], [expout])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 1-term expression conversion])
-AT_CHECK([ovstest test-ovn exhaustive --operation=convert 1], [0],
-  [Tested converting all 1-terminal expressions with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 2 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2-term expression conversion])
-AT_CHECK([ovstest test-ovn exhaustive --operation=convert 2], [0],
-  [Tested converting 578 expressions of 2 terminals with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 2 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 3-term expression conversion])
-AT_CHECK([ovstest test-ovn exhaustive --operation=convert --bits=2 3], [0],
-  [Tested converting 67410 expressions of 3 terminals with 2 numeric vars (each 2 bits) in terms of operators == != < <= > >= and 2 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 3-term numeric expression simplification])
-AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=2 --svars=0 3], [0],
-  [Tested simplifying 490770 expressions of 3 terminals with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >=.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term string expression simplification])
-AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=0 --svars=4 4], [0],
-  [Tested simplifying 21978 expressions of 4 terminals with 4 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 3-term mixed expression simplification])
-AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=1 --svars=1 3], [0],
-  [Tested simplifying 127890 expressions of 3 terminals with 1 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 1 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- simplification special cases])
-simplify() {
-    echo "$1" | ovstest test-ovn simplify-expr
-}
-AT_CHECK([simplify 'eth.dst == 0/0'], [0], [1
-])
-AT_CHECK([simplify 'eth.dst != 0/0'], [0], [0
-])
-AT_CHECK([simplify 'tcp.dst >= 0'], [0],
-    [ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-])
-AT_CHECK([simplify 'tcp.dst <= 65535'], [0],
-    [ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-])
-AT_CHECK([simplify 'tcp.dst > 0'], [0],
-    [[(tcp.dst[0] || tcp.dst[1] || tcp.dst[2] || tcp.dst[3] || tcp.dst[4] || tcp.dst[5] || tcp.dst[6] || tcp.dst[7] || tcp.dst[8] || tcp.dst[9] || tcp.dst[10] || tcp.dst[11] || tcp.dst[12] || tcp.dst[13] || tcp.dst[14] || tcp.dst[15]) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-]])
-AT_CHECK([simplify 'tcp.dst < 65535'], [0],
-    [[(!tcp.dst[0] || !tcp.dst[1] || !tcp.dst[2] || !tcp.dst[3] || !tcp.dst[4] || !tcp.dst[5] || !tcp.dst[6] || !tcp.dst[7] || !tcp.dst[8] || !tcp.dst[9] || !tcp.dst[10] || !tcp.dst[11] || !tcp.dst[12] || !tcp.dst[13] || !tcp.dst[14] || !tcp.dst[15]) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-]])
-AT_CLEANUP
-
-AT_SETUP([ovn -- is_chassis_resident simplification])
-simplify() {
-    echo "$1" | ovstest test-ovn simplify-expr
-}
-AT_CHECK([simplify 'is_chassis_resident("eth1")'], [0], [1
-])
-AT_CHECK([simplify 'is_chassis_resident("eth2")'], [0], [0
-])
-AT_CHECK([simplify '!is_chassis_resident("eth1")'], [0], [0
-])
-AT_CHECK([simplify '!is_chassis_resident("eth2")'], [0], [1
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term numeric expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=3 --svars=0 --bits=1 4], [0],
-  [Tested normalizing 1874026 expressions of 4 terminals with 3 numeric vars (each 1 bits) in terms of operators == != < <= > >=.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term string expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=0 --svars=3 --bits=1 4], [0],
-  [Tested normalizing 11242 expressions of 4 terminals with 3 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term mixed expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=1 --bits=1 --svars=2 4], [0],
-  [Tested normalizing 175978 expressions of 4 terminals with 1 numeric vars (each 1 bits) in terms of operators == != < <= > >= and 2 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 5-term numeric expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=3 --svars=0 --bits=1 --relops='==' 5], [0],
-  [Tested normalizing 1317600 expressions of 5 terminals with 3 numeric vars (each 1 bits) in terms of operators ==.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 5-term string expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=0 --svars=3 --bits=1 --relops='==' 5], [0],
-  [Tested normalizing 368550 expressions of 5 terminals with 3 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 5-term mixed expression normalization])
-AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=1 --svars=1 --bits=1 --relops='==' 5], [0],
-  [Tested normalizing 216000 expressions of 5 terminals with 1 numeric vars (each 1 bits) in terms of operators == and 1 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term numeric expressions to flows])
-AT_KEYWORDS([expression])
-AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=2 --svars=0 --bits=2 --relops='==' 4], [0],
-  [Tested converting to flows 175978 expressions of 4 terminals with 2 numeric vars (each 2 bits) in terms of operators ==.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term string expressions to flows])
-AT_KEYWORDS([expression])
-AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=0 --svars=4 4], [0],
-  [Tested converting to flows 21978 expressions of 4 terminals with 4 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4-term mixed expressions to flows])
-AT_KEYWORDS([expression])
-AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=1 --bits=2 --svars=1 --relops='==' 4], [0],
-  [Tested converting to flows 48312 expressions of 4 terminals with 1 numeric vars (each 2 bits) in terms of operators == and 1 string vars.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 3-term numeric expressions to flows])
-AT_KEYWORDS([expression])
-AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=3 --svars=0 --bits=3 --relops='==' 3], [0],
-  [Tested converting to flows 41328 expressions of 3 terminals with 3 numeric vars (each 3 bits) in terms of operators ==.
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- converting expressions to flows -- string fields])
-AT_KEYWORDS([expression])
-expr_to_flow () {
-    echo "$1" | ovstest test-ovn expr-to-flows | sort
-}
-AT_CHECK([expr_to_flow 'inport == "eth0"'], [0], [reg14=0x5
-])
-AT_CHECK([expr_to_flow 'inport == "eth1"'], [0], [reg14=0x6
-])
-AT_CHECK([expr_to_flow 'inport == "eth2"'], [0], [(no flows)
-])
-AT_CHECK([expr_to_flow 'inport == "eth0" && ip'], [0], [dnl
-ip,reg14=0x5
-ipv6,reg14=0x5
-])
-AT_CHECK([expr_to_flow 'inport == "eth1" && ip'], [0], [dnl
-ip,reg14=0x6
-ipv6,reg14=0x6
-])
-AT_CHECK([expr_to_flow 'inport == "eth2" && ip'], [0], [(no flows)
-])
-AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2", "LOCAL"}'], [0],
-[reg14=0x5
-reg14=0x6
-reg14=0xfffe
-])
-AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2"} && ip'], [0], [dnl
-ip,reg14=0x5
-ip,reg14=0x6
-ipv6,reg14=0x5
-ipv6,reg14=0x6
-])
-AT_CHECK([expr_to_flow 'inport == "eth0" && inport == "eth1"'], [0], [dnl
-(no flows)
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- converting expressions to flows -- address sets])
-AT_KEYWORDS([expression])
-expr_to_flow () {
-    echo "$1" | ovstest test-ovn expr-to-flows | sort
-}
-AT_CHECK([expr_to_flow 'ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3}'], [0], [dnl
-ip,nw_src=10.0.0.1
-ip,nw_src=10.0.0.2
-ip,nw_src=10.0.0.3
-])
-AT_CHECK([expr_to_flow 'ip4.src == $set1'], [0], [dnl
-ip,nw_src=10.0.0.1
-ip,nw_src=10.0.0.2
-ip,nw_src=10.0.0.3
-])
-AT_CHECK([expr_to_flow 'ip4.src == {1.2.3.4, $set1}'], [0], [dnl
-ip,nw_src=1.2.3.4
-ip,nw_src=10.0.0.1
-ip,nw_src=10.0.0.2
-ip,nw_src=10.0.0.3
-])
-AT_CHECK([expr_to_flow 'ip4.src == {1.2.0.0/20, 5.5.5.0/24, $set1}'], [0], [dnl
-ip,nw_src=1.2.0.0/20
-ip,nw_src=10.0.0.1
-ip,nw_src=10.0.0.2
-ip,nw_src=10.0.0.3
-ip,nw_src=5.5.5.0/24
-])
-AT_CHECK([expr_to_flow 'ip6.src == {::1, ::2, ::3}'], [0], [dnl
-ipv6,ipv6_src=::1
-ipv6,ipv6_src=::2
-ipv6,ipv6_src=::3
-])
-AT_CHECK([expr_to_flow 'ip6.src == {::1, $set2, ::4}'], [0], [dnl
-ipv6,ipv6_src=::1
-ipv6,ipv6_src=::2
-ipv6,ipv6_src=::3
-ipv6,ipv6_src=::4
-])
-AT_CHECK([expr_to_flow 'eth.src == {00:00:00:00:00:01, 00:00:00:00:00:02, 00:00:00:00:00:03}'], [0], [dnl
-dl_src=00:00:00:00:00:01
-dl_src=00:00:00:00:00:02
-dl_src=00:00:00:00:00:03
-])
-AT_CHECK([expr_to_flow 'eth.src == {$set3}'], [0], [dnl
-dl_src=00:00:00:00:00:01
-dl_src=00:00:00:00:00:02
-dl_src=00:00:00:00:00:03
-])
-AT_CHECK([expr_to_flow 'eth.src == {00:00:00:00:00:01, $set3, ba:be:be:ef:de:ad, $set3}'], [0], [dnl
-dl_src=00:00:00:00:00:01
-dl_src=00:00:00:00:00:02
-dl_src=00:00:00:00:00:03
-dl_src=ba:be:be:ef:de:ad
-])
-AT_CHECK([expr_to_flow 'ip4.src == {$set4}'], [0], [dnl
-(no flows)
-])
-AT_CHECK([expr_to_flow 'ip4.src == {1.2.3.4, $set4}'], [0], [dnl
-ip,nw_src=1.2.3.4
-])
-AT_CHECK([expr_to_flow 'ip4.src == 1.2.3.4 || ip4.src == {$set4}'], [0], [dnl
-ip,nw_src=1.2.3.4
-])
-AT_CHECK([expr_to_flow 'ip4.src != {$set4}'], [0], [dnl
-
-])
-AT_CHECK([expr_to_flow 'ip4.src != {1.0.0.0/8, $set4}'], [0], [dnl
-ip,nw_src=0.0.0.0/1.0.0.0
-ip,nw_src=128.0.0.0/1
-ip,nw_src=16.0.0.0/16.0.0.0
-ip,nw_src=2.0.0.0/2.0.0.0
-ip,nw_src=32.0.0.0/32.0.0.0
-ip,nw_src=4.0.0.0/4.0.0.0
-ip,nw_src=64.0.0.0/64.0.0.0
-ip,nw_src=8.0.0.0/8.0.0.0
-])
-AT_CHECK([expr_to_flow 'ip4.src != 1.0.0.0/8 && ip4.src != {$set4}'], [0], [dnl
-ip,nw_src=0.0.0.0/1.0.0.0
-ip,nw_src=128.0.0.0/1
-ip,nw_src=16.0.0.0/16.0.0.0
-ip,nw_src=2.0.0.0/2.0.0.0
-ip,nw_src=32.0.0.0/32.0.0.0
-ip,nw_src=4.0.0.0/4.0.0.0
-ip,nw_src=64.0.0.0/64.0.0.0
-ip,nw_src=8.0.0.0/8.0.0.0
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- converting expressions to flows -- port groups])
-AT_KEYWORDS([expression])
-expr_to_flow () {
-    echo "$1" | ovstest test-ovn expr-to-flows | sort
-}
-AT_CHECK([expr_to_flow 'outport == @pg1'], [0], [dnl
-reg15=0x11
-reg15=0x12
-reg15=0x13
-])
-AT_CHECK([expr_to_flow 'outport == {@pg_empty}'], [0], [dnl
-(no flows)
-])
-AT_CHECK([expr_to_flow 'outport == {"lsp1", @pg_empty}'], [0], [dnl
-reg15=0x11
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- converting expressions to flows -- conjunction])
-AT_KEYWORDS([conjunction])
-expr_to_flow () {
-    echo "$1" | ovstest test-ovn expr-to-flows | sort
-}
-
-lflow="ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \
-ip4.dst == {20.0.0.1, 20.0.0.2, 20.0.0.3}"
-AT_CHECK([expr_to_flow "$lflow"], [0], [dnl
-conj_id=1,ip
-ip,nw_dst=20.0.0.1: conjunction(1, 0/2)
-ip,nw_dst=20.0.0.2: conjunction(1, 0/2)
-ip,nw_dst=20.0.0.3: conjunction(1, 0/2)
-ip,nw_src=10.0.0.1: conjunction(1, 1/2)
-ip,nw_src=10.0.0.2: conjunction(1, 1/2)
-ip,nw_src=10.0.0.3: conjunction(1, 1/2)
-])
-
-lflow="ip && (!ct.est || (ct.est && ct_label.blocked == 1))"
-AT_CHECK([expr_to_flow "$lflow"], [0], [dnl
-ct_state=+est+trk,ct_label=0x1/0x1,ip
-ct_state=+est+trk,ct_label=0x1/0x1,ipv6
-ct_state=-est+trk,ip
-ct_state=-est+trk,ipv6
-])
-
-lflow="ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \
-ip4.dst == {20.0.0.1, 20.0.0.2}"
-AT_CHECK([expr_to_flow "$lflow"], [0], [dnl
-conj_id=1,ip
-ip,nw_dst=20.0.0.1: conjunction(1, 0/2)
-ip,nw_dst=20.0.0.2: conjunction(1, 0/2)
-ip,nw_src=10.0.0.1: conjunction(1, 1/2)
-ip,nw_src=10.0.0.2: conjunction(1, 1/2)
-ip,nw_src=10.0.0.3: conjunction(1, 1/2)
-])
-
-lflow="ip4 && ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \
-ip4.dst == {20.0.0.1, 20.0.0.2, 20.0.0.3} && \
-tcp.dst >= 1000 && tcp.dst <= 1010"
-
-AT_CHECK([expr_to_flow "$lflow"], [0], [dnl
-conj_id=1,tcp
-tcp,nw_dst=20.0.0.1: conjunction(1, 0/3)
-tcp,nw_dst=20.0.0.2: conjunction(1, 0/3)
-tcp,nw_dst=20.0.0.3: conjunction(1, 0/3)
-tcp,nw_src=10.0.0.1: conjunction(1, 1/3)
-tcp,nw_src=10.0.0.2: conjunction(1, 1/3)
-tcp,nw_src=10.0.0.3: conjunction(1, 1/3)
-tcp,tp_dst=0x3ea/0xfffe: conjunction(1, 2/3)
-tcp,tp_dst=0x3ec/0xfffc: conjunction(1, 2/3)
-tcp,tp_dst=0x3f0/0xfffe: conjunction(1, 2/3)
-tcp,tp_dst=1000: conjunction(1, 2/3)
-tcp,tp_dst=1001: conjunction(1, 2/3)
-tcp,tp_dst=1010: conjunction(1, 2/3)
-])
-
-lflow="ip4 && ip4.src == {10.0.0.4, 10.0.0.5, 10.0.0.6} && \
-((ip4.dst == {20.0.0.4, 20.0.0.7, 20.0.0.8} && tcp.dst >= 1000 && \
-tcp.dst <= 2000 && tcp.src >=1000 && tcp.src <= 2000) \
-|| ip4.dst == 20.0.0.5 || ip4.dst == 20.0.0.6)"
-
-AT_CHECK([expr_to_flow "$lflow"], [0], [dnl
-conj_id=1,tcp
-ip,nw_src=10.0.0.4,nw_dst=20.0.0.5
-ip,nw_src=10.0.0.4,nw_dst=20.0.0.6
-ip,nw_src=10.0.0.5,nw_dst=20.0.0.5
-ip,nw_src=10.0.0.5,nw_dst=20.0.0.6
-ip,nw_src=10.0.0.6,nw_dst=20.0.0.5
-ip,nw_src=10.0.0.6,nw_dst=20.0.0.6
-tcp,nw_dst=20.0.0.4: conjunction(1, 0/4)
-tcp,nw_dst=20.0.0.7: conjunction(1, 0/4)
-tcp,nw_dst=20.0.0.8: conjunction(1, 0/4)
-tcp,nw_src=10.0.0.4: conjunction(1, 1/4)
-tcp,nw_src=10.0.0.5: conjunction(1, 1/4)
-tcp,nw_src=10.0.0.6: conjunction(1, 1/4)
-tcp,tp_dst=0x3ea/0xfffe: conjunction(1, 2/4)
-tcp,tp_dst=0x3ec/0xfffc: conjunction(1, 2/4)
-tcp,tp_dst=0x3f0/0xfff0: conjunction(1, 2/4)
-tcp,tp_dst=0x400/0xfe00: conjunction(1, 2/4)
-tcp,tp_dst=0x600/0xff00: conjunction(1, 2/4)
-tcp,tp_dst=0x700/0xff80: conjunction(1, 2/4)
-tcp,tp_dst=0x780/0xffc0: conjunction(1, 2/4)
-tcp,tp_dst=0x7c0/0xfff0: conjunction(1, 2/4)
-tcp,tp_dst=1000: conjunction(1, 2/4)
-tcp,tp_dst=1001: conjunction(1, 2/4)
-tcp,tp_dst=2000: conjunction(1, 2/4)
-tcp,tp_src=0x3ea/0xfffe: conjunction(1, 3/4)
-tcp,tp_src=0x3ec/0xfffc: conjunction(1, 3/4)
-tcp,tp_src=0x3f0/0xfff0: conjunction(1, 3/4)
-tcp,tp_src=0x400/0xfe00: conjunction(1, 3/4)
-tcp,tp_src=0x600/0xff00: conjunction(1, 3/4)
-tcp,tp_src=0x700/0xff80: conjunction(1, 3/4)
-tcp,tp_src=0x780/0xffc0: conjunction(1, 3/4)
-tcp,tp_src=0x7c0/0xfff0: conjunction(1, 3/4)
-tcp,tp_src=1000: conjunction(1, 3/4)
-tcp,tp_src=1001: conjunction(1, 3/4)
-tcp,tp_src=2000: conjunction(1, 3/4)
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- action parsing])
-dnl Unindented text is input (a set of OVN logical actions).
-dnl Indented text is expected output.
-AT_DATA([test-cases.txt],
-[[# drop
-drop;
-    encodes as drop
-drop; next;
-    Syntax error at `next' expecting end of input.
-next; drop;
-    Syntax error at `drop' expecting action.
-
-# output
-output;
-    encodes as resubmit(,64)
-
-# next
-next;
-    encodes as resubmit(,19)
-next(11);
-    formats as next;
-    encodes as resubmit(,19)
-next(0);
-    encodes as resubmit(,8)
-next(23);
-    encodes as resubmit(,31)
-
-next();
-    Syntax error at `)' expecting "pipeline" or "table".
-next(10;
-    Syntax error at `;' expecting `)'.
-next(24);
-    "next" action cannot advance beyond table 23.
-
-next(table=11);
-    formats as next;
-    encodes as resubmit(,19)
-next(pipeline=ingress);
-    formats as next;
-    encodes as resubmit(,19)
-next(table=11, pipeline=ingress);
-    formats as next;
-    encodes as resubmit(,19)
-next(pipeline=ingress, table=11);
-    formats as next;
-    encodes as resubmit(,19)
-
-next(pipeline=egress);
-    "next" action cannot advance from ingress to egress pipeline (use "output" action instead)
-
-next(table=10);
-    formats as next(10);
-    encodes as resubmit(,18)
-
-# Loading a constant value.
-tcp.dst=80;
-    formats as tcp.dst = 80;
-    encodes as set_field:80->tcp_dst
-    has prereqs ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
-eth.dst[40] = 1;
-    encodes as set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_dst
-vlan.pcp = 2;
-    encodes as set_field:0x4000/0xe000->vlan_tci
-    has prereqs vlan.tci[12]
-vlan.tci[13..15] = 2;
-    encodes as set_field:0x4000/0xe000->vlan_tci
-inport = "";
-    encodes as set_field:0->reg14
-ip.ttl=4;
-    formats as ip.ttl = 4;
-    encodes as set_field:4->nw_ttl
-    has prereqs eth.type == 0x800 || eth.type == 0x86dd
-outport="eth0"; next; outport="LOCAL"; next;
-    formats as outport = "eth0"; next; outport = "LOCAL"; next;
-    encodes as set_field:0x5->reg15,resubmit(,19),set_field:0xfffe->reg15,resubmit(,19)
-
-inport[1] = 1;
-    Cannot select subfield of string field inport.
-ip.proto[1] = 1;
-    Cannot select subfield of nominal field ip.proto.
-eth.dst[40] == 1;
-    Syntax error at `==' expecting `=' or `<->'.
-ip = 1;
-    Predicate symbol ip used where lvalue required.
-ip.proto = 6;
-    Field ip.proto is not modifiable.
-inport = {"a", "b"};
-    Syntax error at `{' expecting constant.
-inport = {};
-    Syntax error at `{' expecting constant.
-bad_prereq = 123;
-    Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name.
-self_recurse = 123;
-    Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'.
-vlan.present = 0;
-    Predicate symbol vlan.present used where lvalue required.
-
-# Moving one field into another.
-reg0=reg1;
-    formats as reg0 = reg1;
-    encodes as move:NXM_NX_XXREG0[64..95]->NXM_NX_XXREG0[96..127]
-vlan.pcp = reg0[0..2];
-    encodes as move:NXM_NX_XXREG0[96..98]->NXM_OF_VLAN_TCI[13..15]
-    has prereqs vlan.tci[12]
-reg0[10] = vlan.pcp[1];
-    encodes as move:NXM_OF_VLAN_TCI[14]->NXM_NX_XXREG0[106]
-    has prereqs vlan.tci[12]
-outport = inport;
-    encodes as move:NXM_NX_REG14[]->NXM_NX_REG15[]
-
-reg0[0] = vlan.present;
-    Predicate symbol vlan.present used where lvalue required.
-reg0 = reg1[0..10];
-    Can't assign 11-bit value to 32-bit destination.
-inport = reg0;
-    Can't assign integer field (reg0) to string field (inport).
-inport = big_string;
-    String fields inport and big_string are incompatible for assignment.
-ip.proto = reg0[0..7];
-    Field ip.proto is not modifiable.
-
-# Exchanging fields.
-reg0 <-> reg1;
-    encodes as push:NXM_NX_XXREG0[64..95],push:NXM_NX_XXREG0[96..127],pop:NXM_NX_XXREG0[64..95],pop:NXM_NX_XXREG0[96..127]
-vlan.pcp <-> reg0[0..2];
-    encodes as push:NXM_NX_XXREG0[96..98],push:NXM_OF_VLAN_TCI[13..15],pop:NXM_NX_XXREG0[96..98],pop:NXM_OF_VLAN_TCI[13..15]
-    has prereqs vlan.tci[12]
-reg0[10] <-> vlan.pcp[1];
-    encodes as push:NXM_OF_VLAN_TCI[14],push:NXM_NX_XXREG0[106],pop:NXM_OF_VLAN_TCI[14],pop:NXM_NX_XXREG0[106]
-    has prereqs vlan.tci[12]
-outport <-> inport;
-    encodes as push:NXM_NX_REG14[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_NX_REG15[]
-
-reg0[0] <-> vlan.present;
-    Predicate symbol vlan.present used where lvalue required.
-reg0 <-> reg1[0..10];
-    Can't exchange 32-bit field with 11-bit field.
-inport <-> reg0;
-    Can't exchange string field (inport) with integer field (reg0).
-inport <-> big_string;
-    String fields inport and big_string are incompatible for exchange.
-ip.proto <-> reg0[0..7];
-    Field ip.proto is not modifiable.
-reg0[0..7] <-> ip.proto;
-    Field ip.proto is not modifiable.
-
-# TTL decrement.
-ip.ttl--;
-    encodes as dec_ttl
-    has prereqs ip
-ip.ttl
-    Syntax error at end of input expecting `--'.
-
-# load balancing.
-ct_lb;
-    encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
-    has prereqs ip
-ct_lb();
-    formats as ct_lb;
-    encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
-    has prereqs ip
-ct_lb(192.168.1.2:80, 192.168.1.3:80);
-    encodes as group:1
-    has prereqs ip
-ct_lb(192.168.1.2, 192.168.1.3, );
-    formats as ct_lb(192.168.1.2, 192.168.1.3);
-    encodes as group:2
-    has prereqs ip
-ct_lb(fd0f::2, fd0f::3, );
-    formats as ct_lb(fd0f::2, fd0f::3);
-    encodes as group:3
-    has prereqs ip
-
-ct_lb(192.168.1.2:);
-    Syntax error at `)' expecting port number.
-ct_lb(192.168.1.2:123456);
-    Syntax error at `123456' expecting port number.
-ct_lb(foo);
-    Syntax error at `foo' expecting IP address.
-ct_lb([192.168.1.2]);
-    Syntax error at `192.168.1.2' expecting IPv6 address.
-
-# ct_next
-ct_next;
-    encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
-    has prereqs ip
-
-# ct_commit
-ct_commit;
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15])
-    has prereqs ip
-ct_commit();
-    formats as ct_commit;
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15])
-    has prereqs ip
-ct_commit(ct_mark=1);
-    formats as ct_commit(ct_mark=0x1);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark))
-    has prereqs ip
-ct_commit(ct_mark=1/1);
-    formats as ct_commit(ct_mark=0x1/0x1);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_mark))
-    has prereqs ip
-ct_commit(ct_label=1);
-    formats as ct_commit(ct_label=0x1);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_label))
-    has prereqs ip
-ct_commit(ct_label=1/1);
-    formats as ct_commit(ct_label=0x1/0x1);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_label))
-    has prereqs ip
-ct_commit(ct_mark=1, ct_label=2);
-    formats as ct_commit(ct_mark=0x1, ct_label=0x2);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark,set_field:0x2->ct_label))
-    has prereqs ip
-
-ct_commit(ct_label=0x01020304050607080910111213141516);
-    formats as ct_commit(ct_label=0x1020304050607080910111213141516);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1020304050607080910111213141516->ct_label))
-    has prereqs ip
-ct_commit(ct_label=0x181716151413121110090807060504030201);
-    formats as ct_commit(ct_label=0x16151413121110090807060504030201);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x16151413121110090807060504030201->ct_label))
-    has prereqs ip
-ct_commit(ct_label=0x1000000000000000000000000000000/0x1000000000000000000000000000000);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1000000000000000000000000000000/0x1000000000000000000000000000000->ct_label))
-    has prereqs ip
-ct_commit(ct_label=18446744073709551615);
-    formats as ct_commit(ct_label=0xffffffffffffffff);
-    encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0xffffffffffffffff->ct_label))
-    has prereqs ip
-ct_commit(ct_label=18446744073709551616);
-    Decimal constants must be less than 2**64.
-
-# ct_dnat
-ct_dnat;
-    encodes as ct(table=19,zone=NXM_NX_REG11[0..15],nat)
-    has prereqs ip
-ct_dnat(192.168.1.2);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2))
-    has prereqs ip
-
-ct_dnat(192.168.1.2, 192.168.1.3);
-    Syntax error at `,' expecting `)'.
-ct_dnat(foo);
-    Syntax error at `foo' expecting IPv4 address.
-ct_dnat(foo, bar);
-    Syntax error at `foo' expecting IPv4 address.
-ct_dnat();
-    Syntax error at `)' expecting IPv4 address.
-
-# ct_snat
-ct_snat;
-    encodes as ct(table=19,zone=NXM_NX_REG12[0..15],nat)
-    has prereqs ip
-ct_snat(192.168.1.2);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=192.168.1.2))
-    has prereqs ip
-
-ct_snat(192.168.1.2, 192.168.1.3);
-    Syntax error at `,' expecting `)'.
-ct_snat(foo);
-    Syntax error at `foo' expecting IPv4 address.
-ct_snat(foo, bar);
-    Syntax error at `foo' expecting IPv4 address.
-ct_snat();
-    Syntax error at `)' expecting IPv4 address.
-
-# ct_clear
-ct_clear;
-    encodes as ct_clear
-
-# clone
-clone { ip4.dst = 255.255.255.255; output; }; next;
-    encodes as clone(set_field:255.255.255.255->ip_dst,resubmit(,64)),resubmit(,19)
-    has prereqs eth.type == 0x800
-
-# arp
-arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-    encodes as controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip4
-arp { };
-    formats as arp { drop; };
-    encodes as controller(userdata=00.00.00.00.00.00.00.00)
-    has prereqs ip4
-
-# get_arp
-get_arp(outport, ip4.dst);
-    encodes as push:NXM_NX_REG0[],push:NXM_OF_IP_DST[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[]
-    has prereqs eth.type == 0x800
-get_arp(inport, reg0);
-    encodes as push:NXM_NX_REG15[],push:NXM_NX_REG0[],push:NXM_NX_XXREG0[96..127],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[],pop:NXM_NX_REG15[]
-
-get_arp;
-    Syntax error at `;' expecting `('.
-get_arp();
-    Syntax error at `)' expecting field name.
-get_arp(inport);
-    Syntax error at `)' expecting `,'.
-get_arp(inport ip4.dst);
-    Syntax error at `ip4.dst' expecting `,'.
-get_arp(inport, ip4.dst;
-    Syntax error at `;' expecting `)'.
-get_arp(inport, eth.dst);
-    Cannot use 48-bit field eth.dst[0..47] where 32-bit field is required.
-get_arp(inport, outport);
-    Cannot use string field outport where numeric field is required.
-get_arp(reg0, ip4.dst);
-    Cannot use numeric field reg0 where string field is required.
-
-# put_arp
-put_arp(inport, arp.spa, arp.sha);
-    encodes as push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[]
-    has prereqs eth.type == 0x806 && eth.type == 0x806
-
-# put_dhcp_opts
-reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
-reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot");
-    formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot");
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause)
-reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5});
-    formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5});
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05,pause)
-
-reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
-    Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
-reg1[0] = put_dhcp_opts();
-    put_dhcp_opts requires offerip to be specified.
-reg1[0] = put_dhcp_opts(x = 1.2.3.4, router = 10.0.0.1);
-    Syntax error at `x' expecting DHCPv4 option name.
-reg1[0] = put_dhcp_opts(router = 10.0.0.1);
-    put_dhcp_opts requires offerip to be specified.
-reg1[0] = put_dhcp_opts(offerip=1.2.3.4, "hi");
-    Syntax error at `"hi"'.
-reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy);
-    Syntax error at `xyzzy' expecting DHCPv4 option name.
-reg1[0] = put_dhcp_opts(offerip="xyzzy");
-    DHCPv4 option offerip requires numeric value.
-reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain_name=1.2.3.4);
-    DHCPv4 option domain_name requires string value.
-
-# nd_ns
-nd_ns { nd.target = xxreg0; output; };
-    encodes as controller(userdata=00.00.00.09.00.00.00.00.ff.ff.00.18.00.00.23.20.00.06.00.80.00.00.00.00.00.01.de.10.00.01.2e.10.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
-    has prereqs ip6
-
-nd_ns { };
-    formats as nd_ns { drop; };
-    encodes as controller(userdata=00.00.00.09.00.00.00.00)
-    has prereqs ip6
-
-# nd_na
-nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; };
-    formats as nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; };
-    encodes as controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
-    has prereqs nd_ns
-# nd_na_router
-nd_na_router { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; };
-    formats as nd_na_router { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; };
-    encodes as controller(userdata=00.00.00.0c.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
-    has prereqs nd_ns
-
-# get_nd
-get_nd(outport, ip6.dst);
-    encodes as push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_XXREG0[]
-    has prereqs eth.type == 0x86dd
-get_nd(inport, xxreg0);
-    encodes as push:NXM_NX_REG15[],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG15[]
-get_nd;
-    Syntax error at `;' expecting `('.
-get_nd();
-    Syntax error at `)' expecting field name.
-get_nd(inport);
-    Syntax error at `)' expecting `,'.
-get_nd(inport ip6.dst);
-    Syntax error at `ip6.dst' expecting `,'.
-get_nd(inport, ip6.dst;
-    Syntax error at `;' expecting `)'.
-get_nd(inport, eth.dst);
-    Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required.
-get_nd(inport, outport);
-    Cannot use string field outport where numeric field is required.
-get_nd(xxreg0, ip6.dst);
-    Cannot use numeric field xxreg0 where string field is required.
-
-# put_nd
-put_nd(inport, nd.target, nd.sll);
-    encodes as push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[]
-    has prereqs (icmp6.type == 0x87 || icmp6.type == 0x88) && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
-
-# put_dhcpv6_opts
-reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02);
-    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.05.00.10.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.00.02.00.06.00.00.00.00.10.02,pause)
-reg1[0] = put_dhcpv6_opts();
-    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40,pause)
-reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2});
-    formats as reg1[0] = put_dhcpv6_opts(dns_server = {ae70::1, ae70::2});
-    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.17.00.20.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause)
-reg1[0] = put_dhcpv6_opts(server_id=12:34:56:78:9a:bc, dns_server={ae70::1,ae89::2});
-    formats as reg1[0] = put_dhcpv6_opts(server_id = 12:34:56:78:9a:bc, dns_server = {ae70::1, ae89::2});
-    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.02.00.06.12.34.56.78.9a.bc.00.17.00.20.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.89.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause)
-reg1[0] = put_dhcpv6_opts(domain_search = "ovn.org");
-    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.18.00.07.6f.76.6e.2e.6f.72.67,pause)
-reg1[0] = put_dhcpv6_opts(x = 1.2.3.4);
-    Syntax error at `x' expecting DHCPv6 option name.
-reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, "hi");
-    Syntax error at `"hi"'.
-reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, xyzzy);
-    Syntax error at `xyzzy' expecting DHCPv6 option name.
-reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4");
-    DHCPv6 option ia_addr requires numeric value.
-reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1);
-    DHCPv6 option domain_search requires string value.
-
-# set_queue
-set_queue(0);
-    encodes as set_queue:0
-set_queue(61440);
-    encodes as set_queue:61440
-set_queue(65535);
-    Queue ID 65535 for set_queue is not in valid range 0 to 61440.
-
-# dns_lookup
-reg1[0] = dns_lookup();
-    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.01.de.10.00.00.00.40,pause)
-    has prereqs udp
-reg1[0] = dns_lookup("foo");
-    dns_lookup doesn't take any parameters
-
-# set_meter
-set_meter(0);
-    Rate 0 for set_meter is not in valid.
-set_meter(1);
-    encodes as meter:1
-set_meter(100, 1000);
-    encodes as meter:2
-set_meter(100, 1000, );
-    Syntax error at `,' expecting `)'.
-set_meter(4294967295, 4294967295);
-    encodes as meter:3
-
-# log
-log(verdict=allow, severity=warning);
-    encodes as controller(userdata=00.00.00.07.00.00.00.00.00.04)
-log(name="test1", verdict=drop, severity=info);
-    encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06.74.65.73.74.31)
-log(verdict=drop, severity=info, meter="meter1");
-    encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06,meter_id=4)
-log(name="test1", verdict=drop, severity=info, meter="meter1");
-    encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06.74.65.73.74.31,meter_id=4)
-log(verdict=drop);
-    formats as log(verdict=drop, severity=info);
-    encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06)
-log(verdict=bad_verdict, severity=info);
-    Syntax error at `bad_verdict' unknown verdict.
-log(verdict=drop, severity=bad_severity);
-    Syntax error at `bad_severity' unknown severity.
-log(severity=notice);
-    Syntax error at `;' expecting verdict.
-
-# put_nd_ra_opts
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05);
-    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.00.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.dc.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.05,pause)
-    has prereqs ip6
-reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", slla = ae:01:02:03:04:10, mtu = 1450);
-    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10.05.01.00.00.00.00.05.aa,pause)
-    has prereqs ip6
-reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:06, prefix = aef0::/64);
-    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.40.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.06.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00,pause)
-    has prereqs ip6
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64);
-    slla option not present
-reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10);
-    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.aa.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.be.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10,pause)
-    has prereqs ip6
-reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10);
-    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.aa.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.be.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10,pause)
-    has prereqs ip6
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10);
-    prefix option needs to be set when address mode is slaac/dhcpv6_stateless.
-reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:10);
-    prefix option needs to be set when address mode is slaac/dhcpv6_stateless.
-reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10);
-    Syntax error at `dhcpv6_stateless' expecting constant.
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10);
-    Invalid value for "prefix" option
-reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = ae:01:02:03:04:10);
-    Invalid value for "addr_mode" option
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = ae:01:02:03:04:10);
-    IPv6 ND RA option mtu requires numeric value.
-reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = ae:01:02:03:04:10);
-    Invalid value for "mtu" option
-
-# icmp4
-icmp4 { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-    encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip4
-
-icmp4 { };
-    formats as icmp4 { drop; };
-    encodes as controller(userdata=00.00.00.0a.00.00.00.00)
-    has prereqs ip4
-
-# icmp4 with icmp4.frag_mtu
-icmp4 { eth.dst = ff:ff:ff:ff:ff:ff; icmp4.frag_mtu = 1500; output; }; output;
-    encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.28.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.0e.00.00.00.0d.00.00.00.00.05.dc.00.00.00.04.00.04.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip4
-
-# icmp4_error
-icmp4_error { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-    encodes as controller(userdata=00.00.00.0e.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip4
-
-icmp4_error { };
-    formats as icmp4_error { drop; };
-    encodes as controller(userdata=00.00.00.0e.00.00.00.00)
-    has prereqs ip4
-
-# icmp4_error with icmp4.frag_mtu
-icmp4_error { eth.dst = ff:ff:ff:ff:ff:ff; icmp4.frag_mtu = 1500; output; }; output;
-    encodes as controller(userdata=00.00.00.0e.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.28.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.0e.00.00.00.0d.00.00.00.00.05.dc.00.00.00.04.00.04.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip4
-
-icmp4.frag_mtu = 1500;
-    encodes as controller(userdata=00.00.00.0d.00.00.00.00.05.dc,pause)
-
-# icmp6
-icmp6 { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-    encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs ip6
-
-icmp6 { };
-    formats as icmp6 { drop; };
-    encodes as controller(userdata=00.00.00.0a.00.00.00.00)
-    has prereqs ip6
-
-# tcp_reset
-tcp_reset { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-    encodes as controller(userdata=00.00.00.0b.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
-    has prereqs tcp
-
-tcp_reset { };
-    formats as tcp_reset { drop; };
-    encodes as controller(userdata=00.00.00.0b.00.00.00.00)
-    has prereqs tcp
-
-# trigger_event
-trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
-    encodes as controller(userdata=00.00.00.0f.00.00.00.00.00.00.00.00.00.01.00.0b.31.30.2e.30.2e.30.2e.31.3a.38.30.00.02.00.03.74.63.70.00.03.00.24.31.32.33.34.35.36.37.38.2d.61.62.63.64.2d.39.38.37.36.2d.66.65.64.63.2d.31.31.31.31.39.66.38.65.37.64.36.63)
-
-# Testing invalid vip results in extra error messages from socket-util.c
-trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "sctp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
-    Load balancer protocol 'sctp' is not 'tcp' or 'udp'
-trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "bacon");
-    Load balancer 'bacon' is not a UUID
-
-# IGMP
-igmp;
-    encodes as controller(userdata=00.00.00.10.00.00.00.00)
-
-# Contradictionary prerequisites (allowed but not useful):
-ip4.src = ip6.src[0..31];
-    encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
-    has prereqs eth.type == 0x800 && eth.type == 0x86dd
-ip4.src <-> ip6.src[0..31];
-    encodes as push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[]
-    has prereqs eth.type == 0x800 && eth.type == 0x86dd
-
-# check_pkt_larger
-reg0[0] = check_pkt_larger(1500);
-    encodes as check_pkt_larger(1500)->NXM_NX_XXREG0[96]
-
-reg0 = check_pkt_larger(1500);
-    Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
-
-reg0 = check_pkt_larger(foo);
-    Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
-
-reg0[0] = check_pkt_larger(foo);
-    Syntax error at `foo' expecting `;'.
-
-# Miscellaneous negative tests.
-;
-    Syntax error at `;'.
-xyzzy;
-    Syntax error at `xyzzy' expecting action.
-next; 123;
-    Syntax error at `123'.
-next; xyzzy;
-    Syntax error at `xyzzy' expecting action.
-next
-    Syntax error at end of input expecting `;'.
-]])
-sed '/^[[ 	]]/d' test-cases.txt > input.txt
-cp test-cases.txt expout
-AT_CHECK([ovstest test-ovn parse-actions < input.txt], [0], [expout])
-AT_CLEANUP
-
-AT_BANNER([OVN end-to-end tests])
-
-# 3 hypervisors, one logical switch, 3 logical ports per hypervisor
-AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV])
-AT_KEYWORDS([ovnarp])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Create hypervisors hv[123].
-# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
-# Add all of the vifs to a single logical switch lsw0.
-# Turn on port security on all the vifs except vif[123]1.
-# Make vif13, vif2[23], vif3[123] destinations for unknown MACs.
-# Add some ACLs for Ethertypes 1234, 1235, 1236.
-ovn-nbctl ls-add lsw0
-net_add n1
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2 3; do
-        ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
-        ovn-nbctl lsp-add lsw0 lp$i$j
-        if test $j = 1; then
-            ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
-        else
-            if test $j = 3; then
-                ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j"
-            else
-                ip_addrs="192.168.0.$i$j"
-            fi
-            ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
-            ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
-        fi
-    done
-done
-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop
-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop
-ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\"
-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop
-
-get_lsp_uuid () {
-    ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }'
-}
-
-ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33`
-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Make sure there is no attempt to adding duplicated flows by ovn-controller
-AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
-AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"])
-AT_FAIL_IF([test -n "`grep duplicate hv3/ovn-controller.log`"])
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv${1%?}
-}
-
-# test_packet INPORT DST SRC ETHTYPE OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        : > $i$j.expected
-    done
-done
-test_packet() {
-    local inport=$1 packet=$2$3$4; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
-    local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $request
-
-    if test X$reply_ha = X; then
-        # Expect to receive the broadcast ARP on the other logical switch ports
-        # if no reply is expected.
-        local i j
-        for i in 1 2 3; do
-            for j in 1 2 3; do
-                if test $i$j != $inport; then
-                    echo $request >> $i$j.expected
-                fi
-            done
-        done
-    else
-        # Expect to receive the reply, if any.
-        local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-        echo $reply >> $inport.expected
-    fi
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send packets between all pairs of source and destination ports:
-#
-# 1. Unicast packets are delivered to exactly one logical switch port
-#    (except that packets destined to their input ports are dropped).
-#
-# 2. Broadcast and multicast are delivered to all logical switch ports
-#    except the input port.
-#
-# 3. When port security is turned on, the switch drops packets from the wrong
-#    MAC address.
-#
-# 4. The switch drops all packets with a VLAN tag.
-#
-# 5. The switch drops all packets with a multicast source address.  (This only
-#    affects behavior when port security is turned off, since otherwise port
-#    security would drop the packet anyway.)
-#
-# 6. The switch delivers packets with an unknown destination to logical
-#    switch ports with "unknown" among their MAC addresses (and port
-#    security disabled).
-#
-# 7. The switch drops unicast packets that violate an ACL.
-#
-# 8. The switch drops multicast and broadcast packets that violate an ACL.
-#
-# 9. OVN generates responses to ARP requests for known IPs, except for
-#    requests from a port for the port's own IP.
-#
-# 10. No response to ARP requests for unknown IPs.
-
-for is in 1 2 3; do
-    for js in 1 2 3; do
-        s=$is$js
-        bcast=
-        unknown=
-        bacl2=
-        bacl3=
-        for id in 1 2 3; do
-            for jd in 1 2 3; do
-                d=$id$jd
-
-                if test $d != $s; then unicast=$d; else unicast=; fi
-                test_packet $s f000000000$d f000000000$s $s$d $unicast     #1
-
-                if test $d != $s && test $js = 1; then
-                    impersonate=$d
-                else
-                    impersonate=
-                fi
-                test_packet $s f000000000$d f00000000055 55$d $impersonate #3
-
-                if test $d != $s && test $s != 11; then acl2=$d; else acl2=; fi
-                if test $d != $s && test $d != 33; then acl3=$d; else acl3=; fi
-                if test $d = $s || (test $js = 1 && test $d = 33); then
-                    # Source of 11, 21, or 31 and dest of 33 should be dropped
-                    # due to the 4th ACL that uses address_set(set1).
-                    acl4=
-                else
-                    acl4=$d
-                fi
-                if test $d = $s || test $d = 22 || test $d = 33; then
-                    # dest of 22 and 33 should be dropped
-                    # due to the 5th ACL that uses port_group(pg1).
-                    acl5=
-                else
-                    acl5=$d
-                fi
-                test_packet $s f000000000$d f000000000$s 1234        #7, acl1
-                test_packet $s f000000000$d f000000000$s 1235 $acl2  #7, acl2
-                test_packet $s f000000000$d f000000000$s 1236 $acl3  #7, acl3
-                test_packet $s f000000000$d f000000000$s 1237 $acl4  #7, acl4
-                test_packet $s f000000000$d f000000000$s 1238 $acl5  #7, acl5
-
-                test_packet $s f000000000$d f00000000055 810000091234      #4
-                test_packet $s f000000000$d 0100000000$s $s$d              #5
-
-                if test $d != $s && test $jd = 1; then
-                    unknown="$unknown $d"
-                fi
-                bcast="$bcast $unicast"
-                bacl2="$bacl2 $acl2"
-                bacl3="$bacl3 $acl3"
-
-                sip=`ip_to_hex 192 168 0 $is$js`
-                tip=`ip_to_hex 192 168 0 $id$jd`
-                tip_unknown=`ip_to_hex 11 11 11 11`
-                if test $d != $s; then
-                    reply_ha=f000000000$d
-                else
-                    reply_ha=
-                fi
-                test_arp $s f000000000$s $sip $tip $reply_ha               #9
-                test_arp $s f000000000$s $sip $tip_unknown                 #10
-
-                if test $jd = 3; then
-                    # lsp[123]3 has an additional ip 192.169.0.[123]3.
-                    tip=`ip_to_hex 192 169 0 $id$jd`
-                    test_arp $s f000000000$s $sip $tip $reply_ha           #9
-                fi
-            done
-        done
-
-        # Broadcast and multicast.
-        test_packet $s ffffffffffff f000000000$s ${s}ff $bcast             #2
-        test_packet $s 010000000000 f000000000$s ${s}ff $bcast             #2
-        if test $js = 1; then
-            bcast_impersonate=$bcast
-        else
-            bcast_impersonate=
-        fi
-        test_packet $s 010000000000 f00000000044 44ff $bcast_impersonate   #3
-
-        test_packet $s f0000000ffff f000000000$s ${s}66 $unknown           #6
-
-        test_packet $s ffffffffffff f000000000$s 1234                #8, acl1
-        test_packet $s ffffffffffff f000000000$s 1235 $bacl2         #8, acl2
-        test_packet $s ffffffffffff f000000000$s 1236 $bacl3         #8, acl3
-        test_packet $s 010000000000 f000000000$s 1234                #8, acl1
-        test_packet $s 010000000000 f000000000$s 1235 $bacl2         #8, acl2
-        test_packet $s 010000000000 f000000000$s 1236 $bacl3         #8, acl3
-    done
-done
-
-# set address for lp13 with invalid characters.
-# lp13 should be configured with only 192.168.0.13.
-ovn-nbctl lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13"
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-sip=`ip_to_hex 192 168 0 11`
-tip=`ip_to_hex 192 168 0 13`
-test_arp 11 f00000000011  $sip $tip f00000000013
-
-tip=`ip_to_hex 192 169 0 13`
-#arp request for 192.169.0.13 should be flooded
-test_arp 11 f00000000011  $sip $tip
-
-# dump information and flows with counters
-ovn-sbctl dump-flows -- list multicast_group
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
-    done
-done
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-# 2 hypervisors, one logical switch, 2 logical ports per hypervisor
-# logical ports bound to chassis encap-ip.
-AT_SETUP([ovn -- 2 HVs, 1 LS, 2 lports/HV])
-AT_KEYWORDS([ovnarp])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Create hypervisors hv[12].
-# Add vif1[12] to hv1, vif2[12] to hv2
-ovn-nbctl ls-add lsw0
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2; do
-        ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
-        ovn-nbctl lsp-add lsw0 lp$i$j
-        ip_addrs="192.168.0.$i$j"
-        ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
-        ovn-nbctl --wait=hv lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
-    done
-done
-
-get_lsp_uuid () {
-    ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }'
-}
-
-# XXX-Check how to pass lp$i1 in AT_CHECK_UNQUOTED, for now just do it
-# explictly
-
-# For Chassis hv1
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp11], [0], [dnl
-encap               : [[]]
-])
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp12], [0], [dnl
-encap               : [[]]
-])
-
-# For Chassis hv2
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp21], [0], [dnl
-encap               : [[]]
-])
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp22], [0], [dnl
-encap               : [[]]
-])
-
-# Bind the ports to the encap-ip
-for i in 1 2; do
-    for j in 1 2; do
-        as hv$i
-        ovs-vsctl set Interface vif$i$j external-ids:encap-ip=192.168.0.$i
-    done
-done
-
-sleep 1
-
-# dump port bindings; since we have vxlan and geneve tunnels, we expect the
-# ports to be bound to geneve tunnels.
-
-# For Chassis 1
-encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv1 type=geneve ip=192.168.0.1`
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp11], [0], [dnl
-encap               : ${encap_rec}
-])
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp12], [0], [dnl
-encap               : ${encap_rec}
-])
-
-# For Chassis 2
-encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv2 type=geneve ip=192.168.0.2`
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp21], [0], [dnl
-encap               : ${encap_rec}
-])
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp22], [0], [dnl
-encap               : ${encap_rec}
-])
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Make sure there is no attempt to adding duplicated flows by ovn-controller
-AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
-AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"])
-AT_FAIL_IF([test -n "`grep duplicate hv3/ovn-controller.log`"])
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv${1%?}
-}
-
-# test_packet INPORT DST SRC ETHTYPE OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11.
-for i in 1 2; do
-    for j in 1 2; do
-        : > $i$j.expected
-    done
-done
-test_packet() {
-    local inport=$1 packet=$2$3$4; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send packets between all pairs of source and destination ports:
-#
-# 1. Unicast packets are delivered to exactly one logical switch port
-#    (except that packets destined to their input ports are dropped).
-
-for is in 1 2; do
-    for js in 1 2; do
-        s=$is$js
-        bcast=
-        unknown=
-        bacl2=
-        bacl3=
-        for id in 1 2 3; do
-            for jd in 1 2 3; do
-                d=$id$jd
-
-                if test $d != $s; then unicast=$d; else unicast=; fi
-                test_packet $s f000000000$d f000000000$s $s$d $unicast     #1
-            done
-        done
-
-    done
-done
-
-# dump information and flows with counters
-ovn-sbctl dump-flows -- list multicast_group
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2; do
-    for j in 1 2; do
-        OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
-    done
-done
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- trace 1 LS, 3 LSPs])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Create a logical switch and some logical ports.
-# Turn on port security on all lports except ls1.
-# Make ls1 a destination for unknown MACs.
-# Add some ACLs for Ethertypes 1234, 1235, 1236.
-ovn-nbctl ls-add lsw0
-ovn-sbctl chassis-add hv0 geneve 127.0.0.1
-for i in 1 2 3; do
-    ovn-nbctl lsp-add lsw0 lp$i
-done
-ovn-nbctl --wait=sb sync
-for i in 1 2 3; do
-    ovn-sbctl lsp-bind lp$i hv0
-    if test $i = 1; then
-        ovn-nbctl lsp-set-addresses lp$i "f0:00:00:00:00:0$i 192.168.0.$i" unknown
-    else
-        if test $i = 3; then
-           ip_addrs="192.168.0.$i fe80::ea2a:eaff:fe28:$i/64 192.169.0.$i"
-        else
-           ip_addrs="192.168.0.$i"
-        fi
-        ovn-nbctl lsp-set-addresses lp$i "f0:00:00:00:00:0$i $ip_addrs"
-        ovn-nbctl lsp-set-port-security lp$i f0:00:00:00:00:0$i
-    fi
-done
-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp1"' drop
-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp3"' drop
-ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:01\",\"f0:00:00:00:00:02\"
-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp3"' drop
-
-ovn-nbctl --wait=sb sync
-on_exit 'kill `cat ovn-trace.pid`'
-ovn-trace --detach --pidfile --no-chdir
-
-# test_packet INPORT DST SRC [-vlan] [-eth TYPE] OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11.
-test_packet() {
-    local inport=$1 eth_dst=$2 eth_src=$3; shift; shift; shift
-    uflow="inport==\"lp$inport\" && eth.dst==$eth_dst && eth.src==$eth_src"
-    while :; do
-        case $1 in # (
-            -vlan) uflow="$uflow && vlan.vid == 1234"; shift ;; # (
-            -eth) uflow="$uflow && eth.type == 0x$2"; shift; shift ;; # (
-            *) break ;;
-        esac
-    done
-    for outport; do
-        echo "output(\"lp$outport\");"
-    done > expout
-
-    AT_CAPTURE_FILE([trace])
-    AT_CHECK([ovs-appctl -t ovn-trace trace --all lsw0 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
-}
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
-
-    local request="inport == \"lp$inport\"
-                   && eth.dst == ff:ff:ff:ff:ff:ff && eth.src == $sha
-                   && arp.op == 1 && arp.sha == $sha && arp.spa == $spa
-                   && arp.tha == ff:ff:ff:ff:ff:ff && arp.tpa == $tpa"
-
-    if test -z "$reply_ha"; then
-        reply=
-        local i
-        for i in 1 2 3; do
-            if test $i != $inport; then
-                reply="${reply}output(\"lp$i\");
-"
-            fi
-        done
-    else
-        reply="\
-eth.dst = $sha;
-eth.src = $reply_ha;
-arp.op = 2;
-arp.tha = $sha;
-arp.sha = $reply_ha;
-arp.tpa = $spa;
-arp.spa = $tpa;
-output(\"lp$inport\");
-"
-    fi
-
-    AT_CAPTURE_FILE([trace])
-    AT_CHECK_UNQUOTED([ovs-appctl -t ovn-trace trace --all lsw0 "$request" | tee trace | sed '1,/Minimal trace/d'], [0], [$reply])
-}
-
-# Send packets between all pairs of source and destination ports:
-#
-# 1. Unicast packets are delivered to exactly one logical switch port
-#    (except that packets destined to their input ports are dropped).
-#
-# 2. Broadcast and multicast are delivered to all logical switch ports
-#    except the input port.
-#
-# 3. When port security is turned on, the switch drops packets from the wrong
-#    MAC address.
-#
-# 4. The switch drops all packets with a VLAN tag.
-#
-# 5. The switch drops all packets with a multicast source address.  (This only
-#    affects behavior when port security is turned off, since otherwise port
-#    security would drop the packet anyway.)
-#
-# 6. The switch delivers packets with an unknown destination to logical
-#    switch ports with "unknown" among their MAC addresses (and port
-#    security disabled).
-#
-# 7. The switch drops unicast packets that violate an ACL.
-#
-# 8. The switch drops multicast and broadcast packets that violate an ACL.
-#
-# 9. OVN generates responses to ARP requests for known IPs, except for
-#    requests from a port for the port's own IP.
-#
-# 10. No response to ARP requests for unknown IPs.
-
-for s in 1 2 3; do
-    bcast=
-    unknown=
-    bacl2=
-    bacl3=
-    for d in 1 2 3; do
-        echo
-        echo "lp$s -> lp$d"
-        if test $d != $s; then unicast=$d; else unicast=; fi
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s $unicast      #1
-
-        if test $d != $s && test $s = 1; then
-            impersonate=$d
-        else
-            impersonate=
-        fi
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:55 $impersonate   #3
-
-        if test $d != $s && test $s != 1; then acl2=$d; else acl2=; fi
-        if test $d != $s && test $d != 3; then acl3=$d; else acl3=; fi
-        if test $d = $s || ( (test $s = 1 || test $s = 2) && test $d = 3); then
-            # Source of 1 or 2 and dest of 3 should be dropped
-            # due to the 4th ACL that uses address_set(set1).
-            acl4=
-        else
-            acl4=$d
-        fi
-
-        #7, acl1 to acl4:
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1234
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1235 $acl2
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1236 $acl3
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1237 $acl4
-
-        test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:55 -vlan          #4
-        test_packet $s f0:00:00:00:00:0$d 01:00:00:00:00:0$s               #5
-
-        if test $d != $s && test $d = 1; then
-            unknown="$unknown $d"
-        fi
-        bcast="$bcast $unicast"
-        bacl2="$bacl2 $acl2"
-        bacl3="$bacl3 $acl3"
-
-        sip=192.168.0.$s
-        tip=192.168.0.$d
-        tip_unknown=11.11.11.11
-        if test $d != $s; then reply_ha=f0:00:00:00:00:0$d; else reply_ha=; fi
-        test_arp $s f0:00:00:00:00:0$s $sip $tip $reply_ha                 #9
-        test_arp $s f0:00:00:00:00:0$s $sip $tip_unknown                   #10
-
-        if test $d = 3; then
-            # lp3 has an additional ip 192.169.0.[123]3.
-            tip=192.169.0.$d
-            test_arp $s f0:00:00:00:00:0$s $sip $tip $reply_ha             #9
-        fi
-    done
-
-    # Broadcast and multicast.
-    test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s $bcast             #2
-    test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s $bcast             #2
-    if test $s = 1; then
-       bcast_impersonate=$bcast
-    else
-       bcast_impersonate=
-    fi
-    test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:44 $bcast_impersonate  #3
-
-    test_packet $s f0:00:00:00:ff:ff f0:00:00:00:00:0$s $unknown           #6
-
-    #8, acl1 to acl3:
-    test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1234
-    test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1235 $bacl2
-    test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1236 $bacl3
-
-    #8, acl1 to acl3:
-    test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1234
-    test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1235 $bacl2
-    test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1236 $bacl3
-done
-
-AT_CLEANUP
-
-# 2 hypervisors, 4 logical ports per HV
-# 2 locally attached networks (one flat, one vlan tagged over same device)
-# 2 ports per HV on each network
-AT_SETUP([ovn -- 2 HVs, 4 lports/HV, localnet ports])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# In this test cases we create 3 switches, all connected to same
-# physical network (through br-phys on each HV). Each switch has
-# VIF ports across 2 HVs. Each HV has 5 VIF ports. The first digit
-# of VIF port name indicates the hypervisor it is bound to, e.g.
-# lp23 means VIF 3 on hv2.
-#
-# Each switch's VLAN tag and their logical switch ports are:
-#   - ls1:
-#       - untagged
-#       - ports: lp11, lp12, lp21, lp22
-#
-#   - ls2:
-#       - tagged with VLAN 101
-#       - ports: lp13, lp14, lp23, lp24
-#   - ls3:
-#       - untagged
-#       - ports: lp15, lp25
-#
-# Note: a localnet port is created for each switch to connect to
-# physical network.
-
-for i in 1 2 3; do
-    ls_name=ls$i
-    ovn-nbctl ls-add $ls_name
-    ln_port_name=ln$i
-    if test $i -eq 2; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
-    else
-        ovn-nbctl lsp-add $ls_name $ln_port_name
-    fi
-    ovn-nbctl lsp-set-addresses $ln_port_name unknown
-    ovn-nbctl lsp-set-type $ln_port_name localnet
-    ovn-nbctl lsp-set-options $ln_port_name network_name=phys
-done
-
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[12]]) echo ls1 ;; dnl (
-        lp?[[34]]) echo ls2 ;; dnl (
-        lp?5) echo ls3 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2 3 4 5; do
-        ovs-vsctl add-port br-int vif$i$j -- \
-            set Interface vif$i$j external-ids:iface-id=lp$i$j \
-                                  options:tx_pcap=hv$i/vif$i$j-tx.pcap \
-                                  options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
-                                  ofport-request=$i$j
-
-        lsp_name=lp$i$j
-        ls_name=$(lsp_to_ls $lsp_name)
-
-        ovn-nbctl lsp-add $ls_name $lsp_name
-        ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:00:$i$j
-        ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$j
-
-        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
-    done
-done
-ovn-nbctl --wait=sb sync
-ovn-sbctl dump-flows
-
-OVN_POPULATE_ARP
-
-# XXX This is now the 3rd copy of these functions in this file ...
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv${1%?}
-}
-#
-# test_packet INPORT DST SRC ETHTYPE EOUT LOUT
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  INPORT is specified as
-# logical switch port numbers, e.g. 11 for vif11.
-#
-# EOUT is the end-to-end output port, that is, where the packet will end up
-# after possibly bouncing through one or more localnet ports.  LOUT is the
-# logical output port, which might be a localnet port, as seen by ovn-trace
-# (which doesn't know what localnet ports are connected to and therefore can't
-# figure out the end-to-end answer).
-for i in 1 2; do
-    for j in 1 2 3 4 5; do
-        : > $i$j.expected
-    done
-done
-test_packet() {
-    local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6
-    echo "$@"
-
-    # First try tracing the packet.
-    uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
-    if test $lout != drop; then
-        echo "output(\"$lout\");"
-    fi > expout
-    AT_CAPTURE_FILE([trace])
-    AT_CHECK([ovn-trace --all $(lsp_to_ls lp$inport) "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
-
-    # Then actually send a packet, for an end-to-end test.
-    local packet=$(echo $dst$src | sed 's/://g')${eth}
-    hv=`vif_to_hv $inport`
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    if test $eout != drop; then
-        echo $packet >> ${eout#lp}.expected
-    fi
-}
-
-# lp11 and lp21 are on the same network (phys, untagged)
-# and on different hypervisors
-test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21
-test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11
-
-# lp11 and lp12 are on the same network (phys, untagged)
-# and on the same hypervisor
-test_packet 11 f0:00:00:00:00:12 f0:00:00:00:00:11 1112 lp12 lp12
-test_packet 12 f0:00:00:00:00:11 f0:00:00:00:00:12 1211 lp11 lp11
-
-# lp13 and lp23 are on the same network (phys, VLAN 101)
-# and on different hypervisors
-test_packet 13 f0:00:00:00:00:23 f0:00:00:00:00:13 1323 lp23 lp23
-test_packet 23 f0:00:00:00:00:13 f0:00:00:00:00:23 2313 lp13 lp13
-
-# lp13 and lp14 are on the same network (phys, VLAN 101)
-# and on the same hypervisor
-test_packet 13 f0:00:00:00:00:14 f0:00:00:00:00:13 1314 lp14 lp14
-test_packet 14 f0:00:00:00:00:13 f0:00:00:00:00:14 1413 lp13 lp13
-
-# lp11 and lp15 are on the same network (phys, untagged),
-# same hypervisor, and on different switches
-test_packet 11 f0:00:00:00:00:15 f0:00:00:00:00:11 1115 lp15 ln1
-test_packet 15 f0:00:00:00:00:11 f0:00:00:00:00:15 1511 lp11 ln3
-
-# lp11 and lp25 are on the same network (phys, untagged),
-# different hypervisors, and on different switches
-test_packet 11 f0:00:00:00:00:25 f0:00:00:00:00:11 1125 lp25 ln1
-test_packet 25 f0:00:00:00:00:11 f0:00:00:00:00:25 2511 lp11 ln3
-
-# Ports that should not be able to communicate
-test_packet 11 f0:00:00:00:00:13 f0:00:00:00:00:11 1113 drop ln1
-test_packet 11 f0:00:00:00:00:23 f0:00:00:00:00:11 1123 drop ln1
-test_packet 21 f0:00:00:00:00:13 f0:00:00:00:00:21 2113 drop ln1
-test_packet 21 f0:00:00:00:00:23 f0:00:00:00:00:21 2123 drop ln1
-test_packet 13 f0:00:00:00:00:11 f0:00:00:00:00:13 1311 drop ln2
-test_packet 13 f0:00:00:00:00:21 f0:00:00:00:00:13 1321 drop ln2
-test_packet 23 f0:00:00:00:00:11 f0:00:00:00:00:23 2311 drop ln2
-test_packet 23 f0:00:00:00:00:21 f0:00:00:00:00:23 2321 drop ln2
-
-# Dump a bunch of info helpful for debugging if there's a failure.
-
-echo "------ OVN dump ------"
-ovn-nbctl show
-ovn-sbctl show
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2; do
-    for j in 1 2 3 4 5; do
-        OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
-    done
-done
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS])
-AT_KEYWORDS([vtep])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Configure the Northbound database
-ovn-nbctl ls-add lsw0
-
-ovn-nbctl lsp-add lsw0 lp1
-ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01
-
-ovn-nbctl lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02
-
-ovn-nbctl lsp-add lsw0 lp-vtep
-ovn-nbctl lsp-set-type lp-vtep vtep
-ovn-nbctl lsp-set-options lp-vtep vtep-physical-switch=br-vtep vtep-logical-switch=lsw0
-ovn-nbctl lsp-set-addresses lp-vtep unknown
-
-# lpr, lr and lrp1 are used for the ARP request handling test only.
-ovn-nbctl lsp-add lsw0 lpr
-ovn-nbctl lr-add lr
-ovn-nbctl lrp-add lr lrp1 f0:00:00:00:00:f1 192.168.1.1/24
-ovn-nbctl set Logical_Switch_Port lpr type=router \
-                             options:router-port=lrp1 \
-    addresses='"f0:00:00:00:00:f1 192.168.1.1"'
-
-
-net_add n1               # Network to connect hv1, hv2, and vtep
-net_add n2               # Network to connect vtep and hv3
-
-# Create hypervisor hv1 connected to n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
-
-# Create hypervisor hv2 connected to n1
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
-
-
-# Start the vtep emulator with a leg in both networks
-sim_add vtep
-as vtep
-
-ovsdb-tool create "$ovs_base"/vtep/vtep.db "$abs_top_srcdir"/vtep/vtep.ovsschema || return 1
-ovs-appctl -t ovsdb-server ovsdb-server/add-db "$ovs_base"/vtep/vtep.db
-
-ovs-vsctl add-br br-phys
-net_attach n1 br-phys
-
-mac=`ovs-vsctl get Interface br-phys mac_in_use | sed s/\"//g`
-arp_table="$arp_table $sandbox,br-phys,192.168.0.3,$mac"
-ovs-appctl netdev-dummy/ip4addr br-phys 192.168.0.3/24 >/dev/null || return 1
-ovs-appctl ovs/route/add 192.168.0.3/24 br-phys >/dev/null || return 1
-
-ovs-vsctl add-br br-vtep
-net_attach n2 br-vtep
-
-vtep-ctl add-ps br-vtep
-vtep-ctl set Physical_Switch br-vtep tunnel_ips=192.168.0.3
-vtep-ctl add-ls lsw0
-
-start_daemon ovs-vtep br-vtep
-start_daemon ovn-controller-vtep --vtep-db=unix:"$ovs_base"/vtep/db.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-
-OVS_WAIT_UNTIL([vtep-ctl bind-ls br-vtep br-vtep_n2 0 lsw0])
-
-OVS_WAIT_UNTIL([test -n "`as vtep vtep-ctl get-replication-mode lsw0 |
-               grep -- source`"])
-# It takes more time for the update to be processed by ovs-vtep.
-sleep 1
-
-# Add hv3 on the other side of the vtep
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-net_attach n2 br-phys
-
-ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# test_packet INPORT DST SRC ETHTYPE OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 1 for vif1.
-for i in 1 2 3; do
-    : > $i.expected
-done
-test_packet() {
-    local inport=$1 packet=$2$3$4; shift; shift; shift; shift
-    #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'`
-    hv=hv$inport
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-# Send packets between all pairs of source and destination ports:
-#
-# 1. Unicast packets are delivered to exactly one logical switch port
-#    (except that packets destined to their input ports are dropped).
-#
-# 2. Broadcast and multicast are delivered to all logical switch ports
-#    except the input port.
-#
-# 3. The switch delivers packets with an unknown destination to logical
-#    switch ports with "unknown" among their MAC addresses (and port
-#    security disabled).
-for s in 1 2 3; do
-    bcast=
-    unknown=
-    for d in 1 2 3; do
-        if test $d != $s; then unicast=$d; else unicast=; fi
-        test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast       #1
-
-        # The vtep (vif3) is the only one configured for "unknown"
-        if test $d != $s && test $d = 3; then
-            unknown="$unknown $d"
-        fi
-        bcast="$bcast $unicast"
-    done
-
-    # Broadcast and multicast.
-    test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast             #2
-    test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast             #2
-
-    test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown           #3
-done
-
-# ARP request should not be responded to by logical switch router
-# type arp responder on HV1 and HV2 and should reach directly to
-# vif1 and vif2
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-sha=f00000000003
-spa=`ip_to_hex 192 168 1 2`
-tpa=`ip_to_hex 192 168 1 1`
-request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-as hv3 ovs-appctl netdev-dummy/receive vif3 $request
-echo $request >> 1.expected
-echo $request >> 2.expected
-
-# dump information with counters
-echo "------ OVN dump ------"
-ovn-nbctl show
-ovn-sbctl show
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 show br-int
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-ofctl -O OpenFlow13 show br-int
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-# note: hv3 has no logical port bind, thus it should not have br-int
-AT_CHECK([as hv3 ovs-ofctl -O OpenFlow13 show br-int], [1], [],
-[ovs-ofctl: br-int is not a bridge or a socket
-])
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    OVN_CHECK_PACKETS([hv$i/vif$i-tx.pcap], [$i.expected])
-done
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1],[hv2],[vtep])
-OVN_CLEANUP_VSWITCH([hv3])
-
-AT_CLEANUP
-
-# Similar test to "hardware GW"
-AT_SETUP([ovn -- 3 HVs, 1 VIFs/HV, 1 software GW, 1 LS])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Configure the Northbound database
-ovn-nbctl ls-add lsw0
-
-ovn-nbctl lsp-add lsw0 lp1
-ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01
-
-ovn-nbctl lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02
-
-ovn-nbctl lsp-add lsw0 lp-gw
-ovn-nbctl lsp-set-type lp-gw l2gateway
-ovn-nbctl lsp-set-options lp-gw network_name=physnet1 l2gateway-chassis=hv_gw
-ovn-nbctl lsp-set-addresses lp-gw unknown
-
-net_add n1               # Network to connect hv1, hv2, and gw
-net_add n2               # Network to connect gw and hv3
-
-# Create hypervisor hv1 connected to n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
-
-# Create hypervisor hv2 connected to n1
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
-
-# Create hypervisor hv_gw connected to n1 and n2
-# connect br-phys bridge to n1; connect hv-gw bridge to n2
-sim_add hv_gw
-as hv_gw
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl add-br br-phys2
-net_attach n2 br-phys2
-ovs-vsctl set open . external_ids:ovn-bridge-mappings="physnet1:br-phys2"
-
-# Add hv3 on the other side of the GW
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-net_attach n2 br-phys
-ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
-
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# test_packet INPORT DST SRC ETHTYPE OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as lport numbers, e.g. 1 for vif1.
-for i in 1 2 3; do
-    : > $i.expected
-done
-test_packet() {
-    local inport=$1 packet=$2$3$4; shift; shift; shift; shift
-    #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'`
-    hv=hv$inport
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-# Send packets between all pairs of source and destination ports:
-#
-# 1. Unicast packets are delivered to exactly one lport (except that packets
-#    destined to their input ports are dropped).
-#
-# 2. Broadcast and multicast are delivered to all lports except the input port.
-#
-# 3. The lswitch delivers packets with an unknown destination to lports with
-#    "unknown" among their MAC addresses (and port security disabled).
-for s in 1 2 3 ; do
-    bcast=
-    unknown=
-    for d in 1 2 3 ; do
-        if test $d != $s; then unicast=$d; else unicast=; fi
-        test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast       #1
-
-        # The vtep (vif3) is the only one configured for "unknown"
-        if test $d != $s && test $d = 3; then
-            unknown="$unknown $d"
-        fi
-        bcast="$bcast $unicast"
-    done
-
-    test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast             #2
-    test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast             #3
-    test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown           #4
-done
-
-echo "------ ovn-nbctl show ------"
-ovn-nbctl show
-echo "------ ovn-sbctl show ------"
-ovn-sbctl show
-
-echo "------ hv1 ------"
-as hv1 ovs-vsctl show
-echo "------ hv1 br-int ------"
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-echo "------ hv1 br-phys ------"
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-phys
-
-echo "------ hv2 ------"
-as hv2 ovs-vsctl show
-echo "------ hv2 br-int ------"
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-echo "------ hv2 br-phys ------"
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-phys
-
-echo "------ hv_gw ------"
-as hv_gw ovs-vsctl show
-echo "------ hv_gw br-phys ------"
-as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys
-echo "------ hv_gw br-phys2 ------"
-as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys2
-
-echo "------ hv3 ------"
-as hv3 ovs-vsctl show
-echo "------ hv3 br-phys ------"
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-phys
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    OVN_CHECK_PACKETS([hv$i/vif$i-tx.pcap], [$i.expected])
-done
-AT_CLEANUP
-
-# 3 hypervisors, 3 logical switches with 3 logical ports each, 1 logical router
-AT_SETUP([ovn -- 3 HVs, 3 LS, 3 lports/LS, 1 LR])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-#
-# Three logical switches ls1, ls2, ls3.
-# One logical router lr0 connected to ls[123],
-# with nine subnets, three per logical switch:
-#
-#    lrp11 on ls1 for subnet 192.168.11.0/24
-#    lrp12 on ls1 for subnet 192.168.12.0/24
-#    lrp13 on ls1 for subnet 192.168.13.0/24
-#    ...
-#    lrp33 on ls3 for subnet 192.168.33.0/24
-#
-# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two
-# digits are the subnet and the last digit distinguishes the VIF.
-for i in 1 2 3; do
-    ovn-nbctl ls-add ls$i
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            # Add "unknown" to MAC addresses for lp?11, so packets for
-            # MAC-IP bindings discovered via ARP later have somewhere to go.
-            if test $j$k = 11; then unknown=unknown; else unknown=; fi
-
-            ovn-nbctl \
-                -- lsp-add ls$i lp$i$j$k \
-                -- lsp-set-addresses lp$i$j$k \
-                   "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" $unknown
-        done
-    done
-done
-
-ovn-nbctl lr-add lr0
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24
-        ovn-nbctl \
-            -- lsp-add ls$i lrp$i$j-attachment \
-            -- set Logical_Switch_Port lrp$i$j-attachment type=router \
-                             options:router-port=lrp$i$j \
-                             addresses='"00:00:00:00:ff:'$i$j'"'
-    done
-done
-
-ovn-nbctl set Logical_Switch_Port lrp33-attachment \
-    addresses='"00:00:00:00:ff:33 192.168.33.254"'
-
-# Physical network:
-#
-# Three hypervisors hv[123].
-# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3.
-# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3.
-# lp?3[123] all on hv3.
-
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    case $1 in dnl (
-        ?11) echo 1 ;; dnl (
-        ?12 | ?21 | ?22) echo 2 ;; dnl (
-        ?13 | ?23 | ?3?) echo 3 ;;
-    esac
-}
-
-# Given the name of a logical port, prints the name of its logical router
-# port, e.g. "vif_to_lrp 123" yields 12.
-vif_to_lrp() {
-    echo ${1%?}
-}
-
-# Given the name of a logical port, prints the name of its logical
-# switch, e.g. "vif_to_ls 123" yields 1.
-vif_to_ls() {
-    echo ${1%??}
-}
-
-net_add n1
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-done
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            hv=`vif_to_hv $i$j$k`
-                as hv$hv ovs-vsctl \
-                -- add-port br-int vif$i$j$k \
-                -- set Interface vif$i$j$k \
-                    external-ids:iface-id=lp$i$j$k \
-                    options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
-                    options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
-                    ofport-request=$i$j$k
-        done
-    done
-done
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 123 for vif123.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            : > $i$j$k.expected
-        done
-    done
-done
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    in_ls=`vif_to_ls $inport`
-    in_lrp=`vif_to_lrp $inport`
-    for outport; do
-        out_ls=`vif_to_ls $outport`
-        if test $in_ls = $out_ls; then
-            # Ports on the same logical switch receive exactly the same packet.
-            echo $packet
-        else
-            # Routing decrements TTL and updates source and dest MAC
-            # (and checksum).
-            out_lrp=`vif_to_lrp $outport`
-            echo f00000000${outport}00000000ff${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
-        fi >> $outport.expected
-    done
-}
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
-    local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $request
-    as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request
-
-    # Expect to receive the broadcast ARP on the other logical switch ports if
-    # IP address is not configured to the switch patch port.
-    local i=`vif_to_ls $inport`
-    local j k
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            # 192.168.33.254 is configured to the switch patch port for lrp33,
-            # so no ARP flooding expected for it.
-            if test $i$j$k != $inport && test $tpa != `ip_to_hex 192 168 33 254`; then
-                echo $request >> $i$j$k.expected
-            fi
-        done
-    done
-
-    # Expect to receive the reply, if any.
-    if test X$reply_ha != X; then
-        lrp=`vif_to_lrp $inport`
-        local reply=${sha}00000000ff${lrp}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-        echo $reply >> $inport.expected
-    fi
-}
-
-as hv1 ovs-vsctl --columns=name,ofport list interface
-as hv1 ovn-sbctl list port_binding
-as hv1 ovn-sbctl list datapath_binding
-as hv1 ovn-sbctl dump-flows
-as hv1 ovs-ofctl dump-flows br-int
-
-# Send IP packets between all pairs of source and destination ports:
-#
-# 1. Unicast IP packets are delivered to exactly one logical switch port
-#    (except that packets destined to their input ports are dropped).
-#
-# 2. Broadcast IP packets are delivered to all logical switch ports
-#    except the input port.
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-for is in 1 2 3; do
-  for js in 1 2 3; do
-    for ks in 1 2 3; do
-      bcast=
-      s=$is$js$ks
-      smac=f00000000$s
-      sip=`ip_to_hex 192 168 $is$js $ks`
-      for id in 1 2 3; do
-          for jd in 1 2 3; do
-              for kd in 1 2 3; do
-                d=$id$jd$kd
-                dip=`ip_to_hex 192 168 $id$jd $kd`
-                if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi
-                if test $d != $s; then unicast=$d; else unicast=; fi
-
-                test_ip $s $smac $dmac $sip $dip $unicast #1
-
-                if test $id = $is && test $d != $s; then bcast="$bcast $d"; fi
-              done
-          done
-        done
-      test_ip $s $smac ffffffffffff $sip ffffffff $bcast #2
-      done
-  done
-done
-
-: > mac_bindings.expected
-
-# 3. Send an IP packet from every logical port to every other subnet,
-#    to an IP address that does not have a static IP-MAC binding.
-#    This should generate a broadcast ARP request for the destination
-#    IP address in the destination subnet.
-#    Moreover generate an ARP reply for each of the IP addresses ARPed
-for is in 1 2 3; do
-  for js in 1 2 3; do
-    for ks in 1 2 3; do
-      s=$is$js$ks
-      smac=f00000000$s
-      sip=`ip_to_hex 192 168 $is$js $ks`
-      for id in 1 2 3; do
-        for jd in 1 2 3; do
-          if test $is$js = $id$jd; then
-            continue
-          fi
-
-          # Send the packet.
-          dmac=00000000ff$is$js
-          # Calculate a 4th octet for the destination that is
-          # unique per $s, avoids the .1 .2 .3 and .254 IP addresses
-          # that have static MAC bindings, and fits in the range
-          # 0-255.
-          o4=`expr $is '*' 9 + $js '*' 3 + $ks + 10`
-          dip=`ip_to_hex 192 168 $id$jd $o4`
-          test_ip $s $smac $dmac $sip $dip
-
-          # Every LP on the destination subnet's lswitch should
-          # receive the ARP request.
-          lrmac=00000000ff$id$jd
-          lrip=`ip_to_hex 192 168 $id$jd 254`
-          arp=ffffffffffff${lrmac}08060001080006040001${lrmac}${lrip}000000000000${dip}
-          for jd2 in 1 2 3; do
-            for kd in 1 2 3; do
-              echo $arp >> $id$jd2$kd.expected
-            done
-          done
-
-          hmac=8000000000$o4
-          rmac=00000000ff$id$jd
-          echo ${hmac}${rmac}08004500001c00000000"3f1101"00${sip}${dip}0035111100080000 >> ${id}11.expected
-
-          host_mac=8000000000$o4
-          lrmac=00000000ff$id$jd
-
-          arp_reply=${lrmac}${host_mac}08060001080006040002${host_mac}${dip}${lrmac}${lrip}
-
-          hv=hv`vif_to_hv ${id}${jd}1`
-          as $hv ovs-appctl netdev-dummy/receive vif${id}${jd}1 $arp_reply
-
-          host_ip_pretty=192.168.$id$jd.$o4
-          host_mac_pretty=80:00:00:00:00:$o4
-          echo lrp$id$jd,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected
-        done
-      done
-    done
-  done
-done
-
-# Test router replies to ARP requests from all source ports:
-#
-# 4. Router replies to query for its MAC address from port's own IP address.
-#
-# 5. Router replies to query for its MAC address from any random IP address
-#    in its subnet.
-#
-# 6. No reply to query for IP address other than router IP.
-#
-# 7. No reply to query from another subnet.
-for i in 1 2 3; do
-  for j in 1 2 3; do
-    for k in 1 2 3; do
-      smac=f00000000$i$j$k               # Source MAC
-      sip=`ip_to_hex 192 168 $i$j $k`    # Source IP
-      rip=`ip_to_hex 192 168 $i$j 254`   # Router IP
-      rmac=00000000ff$i$j                # Router MAC
-      otherip=`ip_to_hex 192 168 $i$j 55` # Some other IP in subnet
-      externalip=`ip_to_hex 1 2 3 4`      # Some other IP not in subnet
-
-      test_arp $i$j$k $smac $sip        $rip        $rmac      #4
-      test_arp $i$j$k $smac $otherip    $rip        $rmac      #5
-      test_arp $i$j$k $smac $sip        $otherip               #6
-
-      # When rip is 192.168.33.254, ARP request from externalip won't be
-      # filtered, because 192.168.33.254 is configured to switch peer port
-      # for lrp33.
-      lrp33_rsp=
-      if test $i = 3 && test $j = 3; then
-        lrp33_rsp=$rmac
-      fi
-      test_arp $i$j$k $smac $externalip $rip        $lrp33_rsp #7
-
-      # MAC binding should be learned from ARP request.
-      host_mac_pretty=f0:00:00:00:0$i:$j$k
-
-      host_ip_pretty=192.168.$i$j.$k
-      echo lrp$i$j,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected
-
-      # mac_binding is learned and overwritten so only the last one remains.
-      if test $k = 3; then
-          # lrp33 will not learn from ARP request, because 192.168.33.254 is
-          # configured to switch peer port for lrp33.
-          if test $i != 3 || test $j != 3; then
-              host_ip_pretty=192.168.$i$j.55
-              echo lrp$i$j,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected
-          fi
-      fi
-
-    done
-  done
-done
-
-
-# Allow some time for packet forwarding.
-# XXX This can be improved.
-sleep 1
-
-# 8. Send an IP packet from every logical port to every other subnet.  These
-#    are the same packets already sent as #3, but now the destinations' IP-MAC
-#    bindings have been discovered via ARP, so instead of provoking an ARP
-#    request, these packets now get routed to their destinations (which don't
-#    have static MAC bindings, so they go to the port we've designated as
-#    accepting "unknown" MACs.)
-for is in 1 2 3; do
-  for js in 1 2 3; do
-    for ks in 1 2 3; do
-      s=$is$js$ks
-      smac=f00000000$s
-      sip=`ip_to_hex 192 168 $is$js $ks`
-      for id in 1 2 3; do
-        for jd in 1 2 3; do
-          if test $is$js = $id$jd; then
-            continue
-          fi
-
-          # Send the packet.
-          dmac=00000000ff$is$js
-          # Calculate a 4th octet for the destination that is
-          # unique per $s, avoids the .1 .2 .3 and .254 IP addresses
-          # that have static MAC bindings, and fits in the range
-          # 0-255.
-          o4=`expr $is '*' 9 + $js '*' 3 + $ks + 10`
-          dip=`ip_to_hex 192 168 $id$jd $o4`
-          test_ip $s $smac $dmac $sip $dip
-
-          # Expect the packet egress.
-          host_mac=8000000000$o4
-          outport=${id}11
-          out_lrp=$id$jd
-          echo ${host_mac}00000000ff${out_lrp}08004500001c00000000"3f1101"00${sip}${dip}0035111100080000 >> $outport.expected
-        done
-      done
-    done
-  done
-done
-
-ovn-sbctl -f csv -d bare --no-heading \
-    -- --columns=logical_port,ip,mac list mac_binding > mac_bindings
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap],
-                              [$i$j$k.expected])
-        done
-    done
-done
-
-# Check the MAC bindings against those expected.
-AT_CHECK_UNQUOTED([sort < mac_bindings], [0], [`sort < mac_bindings.expected`
-])
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1], [hv2], [hv3])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- IP relocation using GARP request])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-#
-# Two logical switches ls1, ls2.
-# One logical router lr0 connected to ls[12],
-# with 2 subnets, 1 per logical switch:
-#
-#    lrp1 on ls1 for subnet 192.168.1.1/24
-#    lrp2 on ls2 for subnet 192.168.2.1/24
-#
-# 4 VIFs, 2 per LS lp[12][12], first digit being LS.
-# VIFs' fixed IP addresses are 192.168.[12].1[12].
-#
-# There is a secondary IP 192.168.1.100 that is unknown in NB and learned
-# through ARP only, and it can move between lp11 and lp12.
-#
-ovn-nbctl lr-add lr0
-for i in 1 2 ; do
-    ovn-nbctl ls-add ls$i
-    ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.1/24
-    ovn-nbctl \
-        -- lsp-add ls$i lrp$i-attachment \
-        -- set Logical_Switch_Port lrp$i-attachment type=router \
-                         options:router-port=lrp$i \
-                         addresses=router
-    for j in 1 2; do
-        ovn-nbctl \
-            -- lsp-add ls$i lp$i$j \
-            -- lsp-set-addresses lp$i$j \
-               "f0:00:00:00:00:$i$j 192.168.$i.1$j"
-    done
-done
-
-# Physical network:
-# 2 hypervisors hv[12], lp?1 on hv1, lp?2 on hv2.
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located, e.g. "vif_to_hv 12" yields 2.
-vif_to_hv() {
-    echo ${1#?}
-}
-
-# Given the name of a logical port, prints the name of its logical router
-# port, e.g. "vif_to_lrp 12" yields 1.
-vif_to_lrp() {
-    echo ${1%?}
-}
-
-# Given the name of a logical port, prints the name of its logical
-# switch, e.g. "vif_to_ls 12" yields 1.
-vif_to_ls() {
-    echo ${1%?}
-}
-
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-done
-for i in 1 2; do
-    for j in 1 2; do
-        hv=`vif_to_hv $i$j`
-            as hv$hv ovs-vsctl \
-                -- add-port br-int vif$i$j \
-                -- set Interface vif$i$j \
-                    external-ids:iface-id=lp$i$j \
-                    options:tx_pcap=hv$hv/vif$i$j-tx.pcap \
-                    options:rxq_pcap=hv$hv/vif$i$j-rx.pcap \
-                    ofport-request=$i$j
-    done
-done
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 12 for vif12.
-for i in 1 2; do
-    for j in 1 2; do
-        : > $i$j.expected
-    done
-done
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    in_ls=`vif_to_ls $inport`
-    in_lrp=`vif_to_lrp $inport`
-    for outport; do
-        out_ls=`vif_to_ls $outport`
-        if test $in_ls = $out_ls; then
-            # Ports on the same logical switch receive exactly the same packet.
-            echo $packet
-        else
-            # Routing decrements TTL and updates source and dest MAC
-            # (and checksum).
-            out_lrp=`vif_to_lrp $outport`
-            echo f000000000${outport}00000000ff0${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
-        fi >> $outport.expected
-    done
-}
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
-    local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $request
-
-    # Expect to receive the broadcast ARP on the other logical switch ports if
-    # IP address is not configured to the switch patch port.
-    local i=`vif_to_ls $inport`
-    local j
-    for j in 1 2; do
-        if test $i$j != $inport; then
-            echo $request >> $i$j$k.expected
-        fi
-    done
-
-    # Expect to receive the reply, if any.
-    if test X$reply_ha != X; then
-        lrp=`vif_to_lrp $inport`
-        local reply=${sha}00000000ff0${lrp}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-        echo $reply >> $inport.expected
-    fi
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# lp11 send GARP request to announce ownership of 192.168.1.100.
-
-sha=f00000000011
-spa=`ip_to_hex 192 168 1 100`
-tpa=$spa
-test_arp 11 $sha $spa $tpa
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="192.168.1.100" | wc -l` -gt 0])
-ovn-nbctl --wait=hv sync
-
-# Send an IP packet from lp21 to 192.168.1.100, which should go to lp11.
-
-smac=f00000000021
-dmac=00000000ff02
-sip=`ip_to_hex 192 168 2 11`
-dip=`ip_to_hex 192 168 1 100`
-test_ip 21 $smac $dmac $sip $dip 11
-
-# lp12 send GARP request to announce ownership of 192.168.1.100.
-
-sha=f00000000012
-test_arp 12 $sha $spa $tpa
-OVS_WAIT_UNTIL([ovn-sbctl find mac_binding ip="192.168.1.100" | grep f0:00:00:00:00:12])
-ovn-nbctl --wait=hv sync
-# give to the hv the time to send queued ip packets
-sleep 1
-
-# Send an IP packet from lp21 to 192.168.1.100, which should go to lp12.
-
-test_ip 21 $smac $dmac $sip $dip 12
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2; do
-    for j in 1 2; do
-        OVN_CHECK_PACKETS([hv`vif_to_hv $i$j`/vif$i$j-tx.pcap],
-                          [$i$j.expected])
-    done
-done
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1], [hv2])
-
-AT_CLEANUP
-
-# 3 hypervisors, one logical switch, 3 logical ports per hypervisor
-AT_SETUP([ovn -- portsecurity : 3 HVs, 1 LS, 3 lports/HV])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Create hypervisors hv[123].
-# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
-# Add all of the vifs to a single logical switch lsw0.
-# Turn off port security on vifs vif[123]1
-# Turn on l2 port security on vifs vif[123]2
-# Turn of l2 and l3 port security on vifs vif[123]3
-# Make vif13, vif2[23], vif3[123] destinations for unknown MACs.
-ovn-nbctl ls-add lsw0
-net_add n1
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2 3; do
-        ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
-        ovn-nbctl lsp-add lsw0 lp$i$j
-        if test $j = 1; then
-            ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
-        elif test $j = 2; then
-            ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j"
-            ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
-        else
-            extra_addr="f0:00:00:00:0$i:$i$j fe80::ea2a:eaff:fe28:$i$j"
-            ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr"
-            ovn-nbctl lsp-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr"
-        fi
-    done
-done
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv${1%?}
-}
-
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        : > $i$j.expected
-    done
-done
-
-# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-#
-# This shell function causes an ip packet to be received on INPORT.
-# The packet's content has Ethernet destination DST and source SRC
-# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits).
-# The OUTPORTs (zero or more) list the VIFs on which the packet should
-# be received.  INPORT and the OUTPORTs are specified as logical switch
-# port numbers, e.g. 11 for vif11.
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-# test_arp INPORT SHA SPA TPA DROP [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 smac=$2 sha=$3 spa=$4 tpa=$5 drop=$6 reply_ha=$7
-    local request=ffffffffffff${smac}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $request
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request
-    if test $drop != 1; then
-        if test X$reply_ha = X; then
-            # Expect to receive the broadcast ARP on the other logical switch ports
-            # if no reply is expected.
-            local i j
-            for i in 1 2 3; do
-                for j in 1 2 3; do
-                    if test $i$j != $inport; then
-                        echo $request >> $i$j.expected
-                    fi
-                done
-            done
-        else
-            # Expect to receive the reply, if any.
-            local reply=${smac}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-            echo $reply >> $inport.expected
-        fi
-    fi
-}
-
-# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-# This function is similar to test_ip() except that it sends
-# ipv6 packet
-test_ipv6() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}0000000000000000
-    shift; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-# test_icmpv6 INPORT  SRC_MAC DST_MAC SRC_IP DST_IP ICMP_TYPE OUTPORT...
-# This function is similar to test_ipv6() except it specifies the ICMPv6 type
-# of the test packet
-test_icmpv6() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6
-    local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}${icmp_type}00000000000000
-    shift; shift; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# no port security
-sip=`ip_to_hex 192 168 0 12`
-tip=`ip_to_hex 192 168 0 13`
-# the arp packet should be allowed even if lp[123]1 is
-# not configured with mac f00000000023 and ip 192.168.0.12
-for i in 1 2 3; do
-    test_arp ${i}1 f00000000023 f00000000023 $sip $tip 0 f00000000013
-    for j in 1 2 3; do
-        if test $i != $j; then
-            test_ip ${i}1 f000000000${i}1 f000000000${j}1 $sip $tip ${j}1
-        fi
-    done
-done
-
-# l2 port security
-sip=`ip_to_hex 192 168 0 12`
-tip=`ip_to_hex 192 168 0 13`
-
-# arp packet should be allowed since lp22 is configured with
-# mac f00000000022
-test_arp 22 f00000000022 f00000000022 $sip $tip 0 f00000000013
-
-# arp packet should not be allowed since lp32 is not configured with
-# mac f00000000021
-test_arp 32 f00000000021 f00000000021 $sip $tip 1
-
-# arp packet with sha set to f00000000021 should not be allowed
-# for lp12
-test_arp 12 f00000000012 f00000000021 $sip $tip 1
-
-# ip packets should be allowed and received since lp[123]2 do not
-# have l3 port security
-sip=`ip_to_hex 192 168 0 55`
-tip=`ip_to_hex 192 168 0 66`
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        if test $i != $j; then
-            test_ip ${i}2 f000000000${i}2 f000000000${j}2 $sip $tip ${j}2
-        fi
-    done
-done
-
-# ipv6 packets should be received by lp[123]2
-# lp[123]1 can send ipv6 traffic as there is no port security
-sip=fe800000000000000000000000000000
-tip=ff020000000000000000000000000000
-
-for i in 1 2 3; do
-    test_ipv6 ${i}1 f000000000${i}1 f000000000${i}2 $sip $tip ${i}2
-done
-
-
-# l2 and l3 port security
-sip=`ip_to_hex 192 168 0 13`
-tip=`ip_to_hex 192 168 0 22`
-# arp packet should be allowed since lp13 is configured with
-# f00000000013 and 192.168.0.13
-test_arp 13 f00000000013 f00000000013 $sip $tip 0 f00000000022
-
-# the arp packet should be dropped because lp23 is not configured
-# with mac f00000000022
-sip=`ip_to_hex 192 168 0 13`
-tip=`ip_to_hex 192 168 0 22`
-test_arp 23 f00000000022 f00000000022 $sip $tip 1
-
-# the arp packet should be dropped because lp33 is not configured
-# with ip 192.168.0.55
-spa=`ip_to_hex 192 168 0 55`
-tpa=`ip_to_hex 192 168 0 22`
-test_arp 33 f00000000031 f00000000031 $spa $tpa 1
-
-# ip packets should not be received by lp[123]3 since
-# l3 port security is enabled
-sip=`ip_to_hex 192 168 0 55`
-tip=`ip_to_hex 192 168 0 66`
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        test_ip ${i}2 f000000000${i}2 f000000000${j}3 $sip $tip
-    done
-done
-
-# ipv6 packets should be dropped for lp[123]3 since
-# it is configured with only ipv4 address
-sip=fe800000000000000000000000000000
-tip=ff020000000000000000000000000000
-
-for i in 1 2 3; do
-    test_ipv6 ${i}3 f000000000${i}3 f00000000022 $sip $tip
-done
-
-# ipv6 packets should not be received by lp[123]3 with mac f000000000$[123]3
-# lp[123]1 can send ipv6 traffic as there is no port security
-for i in 1 2 3; do
-    test_ipv6 ${i}1 f000000000${i}1 f000000000${i}3 $sip $tip
-done
-
-# lp13 has extra port security with mac f0000000113 and ipv6 addr
-# fe80::ea2a:eaff:fe28:0012
-
-# ipv4 packet should be dropped for lp13 with mac f0000000113
-sip=`ip_to_hex 192 168 0 13`
-tip=`ip_to_hex 192 168 0 23`
-test_ip 13 f00000000113 f00000000023 $sip $tip
-
-# ipv6 packet should be received by lp[123]3 with mac f00000000${i}${i}3
-# and ip6.dst as fe80::ea2a:eaff:fe28:0${i}${i}3.
-# lp11 can send ipv6 traffic as there is no port security
-sip=ee800000000000000000000000000000
-for i in 1 2 3; do
-    tip=fe80000000000000ea2aeafffe2800${i}3
-    test_ipv6 11 f00000000011 f00000000${i}${i}3 $sip $tip ${i}3
-done
-
-
-# ipv6 packet should not be received by lp33 with mac f0000000333
-# and ip6.dst as fe80::ea2a:eaff:fe28:0023 as it is
-# configured with fe80::ea2a:eaff:fe28:0033
-# lp11 can send ipv6 traffic as there is no port security
-
-sip=ee800000000000000000000000000000
-tip=fe80000000000000ea2aeafffe280023
-test_ipv6 11 f00000000011 f00000000333 $sip $tip
-
-# ipv6 packet should be allowed for lp[123]3 with mac f0000000${i}${i}3
-# and ip6.src fe80::ea2a:eaff:fe28:0${i}${i}3 and ip6.src ::.
-# and should be dropped for any other ip6.src
-# lp21 can receive ipv6 traffic as there is no port security
-
-tip=ee800000000000000000000000000000
-for i in 1 2 3; do
-    sip=fe80000000000000ea2aeafffe2800${i}3
-    test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21
-
-    # Test ICMPv6 MLD reports (v1 and v2) and NS for DAD
-    sip=00000000000000000000000000000000
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 83 21
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 8f 21
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff0200000000000000ea2aeafffe2800 87 21
-    # Traffic to non-multicast traffic should be dropped
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 83
-    # Traffic of other ICMPv6 types should be dropped
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 80
-
-    # should be dropped
-    sip=ae80000000000000ea2aeafffe2800aa
-    test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip
-done
-
-# configure lsp13 to send and received IPv4 packets with an address range
-ovn-nbctl lsp-set-port-security lp13 "f0:00:00:00:00:13 192.168.0.13 20.0.0.4/24 10.0.0.0/24"
-
-sleep 2
-
-sip=`ip_to_hex 10 0 0 13`
-tip=`ip_to_hex 192 168 0 22`
-# arp packet with inner ip 10.0.0.13 should be allowed for lsp13
-test_arp 13 f00000000013 f00000000013 $sip $tip 0 f00000000022
-
-sip=`ip_to_hex 10 0 0 14`
-tip=`ip_to_hex 192 168 0 23`
-# IPv4 packet from lsp13 with src ip 10.0.0.14 destined to lsp23
-# with dst ip 192.168.0.23 should be allowed
-test_ip 13 f00000000013 f00000000023 $sip $tip 23
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 10 0 0 15`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 10.0.0.15 should be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip 13
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 20 0 0 4`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 20.0.0.4 should be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip 13
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 20 0 0 5`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 20.0.0.5 should not be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 20 0 0 255`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 20.0.0.255 should be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip 13
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 192 168 0 255`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 192.168.0.255 should not be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip
-
-sip=`ip_to_hex 192 168 0 33`
-tip=`ip_to_hex 224 0 0 4`
-# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13
-# with dst ip 224.0.0.4  should be received by lsp13
-test_ip 33 f00000000033 f00000000013 $sip $tip 13
-
-#dump information including flow counters
-ovn-nbctl show
-ovn-sbctl dump-flows -- list multicast_group
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 show br-int
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-ofctl -O OpenFlow13 show br-int
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-as hv3 ovs-ofctl -O OpenFlow13 show br-int
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
-    done
-done
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 2 LS, 1 lport/LS, 2 peer LRs])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24
-# network. R1 has a switchs ls1 (191.168.1.0/24) connected to it.
-# R2 has ls2 (172.16.1.0/24) connected to it.
-
-ls1_lp1_mac="f0:00:00:01:02:03"
-rp_ls1_mac="00:00:00:01:02:03"
-rp_ls2_mac="00:00:00:01:02:04"
-ls2_lp1_mac="f0:00:00:01:02:04"
-
-ls1_lp1_ip="192.168.1.2"
-ls2_lp1_ip="172.16.1.2"
-
-ovn-nbctl lr-add R1
-ovn-nbctl lr-add R2
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24
-
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
-  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
-
-# Connect ls2 to R2
-ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24
-
-ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \
-  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
-
-# Connect R1 to R2
-ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1
-ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2
-
-ovn-nbctl lr-route-add R1 "0.0.0.0/0" 20.0.0.2
-ovn-nbctl lr-route-add R2 "0.0.0.0/0" 20.0.0.1
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
-
-# Create logical port ls2-lp1 in ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Packet to send.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
-        udp && udp.src==53 && udp.dst==4369"
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-
-# Packet to Expect
-# The TTL should be decremented by 2.
-packet="eth.src==$rp_ls2_mac && eth.dst==$ls2_lp1_mac &&
-        ip4 && ip.ttl==62 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
-        udp && udp.src==53 && udp.dst==4369"
-echo $packet | ovstest test-ovn expr-to-packets > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
-grep "reg0 == 172.16.1.2" | wc -l], [0], [1
-])
-
-# Disable the ls2-lp1 port.
-ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
-
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
-grep "reg0 == 172.16.1.2" | wc -l], [0], [0
-])
-
-# Generate the packet destined for ls2-lp1 and it should not be delivered.
-# Packet to send.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
-        udp && udp.src==53 && udp.dst==4369"
-
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-# The 2nd packet sent shound not be received.
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-
-AT_SETUP([ovn -- 1 HV, 1 LS, 2 lport/LS, 1 LR])
-AT_KEYWORDS([router-admin-state])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR - R1 has switch ls1 with two subnets attached to it (191.168.1.0/24
-# and 172.16.1.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add ls1
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 172.16.1.1/24
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
-          options:router-port=ls1 addresses=\"00:00:00:01:02:03\"
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
-          -- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port ls1-lp2 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp2 \
-          -- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:04 172.16.1.2"
-
-# Create one hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int vif1 -- \
-    set interface vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif2 -- \
-    set interface vif2 external-ids:iface-id=ls1-lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=1
-
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Send ip packets between the two ports.
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Packet to send.
-src_mac="f00000010203"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-
-#Disable router R1
-ovn-nbctl set Logical_Router R1 enabled=false
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
-# Packet to Expect
-expect_src_mac="000000010203"
-expect_dst_mac="f00000010204"
-echo "${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-
-AT_SETUP([ovn -- 1 HV, 2 LSs, 1 lport/LS, 1 LR])
-AT_KEYWORDS([router-admin-state])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
-# and has switch ls2 (172.16.1.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \
-          options:router-port=ls1 addresses=\"00:00:00:01:02:03\"
-
-# Connect ls2 to R1
-ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24
-ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \
-          options:router-port=ls2 addresses=\"00:00:00:01:02:04\"
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port ls2-lp1 in ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Create one hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int vif1 -- \
-    set interface vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif2 -- \
-    set interface vif2 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=1
-
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Send ip packets between the two ports.
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Packet to send.
-src_mac="f00000010203"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-#Disable router R1
-ovn-nbctl set Logical_Router R1 enabled=false
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-# Allow some time for the disabling of logical router R1 to propagate.
-# XXX This should be more systematic.
-sleep 1
-
-as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
-# Packet to Expect
-expect_src_mac="000000010204"
-expect_dst_mac="f00000010204"
-echo "${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 3 LS, 1 lport/LS, 2 peer LRs, static routes])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24
-# network. R1 has switchess foo (192.168.1.0/24)
-# connected to it.
-# R2 has alice (172.16.1.0/24) and bob (172.16.2.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-ovn-nbctl lr-add R2
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add bob
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-          options:router-port=foo addresses=\"00:00:00:01:02:03\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-          type=router options:router-port=alice addresses=\"00:00:00:01:02:04\"
-
-# Connect bob to R2
-ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24
-ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob type=router \
-          options:router-port=bob addresses=\"00:00:00:01:02:05\"
-
-# Connect R1 to R2
-ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1
-ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2
-
-#install static routes
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R2 172.16.2.0/24 20.0.0.2 R1_R2
-ovn-nbctl lr-route-add R2 192.168.1.0/24 20.0.0.1
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port alice1 in alice
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Create logical port bob1 in bob
-ovn-nbctl lsp-add bob bob1 \
--- lsp-set-addresses bob1 "f0:00:00:01:02:05 172.16.2.2"
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=bob1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and alice1
-src_mac="f00000010203"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-# Send ip packets between foo1 and bob1
-src_mac="f00000010203"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 2 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump ----------"
-as hv2 ovs-ofctl dump-flows br-int
-
-# Packet to Expect at bob1
-src_mac="000000010205"
-dst_mac="f00000010205"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 2 2`
-echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Packet to Expect at alice1
-src_mac="000000010204"
-dst_mac="f00000010204"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- send gratuitous arp on localnet])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-ovn-nbctl ls-add lsw0
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0
-
-ovn_attach n1 br-phys 192.168.0.1
-
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
-AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv/snoopvif-tx.pcap options:rxq_pcap=hv/snoopvif-rx.pcap])
-
-# Create a vif.
-AT_CHECK([ovn-nbctl lsp-add lsw0 localvif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses localvif1 "f0:00:00:00:00:01 192.168.1.2"])
-AT_CHECK([ovn-nbctl lsp-set-port-security localvif1 "f0:00:00:00:00:01"])
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add lsw0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
-AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1])
-
-# Wait for packet to be received.
-echo "fffffffffffff0000000000108060001080006040001f00000000001c0a80102000000000000c0a80102" > expected
-OVN_CHECK_PACKETS([hv/snoopvif-tx.pcap], [expected])
-
-# Check GARP packet when restart openflow connection.
-as hv
-OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
-
-OVS_WAIT_UNTIL([grep -c "waiting 4 seconds before reconnect" hv/ovn-controller.log])
-
-as hv
-start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl
-
-# Wait for packet to be received.
-echo "fffffffffffff0000000000108060001080006040001f00000000001c0a80102000000000000c0a80102" > expected
-OVN_CHECK_PACKETS([hv/snoopvif-tx.pcap], [expected])
-
-# Delete the localnet ports.
-AT_CHECK([ovs-vsctl del-port localvif1])
-AT_CHECK([ovn-nbctl lsp-del ln_port])
-
-OVN_CLEANUP([hv])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 3 LRs connected via LS, static routes])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24)
-# connected to it. R2 has alice (172.16.1.0/24) and R3 has bob (10.32.1.0/24)
-# connected to it.
-
-ovn-nbctl lr-add R1
-ovn-nbctl lr-add R2
-ovn-nbctl lr-add R3
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add bob
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-    options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect bob to R3
-ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 10.32.1.1/24
-ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
-    type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Connect R3 to join
-ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24
-ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
-    type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
-
-#install static routes
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R1 10.32.1.0/24 20.0.0.3
-
-ovn-nbctl lr-route-add R2 192.168.1.0/24 20.0.0.1
-ovn-nbctl lr-route-add R2 10.32.1.0/24 20.0.0.3
-
-ovn-nbctl lr-route-add R3 192.168.1.0/24 20.0.0.1
-ovn-nbctl lr-route-add R3 172.16.1.0/24 20.0.0.2
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port alice1 in alice
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Create logical port bob1 in bob
-ovn-nbctl lsp-add bob bob1 \
--- lsp-set-addresses bob1 "f0:00:00:01:02:05 10.32.1.2"
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=bob1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and alice1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet
-
-# Send ip packets between foo1 and bob1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 10 32 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-echo "----------------------------"
-
-# Packet to Expect at bob1
-src_mac="000003010203"
-dst_mac="f00000010205"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 10 32 1 2`
-echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Packet to Expect at alice1
-src_mac="000002010203"
-dst_mac="f00000010204"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-ovn-nbctl lsp-add ls1 ls1-lp2 \
--- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
-
-ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
-
-ovn-nbctl ls-add ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4"
-ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4"
-ovn-nbctl lsp-add ls2 ls2-lp2 \
--- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7"
-ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7"
-
-d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \
-options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \
-\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")"
-
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1}
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp2 ${d1}
-
-d2="$(ovn-nbctl create DHCP_Options cidr=30.0.0.0/24 \
-options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \
-\"lease_time\"=\"3600\"")"
-
-ovn-nbctl lsp-set-dhcpv4-options ls2-lp2 ${d2}
-
-net_add n1
-sim_add hv1
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-ovs-vsctl -- add-port br-int hv1-vif3 -- \
-    set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=hv1/vif3-tx.pcap \
-    options:rxq_pcap=hv1/vif3-rx.pcap \
-    ofport-request=3
-
-ovs-vsctl -- add-port br-int hv1-vif4 -- \
-    set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
-    options:tx_pcap=hv1/vif4-tx.pcap \
-    options:rxq_pcap=hv1/vif4-rx.pcap \
-    ofport-request=4
-
-OVN_POPULATE_ARP
-
-sleep 2
-
-as hv1 ovs-vsctl show
-
-# This shell function sends a DHCP request packet
-# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP REQUEST_IP ...
-test_dhcp() {
-    local inport=$1 src_mac=$2 dhcp_type=$3 ciaddr=$4 offer_ip=$5 request_ip=$6 use_ip=$7
-    shift; shift; shift; shift; shift; shift; shift;
-    if test $use_ip != 0; then
-        src_ip=$1
-        dst_ip=$2
-        shift; shift;
-    else
-        src_ip=`ip_to_hex 0 0 0 0`
-        dst_ip=`ip_to_hex 255 255 255 255`
-    fi
-    if test $request_ip != 0; then
-        ip_len=0120
-        udp_len=010b
-    else
-        ip_len=011a
-        udp_len=0106
-    fi
-    local request=ffffffffffff${src_mac}08004510${ip_len}0000000080110000${src_ip}${dst_ip}
-    # udp header and dhcp header
-    request=${request}00440043${udp_len}0000
-    request=${request}010106006359aa7600000000${ciaddr}000000000000000000000000${src_mac}
-    # client hardware padding
-    request=${request}00000000000000000000
-    # server hostname
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    # boot file name
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    # dhcp magic cookie
-    request=${request}63825363
-    # dhcp message type
-    request=${request}3501${dhcp_type}
-    # dhcp unknown option
-    request=${request}d70701020304050607
-    # dhcp pad option
-    request=${request}00
-    if test $request_ip != 0; then
-        # dhcp requested ip
-        request=${request}3204${request_ip}
-    fi
-    # dhcp end option
-    request=${request}ff
-
-    for port in $inport "$@"; do
-        : >> $port.expected
-    done
-    if test $offer_ip != 0; then
-        local srv_mac=$1 srv_ip=$2 dhcp_reply_type=$3 expected_dhcp_opts=$4
-        # total IP length will be the IP length of the request packet
-        # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2)
-        ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
-        udp_len=`expr $ip_len - 20`
-        ip_len=$(printf "%x" $ip_len)
-        udp_len=$(printf "%x" $udp_len)
-        # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len
-        local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip}
-        # udp header and dhcp header.
-        # $udp_len var will be in 3 digits. So adding a '0' before $udp_len
-        reply=${reply}004300440${udp_len}0000020106006359aa7600000000${ciaddr}
-        # your ip address; 0 for NAK
-        if test $dhcp_reply_type = 06; then
-            reply=${reply}00000000
-        else
-            reply=${reply}${offer_ip}
-        fi
-        # next server ip address, relay agent ip address, client mac address
-        reply=${reply}0000000000000000${src_mac}
-        # client hardware padding
-        reply=${reply}00000000000000000000
-        # server hostname
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        # boot file name
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-        # dhcp magic cookie
-        reply=${reply}63825363
-        reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
-        echo $reply >> $inport.expected
-    else
-        for outport; do
-            echo $request >> $outport.expected
-        done
-    fi
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-AT_CAPTURE_FILE([ofctl_monitor0.log])
-as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-# Send DHCPDISCOVER.
-offer_ip=`ip_to_hex 10 0 0 4`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=`ip_to_hex 0 0 0 0`
-request_ip=0
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000001 01 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
-
-# ovs-ofctl also resumes the packets and this causes other ports to receive
-# the DHCP request packet. So reset the pcap files so that its easier to test.
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with the offered IP
-# address in the Requested IP Address option.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=`ip_to_hex 0 0 0 0`
-request_ip=$offer_ip
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with a mismatched IP in
-# the Requested IP Address option, expect a DHCPNAK.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=`ip_to_hex 0 0 0 0`
-request_ip=`ip_to_hex 10 0 0 7`
-expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 3.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send Invalid DHCPv4 packet on ls1-lp2. It should be received by ovn-controller
-# but should be resumed without the reply.
-# ls1-lp1 (vif1-tx.pcap) should receive the DHCPv4 request packet twice,
-# one from ovn-controller and the other from "ovs-ofctl resume."
-ciaddr=`ip_to_hex 0 0 0 0`
-offer_ip=0
-request_ip=0
-test_dhcp 2 f00000000002 08 $ciaddr $offer_ip $request_ip 0 1 1
-
-# NXT_RESUMEs should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-# vif1-tx.pcap should have received the DHCPv4 (invalid) request packet
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined.
-# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
-
-ciaddr=`ip_to_hex 0 0 0 0`
-test_dhcp 3 f00000000003 01 $ciaddr 0 0 4 0
-
-# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
-# this lport.
-ciaddr=`ip_to_hex 0 0 0 0`
-test_dhcp 4 f00000000004 01 $ciaddr 0 0 3 0
-
-# NXT_RESUMEs should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
-#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
-
-# Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
-# and ip4.dst set to 10.0.0.1.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=$offer_ip
-request_ip=0
-src_ip=$offer_ip
-dst_ip=$server_ip
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 5.
-OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
-# and ip4.dst set to 255.255.255.255.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=$offer_ip
-request_ip=0
-src_ip=$offer_ip
-dst_ip=`ip_to_hex 255 255 255 255`
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 6.
-OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST in the RENEWING/REBINDING state with a mismatched IP in the
-# ciaddr, expect a DHCPNAK.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=`ip_to_hex 10 0 0 7`
-request_ip=0
-src_ip=$offer_ip
-dst_ip=`ip_to_hex 255 255 255 255`
-expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 7.
-OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST in the RENEWING/REBINDING state without a specifyied ciaddr,
-# expect a DHCPNAK.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-ciaddr=`ip_to_hex 0 0 0 0`
-request_ip=0
-src_ip=$offer_ip
-dst_ip=`ip_to_hex 255 255 255 255`
-expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
-
-# NXT_RESUMEs should be 8.
-OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Send DHCPREQUEST with ip4.src set to 10.0.0.6 and ip4.dst set to 10.0.0.4.
-# The packet should not be received by ovn-controller.
-ciaddr=`ip_to_hex 0 0 0 0`
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 4`
-test_dhcp 2 f00000000002 03 $ciaddr 0 0 1 $src_ip $dst_ip 1
-
-# NXT_RESUMEs should be 8.
-OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-# vif1-tx.pcap should have received the DHCPv4 request packet
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- dhcpv6 : 1 HV, 2 LS, 5 LSPs])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
-
-ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
-
-ovn-nbctl lsp-add ls1 ls1-lp2 \
--- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 ae70::5"
-
-ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 ae70::5"
-
-ovn-nbctl lsp-add ls1 ls1-lp3 \
--- lsp-set-addresses ls1-lp3 "f0:00:00:00:00:22 ae70::22"
-
-ovn-nbctl lsp-set-port-security ls1-lp3 "f0:00:00:00:00:22 ae70::22"
-
-d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
-options="\"server_id\"=\"00:00:00:10:00:01\"")"
-
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d1}
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
-
-d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
-options="\"dhcpv6_stateless\"=\"true\" \"server_id\"=\"00:00:00:10:00:01\"")"
-
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp3 ${d2}
-
-ovn-nbctl ls-add ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 be70::3"
-ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 be70::3"
-ovn-nbctl lsp-add ls2 ls2-lp2 \
--- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 be70::4"
-ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 be70::4"
-
-net_add n1
-sim_add hv1
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-ovs-vsctl -- add-port br-int hv1-vif3 -- \
-    set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=hv1/vif3-tx.pcap \
-    options:rxq_pcap=hv1/vif3-rx.pcap \
-    ofport-request=3
-
-ovs-vsctl -- add-port br-int hv1-vif4 -- \
-    set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
-    options:tx_pcap=hv1/vif4-tx.pcap \
-    options:rxq_pcap=hv1/vif4-rx.pcap \
-    ofport-request=4
-
-ovs-vsctl -- add-port br-int hv1-vif5 -- \
-    set interface hv1-vif5 external-ids:iface-id=ls1-lp3 \
-    options:tx_pcap=hv1/vif5-tx.pcap \
-    options:rxq_pcap=hv1/vif5-rx.pcap \
-    ofport-request=5
-
-OVN_POPULATE_ARP
-
-sleep 2
-
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-# This shell function sends a DHCPv6 request packet
-# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
-# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
-# packet should be received twice (one from ovn-controller and the other
-# from the "ovs-ofctl monitor br-int resume"
-test_dhcpv6() {
-    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
-    if test $msg_code != 0b; then
-        req_len=2a
-    else
-        req_len=1a
-    fi
-    local request=ffffffffffff${src_mac}86dd0000000000${req_len}1101${src_lla}
-    # dst ip ff02::1:2
-    request=${request}ff020000000000000000000000010002
-    # udp header and dhcpv6 header
-    request=${request}0222022300${req_len}ffff${msg_code}010203
-    # Client identifier
-    request=${request}0001000a00030001${src_mac}
-    # Add IA-NA (Identity Association for Non Temporary Address) if msg_code
-    # is not 11 (information request packet)
-    if test $msg_code != 0b; then
-        request=${request}0003000c0102030400000e1000001518
-    fi
-    shift; shift; shift; shift; shift;
-    if test $offer_ip != 0; then
-        local server_mac=000000100001
-        local server_lla=fe80000000000000020000fffe100001
-        local reply_code=07
-        if test $msg_code = 01; then
-            reply_code=02
-        fi
-        local msg_len=54
-        if test $offer_ip = 1; then
-            msg_len=28
-        fi
-        local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101${server_lla}${src_lla}
-        # udp header and dhcpv6 header
-        reply=${reply}0223022200${msg_len}ffff${reply_code}010203
-        # Client identifier
-        reply=${reply}0001000a00030001${src_mac}
-        # IA-NA
-        if test $offer_ip != 1; then
-            reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
-        fi
-        # Server identifier
-        reply=${reply}0002000a00030001${server_mac}
-        echo $reply | trim_zeros >> $inport.expected
-    else
-        for outport; do
-            echo $request | trim_zeros >> $outport.expected
-        done
-    fi
-
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-AT_CAPTURE_FILE([ofctl_monitor0.log])
-as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-src_mac=f00000000001
-src_lla=fe80000000000000f20000fffe000001
-offer_ip=ae700000000000000000000000000004
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
-
-# NXT_RESUMEs should be 1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets
-# cat 1.expected | trim_zeros > expout
-cat 1.expected | cut -c -120 > expout
-AT_CHECK([cat 1.packets | cut -c -120], [0], [expout])
-# Skipping the UDP checksum
-cat 1.expected | cut -c 125- > expout
-AT_CHECK([cat 1.packets | cut -c 125-], [0], [expout])
-
-rm  1.expected
-
-# Send invalid packet on ls1-lp2. ovn-controller should resume the packet
-# without any modifications and the packet should be received by ls1-lp1.
-# ls1-lp1 will receive the packet twice, one from the ovn-controller after the
-# resume and the other from ovs-ofctl monitor resume.
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-
-src_mac=f00000000002
-src_lla=fe80000000000000f20000fffe000002
-offer_ip=ae700000000000000000000000000005
-# Set invalid msg_type
-
-test_dhcpv6 2 $src_mac $src_lla 10 0 1 1
-
-# NXT_RESUMEs should be 2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-# vif2-tx.pcap should not have received the DHCPv6 reply packet
-rm 2.packets
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > 2.packets
-AT_CHECK([cat 2.packets], [0], [])
-
-# vif1-tx.pcap should have received the DHCPv6 (invalid) request packet
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets
-cat 1.expected > expout
-AT_CHECK([cat 1.packets], [0], [expout])
-
-# Send DHCPv6 packet on ls2-lp1. native DHCPv6 is disabled on this port.
-# There should be no DHCPv6 reply from ovn-controller and the request packet
-# should be received by ls2-lp2.
-
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-test_dhcpv6 3 $src_mac $src_lla 01 0 4
-
-# NXT_RESUMEs should be 2 only.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-# vif3-tx.pcap should not have received the DHCPv6 reply packet
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets
-AT_CHECK([cat 3.packets], [0], [])
-
-# vif4-tx.pcap should have received the DHCPv6 request packet
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets
-cat 4.expected > expout
-AT_CHECK([cat 4.packets], [0], [expout])
-
-# Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode for this port.
-# The DHCPv6 reply shouldn't contain offer_ip.
-src_mac=f00000000022
-src_lla=fe80000000000000f20000fffe000022
-reset_pcap_file hv1-vif5 hv1/vif5
-test_dhcpv6 5 $src_mac $src_lla 01 1 5
-
-# NXT_RESUMEs should be 3.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif5-tx.pcap | trim_zeros > 5.packets
-# Skipping the UDP checksum
-cat 5.expected | cut -c 1-120,125- > expout
-AT_CHECK([cat 5.packets | cut -c 1-120,125- ], [0], [expout])
-
-# Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6 reply
-# shouldn't contain offer_ip
-src_mac=f00000000022
-src_lla=fe80000000000000f20000fffe000022
-reset_pcap_file hv1-vif5 hv1/vif5
-rm -f 5.expected
-test_dhcpv6 5 $src_mac $src_lla 0b 1 5
-
-# NXT_RESUMEs should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif5-tx.pcap |
-trim_zeros > 5.packets
-# Skipping the UDP checksum
-cat 5.expected | cut -c 1-120,125- > expout
-AT_CHECK([cat 5.packets | cut -c 1-120,125- ], [0], [expout])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24)
-# connected to it. R2 has alice (172.16.1.0/24) connected to it.
-# R2 is a gateway router.
-
-
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis="hv2"
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-
-#install static routes
-ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \
-ip_prefix=172.16.1.0/24 nexthop=20.0.0.2 -- add Logical_Router \
-R1 static_routes @lrt
-
-ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \
-ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \
-R2 static_routes @lrt
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port alice1 in alice
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 2
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and alice1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-ovn-sbctl list chassis
-ovn-sbctl list encap
-echo "---------------------"
-
-# Packet to Expect at alice1
-src_mac="000002010203"
-dst_mac="f00000010204"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
-
-
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet
-
-echo "------ hv1 dump after packet 1 ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump after packet 1 ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-echo "----------------------------"
-
-echo $expected > expected
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Delete the router and re-create it. Things should work as before.
-ovn-nbctl  lr-del R2
-ovn-nbctl create Logical_Router name=R2 options:chassis="hv2"
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-
-ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \
-ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \
-R2 static_routes @lrt
-
-# Wait for ovn-controller to catch up.
-sleep 1
-
-# Send the packet again.
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-echo "------ hv1 dump after packet 2 ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump after packet 2 ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-echo "----------------------------"
-
-echo $expected >> expected
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- icmp_reply: 1 HVs, 2 LSs, 1 lport/LS, 1 LR])
-AT_KEYWORDS([router-icmp-reply])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
-# and has switch ls2 (172.16.1.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
-    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
-
-# Connect ls2 to R1
-ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
-ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
-    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
-
-# Create logical port ls2-lp1 in ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
-
-# Create one hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int vif1 -- \
-    set interface vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif2 -- \
-    set interface vif2 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=1
-
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-for i in 1 2; do
-    : > vif$i.expected
-done
-# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM]
-#
-# Causes a packet to be received on INPORT.  The packet is an ICMPv4
-# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
-# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
-# provided, then it should be the ip and icmp checksums of the packet
-# responded; otherwise, no reply is expected.
-# In the absence of an ip checksum calculation helpers, this relies
-# on the caller to provide the checksums for the ip and icmp headers.
-# XXX This should be more systematic.
-#
-# INPORT is an lport number, e.g. 11 for vif11.
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC and IPV4_DST are each 8 hex digits.
-# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
-# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
-test_ipv4_icmp_request() {
-    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
-    local exp_ip_chksum=$8 exp_icmp_chksum=$9
-    shift; shift; shift; shift; shift; shift; shift
-    shift; shift
-
-    # Use ttl to exercise section 4.2.2.9 of RFC1812
-    local ip_ttl=01
-    local icmp_id=5fbf
-    local icmp_seq=0001
-    local icmp_data=$(seq 1 56 | xargs printf "%02x")
-    local icmp_type_code_request=0800
-    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
-    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
-
-    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
-    if test X$exp_icmp_chksum != X; then
-        # Expect to receive the reply, if any. In same port where packet was sent.
-        # Note: src and dst fields are expected to be reversed.
-        local icmp_type_code_response=0000
-        local reply_icmp_ttl=fe
-        local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
-        local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
-        echo $reply >> vif$inport.expected
-    fi
-}
-
-# Send ping packet to router's ip addresses, from each of the 2 logical ports.
-rtr_l1_ip=$(ip_to_hex 192 168 1 1)
-rtr_l2_ip=$(ip_to_hex 172 16 1 1)
-l1_ip=$(ip_to_hex 192 168 1 2)
-l2_ip=$(ip_to_hex 172 16 1 2)
-
-# Ping router ip address that is on same subnet as the logical port
-test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l1_ip 0000 8510 02ff 8d10
-test_ipv4_icmp_request 2 000000010204 0000000102f2 $l2_ip $rtr_l2_ip 0000 8510 02ff 8d10
-
-# Ping router ip address that is on the other side of the logical ports
-test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 02ff 8d10
-test_ipv4_icmp_request 2 000000010204 0000000102f2 $l2_ip $rtr_l1_ip 0000 8510 02ff 8d10
-
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list logical_flow
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-# Now check the packets actually received against the ones expected.
-for inport in 1 2; do
-    OVN_CHECK_PACKETS([hv1/vif${inport}-tx.pcap], [vif$inport.expected])
-done
-
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- policy-based routing: 1 HVs, 2 LSs, 1 lport/LS, 1 LR])
-AT_KEYWORDS([policy-based-routing])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
-# and has switch ls2 (172.16.1.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-ovn-nbctl ls-add ls3
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
-    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
-
-# Connect ls2 to R1
-ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
-ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
-    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
-
-# Connect ls3 to R1
-ovn-nbctl lrp-add R1 ls3 00:00:00:01:02:f3 20.20.1.1/24
-ovn-nbctl lsp-add ls3 rp-ls3 -- set Logical_Switch_Port rp-ls3 \
-    type=router options:router-port=ls3 addresses=\"00:00:00:01:02:f3\"
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
-
-# Create logical port ls2-lp1 in ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
-
-# Create logical port ls3-lp1 in ls3
-ovn-nbctl lsp-add ls3 ls3-lp1 \
--- lsp-set-addresses ls3-lp1 "00:00:00:01:02:05 20.20.1.2"
-
-# Create one hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add pbr-hv
-as pbr-hv
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-
-ovs-vsctl -- add-port br-int vif1 -- \
-    set interface vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=pbr-hv/vif1-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif2 -- \
-    set interface vif2 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=pbr-hv/vif2-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif2-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif3 -- \
-    set interface vif3 external-ids:iface-id=ls3-lp1 \
-    options:tx_pcap=pbr-hv/vif3-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif3-rx.pcap \
-    ofport-request=1
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ls1_ro_mac=00:00:00:01:02:f1
-ls1_ro_ip=192.168.1.1
-
-ls2_ro_mac=00:00:00:01:02:f2
-ls2_ro_ip=172.16.1.1
-
-ls3_ro_mac=00:00:00:01:02:f3
-
-ls1_p1_mac=00:00:00:01:02:03
-ls1_p1_ip=192.168.1.2
-
-ls2_p1_mac=00:00:00:01:02:04
-ls2_p1_ip=172.16.1.2
-
-ls3_p1_mac=00:00:00:01:02:05
-
-# Create a drop policy
-ovn-nbctl lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl
-1
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Check if packet hit the drop policy
-AT_CHECK([ovs-ofctl dump-flows br-int | \
-    grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24 actions=drop" | \
-    grep "priority=10" | \
-    grep "n_packets=1" | wc -l], [0], [dnl
-1
-])
-
-# Expected to drop the packet.
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
-rcvd_packet=`cat vif2.packets`
-AT_FAIL_IF([rcvd_packet = ""])
-
-# Override drop policy with allow
-ovn-nbctl lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl
-2
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Check if packet hit the allow policy
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
-    grep "192.168.1.0" | \
-    grep "priority=20" | wc -l], [0], [dnl
-1
-])
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
-       ip4 && ip.ttl==63 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > expected
-
-OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected])
-
-# Override allow policy with reroute
-ovn-nbctl lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" reroute 20.20.1.2
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
-    grep "192.168.1.0" | \
-    grep "priority=30" | wc -l], [0], [dnl
-1
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-echo "southbound flows"
-
-ovn-sbctl dump-flows | grep lr_in_policy
-echo "ovs flows"
-ovs-ofctl dump-flows br-int
-# Check if packet hit the allow policy
-AT_CHECK([ovs-ofctl dump-flows br-int | \
-    grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" | \
-    grep "priority=30" | \
-    grep "n_packets=1" | wc -l], [0], [dnl
-1
-])
-echo "packet hit reroute policy"
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac &&
-       ip4 && ip.ttl==63 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > 3.expected
-
-OVN_CHECK_PACKETS([pbr-hv/vif3-tx.pcap], [3.expected])
-
-OVN_CLEANUP([pbr-hv])
-AT_CLEANUP
-
-AT_SETUP([ovn -- policy-based routing IPv6: 1 HVs, 3 LSs, 1 lport/LS, 1 LR])
-AT_KEYWORDS([policy-based-routing])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
-# and has switch ls2 (172.16.1.0/24) connected to it.
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-ovn-nbctl ls-add ls3
-
-# Connect ls1 to R1
-ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 2001::1/64
-ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
-    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
-
-# Connect ls2 to R1
-ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 2002::1/64
-ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
-    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
-
-# Connect ls3 to R1
-ovn-nbctl lrp-add R1 ls3 00:00:00:01:02:f3 2003::1/64
-ovn-nbctl lsp-add ls3 rp-ls3 -- set Logical_Switch_Port rp-ls3 \
-    type=router options:router-port=ls3 addresses=\"00:00:00:01:02:f3\"
-
-# Create logical port ls1-lp1 in ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 2001::2"
-
-# Create logical port ls2-lp1 in ls2
-ovn-nbctl lsp-add ls2 ls2-lp1 \
--- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 2002::2"
-
-# Create logical port ls3-lp1 in ls3
-ovn-nbctl lsp-add ls3 ls3-lp1 \
--- lsp-set-addresses ls3-lp1 "00:00:00:01:02:05 2003::2"
-
-# Create one hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add pbr-hv
-as pbr-hv
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-
-ovs-vsctl -- add-port br-int vif1 -- \
-    set interface vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=pbr-hv/vif1-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif2 -- \
-    set interface vif2 external-ids:iface-id=ls2-lp1 \
-    options:tx_pcap=pbr-hv/vif2-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif2-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int vif3 -- \
-    set interface vif3 external-ids:iface-id=ls3-lp1 \
-    options:tx_pcap=pbr-hv/vif3-tx.pcap \
-    options:rxq_pcap=pbr-hv/vif3-rx.pcap \
-    ofport-request=1
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ls1_ro_mac=00:00:00:01:02:f1
-ls1_ro_ip=2001::1
-
-ls2_ro_mac=00:00:00:01:02:f2
-ls2_ro_ip=2002::1
-
-ls3_ro_mac=00:00:00:01:02:f3
-
-ls1_p1_mac=00:00:00:01:02:03
-ls1_p1_ip=2001::2
-
-ls2_p1_mac=00:00:00:01:02:04
-ls2_p1_ip=2002::2
-
-ls3_p1_mac=00:00:00:01:02:05
-
-# Create a drop policy
-ovn-nbctl lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl
-1
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Check if packet hit the drop policy
-AT_CHECK([ovs-ofctl dump-flows br-int | \
-    grep "ipv6_src=2001::/64,ipv6_dst=2002::/64 actions=drop" | \
-    grep "priority=10" | \
-    grep "n_packets=1" | wc -l], [0], [dnl
-1
-])
-
-# Expected to drop the packet.
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
-rcvd_packet=`cat vif2.packets`
-AT_FAIL_IF([rcvd_packet = ""])
-
-# Override drop policy with allow
-ovn-nbctl lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl
-2
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Check if packet hit the allow policy
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
-    grep "2001" | \
-    grep "priority=20" | wc -l], [0], [dnl
-1
-])
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
-       ip6 && ip.ttl==63 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > expected
-
-OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected])
-
-# Override allow policy with reroute
-ovn-nbctl lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2
-
-# Check logical flow
-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
-    grep "2001" | \
-    grep "priority=30" | wc -l], [0], [dnl
-1
-])
-
-# Send packet.
-packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
-       ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-echo "southbound flows"
-
-ovn-sbctl dump-flows | grep lr_in_policy
-echo "ovs flows"
-ovs-ofctl dump-flows br-int
-# Check if packet hit the allow policy
-AT_CHECK([ovs-ofctl dump-flows br-int | \
-    grep "ipv6_src=2001::/64,ipv6_dst=2002::/64" | \
-    grep "priority=30" | \
-    grep "n_packets=1" | wc -l], [0], [dnl
-1
-])
-echo "packet hit reroute policy"
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac &&
-       ip6 && ip.ttl==63 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > 3.expected
-
-OVN_CHECK_PACKETS([pbr-hv/vif3-tx.pcap], [3.expected])
-
-OVN_CLEANUP([pbr-hv])
-AT_CLEANUP
-
-# 1 hypervisor, 1 port
-# make sure that the port state is properly set to up and back down
-# when created and deleted.
-AT_SETUP([ovn -- port state up and down])
-ovn_start
-
-ovn-nbctl ls-add ls1
-ovn-nbctl lsp-add ls1 lp1
-ovn-nbctl lsp-set-addresses lp1 unknown
-
-net_add n1
-sim_add hv1
-as hv1 ovs-vsctl add-br br-phys
-as hv1 ovn_attach n1 br-phys 192.168.0.1
-
-as hv1 ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup])
-
-as hv1 ovs-vsctl del-port br-int vif1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xdown])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-# 1 hypervisor, 1 port
-# make sure that the OF rules created to support a datapath are added/cleared
-# when logical switch is created and removed.
-AT_SETUP([ovn -- datapath rules added/removed])
-AT_KEYWORDS([cleanup])
-ovn_start
-
-net_add n1
-sim_add hv1
-as hv1 ovs-vsctl add-br br-phys
-as hv1 ovn_attach n1 br-phys 192.168.0.1
-
-# This shell function checks if OF rules in br-int have clauses
-# related to OVN datapaths. The caller determines if it should find
-# a match in the output, or not.
-#
-# EXPECT_DATAPATH param determines whether flows that refer to
-#                 datapath to should be present or not. 0 means
-#                 they should not be.
-# STAGE_INFO param is a simple string to help identify the stage
-#            in the test when this function was invoked.
-test_datapath_in_of_rules() {
-    local expect_datapath=$1 stage_info=$2
-    echo "------ ovn-nbctl show ${stage_info} ------"
-    ovn-nbctl show
-    echo "------ ovn-sbctl show ${stage_info} ------"
-    ovn-sbctl show
-    echo "------ OF rules ${stage_info} ------"
-    AT_CHECK([ovs-ofctl dump-flows br-int], [0], [stdout])
-    # if there is a datapath mentioned in the output, check for the
-    # magic keyword that represents one, based on the exit status of
-    # a quiet grep
-    if test $expect_datapath != 0; then
-       AT_CHECK([grep -q -i 'metadata=' stdout], [0], [ignore-nolog])
-    else
-       AT_CHECK([grep -q -i 'metadata=' stdout], [1], [ignore-nolog])
-    fi
-}
-
-test_datapath_in_of_rules 0 "before ls+port create"
-
-ovn-nbctl ls-add ls1
-ovn-nbctl lsp-add ls1 lp1
-ovn-nbctl lsp-set-addresses lp1 unknown
-
-as hv1 ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup])
-
-test_datapath_in_of_rules 1 "after port is bound"
-
-as hv1 ovs-vsctl del-port br-int vif1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xdown])
-
-ovn-nbctl lsp-set-addresses lp1
-ovn-nbctl lsp-del lp1
-ovn-nbctl ls-del ls1
-
-# wait for earlier changes to take effect
-AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore])
-
-# ensure OF rules are no longer present. There used to be a bug here.
-test_datapath_in_of_rules 0 "after lport+ls removal"
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- nd_na ])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-#TODO: since patch port for IPv6 logical router port is not ready not,
-#  so we are not going to test vifs on different lswitches cases. Try
-#  to update for that once relevant stuff implemented.
-
-# In this test cases we create 1 lswitch, it has 2 VIF ports attached
-# with. NS packet we test, from one VIF for another VIF, will be replied
-# by local ovn-controller, but not by target VIF.
-
-# Create hypervisors and logical switch lsw0.
-ovn-nbctl ls-add lsw0
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1.
-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
-ovn-nbctl lsp-add lsw0 lp1
-ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598"
-ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598"
-
-# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2.
-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2
-ovn-nbctl lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae"
-ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae"
-
-# Add ACL rule for ICMPv6 on lsw0
-ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6'  allow-related
-ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6'  allow-related
-ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6'  allow-related
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv1${1%?}
-}
-for i in 1 2; do
-    : > $i.expected
-done
-
-# Complete Neighbor Solicitation packet and Neighbor Advertisement packet
-# vif1 -> NS -> vif2.  vif1 <- NA <- ovn-controller.
-# vif2 will not receive NS packet, since ovn-controller will reply for it.
-ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598
-na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae
-
-as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet
-echo $na_packet >> 1.expected
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-ofctl -O OpenFlow13 show br-int
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-
-for i in 1 2; do
-    OVN_CHECK_PACKETS([hv1/vif$i-tx.pcap], [$i.expected])
-done
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- address sets modification/removal smoke test])
-ovn_start
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-
-row=`ovn-nbctl create Address_Set name=set1 addresses=\"1.1.1.1\"`
-ovn-nbctl set Address_Set $row name=set1 addresses=\"1.1.1.1,1.1.1.2\"
-ovn-nbctl destroy Address_Set $row
-
-sleep 1
-
-# A bug previously existed in the address set support code
-# that caused ovn-controller to crash after an address set
-# was updated and then removed.  This test case ensures
-# that ovn-controller is at least still running after
-# creating, updating, and deleting an address set.
-AT_CHECK([ovs-appctl -t ovn-controller version], [0], [ignore])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ipam])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Add a port to a switch that does not have a subnet set, then set the
-# subnet which should result in an address being allocated for the port.
-ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
-ovn-nbctl ls-add sw0
-ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic
-ovn-nbctl --wait=sb add Logical-Switch sw0 other_config subnet=192.168.1.0/24
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:03 192.168.1.2"
-])
-
-# Add 9 more ports to sw0, addresses should all be unique.
-for n in `seq 1 9`; do
-    ovn-nbctl --wait=sb lsp-add sw0 "p$n" -- lsp-set-addresses "p$n" dynamic
-done
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p1 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:04 192.168.1.3"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p2 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:05 192.168.1.4"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p3 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:06 192.168.1.5"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p4 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:07 192.168.1.6"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p5 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:08 192.168.1.7"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p6 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:09 192.168.1.8"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p7 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:0a 192.168.1.9"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p8 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:0b 192.168.1.10"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p9 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:0c 192.168.1.11"
-])
-
-# Trying similar tests with a second switch. MAC addresses should be unique
-# across both switches but IP's only need to be unique within the same switch.
-ovn-nbctl ls-add sw1
-ovn-nbctl lsp-add sw1 p10 -- lsp-set-addresses p10 dynamic
-ovn-nbctl --wait=sb add Logical-Switch sw1 other_config subnet=192.168.1.0/24
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p10 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:0d 192.168.1.2"
-])
-
-for n in `seq 11 19`; do
-    ovn-nbctl --wait=sb lsp-add sw1 "p$n" -- lsp-set-addresses "p$n" dynamic
-done
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p11 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:0e 192.168.1.3"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p12 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:0f 192.168.1.4"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p13 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:10 192.168.1.5"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p14 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:11 192.168.1.6"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p15 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:12 192.168.1.7"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p16 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:13 192.168.1.8"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p17 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:14 192.168.1.9"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p18 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:15 192.168.1.10"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p19 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:16 192.168.1.11"
-])
-
-# Change a port's address to test for multiple ip's for a single address entry
-# and addresses set by the user.
-ovn-nbctl lsp-set-addresses p0 "0a:00:00:a8:01:17 192.168.1.2 192.168.1.12 192.168.1.14"
-ovn-nbctl --wait=sb lsp-add sw0 p20 -- lsp-set-addresses p20 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p20 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:18 192.168.1.13"
-])
-
-# Test for logical router port address management.
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw0 \
-network="192.168.1.1/24" mac=\"0a:00:00:a8:01:19\" \
--- add Logical_Router R1 ports @lrp -- lsp-add sw0 rp-sw0 \
--- set Logical_Switch_Port rp-sw0 type=router options:router-port=sw0
-ovn-nbctl --wait=sb lsp-add sw0 p21 -- lsp-set-addresses p21 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p21 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:1a 192.168.1.15"
-])
-
-# Test for address reuse after logical port is deleted.
-ovn-nbctl lsp-del p0
-ovn-nbctl --wait=sb lsp-add sw0 p23 -- lsp-set-addresses p23 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p23 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:03 192.168.1.2"
-])
-
-# Test for multiple addresses to one logical port.
-ovn-nbctl lsp-add sw0 p25 -- lsp-set-addresses p25 \
-"0a:00:00:a8:01:1b 192.168.1.12" "0a:00:00:a8:01:1c 192.168.1.14"
-ovn-nbctl --wait=sb lsp-add sw0 p26 -- lsp-set-addresses p26 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p26 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:17 192.168.1.16"
-])
-
-# Test for exhausting subnet address space.
-ovn-nbctl ls-add sw2 -- add Logical-Switch sw2 other_config subnet=172.16.1.0/30
-ovn-nbctl --wait=sb lsp-add sw2 p27 -- lsp-set-addresses p27 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p27 dynamic_addresses], [0],
-     ["0a:00:00:10:01:03 172.16.1.2"
-])
-
-ovn-nbctl --wait=sb lsp-add sw2 p28 -- lsp-set-addresses p28 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p28 dynamic_addresses], [0],
-     ["0a:00:00:00:00:01"
-])
-
-# Test that address management does not add duplicate MAC for lsp/lrp peers.
-ovn-nbctl create Logical_Router name=R2
-ovn-nbctl ls-add sw3
-ovn-nbctl lsp-add sw3 p29 -- lsp-set-addresses p29 \
-"0a:00:00:a8:01:18"
-ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw3 \
-network="192.168.2.1/24" mac=\"0a:00:00:a8:01:18\" \
--- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \
--- set Logical_Switch_Port rp-sw3 type=router options:router-port=sw3
-ovn-nbctl --wait=sb lsp-add sw0 p30 -- lsp-set-addresses p30 dynamic
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p30 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:1d 192.168.1.17"
-])
-
-# Test static MAC address with dynamically allocated IP
-ovn-nbctl --wait=sb lsp-add sw0 p31 -- lsp-set-addresses p31 \
-"fe:dc:ba:98:76:54 dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0],
-     ["fe:dc:ba:98:76:54 192.168.1.18"
-])
-
-# Update the static MAC address with dynamically allocated IP and check
-# if the MAC address is updated in 'Logical_Switch_Port.dynamic_adddresses'
-ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:55 dynamic"
-
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0],
-     ["fe:dc:ba:98:76:55 192.168.1.18"
-])
-
-ovn-nbctl --wait=sb lsp-set-addresses p31 "dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:1e 192.168.1.18"
-])
-
-ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:56 dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0],
-     ["fe:dc:ba:98:76:56 192.168.1.18"
-])
-
-
-# Test the exclude_ips from the IPAM list
-ovn-nbctl --wait=sb set logical_switch sw0 \
-other_config:exclude_ips="192.168.1.19 192.168.1.21 192.168.1.23..192.168.1.50"
-
-ovn-nbctl --wait=sb lsp-add sw0 p32 -- lsp-set-addresses p32 \
-"dynamic"
-# 192.168.1.20 should be assigned as 192.168.1.19 is excluded.
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p32 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:1e 192.168.1.20"
-])
-
-ovn-nbctl --wait=sb lsp-add sw0 p33 -- lsp-set-addresses p33 \
-"dynamic"
-# 192.168.1.22 should be assigned as 192.168.1.21 is excluded.
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p33 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:1f 192.168.1.22"
-])
-
-ovn-nbctl --wait=sb lsp-add sw0 p34 -- lsp-set-addresses p34 \
-"dynamic"
-# 192.168.1.51 should be assigned as 192.168.1.23-192.168.1.50 is excluded.
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p34 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:34 192.168.1.51"
-])
-
-# Now clear the exclude_ips list. 192.168.1.19 should be assigned.
-ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="invalid"
-ovn-nbctl --wait=sb lsp-add sw0 p35 -- lsp-set-addresses p35 \
-"dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p35 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:20 192.168.1.19"
-])
-
-# Set invalid data in exclude_ips list. It should be ignored.
-ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="182.168.1.30"
-ovn-nbctl --wait=sb lsp-add sw0 p36 -- lsp-set-addresses p36 \
-"dynamic"
-# 192.168.1.21 should be assigned as that's the next free one.
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:21 192.168.1.21"
-])
-
-# Clear the dynamic addresses assignment request.
-ovn-nbctl --wait=sb clear logical_switch_port p36 addresses
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0],
-         [[[]]
-])
-
-# Set IPv6 prefix
-ovn-nbctl --wait=sb set Logical-switch sw0 other_config:ipv6_prefix="aef0::"
-ovn-nbctl --wait=sb lsp-add sw0 p37 -- lsp-set-addresses p37 \
-"dynamic"
-
-# With prefix aef0 and mac 0a:00:00:00:00:26, the dynamic IPv6 should be
-# - aef0::800:ff:fe00:26 (EUI64)
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p37 dynamic_addresses], [0],
-     ["0a:00:00:a8:01:21 192.168.1.21 aef0::800:ff:fea8:121"
-])
-
-ovn-nbctl --wait=sb ls-add sw4
-ovn-nbctl --wait=sb set Logical-switch sw4 other_config:ipv6_prefix="bef0::" \
--- set Logical-switch sw4 other_config:subnet=192.168.2.0/30
-ovn-nbctl --wait=sb lsp-add sw4 p38 -- lsp-set-addresses p38 \
-"dynamic"
-
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p38 dynamic_addresses], [0],
-     ["0a:00:00:a8:02:03 192.168.2.2 bef0::800:ff:fea8:203"
-])
-
-ovn-nbctl --wait=sb lsp-add sw4 p39 -- lsp-set-addresses p39 \
-"f0:00:00:00:10:12 dynamic"
-
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p39 dynamic_addresses], [0],
-     ["f0:00:00:00:10:12 bef0::f200:ff:fe00:1012"
-])
-
-# Test the case where IPv4 addresses are exhausted and IPv6 prefix is set
-# p40 should not have an IPv4 address since the pool is exhausted
-ovn-nbctl --wait=sb lsp-add sw4 p40 -- lsp-set-addresses p40 \
-"dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p40 dynamic_addresses], [0],
-         ["0a:00:00:00:00:02 bef0::800:ff:fe00:2"
-])
-
-# Test dynamic changes on switch ports.
-#
-ovn-nbctl --wait=sb ls-add sw5
-ovn-nbctl --wait=sb lsp-add sw5 p41 -- lsp-set-addresses p41 \
-"dynamic"
-# p41 will start with nothing
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         [[[]]
-])
-
-# Set a subnet. Now p41 should have an ipv4 address, too
-ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["0a:00:00:a8:01:22 192.168.1.2"
-])
-
-# Clear the other_config. The IPv4 address should be gone
-ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         [[[]]
-])
-
-# Set an IPv6 prefix. Now p41 should have an IPv6 address.
-ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="aef0::"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["0a:00:00:00:00:03 aef0::800:ff:fe00:3"
-])
-
-# Change the MAC address to a static one. The IPv6 address should update.
-ovn-nbctl --wait=sb lsp-set-addresses p41 "f0:00:00:00:10:2b dynamic"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["f0:00:00:00:10:2b aef0::f200:ff:fe00:102b"
-])
-
-# Change the IPv6 prefix. The IPv6 address should update.
-ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="bef0::"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["f0:00:00:00:10:2b bef0::f200:ff:fe00:102b"
-])
-
-# Clear the other_config. The IPv6 address should be gone
-ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         [[[]]
-])
-
-# Set the subnet again. Now p41 should get the IPv4 address again.
-ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["f0:00:00:00:10:2b 192.168.1.2"
-])
-
-# Add an excluded IP address that conflicts with p41. p41 should update.
-ovn-nbctl --wait=sb add Logical-Switch sw5 other_config \
-exclude_ips="192.168.1.2"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["f0:00:00:00:10:2b 192.168.1.3"
-])
-
-# Add static ip address
-ovn-nbctl --wait=sb lsp-set-addresses p41 "dynamic 192.168.1.100"
-ovn-nbctl list Logical-Switch-Port p41
-ovn-nbctl --wait=sb lsp-add sw5 p42 -- lsp-set-addresses p42 \
-"dynamic 192.168.1.101"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
-         ["0a:00:00:a8:01:65 192.168.1.100"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p42 dynamic_addresses], [0],
-         ["0a:00:00:a8:01:66 192.168.1.101"
-])
-
-# define a mac address prefix
-ovn-nbctl ls-add sw6
-ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22:33:44:55"
-ovn-nbctl --wait=sb set Logical-Switch sw6 other_config:subnet=192.168.100.0/24
-for n in $(seq 1 3); do
-    ovn-nbctl --wait=sb lsp-add sw6 "p5$n" -- lsp-set-addresses "p5$n" dynamic
-done
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p51 dynamic_addresses], [0],
-    ["00:11:22:a8:64:03 192.168.100.2"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p52 dynamic_addresses], [0],
-    ["00:11:22:a8:64:04 192.168.100.3"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p53 dynamic_addresses], [0],
-    ["00:11:22:a8:64:05 192.168.100.4"
-])
-
-# verify configuration order does not break IPAM/MACAM
-ovn-nbctl ls-add sw7
-for n in $(seq 1 3); do
-    ovn-nbctl --wait=sb lsp-add sw7 "p7$n" -- lsp-set-addresses "p7$n" dynamic
-done
-ovn-nbctl --wait=sb set Logical-Switch sw7 other_config:ipv6_prefix="bef0::"
-p71_addr=$(ovn-nbctl get Logical-Switch-Port p71 dynamic_addresses)
-p72_addr=$(ovn-nbctl get Logical-Switch-Port p72 dynamic_addresses)
-p73_addr=$(ovn-nbctl get Logical-Switch-Port p73 dynamic_addresses)
-AT_CHECK([test "$p71_addr" != "$p72_addr"], [0], [])
-AT_CHECK([test "$p71_addr" != "$p73_addr"], [0], [])
-AT_CHECK([test "$p72_addr" != "$p73_addr"], [0], [])
-
-# request to assign mac only
-#
-ovn-nbctl ls-add sw8
-ovn-nbctl --wait=sb set Logical-Switch sw8 other_config:mac_only=true
-for n in $(seq 1 3); do
-    ovn-nbctl --wait=sb lsp-add sw8 "p8$n" -- lsp-set-addresses "p8$n" dynamic
-done
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p81 dynamic_addresses], [0],
-    ["00:11:22:00:00:06"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p82 dynamic_addresses], [0],
-    ["00:11:22:00:00:07"
-])
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p83 dynamic_addresses], [0],
-    ["00:11:22:00:00:08"
-])
-
-# clear mac_prefix and check it is allocated in a random manner
-ovn-nbctl --wait=hv remove NB_Global . options mac_prefix
-ovn-nbctl ls-add sw9
-ovn-nbctl --wait=sb set Logical-Switch sw9 other_config:mac_only=true
-ovn-nbctl --wait=sb lsp-add sw9 p91 -- lsp-set-addresses p91 dynamic
-
-mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \")
-port_addr=$(ovn-nbctl get Logical-Switch-Port p91 dynamic_addresses | tr -d \")
-AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:09"], [0], [])
-
-ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22"
-ovn-nbctl ls-add sw10
-ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:ipv6_prefix="ae01::"
-ovn-nbctl --wait=sb lsp-add sw10 p101 -- lsp-set-addresses p101 "dynamic ae01::1"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p101 dynamic_addresses], [0],
-    ["00:11:22:00:00:0a ae01::1"
-])
-
-ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:subnet=192.168.110.0/24
-ovn-nbctl --wait=sb lsp-add sw10 p102 -- lsp-set-addresses p102 "dynamic 192.168.110.10 ae01::2"
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p102 dynamic_addresses], [0],
-    ["00:11:22:a8:6e:0b 192.168.110.10 ae01::2"
-])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as northd-backup
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ipam connectivity])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl lr-add R1
-
-# Test for a ping using dynamically allocated addresses.
-ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
-ovn-nbctl ls-add foo -- add Logical_Switch foo other_config subnet=192.168.1.0/24
-ovn-nbctl ls-add alice -- add Logical_Switch alice other_config subnet=192.168.2.0/24
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-          options:router-port=foo \
-          -- lsp-set-addresses rp-foo router
-
-# Connect alice to R1
-ovn-nbctl lrp-add R1 alice 00:00:00:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice type=router \
-          options:router-port=alice addresses=\"00:00:00:01:02:04\"
-
-# Create logical port foo1 in foo
-ovn-nbctl --wait=sb lsp-add foo foo1 \
--- lsp-set-addresses foo1 "dynamic"
-AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo1 dynamic_addresses='"0a:00:00:a8:01:03 192.168.1.2"'], [0])
-
-# Create logical port alice1 in alice
-ovn-nbctl --wait=sb lsp-add alice alice1 \
--- lsp-set-addresses alice1 "dynamic"
-AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port alice1 dynamic_addresses='"0a:00:00:a8:02:03 192.168.2.2"'])
-
-# Create logical port foo2 in foo
-ovn-nbctl --wait=sb lsp-add foo foo2 \
--- lsp-set-addresses foo2 "dynamic"
-AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo2 dynamic_addresses='"0a:00:00:a8:01:04 192.168.1.3"'])
-
-# Create a hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=foo2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-ovs-vsctl -- add-port br-int hv1-vif3 -- \
-    set interface hv1-vif3 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv1/vif3-tx.pcap \
-    options:rxq_pcap=hv1/vif3-rx.pcap \
-    ofport-request=3
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and foo2
-src_mac="0a0000a80103"
-dst_mac="0a0000a80104"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 1 3`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-# Send ip packets between foo1 and alice1
-src_mac="0a0000a80103"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-
-# Packet to Expect at foo2
-src_mac="0a0000a80103"
-dst_mac="0a0000a80104"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 1 3`
-expected=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > received1.packets
-echo $expected > expout
-AT_CHECK([cat received1.packets], [0], [expout])
-
-# Packet to Expect at alice1
-src_mac="000000010204"
-dst_mac="0a0000a80203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > received2.packets
-echo $expected > expout
-AT_CHECK([cat received2.packets], [0], [expout])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ovs-vswitchd restart])
-AT_KEYWORDS([vswitchd])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-net_add n1
-sim_add hv1
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-OVN_POPULATE_ARP
-sleep 2
-
-as hv1 ovs-vsctl show
-
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl dump-flows br-int
-total_flows=`as hv1 ovs-ofctl dump-flows br-int | wc -l`
-
-echo "Total flows before vswitchd restart = " $total_flows
-
-# Code taken from ovs-save utility
-save_flows () {
-    echo "ovs-ofctl add-flows br-int - << EOF" > restore_flows.sh
-    as hv1 ovs-ofctl dump-flows "br-int" | sed -e '/NXST_FLOW/d' \
-            -e 's/\(idle\|hard\)_age=[^,]*,//g' >> restore_flows.sh
-    echo "EOF" >> restore_flows.sh
-}
-
-restart_vswitchd () {
-    restore_flows=$1
-
-    if test $restore_flows = true; then
-        save_flows
-    fi
-
-    as hv1
-    OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
-
-    if test $restore_flows = true; then
-        as hv1
-        ovs-vsctl --no-wait set open_vswitch . other_config:flow-restore-wait="true"
-    fi
-
-    as hv1
-    start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl
-    ovs-ofctl dump-flows br-int
-
-    if test $restore_flows = true; then
-        sh ./restore_flows.sh
-        echo "Flows after restore"
-        as hv1
-        ovs-ofctl dump-flows br-int
-        ovs-vsctl --no-wait --if-exists remove open_vswitch . other_config \
-            flow-restore-wait="true"
-    fi
-}
-
-# Save the flows, restart vswitchd and restore the flows
-restart_vswitchd true
-OVS_WAIT_UNTIL([
-    total_flows_after_restart=`as hv1 ovs-ofctl dump-flows br-int | wc -l`
-    echo "Total flows after vswitchd restart = " $total_flows_after_restart
-    test "${total_flows}" = "${total_flows_after_restart}"
-])
-
-# Restart vswitchd without restoring
-restart_vswitchd false
-OVS_WAIT_UNTIL([
-    total_flows_after_restart=`as hv1 ovs-ofctl dump-flows br-int | wc -l`
-    echo "Total flows after vswitchd restart = " $total_flows_after_restart
-    test "${total_flows}" = "${total_flows_after_restart}"
-])
-
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- send arp for nexthop])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Topology: Two LSs - ls1 and ls2 are connected via router r0
-
-# Create logical switches
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-
-# Create  router
-ovn-nbctl create Logical_Router name=lr0
-
-# Add router ls1p1 port to gateway router
-ovn-nbctl lrp-add lr0 lrp-ls1lp1 f0:00:00:00:00:01 192.168.0.1/24
-ovn-nbctl lsp-add ls1 ls1lp1 -- set Logical_Switch_Port ls1lp1  \
-    type=router options:router-port=lrp-ls1lp1 \
-    addresses='"f0:00:00:00:00:01 192.168.0.1"'
-
-# Add router ls2p2 port to gateway router
-ovn-nbctl lrp-add lr0 lrp-ls2lp1 f0:00:00:00:00:02 192.168.1.1/24
-ovn-nbctl lsp-add ls2 ls2lp1 -- set Logical_Switch_Port ls2lp1 \
-    type=router options:router-port=lrp-ls2lp1 \
-    addresses='"f0:00:00:00:00:02 192.168.1.1"'
-
-# Set default gateway (nexthop) to 192.168.1.254
-ovn-nbctl lr-route-add lr0 "0.0.0.0/0" 192.168.1.254 lrp-ls2lp1
-
-# Create logical port ls1lp2 in ls1
-ovn-nbctl lsp-add ls1 ls1lp2 \
--- lsp-set-addresses ls1lp2 "f0:00:00:00:00:03 192.168.0.2"
-
-# Create logical port ls2lp2 in ls2
-ovn-nbctl lsp-add ls2 ls2lp2 \
--- lsp-set-addresses ls2lp2 "f0:00:00:00:00:04 192.168.1.10"
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-ls1lp2 -- \
-    set interface hv1-ls1lp2 external-ids:iface-id=ls1lp2 \
-    options:tx_pcap=hv1/ls1lp2-tx.pcap \
-    options:rxq_pcap=hv1/ls1lp2-rx.pcap \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \
-    set interface hv1-ls2lp2 external-ids:iface-id=ls2lp2 \
-    options:tx_pcap=hv1/ls2lp2-tx.pcap \
-    options:rxq_pcap=hv1/ls2lp2-rx.pcap \
-    ofport-request=2
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-ovn-sbctl list chassis
-ovn-sbctl list encap
-echo "---------------------"
-
-echo "------Flows dump-----"
-as hv1
-ovs-ofctl dump-flows
-echo "---------------------"
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-src_mac="f00000000003"
-dst_mac="f00000000001"
-src_ip=`ip_to_hex 192 168 0 2`
-dst_ip=`ip_to_hex 8 8 8 8`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-# Send IP packet destined to 8.8.8.8 from lsp1lp2
-as hv1 ovs-appctl netdev-dummy/receive hv1-ls1lp2 $packet
-
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-# ARP packet should be received with Target IP Address set to 192.168.1.254 and
-# not 8.8.8.8
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ls2lp2-tx.pcap | trim_zeros > packets
-expected="fffffffffffff0000000000208060001080006040001f00000000002c0a80101000000000000c0a801fe"
-echo $expected > expout
-AT_CHECK([cat packets], [0], [expout])
-cat packets
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- send gratuitous arp for nat ips in localnet])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-# Create logical switch
-ovn-nbctl ls-add ls0
-# Create gateway router
-ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1
-# Add router port to gateway router
-ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24
-ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \
-    type=router options:router-port=lrp0 addresses='"f0:00:00:00:00:01"'
-# Add nat-address option
-ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="f0:00:00:00:00:01 192.168.0.2"
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0
-
-ovn_attach n1 br-phys 192.168.0.1
-
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
-AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
-# Wait until the patch ports are created in hv1 to connect br-int to br-eth0
-OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-vsctl show | \
-grep "Port patch-br-int-to-ln_port" | wc -l`])
-
-# Wait for packet to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
-echo $expected > expout
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002"
-echo $expected >> expout
-AT_CHECK([sort packets], [0], [expout])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- send gratuitous arp with nat-addresses router in localnet])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-# Create logical switch
-ovn-nbctl ls-add ls0
-# Create gateway router
-ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1
-# Add router port to gateway router
-ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24
-ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \
-    type=router options:router-port=lrp0 addresses='"f0:00:00:00:00:01"'
-# Add nat-address option
-ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
-# Add NAT rules
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.1 10.0.0.0/24])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 192.168.0.2 10.0.0.1])
-# Add load balancers
-AT_CHECK([ovn-nbctl lb-add lb0 192.168.0.3:80 10.0.0.2:80,10.0.0.3:80])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0])
-AT_CHECK([ovn-nbctl lb-add lb1 192.168.0.3:8080 10.0.0.2:8080,10.0.0.3:8080])
-AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1])
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl \
-    -- add-br br-phys \
-    -- add-br br-eth0
-
-ovn_attach n1 br-phys 192.168.0.1
-
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
-AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
-# Wait until the patch ports are created to connect br-int to br-eth0
-OVS_WAIT_UNTIL([test 1 = `ovs-vsctl show | \
-grep "Port patch-br-int-to-ln_port" | wc -l`])
-
-ovn-sbctl list port_binding lrp0-rp
-echo "*****"
-ovn-nbctl list logical_switch_port lrp0-rp
-ovn-nbctl list logical_router_port lrp0
-ovn-nbctl show
-# Wait for packet to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
-echo $expected > expout
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002"
-echo $expected >> expout
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80003000000000000c0a80003"
-echo $expected >> expout
-AT_CHECK([sort packets], [0], [expout])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- delete mac bindings])
-ovn_start
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl -- add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-# Create logical switch ls0
-ovn-nbctl ls-add ls0
-# Create ports lp0, lp1 in ls0
-ovn-nbctl lsp-add ls0 lp0
-ovn-nbctl lsp-add ls0 lp1
-ovn-nbctl lsp-set-addresses lp0 "f0:00:00:00:00:01 192.168.0.1"
-ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:02 192.168.0.2"
-dp_uuid=`ovn-sbctl find datapath | grep uuid | cut -f2 -d ":" | cut -f2 -d " "`
-ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp0 mac="mac1"
-ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp1 mac="mac2"
-ovn-sbctl find MAC_Binding
-# Delete port lp0 and check that its MAC_Binding is deleted.
-ovn-nbctl lsp-del lp0
-ovn-sbctl find MAC_Binding
-OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding logical_port=lp0 | wc -l` = 0])
-# Delete logical switch ls0 and check that its MAC_Binding is deleted.
-ovn-nbctl ls-del ls0
-ovn-sbctl find MAC_Binding
-OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding | wc -l` = 0])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- conntrack zone allocation])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# 2 logical switches "foo" (192.168.1.0/24) and "bar" (172.16.1.0/24)
-# connected to a router R1.
-# foo has foo1 to act as a client.
-# bar has bar1, bar2, bar3 to act as servers.
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-for i in foo1 bar1 bar2 bar3; do
-    ovs-vsctl -- add-port br-int $i -- \
-        set interface $i external-ids:iface-id=$i \
-        options:tx_pcap=hv1/$i-tx.pcap \
-        options:rxq_pcap=hv1/$i-rx.pcap
-done
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 172.16.1.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port bar1, bar2 and bar3 in bar
-for i in `seq 1 3`; do
-    ip=`expr $i + 1`
-    ovn-nbctl lsp-add bar bar$i \
-    -- lsp-set-addresses bar$i "f0:00:0a:01:02:$i 172.16.1.$ip"
-done
-
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=0 | grep REG13 | wc -l` -eq 4])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- tag allocation])
-ovn_start
-
-AT_CHECK([ovn-nbctl ls-add ls0])
-AT_CHECK([ovn-nbctl lsp-add ls0 parent1])
-AT_CHECK([ovn-nbctl lsp-add ls0 parent2])
-AT_CHECK([ovn-nbctl ls-add ls1])
-
-dnl When a tag is provided, no allocation is done
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c0 parent1 3])
-AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3
-])
-dnl The same 'tag' gets created in southbound database.
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c0"], [0], [3
-])
-
-dnl Allocate tags and see it getting created in both NB and SB
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c1 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c1], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c1"], [0], [1
-])
-
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c2 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c2"], [0], [2
-])
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c3"], [0], [4
-])
-
-dnl A different parent.
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c4 parent2 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c4"], [0], [1
-])
-
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c5 parent2 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c5"], [0], [2
-])
-
-dnl Delete a logical port and create a new one.
-AT_CHECK([ovn-nbctl --wait=sb lsp-del c1])
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c6 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c6"], [0], [1
-])
-
-dnl Restart northd to see that the same allocation remains.
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-start_daemon ovn-northd \
-    --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \
-    --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
-
-dnl Create a switch to make sure that ovn-northd has run through the main loop.
-AT_CHECK([ovn-nbctl --wait=sb ls-add ls-dummy])
-AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
-])
-
-dnl Create a switch port with a tag that has already been allocated.
-dnl It should go through fine with a duplicate tag.
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 2])
-AT_CHECK([ovn-nbctl lsp-get-tag c7], [0], [2
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c7"], [0], [2
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
-])
-
-AT_CHECK([ovn-nbctl ls-add ls2])
-dnl When there is no parent_name provided (for say, 'localnet'), 'tag_request'
-dnl gets copied to 'tag'
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls2 local0 "" 25])
-AT_CHECK([ovn-nbctl lsp-get-tag local0], [0], [25
-])
-dnl The same 'tag' gets created in southbound database.
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="local0"], [0], [25
-])
-dnl If 'tag_request' is 0 for localnet, nothing gets written to 'tag'
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls2 local1 "" 0])
-AT_CHECK([ovn-nbctl lsp-get-tag local1])
-dnl change the tag_request.
-AT_CHECK([ovn-nbctl --wait=sb  set logical_switch_port local1 tag_request=50])
-AT_CHECK([ovn-nbctl lsp-get-tag local1], [0], [50
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- lsp deletion and broadcast-flow deletion on localnet])
-ovn_start
-ovn-nbctl ls-add lsw0
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-    ovs-vsctl add-br br-eth0
-    AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
-done
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add lsw0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
-
-# Create 3 vifs.
-AT_CHECK([ovn-nbctl lsp-add lsw0 localvif1])
-AT_CHECK([ovn-nbctl lsp-set-addresses localvif1 "f0:00:00:00:00:01 192.168.1.1"])
-AT_CHECK([ovn-nbctl lsp-set-port-security localvif1 "f0:00:00:00:00:01"])
-AT_CHECK([ovn-nbctl lsp-add lsw0 localvif2])
-AT_CHECK([ovn-nbctl lsp-set-addresses localvif2 "f0:00:00:00:00:02 192.168.1.2"])
-AT_CHECK([ovn-nbctl lsp-set-port-security localvif2 "f0:00:00:00:00:02"])
-AT_CHECK([ovn-nbctl lsp-add lsw0 localvif3])
-AT_CHECK([ovn-nbctl lsp-set-addresses localvif3 "f0:00:00:00:00:03 192.168.1.3"])
-AT_CHECK([ovn-nbctl lsp-set-port-security localvif3 "f0:00:00:00:00:03"])
-
-# Bind the localvif1 to hv1.
-as hv1
-AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1])
-
-# On hv1, check that there are no flows outputting bcast to tunnel
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-
-# On hv2, check that no flow outputs bcast to tunnel to hv1.
-as hv2
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-
-# Now bind vif2 on hv2.
-AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2])
-
-# At this point, the broadcast flow on vif2 should be deleted.
-# because, there is now a localnet vif bound (table=32 programming logic)
-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-
-# Verify that the local net patch port exists on hv2.
-OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1])
-
-# Now bind vif3 on hv2.
-AT_CHECK([ovs-vsctl add-port br-int localvif3 -- set Interface localvif3 external_ids:iface-id=localvif3])
-
-# Verify that the local net patch port still exists on hv2
-OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1])
-
-# Delete localvif2
-AT_CHECK([ovn-nbctl lsp-del localvif2])
-
-# Verify that the local net patch port still exists on hv2,
-# because, localvif3 is still bound.
-OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-
-AT_SETUP([ovn -- ACL logging])
-AT_KEYWORDS([ovn])
-ovn_start
-
-net_add n1
-
-sim_add hv
-as hv
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-for i in lp1 lp2; do
-    ovs-vsctl -- add-port br-int $i -- \
-        set interface $i external-ids:iface-id=$i \
-        options:tx_pcap=hv/$i-tx.pcap \
-        options:rxq_pcap=hv/$i-rx.pcap
-done
-
-lp1_mac="f0:00:00:00:00:01"
-lp1_ip="192.168.1.2"
-
-lp2_mac="f0:00:00:00:00:02"
-lp2_ip="192.168.1.3"
-
-ovn-nbctl ls-add lsw0
-ovn-nbctl --wait=sb lsp-add lsw0 lp1
-ovn-nbctl --wait=sb lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp1 $lp1_mac
-ovn-nbctl lsp-set-addresses lp2 $lp2_mac
-ovn-nbctl --wait=sb sync
-
-ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
-ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000 'tcp.dst==81' drop
-
-ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==82' allow
-ovn-nbctl --log --severity=info --name=allow-flow acl-add lsw0 to-lport 1000 'tcp.dst==83' allow
-
-ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==84' allow-related
-ovn-nbctl --log acl-add lsw0 to-lport 1000 'tcp.dst==85' allow-related
-
-ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==86' reject
-ovn-nbctl --wait=hv --log --severity=alert --name=reject-flow acl-add lsw0 to-lport 1000 'tcp.dst==87' reject
-
-ovn-sbctl dump-flows
-
-
-# Send packet that should be dropped without logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==80"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should be dropped with logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4361 && tcp.dst==81"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should be allowed without logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4362 && tcp.dst==82"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should be allowed with logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4363 && tcp.dst==83"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should allow related flows without logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4364 && tcp.dst==84"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should allow related flows with logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4365 && tcp.dst==85"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should be rejected without logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4366 && tcp.dst==86"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Send packet that should be rejected with logging.
-packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
-        ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
-        tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==87"
-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-OVS_WAIT_UNTIL([ test 4 = $(grep -c 'acl_log' hv/ovn-controller.log) ])
-
-AT_CHECK([grep 'acl_log' hv/ovn-controller.log | sed 's/.*name=/name=/'], [0], [dnl
-name="drop-flow", verdict=drop, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4361,tp_dst=81,tcp_flags=syn
-name="allow-flow", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4363,tp_dst=83,tcp_flags=syn
-name="<unnamed>", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4365,tp_dst=85,tcp_flags=syn
-name="reject-flow", verdict=reject, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4367,tp_dst=87,tcp_flags=syn
-])
-
-OVN_CLEANUP([hv])
-AT_CLEANUP
-
-
-AT_SETUP([ovn -- ACL rate-limited logging])
-AT_KEYWORDS([ovn])
-ovn_start
-
-net_add n1
-
-sim_add hv
-as hv
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-for i in lp1 lp2; do
-    ovs-vsctl -- add-port br-int $i -- \
-        set interface $i external-ids:iface-id=$i \
-        options:tx_pcap=hv/$i-tx.pcap \
-        options:rxq_pcap=hv/$i-rx.pcap
-done
-
-lp1_mac="f0:00:00:00:00:01"
-lp1_ip="192.168.1.2"
-
-lp2_mac="f0:00:00:00:00:02"
-lp2_ip="192.168.1.3"
-
-ovn-nbctl ls-add lsw0
-ovn-nbctl --wait=sb lsp-add lsw0 lp1
-ovn-nbctl --wait=sb lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp1 $lp1_mac
-ovn-nbctl lsp-set-addresses lp2 $lp2_mac
-ovn-nbctl --wait=sb sync
-
-
-# Add an ACL that rate-limits logs at 10 per second.
-ovn-nbctl meter-add http-rl1 drop 10 pktps
-ovn-nbctl --log --severity=alert --meter=http-rl1 --name=http-acl1 acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
-
-# Add an ACL that rate-limits logs at 5 per second.
-ovn-nbctl meter-add http-rl2 drop 5 pktps
-ovn-nbctl --log --severity=alert --meter=http-rl2 --name=http-acl2 acl-add lsw0 to-lport 1000 'tcp.dst==81' allow
-
-# Add an ACL that doesn't rate-limit logs.
-ovn-nbctl --log --severity=alert --name=http-acl3 acl-add lsw0 to-lport 1000 'tcp.dst==82' drop
-ovn-nbctl --wait=hv sync
-
-# For each ACL, send 100 packets.
-for i in `seq 1 100`; do
-    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=80)'
-
-    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=81)'
-
-    ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=82)'
-done
-
-# The rate at which packets are sent is highly system-dependent, so we
-# can't count on precise drop counts.  To work around that, we just
-# check that exactly 100 "http-acl3" actions were logged and that there
-# were more "http-acl1" actions than "http-acl2" ones.
-OVS_WAIT_UNTIL([ test 100 = $(grep -c 'http-acl3' hv/ovn-controller.log) ])
-
-# On particularly slow or overloaded systems, the transmission rate may
-# be lower than the configured meter rate.  To prevent false test
-# failures, we check the duration count of the meter, and if it's
-# greater than nine seconds, just skip the test.
-d_secs=$(as hv ovs-ofctl -O OpenFlow13 meter-stats br-int | grep "meter:1" | sed 's/.* duration:\([[0-9]]\{1,\}\)\.[[0-9]]\+s .*/\1/')
-
-echo "Meter duration: $d_secs"
-AT_SKIP_IF([test $d_secs -gt 9])
-
-# Print some information that may help debugging.
-as hv ovs-appctl -t ovn-controller meter-table-list
-as hv ovs-ofctl -O OpenFlow13 meter-stats br-int
-
-n_acl1=$(grep -c 'http-acl1' hv/ovn-controller.log)
-n_acl2=$(grep -c 'http-acl2' hv/ovn-controller.log)
-n_acl3=$(grep -c 'http-acl3' hv/ovn-controller.log)
-
-AT_CHECK([ test $n_acl3 -gt $n_acl1 ], [0], [])
-AT_CHECK([ test $n_acl1 -gt $n_acl2 ], [0], [])
-
-OVN_CLEANUP([hv])
-AT_CLEANUP
-
-
-AT_SETUP([ovn -- DSCP marking and meter check])
-AT_KEYWORDS([ovn])
-ovn_start
-
-ovn-nbctl ls-add lsw0
-ovn-nbctl --wait=sb lsp-add lsw0 lp1
-ovn-nbctl --wait=sb lsp-add lsw0 lp2
-ovn-nbctl --wait=sb lsp-add lsw0 lp3
-ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01
-ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02
-ovn-nbctl lsp-set-addresses lp3 f0:00:00:00:00:03
-ovn-nbctl lsp-set-port-security lp1 f0:00:00:00:00:01
-ovn-nbctl lsp-set-port-security lp2 f0:00:00:00:00:02
-ovn-nbctl --wait=sb sync
-net_add n1
-sim_add hv
-as hv
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1
-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2
-
-AT_CAPTURE_FILE([trace])
-ovn_trace () {
-    ovn-trace --all "$@" | tee trace | sed '1,/Minimal trace/d'
-}
-
-# Extracts nw_tos from the final flow from ofproto/trace output and prints
-# it on stdout.  Prints "none" if no nw_tos was included.
-get_final_nw_tos() {
-    if flow=$(grep '^Final flow:' stdout); then :; else
-       # The output didn't have a final flow.
-       return 99
-    fi
-
-    tos=$(echo "$flow" | sed -n 's/.*nw_tos=\([[0-9]]\{1,\}\).*/\1/p')
-    case $tos in
-        '') echo none ;;
-        *) echo $tos ;;
-    esac
-}
-
-# check_tos TOS
-#
-# Checks that a packet from 1.1.1.1 to 1.1.1.2 gets its DSCP set to TOS.
-check_tos() {
-    # First check with ovn-trace for logical flows.
-    echo "checking for tos $1"
-    (if test $1 != 0; then echo "ip.dscp = $1;"; fi;
-     echo 'output("lp2");') > expout
-    AT_CHECK_UNQUOTED([ovn_trace lsw0 'inport == "lp1" && eth.src == f0:00:00:00:00:01 && eth.dst == f0:00:00:00:00:02 && ip4.src == 1.1.1.1 && ip4.dst == 1.1.1.2'], [0], [expout])
-
-    # Then re-check with ofproto/trace for a physical packet.
-    AT_CHECK([ovs-appctl ofproto/trace br-int 'in_port=1,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,dl_type=0x800,nw_src=1.1.1.1,nw_dst=1.1.1.2'], [0], [stdout-nolog])
-    AT_CHECK_UNQUOTED([get_final_nw_tos], [0], [`expr $1 \* 4`
-])
-}
-
-# check at L2
-AT_CHECK([ovn_trace lsw0 'inport == "lp1" && eth.src == f0:00:00:00:00:01 && eth.dst == f0:00:00:00:00:02'], [0], [output("lp2");
-])
-AT_CHECK([ovs-appctl ofproto/trace br-int 'in_port=1,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02'], [0], [stdout-nolog])
-AT_CHECK([get_final_nw_tos], [0], [none
-])
-
-# check at L3 without dscp marking
-check_tos 0
-
-# Mark DSCP with a valid value
-qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 match="inport\=\=\"lp1\"\ &&\ is_chassis_resident(\"lp1\")" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos)
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1
-])
-check_tos 48
-
-# check at hv without qos meter
-AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0
-])
-
-# Update the meter rate
-ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=100
-
-# check at hv with a qos meter table
-AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep rate=100 | wc -l], [0], [1
-])
-AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [1
-])
-
-# Update the DSCP marking
-ovn-nbctl --wait=hv set QoS $qos_id action=dscp=63
-check_tos 63
-
-# Update the meter rate
-ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=4294967295,burst=4294967295
-
-# check at hv with a qos meter table
-AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep burst_size=4294967295 | wc -l], [0], [1
-])
-AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [1
-])
-
-ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lport"
-check_tos 63
-
-# Disable DSCP marking
-ovn-nbctl --wait=hv qos-del lsw0
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [0
-])
-check_tos 0
-
-# check at hv without qos meter
-AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0
-])
-
-# check meter with chassis not resident
-ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp3" && is_chassis_resident("lp3")' rate=11123 burst=111230
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1
-])
-
-# check no meter table
-AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0
-])
-AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep rate=11123 | wc -l], [0], [0
-])
-
-OVN_CLEANUP([hv])
-AT_CLEANUP
-
-AT_SETUP([ovn -- read-only sb db:ptcp access])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-
-: > .$1.db.~lock~
-ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema
-
-# Add read-only remote to sb ovsdb-server
-AT_CHECK(
-  [ovsdb-tool transact ovn-sb.db \
-     ['["OVN_Southbound",
-       {"op": "insert",
-        "table": "SB_Global",
-        "row": {
-          "connections": ["set", [["named-uuid", "xyz"]]]}},
-       {"op": "insert",
-        "table": "Connection",
-        "uuid-name": "xyz",
-        "row": {"target": "ptcp:0:127.0.0.1",
-               "read_only": true}}]']], [0], [ignore], [ignore])
-
-start_daemon ovsdb-server --remote=punix:ovn-sb.sock --remote=db:OVN_Southbound,SB_Global,connections ovn-sb.db
-
-PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
-
-# read-only accesses should succeed
-AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT list SB_Global], [0], [stdout], [ignore])
-AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT list Connection], [0], [stdout], [ignore])
-
-# write access should fail
-AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT chassis-add ch vxlan 1.2.4.8], [1], [ignore],
-[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
-])
-
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-AT_CLEANUP
-
-AT_SETUP([ovn -- read-only sb db:pssl access])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
-PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
-AT_SKIP_IF([expr "$PKIDIR" : ".*[ 	'\"
-\\]"])
-
-: > .$1.db.~lock~
-ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema
-
-# Add read-only remote to sb ovsdb-server
-AT_CHECK(
-  [ovsdb-tool transact ovn-sb.db \
-     ['["OVN_Southbound",
-       {"op": "insert",
-        "table": "SB_Global",
-        "row": {
-          "connections": ["set", [["named-uuid", "xyz"]]]}},
-       {"op": "insert",
-        "table": "Connection",
-        "uuid-name": "xyz",
-        "row": {"target": "pssl:0:127.0.0.1",
-               "read_only": true}}]']], [0], [ignore], [ignore])
-
-start_daemon ovsdb-server --remote=punix:ovn-sb.sock \
-                          --remote=db:OVN_Southbound,SB_Global,connections \
-                          --private-key="$PKIDIR/testpki-privkey2.pem" \
-                          --certificate="$PKIDIR/testpki-cert2.pem" \
-                          --ca-cert="$PKIDIR/testpki-cacert.pem" \
-                          ovn-sb.db
-
-PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
-
-# read-only accesses should succeed
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-                    list SB_Global], [0], [stdout], [ignore])
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-                    list Connection], [0], [stdout], [ignore])
-
-# write access should fail
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-                    chassis-add ch vxlan 1.2.4.8], [1], [ignore],
-[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
-])
-
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-AT_CLEANUP
-
-AT_SETUP([ovn -- nb connection/ssl commands])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
-PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
-AT_SKIP_IF([expr "$PKIDIR" : ".*[ 	'\"
-\\]"])
-
-: > .$1.db.~lock~
-ovsdb-tool create ovn-nb.db "$abs_top_srcdir"/ovn/ovn-nb.ovsschema
-
-# Start nb db server using db connection/ssl entries (unpopulated initially)
-start_daemon ovsdb-server --remote=punix:ovnnb_db.sock \
-                          --remote=db:OVN_Northbound,NB_Global,connections \
-                          --private-key=db:OVN_Northbound,SSL,private_key \
-                          --certificate=db:OVN_Northbound,SSL,certificate \
-                          --ca-cert=db:OVN_Northbound,SSL,ca_cert \
-                          ovn-nb.db
-
-# Populate SSL configuration entries in nb db
-AT_CHECK(
-    [ovn-nbctl set-ssl $PKIDIR/testpki-privkey.pem \
-                       $PKIDIR/testpki-cert.pem \
-                       $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore])
-
-# Populate a passive SSL connection in nb db
-AT_CHECK([ovn-nbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore])
-
-PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
-
-# Verify SSL connetivity to nb db server
-AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          list NB_Global],
-         [0], [stdout], [ignore])
-AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          list Connection],
-         [0], [stdout], [ignore])
-AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          get-connection],
-         [0], [stdout], [ignore])
-
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-AT_CLEANUP
-
-AT_SETUP([ovn -- sb connection/ssl commands])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
-PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
-AT_SKIP_IF([expr "$PKIDIR" : ".*[ 	'\"
-\\]"])
-
-: > .$1.db.~lock~
-ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema
-
-# Start sb db server using db connection/ssl entries (unpopulated initially)
-start_daemon ovsdb-server --remote=punix:ovnsb_db.sock \
-                          --remote=db:OVN_Southbound,SB_Global,connections \
-                          --private-key=db:OVN_Southbound,SSL,private_key \
-                          --certificate=db:OVN_Southbound,SSL,certificate \
-                          --ca-cert=db:OVN_Southbound,SSL,ca_cert \
-                          ovn-sb.db
-
-# Populate SSL configuration entries in sb db
-AT_CHECK(
-    [ovn-sbctl set-ssl $PKIDIR/testpki-privkey.pem \
-                       $PKIDIR/testpki-cert.pem \
-                       $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore])
-
-# Populate a passive SSL connection in sb db
-AT_CHECK([ovn-sbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore])
-
-PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
-
-# Verify SSL connetivity to sb db server
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          list SB_Global],
-         [0], [stdout], [ignore])
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          list Connection],
-         [0], [stdout], [ignore])
-AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
-                    --private-key=$PKIDIR/testpki-privkey.pem \
-                    --certificate=$PKIDIR/testpki-cert.pem \
-                    --ca-cert=$PKIDIR/testpki-cacert.pem \
-          get-connection],
-         [0], [stdout], [ignore])
-
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-AT_CLEANUP
-
-AT_SETUP([ovn -- nested containers])
-ovn_start
-
-# Physical network:
-# 2 HVs. HV1 has 2 VMs - "VM1" and "bar3". HV2 has 1 VM - "VM2"
-
-# Logical network:
-# 3 Logical switches - "mgmt" (172.16.1.0/24), "foo" (192.168.1.0/24)
-# and "bar" (192.168.2.0/24). They are all connected to router R1.
-
-ovn-nbctl lr-add R1
-ovn-nbctl ls-add mgmt
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-
-# Connect mgmt to R1
-ovn-nbctl lrp-add R1 mgmt 00:00:00:01:02:02 172.16.1.1/24
-ovn-nbctl lsp-add mgmt rp-mgmt -- set Logical_Switch_Port rp-mgmt type=router \
-          options:router-port=mgmt addresses=\"00:00:00:01:02:02\"
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-          options:router-port=foo addresses=\"00:00:00:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:00:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar type=router \
-          options:router-port=bar addresses=\"00:00:00:01:02:04\"
-
-# "mgmt" has VM1 and VM2 connected
-ovn-nbctl lsp-add mgmt vm1 \
--- lsp-set-addresses vm1 "f0:00:00:01:02:03 172.16.1.2"
-
-ovn-nbctl lsp-add mgmt vm2 \
--- lsp-set-addresses vm2 "f0:00:00:01:02:04 172.16.1.3"
-
-# "foo1" and "foo2" are containers belonging to switch "foo"
-# "foo1" has "VM1" as parent_port and "foo2" has "VM2" as parent_port.
-ovn-nbctl lsp-add foo foo1 vm1 1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:05 192.168.1.2"
-
-ovn-nbctl lsp-add foo foo2 vm2 2 \
--- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
-# "bar1" and "bar2" are containers belonging to switch "bar"
-# "bar1" has "VM1" as parent_port and "bar2" has "VM2" as parent_port.
-ovn-nbctl lsp-add bar bar1 vm1 2 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:07 192.168.2.2"
-
-ovn-nbctl lsp-add bar bar2 vm2 1 \
--- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3"
-
-# bar3 is a standalone VM belonging to switch "bar"
-ovn-nbctl lsp-add bar bar3 \
--- lsp-set-addresses bar3 "f0:00:00:01:02:09 192.168.2.4"
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int vm1 -- \
-    set interface vm1 external-ids:iface-id=vm1 \
-    options:tx_pcap=hv1/vm1-tx.pcap \
-    options:rxq_pcap=hv1/vm1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int bar3 -- \
-    set interface bar3 external-ids:iface-id=bar3 \
-    options:tx_pcap=hv1/bar3-tx.pcap \
-    options:rxq_pcap=hv1/bar3-rx.pcap \
-    ofport-request=2
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int vm2 -- \
-    set interface vm2 external-ids:iface-id=vm2 \
-    options:tx_pcap=hv2/vm2-tx.pcap \
-    options:rxq_pcap=hv2/vm2-rx.pcap \
-    ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and foo2 (same switch, different HVs and
-# different VLAN tags).
-src_mac="f00000010205"
-dst_mac="f00000010206"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 1 3`
-packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at foo2
-packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-echo  $packet > expected
-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected])
-
-# Send ip packets between foo1 and bar2 (different switch, different HV)
-src_mac="f00000010205"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 3`
-packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at bar2
-src_mac="000000010204"
-dst_mac="f00000010208"
-packet=${dst_mac}${src_mac}8100000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-echo  $packet >> expected
-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected])
-
-# Send ip packets between foo1 and bar1
-# (different switch, loopback to same vm but different tag)
-src_mac="f00000010205"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at bar1
-src_mac="000000010204"
-dst_mac="f00000010207"
-packet=${dst_mac}${src_mac}8100000208004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-echo  $packet > expected1
-OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
-
-# Send ip packets between bar1 and bar3
-# (same switch. But one is container and another is a standalone VM)
-src_mac="f00000010207"
-dst_mac="f00000010209"
-src_ip=`ip_to_hex 192 168 2 2`
-dst_ip=`ip_to_hex 192 168 2 3`
-packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at bar3
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-echo  $packet > expected
-OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected])
-
-# Send ip packets between foo1 and vm1.
-(different switch, container to the VM hosting it.)
-src_mac="f00000010205"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 2`
-packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at vm1
-src_mac="000000010202"
-dst_mac="f00000010203"
-packet=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-echo  $packet >> expected1
-OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
-
-# Send packets from vm1 to bar1.
-(different switch, A hosting VM to a container inside it)
-src_mac="f00000010203"
-dst_mac="000000010202"
-src_ip=`ip_to_hex 172 16 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at vm1
-src_mac="000000010204"
-dst_mac="f00000010207"
-packet=${dst_mac}${src_mac}8100000208004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-echo  $packet >> expected1
-OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
-
-# Send broadcast packet from foo1. foo1 should not receive the same packet.
-src_mac="f00000010205"
-dst_mac="ffffffffffff"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 255 255 255 255`
-packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
-
-# expected packet at VM1
-OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 3 HVs, 3 LRs connected via LS, source IP based routes])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and bar
-# (192.168.2.0/24) connected to it.
-#
-# R2 and R3 are gateway routers.
-# R2 has alice (172.16.1.0/24) and R3 has bob (172.16.1.0/24)
-# connected to it. Note how both alice and bob have the same subnet behind it.
-# We are trying to simulate external network via those 2 switches. In real
-# world the switch ports of these switches will have addresses set as "unknown"
-# to make them learning switches. Or those switches will be "localnet" ones.
-
-# Create three hypervisors and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=bar1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-int hv3-vif1 -- \
-    set interface hv3-vif1 external-ids:iface-id=bob1 \
-    options:tx_pcap=hv3/vif1-tx.pcap \
-    options:rxq_pcap=hv3/vif1-rx.pcap \
-    ofport-request=1
-
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis="hv2"
-ovn-nbctl create Logical_Router name=R3 options:chassis="hv3"
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add bob
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-    options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar type=router \
-    options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect bob to R3
-ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24
-ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
-    type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Connect R3 to join
-ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24
-ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
-    type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
-
-# Install static routes with source ip address as the policy for routing.
-# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
-
-# Install static routes with destination ip address as the policy for routing.
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-
-ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port bar1 in bar
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2"
-
-# Create logical port alice1 in alice
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.3"
-
-# Create logical port bob1 in bob
-ovn-nbctl lsp-add bob bob1 \
--- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-# Send ip packets between foo1 and bar1
-# (East-west traffic should flow normally)
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-# Send ip packets between foo1 and alice1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 3`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet
-
-# Send ip packets between bar1 and bob1
-src_mac="f00000010204"
-dst_mac="000001010204"
-src_ip=`ip_to_hex 192 168 2 2`
-dst_ip=`ip_to_hex 172 16 1 4`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
-#as hv1 ovs-appctl ofproto/trace br-int in_port=2 $packet
-
-# Packet to expect at bar1
-src_mac="000001010204"
-dst_mac="f00000010204"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 192 168 2 2`
-expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-echo $expected > expected
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-
-# Packet to Expect at alice1
-src_mac="000002010203"
-dst_mac="f00000010205"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 3`
-expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
-echo $expected > expected
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Packet to Expect at bob1
-src_mac="000003010203"
-dst_mac="f00000010206"
-src_ip=`ip_to_hex 192 168 2 2`
-dst_ip=`ip_to_hex 172 16 1 4`
-expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
-echo $expected > expected
-OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4"
-
-ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4"
-
-ovn-nbctl lsp-add ls1 ls1-lp2 \
--- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
-
-ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
-
-DNS1=`ovn-nbctl create DNS records={}`
-DNS2=`ovn-nbctl create DNS records={}`
-
-ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4"
-ovn-nbctl set DNS $DNS1 records:vm2.ovn.org="10.0.0.6 20.0.0.4"
-ovn-nbctl set DNS $DNS2 records:vm3.ovn.org="40.0.0.4"
-
-ovn-nbctl set Logical_switch ls1 dns_records="$DNS1"
-
-net_add n1
-sim_add hv1
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-OVN_POPULATE_ARP
-sleep 2
-as hv1 ovs-vsctl show
-
-echo "*************************"
-ovn-sbctl list DNS
-echo "*************************"
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-# set_dns_params host_name
-# Sets the dns_req_data and dns_resp_data
-set_dns_params() {
-    local hname=$1
-    local ttl=00000e10
-    an_count=0001
-    type=0001
-    case $hname in
-    vm1)
-        # vm1.ovn.org
-        query_name=03766d31036f766e036f726700
-        # IPv4 address - 10.0.0.4
-        expected_dns_answer=${query_name}00010001${ttl}00040a000004
-        ;;
-    vm2)
-        # vm2.ovn.org
-        query_name=03766d32036f766e036f726700
-        # IPv4 address - 10.0.0.6
-        expected_dns_answer=${query_name}00010001${ttl}00040a000006
-        # IPv4 address - 20.0.0.4
-        expected_dns_answer=${expected_dns_answer}${query_name}00010001${ttl}000414000004
-        an_count=0002
-        ;;
-    vm3)
-        # vm3.ovn.org
-        query_name=03766d33036f766e036f726700
-        # IPv4 address - 40.0.0.4
-        expected_dns_answer=${query_name}00010001${ttl}000428000004
-        ;;
-    vm1_ipv6_only)
-        # vm1.ovn.org
-        query_name=03766d31036f766e036f726700
-        # IPv6 address - aef0::4
-        type=001c
-        expected_dns_answer=${query_name}${type}0001${ttl}0010aef00000000000000000000000000004
-        ;;
-    vm1_ipv4_v6)
-        # vm1.ovn.org
-        query_name=03766d31036f766e036f726700
-        type=00ff
-        an_count=0002
-        # IPv4 address - 10.0.0.4
-        # IPv6 address - aef0::4
-        expected_dns_answer=${query_name}00010001${ttl}00040a000004
-        expected_dns_answer=${expected_dns_answer}${query_name}001c0001${ttl}0010
-        expected_dns_answer=${expected_dns_answer}aef00000000000000000000000000004
-        ;;
-    vm1_invalid_type)
-        # vm1.ovn.org
-        query_name=03766d31036f766e036f726700
-        # IPv6 address - aef0::4
-        type=0002
-        ;;
-    vm1_incomplete)
-        # set type to none
-        type=''
-    esac
-    # TTL - 3600
-    local dns_req_header=010201200001000000000000
-    local dns_resp_header=010281200001${an_count}00000000
-    dns_req_data=${dns_req_header}${query_name}${type}0001
-    dns_resp_data=${dns_resp_header}${query_name}${type}0001${expected_dns_answer}
-}
-
-# This shell function sends a DNS request packet
-# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC
-test_dns() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6
-    local dns_query_data=$7
-    shift; shift; shift; shift; shift; shift; shift;
-    # Packet size => IPv4 header (20) + UDP header (8) +
-    #                DNS data (header + query)
-    ip_len=`expr 28 + ${#dns_query_data} / 2`
-    udp_len=`expr $ip_len - 20`
-    ip_len=$(printf "%x" $ip_len)
-    udp_len=$(printf "%x" $udp_len)
-    local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000
-    request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000
-    # dns data
-    request=${request}${dns_query_data}
-
-    if test $dns_reply != 0; then
-        local dns_reply=$1
-        ip_len=`expr 28 + ${#dns_reply} / 2`
-        udp_len=`expr $ip_len - 20`
-        ip_len=$(printf "%x" $ip_len)
-        udp_len=$(printf "%x" $udp_len)
-        local reply=${src_mac}${dst_mac}0800450000${ip_len}0000000080110000
-        reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${dns_reply}
-        echo $reply >> $inport.expected
-    else
-        for outport; do
-            echo $request >> $outport.expected
-        done
-    fi
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
-}
-
-test_dns6() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6
-    local dns_query_data=$7
-    shift; shift; shift; shift; shift; shift; shift;
-    # Packet size => UDP header (8) +
-    #                DNS data (header + query)
-    ip_len=`expr 8 + ${#dns_query_data} / 2`
-    udp_len=$ip_len
-    ip_len=$(printf "%x" $ip_len)
-    udp_len=$(printf "%x" $udp_len)
-    local request=${dst_mac}${src_mac}86dd6000000000${ip_len}11ff${src_ip}${dst_ip}
-    request=${request}9234003500${udp_len}0000
-    #dns data
-    request=${request}${dns_query_data}
-
-    if test $dns_reply != 0; then
-        local dns_reply=$1
-        ip_len=`expr 8 + ${#dns_reply} / 2`
-        udp_len=$ip_len
-        ip_len=$(printf "%x" $ip_len)
-        udp_len=$(printf "%x" $udp_len)
-        local reply=${src_mac}${dst_mac}86dd6000000000${ip_len}11ff${dst_ip}${src_ip}
-        reply=${reply}0035923400${udp_len}0000${dns_reply}
-        echo $reply >> $inport.expected
-    else
-        for outport; do
-            echo $request >> $outport.expected
-        done
-    fi
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
-}
-
-AT_CAPTURE_FILE([ofctl_monitor0.log])
-as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
-
-set_dns_params vm2
-src_ip=`ip_to_hex 10 0 0 4`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=1
-test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-set_dns_params vm1
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=1
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Clear the query name options for ls1-lp2
-ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org
-
-set_dns_params vm2
-src_ip=`ip_to_hex 10 0 0 4`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=0
-test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply $dns_req_data
-
-# NXT_RESUMEs should be 3.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-AT_CHECK([cat 1.packets], [0], [])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Clear the query name for ls1-lp1
-# Since ls1 has no query names configued,
-# ovn-northd should not add the DNS flows.
-ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org
-
-set_dns_params vm1
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=0
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
-
-# NXT_RESUMEs should be 3 only.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-AT_CHECK([cat 2.packets], [0], [])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Test IPv6 (AAAA records) using IPv4 packet.
-# Add back the DNS options for ls1-lp1.
-ovn-nbctl --wait=hv set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4"
-
-set_dns_params vm1_ipv6_only
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=1
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Test both IPv4 (A) and IPv6 (AAAA records) using IPv4 packet.
-set_dns_params vm1_ipv4_v6
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=1
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 5.
-OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Invalid type.
-set_dns_params vm1_invalid_type
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=0
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
-
-# NXT_RESUMEs should be 6.
-OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-AT_CHECK([cat 2.packets], [0], [])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Incomplete DNS packet.
-set_dns_params vm1_incomplete
-src_ip=`ip_to_hex 10 0 0 6`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=0
-test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
-
-# NXT_RESUMEs should be 7.
-OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-AT_CHECK([cat 2.packets], [0], [])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Add one more DNS record to the ls1.
-ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2"
-
-set_dns_params vm3
-src_ip=`ip_to_hex 10 0 0 4`
-dst_ip=`ip_to_hex 10 0 0 1`
-dns_reply=1
-test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 8.
-OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-# Try DNS query over IPv6
-set_dns_params vm1
-src_ip=aef00000000000000000000000000004
-dst_ip=aef00000000000000000000000000001
-dns_reply=1
-test_dns6 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
-
-# NXT_RESUMEs should be 9.
-OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-# Skipping the UDP checksum.
-cat 1.expected | cut -c 1-120,125- > expout
-AT_CHECK([cat 1.packets | cut -c 1-120,125-], [0], [expout])
-
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-rm -f 1.expected
-rm -f 2.expected
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4 HV, 1 LS, 1 LR, packet test with HA distributed router gateway port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add gw1
-as gw1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-sim_add gw2
-as gw2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.4
-
-sim_add ext1
-as ext1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-int ext1-vif1 -- \
-    set interface ext1-vif1 external-ids:iface-id=outside1 \
-    options:tx_pcap=ext1/vif1-tx.pcap \
-    options:rxq_pcap=ext1/vif1-rx.pcap \
-    ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add outside
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect alice to R1 as distributed router gateway port on gw1
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24
-
-ovn-nbctl \
-    --id=@gc0 create Gateway_Chassis name=alice_gw1 \
-                                     chassis_name=gw1 \
-                                     priority=20 -- \
-    --id=@gc1 create Gateway_Chassis name=alice_gw2 \
-                                     chassis_name=gw2 \
-                                     priority=10 -- \
-    set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]'
-
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port outside1 in outside
-ovn-nbctl lsp-add outside outside1 \
--- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3"
-
-# Create localnet port in alice
-ovn-nbctl lsp-add alice ln-alice
-ovn-nbctl lsp-set-addresses ln-alice unknown
-ovn-nbctl lsp-set-type ln-alice localnet
-ovn-nbctl lsp-set-options ln-alice network_name=phys
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Create bridge-mappings on gw1, gw2 and ext1, hv1 doesn't need
-# mapping to the external network, is the one generating packets
-as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore])
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 2
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-test_ip_packet()
-{
-    local active_gw=$1
-    local backup_gw=$2
-    local backup_vswitchd_dead=$3
-
-    # Send ip packet between foo1 and outside1
-    src_mac="f00000010203" # foo1 mac
-    dst_mac="000001010203" # rp-foo mac (internal router leg)
-    src_ip=`ip_to_hex 192 168 1 2`
-    dst_ip=`ip_to_hex 172 16 1 3`
-    packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-    # ARP request packet to expect at outside1
-    #arp_request=ffffffffffff${src_mac}08060001080006040001${src_mac}${src_ip}000000000000${dst_ip}
-
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-    # Send ARP reply from outside1 back to the router
-    # XXX: note, we could avoid this if we plug this port into a netns
-    # and setup the IP address into the port, so the kernel would simply reply
-    src_mac="000002010203"
-    reply_mac="f00000010204"
-    dst_ip=`ip_to_hex 172 16 1 3`
-    src_ip=`ip_to_hex 172 16 1 1`
-    arp_reply=${src_mac}${reply_mac}08060001080006040002${reply_mac}${dst_ip}${src_mac}${src_ip}
-
-    as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply
-
-    OVS_WAIT_UNTIL([
-        test `as $active_gw ovs-ofctl dump-flows br-int | grep table=66 | \
-grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
-    ])
-
-    # Packet to Expect at ext1 chassis, outside1 port
-    src_mac="000002010203"
-    dst_mac="f00000010204"
-    src_ip=`ip_to_hex 192 168 1 2`
-    dst_ip=`ip_to_hex 172 16 1 3`
-    expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-    echo $expected > ext1-vif1.expected
-    exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101
-    echo $exp_gw_ip_garp >> ext1-vif1.expected
-    as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1
-
-    if test $backup_vswitchd_dead != 1; then
-        # Reset the file only if vswitchd in backup gw is alive
-        as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1
-    fi
-    as ext1 reset_pcap_file ext1-vif1 ext1/vif1
-
-    # Resend packet from foo1 to outside1
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-    sleep 1
-
-    OVN_CHECK_PACKETS([ext1/vif1-tx.pcap], [ext1-vif1.expected])
-    $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $active_gw/br-phys_n1-tx.pcap  > packets
-    cat packets | grep $expected > exp
-    # Its possible that $active_gw/br-phys_n1-tx.pcap may have received multiple
-    # garp packets. So consider only the first packet.
-    cat packets | grep $exp_gw_ip_garp | head -1 >> exp
-    AT_CHECK([cat exp], [0], [expout])
-    rm -f expout
-    if test $backup_vswitchd_dead != 1; then
-        # Check for backup gw only if vswitchd is alive
-        $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $backup_gw/br-phys_n1-tx.pcap  > packets
-        AT_CHECK([grep $expected packets | sort], [0], [])
-    fi
-}
-
-test_ip_packet gw1 gw2 0
-
-ovn-nbctl --timeout=3 --wait=hv \
-    --id=@gc0 create Gateway_Chassis name=alice_gw1 \
-                                     chassis_name=gw1 \
-                                     priority=10 -- \
-    --id=@gc1 create Gateway_Chassis name=alice_gw2 \
-                                     chassis_name=gw2 \
-                                     priority=20 -- \
-    set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]'
-
-test_ip_packet gw2 gw1 0
-
-# Get the claim count of both gw1 and gw2.
-gw1_claim_ct=`grep "cr-alice: Claiming" gw1/ovn-controller.log | wc -l`
-gw2_claim_ct=`grep "cr-alice: Claiming" gw2/ovn-controller.log | wc -l`
-
-# Stop ovs-vswitchd in gw2. gw1 should claim the gateway port.
-as gw2
-OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
-
-# gw1 should claim the cr-alice and the claim count of gw1 should be
-# incremented by 1.
-gw1_claim_ct=$((gw1_claim_ct+1))
-
-OVS_WAIT_UNTIL([test $gw1_claim_ct = `cat gw1/ovn-controller.log \
-| grep -c "cr-alice: Claiming"`])
-
-AT_CHECK([test $gw2_claim_ct = `cat gw2/ovn-controller.log | \
-grep -c "cr-alice: Claiming"`])
-
-test_ip_packet gw1 gw2 1
-
-as gw2
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-OVN_CLEANUP([hv1],[gw1],[ext1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 4 HV, 3 LS, 2 LR, packet test with HA distributed router gateway port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add gw1
-as gw1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-sim_add gw2
-as gw2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.4
-
-sim_add ext1
-as ext1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-int ext1-vif1 -- \
-    set interface ext1-vif1 external-ids:iface-id=outside1 \
-    options:tx_pcap=ext1/vif1-tx.pcap \
-    options:rxq_pcap=ext1/vif1-rx.pcap \
-    ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R0
-ovn-nbctl create Logical_Router name=R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add join
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add outside
-
-#Connect foo to R0
-ovn-nbctl lrp-add R0 R0-foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo foo-R0 -- set Logical_Switch_Port foo-R0 \
-    type=router options:router-port=R0-foo \
-    -- lsp-set-addresses foo-R0 router
-
-#Connect R0 to join
-ovn-nbctl lrp-add R0 R0-join 00:00:0d:01:02:03 100.60.1.1/24
-ovn-nbctl lsp-add join join-R0 -- set Logical_Switch_Port join-R0 \
-    type=router options:router-port=R0-join \
-    -- lsp-set-addresses join-R0 router
-
-#Connect join to R1
-ovn-nbctl lrp-add R1 R1-join 00:00:0e:01:02:03 100.60.1.2/24
-ovn-nbctl lsp-add join join-R1 -- set Logical_Switch_Port join-R1 \
-    type=router options:router-port=R1-join \
-    -- lsp-set-addresses join-R1 router
-
-#add route rules
-ovn-nbctl lr-route-add R0 0.0.0.0/0 100.60.1.2
-ovn-nbctl lr-route-add R1 192.168.0.0/16 100.60.1.1
-
-# Connect alice to R1 as distributed router gateway port on gw1
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24
-
-ovn-nbctl \
-    --id=@gc0 create Gateway_Chassis name=alice_gw1 \
-                                     chassis_name=gw1 \
-                                     priority=20 -- \
-    --id=@gc1 create Gateway_Chassis name=alice_gw2 \
-                                     chassis_name=gw2 \
-                                     priority=10 -- \
-    set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]'
-
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port outside1 in outside
-ovn-nbctl lsp-add outside outside1 \
--- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3"
-
-# Create localnet port in alice
-ovn-nbctl lsp-add alice ln-alice
-ovn-nbctl lsp-set-addresses ln-alice unknown
-ovn-nbctl lsp-set-type ln-alice localnet
-ovn-nbctl lsp-set-options ln-alice network_name=phys
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Create bridge-mappings on gw1, gw2 and ext1, hv1 doesn't need
-# mapping to the external network, is the one generating packets
-as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore])
-
-# hv1 should be in 'ref_chassis' of the ha_chasssi_group as logical
-# switch 'foo' can reach the router 'R1' (which has gw router port)
-# via foo1 -> foo -> R0 -> join -> R1
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$hv1_ch_uuid" = "$ref_ch_list"])
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 2
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-test_ip_packet()
-{
-    local active_gw=$1
-    local backup_gw=$2
-
-    # Send ip packet between foo1 and outside1
-    src_mac="f00000010203" # foo1 mac
-    dst_mac="000001010203" # foo-R0 mac (internal router leg)
-    src_ip=`ip_to_hex 192 168 1 2`
-    dst_ip=`ip_to_hex 172 16 1 3`
-    packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-    # ARP request packet to expect at outside1
-    #arp_request=ffffffffffff${src_mac}08060001080006040001${src_mac}${src_ip}000000000000${dst_ip}
-
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-    # Send ARP reply from outside1 back to the router
-    # XXX: note, we could avoid this if we plug this port into a netns
-    # and setup the IP address into the port, so the kernel would simply reply
-    src_mac="000002010203"
-    reply_mac="f00000010204"
-    dst_ip=`ip_to_hex 172 16 1 3`
-    src_ip=`ip_to_hex 172 16 1 1`
-    arp_reply=${src_mac}${reply_mac}08060001080006040002${reply_mac}${dst_ip}${src_mac}${src_ip}
-
-    as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply
-
-    OVS_WAIT_UNTIL([
-        test `as $active_gw ovs-ofctl dump-flows br-int | grep table=66 | \
-grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
-    ])
-
-    # Packet to Expect at ext1 chassis, outside1 port
-    src_mac="000002010203"
-    dst_mac="f00000010204"
-    src_ip=`ip_to_hex 192 168 1 2`
-    dst_ip=`ip_to_hex 172 16 1 3`
-    expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
-    echo $expected > ext1-vif1.expected
-    exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101
-    echo $exp_gw_ip_garp >> ext1-vif1.expected
-
-    as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1
-    as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1
-    as ext1 reset_pcap_file ext1-vif1 ext1/vif1
-
-    # Resend packet from foo1 to outside1
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-    OVN_CHECK_PACKETS([ext1/vif1-tx.pcap], [ext1-vif1.expected])
-    $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $active_gw/br-phys_n1-tx.pcap  > packets
-    cat packets | grep $expected > exp
-    cat packets | grep $exp_gw_ip_garp | head -1 >> exp
-    AT_CHECK([cat exp], [0], [expout])
-
-    $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $backup_gw/br-phys_n1-tx.pcap  > packets
-    AT_CHECK([grep $expected packets | sort], [0], [])
-}
-
-test_ip_packet gw1 gw2
-
-ovn-nbctl --timeout=3 --wait=hv \
-    --id=@gc0 create Gateway_Chassis name=alice_gw1 \
-                                     chassis_name=gw1 \
-                                     priority=10 -- \
-    --id=@gc1 create Gateway_Chassis name=alice_gw2 \
-                                     chassis_name=gw2 \
-                                     priority=20 -- \
-    set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]'
-
-test_ip_packet gw2 gw1
-
-OVN_CLEANUP([hv1],[gw1],[gw2],[ext1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 1 LR with distributed router gateway port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR R1 that has switches foo (192.168.1.0/24) and
-# alice (172.16.1.0/24) connected to it.  The logical port
-# between R1 and alice has a "redirect-chassis" specified,
-# i.e. it is the distributed router gateway port.
-# Switch alice also has a localnet port defined.
-# An additional switch outside has a localnet port and the
-# same subnet as alice (172.16.1.0/24).
-
-# Physical network:
-# Three hypervisors hv[123].
-# hv1 hosts vif foo1.
-# hv2 is the "redirect-chassis" that hosts the distributed
-# router gateway port.
-# hv3 hosts vif outside1.
-# In order to show that connectivity works only through hv2,
-# an initial round of tests is run without any bridge-mapping
-# defined for the localnet on hv2.  These tests are expected
-# to fail.
-# Subsequent tests are run after defining the bridge-mapping
-# for the localnet on hv2. These tests are expected to succeed.
-
-# Create three hypervisors and create OVS ports corresponding
-# to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-int hv3-vif1 -- \
-    set interface hv3-vif1 external-ids:iface-id=outside1 \
-    options:tx_pcap=hv3/vif1-tx.pcap \
-    options:rxq_pcap=hv3/vif1-rx.pcap \
-    ofport-request=1
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add outside
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect alice to R1 as distributed router gateway port on hv2
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \
-    -- set Logical_Router_Port alice options:redirect-chassis="hv2"
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port outside1 in outside
-ovn-nbctl lsp-add outside outside1 \
--- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3"
-
-# Create localnet port in alice
-ovn-nbctl lsp-add alice ln-alice
-ovn-nbctl lsp-set-addresses ln-alice unknown
-ovn-nbctl lsp-set-type ln-alice localnet
-ovn-nbctl lsp-set-options ln-alice network_name=phys
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Create bridge-mappings on hv1 and hv3, leaving hv2 for later
-as hv1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 2
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-ovn-sbctl list chassis
-ovn-sbctl list encap
-echo "------ Gateway_Chassis dump (SBDB) -------"
-ovn-sbctl list Gateway_Chassis
-echo "------ Port_Binding chassisredirect -------"
-ovn-sbctl find Port_Binding type=chassisredirect
-echo "-------------------------------------------"
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-echo "------ hv3 dump ----------"
-as hv3 ovs-ofctl show br-int
-as hv3 ovs-ofctl dump-flows br-int
-echo "--------------------------"
-
-
-# Check that redirect mapping is programmed only on hv2
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l], [0], [1
-])
-# Check that hv1 sends chassisredirect port traffic to hv2
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | grep output | wc -l], [0], [1
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | wc -l], [0], [0
-])
-# Check that arp reply on distributed gateway port is only programmed on hv2
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [1
-])
-
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-
-: > hv2-vif1.expected
-: > hv3-vif1.expected
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local hv=$1 inport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6
-    local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
-
-    if test X$reply_ha != X; then
-        # Expect to receive the reply, if any.
-        local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-        echo $reply >> hv${hv}-vif$inport.expected
-    fi
-}
-
-rtr_ip=$(ip_to_hex 172 16 1 1)
-foo_ip=$(ip_to_hex 192 168 1 2)
-outside_ip=$(ip_to_hex 172 16 1 3)
-
-echo $rtr_ip
-echo $foo_ip
-echo $outside_ip
-
-# ARP for router IP address from outside1, no response expected
-test_arp 3 1 f00000010204 $outside_ip $rtr_ip
-
-# Now check the packets actually received against the ones expected.
-OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected])
-
-# Send ip packet between foo1 and outside1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 3`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-# Now check the packets actually received against the ones expected.
-OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected])
-
-# Now add bridge-mappings on hv2, which should make everything work
-as hv2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-# Wait until the patch ports are created in hv2 to connect br-int to br-phys
-OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \
-grep "Port patch-br-int-to-ln-alice" | wc -l`])
-
-# ARP for router IP address from outside1
-test_arp 3 1 f00000010204 $outside_ip $rtr_ip 000002010203
-
-# hv3-vif1.expected should also have the gw router port garp packet.
-exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101
-echo $exp_gw_ip_garp >> hv3-vif1.expected
-
-# Now check the packets actually received against the ones expected.
-OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected])
-
-# Send ip packet between foo1 and outside1
-src_mac="f00000010203"
-dst_mac="000001010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 172 16 1 3`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-# Packet to Expect at outside1
-src_mac="000002010203"
-dst_mac="f00000010204"
-expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-echo "------ hv1 dump ----------"
-as hv1 ovs-ofctl show br-int
-as hv1 ovs-ofctl dump-flows br-int
-echo "------ hv2 dump ----------"
-as hv2 ovs-ofctl show br-int
-as hv2 ovs-ofctl dump-flows br-int
-echo "------ hv3 dump ----------"
-as hv3 ovs-ofctl show br-int
-as hv3 ovs-ofctl dump-flows br-int
-echo "----------------------------"
-
-echo $expected >> hv3-vif1.expected
-OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected])
-
-#Check ovn-trace over "chassisredirect" port
-AT_CAPTURE_FILE([trace])
-ovn_trace () {
-    ovn-trace --all "$@" | tee trace | sed '1,/Minimal trace/d'
-}
-
-echo 'ip.ttl--;' > expout
-echo 'eth.src = 00:00:02:01:02:03;' >> expout
-echo 'eth.dst = f0:00:00:01:02:04;' >> expout
-echo 'output("ln-alice");' >> expout
-AT_CHECK_UNQUOTED([ovn_trace foo 'inport == "foo1" && eth.src == f0:00:00:01:02:03 && eth.dst == 00:00:01:01:02:03 && ip4.src == 192.168.1.2 && ip4.dst == 172.16.1.3 && ip.ttl == 0xff'], [0], [expout])
-
-# Create logical port alice1 in alice on hv1
-as hv1 ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=1
-
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.4"
-
-# Create logical port foo2 in foo on hv2
-as hv2 ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=foo2 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-ovn-nbctl lsp-add foo foo2 \
--- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-: > hv1-vif2.expected
-
-# Send ip packet between alice1 and foo2
-src_mac="f00000010205"
-dst_mac="000002010203"
-src_ip=`ip_to_hex 172 16 1 4`
-dst_ip=`ip_to_hex 192 168 1 3`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
-
-# Packet to Expect at foo2
-src_mac="000001010203"
-dst_mac="f00000010206"
-src_ip=`ip_to_hex 172 16 1 4`
-dst_ip=`ip_to_hex 192 168 1 3`
-expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
-
-echo $expected >> hv2-vif1.expected
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [hv2-vif1.expected])
-
-AT_CHECK([ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=cr-alice | wc -l], [0], [1
-])
-
-ovn-nbctl --timeout=3 --wait=sb remove Logical_Router_Port alice options redirect-chassis
-
-AT_CHECK([ovn-sbctl find Port_Binding logical_port=cr-alice | wc -l], [0], [0
-])
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- send gratuitous arp for NAT rules on distributed router])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-# Create logical switches
-ovn-nbctl ls-add ls0
-ovn-nbctl ls-add ls1
-# Create distributed router
-ovn-nbctl create Logical_Router name=lr0
-# Add distributed gateway port to distributed router
-ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24 \
-    -- set Logical_Router_Port lrp0 options:redirect-chassis="hv2"
-ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \
-    type=router options:router-port=lrp0 addresses="router"
-# Add router port to ls1
-ovn-nbctl lrp-add lr0 lrp1 f0:00:00:00:00:02 10.0.0.1/24
-ovn-nbctl lsp-add ls1 lrp1-rp -- set Logical_Switch_Port lrp1-rp \
-    type=router options:router-port=lrp1 addresses="router"
-# Add logical ports for NAT rules
-ovn-nbctl lsp-add ls1 foo1 \
--- lsp-set-addresses foo1 "00:00:00:00:00:03 10.0.0.3"
-ovn-nbctl lsp-add ls1 foo2 \
--- lsp-set-addresses foo2 "00:00:00:00:00:04 10.0.0.4"
-# Add nat-addresses option
-ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
-# Add NAT rules
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.1 10.0.0.0/24])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 192.168.0.2 10.0.0.2])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 192.168.0.3 10.0.0.3 foo1 f0:00:00:00:00:03])
-AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 192.168.0.4 10.0.0.4 foo2 f0:00:00:00:00:04])
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-AT_CHECK([ovs-vsctl add-port br-phys snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-# Initially test with no bridge-mapping on hv2, expect to receive no packets
-
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-# Initially test with no bridge-mapping on hv3
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1])
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 2
-
-# Expect no packets when hv2 bridge-mapping is not present
-: > packets
-OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [packets])
-
-# Add bridge-mapping on hv2
-AT_CHECK([as hv2 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-
-# Wait until the patch ports are created in hv2 to connect br-int to br-phys
-OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \
-grep "Port patch-br-int-to-ln_port" | wc -l`])
-
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
-echo $expected > expout
-expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002"
-echo $expected >> expout
-AT_CHECK([sort packets], [0], [expout])
-sort packets | cat
-
-# Temporarily remove nat-addresses option to avoid race conditions
-# due to GARP backoff
-ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses=""
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-as hv1 reset_pcap_file snoopvif hv1/snoopvif
-
-# Add OVS ports for foo1 and foo2 on hv3
-ovs-vsctl -- add-port br-int hv3-vif1 -- \
-    set interface hv3-vif1 external-ids:iface-id=foo1 \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv3-vif2 -- \
-    set interface hv3-vif2 external-ids:iface-id=foo2 \
-    ofport-request=2
-
-# Add bridge-mapping on hv3
-AT_CHECK([as hv3 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-
-# Wait until the patch ports are created in hv3 to connect br-int to br-phys
-OVS_WAIT_UNTIL([test 1 = `as hv3 ovs-vsctl show | \
-grep "Port patch-br-int-to-ln_port" | wc -l`])
-
-# Re-add nat-addresses option
-ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
-
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
-garp_1="fffffffffffff0000000000308060001080006040001f00000000003c0a80003000000000000c0a80003"
-echo $garp_1 > expout
-garp_2="fffffffffffff0000000000408060001080006040001f00000000004c0a80004000000000000c0a80004"
-echo $garp_2 >> expout
-
-cat packets | grep $garp_1 | head -1 > exp
-cat packets | grep $garp_2 | head -1 >> exp
-AT_CHECK([cat exp], [0], [expout])
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-# VLAN traffic for external network redirected through distributed router
-# gateway port should use vlans(i.e input network vlan tag) across hypervisors
-# instead of tunneling.
-AT_SETUP([ovn -- vlan traffic for external network with distributed router gateway port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# # One LR R1 that has switches foo (192.168.1.0/24) and
-# # alice (172.16.1.0/24) connected to it.  The logical port
-# # between R1 and alice has a "redirect-chassis" specified,
-# # i.e. it is the distributed router gateway port(172.16.1.6).
-# # Switch alice also has a localnet port defined.
-# # An additional switch outside has the same subnet as alice
-# # (172.16.1.0/24), a localnet port and nexthop port(172.16.1.1)
-# # which will receive the packet destined for external network
-# # (i.e 8.8.8.8 as destination ip).
-
-# Physical network:
-# # Four hypervisors hv[1234].
-# # hv1 hosts vif foo1.
-# # hv2 is the "redirect-chassis" that hosts the distributed router gateway port.
-# # Later to test GARPs for the router port - foo, hv2 and hv4 are added to the ha_chassis_group
-# # hv3 hosts nexthop port vif outside1.
-# # All other tests connect hypervisors to network n1 through br-phys for tunneling.
-# # But in this test, hv1 won't connect to n1(and no br-phys in hv1), and
-# # in order to show vlans(instead of tunneling) used between hv1 and hv2,
-# # a new network n2 created and hv1 and hv2 connected to this network through br-ex.
-# # hv2 and hv3 are still connected to n1 network through br-phys.
-net_add n1
-
-# We are not calling ovn_attach for hv1, to avoid adding br-phys.
-# Tunneling won't work in hv1 as ovn-encap-ip is not added to any bridge in hv1
-sim_add hv1
-as hv1
-ovs-vsctl \
-    -- set Open_vSwitch . external-ids:system-id=hv1 \
-    -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-    -- set Open_vSwitch . external-ids:ovn-encap-type=geneve,vxlan \
-    -- set Open_vSwitch . external-ids:ovn-encap-ip=192.168.0.1 \
-    -- add-br br-int \
-    -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
-    -- set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-ex
-
-start_daemon ovn-controller
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="public:br-ex,phys:br-phys"
-
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-int hv3-vif1 -- \
-    set interface hv3-vif1 external-ids:iface-id=outside1 \
-    options:tx_pcap=hv3/vif1-tx.pcap \
-    options:rxq_pcap=hv3/vif1-rx.pcap \
-    ofport-request=1
-ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="phys:br-phys"
-
-sim_add hv4
-as hv4
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.4
-ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="public:br-ex,phys:br-phys"
-
-# Create network n2 for vlan connectivity between hv1 and hv2
-net_add n2
-
-as hv1
-ovs-vsctl add-br br-ex
-net_attach n2 br-ex
-
-as hv2
-ovs-vsctl add-br br-ex
-net_attach n2 br-ex
-
-as hv4
-ovs-vsctl add-br br-ex
-net_attach n2 br-ex
-
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add outside
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect alice to R1 as distributed router gateway port (172.16.1.6) on hv2
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.6/24 \
-    -- set Logical_Router_Port alice options:redirect-chassis="hv2"
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router \
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port outside1 in outside, which is a nexthop address
-# for 172.16.1.0/24
-ovn-nbctl lsp-add outside outside1 \
--- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.1"
-
-# Set default gateway (nexthop) to 172.16.1.1
-ovn-nbctl lr-route-add R1 "0.0.0.0/0" 172.16.1.1 alice
-AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.6 192.168.1.1/24])
-ovn-nbctl set Logical_Switch_Port rp-alice options:nat-addresses=router
-
-ovn-nbctl lsp-add foo ln-foo
-ovn-nbctl lsp-set-addresses ln-foo unknown
-ovn-nbctl lsp-set-options ln-foo network_name=public
-ovn-nbctl lsp-set-type ln-foo localnet
-AT_CHECK([ovn-nbctl set Logical_Switch_Port ln-foo tag=2])
-
-# Create localnet port in alice
-ovn-nbctl lsp-add alice ln-alice
-ovn-nbctl lsp-set-addresses ln-alice unknown
-ovn-nbctl lsp-set-type ln-alice localnet
-ovn-nbctl lsp-set-options ln-alice network_name=phys
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-ovn-nbctl --wait=hv --timeout=3 sync
-
-# Check that there is a logical flow in logical switch foo's pipeline
-# to set the outport to rp-foo (which is expected).
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
-grep rp-foo | grep -v is_chassis_resident | wc -l`])
-
-# Set the option 'reside-on-redirect-chassis' for foo
-ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true
-# Check that there is a logical flow in logical switch foo's pipeline
-# to set the outport to rp-foo with the condition is_chassis_redirect.
-ovn-sbctl dump-flows foo
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
-grep rp-foo | grep is_chassis_resident | wc -l`])
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list nat
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-ovn-sbctl list chassis
-echo "---------------------"
-
-for chassis in hv1 hv2 hv3; do
-    as $chassis
-    echo "------ $chassis dump ----------"
-    ovs-vsctl show br-int
-    ovs-ofctl show br-int
-    ovs-ofctl dump-flows br-int
-    echo "--------------------------"
-done
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-foo1_ip=$(ip_to_hex 192 168 1 2)
-gw_ip=$(ip_to_hex 172 16 1 6)
-dst_ip=$(ip_to_hex 8 8 8 8)
-nexthop_ip=$(ip_to_hex 172 16 1 1)
-
-foo1_mac="f00000010203"
-foo_mac="000001010203"
-gw_mac="000002010203"
-nexthop_mac="f00000010204"
-
-# Send ip packet from foo1 to 8.8.8.8
-src_mac="f00000010203"
-dst_mac="000001010203"
-packet=${foo_mac}${foo1_mac}08004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000
-
-# Wait for GARPs announcing gw IP to arrive
-OVS_WAIT_UNTIL([
-    test `as hv2 ovs-ofctl dump-flows br-int | grep table=66 | \
-grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
-    ])
-
-# VLAN tagged packet with router port(192.168.1.1) MAC as destination MAC
-# is expected on bridge connecting hv1 and hv2
-expected=${foo_mac}${foo1_mac}8100000208004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000
-echo $expected > hv1-br-ex_n2.expected
-
-# Packet to Expect at outside1 i.e nexthop(172.16.1.1) port.
-# As connection tracking not enabled for this test, snat can't be done on the packet.
-# We still see foo1 as the source ip address. But source mac(gateway MAC) and
-# dest mac(nexthop mac) are properly configured.
-expected=${nexthop_mac}${gw_mac}08004500001c000000003f110100${foo1_ip}${dst_ip}0035111100080000
-echo $expected > hv3-vif1.expected
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-as hv1 reset_pcap_file br-ex_n2 hv1/br-ex_n2
-as hv3 reset_pcap_file hv3-vif1 hv3/vif1
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-sleep 2
-
-# On hv1, table 32 check that no packet goes via the tunnel port
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \
-| grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0
-]])
-
-ip_packet() {
-    grep "1010203f00000010203"
-}
-
-# Check vlan tagged packet on the bridge connecting hv1 and hv2 with the
-# foo1's mac.
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-ex_n2-tx.pcap | ip_packet | uniq > hv1-br-ex_n2
-cat hv1-br-ex_n2.expected > expout
-AT_CHECK([sort hv1-br-ex_n2], [0], [expout])
-
-# Check expected packet on nexthop interface
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/vif1-tx.pcap | grep ${foo1_ip}${dst_ip} | uniq > hv3-vif1
-cat hv3-vif1.expected > expout
-AT_CHECK([sort hv3-vif1], [0], [expout])
-
-# Test the GARP for the router port ip - 192.168.1.1
-ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
-
-as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-as hv2 reset_pcap_file br-ex_n2 hv2/br-ex_n2
-as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2
-
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv2 30
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 20
-
-hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1`
-ovn-nbctl remove logical_router_port alice options redirect-chassis
-ovn-nbctl --wait=sb set logical_router_port alice ha_chassis_group=$hagrp1_uuid
-
-# When hv2 claims the gw router port cr-alice, it should send out
-# GARP for 192.168.1.1 and it should be received by foo1 on hv1.
-
-# foo1 (on hv1) should receive GARP without VLAN tag
-exp_garp_on_foo1="ffffffffffff00000101020308060001080006040001000001010203c0a80101000000000000c0a80101"
-echo $exp_garp_on_foo1 > foo1.expout
-
-# ovn-controller on hv2 should send garp with VLAN tag
-sent_garp="ffffffffffff0000010102038100000208060001080006040001000001010203c0a80101000000000000c0a80101"
-
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [foo1.expout])
-# Wait until we receive atleast 1 packet
-OVS_WAIT_UNTIL([test 1=`$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap | wc -l`])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap | head -1 > packets
-echo $sent_garp > expout
-AT_CHECK([cat packets], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv4/br-ex_n2-tx.pcap > empty
-AT_CHECK([cat empty], [0], [])
-
-# Make hv4 master
-as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 40
-
-# Wait till cr-alice is claimed by hv4
-hv4_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=hv4)
-# check that the chassis redirect port has been claimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-alice | grep $hv4_chassis | wc -l], [0],[[1
-]])
-
-# Reset the pcap file for hv2/br-ex_n2. From now on ovn-controller in hv2
-# should not send GARPs for the router ports.
-as hv2 reset_pcap_file br-ex_n2 hv2/br-ex_n2
-
-echo $sent_garp > br-ex_n2.expout
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [foo1.expout])
-OVN_CHECK_PACKETS([hv4/br-ex_n2-tx.pcap], [br-ex_n2.expout])
-
-sleep 2
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap > empty
-AT_CHECK([cat empty], [0], [])
-
-OVN_CLEANUP([hv1],[hv2],[hv3], [hv4])
-AT_CLEANUP
-
-AT_SETUP([ovn -- IPv6 ND Router Solicitation responder])
-AT_KEYWORDS([ovn-nd_ra])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# In this test case we create 1 lswitch with 3 VIF ports attached,
-# and a lrouter connected to the lswitch.
-# We generate the Router solicitation packet and verify the Router Advertisement
-# reply packet from the ovn-controller.
-
-# Create hypervisor and logical switch lsw0, logical router lr0, attach lsw0
-# onto lr0, set Logical_Router_Port.ipv6_ra_configs:address_mode column to
-# 'slaac' to allow lrp0 send RA for SLAAC mode.
-ovn-nbctl ls-add lsw0
-ovn-nbctl lr-add lr0
-ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:00:00:01 fdad:1234:5678::1/64
-ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac"
-ovn-nbctl \
-    -- lsp-add lsw0 lsp0 \
-    -- set Logical_Switch_Port lsp0 type=router \
-                     options:router-port=lrp0 \
-                     addresses='"fa:16:3e:00:00:01 fdad:1234:5678::1"'
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-ovn-nbctl lsp-add lsw0 lp1
-ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
-ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
-
-ovn-nbctl lsp-add lsw0 lp2
-ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
-ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3"
-
-ovn-nbctl lsp-add lsw0 lp3
-ovn-nbctl lsp-set-addresses lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
-ovn-nbctl lsp-set-port-security lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4"
-
-# Add ACL rule for ICMPv6 on lsw0
-ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6'  allow-related
-ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6'  allow-related
-ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6'  allow-related
-ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6'  allow-related
-
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-ovs-vsctl -- add-port br-int hv1-vif3 -- \
-    set interface hv1-vif3 external-ids:iface-id=lp3 \
-    options:tx_pcap=hv1/vif3-tx.pcap \
-    options:rxq_pcap=hv1/vif3-rx.pcap \
-    ofport-request=3
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-# Make sure that ovn-controller has installed the corresponding OF Flow.
-OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
-
-# This shell function sends a Router Solicitation packet.
-# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
-test_ipv6_ra() {
-    local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
-    local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
-
-    local len=24
-    local mtu_opt=""
-    if test $mtu != 0; then
-        len=`expr $len + 8`
-        mtu_opt=05010000${mtu}
-    fi
-
-    if test ${#prefix_opt} != 0; then
-        prefix_opt=${prefix_opt}fdad1234567800000000000000000000
-        len=`expr $len + ${#prefix_opt} / 2`
-    fi
-
-    len=$(printf "%x" $len)
-    local lrp_mac=fa163e000001
-    local lrp_lla=fe80000000000000f8163efffe000001
-    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
-    echo $reply >> $inport.expected
-
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
-}
-
-AT_CAPTURE_FILE([ofctl_monitor0.log])
-as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
-
-# MTU is not set and the address mode is set to slaac
-addr_mode=00
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config
-
-# NXT_RESUME should be 1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
-
-cat 1.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 1.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
-
-# Set the MTU to 1500
-ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
-
-# Make sure that ovn-controller has installed the corresponding OF Flow.
-OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
-
-addr_mode=00
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000003
-src_lla=fe80000000000000f8163efffe000003
-mtu=000005dc
-
-test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
-
-# NXT_RESUME should be 2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap  > 2.packets
-
-cat 2.expected | cut -c -112 > expout
-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 2.expected | cut -c 117- > expout
-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
-
-# Set the address mode to dhcpv6_stateful
-ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
-# Make sure that ovn-controller has installed the corresponding OF Flow.
-OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
-
-addr_mode=80
-default_prefix_option_config=03044080ffffffffffffffff00000000
-src_mac=fa163e000004
-src_lla=fe80000000000000f8163efffe000004
-mtu=000005dc
-
-test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
-
-# NXT_RESUME should be 3.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap  > 3.packets
-
-cat 3.expected | cut -c -112 > expout
-AT_CHECK([cat 3.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 3.expected | cut -c 117- > expout
-AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
-
-# Set the address mode to dhcpv6_stateless
-ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
-# Make sure that ovn-controller has installed the corresponding OF Flow.
-OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
-
-addr_mode=40
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-mtu=000005dc
-
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
-
-# NXT_RESUME should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
-
-cat 1.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 1.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
-
-# Set the address mode to invalid.
-ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid
-# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA.
-OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
-
-addr_mode=40
-default_prefix_option_config=""
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-mtu=000005dc
-
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
-
-# NXT_RESUME should be 4 only.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
-AT_CHECK([cat 1.packets], [0], [])
-
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- /32 router IP address])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# 2 LS 'foo' and 'alice' connected via router R1.
-# R1 connects to 'alice' with a /32 IP address. We use static routes and
-# nexthop to push traffic to a logical port in switch 'alice'
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
-          options:router-port=foo addresses=\"00:00:00:01:02:03\"
-
-# Connect alice to R1.
-ovn-nbctl lrp-add R1 alice 00:00:00:01:02:04 172.16.1.1/32
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-          type=router options:router-port=alice addresses=\"00:00:00:01:02:04\"
-
-# Create logical port foo1 in foo
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical port alice1 in alice
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 10.0.0.2"
-
-#install default route in R1 to use alice1's IP address as nexthop
-ovn-nbctl lr-route-add R1 0.0.0.0/0 10.0.0.2 alice
-
-# Create two hypervisor and create OVS ports corresponding to logical ports.
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=foo1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=alice1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-# Send ip packets between foo1 and alice1
-src_mac="f00000010203"
-dst_mac="000000010203"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 10 0 0 2`
-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-
-# Send the first packet to trigger a ARP response and population of
-# mac_bindings table.
-as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="10.0.0.2" | wc -l` -gt 0])
-ovn-nbctl --wait=hv sync
-
-# Packet to Expect at 'alice1'
-src_mac="000000010204"
-dst_mac="f00000010204"
-src_ip=`ip_to_hex 192 168 1 2`
-dst_ip=`ip_to_hex 10 0 0 2`
-echo "${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 1 lport/HV, localport ports])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-# Add localport to the switch
-ovn-nbctl lsp-add ls1 lp01
-ovn-nbctl lsp-set-addresses lp01 f0:00:00:00:00:01
-ovn-nbctl lsp-set-type lp01 localport
-
-net_add n1
-
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-    ovs-vsctl add-port br-int vif01 -- \
-        set Interface vif01 external-ids:iface-id=lp01 \
-                              options:tx_pcap=hv${i}/vif01-tx.pcap \
-                              options:rxq_pcap=hv${i}/vif01-rx.pcap \
-                              ofport-request=${i}0
-
-    ovs-vsctl add-port br-int vif${i}1 -- \
-        set Interface vif${i}1 external-ids:iface-id=lp${i}1 \
-                              options:tx_pcap=hv${i}/vif${i}1-tx.pcap \
-                              options:rxq_pcap=hv${i}/vif${i}1-rx.pcap \
-                              ofport-request=${i}1
-
-    ovn-nbctl lsp-add ls1 lp${i}1
-    ovn-nbctl lsp-set-addresses lp${i}1 f0:00:00:00:00:${i}1
-    ovn-nbctl lsp-set-port-security lp${i}1 f0:00:00:00:00:${i}1
-
-        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup])
-done
-
-ovn-nbctl --wait=sb sync
-ovn-sbctl dump-flows
-
-OVN_POPULATE_ARP
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    echo hv${1%?}
-}
-#
-# test_packet INPORT DST SRC ETHTYPE EOUT LOUT DEFHV
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  INPORT is specified as
-# logical switch port numbers, e.g. 11 for vif11.
-#
-# EOUT is the end-to-end output port, that is, where the packet will end up
-# after possibly bouncing through one or more localnet ports.  LOUT is the
-# logical output port, which might be a localnet port, as seen by ovn-trace
-# (which doesn't know what localnet ports are connected to and therefore can't
-# figure out the end-to-end answer).
-#
-# DEFHV is the default hypervisor from where the packet is going to be sent
-# if the source port is a localport.
-for i in 1 2; do
-    for j in 0 1; do
-        : > $i$j.expected
-    done
-done
-test_packet() {
-    local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 defhv=$7
-    echo "$@"
-
-    # First try tracing the packet.
-    uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
-    if test $lout != drop; then
-        echo "output(\"$lout\");"
-    fi > expout
-    AT_CAPTURE_FILE([trace])
-    AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
-
-    # Then actually send a packet, for an end-to-end test.
-    local packet=$(echo $dst$src | sed 's/://g')${eth}
-    hv=`vif_to_hv $inport`
-    # If hypervisor 0 (localport) use the defhv parameter
-    if test $hv = hv0; then
-        hv=$defhv
-    fi
-    vif=vif$inport
-    as $hv ovs-appctl netdev-dummy/receive $vif $packet
-    if test $eout != drop; then
-        echo $packet >> ${eout#lp}.expected
-    fi
-}
-
-
-# lp11 and lp21 are on different hypervisors
-test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21
-test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11
-
-# Both VIFs should be able to reach the localport on their own HV
-test_packet 11 f0:00:00:00:00:01 f0:00:00:00:00:11 1101 lp01 lp01
-test_packet 21 f0:00:00:00:00:01 f0:00:00:00:00:21 2101 lp01 lp01
-
-# Packet sent from localport on same hv should reach the vif
-test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 lp11 lp11 hv1
-test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 lp21 lp21 hv2
-
-# Packet sent from localport on different hv should be dropped
-test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 drop lp21 hv1
-test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 drop lp11 hv2
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2; do
-    for j in 0 1; do
-        OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
-    done
-done
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 1 LR with HA distributed router gateway port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-
-# create gateways with external network connectivity
-
-for i in 1 2; do
-    sim_add gw$i
-    as gw$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-done
-
-ovn-nbctl ls-add inside
-ovn-nbctl ls-add outside
-
-# create hypervisors with a vif port each to an internal network
-
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.1$i
-    ovs-vsctl -- add-port br-int hv$i-vif1 -- \
-        set interface hv$i-vif1 external-ids:iface-id=inside$i \
-        options:tx_pcap=hv$i/vif1-tx.pcap \
-        options:rxq_pcap=hv$i/vif1-rx.pcap \
-        ofport-request=1
-
-        ovn-nbctl lsp-add inside inside$i \
-            -- lsp-set-addresses inside$i "f0:00:00:01:22:$i 192.168.1.10$i"
-
-done
-
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-
-# Connect inside to R1
-ovn-nbctl lrp-add R1 inside 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add inside rp-inside -- set Logical_Switch_Port rp-inside \
-    type=router options:router-port=inside \
-    -- lsp-set-addresses rp-inside router
-
-# Connect outside to R1 as distributed router gateway port on gw1+gw2
-ovn-nbctl lrp-add R1 outside 00:00:02:01:02:04 192.168.0.101/24
-
-ovn-nbctl --id=@gc0 create Gateway_Chassis \
-                    name=outside_gw1 chassis_name=gw1 priority=20 -- \
-          --id=@gc1 create Gateway_Chassis \
-                    name=outside_gw2 chassis_name=gw2 priority=10 -- \
-          set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]'
-
-ovn-nbctl lsp-add outside rp-outside -- set Logical_Switch_Port rp-outside \
-    type=router options:router-port=outside \
-    -- lsp-set-addresses rp-outside router
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-ovn-nbctl --wait=hv --timeout=3 sync
-
-echo "---------NB dump-----"
-ovn-nbctl show
-echo "---------------------"
-ovn-nbctl list logical_router
-echo "---------------------"
-ovn-nbctl list logical_router_port
-echo "---------------------"
-
-echo "---------SB dump-----"
-ovn-sbctl list datapath_binding
-echo "---------------------"
-ovn-sbctl list port_binding
-echo "---------------------"
-ovn-sbctl dump-flows
-echo "---------------------"
-ovn-sbctl list chassis
-ovn-sbctl list encap
-echo "---------------------"
-echo "------ Gateway_Chassis dump (SBDB) -------"
-ovn-sbctl list Gateway_Chassis
-echo "------ Port_Binding chassisredirect -------"
-ovn-sbctl find Port_Binding type=chassisredirect
-echo "-------------------------------------------"
-
-# There should be one ha_chassis_group with the name "outside"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="outside"`
-
-AT_CHECK([test $ha_chassi_grp_name = outside])
-
-# There should be 2 ha_chassis rows in SB DB.
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | awk '{print $3}' \
-| grep '-' | wc -l ], [0], [2
-])
-
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-# Trim the spaces.
-ha_ch=`echo $ha_ch | sed 's/ //g'`
-
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
-
-for chassis in gw1 gw2 hv1 hv2; do
-    as $chassis
-    echo "------ $chassis dump ----------"
-    ovs-ofctl show br-int
-    ovs-ofctl dump-flows br-int
-    echo "--------------------------"
-done
-bfd_dump() {
-    for chassis in gw1 gw2 hv1 hv2; do
-        as $chassis
-        echo "------ $chassis dump (BFD)----"
-        echo "BFD (from $chassis):"
-        # dump BFD config and status to the other chassis
-        for chassis2 in gw1 gw2 hv1 hv2; do
-            if [[ "$chassis" != "$chassis2" ]]; then
-                echo " -> $chassis2:"
-                echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis2-0)"
-            fi
-        done
-        echo "--------------------------"
-    done
-}
-
-bfd_dump
-
-hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
-hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
-hv2_gw1_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
-hv2_gw2_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
-
-echo $hv1_gw1_ofport
-echo $hv1_gw2_ofport
-echo $hv2_gw1_ofport
-echo $hv2_gw2_ofport
-
-echo "--- hv1 ---"
-as hv1 ovs-ofctl dump-flows br-int table=32
-
-echo "--- hv2 ---"
-as hv2 ovs-ofctl dump-flows br-int table=32
-
-gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1)
-gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2)
-
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
-| wc -l], [0], [1
-])
-
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
-| wc -l], [0], [1
-])
-
-# make sure that flows for handling the outside router port reside on gw1
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# make sure ARP responder flows for outside router port reside on gw1 too
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \
-grep arp_tpa=192.168.0.101 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0
-]])
-
-# check that the chassis redirect port has been claimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
-
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"`
-
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $hv1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $hv2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-
-# at this point, we invert the priority of the gw chassis between gw1 and gw2
-
-ovn-nbctl --id=@gc0 create Gateway_Chassis \
-                    name=outside_gw1 chassis_name=gw1 priority=10 -- \
-          --id=@gc1 create Gateway_Chassis \
-                    name=outside_gw2 chassis_name=gw2 priority=20 -- \
-          set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]'
-
-
-# XXX: Let the change propagate down to the ovn-controllers
-ovn-nbctl --wait=hv --timeout=3 sync
-
-# we make sure that the hypervisors noticed, and inverted the slave ports
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
-| wc -l], [0], [1
-])
-
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
-| wc -l], [0], [1
-])
-
-# check that the chassis redirect port has been reclaimed by the gw2 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1
-]])
-
-# check BFD enablement on tunnel ports from gw1 #########
-as gw1
-for chassis in gw2 hv1 hv2; do
-    echo "checking gw1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-
-
-# check BFD enablement on tunnel ports from gw2 ##########
-as gw2
-for chassis in gw1 hv1 hv2; do
-    echo "checking gw2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-
-# check BFD enablement on tunnel ports from hv1 ###########
-as hv1
-for chassis in gw1 gw2; do
-    echo "checking hv1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-# make sure BFD is not enabled to hv2, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
-         [[
-]])
-
-
-# check BFD enablement on tunnel ports from hv2 ##########
-as hv2
-for chassis in gw1 gw2; do
-    echo "checking hv2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-# make sure BFD is not enabled to hv1, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
-         [[
-]])
-
-# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# disconnect GW2 from the network, GW1 should take over
-as gw2
-port=${sandbox}_br-phys
-as main ovs-vsctl del-port n1 $port
-
-bfd_dump
-
-# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# check that the chassis redirect port has been reclaimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
-
-ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-rx"=2000
-as gw2
-for chassis in gw1 hv1 hv2; do
-    echo "checking gw2 -> $chassis"
-    OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_rx=2000"
-])
-done
-ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-tx"=1500
-for chassis in gw1 hv1 hv2; do
-    echo "checking gw2 -> $chassis"
-    OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_rx=2000 min_tx=1500"
-])
-done
-ovn-nbctl remove NB_Global . options "bfd-min-rx"
-ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=5
-for chassis in gw1 hv1 hv2; do
-    echo "checking gw2 -> $chassis"
-    OVS_WAIT_UNTIL([
-    bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_tx=1500 mult=5"
-])
-done
-
-# Delete the inside1 vif. The ref_chassis in ha_chassis_group shouldn't have
-# reference to hv1.
-as hv1 ovs-vsctl del-port hv1-vif1
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$hv2_ch_uuid" = "$ref_ch_list"])
-
-# Delete the inside2 vif.
-ovn-sbctl show
-
-echo "Deleting hv2-vif1"
-as hv2 ovs-vsctl del-port hv2-vif1
-
-# ref_chassis of ha_chassis_group should be empty
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     exp_ref_ch_list=""
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# Delete the Gateway_Chassis for lrp - outside
-ovn-nbctl clear Logical_Router_Port outside gateway_chassis
-
-# There shoud be no ha_chassis_group rows in SB DB.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`])
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
-
-ovn-nbctl remove NB_Global . options "bfd-min-rx"
-ovn-nbctl remove NB_Global . options "bfd-min-tx"
-ovn-nbctl remove NB_Global . options "bfd-mult"
-
-# Now test with HA chassis group instead of Gateway chassis in NB DB
-ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
-
-ovn-nbctl list ha_chassis_group
-ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1
-hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1`
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw1 30
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 20
-
-# ovn-northd should not create HA chassis group and HA chassis rows
-# unless the HA chassis group in OVN NB DB is associated to
-# a logical router port.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
-
-# Associate hagrp1 to outside logical router port
-ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \
-find ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
-
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
-| wc -l], [0], [1
-])
-
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
-| wc -l], [0], [1
-])
-
-# make sure that flows for handling the outside router port reside on gw1
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# make sure ARP responder flows for outside router port reside on gw1 too
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \
-grep arp_tpa=192.168.0.101 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0
-]])
-
-# check that the chassis redirect port has been claimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
-
-# Re add the ovs ports.
-for i in 1 2; do
-    as hv$i
-    ovs-vsctl -- add-port br-int hv$i-vif1 -- \
-        set interface hv$i-vif1 external-ids:iface-id=inside$i \
-        options:tx_pcap=hv$i/vif1-tx.pcap \
-        options:rxq_pcap=hv$i/vif1-rx.pcap \
-        ofport-request=1
-done
-
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"`
-
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $hv1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $hv2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
-
-# Increase the priority of gw2
-ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
-
-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
-| wc -l], [0], [1
-])
-
-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
-| wc -l], [0], [1
-])
-
-# check that the chassis redirect port has been reclaimed by the gw2 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1
-]])
-
-# check BFD enablement on tunnel ports from gw1 #########
-as gw1
-for chassis in gw2 hv1 hv2; do
-    echo "checking gw1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-
-# check BFD enablement on tunnel ports from gw2 ##########
-as gw2
-for chassis in gw1 hv1 hv2; do
-    echo "checking gw2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-
-# check BFD enablement on tunnel ports from hv1 ###########
-as hv1
-for chassis in gw1 gw2; do
-    echo "checking hv1 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-# make sure BFD is not enabled to hv2, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
-         [[
-]])
-
-# check BFD enablement on tunnel ports from hv2 ##########
-as hv2
-for chassis in gw1 gw2; do
-    echo "checking hv2 -> $chassis"
-    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
-             [[enable=true
-]])
-done
-# make sure BFD is not enabled to hv1, we don't need it
-AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
-         [[
-]])
-
-# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# disconnect GW2 from the network, GW1 should take over
-as gw2
-port=${sandbox}_br-phys
-as main ovs-vsctl del-port n1 $port
-
-bfd_dump
-
-# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[1
-]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
-grep 00:00:02:01:02:04 | wc -l], [0], [[0
-]])
-
-# check that the chassis redirect port has been reclaimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
-
-OVN_CLEANUP([gw1],[gw2],[hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- send gratuitous ARP for NAT rules on HA distributed router])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-ovn-nbctl ls-add ls0
-ovn-nbctl ls-add ls1
-ovn-nbctl create Logical_Router name=lr0
-ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.100/24
-
-ovn-nbctl --id=@gc0 create Gateway_Chassis \
-                    name=outside_gw1 chassis_name=hv2 priority=10 -- \
-          --id=@gc1 create Gateway_Chassis \
-                    name=outside_gw2 chassis_name=hv3 priority=1 -- \
-          set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]'
-
-ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \
-    type=router options:router-port=lrp0 addresses="router"
-ovn-nbctl lrp-add lr0 lrp1 f0:00:00:00:00:02 10.0.0.1/24
-ovn-nbctl lsp-add ls1 lrp1-rp -- set Logical_Switch_Port lrp1-rp \
-    type=router options:router-port=lrp1 addresses="router"
-
-# Add NAT rules
-AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.100 10.0.0.0/24])
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-AT_CHECK([ovs-vsctl add-port br-phys snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-AT_CHECK([as hv2 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-
-sim_add hv3
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-AT_CHECK([as hv3 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
-
-# Create a localnet port.
-AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
-AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
-AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
-AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
-# wait for earlier changes to take effect
-AT_CHECK([ovn-nbctl --timeout=3 --wait=hv sync], [0], [ignore])
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-as hv1 reset_pcap_file snoopvif hv1/snoopvif
-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
-as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1
-# add nat-addresses option
-ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
-
-# Wait for packets to be received through hv2.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-only_broadcast_from_lrp1() {
-    grep "fffffffffffff00000000001"
-}
-
-garp="fffffffffffff0000000000108060001080006040001f00000000001c0a80064000000000000c0a80064"
-echo $garp > expout
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoop_tx
-echo "packets on hv1-snoopvif:"
-cat hv1_snoop_tx
-AT_CHECK([sort hv1_snoop_tx], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
-echo "packets on hv2 br-phys tx"
-cat hv2_br_phys_tx
-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
-echo "packets on hv3 br-phys tx"
-cat hv3_br_phys_tx
-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [])
-
-
-# at this point, we invert the priority of the gw chassis between hv2 and hv3
-
-ovn-nbctl --wait=hv \
-          --id=@gc0 create Gateway_Chassis \
-                    name=outside_gw1 chassis_name=hv2 priority=1 -- \
-          --id=@gc1 create Gateway_Chassis \
-                    name=outside_gw2 chassis_name=hv3 priority=10 -- \
-          set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]'
-
-
-as hv1 reset_pcap_file snoopvif hv1/snoopvif
-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
-as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1
-
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
-
-# change localnet port tag.
-AT_CHECK([ovn-nbctl set Logical_Switch_Port ln_port tag=2014])
-
-# wait for earlier changes to take effect
-OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-ofctl dump-flows br-int table=65 | \
-grep "actions=mod_vlan_vid:2014" | wc -l`
-])
-
-OVS_WAIT_UNTIL([test 1 = `as hv3 ovs-ofctl dump-flows br-int table=65 | \
-grep "actions=mod_vlan_vid:2014" | wc -l`
-])
-
-# update nat-addresses option
-ovn-nbctl --wait=hv clear logical_switch_port lrp0-rp options
-
-#Wait until the Port_Binding.nat_addresses is cleared.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns nat_addresses find port_binding \
-logical_port=lrp0-rp | grep is_chassis | wc -l`])
-
-as hv1 reset_pcap_file snoopvif hv1/snoopvif
-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
-as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1
-
-ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
-
-#Wait until the Port_Binding.nat_addresses is set.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns nat_addresses find port_binding \
-logical_port=lrp0-rp | grep is_chassis | wc -l`])
-
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-garp="fffffffffffff00000000001810007de08060001080006040001f00000000001c0a80064000000000000c0a80064"
-echo $garp > expout
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ensure one gw controller restart in HA doesn't bounce the master])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-
-# create two gateways with external network connectivity
-for i in 1 2; do
-    sim_add gw$i
-    as gw$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-done
-
-ovn-nbctl ls-add inside
-ovn-nbctl ls-add outside
-
-# create one hypervisors with a vif port the internal network
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.11
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=inside1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovn-nbctl lsp-add inside inside1 \
-        -- lsp-set-addresses inside1 "f0:00:00:01:22:01 192.168.1.101"
-
-
-OVN_POPULATE_ARP
-
-ovn-nbctl create Logical_Router name=R1
-
-# Connect inside to R1
-ovn-nbctl lrp-add R1 inside 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add inside rp-inside -- set Logical_Switch_Port rp-inside \
-    type=router options:router-port=inside \
-    -- lsp-set-addresses rp-inside router
-
-# Connect outside to R1 as distributed router gateway port on gw1+gw2
-ovn-nbctl lrp-add R1 outside 00:00:02:01:02:04 192.168.0.101/24
-
-ovn-nbctl --id=@gc0 create Gateway_Chassis \
-                    name=outside_gw1 chassis_name=gw1 priority=20 -- \
-          --id=@gc1 create Gateway_Chassis \
-                    name=outside_gw2 chassis_name=gw2 priority=10 -- \
-          set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]'
-
-ovn-nbctl lsp-add outside rp-outside -- set Logical_Switch_Port rp-outside \
-    type=router options:router-port=outside \
-    -- lsp-set-addresses rp-outside router
-
-# Create localnet port in outside
-ovn-nbctl lsp-add outside ln-outside
-ovn-nbctl lsp-set-addresses ln-outside unknown
-ovn-nbctl lsp-set-type ln-outside localnet
-ovn-nbctl lsp-set-options ln-outside network_name=phys
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-ovn-nbctl --wait=hv --timeout=3 sync
-
-# currently when ovn-controller is restarted, the old entry is deleted
-# and a new one is created, which leaves the Gateway_Chassis with
-# an empty chassis for a while. NOTE: restarting ovn-controller in tests
-# doesn't have the same effect because "name" is conserved, and the
-# Chassis entry is not replaced.
-
-> gw1/ovn-controller.log
-
-gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2)
-ovn-sbctl destroy Chassis $gw2_chassis
-
-OVS_WAIT_UNTIL([test 0 = `grep -c "Releasing lport" gw1/ovn-controller.log`])
-
-OVN_CLEANUP([gw1],[gw2],[hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- IPv6 Neighbor Solicitation for unknown MAC])
-AT_KEYWORDS([ovn-nd_ns for unknown mac])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-ovn-nbctl ls-add sw0_ip6
-ovn-nbctl lsp-add sw0_ip6 sw0_ip6-port1
-ovn-nbctl lsp-set-addresses sw0_ip6-port1 \
-"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002"
-
-ovn-nbctl lsp-set-port-security sw0_ip6-port1 \
-"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002"
-
-ovn-nbctl lr-add lr0_ip6
-ovn-nbctl lrp-add lr0_ip6 lrp0_ip6 00:00:00:00:af:01 aef0:0:0:0:0:0:0:0/64
-ovn-nbctl lsp-add sw0_ip6 lrp0_ip6-attachment
-ovn-nbctl lsp-set-type lrp0_ip6-attachment router
-ovn-nbctl lsp-set-addresses lrp0_ip6-attachment router
-ovn-nbctl lsp-set-options lrp0_ip6-attachment router-port=lrp0_ip6
-ovn-nbctl set logical_router_port lrp0_ip6 ipv6_ra_configs:address_mode=slaac
-
-ovn-nbctl ls-add public
-ovn-nbctl lsp-add public ln-public
-ovn-nbctl lsp-set-addresses ln-public unknown
-ovn-nbctl lsp-set-type ln-public localnet
-ovn-nbctl lsp-set-options ln-public network_name=phys
-
-ovn-nbctl lrp-add lr0_ip6 ip6_public 00:00:02:01:02:04 \
-2001:db8:1:0:200:02ff:fe01:0204/64 \
--- set Logical_Router_port ip6_public options:redirect-chassis="hv1"
-
-#install static route
-ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \
-ip_prefix="\:\:/0" nexthop="2001\:db8\:1\:0\:200\:02ff\:fe01\:1305" \
--- add Logical_Router lr0_ip6 static_routes @lrt
-
-ovn-nbctl lsp-add public rp-ip6_public -- set Logical_Switch_Port \
-rp-ip6_public  type=router options:router-port=ip6_public \
--- lsp-set-addresses rp-ip6_public router
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw0_ip6-port1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0_ip6-port1` = xup])
-
-# There should be 2 Neighbor Advertisement flows for the router port
-# aef0:: ip address in logical switch pipeline with action nd_na_router.
-AT_CHECK([ovn-sbctl dump-flows sw0_ip6 | grep ls_in_arp_rsp | \
-grep "nd_na_router" | wc -l], [0], [2
-])
-
-# There should be 4 Neighbor Advertisement flows with action nd_na_router
-# in the router pipeline for the router lr0_ip6.
-AT_CHECK([ovn-sbctl dump-flows lr0_ip6 | grep nd_na_router | \
-wc -l], [0], [4
-])
-
-cr_uuid=`ovn-sbctl find port_binding logical_port=cr-ip6_public | grep _uuid | cut -f2 -d ":"`
-
-# There is only one chassis.
-chassis_uuid=`ovn-sbctl list chassis | grep _uuid | cut -f2 -d ":"`
-OVS_WAIT_UNTIL([test $chassis_uuid = `ovn-sbctl get port_binding $cr_uuid chassis`])
-
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-# Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC
-# addresses. ovn-controller should generate an IPv6 NS request for IPv6
-# packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage.
-# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-# This function sends ipv6 packet
-test_ipv6() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local dst_mcast_mac=$6 mcast_node_ip=$7 nd_target=$8
-
-    local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}
-    packet=${packet}8000000000000000
-
-    src_mac=000002010204
-    expected_packet=${dst_mcast_mac}${src_mac}86dd6000000000203aff${src_ip}
-    expected_packet=${expected_packet}${mcast_node_ip}8700XXXX00000000
-    expected_packet=${expected_packet}${nd_target}0101${src_mac}
-
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $packet
-    rm -f ipv6_ns.expected
-    echo $expected_packet >> ipv6_ns.expected
-}
-
-src_mac=506400000002
-dst_mac=00000000af01
-src_ip=aef0000000000000526400fffe000002
-dst_ip=20010db800010000020002fffe010205
-dst_mcast_mac=3333ff010205
-mcast_node_ip=ff0200000000000000000001ff010205
-nd_target=20010db800010000020002fffe010205
-# Send an IPv6 packet. Generated IPv6 Neighbor solicitation packet
-# should be received by the ports attached to br-phys.
-test_ipv6 1 $src_mac $dst_mac $src_ip $dst_ip $dst_mcast_mac \
-$mcast_node_ip $nd_target
-
-OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys_n1-tx.pcap | cut -d " " -f1)])
-OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys-tx.pcap | cut -d " " -f1)])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \
-trim_zeros > 1.packets
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \
-trim_zeros > 2.packets
-
-cat ipv6_ns.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum
-cat ipv6_ns.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
-
-# Now send a packet with destination ip other than
-# 2001:db8:1:0:200:02ff:fe01:0204/64 prefix.
-reset_pcap_file br-phys_n1 hv1/br-phys_n1
-reset_pcap_file br-phys hv1/br-phys
-
-src_mac=506400000002
-dst_mac=00000000af01
-src_ip=aef0000000000000526400fffe000002
-dst_ip=20020ab8000100000200020000020306
-# multicast mac of the nexthop IP - 2001:db8:1:0:200:02ff:fe01:1305
-dst_mcast_mac=3333ff011305
-mcast_node_ip=ff0200000000000000000001ff011305
-nd_target=20010db800010000020002fffe011305
-test_ipv6 1 $src_mac $dst_mac $src_ip $dst_ip $dst_mcast_mac \
-$mcast_node_ip $nd_target
-
-OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys_n1-tx.pcap | cut -d " " -f1)])
-OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys-tx.pcap | cut -d " " -f1)])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \
-trim_zeros > 1.packets
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \
-trim_zeros > 2.packets
-
-cat ipv6_ns.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum
-cat ipv6_ns.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- options:requested-chassis for logical port])
-ovn_start
-
-net_add n1
-
-ovn-nbctl ls-add ls0
-ovn-nbctl lsp-add ls0 lsp0
-
-# create two hypervisors, each with one vif port
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.11
-ovs-vsctl -- add-port br-int hv1-vif0 -- \
-set Interface hv1-vif0 ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.12
-ovs-vsctl -- add-port br-int hv2-vif0 -- \
-set Interface hv2-vif0 ofport-request=1
-
-# Allow only chassis hv1 to bind logical port lsp0.
-ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-ovn-nbctl --wait=hv --timeout=3 sync
-
-# Retrieve hv1 and hv2 chassis UUIDs from southbound database
-ovn-sbctl wait-until chassis hv1
-ovn-sbctl wait-until chassis hv2
-hv1_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv1)
-hv2_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv2)
-
-# (1) Chassis hv2 should not bind lsp0 when requested-chassis is hv1.
-echo "verifying that hv2 does not bind lsp0 when hv2 physical/logical mapping is added"
-as hv2
-ovs-vsctl set interface hv2-vif0 external-ids:iface-id=lsp0
-
-OVS_WAIT_UNTIL([test 1 = $(grep -c "Not claiming lport lsp0" hv2/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], [])
-
-# (2) Chassis hv2 should not add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
-
-# (3) Chassis hv1 should bind lsp0 when physical to logical mapping exists on hv1.
-echo "verifying that hv1 binds lsp0 when hv1 physical/logical mapping is added"
-as hv1
-ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0
-
-OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], [])
-
-# (4) Chassis hv1 should add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
-as hv1 ovs-ofctl dump-flows br-int
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
-
-# (5) Chassis hv1 should release lsp0 binding and chassis hv2 should bind lsp0 when
-# the requested chassis for lsp0 is changed from hv1 to hv2.
-echo "verifying that lsp0 binding moves when requested-chassis is changed"
-
-ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
-OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv2_uuid"])
-
-# (6) Chassis hv2 should add flows and hv1 should not.
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
-
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- options:requested-chassis with hostname])
-
-ovn_start
-
-ovn-nbctl ls-add ls0
-ovn-nbctl lsp-add ls0 lsp0
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.11
-ovs-vsctl -- add-port br-int hv1-vif0 -- set Interface hv1-vif0 ofport-request=1
-
-ovn-sbctl wait-until chassis hv1
-hv1_hostname=$(ovn-sbctl --bare --columns hostname find Chassis name=hv1)
-echo "hv1_hostname=${hv1_hostname}"
-ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=${hv1_hostname}
-as hv1 ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0
-
-hv1_uuid=$(ovn-sbctl --bare --columns _uuid find Chassis name=hv1)
-echo "hv1_uuid=${hv1_uuid}"
-OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], [])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
-
-ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=non-existant-chassis
-OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
-ovn-nbctl --wait=hv --timeout=3 sync
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], [])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
-
-OVN_CLEANUP([hv1])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- IPv6 periodic RA])
-ovn_start
-
-# This test sets up two hypervisors.
-# hv1 and hv2 run ovn-controllers, and
-# each has a VIF connected to the same
-# logical switch in OVN. The logical
-# switch is connected to a logical
-# router port that is configured to send
-# periodic router advertisements.
-#
-# The reason for having two ovn-controller
-# hypervisors is to ensure that the
-# periodic RAs being sent by each ovn-controller
-# are kept to their local hypervisors. If the
-# packets are not kept local, then each port
-# will receive too many RAs.
-
-net_add n1
-sim_add hv1
-sim_add hv2
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-
-ovn-nbctl lr-add ro
-ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64
-
-ovn-nbctl ls-add sw
-ovn-nbctl lsp-add sw sw-ro
-ovn-nbctl lsp-set-type sw-ro router
-ovn-nbctl lsp-set-options sw-ro router-port=ro-sw
-ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01
-ovn-nbctl lsp-add sw sw-p1
-ovn-nbctl lsp-set-addresses sw-p1 "00:00:00:00:00:02 aef0::200:ff:fe00:2"
-ovn-nbctl lsp-add sw sw-p2
-ovn-nbctl lsp-set-addresses sw-p2 "00:00:00:00:00:03 aef0::200:ff:fe00:3"
-
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:send_periodic=true
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=slaac
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=4
-ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=3
-
-for i in 1 2 ; do
-    as hv$i
-    ovs-vsctl -- add-port br-int hv$i-vif1 -- \
-        set interface hv$i-vif1 external-ids:iface-id=sw-p$i \
-        options:tx_pcap=hv$i/vif1-tx.pcap \
-        options:rxq_pcap=hv$i/vif1-rx.pcap \
-        ofport-request=1
-done
-
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw-p1` = xup])
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw-p2` = xup])
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-
-}
-
-construct_expected_ra() {
-    local src_mac=000000000001
-    local dst_mac=333300000001
-    local src_addr=fe80000000000000020000fffe000001
-    local dst_addr=ff020000000000000000000000000001
-
-    local mtu=$1
-    local ra_mo=$2
-    local ra_prefix_la=$3
-
-    local slla=0101${src_mac}
-    local mtu_opt=""
-    if test $mtu != 0; then
-        mtu_opt=05010000${mtu}
-    fi
-    shift 3
-
-    local prefix=""
-    while [[ $# -gt 0 ]] ; do
-        local size=$1
-        local net=$2
-        prefix=${prefix}0304${size}${ra_prefix_la}ffffffffffffffff00000000${net}
-        shift 2
-    done
-
-    local ra=ff${ra_mo}ffff0000000000000000${slla}${mtu_opt}${prefix}
-    local icmp=8600XXXX${ra}
-
-    local ip_len=$(expr ${#icmp} / 2)
-    ip_len=$(echo "$ip_len" | awk '{printf "%0.4x\n", $0}')
-
-    local ip=60000000${ip_len}3aff${src_addr}${dst_addr}${icmp}
-    local eth=${dst_mac}${src_mac}86dd${ip}
-    local packet=${eth}
-    echo $packet >> expected
-}
-
-ra_test() {
-    construct_expected_ra $@
-
-    for i in hv1 hv2 ; do
-        OVS_WAIT_WHILE([test 24 = $(wc -c $i/vif1-tx.pcap | cut -d " " -f1)])
-
-        $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $i/vif1-tx.pcap > packets
-
-        cat expected | cut -c -112 > expout
-        AT_CHECK([cat packets | cut -c -112], [0], [expout])
-
-        # Skip ICMPv6 checksum.
-        cat expected | cut -c 117- > expout
-        AT_CHECK([cat packets | cut -c 117-], [0], [expout])
-
-        rm -f packets
-        as $i reset_pcap_file $i-vif1 $i/vif1
-    done
-
-    rm -f expected
-}
-
-# Baseline test with no MTU
-ra_test 0 00 c0 40 aef00000000000000000000000000000
-
-# Now make sure an MTU option makes it
-ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:mtu=1500
-ra_test 000005dc 00 c0 40 aef00000000000000000000000000000
-
-# Now test for multiple network prefixes
-ovn-nbctl --wait=hv set Logical_Router_port ro-sw networks='aef0\:\:1/64 fd0f\:\:1/48'
-ra_test 000005dc 00 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000
-
-# Test a different address mode now
-ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateful
-ra_test 000005dc 80 80 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000
-
-# And the other address mode
-ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateless
-ra_test 000005dc 40 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000
-
-OVN_CLEANUP([hv1],[hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ACL reject rule test])
-AT_KEYWORDS([acl-reject])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with
-# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified.
-# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp destination
-# unreachable frame generated from ACL rule hit
-#
-# INPORT is a lport number, e.g. 11 for vif11.
-# HV is a hypervisor number
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC and IPV4_DST are each 8 hex digits.
-# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits
-test_ip_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
-    local exp_ip_chksum=$8 exp_icmp_chksum=$9
-    shift 9
-
-    local ip_ttl=ff
-    local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}
-
-    local reply_icmp_ttl=ff
-    local icmp_type_code_response=0301
-    local icmp_data=00000000
-    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data}
-    local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_ipv6_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST EXP_ICMP_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 packet with
-# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_DST as specified.
-# EXP_ICMP_CHKSUM is the icmp6 checksums of the icmp6 destination unreachable frame generated from ACL rule hit
-test_ipv6_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 exp_icmp_chksum=$7
-    shift 7
-
-    local ip6_hdr=6000000000083aff${ipv6_src}${ipv6_dst}
-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}0000000000000000
-
-    local reply=${eth_src}${eth_dst}86dd6000000000303aff${ipv6_dst}${ipv6_src}0101${exp_icmp_chksum}00000000${ip6_hdr}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_tcp_syn_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_IP_CHKSUM EXP_TCP_RST_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an TCP syn segment with
-# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, TCP_SPORT, TCP_DPORT, TCP_CHKSUM  as specified.
-# EXP_IP_CHKSUM and EXP_TCP_RST_CHKSUM are the ip and tcp checksums of the tcp reset segment generated from ACL rule hit
-#
-# INPORT is an lport number, e.g. 11 for vif11.
-# HV is an hypervisor number
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC and IPV4_DST are each 8 hex digits.
-# TCP_SPORT and TCP_DPORT are 4 hex digits.
-# IP_CHKSUM, TCP_CHKSUM, EXP_IP_CHSUM and EXP_TCP_RST_CHKSUM are each 4 hex digits
-test_tcp_syn_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
-    local tcp_sport=$8 tcp_dport=$9 tcp_chksum=${10}
-    local exp_ip_chksum=${11} exp_tcp_rst_chksum=${12}
-    shift 12
-
-    local ip_ttl=ff
-    local packet=${eth_dst}${eth_src}08004500002800004000${ip_ttl}06${ip_chksum}${ipv4_src}${ipv4_dst}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000
-
-    local tcp_rst_ttl=ff
-    local reply=${eth_src}${eth_dst}08004500002800004000${tcp_rst_ttl}06${exp_ip_chksum}${ipv4_dst}${ipv4_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# Create hypervisors hv[123].
-# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
-# Add all of the vifs to a single logical switch sw0.
-
-net_add n1
-ovn-nbctl ls-add sw0
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2 3; do
-        ovn-nbctl lsp-add sw0 sw0-p$i$j -- \
-                lsp-set-addresses sw0-p$i$j "00:00:00:00:00:$i$j 192.168.1.$i$j"
-
-        ovs-vsctl -- add-port br-int vif$i$j -- \
-                set interface vif$i$j \
-                external-ids:iface-id=sw0-p$i$j \
-                options:tx_pcap=hv$i/vif$i$j-tx.pcap \
-                options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
-                ofport-request=$i$j
-    done
-done
-
-OVN_POPULATE_ARP
-# allow some time for ovn-northd and ovn-controller to catch up.
-sleep 1
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-for i in 1 2 3; do
-    : > vif${i}1.expected
-done
-
-ovn-nbctl --log acl-add sw0 to-lport 1000 "outport == \"sw0-p12\"" reject
-ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject
-ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-ovn-nbctl --timeout=3 --wait=hv sync
-
-test_ip_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 7d8d fcfe
-test_ip_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 7d8d fcfe
-test_ip_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 7d82 fcfe
-
-test_ipv6_packet 11 1 000000000011 000000000021 fe80000000000000020001fffe000001 fe80000000000000020001fffe000002 6183
-
-test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 8b40 3039 0000 7d8d 4486
-test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 7d8d 4486
-test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 7d82 4486
-
-for i in 1 2 3; do
-    OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected])
-done
-
-OVN_CLEANUP([hv1], [hv2], [hv3])
-AT_CLEANUP
-
-AT_SETUP([ovn -- Port Groups])
-AT_KEYWORDS([ovnpg])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-#
-# Three logical switches ls1, ls2, ls3.
-# One logical router lr0 connected to ls[123],
-# with nine subnets, three per logical switch:
-#
-#    lrp11 on ls1 for subnet 192.168.11.0/24
-#    lrp12 on ls1 for subnet 192.168.12.0/24
-#    lrp13 on ls1 for subnet 192.168.13.0/24
-#    ...
-#    lrp33 on ls3 for subnet 192.168.33.0/24
-#
-# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two
-# digits are the subnet and the last digit distinguishes the VIF.
-#
-# This test will create two port groups and uses them in ACL.
-
-get_lsp_uuid () {
-    ovn-nbctl lsp-list ls${1%??} | grep lp$1 | awk '{ print $1 }'
-}
-
-pg1_ports=
-pg2_ports=
-for i in 1 2 3; do
-    ovn-nbctl ls-add ls$i
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            ovn-nbctl \
-                -- lsp-add ls$i lp$i$j$k \
-                -- lsp-set-addresses lp$i$j$k \
-                   "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k"
-            # logical ports lp[12]?1 belongs to port group pg1
-            if test $i != 3 && test $k == 1; then
-                pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`"
-            fi
-            # logical ports lp[23]?2 belongs to port group pg2
-            if test $i != 1 && test $k == 2; then
-                pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`"
-            fi
-        done
-    done
-done
-
-ovn-nbctl lr-add lr0
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24
-        ovn-nbctl \
-            -- lsp-add ls$i lrp$i$j-attachment \
-            -- set Logical_Switch_Port lrp$i$j-attachment type=router \
-                             options:router-port=lrp$i$j \
-                             addresses='"00:00:00:00:ff:'$i$j'"'
-    done
-done
-
-ovn-nbctl create Port_Group name=pg1 ports="$pg1_ports"
-ovn-nbctl create Port_Group name=pg2 ports="$pg2_ports"
-
-# create ACLs on all lswitches to drop traffic from pg2 to pg1
-ovn-nbctl acl-add ls1 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
-ovn-nbctl acl-add ls2 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
-ovn-nbctl acl-add ls3 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
-
-# Physical network:
-#
-# Three hypervisors hv[123].
-# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3.
-# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3.
-# lp?3[123] all on hv3.
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    case $1 in dnl (
-        ?11) echo 1 ;; dnl (
-        ?12 | ?21 | ?22) echo 2 ;; dnl (
-        ?13 | ?23 | ?3?) echo 3 ;;
-    esac
-}
-
-# Given the name of a logical port, prints the name of its logical router
-# port, e.g. "vif_to_lrp 123" yields 12.
-vif_to_lrp() {
-    echo ${1%?}
-}
-
-# Given the name of a logical port, prints the name of its logical
-# switch, e.g. "vif_to_ls 123" yields 1.
-vif_to_ls() {
-    echo ${1%??}
-}
-
-net_add n1
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-done
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            hv=`vif_to_hv $i$j$k`
-                as hv$hv ovs-vsctl \
-                -- add-port br-int vif$i$j$k \
-                -- set Interface vif$i$j$k \
-                    external-ids:iface-id=lp$i$j$k \
-                    options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
-                    options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
-                    ofport-request=$i$j$k
-        done
-    done
-done
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-#
-# This shell function causes a packet to be received on INPORT.  The packet's
-# content has Ethernet destination DST and source SRC (each exactly 12 hex
-# digits) and Ethernet type ETHTYPE (4 hex digits).  The OUTPORTs (zero or
-# more) list the VIFs on which the packet should be received.  INPORT and the
-# OUTPORTs are specified as logical switch port numbers, e.g. 123 for vif123.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            : > $i$j$k.expected
-        done
-    done
-done
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    in_ls=`vif_to_ls $inport`
-    in_lrp=`vif_to_lrp $inport`
-    for outport; do
-        out_ls=`vif_to_ls $outport`
-        if test $in_ls = $out_ls; then
-            # Ports on the same logical switch receive exactly the same packet.
-            echo $packet
-        else
-            # Routing decrements TTL and updates source and dest MAC
-            # (and checksum).
-            out_lrp=`vif_to_lrp $outport`
-            echo f00000000${outport}00000000ff${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
-        fi >> $outport.expected
-    done
-}
-
-as hv1 ovs-vsctl --columns=name,ofport list interface
-as hv1 ovn-sbctl list port_binding
-as hv1 ovn-sbctl list datapath_binding
-as hv1 ovn-sbctl list port_group
-as hv1 ovn-sbctl list address_set
-as hv1 ovn-sbctl dump-flows
-as hv1 ovs-ofctl dump-flows br-int
-
-# Send IP packets between all pairs of source and destination ports,
-# packets matches ACL (pg2 to pg1) should be dropped
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-for is in 1 2 3; do
-  for js in 1 2 3; do
-    for ks in 1 2 3; do
-      bcast=
-      s=$is$js$ks
-      smac=f00000000$s
-      sip=`ip_to_hex 192 168 $is$js $ks`
-      for id in 1 2 3; do
-          for jd in 1 2 3; do
-              for kd in 1 2 3; do
-                d=$id$jd$kd
-                dip=`ip_to_hex 192 168 $id$jd $kd`
-                if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi
-                if test $d != $s; then unicast=$d; else unicast=; fi
-
-                # packets matches ACL should be dropped
-                if test $id != 3 && test $kd == 1; then
-                    if test $is != 1 && test $ks == 2; then
-                        unicast=
-                    fi
-                fi
-                test_ip $s $smac $dmac $sip $dip $unicast #1
-              done
-          done
-        done
-      done
-  done
-done
-
-# Allow some time for packet forwarding.
-# XXX This can be improved.
-sleep 1
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap],
-                              [$i$j$k.expected])
-        done
-    done
-done
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1], [hv2], [hv3])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ACLs on Port Groups])
-AT_KEYWORDS([ovnpg_acl])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-#
-# Three logical switches ls1, ls2, ls3.
-# One logical router lr0 connected to ls[123],
-# with nine subnets, three per logical switch:
-#
-#    lrp11 on ls1 for subnet 192.168.11.0/24
-#    lrp12 on ls1 for subnet 192.168.12.0/24
-#    lrp13 on ls1 for subnet 192.168.13.0/24
-#    ...
-#    lrp33 on ls3 for subnet 192.168.33.0/24
-#
-# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two
-# digits are the subnet and the last digit distinguishes the VIF.
-#
-# This test will create two port groups and ACLs will be applied on them.
-
-get_lsp_uuid () {
-    ovn-nbctl lsp-list ls${1%??} | grep lp$1 | awk '{ print $1 }'
-}
-
-pg1_ports=
-pg2_ports=
-for i in 1 2 3; do
-    ovn-nbctl ls-add ls$i
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            ovn-nbctl \
-                -- lsp-add ls$i lp$i$j$k \
-                -- lsp-set-addresses lp$i$j$k \
-                   "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k"
-            # logical ports lp[12]?1 belongs to port group pg1
-            if test $i != 3 && test $k == 1; then
-                pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`"
-            fi
-            # logical ports lp[23]?2 belongs to port group pg2
-            if test $i != 1 && test $k == 2; then
-                pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`"
-            fi
-        done
-    done
-done
-
-ovn-nbctl lr-add lr0
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24
-        ovn-nbctl \
-            -- lsp-add ls$i lrp$i$j-attachment \
-            -- set Logical_Switch_Port lrp$i$j-attachment type=router \
-                             options:router-port=lrp$i$j \
-                             addresses='"00:00:00:00:ff:'$i$j'"'
-    done
-done
-
-ovn-nbctl create Port_Group name=pg1 ports="$pg1_ports"
-ovn-nbctl create Port_Group name=pg2 ports="$pg2_ports"
-
-# create ACLs on pg1 to drop traffic from pg2 to pg1
-ovn-nbctl acl-add pg1 to-lport 1001 'outport == @pg1' drop
-ovn-nbctl --type=port-group acl-add pg1 to-lport 1002 \
-        'outport == @pg1 && ip4.src == $pg2_ip4' allow-related
-
-# Physical network:
-#
-# Three hypervisors hv[123].
-# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3.
-# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3.
-# lp?3[123] all on hv3.
-
-# Given the name of a logical port, prints the name of the hypervisor
-# on which it is located.
-vif_to_hv() {
-    case $1 in dnl (
-        ?11) echo 1 ;; dnl (
-        ?12 | ?21 | ?22) echo 2 ;; dnl (
-        ?13 | ?23 | ?3?) echo 3 ;;
-    esac
-}
-
-# Given the name of a logical port, prints the name of its logical router
-# port, e.g. "vif_to_lrp 123" yields 12.
-vif_to_lrp() {
-    echo ${1%?}
-}
-
-# Given the name of a logical port, prints the name of its logical
-# switch, e.g. "vif_to_ls 123" yields 1.
-vif_to_ls() {
-    echo ${1%??}
-}
-
-net_add n1
-for i in 1 2 3; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-done
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            hv=`vif_to_hv $i$j$k`
-                as hv$hv ovs-vsctl \
-                -- add-port br-int vif$i$j$k \
-                -- set Interface vif$i$j$k \
-                    external-ids:iface-id=lp$i$j$k \
-                    options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
-                    options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
-                    ofport-request=$i$j$k
-        done
-    done
-done
-
-# Pre-populate the hypervisors' ARP tables so that we don't lose any
-# packets for ARP resolution (native tunneling doesn't queue packets
-# for ARP resolution).
-OVN_POPULATE_ARP
-
-# Allow some time for ovn-northd and ovn-controller to catch up.
-# XXX This should be more systematic.
-sleep 1
-
-lsp_to_mac() {
-    echo f0:00:00:00:0${1:0:1}:${1:1:2}
-}
-
-lrp_to_mac() {
-    echo 00:00:00:00:ff:$1
-}
-
-# test_icmp INPORT SRC_MAC DST_MAC SRC_IP DST_IP ICMP_TYPE OUTPORT...
-#
-# This shell function causes a ICMP packet to be received on INPORT.
-# The OUTPORTs (zero or more) list the VIFs on which the packet should
-# be received.  INPORT and the OUTPORTs are specified as logical switch
-# port numbers, e.g. 123 for vif123.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            : > $i$j$k.expected
-        done
-    done
-done
-
-test_icmp() {
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6
-    local packet="inport==\"lp$inport\" && eth.src==$src_mac &&
-                  eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip
-                  && ip4.dst==$dst_ip && icmp4.type==$icmp_type &&
-                  icmp4.code==0"
-    shift; shift; shift; shift; shift; shift
-    hv=hv`vif_to_hv $inport`
-    as $hv ovs-appctl -t ovn-controller inject-pkt "$packet"
-    in_ls=`vif_to_ls $inport`
-    in_lrp=`vif_to_lrp $inport`
-    for outport; do
-        out_ls=`vif_to_ls $outport`
-        if test $in_ls = $out_ls; then
-            # Ports on the same logical switch receive exactly the same packet.
-            echo $packet | ovstest test-ovn expr-to-packets
-        else
-            # Routing decrements TTL and updates source and dest MAC
-            # (and checksum).
-            out_lrp=`vif_to_lrp $outport`
-            exp_smac=`lrp_to_mac $out_lrp`
-            exp_dmac=`lsp_to_mac $outport`
-            exp_packet="eth.src==$exp_smac && eth.dst==$exp_dmac &&
-                ip.ttl==63 && ip4.src==$src_ip && ip4.dst==$dst_ip &&
-                icmp4.type==$icmp_type && icmp4.code==0"
-            echo $exp_packet | ovstest test-ovn expr-to-packets
-
-        fi >> $outport.expected
-    done
-}
-
-as hv1 ovs-vsctl --columns=name,ofport list interface
-as hv1 ovn-sbctl list port_binding
-as hv1 ovn-sbctl list datapath_binding
-as hv1 ovn-sbctl list port_group
-as hv1 ovn-sbctl list address_set
-as hv1 ovn-sbctl dump-flows
-as hv1 ovs-ofctl dump-flows br-int
-
-# Send IP packets between all pairs of source and destination ports,
-# packets matches ACL1 but not ACL2 should be dropped
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-for is in 1 2 3; do
-  for js in 1 2 3; do
-    for ks in 1 2 3; do
-      bcast=
-      s=$is$js$ks
-      slsp_mac=`lsp_to_mac $s`
-      slrp_mac=`lrp_to_mac $is$js`
-      sip=192.168.$is$js.$ks
-      for id in 1 2 3; do
-          for jd in 1 2 3; do
-              for kd in 1 2 3; do
-                d=$id$jd$kd
-                dlsp_mac=`lsp_to_mac $d`
-                dlrp_mac=`lrp_to_mac $id$jd`
-                dip=192.168.$id$jd.$kd
-                if test $is = $id; then dmac=$dlsp_mac; else dmac=$slrp_mac; fi
-                if test $d != $s; then unicast=$d; else unicast=; fi
-
-                # packets matches ACL1 but not ACL2 should be dropped
-                if test $id != 3 && test $kd == 1; then
-                    if test $is == 1 || test $ks != 2; then
-                        unicast=
-                    fi
-                fi
-                # icmp request (type = 8)
-                test_icmp $s $slsp_mac $dmac $sip $dip 8 $unicast
-
-                # if packets are not dropped, test the return traffic (icmp echo)
-                # to make sure stateful works, too.
-                if test x$unicast != x; then
-                    if test $is = $id; then dmac=$slsp_mac; else dmac=$dlrp_mac; fi
-                    # icmp echo (type = 0)
-                    test_icmp $unicast $dlsp_mac $dmac $dip $sip 0 $s
-                fi
-              done
-          done
-        done
-      done
-  done
-done
-
-# Allow some time for packet forwarding.
-# XXX This can be improved.
-sleep 1
-
-# Now check the packets actually received against the ones expected.
-for i in 1 2 3; do
-    for j in 1 2 3; do
-        for k in 1 2 3; do
-            OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap],
-                              [$i$j$k.expected])
-        done
-    done
-done
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1], [hv2], [hv3])
-AT_CLEANUP
-
-AT_SETUP([ovn -- Address Set generation from Port Groups (static addressing)])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-ovn-nbctl lsp-add ls1 lp1
-ovn-nbctl lsp-add ls1 lp2
-ovn-nbctl lsp-add ls1 lp3
-
-ovn-nbctl lsp-set-addresses lp1 "02:00:00:00:00:01 10.0.0.1 2001:db8::1"
-ovn-nbctl lsp-set-addresses lp2 "02:00:00:00:00:02 10.0.0.2 2001:db8::2"
-ovn-nbctl lsp-set-addresses lp3 "02:00:00:00:00:03 10.0.0.3 2001:db8::3"
-
-ovn-nbctl create Port_Group name=pg1
-ovn-nbctl create Port_Group name=pg2
-
-ovn-nbctl --id=@p get Logical_Switch_Port lp1 -- add Port_Group pg1 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg1 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg2 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p
-
-ovn-nbctl --wait=sb sync
-
-dnl Check if port group address sets were populated with ports' addresses
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.0.0.1", "10.0.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses],
-         [0], [[["10.0.0.2", "10.0.0.3"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8::1", "2001:db8::2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses],
-         [0], [[["2001:db8::2", "2001:db8::3"]]
-])
-
-ovn-nbctl --wait=sb lsp-set-addresses lp1 \
-    "02:00:00:00:00:01 10.0.0.11 2001:db8::11"
-
-dnl Check if updated address got propagated to the port group address sets
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.0.0.11", "10.0.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8::11", "2001:db8::2"]]
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- Address Set generation from Port Groups (dynamic addressing)])
-ovn_start
-
-ovn-nbctl ls-add ls1
-ovn-nbctl ls-add ls2
-ovn-nbctl ls-add ls3
-
-ovn-nbctl set Logical_Switch ls1 \
-    other_config:subnet=10.1.0.0/24 other_config:ipv6_prefix="2001:db8:1::"
-ovn-nbctl set Logical_Switch ls2 \
-    other_config:subnet=10.2.0.0/24 other_config:ipv6_prefix="2001:db8:2::"
-ovn-nbctl set Logical_Switch ls3 \
-    other_config:subnet=10.3.0.0/24 other_config:ipv6_prefix="2001:db8:3::"
-
-ovn-nbctl lsp-add ls1 lp1
-ovn-nbctl lsp-add ls2 lp2
-ovn-nbctl lsp-add ls3 lp3
-
-ovn-nbctl lsp-set-addresses lp1 "02:00:00:00:00:01 dynamic"
-ovn-nbctl lsp-set-addresses lp2 "02:00:00:00:00:02 dynamic"
-ovn-nbctl lsp-set-addresses lp3 "02:00:00:00:00:03 dynamic"
-
-ovn-nbctl create Port_Group name=pg1
-ovn-nbctl create Port_Group name=pg2
-
-ovn-nbctl --id=@p get Logical_Switch_Port lp1 -- add Port_Group pg1 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg1 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg2 ports @p
-ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p
-
-ovn-nbctl --wait=sb sync
-
-dnl Check if port group address sets were populated with ports' addresses
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.1.0.2", "10.2.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses],
-         [0], [[["10.2.0.2", "10.3.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8:1::ff:fe00:1", "2001:db8:2::ff:fe00:2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses],
-         [0], [[["2001:db8:2::ff:fe00:2", "2001:db8:3::ff:fe00:3"]]
-])
-
-ovn-nbctl --wait=sb set Logical_Switch ls1 \
-    other_config:subnet=10.11.0.0/24 other_config:ipv6_prefix="2001:db8:11::"
-
-dnl Check if updated address got propagated to the port group address sets
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.11.0.2", "10.2.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8:11::ff:fe00:1", "2001:db8:2::ff:fe00:2"]]
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ACL conjunction])
-ovn_start
-
-ovn-nbctl ls-add ls1
-
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
-
-ovn-nbctl lsp-add ls1 ls1-lp2 \
--- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6"
-
-ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6"
-
-net_add n1
-sim_add hv1
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=2
-
-ovn-nbctl create Address_Set name=set1 \
-addresses=\"10.0.0.4\",\"10.0.0.5\",\"10.0.0.6\"
-ovn-nbctl create Address_Set name=set2 \
-addresses=\"10.0.0.7\",\"10.0.0.8\",\"10.0.0.9\"
-ovn-nbctl acl-add ls1 to-lport 1002 \
-'ip4 && ip4.src == $set1 && ip4.dst == $set1' allow
-ovn-nbctl acl-add ls1 to-lport 1001 \
-'ip4 && ip4.src == $set1 && ip4.dst == $set2' drop
-
-# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
-#
-# This shell function causes an ip packet to be received on INPORT.
-# The packet's content has Ethernet destination DST and source SRC
-# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits).
-# The OUTPORTs (zero or more) list the VIFs on which the packet should
-# be received.  INPORT and the OUTPORTs are specified as logical switch
-# port numbers, e.g. 11 for vif11.
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\
-${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-    for outport; do
-        echo $packet >> $outport.expected
-    done
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-
-sip=`ip_to_hex 10 0 0 4`
-dip=`ip_to_hex 10 0 0 6`
-
-test_ip 1 f00000000001 f00000000002 $sip $dip 2
-
-cat 2.expected > expout
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-AT_CHECK([cat 2.packets], [0], [expout])
-
-# There should be total of 12 flows present with conjunction action and 2 flows
-# with conj match. Eg.
-# table=44, priority=2002,conj_id=2,metadata=0x1 actions=resubmit(,45)
-# table=44, priority=2001,conj_id=3,metadata=0x1 actions=drop
-# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.6 actions=conjunction(2,2/2)
-# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,2/2)
-# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.5 actions=conjunction(2,2/2)
-# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.7 actions=conjunction(3,2/2)
-# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.9 actions=conjunction(3,2/2)
-# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(3,2/2)
-# priority=2002,ip,metadata=0x1,nw_src=10.0.0.6 actions=conjunction(2,1/2)
-# priority=2002,ip,metadata=0x1,nw_src=10.0.0.4 actions=conjunction(2,1/2)
-# priority=2002,ip,metadata=0x1,nw_src=10.0.0.5 actions=conjunction(2,1/2)
-# priority=2001,ip,metadata=0x1,nw_src=10.0.0.6 actions=conjunction(3,1/2)
-# priority=2001,ip,metadata=0x1,nw_src=10.0.0.4 actions=conjunction(3,1/2)
-# priority=2001,ip,metadata=0x1,nw_src=10.0.0.5 actions=conjunction(3,1/2)
-
-OVS_WAIT_UNTIL([test 12 = `as hv1 ovs-ofctl dump-flows br-int | \
-grep conjunction | wc -l`])
-OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
-grep conj_id | wc -l`])
-
-as hv1 ovs-ofctl dump-flows br-int
-
-# Set the ip address for ls1-lp2 from set2 so that the drop ACL flow is hit.
-ovn-nbctl lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.7 20.0.0.4"
-ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.7 20.0.0.4"
-
-reset_pcap_file hv1-vif2 hv1/vif2
-
-rm -f 2.packets
-
-sip=`ip_to_hex 10 0 0 4`
-dip=`ip_to_hex 10 0 0 7`
-
-test_ip 1 f00000000001 f00000000002 $sip $dip
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-AT_CHECK([cat 2.packets], [0], [])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- TTL exceeded])
-AT_KEYWORDS([ttl-exceeded])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IPV4_ROUTER IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with
-# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified and TTL set to 1.
-# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp time exceeded frame
-# generated by OVN logical router
-#
-# INPORT is a lport number, e.g. 11 for vif11.
-# HV is a hypervisor number
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC, IPV4_DST and IPV4_ROUTER are each 8 hex digits.
-# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits
-test_ip_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_router=$7 ip_chksum=$8
-    local exp_ip_chksum=$9 exp_icmp_chksum=${10}
-    shift 10
-
-    local ip_ttl=01
-    local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}
-
-    local reply_icmp_ttl=fe
-    local icmp_type_code_response=0b00
-    local icmp_data=00000000
-    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data}
-    local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_ROUTER EXP_ICMP_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6
-# packet with ETH_SRC, ETH_DST, IPV6_SRC and IPV6_DST as specified.
-# IPV6_ROUTER and EXP_ICMP_CHKSUM are the source IP and checksum of the icmpv6 ttl exceeded
-# packet sent by OVN logical router
-test_ip6_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8
-    shift 8
-
-    local ip6_hdr=6000000000151101${ipv6_src}${ipv6_dst}
-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}dbb8303900155bac6b646f65206676676e6d66720a
-
-    local reply=${eth_src}${eth_dst}86dd6000000000303afe${ipv6_router}${ipv6_src}0300${exp_icmp_chksum}00000000${ip6_hdr}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-for i in 1 2; do
-    net_add n$i
-    ovn-nbctl ls-add sw$i
-
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n$i br-phys 192.168.$i.1
-
-    ovn-nbctl lsp-add sw$i sw$i-p${i}0 -- \
-        lsp-set-addresses sw$i-p${i}0 "00:00:00:00:00:0$i 192.168.$i.1 2001:db8:$i::11"
-
-    ovs-vsctl -- add-port br-int vif$i -- \
-        set interface vif$i \
-        external-ids:iface-id=sw$i-p${i}0 \
-            options:tx_pcap=hv$i/vif$i-tx.pcap \
-            options:rxq_pcap=hv$i/vif$i-rx.pcap \
-            ofport-request=$i
-done
-
-ovn-nbctl lr-add lr0
-for i in 1 2; do
-    ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.254/24 2001:db8:$i::1/64
-    ovn-nbctl -- lsp-add sw$i lrp$i-attachment \
-              -- set Logical_Switch_Port lrp$i-attachment type=router \
-                options:router-port=lrp$i addresses='"00:00:00:00:ff:0'$i' 192.168.'$i'.254 2001:db8:'$i'::1"'
-done
-
-OVN_POPULATE_ARP
-# allow some time for ovn-northd and ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-
-test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 7dae f4ff
-test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000200000000000000000011 20010db8000100000000000000000001 d461
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
-
-OVN_CLEANUP([hv1], [hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- router port unreachable])
-AT_KEYWORDS([router-port-unreachable])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_ROUTER L4_PROTCOL IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM EXP_ICMP_CODE
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with
-# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_ROUTER, L4_PROTCOL, IP_CHKSUM as specified.
-# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp frame generated by OVN logical router
-# EXP_ICMP_CODE are code and type of the icmp frame generated by OVN logical router
-#
-# INPORT is a lport number, e.g. 11 for vif11.
-# HV is a hypervisor number
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC and IPV4_ROUTER are each 8 hex digits.
-# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits
-test_ip_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ip_router=$6 l4_proto=$7 ip_chksum=$8
-    local exp_ip_chksum=$9 exp_icmp_chksum=${10} exp_icmp_code=${11}
-    shift 11
-
-    local ip_ttl=ff
-    local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}${l4_proto}${ip_chksum}${ipv4_src}${ip_router}
-
-    local reply_icmp_ttl=fe
-    local icmp_data=00000000
-    local reply_icmp_payload=${exp_icmp_code}${exp_icmp_chksum}${icmp_data}
-    local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_tcp_syn_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_ROUTER IP_CHKSUM TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_IP_CHKSUM EXP_TCP_RST_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an TCP syn segment with
-# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_ROUTER, IP_CHKSUM, TCP_SPORT, TCP_DPORT, TCP_CHKSUM  as specified.
-# EXP_IP_CHKSUM and EXP_TCP_RST_CHKSUM are the ip and tcp checksums of the tcp reset segment generated by OVN logical router
-#
-# INPORT is an lport number, e.g. 11 for vif11.
-# HV is an hypervisor number
-# ETH_SRC and ETH_DST are each 12 hex digits.
-# IPV4_SRC and IPV4_ROUTER are each 8 hex digits.
-# TCP_SPORT and TCP_DPORT are 4 hex digits.
-# IP_CHKSUM, TCP_CHKSUM, EXP_IP_CHSUM and EXP_TCP_RST_CHKSUM are each 4 hex digits
-test_tcp_syn_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ip_router=$6 ip_chksum=$7
-    local tcp_sport=$8 tcp_dport=$9 tcp_chksum=${10}
-    local exp_ip_chksum=${11} exp_tcp_rst_chksum=${12}
-    shift 12
-
-    local ip_ttl=ff
-    local packet=${eth_dst}${eth_src}08004500002800004000${ip_ttl}06${ip_chksum}${ipv4_src}${ip_router}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000
-
-    local tcp_rst_ttl=fe
-    local reply=${eth_src}${eth_dst}08004500002800004000${tcp_rst_ttl}06${exp_ip_chksum}${ip_router}${ipv4_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_TCP_RST_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn segment with
-# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, TCP_SPORT, TCP_DPORT and TCP_CHKSUM as specified.
-# EXP_TCP_RST_CHKSUM is the tcp checksums of the tcp reset segment generated by OVN logical router
-test_tcp6_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_router=$6
-    local tcp_sport=$7 tcp_dport=$8 tcp_chksum=$9
-    local exp_tcp_rst_chksum=${10}
-    shift 10
-
-    local ip6_hdr=60000000001406ff${ipv6_src}${ipv6_router}
-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000
-
-    local reply=${eth_src}${eth_dst}86dd60000000001406fe${ipv6_router}${ipv6_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM
-#
-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6
-# packet with ETH_SRC, ETH_DST, IPV6_SRC, IPV6_DST, IPV6_PROTO, IPV6_LEN and DATA as specified.
-# EXP_ICMP_CODE and EXP_ICMP_CHKSUM are the code and checksum of the icmp6 packet sent by OVN logical router
-test_ip6_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_proto=$7 ipv6_len=$8 data=$9
-    local exp_icmp_code=${10} exp_icmp_chksum=${11}
-    shift 11
-
-    local ip6_hdr=60000000${ipv6_len}${ipv6_proto}ff${ipv6_src}${ipv6_dst}
-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}${data}
-
-    local reply=${eth_src}${eth_dst}86dd6000000000303afe${ipv6_dst}${ipv6_src}${exp_icmp_code}${exp_icmp_chksum}00000000${ip6_hdr}
-    echo $reply >> vif$inport.expected
-
-    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-for i in 1 2; do
-    net_add n$i
-    ovn-nbctl ls-add sw$i
-
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n$i br-phys 192.168.$i.1
-
-    ovn-nbctl lsp-add sw$i sw$i-p${i}0 -- \
-        lsp-set-addresses sw$i-p${i}0 "00:00:00:00:00:0$i 192.168.$i.1 2001:db8:$i::11"
-
-    ovs-vsctl -- add-port br-int vif$i -- \
-        set interface vif$i \
-        external-ids:iface-id=sw$i-p${i}0 \
-            options:tx_pcap=hv$i/vif$i-tx.pcap \
-            options:rxq_pcap=hv$i/vif$i-rx.pcap \
-            ofport-request=$i
-done
-
-ovn-nbctl lr-add lr0
-for i in 1 2; do
-    ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.254/24 2001:db8:$i::1/64
-    ovn-nbctl -- lsp-add sw$i lrp$i-attachment \
-              -- set Logical_Switch_Port lrp$i-attachment type=router \
-                options:router-port=lrp$i addresses='"00:00:00:00:ff:0'$i' 192.168.'$i'.254 2001:db8:'$i'::1"'
-done
-
-OVN_POPULATE_ARP
-# allow some time for ovn-northd and ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-
-test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 11 0000 7dae fcfc 0303
-test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 84 0000 7dae fcfd 0302
-test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000100000000000000000001 11 0015 dbb8303900155bac6b646f65206676676e6d66720a 0104 d570
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
-
-test_tcp_syn_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 0000 7bae 4486
-test_ip6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 84 0004 01020304 0103 627e
-test_tcp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 0000 4486
-OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected])
-
-OVN_CLEANUP([hv1], [hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ovn-controller exit])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-# Logical network:
-# One Logical Router: ro, with two logical switches sw1 and sw2.
-# sw1 is for subnet 10.0.0.0/8
-# sw2 is for subnet 20.0.0.0/8
-# sw1 has a single port bound on hv1
-# sw2 has a single port bound on hv2
-
-ovn-nbctl lr-add ro
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add sw2
-
-sw1_ro_mac=00:00:10:00:00:01
-sw1_ro_ip=10.0.0.1
-sw2_ro_mac=00:00:20:00:00:01
-sw2_ro_ip=20.0.0.1
-sw1_p1_mac=00:00:10:00:00:02
-sw1_p1_ip=10.0.0.2
-sw2_p1_mac=00:00:20:00:00:02
-sw2_p1_ip=20.0.0.2
-
-ovn-nbctl lrp-add ro ro-sw1 $sw1_ro_mac ${sw1_ro_ip}/8
-ovn-nbctl lrp-add ro ro-sw2 $sw2_ro_mac ${sw2_ro_ip}/8
-ovn-nbctl lsp-add sw1 sw1-ro -- set Logical_Switch_Port sw1-ro type=router \
-  options:router-port=ro-sw1 addresses=\"$sw1_ro_mac\"
-ovn-nbctl lsp-add sw2 sw2-ro -- set Logical_Switch_Port sw2-ro type=router \
-  options:router-port=ro-sw2 addresses=\"$sw2_ro_mac\"
-
-ovn-nbctl lsp-add sw1 sw1-p1 \
--- lsp-set-addresses sw1-p1 "$sw1_p1_mac $sw1_p1_ip"
-
-ovn-nbctl lsp-add sw2 sw2-p1 \
--- lsp-set-addresses sw2-p1 "$sw2_p1_mac $sw2_p1_ip"
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=sw2-p1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-OVN_POPULATE_ARP
-
-sleep 1
-
-packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
-       ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-
-# Start by Sending the packet and make sure it makes it there as expected
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
-       ip4 && ip.ttl==63 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Stop ovn-controller on hv2
-as hv2 ovs-appctl -t ovn-controller exit
-
-# Now send the packet again. This time, it should not arrive.
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Start ovn-controller again just so OVN_CLEANUP doesn't complain
-as hv2 start_daemon ovn-controller
-
-OVN_CLEANUP([hv1],[hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- external logical port])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-sim_add hv1
-sim_add hv2
-sim_add hv3
-
-ovn-nbctl ls-add ls1
-ovn-nbctl lsp-add ls1 ls1-lp1 \
--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
-
-# Add a couple of external logical port
-ovn-nbctl lsp-add ls1 ls1-lp_ext1 \
--- lsp-set-addresses ls1-lp_ext1 "f0:00:00:00:00:03 10.0.0.6 ae70::6"
-ovn-nbctl lsp-set-port-security ls1-lp_ext1 \
-"f0:00:00:00:00:03 10.0.0.6 ae70::6"
-ovn-nbctl lsp-set-type ls1-lp_ext1 external
-
-ovn-nbctl lsp-add ls1 ls1-lp_ext2 \
--- lsp-set-addresses ls1-lp_ext2 "f0:00:00:00:00:04 10.0.0.7 ae70::7"
-ovn-nbctl lsp-set-port-security ls1-lp_ext2 \
-"f0:00:00:00:00:04 10.0.0.7 ae70::8"
-ovn-nbctl lsp-set-type ls1-lp_ext2 external
-
-d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \
-options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \
-\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")"
-
-d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
-options="\"server_id\"=\"00:00:00:10:00:01\"")"
-
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1}
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext1 ${d1}
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext2 ${d1}
-
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d2}
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext1 ${d2}
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext2 ${d2}
-
-# Create a logical router and connect it to ls1
-ovn-nbctl lr-add lr0
-ovn-nbctl lrp-add lr0 lr0-ls1 a0:10:00:00:00:01 10.0.0.1/24
-ovn-nbctl lsp-add ls1 ls1-lr0
-ovn-nbctl set Logical_Switch_Port ls1-lr0 type=router \
-    options:router-port=lr0-ls1 addresses=router
-
-# Create HA chassis group
-ovn-nbctl ha-chassis-group-add hagrp1
-ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
-
-hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name="hagrp1"`
-
-# There should be 1 HA_Chassis rows with chassis sets
-OVS_WAIT_UNTIL([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep '-' | wc -l ], [0], [1
-])
-
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-phys hv1-ext1 -- \
-    set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \
-    options:rxq_pcap=hv1/ext1-rx.pcap \
-    ofport-request=2
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-phys hv2-ext2 -- \
-    set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \
-    options:rxq_pcap=hv2/ext2-rx.pcap \
-    ofport-request=2
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-as hv3
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.3
-ovs-vsctl -- add-port br-phys hv3-ext3 -- \
-    set interface hv3-ext3 options:tx_pcap=hv3/ext3-tx.pcap \
-    options:rxq_pcap=hv3/ext3-rx.pcap \
-    ofport-request=2
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and
-# hv2 as ha-chassis-group is not set and no localnet port added to ls1.
-AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
-wc -l], [0], [0
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
-])
-
-hv1_uuid=$(ovn-sbctl list chassis hv1 | grep uuid | awk '{print $3}')
-hv2_uuid=$(ovn-sbctl list chassis hv2 | grep uuid | awk '{print $3}')
-hv3_uuid=$(ovn-sbctl list chassis hv3 | grep uuid | awk '{print $3}')
-
-# The port_binding row for ls1-lp_ext1 should have empty chassis
-chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-
-AT_CHECK([test x$chassis == x], [0], [])
-
-# Associate hagrp1 ha-chassis-group to ls1-lp_ext1
-ovn-nbctl --wait=hv set Logical_Switch_Port ls1-lp_ext1 \
-ha-chassis-group=$hagrp1_uuid
-
-# Get the hagrp1 uuid in SB DB.
-sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
-name="hagrp1"`
-
-# Wait till ls1-lp_ext1 port_binding has ha_chassis_group set
-OVS_WAIT_UNTIL(
-    [sb_pb_hagrp=`ovn-sbctl --bare --columns ha_chassis_group find \
-port_binding logical_port=ls1-lp_ext1`
-     test "$sb_pb_hagrp" = "$sb_hagrp1_uuid"])
-
-# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2
-# as no localnet port added to ls1 yet.
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
-])
-
-# Add the localnet port to the logical switch ls1
-ovn-nbctl lsp-add ls1 ln-public
-ovn-nbctl lsp-set-addresses ln-public unknown
-ovn-nbctl lsp-set-type ln-public localnet
-ovn-nbctl --wait=hv lsp-set-options ln-public network_name=phys
-
-ln_public_key=$(ovn-sbctl list port_binding ln-public | grep  tunnel_key | \
-awk '{print $3}')
-
-# The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the
-# ha chassis group.
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = "$hv1_uuid"])
-
-# There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
-wc -l], [0], [3
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$ln_public_key | wc -l], [0], [1
-])
-
-# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
-])
-
-# No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and
-# hv2 as requested-chassis option is not set.
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.07" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.07" | wc -l], [0], [0
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
-])
-
-as hv1
-ovs-vsctl show
-
-# This shell function sends a DHCP request packet
-# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ...
-test_dhcp() {
-    local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 use_ip=$5
-    shift; shift; shift; shift; shift;
-    if test $use_ip != 0; then
-        src_ip=$1
-        dst_ip=$2
-        shift; shift;
-    else
-        src_ip=`ip_to_hex 0 0 0 0`
-        dst_ip=`ip_to_hex 255 255 255 255`
-    fi
-    local request=ffffffffffff${src_mac}0800451001100000000080110000${src_ip}${dst_ip}
-    # udp header and dhcp header
-    request=${request}0044004300fc0000
-    request=${request}010106006359aa760000000000000000000000000000000000000000${src_mac}
-    # client hardware padding
-    request=${request}00000000000000000000
-    # server hostname
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    # boot file name
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    request=${request}0000000000000000000000000000000000000000000000000000000000000000
-    # dhcp magic cookie
-    request=${request}63825363
-    # dhcp message type
-    request=${request}3501${dhcp_type}ff
-
-    local srv_mac=$1 srv_ip=$2 expected_dhcp_opts=$3
-    # total IP length will be the IP length of the request packet
-    # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2)
-    ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
-    udp_len=`expr $ip_len - 20`
-    ip_len=$(printf "%x" $ip_len)
-    udp_len=$(printf "%x" $udp_len)
-    # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len
-    local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip}
-    # udp header and dhcp header.
-    # $udp_len var will be in 3 digits. So adding a '0' before $udp_len
-    reply=${reply}004300440${udp_len}0000020106006359aa760000000000000000
-    # your ip address
-    reply=${reply}${offer_ip}
-    # next server ip address, relay agent ip address, client mac address
-    reply=${reply}0000000000000000${src_mac}
-    # client hardware padding
-    reply=${reply}00000000000000000000
-    # server hostname
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    # boot file name
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
-    # dhcp magic cookie
-    reply=${reply}63825363
-    # dhcp message type
-    local dhcp_reply_type=02
-    if test $dhcp_type = 03; then
-        dhcp_reply_type=05
-    fi
-    reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
-    echo $reply >> ext1_v4.expected
-
-    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
-}
-
-
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-# This shell function sends a DHCPv6 request packet
-# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
-# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
-# packet should be received twice (one from ovn-controller and the other
-# from the "ovs-ofctl monitor br-int resume"
-test_dhcpv6() {
-    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
-    local req_pkt_in_expected=$6
-    local request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla}
-    # dst ip ff02::1:2
-    request=${request}ff020000000000000000000000010002
-    # udp header and dhcpv6 header
-    request=${request}02220223002affff${msg_code}010203
-    # Client identifier
-    request=${request}0001000a00030001${src_mac}
-    # IA-NA (Identity Association for Non Temporary Address)
-    request=${request}0003000c0102030400000e1000001518
-    shift; shift; shift; shift; shift;
-
-    local server_mac=000000100001
-    local server_lla=fe80000000000000020000fffe100001
-    local reply_code=07
-    if test $msg_code = 01; then
-        reply_code=02
-    fi
-    local msg_len=54
-    if test $offer_ip = 1; then
-        msg_len=28
-    fi
-    local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101
-    reply=${reply}${server_lla}${src_lla}
-
-    # udp header and dhcpv6 header
-    reply=${reply}0223022200${msg_len}ffff${reply_code}010203
-    # Client identifier
-    reply=${reply}0001000a00030001${src_mac}
-    # IA-NA
-    if test $offer_ip != 1; then
-        reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}
-        reply=${reply}ffffffffffffffff
-    fi
-    # Server identifier
-    reply=${reply}0002000a00030001${server_mac}
-
-    echo $reply | trim_zeros >> ext${inport}_v6.expected
-    # The inport also receives the request packet since it is connected
-    # to the br-phys.
-    #echo $request >> ext${inport}_v6.expected
-
-    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
-}
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-AT_CAPTURE_FILE([ofctl_monitor0_hv1.log])
-as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log
-
-AT_CAPTURE_FILE([ofctl_monitor0_hv2.log])
-as hv2 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv2.log
-
-AT_CAPTURE_FILE([ofctl_monitor0_hv3.log])
-as hv3 ovs-ofctl monitor br-int resume --detach --no-chdir \
---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv3.log
-
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-# Send DHCPDISCOVER.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-server_mac=ff1000000001
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
-$expected_dhcp_opts
-
-# NXT_RESUMEs should be 1 in hv1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 0 in hv2.
-OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
-
-# ovs-ofctl also resumes the packets and this causes other ports to receive
-# the DHCP request packet. So reset the pcap files so that its easier to test.
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-rm -f ext1_v4.expected
-rm -f ext1_v4.packets
-
-# Send DHCPv6 request
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-offer_ip=ae700000000000000000000000000006
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
-
-# NXT_RESUMEs should be 2 in hv1.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 0 in hv2.
-OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
-# Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
-
-rm -f ext1_v6.expected
-rm -f ext1_v6.packets
-
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-# Delete the ha-chassis hv1.
-ovn-nbctl ha-chassis-group-remove-chassis hagrp1 hv1
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = ""])
-
-# Add hv2 to the ha chassis group
-ovn-nbctl --wait=hv ha-chassis-group-add-chassis hagrp1 hv2 20
-
-ovn-sbctl list ha_chassis_group
-ovn-sbctl list ha_chassis
-
-ovn-sbctl find port_binding logical_port=ls1-lp_ext1
-
-# The ls1-lp_ext1 should be bound to hv2
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = "$hv2_uuid"])
-
-# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
-wc -l], [0], [3
-])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$ln_public_key | wc -l], [0], [1
-])
-
-# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep "0a.00.00.06" | wc -l], [0], [0
-])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
-grep controller | grep tp_src=546 | grep \
-"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$ln_public_key | wc -l], [0], [0
-])
-
-# Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from
-# hv2 ovn-controller.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-server_mac=ff1000000001
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
-$expected_dhcp_opts
-
-# NXT_RESUMEs should be 2 in hv1.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 1 in hv2.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
-
-# ovs-ofctl also resumes the packets and this causes other ports to receive
-# the DHCP request packet. So reset the pcap files so that its easier to test.
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-rm -f ext1_v4.expected
-
-# Send DHCPv6 request again
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-offer_ip=ae700000000000000000000000000006
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 1
-
-# NXT_RESUMEs should be 2 in hv1.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
-# Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
-
-rm -f ext1_v6.expected
-rm -f ext1_v6.packets
-
-as hv1
-ovs-vsctl show
-reset_pcap_file hv1-ext1 hv1/ext1
-reset_pcap_file br-phys_n1 hv1/br-phys_n1
-reset_pcap_file br-phys hv1/br-phys
-
-as hv2
-ovs-vsctl show
-reset_pcap_file hv2-ext2 hv2/ext2
-reset_pcap_file br-phys_n1 hv2/br-phys_n1
-reset_pcap_file br-phys hv2/br-phys
-
-# From  ls1-lp_ext1, send ARP request for the router ip. The ARP
-# response should come from the router pipeline of hv2.
-ext1_mac=f00000000003
-router_mac=a01000000001
-ext1_ip=`ip_to_hex 10 0 0 6`
-router_ip=`ip_to_hex 10 0 0 1`
-arp_request=ffffffffffff${ext1_mac}08060001080006040001${ext1_mac}${ext1_ip}000000000000${router_ip}
-
-as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request
-expected_response=${src_mac}${router_mac}08060001080006040002${router_mac}${router_ip}${ext1_mac}${ext1_ip}
-echo $expected_response > expout
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_arp_resp
-AT_CHECK([cat ext1_arp_resp], [0], [expout])
-
-# Verify that the response came from hv2
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > ext1_arp_resp
-AT_CHECK([cat ext1_arp_resp], [0], [expout])
-
-# Now add 3 ha chassis to the ha chassis group
-ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
-ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv2 20
-ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 10
-
-# hv1 should be master and claim ls1-lp_ext1
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = "$hv1_uuid"])
-
-as hv1
-ovs-vsctl show
-reset_pcap_file hv1-ext1 hv1/ext1
-reset_pcap_file br-phys_n1 hv1/br-phys_n1
-reset_pcap_file br-phys hv1/br-phys
-
-as hv2
-ovs-vsctl show
-reset_pcap_file hv2-ext2 hv2/ext2
-reset_pcap_file br-phys_n1 hv2/br-phys_n1
-reset_pcap_file br-phys hv2/br-phys
-
-as hv3
-ovs-vsctl show
-reset_pcap_file hv3-ext3 hv3/ext3
-reset_pcap_file br-phys_n1 hv3/br-phys_n1
-reset_pcap_file br-phys hv3/br-phys
-
-# Send DHCPDISCOVER.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-server_mac=ff1000000001
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
-$expected_dhcp_opts
-
-# NXT_RESUMEs should be 3 in hv1.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
-
-# ovs-ofctl also resumes the packets and this causes other ports to receive
-# the DHCP request packet. So reset the pcap files so that its easier to test.
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-rm -f ext1_v4.expected
-rm -f ext1_v4.packets
-
-# Send DHCPv6 request
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-offer_ip=ae700000000000000000000000000006
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
-
-# NXT_RESUMEs should be 4 in hv1.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
-# Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
-
-rm -f ext1_v6.expected
-rm -f ext1_v6.packets
-as hv1 reset_pcap_file hv1-ext1 hv1/ext1
-
-# Now increase the priority of hv3 so it becomes master.
-ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 50
-
-# hv3 should be master and claim ls1-lp_ext1
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = "$hv3_uuid"])
-
-as hv1
-ovs-vsctl show
-reset_pcap_file hv1-ext1 hv1/ext1
-reset_pcap_file br-phys_n1 hv1/br-phys_n1
-reset_pcap_file br-phys hv1/br-phys
-
-as hv2
-ovs-vsctl show
-reset_pcap_file hv2-ext2 hv2/ext2
-reset_pcap_file br-phys_n1 hv2/br-phys_n1
-reset_pcap_file br-phys hv2/br-phys
-
-as hv2
-ovs-vsctl show
-reset_pcap_file hv3-ext3 hv3/ext3
-reset_pcap_file br-phys_n1 hv3/br-phys_n1
-reset_pcap_file br-phys hv3/br-phys
-
-# Send DHCPDISCOVER.
-offer_ip=`ip_to_hex 10 0 0 6`
-server_ip=`ip_to_hex 10 0 0 1`
-server_mac=ff1000000001
-expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
-$expected_dhcp_opts
-
-# NXT_RESUMEs should be 4 in hv1.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 1 in hv3.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
-# Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
-
-# ovs-ofctl also resumes the packets and this causes other ports to receive
-# the DHCP request packet. So reset the pcap files so that its easier to test.
-as hv1
-reset_pcap_file hv1-ext1 hv1/ext1
-
-rm -f ext1_v4.expected
-rm -f ext1_v4.packets
-
-# Send DHCPv6 request
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-offer_ip=ae700000000000000000000000000006
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
-
-# NXT_RESUMEs should be 4 in hv1.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
-
-# NXT_RESUMEs should be 2 in hv3.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
-
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
-# Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
-
-# disconnect hv3 from the network, hv1 should take over
-as hv3
-port=${sandbox}_br-phys
-as main ovs-vsctl del-port n1 $port
-
-# hv1 should be master and claim ls1-lp_ext1
-OVS_WAIT_UNTIL(
-    [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=ls1-lp_ext1`
-    test "$chassis" = "$hv1_uuid"])
-
-OVN_CLEANUP([hv1],[hv2],[hv3])
-AT_CLEANUP
-
-AT_SETUP([ovn -- Address Set Incremental Processing])
-AT_KEYWORDS([ovn_as_inc])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.10
-
-ovn-nbctl ls-add ls1
-for i in 1 2; do
-    ovn-nbctl lsp-add ls1 lp$i \
-        -- lsp-set-addresses lp$i "f0:00:00:00:00:0$i 192.168.1.$i"
-    as hv1 ovs-vsctl \
-        -- add-port br-int vif$i \
-        -- set Interface vif$i \
-            external-ids:iface-id=lp$i
-done
-
-for i in 1 2 3; do
-    as1_uuid=`ovn-nbctl --wait=hv create addr name=as1`
-    as2_uuid=`ovn-nbctl --wait=hv create addr name=as2`
-    ovn-nbctl --wait=hv acl-add ls1 to-lport 200 \
-            'outport=="lp1" && ip4 && ip4.src == {$as1, $as2}' allow-related
-    ovn-nbctl --wait=hv set addr as1 addresses="10.1.2.10"
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.10"], [0], [ignore])
-
-    # Update address set as1
-    ovn-nbctl --wait=hv set addr as1 addresses="10.1.2.10 10.1.2.11"
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.11"], [0], [ignore])
-
-    # Update address set as2
-    ovn-nbctl --wait=hv set addr as2 addresses="10.1.2.12 10.1.2.13"
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [0], [ignore])
-
-    # Add another ACL referencing as1
-    n_flows_before=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l`
-    ovn-nbctl --wait=hv acl-add ls1 to-lport 200 \
-            'outport=="lp2" && ip4 && ip4.src == $as1' allow-related
-    n_flows_after=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l`
-    AT_CHECK([test $(expr $n_flows_before \* 2) = $n_flows_after], [0], [ignore])
-
-    # Remove an ACL
-    ovn-nbctl --wait=hv acl-del ls1 to-lport 200 \
-            'outport=="lp2" && ip4 && ip4.src == $as1'
-    n_flows_after=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l`
-    AT_CHECK([test $n_flows_before = $n_flows_after], [0], [ignore])
-
-    # Remove as1 while it is still used by an ACL, the lflows should be reparsed and
-    # parsing should fail.
-    echo "before del as1"
-    ovn-nbctl list addr | grep as1
-    ovn-nbctl --wait=hv destroy addr $as1_uuid
-    echo "after del as1"
-    ovn-nbctl list addr | grep as1
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.10"], [1], [ignore])
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [1], [ignore])
-
-    # Recreate as1
-    as1_uuid=`ovn-nbctl --wait=hv create addr name=as1`
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [0], [ignore])
-
-    # Remove ACLs and address sets
-    ovn-nbctl --wait=hv destroy addr $as1_uuid -- destroy addr $as2_uuid
-    AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [1], [ignore])
-
-    ovn-nbctl --wait=hv acl-del ls1
-done
-
-# Gracefully terminate daemons
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ovn-controller restart])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One Logical Router: ro, with two logical switches sw1 and sw2.
-# sw1 is for subnet 10.0.0.0/8
-# sw2 is for subnet 20.0.0.0/8
-# sw1 has a single port bound on hv1
-# sw2 has a single port bound on hv2
-
-ovn-nbctl lr-add ro
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add sw2
-
-sw1_ro_mac=00:00:10:00:00:01
-sw1_ro_ip=10.0.0.1
-sw2_ro_mac=00:00:20:00:00:01
-sw2_ro_ip=20.0.0.1
-sw1_p1_mac=00:00:10:00:00:02
-sw1_p1_ip=10.0.0.2
-sw2_p1_mac=00:00:20:00:00:02
-sw2_p1_ip=20.0.0.2
-
-ovn-nbctl lrp-add ro ro-sw1 $sw1_ro_mac ${sw1_ro_ip}/8
-ovn-nbctl lrp-add ro ro-sw2 $sw2_ro_mac ${sw2_ro_ip}/8
-ovn-nbctl lsp-add sw1 sw1-ro -- set Logical_Switch_Port sw1-ro type=router \
-  options:router-port=ro-sw1 addresses=\"$sw1_ro_mac\"
-ovn-nbctl lsp-add sw2 sw2-ro -- set Logical_Switch_Port sw2-ro type=router \
-  options:router-port=ro-sw2 addresses=\"$sw2_ro_mac\"
-
-ovn-nbctl lsp-add sw1 sw1-p1 \
--- lsp-set-addresses sw1-p1 "$sw1_p1_mac $sw1_p1_ip"
-
-ovn-nbctl lsp-add sw2 sw2-p1 \
--- lsp-set-addresses sw2-p1 "$sw2_p1_mac $sw2_p1_ip"
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=sw2-p1 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-OVN_POPULATE_ARP
-
-sleep 1
-
-packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
-       ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-
-# Start by Sending the packet and make sure it makes it there as expected
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-# Expected packet has TTL decreased by 1
-expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
-       ip4 && ip.ttl==63 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-       udp && udp.src==53 && udp.dst==4369"
-echo $expected | ovstest test-ovn expr-to-packets > expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-# Stop ovn-controller on hv2 with --restart flag
-as hv2 ovs-appctl -t ovn-controller exit --restart
-
-# Now send the packet again. This time, it should still arrive
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-cat expected expected > expected2
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected2])
-
-# Start ovn-controller again just so OVN_CLEANUP doesn't complain
-as hv2 start_daemon ovn-controller
-
-OVN_CLEANUP([hv1],[hv2])
-
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- ovn-nbctl duplicate addresses])
-ovn_start
-
-# Set up a switch with some switch ports of varying address types
-ovn-nbctl ls-add sw1
-ovn-nbctl set logical_switch sw1 other_config:subnet=192.168.0.0/24
-
-ovn-nbctl lsp-add sw1 sw1-p1
-ovn-nbctl lsp-add sw1 sw1-p2
-ovn-nbctl lsp-add sw1 sw1-p3
-ovn-nbctl lsp-add sw1 sw1-p4
-
-ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1" "00:00:00:00:00:02 10.0.0.2 aef0::2"
-ovn-nbctl lsp-set-addresses sw1-p2 "00:00:00:00:00:03 dynamic"
-ovn-nbctl lsp-set-addresses sw1-p3 "dynamic"
-ovn-nbctl lsp-set-addresses sw1-p4 "router"
-ovn-nbctl lsp-set-addresses sw1-p5 "unknown"
-
-ovn-nbctl list logical_switch_port
-
-# Now try to add duplicate addresses on a new port. These should all fail
-ovn-nbctl --wait=sb lsp-add sw1 sw1-p5
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 10.0.0.1"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 10.0.0.1
-])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 10.0.0.2"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 10.0.0.2
-])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 aef0::1"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv6 address aef0::1
-])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 aef0::2"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv6 address aef0::2
-])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 192.168.0.2"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 192.168.0.2
-])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 192.168.0.3"], [1], [],
-[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 192.168.0.3
-])
-
-# Now try re-setting sw1-p1. This should succeed
-AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1"])
-
-# Now create a new switch and try setting IP addresses the same as the
-# first switch. This should succeed.
-ovn-nbctl ls-add sw2
-ovn-nbctl lsp-add sw2 sw2-p1
-
-AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 10.0.0.1"])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 192.168.0.2"])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 192.168.0.3"])
-AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 aef0::1"])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- router - check packet length - icmp defrag])
-AT_KEYWORDS([check packet length])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-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 10.0.0.3"
-
-ovn-nbctl lr-add lr0
-ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
-ovn-nbctl lsp-add sw0 sw0-lr0
-ovn-nbctl lsp-set-type sw0-lr0 router
-ovn-nbctl lsp-set-addresses sw0-lr0 router
-ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-
-ovn-nbctl ls-add public
-ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-ovn-nbctl lsp-add public public-lr0
-ovn-nbctl lsp-set-type public-lr0 router
-ovn-nbctl lsp-set-addresses public-lr0 router
-ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-
-# localnet port
-ovn-nbctl lsp-add public ln-public
-ovn-nbctl lsp-set-type ln-public localnet
-ovn-nbctl lsp-set-addresses ln-public unknown
-ovn-nbctl lsp-set-options ln-public network_name=phys
-
-ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20
-ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-reset_pcap_file() {
-     local iface=$1
-     local pcap_file=$2
-     ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
- options:rxq_pcap=dummy-rx.pcap
-     rm -f ${pcap_file}*.pcap
-     ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
- options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-ip_to_hex() {
-     printf "%02x%02x%02x%02x" "$@"
-}
-
-test_ip_packet_larger() {
-    local icmp_pmtu_reply_expected=$1
-
-    # Send ip packet from sw0-port1 to outside
-    src_mac="505400000001" # sw-port1 mac
-    dst_mac="00000000ff01" # sw0-lr0 mac (internal router leg)
-    src_ip=`ip_to_hex 10 0 0 3`
-    dst_ip=`ip_to_hex 172 168 0 3`
-    # Set the packet length to 100.
-    pkt_len=0064
-    packet=${dst_mac}${src_mac}08004500${pkt_len}0000000040010000
-    orig_packet_l3=${src_ip}${dst_ip}0304000000000000
-    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-    orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-    packet=${packet}${orig_packet_l3}
-
-    gw_ip_garp=ffffffffffff00002020121308060001080006040001000020201213aca80064000000000000aca80064
-
-    # If icmp_pmtu_reply_expected is 0, it means the packet is lesser than
-    # the gateway mtu and should be delivered to the provider bridge via the
-    # localnet port.
-    # If icmp_pmtu_reply_expected is 1, it means the packet is larger than
-    # the gateway mtu and ovn-controller should drop the packet and instead
-    # generate ICMPv4  Destination Unreachable message with pmtu set to 42.
-    if test $icmp_pmtu_reply_expected = 0; then
-        # Packet to expect at br-phys.
-        src_mac="000020201213"
-        dst_mac="00000012af11"
-        src_ip=`ip_to_hex 10 0 0 3`
-        dst_ip=`ip_to_hex 172 168 0 3`
-        expected=${dst_mac}${src_mac}08004500${pkt_len}000000003f010100
-        expected=${expected}${src_ip}${dst_ip}0304000000000000
-        expected=${expected}000000000000000000000000000000000000
-        expected=${expected}000000000000000000000000000000000000
-        expected=${expected}000000000000000000000000000000000000
-        expected=${expected}000000000000000000000000000000000000
-        echo $expected > br_phys_n1.expected
-        echo $gw_ip_garp >> br_phys_n1.expected
-    else
-        # MTU would be 100 - 18 = 82 (hex 0052)
-        mtu=0052
-        src_ip=`ip_to_hex 10 0 0 1`
-        dst_ip=`ip_to_hex 10 0 0 3`
-        # pkt len should be 128 (28 (icmp packet) + 100 (orig ip + payload))
-        reply_pkt_len=0080
-        ip_csum=bd91
-        icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe016879
-        icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000${mtu}
-        icmp_reply=${icmp_reply}4500${pkt_len}000000003f010100
-        icmp_reply=${icmp_reply}${orig_packet_l3}
-        echo $icmp_reply > hv1-vif1.expected
-    fi
-
-    as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
-    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-
-    # Send packet from sw0-port1 to outside
-    as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-    if test $icmp_pmtu_reply_expected = 0; then
-        OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [br_phys_n1.expected])
-        $PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > pkts
-        # hv1/vif1-tx.pcap can receive the GARP packet generated by ovn-controller
-        # for the gateway router port. So ignore this packet.
-        cat pkts | grep -v $gw_ip_garp > packets
-        AT_CHECK([cat packets], [0], [])
-    else
-        OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [hv1-vif1.expected])
-        $PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap  > \
-        pkts
-        # hv1/br-phys_n1-tx.pcap can receive the GARP packet generated by ovn-controller
-        # for the gateway router port. So ignore this packet.
-        cat pkts | grep -v $gw_ip_garp > packets
-        AT_CHECK([cat packets], [0], [])
-    fi
-}
-
-ovn-nbctl show
-ovn-sbctl show
-
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int  \
-| grep "check_pkt_larger" | wc -l], [0], [[0
-]])
-dp_uuid=$(ovn-sbctl find datapath_binding | grep sw0 -B2 | grep _uuid | \
-awk '{print $3}')
-ovn-sbctl create MAC_Binding ip=172.168.0.3 datapath=$dp_uuid \
-logical_port=lr0-public mac="00\:00\:00\:12\:af\:11"
-
-# Set the gateway mtu to 100. If the packet length is > 100, ovn-controller
-# should send icmp host not reachable with pmtu set to 100.
-ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=100
-as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply
-OVS_WAIT_UNTIL([
-    test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(100)" | \
-    wc -l` -eq 1
-])
-
-icmp_reply_expected=1
-test_ip_packet_larger $icmp_reply_expected
-
-# Set the gateway mtu to 500.
-ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=500
-as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply
-OVS_WAIT_UNTIL([
-    test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(500)" | \
-    wc -l` -eq 1
-])
-
-# Now the packet should be sent via the localnet port to br-phys.
-icmp_reply_expected=0
-test_ip_packet_larger $icmp_reply_expected
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- IP packet buffering])
-AT_KEYWORDS([ip-buffering])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# One LR lr0 that has switches sw0 (192.168.1.0/24) and
-# sw1 (172.16.1.0/24) connected to it.
-#
-# Physical network:
-# Tw0 hypervisors hv[12].
-# hv1 hosts vif sw0-p0.
-# hv1 hosts vif sw1-p0.
-
-send_icmp_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8
-    shift 8
-
-    local ip_ttl=ff
-    local ip_len=001c
-    local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data}
-    as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
-}
-
-send_icmp6_packet() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8
-    shift 8
-
-    local ip6_hdr=6000000000083aff${ipv6_src}${ipv6_dst}
-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}8000dcb662f00001
-
-    as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
-}
-
-get_arp_req() {
-    local eth_src=$1 spa=$2 tpa=$3
-    local request=ffffffffffff${eth_src}08060001080006040001${eth_src}${spa}000000000000${tpa}
-    echo $request
-}
-
-send_arp_reply() {
-    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
-    local request=${eth_dst}${eth_src}08060001080006040002${eth_src}${spa}${eth_dst}${tpa}
-    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
-}
-
-send_na() {
-    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 src_ip=$5 dst_ip=$6
-    local ip6_hdr=6000000000203aff${src_ip}${dst_ip}
-    local request=${eth_dst}${eth_src}86dd${ip6_hdr}8800d78440000000${src_ip}0201${eth_src}
-
-    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
-}
-
-get_nd() {
-    local eth_src=$1 src_ip=$2 dst_ip=$3 ta=$4
-    local ip6_hdr=6000000000203aff${src_ip}${dst_ip}
-    request=3333ff000010${eth_src}86dd${ip6_hdr}8700357600000000${ta}0101${eth_src}
-
-    echo $request
-}
-
-net_add n1
-
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw0-p0 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=sw1-p0 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-
-ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1
-ovn-nbctl ls-add sw0
-ovn-nbctl ls-add sw1
-
-ovn-nbctl lrp-add lr0 sw0 00:00:01:01:02:03 192.168.1.1/24 2001:0:0:0:0:0:0:1/64
-ovn-nbctl lsp-add sw0 rp-sw0 -- set Logical_Switch_Port rp-sw0 \
-    type=router options:router-port=sw0 \
-    -- lsp-set-addresses rp-sw0 router
-
-ovn-nbctl lrp-add lr0 sw1 00:00:02:01:02:03 172.16.1.1/24 2002:0:0:0:0:0:0:1/64
-ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
-    type=router options:router-port=sw1 \
-    -- lsp-set-addresses rp-sw1 router
-
-ovn-nbctl lsp-add sw0 sw0-p0 \
-    -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2"
-
-ovn-nbctl lsp-add sw1 sw1-p0 \
-    -- lsp-set-addresses sw1-p0 unknown
-
-OVN_POPULATE_ARP
-ovn-nbctl --wait=hv sync
-
-ip_to_hex() {
-    printf "%02x%02x%02x%02x" "$@"
-}
-
-src_mac=f00000010203
-src_ip=$(ip_to_hex 192 168 1 2)
-src_ip6=20010000000000000000000000000002
-
-router_mac0=000001010203
-router_mac1=000002010203
-router_ip=$(ip_to_hex 172 16 1 1)
-router_ip6=20020000000000000000000000000001
-
-dst_mac=001122334455
-dst_ip=$(ip_to_hex 172 16 1 10)
-dst_ip6=20020000000000000000000000000010
-
-data=0800bee4391a0001
-
-send_icmp_packet 1 1 $src_mac $router_mac0 $src_ip $dst_ip 0000 $data
-send_arp_reply 2 1 $dst_mac $router_mac1 $dst_ip $router_ip
-echo $(get_arp_req $router_mac1 $router_ip $dst_ip) > expected
-echo "${dst_mac}${router_mac1}08004500001c00004000fe010100${src_ip}${dst_ip}${data}" >> expected
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-nd_ip=ff0200000000000000000001ff000010
-ip6_hdr=6000000000083afe${src_ip6}${dst_ip6}
-
-send_icmp6_packet 1 1 $src_mac $router_mac0 $src_ip6 $dst_ip6
-echo $(get_nd $router_mac1 $src_ip6 $nd_ip $dst_ip6) >> expected
-echo "${dst_mac}${router_mac1}86dd${ip6_hdr}8000dcb662f00001" >> expected
-send_na 2 1 $dst_mac $router_mac1 $dst_ip6 $router_ip6
-
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1],[hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- neighbor update on same HV])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# A public switch (pub) with a localnet port connected to two LRs (lr0 and lr1)
-# each with a distributed gateway port.
-# Two VMs: lp0 on sw0 connected to lr0
-#          lp1 on sw1 connected to lr1
-#
-# This test adds a floating IP to each VM so when they are bound to the same
-# hypervisor, it checks that the GARP sent by ovn-controller causes the
-# MAC_Binding entries to be updated properly on each logical router.
-# It will also capture packets on the physical interface to make sure that the
-# GARPs have been sent out to the external network as well.
-
-# Create logical switches
-ovn-nbctl ls-add sw0
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add pub
-
-# Created localnet port on public switch
-ovn-nbctl lsp-add pub ln-pub
-ovn-nbctl lsp-set-type ln-pub localnet
-ovn-nbctl lsp-set-addresses ln-pub unknown
-ovn-nbctl lsp-set-options ln-pub network_name=phys
-
-# Create logical routers and connect them to public switch
-ovn-nbctl create Logical_Router name=lr0
-ovn-nbctl create Logical_Router name=lr1
-
-ovn-nbctl lrp-add lr0 lr0-pub f0:00:00:00:00:01 172.24.4.220/24
-ovn-nbctl lsp-add pub pub-lr0 -- set Logical_Switch_Port pub-lr0 \
-    type=router options:router-port=lr0-pub options:nat-addresses="router" addresses="router"
-ovn-nbctl lrp-add lr1 lr1-pub f0:00:00:00:01:01 172.24.4.221/24
-ovn-nbctl lsp-add pub pub-lr1 -- set Logical_Switch_Port pub-lr1 \
-    type=router options:router-port=lr1-pub options:nat-addresses="router" addresses="router"
-
-ovn-nbctl lrp-set-gateway-chassis lr0-pub hv1 10
-ovn-nbctl lrp-set-gateway-chassis lr1-pub hv1 10
-
-# Connect sw0 and sw1 to lr0 and lr1
-ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.254/24
-ovn-nbctl lsp-add sw0 sw0-lr0 -- set Logical_Switch_Port sw0-lr0 type=router \
-    options:router-port=lr0-sw0 addresses="router"
-ovn-nbctl lrp-add lr1 lr1-sw1 00:00:00:00:ff:02 20.0.0.254/24
-ovn-nbctl lsp-add sw1 sw1-lr1 -- set Logical_Switch_Port sw1-lr1 type=router \
-    options:router-port=lr1-sw1 addresses="router"
-
-
-# Add SNAT rules
-ovn-nbctl lr-nat-add lr0 snat 172.24.4.220 10.0.0.0/24
-ovn-nbctl lr-nat-add lr1 snat 172.24.4.221 20.0.0.0/24
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 172.24.4.1
-ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-ovs-vsctl add-port br-int vif0 -- set Interface vif0 external-ids:iface-id=lp0
-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1
-
-ovn-nbctl lsp-add sw0 lp0
-ovn-nbctl lsp-add sw1 lp1
-ovn-nbctl lsp-set-addresses lp0 "50:54:00:00:00:01 10.0.0.10"
-ovn-nbctl lsp-set-addresses lp1 "50:54:00:00:00:02 20.0.0.10"
-
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup])
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup])
-
-# Create two floating IPs, one for each VIF
-ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10
-ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10
-
-# Check that the MAC_Binding entries have been properly created
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr0-pub" ip="172.24.4.200" | wc -l` -gt 0])
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr1-pub" ip="172.24.4.100" | wc -l` -gt 0])
-
-# Check that the GARPs went also to the external physical network
-# Wait until at least 4 packets have arrived and copy them to a separate file as
-# more GARPs are expected in the capture in order to avoid race conditions.
-OVS_WAIT_UNTIL([test `$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | wc -l` -gt 4])
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | head -n4 > hv1/br-phys-tx4.pcap
-
-# GARP for lp0 172.24.4.100 on lr0-pub MAC (f0:00:00:00:00:01)
-echo "fffffffffffff0000000000108060001080006040001f00000000001ac180464000000000000ac180464" > expout
-# GARP for 172.24.4.220 on lr0-pub (f0:00:00:00:00:01)
-echo "fffffffffffff0000000000108060001080006040001f00000000001ac1804dc000000000000ac1804dc" >> expout
-# GARP for lp1 172.24.4.200 on lr1-pub MAC (f0:00:00:00:01:01)
-echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804c8000000000000ac1804c8" >> expout
-# GARP for 172.24.4.221 on lr1-pub (f0:00:00:00:01:01)
-echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804dd000000000000ac1804dd" >> expout
-AT_CHECK([sort hv1/br-phys-tx4.pcap], [0], [expout])
-#OVN_CHECK_PACKETS([hv1/br-phys-tx4.pcap], [br-phys.expected])
-
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ipam to non-ipam])
-ovn_start
-
-ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
-ovn-nbctl ls-add sw0
-ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic
-ovn-nbctl --wait=sb add Logical-Switch sw0 other_config subnet=192.168.1.0/24
-
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0],
-    ["0a:00:00:a8:01:03 192.168.1.2"
-])
-
-ovn-nbctl --wait=sb lsp-set-addresses p0 router
-
-ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses
-
-AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0], [[[]]
-])
-AT_CLEANUP
-
-AT_SETUP([ovn -- ipam router ports])
-ovn_start
-
-ovn-nbctl ls-add sw
-ovn-nbctl set logical_switch sw other-config:subnet=192.168.1.0/24
-
-for i in 2 3 4; do
-    ovn-nbctl lr-add ro$i
-    ovn-nbctl lsp-add sw swp$i
-    ovn-nbctl --wait=sb lsp-set-addresses swp$i "02:00:00:00:00:0$i dynamic"
-    cidr=$(ovn-nbctl get logical_switch_port swp$i dynamic_addresses |cut -f2 -d' '|cut -f1 -d\")
-    ovn-nbctl lrp-add ro$i rop$i 02:00:00:00:00:0$i $cidr/24 -- set logical_switch_port swp$i type=router options:router-port=rop$i addresses=router;
-    AT_CHECK_UNQUOTED([ovn-nbctl get logical_router_port rop$i networks], [0], [[["192.168.1.$i/24"]]
-])
-done
-
-ovn-nbctl list logical_switch_port
-ovn-nbctl list logical_router_port
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- test transport zones])
-ovn_start
-
-net_add n1
-for i in 1 2 3 4 5; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.$i.1
-done
-
-dnl Wait for the changes to be propagated
-ovn-nbctl --wait=sb --timeout=3 sync
-ovn-nbctl --wait=hv --timeout=3 sync
-
-dnl Assert that each Chassis has a tunnel formed to every other Chassis
-as hv1
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv2
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv3
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv4
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv5-0
-]])
-
-as hv5
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-]])
-
-dnl Let's now add some Chassis to different transport zones
-dnl * hv1: Will be part of two transport zones: tz1 and tz2 so it
-dnl   should have tunnels formed between the other two Chassis (hv2 and hv3)
-dnl
-dnl * hv2: Will be part of one transport zone: tz1. It should have a tunnel
-dnl   to hv1 but not to hv3
-dnl
-dnl * hv3: Will be part of one transport zone: tz2. It should have a tunnel
-dnl   to hv1 but not to hv2
-dnl
-dnl * hv4 and hv5: Will not have any TZ set so they will keep the tunnels
-dnl   between themselves and remove the tunnels to other Chassis which now
-dnl   belongs to some TZs
-dnl
-as hv1
-ovs-vsctl set open . external-ids:ovn-transport-zones=tz1,tz2
-
-as hv2
-ovs-vsctl set open . external-ids:ovn-transport-zones=tz1
-
-as hv3
-ovs-vsctl set open . external-ids:ovn-transport-zones=tz2
-
-dnl Wait for the changes to be propagated
-ovn-nbctl --wait=sb --timeout=3 sync
-ovn-nbctl --wait=hv --timeout=3 sync
-
-as hv1
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
-]])
-
-as hv2
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-]])
-
-as hv3
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-]])
-
-as hv4
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv5-0
-]])
-
-as hv5
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv4-0
-]])
-
-dnl Removing the transport zones should make all Chassis to create
-dnl tunnels between every other Chassis again
-for i in 1 2 3; do
-    as hv$i
-    ovs-vsctl remove open . external-ids ovn-transport-zones
-done
-
-dnl Wait for the changes to be propagated
-ovn-nbctl --wait=sb --timeout=3 sync
-ovn-nbctl --wait=hv --timeout=3 sync
-
-as hv1
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv2
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv3-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv3
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv4-0
-ovn-hv5-0
-]])
-
-as hv4
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv5-0
-]])
-
-as hv5
-AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0],
-[[ovn-hv1-0
-ovn-hv2-0
-ovn-hv3-0
-ovn-hv4-0
-]])
-
-OVN_CLEANUP([hv1], [hv2], [hv3])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR chassis mac])
-ovn_start
-
-
-# In this test cases we create 2 switches, all connected to same
-# physical network (through br-phys on each HV). Each switch has
-# 1 VIF. Each HV has 1 VIF port. The first digit
-# of VIF port name indicates the hypervisor it is bound to, e.g.
-# lp23 means VIF 3 on hv2.
-#
-# Each switch's VLAN tag and their logical switch ports are:
-#   - ls1:
-#       - tagged with VLAN 101
-#       - ports: lp11
-#   - ls2:
-#       - tagged with VLAN 201
-#       - ports: lp22
-#
-# Note: a localnet port is created for each switch to connect to
-# physical network.
-
-for i in 1 2; do
-    ls_name=ls$i
-    ovn-nbctl ls-add $ls_name
-    ln_port_name=ln$i
-    if test $i -eq 1; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
-    elif test $i -eq 2; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 201
-    fi
-    ovn-nbctl lsp-set-addresses $ln_port_name unknown
-    ovn-nbctl lsp-set-type $ln_port_name localnet
-    ovn-nbctl lsp-set-options $ln_port_name network_name=phys
-done
-
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[11]]) echo ls1 ;; dnl (
-        lp?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_ls () {
-    case $1 in dnl (
-        vif?[[11]]) echo ls1 ;; dnl (
-        vif?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-hv_to_num () {
-    case $1 in dnl (
-        hv1) echo 1 ;; dnl (
-        hv2) echo 2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_num () {
-    case $1 in dnl (
-        vif22) echo 22 ;; dnl (
-        vif21) echo 21 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_hv () {
-    case $1 in dnl (
-        vif[[1]]?) echo hv1 ;; dnl (
-        vif[[2]]?) echo hv2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_lrp () {
-    echo router-to-`vif_to_ls $1`
-}
-
-hv_to_chassis_mac () {
-     case $1 in dnl (
-        hv[[1]]) echo aa:bb:cc:dd:ee:11 ;; dnl (
-        hv[[2]]) echo aa:bb:cc:dd:ee:22 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-ip_to_hex() {
-       printf "%02x%02x%02x%02x" "$@"
-}
-
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-    ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    ovs-vsctl add-port br-int vif$i$i -- \
-        set Interface vif$i$i external-ids:iface-id=lp$i$i \
-                              options:tx_pcap=hv$i/vif$i$i-tx.pcap \
-                              options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
-                              ofport-request=$i$i
-
-    lsp_name=lp$i$i
-    ls_name=$(lsp_to_ls $lsp_name)
-
-    ovn-nbctl lsp-add $ls_name $lsp_name
-    ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i"
-    ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i
-
-    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
-
-done
-
-ovn-nbctl lr-add router
-ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
-ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
-
-ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
-ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
-
-ovn-nbctl --wait=sb sync
-#ovn-sbctl dump-flows
-
-ovn-nbctl show
-ovn-sbctl show
-
-OVN_POPULATE_ARP
-
-test_ip() {
-    # This packet has bad checksums but logical L3 routing doesn't check.
-    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-    shift; shift; shift; shift; shift
-    hv=`vif_to_hv $inport`
-    hv_num=`hv_to_num $hv`
-    chassis_mac=`hv_to_chassis_mac $hv`
-    as $hv ovs-appctl netdev-dummy/receive $inport $packet
-    #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
-    in_ls=`vif_to_ls $inport`
-    in_lrp=`vif_to_lrp $inport`
-    for outport; do
-        out_ls=`vif_to_ls $outport`
-        if test $in_ls = $out_ls; then
-            # Ports on the same logical switch receive exactly the same packet.
-            echo $packet
-        else
-            # Routing decrements TTL and updates source and dest MAC
-            # (and checksum).
-            outport_num=`vif_to_num $outport`
-            out_lrp=`vif_to_lrp $outport`
-            echo f000000000${outport_num}aabbccddee${hv_num}${hv_num}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
-        fi >> $outport.expected
-    done
-}
-
-# Dump a bunch of info helpful for debugging if there's a failure.
-
-echo "------ OVN dump ------"
-ovn-nbctl show
-ovn-sbctl show
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-vsctl list Open_Vswitch
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-vsctl list Open_Vswitch
-
-echo "Send traffic"
-sip=`ip_to_hex 192 168 1 1`
-dip=`ip_to_hex 192 168 2 2`
-test_ip vif11 f00000000011  000001010203 $sip $dip vif22
-
-echo "----------- Post Traffic hv1 dump -----------"
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv1 ovs-appctl fdb/show br-phys
-
-echo "----------- Post Traffic hv2 dump -----------"
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv2 ovs-appctl fdb/show br-phys
-
-OVN_CHECK_PACKETS([hv2/vif22-tx.pcap], [vif22.expected])
-
-OVN_CLEANUP([hv1],[hv2])
-
-AT_CLEANUP
-
-# Run ovn-nbctl in daemon mode, change to a backup database and verify that
-# an insert operation is not allowed.
-AT_SETUP([ovn -- can't write to a backup database server instance])
-ovn_start
-on_exit 'kill $(cat ovn-nbctl.pid)'
-export OVN_NB_DAEMON=$(ovn-nbctl --pidfile --detach)
-
-AT_CHECK([ovn-nbctl ls-add sw0])
-as ovn-nb
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/sync-status | grep active | wc -l], [0], [1
-])
-ovs-appctl -t ovsdb-server ovsdb-server/set-active-ovsdb-server tcp:192.0.2.2:6641
-ovs-appctl -t ovsdb-server ovsdb-server/connect-active-ovsdb-server
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/sync-status | grep -c backup], [0], [1
-])
-AT_CHECK([ovn-nbctl ls-add sw1], [1], [ignore],
-[ovn-nbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
-])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- controller event])
-AT_KEYWORDS([ovn_controller_event])
-ovn_start
-
-# Create hypervisors hv[12].
-# Add vif1[12] to hv1, vif2[12] to hv2
-# Add all of the vifs to a single logical switch sw0.
-
-net_add n1
-ovn-nbctl ls-add sw0
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    for j in 1 2; do
-        ovn-nbctl lsp-add sw0 sw0-p$i$j -- \
-                lsp-set-addresses sw0-p$i$j "00:00:00:00:00:$i$j 192.168.1.$i$j"
-
-        ovs-vsctl -- add-port br-int vif$i$j -- \
-                set interface vif$i$j \
-                external-ids:iface-id=sw0-p$i$j \
-                options:tx_pcap=hv$i/vif$i$j-tx.pcap \
-                options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
-                ofport-request=$i$j
-    done
-done
-
-ovn-nbctl --wait=hv set NB_Global . options:controller_event=true
-ovn-nbctl lb-add lb0 192.168.1.100:80 ""
-ovn-nbctl ls-lb-add sw0 lb0
-uuid_lb=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb0)
-
-OVN_POPULATE_ARP
-ovn-nbctl --timeout=3 --wait=hv sync
-ovn-sbctl lflow-list
-as hv1 ovs-ofctl dump-flows br-int
-
-packet="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:00:00:21 &&
-       ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.1.100 &&
-       tcp && tcp.src==10000 && tcp.dst==80"
-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
-
-ovn-sbctl list controller_event
-uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}')
-AT_CHECK([ovn-sbctl get controller_event $uuid event_type], [0], [dnl
-empty_lb_backends
-])
-AT_CHECK([ovn-sbctl get controller_event $uuid event_info:vip], [0], [dnl
-"192.168.1.100:80"
-])
-AT_CHECK([ovn-sbctl get controller_event $uuid event_info:protocol], [0], [dnl
-tcp
-])
-AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer], [0], [dnl
-"$uuid_lb"
-])
-AT_CHECK([ovn-sbctl get controller_event $uuid seq_num], [0], [dnl
-1
-])
-
-OVN_CLEANUP([hv1], [hv2])
-AT_CLEANUP
-
-AT_SETUP([ovn -- IGMP snoop/querier])
-AT_SKIP_IF([test $HAVE_PYTHON = no])
-ovn_start
-
-# Logical network:
-# Two independent logical switches (sw1 and sw2).
-# sw1:
-#   - subnet 10.0.0.0/8
-#   - 2 ports bound on hv1 (sw1-p11, sw1-p12)
-#   - 2 ports bound on hv2 (sw1-p21, sw1-p22)
-# sw2:
-#   - subnet 20.0.0.0/8
-#   - 1 port bound on hv1 (sw2-p1)
-#   - 1 port bound on hv2 (sw2-p2)
-#   - IGMP Querier from 20.0.0.254
-
-reset_pcap_file() {
-    local iface=$1
-    local pcap_file=$2
-    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-options:rxq_pcap=dummy-rx.pcap
-    rm -f ${pcap_file}*.pcap
-    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-options:rxq_pcap=${pcap_file}-rx.pcap
-}
-
-ip_to_hex() {
-     printf "%02x%02x%02x%02x" "$@"
-}
-
-#
-# send_igmp_v3_report INPORT HV ETH_SRC IP_SRC IP_CSUM GROUP REC_TYPE
-#                     IGMP_CSUM OUTFILE
-#
-# This shell function causes an IGMPv3 report to be received on INPORT of HV.
-# The packet's content has Ethernet destination 01:00:5E:00:00:22 and source
-# ETH_SRC (exactly 12 hex digits). Ethernet type is set to IP.
-# GROUP is the IP multicast group to be joined/to leave (based on REC_TYPE).
-# REC_TYPE == 04: join GROUP
-# REC_TYPE == 03: leave GROUP
-# The packet hexdump is also stored in OUTFILE.
-#
-send_igmp_v3_report() {
-    local inport=$1 hv=$2 eth_src=$3 ip_src=$4 ip_chksum=$5 group=$6
-    local rec_type=$7 igmp_chksum=$8 outfile=$9
-
-    local eth_dst=01005e000016
-    local ip_dst=$(ip_to_hex 224 0 0 22)
-    local ip_ttl=01
-    local ip_ra_opt=94040000
-
-    local igmp_type=2200
-    local num_rec=00000001
-    local aux_dlen=00
-    local num_src=0000
-
-    local eth=${eth_dst}${eth_src}0800
-    local ip=46c0002800004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}${ip_ra_opt}
-    local igmp=${igmp_type}${igmp_chksum}${num_rec}${rec_type}${aux_dlen}${num_src}${group}
-    local packet=${eth}${ip}${igmp}
-
-    echo ${packet} >> ${outfile}
-    as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
-}
-
-#
-# store_igmp_v3_query ETH_SRC IP_SRC IP_CSUM OUTFILE
-#
-# This shell function builds an IGMPv3 general query from ETH_SRC and IP_SRC
-# and stores the hexdump of the packet in OUTFILE.
-#
-store_igmp_v3_query() {
-    local eth_src=$1 ip_src=$2 ip_chksum=$3 outfile=$4
-
-    local eth_dst=01005e000001
-    local ip_dst=$(ip_to_hex 224 0 0 1)
-    local ip_ttl=01
-    local igmp_type=11
-    local max_resp=0a
-    local igmp_chksum=eeeb
-    local addr=00000000
-
-    local eth=${eth_dst}${eth_src}0800
-    local ip=4500002000004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}
-    local igmp=${igmp_type}${max_resp}${igmp_chksum}${addr}000a0000
-    local packet=${eth}${ip}${igmp}
-
-    echo ${packet} >> ${outfile}
-}
-
-#
-# send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN
-#    IP_PROTO DATA OUTFILE
-#
-# This shell function causes an IP multicast packet to be received on INPORT
-# of HV.
-# The hexdump of the packet is stored in OUTFILE.
-#
-send_ip_multicast_pkt() {
-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ip_src=$5 ip_dst=$6
-    local ip_len=$7 ip_chksum=$8 proto=$9 data=${10} outfile=${11}
-
-    local ip_ttl=20
-
-    local eth=${eth_dst}${eth_src}0800
-    local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst}
-    local packet=${eth}${ip}${data}
-
-    as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
-    echo ${packet} >> ${outfile}
-}
-
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add sw2
-
-ovn-nbctl lsp-add sw1 sw1-p11
-ovn-nbctl lsp-add sw1 sw1-p12
-ovn-nbctl lsp-add sw1 sw1-p21
-ovn-nbctl lsp-add sw1 sw1-p22
-ovn-nbctl lsp-add sw2 sw2-p1
-ovn-nbctl lsp-add sw2 sw2-p2
-
-net_add n1
-sim_add hv1
-as hv1
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw1-p11 \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv1-vif2 -- \
-    set interface hv1-vif2 external-ids:iface-id=sw1-p12 \
-    options:tx_pcap=hv1/vif2-tx.pcap \
-    options:rxq_pcap=hv1/vif2-rx.pcap \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv1-vif3 -- \
-    set interface hv1-vif3 external-ids:iface-id=sw2-p1 \
-    options:tx_pcap=hv1/vif3-tx.pcap \
-    options:rxq_pcap=hv1/vif3-rx.pcap \
-    ofport-request=1
-
-sim_add hv2
-as hv2
-ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.2
-ovs-vsctl -- add-port br-int hv2-vif1 -- \
-    set interface hv2-vif1 external-ids:iface-id=sw1-p21 \
-    options:tx_pcap=hv2/vif1-tx.pcap \
-    options:rxq_pcap=hv2/vif1-rx.pcap \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv2-vif2 -- \
-    set interface hv2-vif2 external-ids:iface-id=sw1-p22 \
-    options:tx_pcap=hv2/vif2-tx.pcap \
-    options:rxq_pcap=hv2/vif2-rx.pcap \
-    ofport-request=1
-ovs-vsctl -- add-port br-int hv2-vif3 -- \
-    set interface hv2-vif3 external-ids:iface-id=sw2-p2 \
-    options:tx_pcap=hv2/vif3-tx.pcap \
-    options:rxq_pcap=hv2/vif3-rx.pcap \
-    ofport-request=1
-
-OVN_POPULATE_ARP
-
-# Enable IGMP snooping on sw1.
-ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false"
-ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true"
-
-# No IGMP query should be generated by sw1 (mcast_querier="false").
-truncate -s 0 expected
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected])
-
-ovn-nbctl --wait=hv sync
-
-# Inject IGMP Join for 239.0.1.68 on sw1-p11.
-send_igmp_v3_report hv1-vif1 hv1 \
-    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
-    $(ip_to_hex 239 0 1 68) 04 e9b9 \
-    /dev/null
-# Inject IGMP Join for 239.0.1.68 on sw1-p21.
-send_igmp_v3_report hv2-vif1 hv2 000000000002 $(ip_to_hex 10 0 0 2) f9f9 \
-    $(ip_to_hex 239 0 1 68) 04 e9b9 \
-    /dev/null
-
-# Check that the IGMP Group is learned on both hv.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    test "${total_entries}" = "2"
-])
-
-# Send traffic and make sure it gets forwarded only on the two ports that
-# joined.
-truncate -s 0 expected
-truncate -s 0 expected_empty
-send_ip_multicast_pkt hv1-vif2 hv1 \
-    000000000001 01005e000144 \
-    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \
-    e518e518000a3b3a0000 \
-    expected
-
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
-
-# Inject IGMP Leave for 239.0.1.68 on sw1-p11.
-send_igmp_v3_report hv1-vif1 hv1 \
-    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
-    $(ip_to_hex 239 0 1 68) 03 eab9 \
-    /dev/null
-
-# Check IGMP_Group table on both HV.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    test "${total_entries}" = "1"
-])
-
-# Send traffic traffic and make sure it gets forwarded only on the port that
-# joined.
-as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-truncate -s 0 expected
-truncate -s 0 expected_empty
-send_ip_multicast_pkt hv1-vif2 hv1 \
-    000000000001 01005e000144 \
-    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \
-    e518e518000a3b3a0000 \
-    expected
-
-OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
-
-# Flush IGMP groups.
-ovn-sbctl ip-multicast-flush sw1
-ovn-nbctl --wait=hv -t 3 sync
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    test "${total_entries}" = "0"
-])
-
-# Enable IGMP snooping and querier on sw2 and set query interval to minimum.
-ovn-nbctl set Logical_Switch sw2 \
-    other_config:mcast_snoop="true" \
-    other_config:mcast_querier="true" \
-    other_config:mcast_query_interval=1 \
-    other_config:mcast_eth_src="00:00:00:00:02:fe" \
-    other_config:mcast_ip4_src="20.0.0.254"
-
-# Wait for 1 query interval (1 sec) and check that two queries are generated.
-truncate -s 0 expected
-store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
-store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
-
-sleep 1
-OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
-OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected])
-
-OVN_CLEANUP([hv1], [hv2])
-AT_CLEANUP
diff --git a/tests/system-kmod-testsuite.at b/tests/system-kmod-testsuite.at
index 2fe2e8f94..3de0290c0 100644
--- a/tests/system-kmod-testsuite.at
+++ b/tests/system-kmod-testsuite.at
@@ -19,11 +19,9 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS])
 m4_include([tests/ovs-macros.at])
 m4_include([tests/ovsdb-macros.at])
 m4_include([tests/ofproto-macros.at])
-m4_include([tests/ovn-macros.at])
 m4_include([tests/system-common-macros.at])
 m4_include([tests/system-kmod-macros.at])
 
 m4_include([tests/system-traffic.at])
 m4_include([tests/system-layer3-tunnels.at])
-m4_include([tests/system-ovn.at])
 m4_include([tests/system-interface.at])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
deleted file mode 100644
index f88ad31e4..000000000
--- a/tests/system-ovn.at
+++ /dev/null
@@ -1,1667 +0,0 @@
-AT_BANNER([system-ovn])
-
-AT_SETUP([ovn -- 2 LRs connected via LS, gateway router, SNAT and DNAT])
-AT_KEYWORDS([ovnnat])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
-# to it.  R2 is a gateway router on which we add NAT rules.
-#
-#    foo -- R1 -- join - R2 -- alice
-#           |
-#    bar ----
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Static routes.
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
-"192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
-
-# Add a DNAT rule.
-ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
-    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
-
-# Add a SNAT rule
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
-    external_ip=30.0.0.1 -- add logical_router R2 nat @nat
-
-# wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=30.0.0.1)'])
-
-# 'alice1' should be able to ping 'foo1' directly.
-NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# North-South DNAT: 'alice1' should also be able to ping 'foo1' via 30.0.0.2
-NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# Check conntrack entries.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.2,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic
-# from 30.0.0.1
-NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# Add static routes to handle east-west NAT.
-ovn-nbctl lr-route-add R1 30.0.0.0/24 20.0.0.2
-
-# wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-
-# Flush conntrack entries for easier output parsing of next test.
-AT_CHECK([ovs-appctl dpctl/flush-conntrack])
-
-# East-west DNAT and SNAT: 'bar1' pings 30.0.0.2. 'foo1' receives it.
-NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# As we have a static route that sends all packets with destination
-# 30.0.0.2 to R2, it hits the DNAT rule and converts 30.0.0.2 to 192.168.1.2
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.2.2,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# As we have a SNAT rule that converts 192.168.2.2 to 30.0.0.1, the source is
-# SNATted and 'foo1' receives it.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.2.2,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 LRs connected via LS, gateway router, easy SNAT])
-AT_KEYWORDS([ovnnat])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) connected
-# to it.  R2 has alice (172.16.1.0/24) connected to it.
-# R2 is a gateway router on which we add NAT rules.
-#
-#    foo -- R1 -- join - R2 -- alice
-
-ovn-nbctl lr-add R1
-ovn-nbctl lr-add R2 -- set Logical_Router R2 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add join
-
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-
-# Connect foo to R1
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect alice to R2
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Static routes.
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Add a SNAT rule
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.1.2 \
-    external_ip=172.16.1.1 -- add logical_router R2 nat @nat
-
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)'])
-
-# South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic
-# from 172.16.1.1
-NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- multiple gateway routers, SNAT and DNAT])
-AT_KEYWORDS([ovnnat])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
-# to it.  R3 has bob (172.16.1.0/24) connected to it. Note how both alice and
-# bob have the same subnet behind it.  We are trying to simulate external
-# network via those 2 switches. In real world the switch ports of these
-# switches will have addresses set as "unknown" to make them learning switches.
-# Or those switches will be "localnet" ones.
-#
-#    foo -- R1 -- join - R2 -- alice
-#           |          |
-#    bar ----          - R3 --- bob
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
-ovn-nbctl create Logical_Router name=R3 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add bob
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect bob to R3
-ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24
-ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
-    type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Connect R3 to join
-ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24
-ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
-    type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
-
-# Install static routes with source ip address as the policy for routing.
-# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
-
-# Static routes.
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
-
-# For gateway routers R2 and R3, set a force SNAT rule.
-ovn-nbctl set logical_router R2 options:dnat_force_snat_ip=20.0.0.2
-ovn-nbctl set logical_router R3 options:dnat_force_snat_ip=20.0.0.3
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
-"192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
-
-# Logical port 'bob1' in switch 'bob'.
-ADD_NAMESPACES(bob1)
-ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \
-         "172.16.1.2")
-ovn-nbctl lsp-add bob bob1 \
--- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
-
-# Router R2
-# Add a DNAT rule.
-ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
-    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
-
-# Add a SNAT rule
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.1.2 \
-    external_ip=30.0.0.1 -- add logical_router R2 nat @nat
-
-# Router R3
-# Add a DNAT rule.
-ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
-    external_ip=30.0.0.3 -- add logical_router R3 nat @nat
-
-# Add a SNAT rule
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
-    external_ip=30.0.0.4 -- add logical_router R3 nat @nat
-
-# wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=30.0.0.4)'])
-
-# North-South DNAT: 'alice1' should be able to ping 'foo1' via 30.0.0.2
-NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# Check conntrack entries.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.3,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# But foo1 should receive traffic from 20.0.0.2
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.3,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# North-South DNAT: 'bob1' should be able to ping 'foo1' via 30.0.0.3
-NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.3 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# Check conntrack entries.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.4,dst=30.0.0.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# But foo1 should receive traffic from 20.0.0.3
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.3) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.4,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# South-North SNAT: 'bar1' pings 'bob1'. But 'bob1' receives traffic
-# from 30.0.0.4
-NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.4) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.2.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=30.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic
-# from 30.0.0.1
-NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=172.16.1.3,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- load-balancing])
-AT_KEYWORDS([ovnlb])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# 2 logical switches "foo" (192.168.1.0/24) and "bar" (172.16.1.0/24)
-# connected to a router R1.
-# foo has foo1 to act as a client.
-# bar has bar1, bar2, bar3 to act as servers.
-#
-# Loadbalancer VIPs in 30.0.0.0/24 network.
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 172.16.1.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Create logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Create logical ports 'bar1', 'bar2', 'bar3' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "172.16.1.2/24", "f0:00:0f:01:02:03", \
-         "172.16.1.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:0f:01:02:03 172.16.1.2"
-
-ADD_NAMESPACES(bar2)
-ADD_VETH(bar2, bar2, br-int, "172.16.1.3/24", "f0:00:0f:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add bar bar2 \
--- lsp-set-addresses bar2 "f0:00:0f:01:02:04 172.16.1.3"
-
-ADD_NAMESPACES(bar3)
-ADD_VETH(bar3, bar3, br-int, "172.16.1.4/24", "f0:00:0f:01:02:05", \
-         "172.16.1.1")
-ovn-nbctl lsp-add bar bar3 \
--- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
-ovn-nbctl set logical_switch foo load_balancer=$uuid
-
-# Create another load-balancer with another VIP.
-uuid=`ovn-nbctl create load_balancer vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
-ovn-nbctl add logical_switch foo load_balancer $uuid
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=172.16.1.4:80)'])
-
-# Start webservers in 'bar1', 'bar2' and 'bar3'.
-OVS_START_L7([bar1], [http])
-OVS_START_L7([bar2], [http])
-OVS_START_L7([bar3], [http])
-
-dnl Should work with the virtual IP 30.0.0.1 address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Should work with the virtual IP 30.0.0.3 address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([foo1], [wget 30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- load-balancing - same subnet.])
-AT_KEYWORDS([ovnlb])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# 1 logical switch "foo" (192.168.1.0/24) connected to router R1.
-# foo has foo1, foo2, foo3, foo4 as logical ports.
-#
-# Loadbalancer VIPs in 30.0.0.0/24 network. Router is needed for default
-# gateway. We will test load-balancing with foo1 as a client and foo2, foo3 and
-# foo4 as servers.
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl ls-add foo
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Create logical port 'foo1', 'foo2', 'foo3' and 'foo4' in switch 'foo'.
-ADD_NAMESPACES(foo1, foo2, foo3, foo4)
-for i in `seq 1 4`; do
-    j=`expr $i + 1`
-    ADD_VETH(foo$i, foo$i, br-int, "192.168.1.$j/24", "f0:00:00:01:02:0$j", \
-             "192.168.1.1")
-    ovn-nbctl lsp-add foo foo$i \
-        -- lsp-set-addresses foo$i "f0:00:00:01:02:0$j 192.168.1.$j"
-done
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:30.0.0.1="192.168.1.3,192.168.1.4,192.168.1.5"`
-ovn-nbctl set logical_switch foo load_balancer=$uuid
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.3:80,192.168.1.4:80,192.168.1.5:80"'
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=192.168.1.5:80)'])
-
-# Start webservers in 'foo2', 'foo3' and 'foo4'.
-OVS_START_L7([foo2], [http])
-OVS_START_L7([foo3], [http])
-OVS_START_L7([foo4], [http])
-
-dnl Should work with the virtual IP address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.5,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.5,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- load balancing in gateway router])
-AT_KEYWORDS([ovnlb])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
-# to it.  R2 is a gateway router on which we add load-balancing rules.
-#
-#    foo -- R1 -- join - R2 -- alice
-#           |
-#    bar ----
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Static routes.
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
-"192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"`
-ovn-nbctl set logical_router R2 load_balancer=$uuid
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"'
-
-# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule.
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
-    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
-
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=192.168.2.2:80)'])
-
-# Start webservers in 'foo1', 'bar1'.
-OVS_START_L7([foo1], [http])
-OVS_START_L7([bar1], [http])
-
-dnl Should work with the virtual IP address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- multiple gateway routers, load-balancing])
-AT_KEYWORDS([ovnlb])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
-# to it.  R3 has bob (172.16.1.0/24) connected to it. Note how both alice and
-# bob have the same subnet behind it.  We are trying to simulate external
-# network via those 2 switches. In real world the switch ports of these
-# switches will have addresses set as "unknown" to make them learning switches.
-# Or those switches will be "localnet" ones.
-#
-#    foo -- R1 -- join - R2 -- alice
-#           |          |
-#    bar ----          - R3 --- bob
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
-ovn-nbctl create Logical_Router name=R3 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add bob
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect bob to R3
-ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24
-ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
-    type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Connect R3 to join
-ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24
-ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
-    type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
-
-# Install static routes with source ip address as the policy for routing.
-# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
-ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
-
-# Static routes.
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
-
-# For gateway routers R2 and R3, set a force SNAT rule.
-ovn-nbctl set logical_router R2 options:lb_force_snat_ip=20.0.0.2
-ovn-nbctl set logical_router R3 options:lb_force_snat_ip=20.0.0.3
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
-"192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
-
-# Logical port 'bob1' in switch 'bob'.
-ADD_NAMESPACES(bob1)
-ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \
-         "172.16.1.2")
-ovn-nbctl lsp-add bob bob1 \
--- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"`
-ovn-nbctl set logical_router R2 load_balancer=$uuid
-ovn-nbctl set logical_router R3 load_balancer=$uuid
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=192.168.2.2)'])
-
-# Start webservers in 'foo1', 'bar1'.
-OVS_START_L7([foo1], [http])
-OVS_START_L7([bar1], [http])
-
-dnl Should work with the virtual IP address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Force SNAT should have worked.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.3,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- load balancing in router with gateway router port])
-AT_KEYWORDS([ovnlb])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24),
-# and alice (172.16.1.0/24) connected to it.  The port between R1 and
-# alice is the router gateway port where the R1 LB rules are applied.
-#
-#    foo -- R1 -- bar
-#           |
-#    alice ----
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \
-    -- set Logical_Router_Port alice options:redirect-chassis=hv1
-
-# Connect foo to R1
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect bar to R1
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar \
-    -- lsp-set-addresses rp-bar router
-
-# Connect alice to R1
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'foo2' in switch 'foo'.
-ADD_NAMESPACES(foo2)
-ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo2 \
--- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \
-         "192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2"
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:172.16.1.10="192.168.1.2,192.168.2.2"`
-ovn-nbctl set logical_router R1 load_balancer=$uuid
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"172.16.1.11:8000"'='"192.168.1.2:80,192.168.2.2:80"'
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=192.168.2.2:80)'])
-
-# Start webservers in 'foo1', 'bar1'.
-OVS_START_L7([foo1], [http])
-OVS_START_L7([bar1], [http])
-
-dnl Should work with the virtual IP address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [wget 172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.10) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.2,dst=172.16.1.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.2,dst=172.16.1.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [wget 172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.11) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-tcp,orig=(src=172.16.1.2,dst=172.16.1.11,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-tcp,orig=(src=172.16.1.2,dst=172.16.1.11,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- DNAT and SNAT on distributed router - N/S])
-AT_KEYWORDS([ovnnat])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24),
-# and alice (172.16.1.0/24) connected to it.  The port between R1 and
-# alice is the router gateway port where the R1 NAT rules are applied.
-#
-#    foo -- R1 -- alice
-#           |
-#    bar ----
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \
-    -- set Logical_Router_Port alice options:redirect-chassis=hv1
-
-# Connect foo to R1
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect bar to R1
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar \
-    -- lsp-set-addresses rp-bar router
-
-# Connect alice to R1
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'foo2' in switch 'foo'.
-ADD_NAMESPACES(foo2)
-ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo2 \
--- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \
-         "192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2"
-
-# Add DNAT rules
-AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.3 192.168.1.2 foo1 00:00:02:02:03:04])
-AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.4 192.168.1.3 foo2 00:00:02:02:03:05])
-
-# Add a SNAT rule
-AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.1 192.168.0.0/16])
-
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)'])
-
-# North-South DNAT: 'alice1' pings 'foo1' using 172.16.1.3.
-NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that DNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-# South-North SNAT: 'foo2' pings 'alice1'. But 'alice1' receives traffic
-# from 172.16.1.4
-NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.1.3,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-AT_CHECK([ovs-appctl dpctl/flush-conntrack])
-
-# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic
-# from 172.16.1.1
-NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that SNAT indeed happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- DNAT and SNAT on distributed router - E/W])
-AT_KEYWORDS([ovnnat])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24),
-# and alice (172.16.1.0/24) connected to it.  The port between R1 and
-# alice is the router gateway port where the R1 NAT rules are applied.
-#
-#    foo -- R1 -- alice
-#           |
-#    bar ----
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \
-    -- set Logical_Router_Port alice options:redirect-chassis=hv1
-
-# Connect foo to R1
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo \
-    -- lsp-set-addresses rp-foo router
-
-# Connect bar to R1
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar \
-    -- lsp-set-addresses rp-bar router
-
-# Connect alice to R1
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice \
-    -- lsp-set-addresses rp-alice router
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'foo2' in switch 'foo'.
-ADD_NAMESPACES(foo2)
-ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo2 \
--- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \
-         "192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2"
-
-# Add DNAT rules
-AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.3 192.168.1.2 foo1 00:00:02:02:03:04])
-AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.4 192.168.2.2 bar1 00:00:02:02:03:05])
-
-# Add a SNAT rule
-AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.1 192.168.0.0/16])
-
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)'])
-
-echo "------ hv dump ------"
-ovs-ofctl show br-int
-ovs-ofctl dump-flows br-int
-echo "---------------------"
-
-# East-West No NAT: 'foo1' pings 'bar1' using 192.168.2.2.
-NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that no NAT happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0
-])
-
-# East-West No NAT: 'foo2' pings 'bar1' using 192.168.2.2.
-NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that no NAT happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0
-])
-
-# East-West No NAT: 'bar1' pings 'foo2' using 192.168.1.3.
-NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 192.168.1.3 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# We verify that no NAT happened via 'dump-conntrack' command.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0
-])
-
-# East-West NAT: 'foo1' pings 'bar1' using 172.16.1.4.
-NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# Check conntrack entries.  First SNAT of 'foo1' address happens.
-# Then DNAT of 'bar1' address happens (listed first below).
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-icmp,orig=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-AT_CHECK([ovs-appctl dpctl/flush-conntrack])
-
-# East-West NAT: 'foo2' pings 'bar1' using 172.16.1.4.
-NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
-[0], [dnl
-3 packets transmitted, 3 received, 0% packet loss, time 0ms
-])
-
-# Check conntrack entries.  First SNAT of 'foo2' address happens.
-# Then DNAT of 'bar1' address happens (listed first below).
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-icmp,orig=(src=192.168.1.3,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 LSs IGMP])
-AT_KEYWORDS([ovnigmp])
-
-ovn_start
-
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Two independent logical switches (sw1 and sw2).
-# sw1:
-#   - subnet 10.0.0.0/8
-#   - 2 ports (sw1-p1 - sw1-p2)
-# sw2:
-#   - subnet 20.0.0.0/8
-#   - 2 port (sw2-p1 - sw2-p2)
-#   - IGMP Querier from 20.0.0.254
-
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add sw2
-
-for i in `seq 1 2`
-do
-    ADD_NAMESPACES(sw1-p$i)
-    ADD_VETH(sw1-p$i, sw1-p$i, br-int, "10.0.0.$i/24", "00:00:00:00:01:0$i", \
-            "10.0.0.254")
-    ovn-nbctl lsp-add sw1 sw1-p$i \
-        -- lsp-set-addresses sw1-p$i "00:00:00:00:01:0$i 10.0.0.$i"
-done
-
-for i in `seq 1 2`
-do
-    ADD_NAMESPACES(sw2-p$i)
-    ADD_VETH(sw2-p$i, sw2-p$i, br-int, "20.0.0.$i/24", "00:00:00:00:02:0$i", \
-            "20.0.0.254")
-    ovn-nbctl lsp-add sw2 sw2-p$i \
-        -- lsp-set-addresses sw2-p$i "00:00:00:00:02:0$i 20.0.0.$i"
-done
-
-# Enable IGMP snooping on sw1.
-ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false"
-ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true"
-
-# Inject IGMP Join for 239.0.1.68 on sw1-p1.
-NS_CHECK_EXEC([sw1-p1], [ip addr add dev sw1-p1 239.0.1.68/32 autojoin], [0])
-
-# Inject IGMP Join for 239.0.1.68 on sw1-p2
-NS_CHECK_EXEC([sw1-p2], [ip addr add dev sw1-p2 239.0.1.68/32 autojoin], [0])
-
-# Check that the IGMP Group is learned.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w`
-    test "${total_entries}" = "1"
-    test "${ports}" = "2"
-])
-
-# Inject IGMP Leave for 239.0.1.68 on sw1-p2.
-NS_CHECK_EXEC([sw1-p2], [ip addr del dev sw1-p2 239.0.1.68/32], [0])
-
-# Check that only one port is left in the group.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w`
-    test "${total_entries}" = "1"
-    test "${ports}" = "1"
-])
-
-# Flush IGMP groups.
-ovn-sbctl ip-multicast-flush sw1
-ovn-nbctl --wait=hv -t 3 sync
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
-    test "${total_entries}" = "0"
-])
-
-# Enable IGMP snooping and querier on sw2 and set query interval to minimum.
-ovn-nbctl set Logical_Switch sw2 \
-    other_config:mcast_snoop="true" \
-    other_config:mcast_querier="true" \
-    other_config:mcast_query_interval=1 \
-    other_config:mcast_eth_src="00:00:00:00:02:fe" \
-    other_config:mcast_ip4_src="20.0.0.254"
-
-# Check that queries are generated.
-NS_CHECK_EXEC([sw2-p1], [tcpdump -n -c 2 -i sw2-p1 igmp > sw2-p1.pcap &])
-
-OVS_WAIT_UNTIL([
-    total_queries=`cat sw2-p1.pcap | grep "igmp query" | wc -l`
-    test "${total_queries}" = "2"
-])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
diff --git a/tests/system-userspace-testsuite.at b/tests/system-userspace-testsuite.at
index 4af36bef0..b40da9579 100644
--- a/tests/system-userspace-testsuite.at
+++ b/tests/system-userspace-testsuite.at
@@ -19,12 +19,10 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS])
 m4_include([tests/ovs-macros.at])
 m4_include([tests/ovsdb-macros.at])
 m4_include([tests/ofproto-macros.at])
-m4_include([tests/ovn-macros.at])
 m4_include([tests/system-userspace-macros.at])
 m4_include([tests/system-common-macros.at])
 
 m4_include([tests/system-traffic.at])
 m4_include([tests/system-layer3-tunnels.at])
-m4_include([tests/system-ovn.at])
 m4_include([tests/system-interface.at])
 m4_include([tests/system-userspace-packet-type-aware.at])
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
deleted file mode 100644
index 0b9e8246e..000000000
--- a/tests/test-ovn.c
+++ /dev/null
@@ -1,1584 +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 <errno.h>
-#include <getopt.h>
-#include <sys/wait.h>
-
-#include "command-line.h"
-#include "dp-packet.h"
-#include "fatal-signal.h"
-#include "flow.h"
-#include "openvswitch/dynamic-string.h"
-#include "openvswitch/match.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/logical-fields.h"
-#include "ovn/lib/ovn-l7.h"
-#include "ovn/lib/extend-table.h"
-#include "ovs-thread.h"
-#include "ovstest.h"
-#include "openvswitch/shash.h"
-#include "simap.h"
-#include "util.h"
-
-/* --relops: Bitmap of the relational operators to test, in exhaustive test. */
-static unsigned int test_relops;
-
-/* --nvars: Number of numeric variables to test, in exhaustive test. */
-static int test_nvars = 2;
-
-/* --svars: Number of string variables to test, in exhaustive test. */
-static int test_svars = 2;
-
-/* --bits: Number of bits per variable, in exhaustive test. */
-static int test_bits = 3;
-
-/* --operation: The operation to test, in exhaustive test. */
-static enum { OP_CONVERT, OP_SIMPLIFY, OP_NORMALIZE, OP_FLOW } operation
-    = OP_FLOW;
-
-/* --parallel: Number of parallel processes to use in test. */
-static int test_parallel = 1;
-
-/* -m, --more: Message verbosity */
-static int verbosity;
-
-static void
-compare_token(const struct lex_token *a, const struct lex_token *b)
-{
-    if (a->type != b->type) {
-        fprintf(stderr, "type differs: %d -> %d\n", a->type, b->type);
-        return;
-    }
-
-    if (!((a->s && b->s && !strcmp(a->s, b->s))
-          || (!a->s && !b->s))) {
-        fprintf(stderr, "string differs: %s -> %s\n",
-                a->s ? a->s : "(null)",
-                b->s ? b->s : "(null)");
-        return;
-    }
-
-    if (a->type == LEX_T_INTEGER || a->type == LEX_T_MASKED_INTEGER) {
-        if (memcmp(&a->value, &b->value, sizeof a->value)) {
-            fprintf(stderr, "value differs\n");
-            return;
-        }
-
-        if (a->type == LEX_T_MASKED_INTEGER
-            && memcmp(&a->mask, &b->mask, sizeof a->mask)) {
-            fprintf(stderr, "mask differs\n");
-            return;
-        }
-
-        if (a->format != b->format
-            && !(a->format == LEX_F_HEXADECIMAL
-                 && b->format == LEX_F_DECIMAL
-                 && a->value.integer == 0)) {
-            fprintf(stderr, "format differs: %d -> %d\n",
-                    a->format, b->format);
-        }
-    }
-}
-
-static void
-test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    struct ds input;
-    struct ds output;
-
-    ds_init(&input);
-    ds_init(&output);
-    while (!ds_get_test_line(&input, stdin)) {
-        struct lexer lexer;
-
-        lexer_init(&lexer, ds_cstr(&input));
-        ds_clear(&output);
-        while (lexer_get(&lexer) != LEX_T_END) {
-            size_t len = output.length;
-            lex_token_format(&lexer.token, &output);
-
-            /* Check that the formatted version can really be parsed back
-             * losslessly. */
-            if (lexer.token.type != LEX_T_ERROR) {
-                const char *s = ds_cstr(&output) + len;
-                struct lexer l2;
-
-                lexer_init(&l2, s);
-                lexer_get(&l2);
-                compare_token(&lexer.token, &l2.token);
-                lexer_destroy(&l2);
-            }
-            ds_put_char(&output, ' ');
-        }
-        lexer_destroy(&lexer);
-
-        ds_chomp(&output, ' ');
-        puts(ds_cstr(&output));
-    }
-    ds_destroy(&input);
-    ds_destroy(&output);
-}
-
-static void
-create_symtab(struct shash *symtab)
-{
-    ovn_init_symtab(symtab);
-
-    /* For negative testing. */
-    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
-    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
-                          "self_recurse != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
-                          "mutual_recurse_2 != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
-                          "mutual_recurse_1 != 0", false);
-    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
-}
-
-static void
-create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
-                struct hmap *nd_ra_opts,
-                struct controller_event_options *event_opts)
-{
-    hmap_init(dhcp_opts);
-    dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
-    dhcp_opt_add(dhcp_opts, "netmask", 1, "ipv4");
-    dhcp_opt_add(dhcp_opts, "router",  3, "ipv4");
-    dhcp_opt_add(dhcp_opts, "dns_server", 6, "ipv4");
-    dhcp_opt_add(dhcp_opts, "log_server", 7, "ipv4");
-    dhcp_opt_add(dhcp_opts, "lpr_server",  9, "ipv4");
-    dhcp_opt_add(dhcp_opts, "domain_name", 15, "str");
-    dhcp_opt_add(dhcp_opts, "swap_server", 16, "ipv4");
-    dhcp_opt_add(dhcp_opts, "policy_filter", 21, "ipv4");
-    dhcp_opt_add(dhcp_opts, "router_solicitation",  32, "ipv4");
-    dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4");
-    dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4");
-    dhcp_opt_add(dhcp_opts, "server_id",  54, "ipv4");
-    dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4");
-    dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes");
-    dhcp_opt_add(dhcp_opts, "ip_forward_enable",  19, "bool");
-    dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool");
-    dhcp_opt_add(dhcp_opts, "ethernet_encap", 36, "bool");
-    dhcp_opt_add(dhcp_opts, "default_ttl",  23, "uint8");
-    dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8");
-    dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16");
-    dhcp_opt_add(dhcp_opts, "lease_time",  51, "uint32");
-    dhcp_opt_add(dhcp_opts, "wpad", 252, "str");
-    dhcp_opt_add(dhcp_opts, "bootfile_name", 67, "str");
-    dhcp_opt_add(dhcp_opts, "path_prefix", 210, "str");
-    dhcp_opt_add(dhcp_opts, "tftp_server_address", 150, "ipv4");
-
-    /* DHCPv6 options. */
-    hmap_init(dhcpv6_opts);
-    dhcp_opt_add(dhcpv6_opts, "server_id",  2, "mac");
-    dhcp_opt_add(dhcpv6_opts, "ia_addr",  5, "ipv6");
-    dhcp_opt_add(dhcpv6_opts, "dns_server",  23, "ipv6");
-    dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
-
-    /* IPv6 ND RA options. */
-    hmap_init(nd_ra_opts);
-    nd_ra_opts_init(nd_ra_opts);
-
-    /* OVN controller events options. */
-    controller_event_opts_init(event_opts);
-}
-
-static void
-create_addr_sets(struct shash *addr_sets)
-{
-    shash_init(addr_sets);
-
-    static const char *const addrs1[] = {
-        "10.0.0.1", "10.0.0.2", "10.0.0.3",
-    };
-    static const char *const addrs2[] = {
-        "::1", "::2", "::3",
-    };
-    static const char *const addrs3[] = {
-        "00:00:00:00:00:01", "00:00:00:00:00:02", "00:00:00:00:00:03",
-    };
-    static const char *const addrs4[] = { NULL };
-
-    expr_const_sets_add(addr_sets, "set1", addrs1, 3, true);
-    expr_const_sets_add(addr_sets, "set2", addrs2, 3, true);
-    expr_const_sets_add(addr_sets, "set3", addrs3, 3, true);
-    expr_const_sets_add(addr_sets, "set4", addrs4, 0, true);
-}
-
-static void
-create_port_groups(struct shash *port_groups)
-{
-    shash_init(port_groups);
-
-    static const char *const pg1[] = {
-        "lsp1", "lsp2", "lsp3",
-    };
-    static const char *const pg2[] = { NULL };
-
-    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
-    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
-}
-
-static bool
-lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp)
-{
-    const struct simap *ports = ports_;
-    const struct simap_node *node = simap_find(ports, port_name);
-    if (!node) {
-        return false;
-    }
-    *portp = node->data;
-    return true;
-}
-
-static bool
-is_chassis_resident_cb(const void *ports_, const char *port_name)
-{
-    const struct simap *ports = ports_;
-    const struct simap_node *node = simap_find(ports, port_name);
-    if (node) {
-        return true;
-    }
-    return false;
-}
-
-static void
-test_parse_expr__(int steps)
-{
-    struct shash symtab;
-    struct shash addr_sets;
-    struct shash port_groups;
-    struct simap ports;
-    struct ds input;
-
-    create_symtab(&symtab);
-    create_addr_sets(&addr_sets);
-    create_port_groups(&port_groups);
-
-    simap_init(&ports);
-    simap_put(&ports, "eth0", 5);
-    simap_put(&ports, "eth1", 6);
-    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
-    simap_put(&ports, "lsp1", 0x11);
-    simap_put(&ports, "lsp2", 0x12);
-    simap_put(&ports, "lsp3", 0x13);
-
-    ds_init(&input);
-    while (!ds_get_test_line(&input, stdin)) {
-        struct expr *expr;
-        char *error;
-
-        expr = expr_parse_string(ds_cstr(&input), &symtab, &addr_sets,
-                                 &port_groups, NULL, &error);
-        if (!error && steps > 0) {
-            expr = expr_annotate(expr, &symtab, &error);
-        }
-        if (!error) {
-            if (steps > 1) {
-                expr = expr_simplify(expr, is_chassis_resident_cb, &ports);
-            }
-            if (steps > 2) {
-                expr = expr_normalize(expr);
-                ovs_assert(expr_is_normalized(expr));
-            }
-        }
-        if (!error) {
-            if (steps > 3) {
-                struct hmap matches;
-
-                expr_to_matches(expr, lookup_port_cb, &ports, &matches);
-                expr_matches_print(&matches, stdout);
-                expr_matches_destroy(&matches);
-            } else {
-                struct ds output = DS_EMPTY_INITIALIZER;
-                expr_format(expr, &output);
-                puts(ds_cstr(&output));
-                ds_destroy(&output);
-            }
-        } else {
-            puts(error);
-            free(error);
-        }
-        expr_destroy(expr);
-    }
-    ds_destroy(&input);
-
-    simap_destroy(&ports);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-    expr_const_sets_destroy(&addr_sets);
-    shash_destroy(&addr_sets);
-    expr_const_sets_destroy(&port_groups);
-    shash_destroy(&port_groups);
-}
-
-static void
-test_parse_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    test_parse_expr__(0);
-}
-
-static void
-test_annotate_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    test_parse_expr__(1);
-}
-
-static void
-test_simplify_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    test_parse_expr__(2);
-}
-
-static void
-test_normalize_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    test_parse_expr__(3);
-}
-
-static void
-test_expr_to_flows(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    test_parse_expr__(4);
-}
-
-/* Print the symbol table. */
-
-static void
-test_dump_symtab(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    struct shash symtab;
-    create_symtab(&symtab);
-
-    const struct shash_node **nodes = shash_sort(&symtab);
-    for (size_t i = 0; i < shash_count(&symtab); i++) {
-        const struct expr_symbol *symbol = nodes[i]->data;
-        struct ds s = DS_EMPTY_INITIALIZER;
-        expr_symbol_format(symbol, &s);
-        puts(ds_cstr(&s));
-        ds_destroy(&s);
-    }
-
-    free(nodes);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-/* Evaluate an expression. */
-
-static bool
-lookup_atoi_cb(const void *aux OVS_UNUSED, const char *port_name,
-               unsigned int *portp)
-{
-    *portp = atoi(port_name);
-    return true;
-}
-
-static void
-test_evaluate_expr(struct ovs_cmdl_context *ctx)
-{
-    struct shash symtab;
-    struct ds input;
-
-    ovn_init_symtab(&symtab);
-
-    struct flow uflow;
-    char *error = expr_parse_microflow(ctx->argv[1], &symtab, NULL, NULL,
-                                       lookup_atoi_cb, NULL, &uflow);
-    if (error) {
-        ovs_fatal(0, "%s", error);
-    }
-
-    ds_init(&input);
-    while (!ds_get_test_line(&input, stdin)) {
-        struct expr *expr;
-
-        expr = expr_parse_string(ds_cstr(&input), &symtab, NULL, NULL, NULL,
-                                 &error);
-        if (!error) {
-            expr = expr_annotate(expr, &symtab, &error);
-        }
-        if (!error) {
-            printf("%d\n", expr_evaluate(expr, &uflow, lookup_atoi_cb, NULL));
-        } else {
-            puts(error);
-            free(error);
-        }
-        expr_destroy(expr);
-    }
-    ds_destroy(&input);
-
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-/* Compositions.
- *
- * The "compositions" of a positive integer N are all of the ways that one can
- * add up positive integers to sum to N.  For example, the compositions of 3
- * are 3, 2+1, 1+2, and 1+1+1.
- *
- * We use compositions to find all the ways to break up N terms of a Boolean
- * expression into subexpressions.  Suppose we want to generate all expressions
- * with 3 terms.  The compositions of 3 (ignoring 3 itself) provide the
- * possibilities (x && x) || x, x || (x && x), and x || x || x.  (Of course one
- * can exchange && for || in each case.)  One must recursively compose the
- * sub-expressions whose values are 3 or greater; that is what the "tree shape"
- * concept later covers.
- *
- * To iterate through all compositions of, e.g., 5:
- *
- *     unsigned int state;
- *     int s[5];
- *     int n;
- *
- *     for (n = first_composition(ARRAY_SIZE(s), &state, s); n > 0;
- *          n = next_composition(&state, s, n)) {
- *          // Do something with composition 's' with 'n' elements.
- *     }
- *
- * Algorithm from D. E. Knuth, _The Art of Computer Programming, Vol. 4A:
- * Combinatorial Algorithms, Part 1_, section 7.2.1.1, answer to exercise
- * 12(a).
- */
-
-/* Begins iteration through the compositions of 'n'.  Initializes 's' to the
- * number of elements in the first composition of 'n' and returns that number
- * of elements.  The first composition in fact is always 'n' itself, so the
- * return value will be 1.
- *
- * Initializes '*state' to some internal state information.  The caller must
- * maintain this state (and 's') for use by next_composition().
- *
- * 's' must have room for at least 'n' elements. */
-static int
-first_composition(int n, unsigned int *state, int s[])
-{
-    *state = 0;
-    s[0] = n;
-    return 1;
-}
-
-/* Advances 's', with 'sn' elements, to the next composition and returns the
- * number of elements in this new composition, or 0 if no compositions are
- * left.  'state' is the same internal state passed to first_composition(). */
-static int
-next_composition(unsigned int *state, int s[], int sn)
-{
-    int j = sn - 1;
-    if (++*state & 1) {
-        if (s[j] > 1) {
-            s[j]--;
-            s[j + 1] = 1;
-            j++;
-        } else {
-            j--;
-            s[j]++;
-        }
-    } else {
-        if (s[j - 1] > 1) {
-            s[j - 1]--;
-            s[j + 1] = s[j];
-            s[j] = 1;
-            j++;
-        } else {
-            j--;
-            if (!j) {
-                return 0;
-            }
-            s[j] = s[j + 1];
-            s[j - 1]++;
-        }
-    }
-    return j + 1;
-}
-
-static void
-test_composition(struct ovs_cmdl_context *ctx)
-{
-    int n = atoi(ctx->argv[1]);
-    unsigned int state;
-    int s[50];
-
-    for (int sn = first_composition(n, &state, s); sn;
-         sn = next_composition(&state, s, sn)) {
-        for (int i = 0; i < sn; i++) {
-            printf("%d%c", s[i], i == sn - 1 ? '\n' : ' ');
-        }
-    }
-}
-
-/* Tree shapes.
- *
- * This code generates all possible Boolean expressions with a specified number
- * of terms N (equivalent to the number of external nodes in a tree).
- *
- * See test_tree_shape() for a simple example. */
-
-/* An array of these structures describes the shape of a tree.
- *
- * A single element of struct tree_shape describes a single node in the tree.
- * The node has 'sn' direct children.  From left to right, for i in 0...sn-1,
- * s[i] is 1 if the child is a leaf node, otherwise the child is a subtree and
- * s[i] is the number of leaf nodes within that subtree.  In the latter case,
- * the subtree is described by another struct tree_shape within the enclosing
- * array.  The tree_shapes are ordered in the array in in-order.
- */
-struct tree_shape {
-    unsigned int state;
-    int s[50];
-    int sn;
-};
-
-static int
-init_tree_shape__(struct tree_shape ts[], int n)
-{
-    if (n <= 2) {
-        return 0;
-    }
-
-    int n_tses = 1;
-    /* Skip the first composition intentionally. */
-    ts->sn = first_composition(n, &ts->state, ts->s);
-    ts->sn = next_composition(&ts->state, ts->s, ts->sn);
-    for (int i = 0; i < ts->sn; i++) {
-        n_tses += init_tree_shape__(&ts[n_tses], ts->s[i]);
-    }
-    return n_tses;
-}
-
-/* Initializes 'ts[]' as the first in the set of all of possible shapes of
- * trees with 'n' leaves.  Returns the number of "struct tree_shape"s in the
- * first tree shape. */
-static int
-init_tree_shape(struct tree_shape ts[], int n)
-{
-    switch (n) {
-    case 1:
-        ts->sn = 1;
-        ts->s[0] = 1;
-        return 1;
-    case 2:
-        ts->sn = 2;
-        ts->s[0] = 1;
-        ts->s[1] = 1;
-        return 1;
-    default:
-        return init_tree_shape__(ts, n);
-    }
-}
-
-/* Advances 'ts', which currently has 'n_tses' elements, to the next possible
- * tree shape with the number of leaves passed to init_tree_shape().  Returns
- * the number of "struct tree_shape"s in the next shape, or 0 if all tree
- * shapes have been visited. */
-static int
-next_tree_shape(struct tree_shape ts[], int n_tses)
-{
-    if (n_tses == 1 && ts->sn == 2 && ts->s[0] == 1 && ts->s[1] == 1) {
-        return 0;
-    }
-    while (n_tses > 0) {
-        struct tree_shape *p = &ts[n_tses - 1];
-        p->sn = p->sn > 1 ? next_composition(&p->state, p->s, p->sn) : 0;
-        if (p->sn) {
-            for (int i = 0; i < p->sn; i++) {
-                n_tses += init_tree_shape__(&ts[n_tses], p->s[i]);
-            }
-            break;
-        }
-        n_tses--;
-    }
-    return n_tses;
-}
-
-static void
-print_tree_shape(const struct tree_shape ts[], int n_tses)
-{
-    for (int i = 0; i < n_tses; i++) {
-        if (i) {
-            printf(", ");
-        }
-        for (int j = 0; j < ts[i].sn; j++) {
-            int k = ts[i].s[j];
-            if (k > 9) {
-                printf("(%d)", k);
-            } else {
-                printf("%d", k);
-            }
-        }
-    }
-}
-
-static void
-test_tree_shape(struct ovs_cmdl_context *ctx)
-{
-    int n = atoi(ctx->argv[1]);
-    struct tree_shape ts[50];
-    int n_tses;
-
-    for (n_tses = init_tree_shape(ts, n); n_tses;
-         n_tses = next_tree_shape(ts, n_tses)) {
-        print_tree_shape(ts, n_tses);
-        putchar('\n');
-    }
-}
-
-/* Iteration through all possible terminal expressions (e.g. EXPR_T_CMP and
- * EXPR_T_BOOLEAN expressions).
- *
- * Given a tree shape, this allows the code to try all possible ways to plug in
- * terms.
- *
- * Example use:
- *
- *     struct expr terminal;
- *     const struct expr_symbol *vars = ...;
- *     int n_vars = ...;
- *     int n_bits = ...;
- *
- *     init_terminal(&terminal, vars[0]);
- *     do {
- *         // Something with 'terminal'.
- *     } while (next_terminal(&terminal, vars, n_vars, n_bits));
- */
-
-/* Sets 'expr' to the first possible terminal expression.  'var' should be the
- * first variable in the ones to be tested. */
-static void
-init_terminal(struct expr *expr, int phase,
-              const struct expr_symbol *nvars[], int n_nvars,
-              const struct expr_symbol *svars[], int n_svars)
-{
-    if (phase < 1 && n_nvars) {
-        expr->type = EXPR_T_CMP;
-        expr->cmp.symbol = nvars[0];
-        expr->cmp.relop = rightmost_1bit_idx(test_relops);
-        memset(&expr->cmp.value, 0, sizeof expr->cmp.value);
-        memset(&expr->cmp.mask, 0, sizeof expr->cmp.mask);
-        expr->cmp.value.integer = htonll(0);
-        expr->cmp.mask.integer = htonll(0);
-        return;
-    }
-
-    if (phase < 2 && n_svars) {
-        expr->type = EXPR_T_CMP;
-        expr->cmp.symbol = svars[0];
-        expr->cmp.relop = EXPR_R_EQ;
-        expr->cmp.string = xstrdup("0");
-        return;
-    }
-
-    expr->type = EXPR_T_BOOLEAN;
-    expr->boolean = false;
-}
-
-/* Returns 'x' with the rightmost contiguous string of 1s changed to 0s,
- * e.g. 01011100 => 01000000.  See H. S. Warren, Jr., _Hacker's Delight_, 2nd
- * ed., section 2-1. */
-static unsigned int
-turn_off_rightmost_1s(unsigned int x)
-{
-    return ((x & -x) + x) & x;
-}
-
-static const struct expr_symbol *
-next_var(const struct expr_symbol *symbol,
-         const struct expr_symbol *vars[], int n_vars)
-{
-    for (int i = 0; i < n_vars; i++) {
-        if (symbol == vars[i]) {
-            return i + 1 >= n_vars ? NULL : vars[i + 1];
-        }
-    }
-    OVS_NOT_REACHED();
-}
-
-static enum expr_relop
-next_relop(enum expr_relop relop)
-{
-    unsigned int remaining_relops = test_relops & ~((1u << (relop + 1)) - 1);
-    return (remaining_relops
-            ? rightmost_1bit_idx(remaining_relops)
-            : rightmost_1bit_idx(test_relops));
-}
-
-/* Advances 'expr' to the next possible terminal expression within the 'n_vars'
- * variables of 'n_bits' bits each in 'vars[]'. */
-static bool
-next_terminal(struct expr *expr,
-              const struct expr_symbol *nvars[], int n_nvars, int n_bits,
-              const struct expr_symbol *svars[], int n_svars)
-{
-    if (expr->type == EXPR_T_BOOLEAN) {
-        if (expr->boolean) {
-            return false;
-        } else {
-            expr->boolean = true;
-            return true;
-        }
-    }
-
-    if (!expr->cmp.symbol->width) {
-        int next_value = atoi(expr->cmp.string) + 1;
-        free(expr->cmp.string);
-        if (next_value > 1) {
-            expr->cmp.symbol = next_var(expr->cmp.symbol, svars, n_svars);
-            if (!expr->cmp.symbol) {
-                init_terminal(expr, 2, nvars, n_nvars, svars, n_svars);
-                return true;
-            }
-            next_value = 0;
-        }
-        expr->cmp.string = xasprintf("%d", next_value);
-        return true;
-    }
-
-    unsigned int next;
-
-    next = (ntohll(expr->cmp.value.integer)
-            + (ntohll(expr->cmp.mask.integer) << n_bits));
-    for (;;) {
-        next++;
-        unsigned m = next >> n_bits;
-        unsigned v = next & ((1u << n_bits) - 1);
-        if (next >= (1u << (2 * n_bits))) {
-            enum expr_relop old_relop = expr->cmp.relop;
-            expr->cmp.relop = next_relop(old_relop);
-            if (expr->cmp.relop <= old_relop) {
-                expr->cmp.symbol = next_var(expr->cmp.symbol, nvars, n_nvars);
-                if (!expr->cmp.symbol) {
-                    init_terminal(expr, 1, nvars, n_nvars, svars, n_svars);
-                    return true;
-                }
-            }
-            next = UINT_MAX;
-        } else if (v & ~m) {
-            /* Skip: 1-bits in value correspond to 0-bits in mask. */
-        } else if ((!m || turn_off_rightmost_1s(m))
-                   && (expr->cmp.relop != EXPR_R_EQ &&
-                       expr->cmp.relop != EXPR_R_NE)) {
-            /* Skip: can't have discontiguous or all-0 mask for > >= < <=. */
-        } else {
-            expr->cmp.value.integer = htonll(v);
-            expr->cmp.mask.integer = htonll(m);
-            return true;
-        }
-    }
-}
-
-static struct expr *
-make_terminal(struct expr ***terminalp)
-{
-    struct expr *e = expr_create_boolean(true);
-    **terminalp = e;
-    (*terminalp)++;
-    return e;
-}
-
-static struct expr *
-build_simple_tree(enum expr_type type, int n, struct expr ***terminalp)
-{
-    if (n == 2) {
-        struct expr *e = expr_create_andor(type);
-        for (int i = 0; i < 2; i++) {
-            struct expr *sub = make_terminal(terminalp);
-            ovs_list_push_back(&e->andor, &sub->node);
-        }
-        return e;
-    } else if (n == 1) {
-        return make_terminal(terminalp);
-    } else {
-        OVS_NOT_REACHED();
-    }
-}
-
-static struct expr *
-build_tree_shape(enum expr_type type, const struct tree_shape **tsp,
-                 struct expr ***terminalp)
-{
-    const struct tree_shape *ts = *tsp;
-    (*tsp)++;
-
-    struct expr *e = expr_create_andor(type);
-    enum expr_type t = type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND;
-    for (int i = 0; i < ts->sn; i++) {
-        struct expr *sub = (ts->s[i] > 2
-                            ? build_tree_shape(t, tsp, terminalp)
-                            : build_simple_tree(t, ts->s[i], terminalp));
-        ovs_list_push_back(&e->andor, &sub->node);
-    }
-    return e;
-}
-
-struct test_rule {
-    struct cls_rule cr;
-};
-
-static void
-free_rule(struct test_rule *test_rule)
-{
-    cls_rule_destroy(&test_rule->cr);
-    free(test_rule);
-}
-
-static bool
-tree_shape_is_chassis_resident_cb(const void *c_aux OVS_UNUSED,
-                                  const char *port_name OVS_UNUSED)
-{
-    return true;
-}
-
-static int
-test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
-                             struct expr *terminals[], int n_terminals,
-                             const struct expr_symbol *nvars[], int n_nvars,
-                             int n_bits,
-                             const struct expr_symbol *svars[], int n_svars)
-{
-    int n_tested = 0;
-
-    const unsigned int var_mask = (1u << n_bits) - 1;
-    for (int i = 0; i < n_terminals; i++) {
-        init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars);
-    }
-
-    struct ds s = DS_EMPTY_INITIALIZER;
-    struct flow f;
-    memset(&f, 0, sizeof f);
-    for (;;) {
-        for (int i = n_terminals - 1; ; i--) {
-            if (!i) {
-                ds_destroy(&s);
-                return n_tested;
-            }
-            if (next_terminal(terminals[i], nvars, n_nvars, n_bits,
-                              svars, n_svars)) {
-                break;
-            }
-            init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars);
-        }
-        ovs_assert(expr_honors_invariants(expr));
-
-        n_tested++;
-
-        struct expr *modified;
-        if (operation == OP_CONVERT) {
-            ds_clear(&s);
-            expr_format(expr, &s);
-
-            char *error;
-            modified = expr_parse_string(ds_cstr(&s), symtab, NULL,
-                                         NULL, NULL, &error);
-            if (error) {
-                fprintf(stderr, "%s fails to parse (%s)\n",
-                        ds_cstr(&s), error);
-                exit(EXIT_FAILURE);
-            }
-        } else if (operation >= OP_SIMPLIFY) {
-            modified = expr_simplify(expr_clone(expr),
-                                     tree_shape_is_chassis_resident_cb,
-                                     NULL);
-            ovs_assert(expr_honors_invariants(modified));
-
-            if (operation >= OP_NORMALIZE) {
-                modified = expr_normalize(modified);
-                ovs_assert(expr_honors_invariants(modified));
-                ovs_assert(expr_is_normalized(modified));
-            }
-        }
-
-        struct hmap matches;
-        struct classifier cls;
-        if (operation >= OP_FLOW) {
-            struct expr_match *m;
-            struct test_rule *test_rule;
-
-            expr_to_matches(modified, lookup_atoi_cb, NULL, &matches);
-
-            classifier_init(&cls, NULL);
-            HMAP_FOR_EACH (m, hmap_node, &matches) {
-                test_rule = xmalloc(sizeof *test_rule);
-                cls_rule_init(&test_rule->cr, &m->match, 0);
-                classifier_insert(&cls, &test_rule->cr, OVS_VERSION_MIN,
-                                  m->conjunctions, m->n);
-            }
-        }
-        for (int subst = 0; subst < 1 << (n_bits * n_nvars + n_svars);
-             subst++) {
-            for (int i = 0; i < n_nvars; i++) {
-                f.regs[i] = (subst >> (i * n_bits)) & var_mask;
-            }
-            for (int i = 0; i < n_svars; i++) {
-                f.regs[n_nvars + i] = ((subst >> (n_nvars * n_bits + i))
-                                       & 1);
-            }
-
-            bool expected = expr_evaluate(expr, &f, lookup_atoi_cb, NULL);
-            bool actual = expr_evaluate(modified, &f, lookup_atoi_cb, NULL);
-            if (actual != expected) {
-                struct ds expr_s, modified_s;
-
-                ds_init(&expr_s);
-                expr_format(expr, &expr_s);
-
-                ds_init(&modified_s);
-                expr_format(modified, &modified_s);
-
-                fprintf(stderr,
-                        "%s evaluates to %d, but %s evaluates to %d, for",
-                        ds_cstr(&expr_s), expected,
-                        ds_cstr(&modified_s), actual);
-                for (int i = 0; i < n_nvars; i++) {
-                    if (i > 0) {
-                        fputs(",", stderr);
-                    }
-                    fprintf(stderr, " n%d = 0x%x", i,
-                            (subst >> (n_bits * i)) & var_mask);
-                }
-                for (int i = 0; i < n_svars; i++) {
-                    fprintf(stderr, ", s%d = \"%d\"", i,
-                            (subst >> (n_bits * n_nvars + i)) & 1);
-                }
-                putc('\n', stderr);
-                exit(EXIT_FAILURE);
-            }
-
-            if (operation >= OP_FLOW) {
-                bool found = classifier_lookup(&cls, OVS_VERSION_MIN,
-                                               &f, NULL) != NULL;
-                if (expected != found) {
-                    struct ds expr_s, modified_s;
-
-                    ds_init(&expr_s);
-                    expr_format(expr, &expr_s);
-
-                    ds_init(&modified_s);
-                    expr_format(modified, &modified_s);
-
-                    fprintf(stderr,
-                            "%s and %s evaluate to %d, for",
-                            ds_cstr(&expr_s), ds_cstr(&modified_s), expected);
-                    for (int i = 0; i < n_nvars; i++) {
-                        if (i > 0) {
-                            fputs(",", stderr);
-                        }
-                        fprintf(stderr, " n%d = 0x%x", i,
-                                (subst >> (n_bits * i)) & var_mask);
-                    }
-                    for (int i = 0; i < n_svars; i++) {
-                        fprintf(stderr, ", s%d = \"%d\"", i,
-                                (subst >> (n_bits * n_nvars + i)) & 1);
-                    }
-                    fputs(".\n", stderr);
-
-                    fprintf(stderr, "Converted to classifier:\n");
-                    expr_matches_print(&matches, stderr);
-                    fprintf(stderr,
-                            "However, %s flow was found in the classifier.\n",
-                            found ? "a" : "no");
-                    exit(EXIT_FAILURE);
-                }
-            }
-        }
-        if (operation >= OP_FLOW) {
-            struct test_rule *test_rule;
-
-            CLS_FOR_EACH (test_rule, cr, &cls) {
-                classifier_remove_assert(&cls, &test_rule->cr);
-                ovsrcu_postpone(free_rule, test_rule);
-            }
-            classifier_destroy(&cls);
-            ovsrcu_quiesce();
-
-            expr_matches_destroy(&matches);
-        }
-        expr_destroy(modified);
-    }
-}
-
-#ifndef _WIN32
-static void
-wait_pid(pid_t *pids, int *n)
-{
-    int status;
-    pid_t pid;
-
-    pid = waitpid(-1, &status, 0);
-    if (pid < 0) {
-        ovs_fatal(errno, "waitpid failed");
-    } else if (WIFEXITED(status)) {
-        if (WEXITSTATUS(status)) {
-            exit(WEXITSTATUS(status));
-        }
-    } else if (WIFSIGNALED(status)) {
-        raise(WTERMSIG(status));
-        exit(1);
-    } else {
-        OVS_NOT_REACHED();
-    }
-
-    for (int i = 0; i < *n; i++) {
-        if (pids[i] == pid) {
-            pids[i] = pids[--*n];
-            return;
-        }
-    }
-    ovs_fatal(0, "waitpid returned unknown child");
-}
-#endif
-
-static void
-test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    int n_terminals = atoi(ctx->argv[1]);
-    struct tree_shape ts[50];
-    int n_tses;
-
-    struct shash symtab;
-    const struct expr_symbol *nvars[4];
-    const struct expr_symbol *svars[4];
-
-    ovs_assert(test_nvars <= ARRAY_SIZE(nvars));
-    ovs_assert(test_svars <= ARRAY_SIZE(svars));
-    ovs_assert(test_nvars + test_svars <= FLOW_N_REGS);
-
-    shash_init(&symtab);
-    for (int i = 0; i < test_nvars; i++) {
-        char *name = xasprintf("n%d", i);
-        nvars[i] = expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL,
-                                         false);
-        free(name);
-    }
-    for (int i = 0; i < test_svars; i++) {
-        char *name = xasprintf("s%d", i);
-        svars[i] = expr_symtab_add_string(&symtab, name,
-                                          MFF_REG0 + test_nvars + i, NULL);
-        free(name);
-    }
-
-#ifndef _WIN32
-    pid_t *children = xmalloc(test_parallel * sizeof *children);
-    int n_children = 0;
-#endif
-
-    int n_tested = 0;
-    for (int i = 0; i < 2; i++) {
-        enum expr_type base_type = i ? EXPR_T_OR : EXPR_T_AND;
-
-        for (n_tses = init_tree_shape(ts, n_terminals); n_tses;
-             n_tses = next_tree_shape(ts, n_tses)) {
-            const struct tree_shape *tsp = ts;
-            struct expr *terminals[50];
-            struct expr **terminalp = terminals;
-            struct expr *expr = build_tree_shape(base_type, &tsp, &terminalp);
-            ovs_assert(terminalp == &terminals[n_terminals]);
-
-            if (verbosity > 0) {
-                print_tree_shape(ts, n_tses);
-                printf(": ");
-                struct ds s = DS_EMPTY_INITIALIZER;
-                expr_format(expr, &s);
-                puts(ds_cstr(&s));
-                ds_destroy(&s);
-            }
-
-#ifndef _WIN32
-            if (test_parallel > 1) {
-                pid_t pid = xfork();
-                if (!pid) {
-                    test_tree_shape_exhaustively(expr, &symtab,
-                                                 terminals, n_terminals,
-                                                 nvars, test_nvars, test_bits,
-                                                 svars, test_svars);
-                    expr_destroy(expr);
-                    exit(0);
-                } else {
-                    if (n_children >= test_parallel) {
-                        wait_pid(children, &n_children);
-                    }
-                    children[n_children++] = pid;
-                }
-            } else
-#endif
-            {
-                n_tested += test_tree_shape_exhaustively(
-                    expr, &symtab, terminals, n_terminals,
-                    nvars, test_nvars, test_bits,
-                    svars, test_svars);
-            }
-            expr_destroy(expr);
-        }
-    }
-#ifndef _WIN32
-    while (n_children > 0) {
-        wait_pid(children, &n_children);
-    }
-    free(children);
-#endif
-
-    printf("Tested ");
-    switch (operation) {
-    case OP_CONVERT:
-        printf("converting");
-        break;
-    case OP_SIMPLIFY:
-        printf("simplifying");
-        break;
-    case OP_NORMALIZE:
-        printf("normalizing");
-        break;
-    case OP_FLOW:
-        printf("converting to flows");
-        break;
-    }
-    if (n_tested) {
-        printf(" %d expressions of %d terminals", n_tested, n_terminals);
-    } else {
-        printf(" all %d-terminal expressions", n_terminals);
-    }
-    if (test_nvars || test_svars) {
-        printf(" with");
-        if (test_nvars) {
-            printf(" %d numeric vars (each %d bits) in terms of operators",
-                   test_nvars, test_bits);
-            for (unsigned int relops = test_relops; relops;
-                 relops = zero_rightmost_1bit(relops)) {
-                enum expr_relop r = rightmost_1bit_idx(relops);
-                printf(" %s", expr_relop_to_string(r));
-            }
-        }
-        if (test_nvars && test_svars) {
-            printf (" and");
-        }
-        if (test_svars) {
-            printf(" %d string vars", test_svars);
-        }
-    } else {
-        printf(" in terms of Boolean constants only");
-    }
-    printf(".\n");
-
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-static void
-test_expr_to_packets(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    struct shash symtab;
-    struct ds input;
-
-    create_symtab(&symtab);
-
-    ds_init(&input);
-    while (!ds_get_test_line(&input, stdin)) {
-        struct flow uflow;
-        char *error = expr_parse_microflow(ds_cstr(&input), &symtab, NULL,
-                                           NULL, lookup_atoi_cb, NULL, &uflow);
-        if (error) {
-            puts(error);
-            free(error);
-            continue;
-        }
-
-        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);
-
-        struct ds output = DS_EMPTY_INITIALIZER;
-        const uint8_t *buf = dp_packet_data(&packet);
-        for (int i = 0; i < dp_packet_size(&packet); i++) {
-            uint8_t val = buf[i];
-            ds_put_format(&output, "%02"PRIx8, val);
-        }
-        puts(ds_cstr(&output));
-        ds_destroy(&output);
-
-        dp_packet_uninit(&packet);
-    }
-    ds_destroy(&input);
-
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-}
-
-/* Actions. */
-
-static void
-test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
-{
-    struct shash symtab;
-    struct hmap dhcp_opts;
-    struct hmap dhcpv6_opts;
-    struct hmap nd_ra_opts;
-    struct controller_event_options event_opts;
-    struct simap ports;
-    struct ds input;
-    bool ok = true;
-
-    create_symtab(&symtab);
-    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts, &event_opts);
-
-    /* Initialize group ids. */
-    struct ovn_extend_table group_table;
-    ovn_extend_table_init(&group_table);
-
-    /* Initialize meter ids for QoS. */
-    struct ovn_extend_table meter_table;
-    ovn_extend_table_init(&meter_table);
-
-    simap_init(&ports);
-    simap_put(&ports, "eth0", 5);
-    simap_put(&ports, "eth1", 6);
-    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
-
-    ds_init(&input);
-    while (!ds_get_test_line(&input, stdin)) {
-        struct ofpbuf ovnacts;
-        struct expr *prereqs;
-        char *error;
-
-        puts(ds_cstr(&input));
-
-        ofpbuf_init(&ovnacts, 0);
-
-        const struct ovnact_parse_params pp = {
-            .symtab = &symtab,
-            .dhcp_opts = &dhcp_opts,
-            .dhcpv6_opts = &dhcpv6_opts,
-            .nd_ra_opts = &nd_ra_opts,
-            .controller_event_opts = &event_opts,
-            .n_tables = 24,
-            .cur_ltable = 10,
-        };
-        error = ovnacts_parse_string(ds_cstr(&input), &pp, &ovnacts, &prereqs);
-        if (!error) {
-            /* Convert the parsed representation back to a string and print it,
-             * if it's different from the input. */
-            struct ds ovnacts_s = DS_EMPTY_INITIALIZER;
-            ovnacts_format(ovnacts.data, ovnacts.size, &ovnacts_s);
-            if (strcmp(ds_cstr(&input), ds_cstr(&ovnacts_s))) {
-                printf("    formats as %s\n", ds_cstr(&ovnacts_s));
-            }
-
-            /* Encode the actions into OpenFlow and print. */
-            const struct ovnact_encode_params ep = {
-                .lookup_port = lookup_port_cb,
-                .aux = &ports,
-                .is_switch = true,
-                .group_table = &group_table,
-                .meter_table = &meter_table,
-
-                .pipeline = OVNACT_P_INGRESS,
-                .ingress_ptable = 8,
-                .egress_ptable = 40,
-                .output_ptable = 64,
-                .mac_bind_ptable = 65,
-            };
-            struct ofpbuf ofpacts;
-            ofpbuf_init(&ofpacts, 0);
-            ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
-            struct ds ofpacts_s = DS_EMPTY_INITIALIZER;
-            struct ofpact_format_params fp = { .s = &ofpacts_s };
-            ofpacts_format(ofpacts.data, ofpacts.size, &fp);
-            printf("    encodes as %s\n", ds_cstr(&ofpacts_s));
-            ds_destroy(&ofpacts_s);
-            ofpbuf_uninit(&ofpacts);
-
-            /* Print prerequisites if any. */
-            if (prereqs) {
-                struct ds prereqs_s = DS_EMPTY_INITIALIZER;
-                expr_format(prereqs, &prereqs_s);
-                printf("    has prereqs %s\n", ds_cstr(&prereqs_s));
-                ds_destroy(&prereqs_s);
-            }
-
-            /* Now re-parse and re-format the string to verify that it's
-             * round-trippable. */
-            struct ofpbuf ovnacts2;
-            struct expr *prereqs2;
-            ofpbuf_init(&ovnacts2, 0);
-            error = ovnacts_parse_string(ds_cstr(&ovnacts_s), &pp, &ovnacts2,
-                                         &prereqs2);
-            if (!error) {
-                struct ds ovnacts2_s = DS_EMPTY_INITIALIZER;
-                ovnacts_format(ovnacts2.data, ovnacts2.size, &ovnacts2_s);
-                if (strcmp(ds_cstr(&ovnacts_s), ds_cstr(&ovnacts2_s))) {
-                    printf("    bad reformat: %s\n", ds_cstr(&ovnacts2_s));
-                    ok = false;
-                }
-                ds_destroy(&ovnacts2_s);
-            } else {
-                printf("    reparse error: %s\n", error);
-                free(error);
-                ok = false;
-            }
-            expr_destroy(prereqs2);
-
-            ovnacts_free(ovnacts2.data, ovnacts2.size);
-            ofpbuf_uninit(&ovnacts2);
-            ds_destroy(&ovnacts_s);
-        } else {
-            printf("    %s\n", error);
-            free(error);
-        }
-
-        expr_destroy(prereqs);
-        ovnacts_free(ovnacts.data, ovnacts.size);
-        ofpbuf_uninit(&ovnacts);
-    }
-    ds_destroy(&input);
-
-    simap_destroy(&ports);
-    expr_symtab_destroy(&symtab);
-    shash_destroy(&symtab);
-    dhcp_opts_destroy(&dhcp_opts);
-    dhcp_opts_destroy(&dhcpv6_opts);
-    nd_ra_opts_destroy(&nd_ra_opts);
-    controller_event_opts_destroy(&event_opts);
-    ovn_extend_table_destroy(&group_table);
-    ovn_extend_table_destroy(&meter_table);
-    exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
-}
-
-static unsigned int
-parse_relops(const char *s)
-{
-    unsigned int relops = 0;
-    struct lexer lexer;
-
-    lexer_init(&lexer, s);
-    lexer_get(&lexer);
-    do {
-        enum expr_relop relop;
-
-        if (expr_relop_from_token(lexer.token.type, &relop)) {
-            relops |= 1u << relop;
-            lexer_get(&lexer);
-        } else {
-            ovs_fatal(0, "%s: relational operator expected at `%.*s'",
-                      s, (int) (lexer.input - lexer.start), lexer.start);
-        }
-        lexer_match(&lexer, LEX_T_COMMA);
-    } while (lexer.token.type != LEX_T_END);
-    lexer_destroy(&lexer);
-
-    return relops;
-}
-
-static void
-usage(void)
-{
-    printf("\
-%s: OVN test utility\n\
-usage: test-ovn %s [OPTIONS] COMMAND [ARG...]\n\
-\n\
-lex\n\
-  Lexically analyzes OVN input from stdin and print them back on stdout.\n\
-\n\
-parse-expr\n\
-annotate-expr\n\
-simplify-expr\n\
-normalize-expr\n\
-expr-to-flows\n\
-  Parses OVN expressions from stdin and prints them back on stdout after\n\
-  differing degrees of analysis.  Available fields are based on packet\n\
-  headers.\n\
-\n\
-expr-to-packets\n\
-  Parses OVN expressions from stdin and prints out matching packets in\n\
-  hexadecimal on stdout.\n\
-\n\
-evaluate-expr MICROFLOW\n\
-  Parses OVN expressions from stdin and evaluates them against the flow\n\
-  specified in MICROFLOW, which must be an expression that constrains\n\
-  the packet, e.g. \"ip4 && tcp.src == 80\" for a TCP packet with source\n\
-  port 80, and prints the results on stdout, either 1 for true or 0 for\n\
-  false.  Use quoted integers, e.g. \"123\", for string fields.\n\
-\n\
-  Example: for MICROFLOW of \"ip4 && tcp.src == 80\", \"eth.type == 0x800\"\n\
-  evaluates to true, \"udp\" evaluates to false, and \"udp || tcp\"\n\
-  evaluates to true.\n\
-\n\
-composition N\n\
-  Prints all the compositions of N on stdout.\n\
-\n\
-tree-shape N\n\
-  Prints all the tree shapes with N terminals on stdout.\n\
-\n\
-exhaustive N\n\
-  Tests that all possible Boolean expressions with N terminals are properly\n\
-  simplified, normalized, and converted to flows.  Available options:\n\
-   Overall options:\n\
-    --operation=OPERATION  Operation to test, one of: convert, simplify,\n\
-        normalize, flow.  Default: flow.  'normalize' includes 'simplify',\n\
-        'flow' includes 'simplify' and 'normalize'.\n\
-    --parallel=N  Number of processes to use in parallel, default 1.\n\
-   Numeric vars:\n\
-    --nvars=N  Number of numeric vars to test, in range 0...4, default 2.\n\
-    --bits=N  Number of bits per variable, in range 1...3, default 3.\n\
-    --relops=OPERATORS   Test only the specified Boolean operators.\n\
-                         OPERATORS may include == != < <= > >=, space or\n\
-                         comma separated.  Default is all operators.\n\
-   String vars:\n\
-    --svars=N  Number of string vars to test, in range 0...4, default 2.\n\
-\n\
-parse-actions\n\
-  Parses OVN actions from stdin and prints the equivalent OpenFlow actions\n\
-  on stdout.\n\
-",
-           program_name, program_name);
-    exit(EXIT_SUCCESS);
-}
-
-static void
-test_ovn_main(int argc, char *argv[])
-{
-    enum {
-        OPT_RELOPS = UCHAR_MAX + 1,
-        OPT_NVARS,
-        OPT_SVARS,
-        OPT_BITS,
-        OPT_OPERATION,
-        OPT_PARALLEL
-    };
-    static const struct option long_options[] = {
-        {"relops", required_argument, NULL, OPT_RELOPS},
-        {"nvars", required_argument, NULL, OPT_NVARS},
-        {"svars", required_argument, NULL, OPT_SVARS},
-        {"bits", required_argument, NULL, OPT_BITS},
-        {"operation", required_argument, NULL, OPT_OPERATION},
-        {"parallel", required_argument, NULL, OPT_PARALLEL},
-        {"more", no_argument, NULL, 'm'},
-        {"help", no_argument, NULL, 'h'},
-        {NULL, 0, NULL, 0},
-    };
-    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
-
-    set_program_name(argv[0]);
-
-    test_relops = parse_relops("== != < <= > >=");
-    for (;;) {
-        int option_index = 0;
-        int c = getopt_long (argc, argv, short_options, long_options,
-                             &option_index);
-
-        if (c == -1) {
-            break;
-        }
-        switch (c) {
-        case OPT_RELOPS:
-            test_relops = parse_relops(optarg);
-            break;
-
-        case OPT_NVARS:
-            test_nvars = atoi(optarg);
-            if (test_nvars < 0 || test_nvars > 4) {
-                ovs_fatal(0, "number of numeric variables must be "
-                          "between 0 and 4");
-            }
-            break;
-
-        case OPT_SVARS:
-            test_svars = atoi(optarg);
-            if (test_svars < 0 || test_svars > 4) {
-                ovs_fatal(0, "number of string variables must be "
-                          "between 0 and 4");
-            }
-            break;
-
-        case OPT_BITS:
-            test_bits = atoi(optarg);
-            if (test_bits < 1 || test_bits > 3) {
-                ovs_fatal(0, "number of bits must be between 1 and 3");
-            }
-            break;
-
-        case OPT_OPERATION:
-            if (!strcmp(optarg, "convert")) {
-                operation = OP_CONVERT;
-            } else if (!strcmp(optarg, "simplify")) {
-                operation = OP_SIMPLIFY;
-            } else if (!strcmp(optarg, "normalize")) {
-                operation = OP_NORMALIZE;
-            } else if (!strcmp(optarg, "flow")) {
-                operation = OP_FLOW;
-            } else {
-                ovs_fatal(0, "%s: unknown operation", optarg);
-            }
-            break;
-
-        case OPT_PARALLEL:
-            test_parallel = atoi(optarg);
-            break;
-
-        case 'm':
-            verbosity++;
-            break;
-
-        case 'h':
-            usage();
-            /* fall through */
-
-        case '?':
-            exit(1);
-
-        default:
-            abort();
-        }
-    }
-    free(short_options);
-
-    static const struct ovs_cmdl_command commands[] = {
-        /* Lexer. */
-        {"lex", NULL, 0, 0, test_lex, OVS_RO},
-
-        /* Symbol table. */
-        {"dump-symtab", NULL, 0, 0, test_dump_symtab, OVS_RO},
-
-        /* Expressions. */
-        {"parse-expr", NULL, 0, 0, test_parse_expr, OVS_RO},
-        {"annotate-expr", NULL, 0, 0, test_annotate_expr, OVS_RO},
-        {"simplify-expr", NULL, 0, 0, test_simplify_expr, OVS_RO},
-        {"normalize-expr", NULL, 0, 0, test_normalize_expr, OVS_RO},
-        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows, OVS_RO},
-        {"evaluate-expr", NULL, 1, 1, test_evaluate_expr, OVS_RO},
-        {"composition", NULL, 1, 1, test_composition, OVS_RO},
-        {"tree-shape", NULL, 1, 1, test_tree_shape, OVS_RO},
-        {"exhaustive", NULL, 1, 1, test_exhaustive, OVS_RO},
-        {"expr-to-packets", NULL, 0, 0, test_expr_to_packets, OVS_RO},
-
-        /* Actions. */
-        {"parse-actions", NULL, 0, 0, test_parse_actions, OVS_RO},
-
-        {NULL, NULL, 0, 0, NULL, OVS_RO},
-    };
-    struct ovs_cmdl_context ctx;
-    ctx.argc = argc - optind;
-    ctx.argv = argv + optind;
-    ovs_cmdl_run_command(&ctx, commands);
-}
-
-OVSTEST_REGISTER("test-ovn", test_ovn_main);
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 4d5e81618..e75912300 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -19,7 +19,6 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS])
 m4_include([tests/ovs-macros.at])
 m4_include([tests/ovsdb-macros.at])
 m4_include([tests/ofproto-macros.at])
-m4_include([tests/ovn-macros.at])
 
 m4_include([tests/completion.at])
 m4_include([tests/checkpatch.at])
@@ -74,13 +73,6 @@ m4_include([tests/rstp.at])
 m4_include([tests/vlog.at])
 m4_include([tests/vtep-ctl.at])
 m4_include([tests/auto-attach.at])
-m4_include([tests/ovn.at])
-m4_include([tests/ovn-northd.at])
-m4_include([tests/ovn-nbctl.at])
-m4_include([tests/ovn-sbctl.at])
-m4_include([tests/ovn-controller.at])
-m4_include([tests/ovn-controller-vtep.at])
 m4_include([tests/mcast-snooping.at])
 m4_include([tests/packet-type-aware.at])
 m4_include([tests/nsh.at])
-m4_include([tests/ovn-performance.at])
diff --git a/tutorial/automake.mk b/tutorial/automake.mk
index b7ea10c98..0f6b0fff5 100644
--- a/tutorial/automake.mk
+++ b/tutorial/automake.mk
@@ -5,8 +5,7 @@ EXTRA_DIST += \
 	tutorial/t-stage1 \
 	tutorial/t-stage2 \
 	tutorial/t-stage3 \
-	tutorial/t-stage4 \
-	tutorial/ovn-setup.sh
+	tutorial/t-stage4
 sandbox: all
 	cd $(srcdir)/tutorial && MAKE=$(MAKE) HAVE_OPENSSL=$(HAVE_OPENSSL) \
 		./ovs-sandbox -b $(abs_builddir) $(SANDBOXFLAGS)
diff --git a/tutorial/ovn-setup.sh b/tutorial/ovn-setup.sh
deleted file mode 100755
index 969b2330f..000000000
--- a/tutorial/ovn-setup.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash
-
-# 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 a 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
-
-ovs-vsctl add-port br-int p1 -- \
-    set Interface p1 external_ids:iface-id=sw0-port1
-ovs-vsctl add-port br-int p2 -- \
-    set Interface p2 external_ids:iface-id=sw1-port1
-
-# View a summary of the configuration
-printf "\n=== ovn-nbctl show ===\n\n"
-ovn-nbctl show
-printf "\n=== ovn-nbctl show with wait hv ===\n\n"
-ovn-nbctl --wait=hv show
-printf "\n=== ovn-sbctl show ===\n\n"
-ovn-sbctl show
diff --git a/tutorial/ovs-sandbox b/tutorial/ovs-sandbox
index 601d0381f..09e9773ce 100755
--- a/tutorial/ovs-sandbox
+++ b/tutorial/ovs-sandbox
@@ -56,27 +56,11 @@ gdb_vswitchd=false
 gdb_ovsdb=false
 gdb_vswitchd_ex=false
 gdb_ovsdb_ex=false
-gdb_ovn_northd=false
-gdb_ovn_northd_ex=false
-gdb_ovn_controller=false
-gdb_ovn_controller_ex=false
-gdb_ovn_controller_vtep=false
-gdb_ovn_controller_vtep_ex=false
 builddir=
 srcdir=
 schema=
 installed=false
 built=false
-ovn=false
-ovnsb_schema=
-ovnnb_schema=
-ovn_rbac=true
-n_northds=1
-n_controllers=1
-nbdb_model=standalone
-nbdb_servers=3
-sbdb_model=backup
-sbdb_servers=3
 dummy=override
 
 for option; do
@@ -120,23 +104,11 @@ These options force ovs-sandbox to use an installed Open vSwitch:
 General options:
   -g, --gdb-vswitchd   run ovs-vswitchd under gdb
   -d, --gdb-ovsdb      run ovsdb-server under gdb
-  --gdb-ovn-northd     run ovn-northd under gdb
-  --gdb-ovn-controller run ovn-controller under gdb
-  --gdb-ovn-controller-vtep run ovn-controller-vtep under gdb
   --dummy=ARG          pass --enable-dummy=ARG to vswitchd (default: override)
   -R, --gdb-run        automatically start running the daemon in gdb
                        for any daemon set to run under gdb
   -S, --schema=FILE    use FILE as vswitch.ovsschema
 
-OVN options:
-  -o, --ovn            enable OVN
-  --no-ovn-rbac        disable role-based access control for OVN
-  --n-northds=NUMBER   run NUMBER copies of northd (default: 1)
-  --nbdb-model=standalone|backup|clustered    northbound database model
-  --nbdb-servers=N     number of servers in nbdb cluster (default: 3)
-  --sbdb-model=standalone|backup|clustered    southbound database model
-  --sbdb-servers=N     number of servers in sbdb cluster (default: 3)
-
 Other options:
   -h, --help           Print this usage message.
 EOF
@@ -192,67 +164,9 @@ EOF
             gdb_ovsdb=true
             gdb_ovsdb_ex=true
             ;;
-        --gdb-ovn-northd)
-            gdb_ovn_northd=true
-            ;;
-        --gdb-ovn-controller)
-            gdb_ovn_controller=true
-            ;;
-        --gdb-ovn-controller-vtep)
-            gdb_ovn_controller_vtep=true
-            ;;
-        -o|--ovn)
-            ovn=true
-            ;;
-        --no-ovn-rbac)
-            ovn_rbac=false
-            ;;
-        --n-northd*=*)
-            n_northds=$optarg
-            ;;
-        --n-northd*)
-            prev=n_northds
-            ;;
-        --n-controller*=*)
-            n_controllers=$optarg
-            ;;
-        --n-controller*)
-            prev=n_controllers
-            ;;
-        --nbdb-s*=*)
-            nbdb_servers=$optarg
-            nbdb_model=clustered
-            ;;
-        --nbdb-s*)
-            prev=nbdb_servers
-            nbdb_model=clustered
-            ;;
-        --nbdb-m*=*)
-            nbdb_model=$optarg
-            ;;
-        --nbdb-m*)
-            prev=nbdb_model
-            ;;
-        --sbdb-s*=*)
-            sbdb_servers=$optarg
-            sbdb_model=clustered
-            ;;
-        --sbdb-s*)
-            prev=sbdb_servers
-            sbdb_model=clustered
-            ;;
-        --sbdb-m*=*)
-            sbdb_model=$optarg
-            ;;
-        --sbdb-m*)
-            prev=sbdb_model
-            ;;
         -R|--gdb-run)
             gdb_vswitchd_ex=true
             gdb_ovsdb_ex=true
-            gdb_ovn_northd_ex=true
-            gdb_ovn_controller_ex=true
-            gdb_ovn_controller_vtep_ex=true
             ;;
         -*)
             echo "unrecognized option $option (use --help for help)" >&2
@@ -304,23 +218,6 @@ if $built; then
         echo >&2 'source directory not found, please use --srcdir'
         exit 1
     fi
-    if $ovn; then
-        ovnsb_schema=$srcdir/ovn/ovn-sb.ovsschema
-        if test ! -e "$ovnsb_schema"; then
-            echo >&2 'source directory not found, please use --srcdir'
-            exit 1
-        fi
-        ovnnb_schema=$srcdir/ovn/ovn-nb.ovsschema
-        if test ! -e "$ovnnb_schema"; then
-            echo >&2 'source directory not found, please use --srcdir'
-            exit 1
-        fi
-        vtep_schema=$srcdir/vtep/vtep.ovsschema
-        if test ! -e "$vtep_schema"; then
-            echo >&2 'source directory not found, please use --srcdir'
-            exit 1
-        fi
-    fi
 
     # Put built tools early in $PATH.
     if test ! -e $builddir/vswitchd/ovs-vswitchd; then
@@ -328,9 +225,6 @@ if $built; then
         exit 1
     fi
     PATH=$builddir/ovsdb:$builddir/vswitchd:$builddir/utilities:$builddir/vtep:$PATH
-    if $ovn; then
-        PATH=$builddir/ovn/controller:$builddir/ovn/controller-vtep:$builddir/ovn/northd:$builddir/ovn/utilities:$PATH
-    fi
     export PATH
 else
     case $schema in
@@ -351,10 +245,6 @@ else
         echo "can't find vswitch.ovsschema, please specify --schema" >&2
         exit 1
     fi
-    if $ovn; then
-        echo "running with ovn is only supported from the build dir." >&2
-        exit 1
-    fi
 fi
 
 # Create sandbox.
@@ -381,109 +271,10 @@ trap 'kill `cat "$sandbox"/*.pid`' 0 1 2 3 13 14 15
 touch "$sandbox"/.conf.db.~lock~
 run ovsdb-tool create conf.db "$schema"
 ovsdb_server_args=
-if $ovn; then
-    touch "$sandbox"/.ovnnb.db.~lock~
-    run ovsdb-tool create ovnnb.db "$ovnnb_schema"
-    run ovsdb-tool create vtep.db "$vtep_schema"
-    ovsdb_server_args="vtep.db conf.db"
-    ovsdb_nb_server_args="ovnnb.db"
-
-    if [ "$HAVE_OPENSSL" = yes ]; then
-        OVS_PKI="run ovs-pki --dir=$sandbox/pki --log=$sandbox/ovs-pki.log"
-        $OVS_PKI init
-        $OVS_PKI req+sign ovnsb switch
-        $OVS_PKI req+sign ovnnb switch
-        for i in $(seq $n_controllers); do
-            $OVS_PKI -u req+sign chassis-$i switch
-        done
-    fi
-fi
 rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \
        --remote=punix:"$sandbox"/db.sock \
        --remote=db:Open_vSwitch,Open_vSwitch,manager_options \
        $ovsdb_server_args
-if $ovn; then
-    ovn_start_db() {
-        local db=$1 model=$2 servers=$3 schema=$4
-        local DB=$(echo $db | tr a-z A-Z)
-        local schema_name=$(ovsdb-tool schema-name $schema)
-
-        case $model in
-            standalone | backup) ;;
-            clustered)
-                case $servers in
-                    [1-9] | [1-9][0-9]) ;;
-                    *) echo "${db}db servers must be between 1 and 99" >&2
-                       exit 1
-                       ;;
-                esac
-                ;;
-            *)
-                echo "unknown ${db}db model \"$model\"" >&2
-                exit 1
-                ;;
-        esac
-
-        ovn_start_ovsdb_server() {
-            local i=$1; shift
-            rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir \
-                   --pidfile=$db$i.pid -vconsole:off --log-file=$db$i.log \
-                   -vsyslog:off \
-                   --remote=db:$schema_name,${DB}_Global,connections \
-                   --private-key=db:$schema_name,SSL,private_key \
-                   --certificate=db:$schema_name,SSL,certificate \
-                   --ca-cert=db:$schema_name,SSL,ca_cert \
-                   --ssl-protocols=db:$schema_name,SSL,ssl_protocols \
-                   --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \
-                   --unixctl=${db}$i --remote=punix:$db$i.ovsdb ${db}$i.db "$@"
-        }
-
-        case $model in
-            standalone)
-                run ovsdb-tool create ${db}1.db "$schema"
-                ovn_start_ovsdb_server 1
-                remote=unix:${db}1.ovsdb
-                ;;
-            backup)
-                for i in 1 2; do
-                    run ovsdb-tool create $db$i.db "$schema"
-                done
-                ovn_start_ovsdb_server 1
-                ovn_start_ovsdb_server 2 --sync-from=unix:${db}1.ovsdb
-                remote=unix:${db}1.ovsdb
-                backup_note="$backup_note
-The backup server of OVN $DB can be accessed by:
-* ovn-${db}ctl --db=unix:`pwd`/sandbox/${db}2.ovsdb
-* ovs-appctl -t `pwd`/sandbox/${db}2
-The backup database file is sandbox/${db}2.db
-"
-                ;;
-            clustered)
-                for i in $(seq $servers); do
-                    if test $i = 1; then
-                        run ovsdb-tool create-cluster ${db}1.db "$schema" unix:${db}1.raft;
-                    else
-                        run ovsdb-tool join-cluster $db$i.db $schema_name unix:$db$i.raft unix:${db}1.raft
-                    fi
-                    ovn_start_ovsdb_server $i
-                done
-                remote=unix:${db}1.ovsdb
-                for i in `seq 2 $servers`; do
-                    remote=$remote,unix:$db$i.ovsdb
-                done
-                for i in $(seq $servers); do
-                    run ovsdb-client wait unix:$db$i.ovsdb $schema_name connected
-                done
-                ;;
-        esac
-        eval OVN_${DB}_DB=\$remote
-        eval export OVN_${DB}_DB
-    }
-
-    backup_note=
-    ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$ovnnb_schema"
-    ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_schema"
-fi
 
 #Add a small delay to allow ovsdb-server to launch.
 sleep 0.1
@@ -504,50 +295,6 @@ run ovs-vsctl --no-wait -- init
 rungdb $gdb_vswitchd $gdb_vswitchd_ex ovs-vswitchd --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \
     --enable-dummy=$dummy -vvconn -vnetdev_dummy
 
-if $ovn; then
-    ovn-nbctl init
-    ovn-sbctl init
-
-    ovs-vsctl set open . external-ids:system-id=chassis-1
-    ovs-vsctl set open . external-ids:hostname=sandbox
-    ovs-vsctl set open . external-ids:ovn-encap-type=geneve
-    ovs-vsctl set open . external-ids:ovn-encap-ip=127.0.0.1
-
-    if [ "$HAVE_OPENSSL" = yes ]; then
-        ovn-nbctl set-ssl $sandbox/ovnnb-privkey.pem  $sandbox/ovnnb-cert.pem $sandbox/pki/switchca/cacert.pem
-        ovn-nbctl set-connection pssl:6641
-        ovn-sbctl set-ssl $sandbox/ovnsb-privkey.pem  $sandbox/ovnsb-cert.pem $sandbox/pki/switchca/cacert.pem
-        if $ovn_rbac; then
-            ovn-sbctl set-connection role=ovn-controller pssl:6642
-        else
-            ovn-sbctl set-connection pssl:6642
-        fi
-        ovs-vsctl set open . external-ids:ovn-remote=ssl:127.0.0.1:6642
-        OVN_CTRLR_PKI="-p $sandbox/chassis-1-privkey.pem -c $sandbox/chassis-1-cert.pem -C $sandbox/pki/switchca/cacert.pem"
-    else
-        ovs-vsctl set open . external-ids:ovn-remote=$OVN_SB_DB
-        OVN_CTRLR_PKI=""
-    fi
-    for i in $(seq $n_northds); do
-        if [ $i -eq 1 ]; then inst=""; else inst=$i; fi
-        rungdb $gdb_ovn_northd $gdb_ovn_northd_ex ovn-northd --detach \
-               --no-chdir --pidfile=ovn-northd${inst}.pid -vconsole:off \
-               --log-file=ovn-northd${inst}.log -vsyslog:off \
-               --ovnsb-db="$OVN_SB_DB" --ovnnb-db="$OVN_NB_DB"
-    done
-    for i in $(seq $n_controllers); do
-        if [ $i -eq 1 ]; then inst=""; else inst=$i; fi
-        rungdb $gdb_ovn_controller $gdb_ovn_controller_ex ovn-controller \
-               $OVN_CTRLR_PKI --detach --no-chdir -vsyslog:off \
-               --log-file=ovn-controller${inst}.log \
-               --pidfile=ovn-controller${inst}.pid -vconsole:off
-    done
-    rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \
-        ovn-controller-vtep --detach --no-chdir --pidfile -vconsole:off \
-        $OVN_CTRLR_PKI --log-file -vsyslog:off \
-        --ovnsb-db=unix:"$sandbox"/ovnsb_db.sock
-fi
-
 cat <<EOF
 
 
@@ -557,14 +304,6 @@ You are running in a dummy Open vSwitch environment.  You can use
 ovs-vsctl, ovs-ofctl, ovs-appctl, and other tools to work with the
 dummy switch.
 
-EOF
-if $ovn; then cat << EOF
-This environment also has the OVN daemons and databases enabled.
-You can use ovn-nbctl and ovn-sbctl to interact with the OVN databases.
-$backup_note
-EOF
-fi
-cat <<EOF
 Log files, pidfiles, and the configuration database are in the
 "sandbox" subdirectory.
 
diff --git a/utilities/bugtool/automake.mk b/utilities/bugtool/automake.mk
index 18fa3478e..40980b367 100644
--- a/utilities/bugtool/automake.mk
+++ b/utilities/bugtool/automake.mk
@@ -32,8 +32,7 @@ bugtoolpluginsdir = $(pkgdatadir)/bugtool-plugins
 INSTALL_DATA_LOCAL += bugtool-install-data-local
 bugtool-install-data-local:
 	for plugin in $(bugtool_plugins); do \
-	  stem=`echo "$$plugin" | sed 's,ovn/,,'`; \
-	  stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \
+	  stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \
 	  dir=`expr "$$stem" : '\(.*\)/[^/]*$$'`; \
 	  $(MKDIR_P) "$(DESTDIR)$(bugtoolpluginsdir)/$$dir"; \
 	  $(INSTALL_DATA) "$(srcdir)/$$plugin" "$(DESTDIR)$(bugtoolpluginsdir)/$$stem"; \
@@ -42,13 +41,11 @@ bugtool-install-data-local:
 UNINSTALL_LOCAL += bugtool-uninstall-local
 bugtool-uninstall-local:
 	for plugin in $(bugtool_plugins); do \
-	  stem=`echo "$$plugin" | sed 's,ovn/,,'`; \
-	  stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \
+	  stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \
 	  rm -f "$(DESTDIR)$(bugtoolpluginsdir)/$$stem"; \
 	done
 	for plugin in $(bugtool_plugins); do \
-	  stem=`echo "$$plugin" | sed 's,ovn/,,'`; \
-	  stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \
+	  stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \
 	  dir=`expr "$$stem" : '\(.*\)/[^/]*$$'`; \
 	  if [ ! -z "$$dir" ]; then \
 	    rm -rf "$(DESTDIR)$(bugtoolpluginsdir)/$$dir"; \
diff --git a/utilities/ovs-sim.in b/utilities/ovs-sim.in
index 47329da21..08957bdf4 100755
--- a/utilities/ovs-sim.in
+++ b/utilities/ovs-sim.in
@@ -70,7 +70,6 @@ fi
 
 # Put built tools early in $PATH.
 PATH=$sim_builddir/ovsdb:$sim_builddir/vswitchd:$sim_builddir/utilities:$PATH
-PATH=$sim_builddir/ovn/controller:$sim_builddir/ovn/northd:$sim_builddir/ovn/utilities:$PATH
 export PATH
 
 rm -rf sandbox
@@ -101,8 +100,6 @@ sim_setvars() {
 export -f sim_setvars
 
 ovs-vsctl () { command ovs-vsctl -vsyslog:off "$@"; }; export -f ovs-vsctl
-ovs-nbctl () { command ovs-nbctl -vsyslog:off "$@"; }; export -f ovs-nbctl
-ovs-sbctl () { command ovs-sbctl -vsyslog:off "$@"; }; export -f ovs-sbctl
 vtep-ctl () { command vtep-ctl -vsyslog:off "$@"; }; export -f vtep-ctl
 
 as() {
@@ -187,7 +184,7 @@ $FUNCNAME: create a new interconnection network
 usage: $FUNCNAME NETWORK
 
 where NETWORK is the name of the new network.  Interconnection networks
-are used with net_attach and ovn_attach.
+are used with net_attach.
 EOF
         return 0
     fi
@@ -235,234 +232,6 @@ EOF
 }
 export -f net_attach
 
-ovn_start_db() {
-    local db=$1 model=$2 servers=$3 schema=$4
-    local DB=$(echo $db | tr a-z A-Z)
-    local schema_name=$(ovsdb-tool schema-name $schema)
-
-    case $model in
-        standalone | backup) ;;
-        clustered)
-            case $servers in
-                [1-9] | [1-9][0-9]) ;;
-                *) echo "${db}db servers must be between 1 and 99" >&2
-                   exit 1
-                   ;;
-            esac
-            ;;
-        *)
-            echo "unknown ${db}db model \"$model\"" >&2
-            exit 1
-            ;;
-    esac
-
-    ovn_start_ovsdb_server() {
-        local i=$1; shift
-        as ${db}$i ovsdb-server --detach --no-chdir --pidfile=$db.pid \
-           -vsyslog:off -vconsole:off --log-file="$sim_base"/$db$i/$db.log \
-           --remote=db:$schema_name,${DB}_Global,connections \
-           --private-key=db:$schema_name,SSL,private_key \
-           --certificate=db:$schema_name,SSL,certificate \
-           --ca-cert=db:$schema_name,SSL,ca_cert \
-           --ssl-protocols=db:$schema_name,SSL,ssl_protocols \
-           --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \
-           --unixctl=${db} --remote=punix:$db.ovsdb \
-           "$sim_base"/$db$i/$db.db "$@"
-    }
-
-    ovn_prep_db() {
-        local i=$1
-        mkdir "$sim_base"/${db}$i
-        touch "$sim_base"/${db}$i/.$db.db.~lock~
-    }
-
-    local n_remotes=1
-    case $model in
-        standalone)
-            ovn_prep_db 1
-            ovsdb-tool create "$sim_base"/${db}1/$db.db "$schema"
-            ovn_start_ovsdb_server 1
-            ;;
-        backup)
-            for i in 1 2; do
-                ovn_prep_db $i
-                ovsdb-tool create "$sim_base"/$db$i/$db.db "$schema"
-            done
-            ovn_start_ovsdb_server 1
-            ovn_start_ovsdb_server 2 --sync-from=unix:"$sim_base"/${db}1/$db.ovsdb
-            cat <<EOF
-The backup server of OVN $DB can be accessed by:
-* ovn-${db}ctl --db=unix:$sim_base/${db}2/$db.ovsdb
-* ovs-appctl -t $sim_base/${db}2/${db}
-The backup database file is $sim_base/${db}2/$db.db
-EOF
-            ;;
-        clustered)
-            n_remotes=$servers
-            for i in $(seq $servers); do
-                ovn_prep_db $i
-                if test $i = 1; then
-                    ovsdb-tool create-cluster "$sim_base"/$db$i/$db.db "$schema" unix:"$sim_base"/$db$i/db.raft
-                else
-                    ovsdb-tool join-cluster "$sim_base"/$db$i/$db.db $schema_name unix:"$sim_base"/$db$i/db.raft unix:"$sim_base"/${db}1/db.raft
-                fi
-                ovn_start_ovsdb_server $i
-            done
-            for i in $(seq $servers); do
-                ovsdb-client wait unix:"$sim_base"/${db}$i/$db.ovsdb $schema_name connected
-            done
-            ;;
-    esac
-
-    remote=unix:"$sim_base"/${db}1/$db.ovsdb
-    for i in `seq 2 $n_remotes`; do
-        remote=$remote,unix:"$sim_base"/${db}$i/$db.ovsdb
-    done
-    eval OVN_${DB}_DB=\$remote
-    eval export OVN_${DB}_DB
-}
-export -f ovn_start_db
-
-ovn_start() {
-    local nbdb_model=standalone
-    local nbdb_servers=3
-    local sbdb_model=standalone
-    local sbdb_servers=3
-    local prev=
-    for option; do
-        # This option-parsing mechanism borrowed from a Autoconf-generated
-        # configure script under the following license:
-
-        # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
-        # 2002, 2003, 2004, 2005, 2006, 2009, 2013 Free Software Foundation, Inc.
-        # This configure script is free software; the Free Software Foundation
-        # gives unlimited permission to copy, distribute and modify it.
-
-        # If the previous option needs an argument, assign it.
-        if test -n "$prev"; then
-            eval $prev=\$option
-            prev=
-            continue
-        fi
-        case $option in
-            *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
-            *) optarg=yes ;;
-        esac
-
-        case $dashdash$option in
-            --)
-                dashdash=yes ;;
-            -h|--help)
-                cat <<EOF
-$FUNCNAME: start OVN central databases and daemons
-usage: $FUNCNAME [OPTION...]
-
-This creates and initializes the central OVN databases (northbound and
-southbound), starts their ovsdb-server daemons, and starts the ovn-northd
-daemon.
-
-Options:
-  --nbdb-model=standalone|backup|clustered    northbound database model
-  --nbdb-servers=N     number of servers in nbdb cluster (default: 3)
-  --sbdb-model=standalone|backup|clustered    southbound database model
-  --sbdb-servers=N     number of servers in sbdb cluster (default: 3)
-  -h, --help           Print this usage message.
-EOF
-                return
-                ;;
-
-            --nbdb-s*=*)
-                nbdb_servers=$optarg
-                nbdb_model=clustered
-                ;;
-            --nbdb-s*)
-                prev=nbdb_servers
-                nbdb_model=clustered
-                ;;
-            --nbdb-m*=*)
-                nbdb_model=$optarg
-                ;;
-            --nbdb-m*)
-                prev=nbdb_model
-                ;;
-            --sbdb-s*=*)
-                sbdb_servers=$optarg
-                sbdb_model=clustered
-                ;;
-            --sbdb-s*)
-                prev=sbdb_servers
-                sbdb_model=clustered
-                ;;
-            --sbdb-m*=*)
-                sbdb_model=$optarg
-                ;;
-            --sbdb-m*)
-                prev=sbdb_model
-                ;;
-            -*)
-                echo "unrecognized option $option (use --help for help)" >&2
-                return 1
-                ;;
-            *)
-                echo "$option: non-option arguments not supported (use --help for help)" >&2
-                return 1
-                ;;
-        esac
-        shift
-    done
-
-    if test -d ovn-sb || test -d ovn-nb; then
-        echo >&2 "OVN already started"
-        return 1
-    fi
-
-    ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$sim_srcdir"/ovn/ovn-nb.ovsschema
-    ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$sim_srcdir"/ovn/ovn-sb.ovsschema
-
-    ovn-nbctl init
-    ovn-sbctl init
-
-    mkdir "$sim_base"/northd
-    as northd ovn-northd --ovnnb-db="$OVN_NB_DB" --ovnsb-db="$OVN_SB_DB" \
-       $daemon_opts
-}
-export -f ovn_start
-
-ovn_attach() {
-    if test "$1" == --help; then
-        cat <<EOF
-$FUNCNAME: attach default sandbox to an interconnection network for OVN
-usage: $FUNCNAME NETWORK BRIDGE IP [MASKLEN]
-
-This starts by doing everything that net_attach does.  Then it configures the
-specified IP and MASKLEN (e.g. 192.168.0.1 and 24) on BRIDGE and starts
-and configures ovn-controller.
-
-MASKLEN defaults to 24 if it is not specified.
-EOF
-        return 0
-    fi
-    if test $# != 3 && test $# != 4; then
-        echo >&2 "$FUNCNAME: wrong number of arguments (use --help for help)"
-        return 1
-    fi
-
-    local net=$1 bridge=$2 ip=$3 masklen=${4-24}
-    net_attach $net $bridge || return $?
-
-    ovs-appctl netdev-dummy/ip4addr $bridge $ip/$masklen >/dev/null
-    ovs-appctl ovs/route/add $ip/$masklen $bridge > /dev/null
-    ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=$sandbox \
-        -- set Open_vSwitch . external-ids:ovn-remote=$OVN_SB_DB \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip\
-        -- add-br br-int \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-    ovn-controller --detach --no-chdir --pidfile -vconsole:off -vsyslog:off --log-file
-}
-export -f ovn_attach
-
 # Easy access to OVS manpages.
 mkdir $sim_base/man
 mandir=`cd $sim_base/man && pwd`
@@ -494,8 +263,8 @@ rc='
  ______________________________________________________________________
 |
 | You are running in a nested shell environment meant for Open vSwitch
-| and OVN testing in simulation.   The OVS manpages are available via
-| "man".  Please see ovs-sim(1) for more information.
+| testing in simulation.   The OVS manpages are available via "man".
+| Please see ovs-sim(1) for more information.
 |
 | Exit the shell to kill the running daemons and leave the simulation
 | environment.
diff --git a/xenserver/openvswitch-xen.spec.in b/xenserver/openvswitch-xen.spec.in
index ba3580836..cdc341dcc 100644
--- a/xenserver/openvswitch-xen.spec.in
+++ b/xenserver/openvswitch-xen.spec.in
@@ -456,7 +456,6 @@ exit 0
 /usr/share/openvswitch/scripts/ovs-ctl
 /usr/share/openvswitch/scripts/ovs-lib
 /usr/share/openvswitch/scripts/ovs-vtep
-/usr/share/openvswitch/scripts/ovndb-servers.ocf
 /usr/share/openvswitch/vswitch.ovsschema
 /usr/share/openvswitch/vtep.ovsschema
 /usr/sbin/ovs-bugtool
@@ -507,12 +506,6 @@ exit 0
 %exclude /usr/share/openvswitch/python/*.py[co]
 %exclude /usr/share/openvswitch/python/ovs/*.py[co]
 %exclude /usr/share/openvswitch/python/ovs/db/*.py[co]
-%exclude /usr/bin/ovn-*
-%exclude /usr/share/man/man5/ovn-*
-%exclude /usr/share/man/man7/ovn-*
-%exclude /usr/share/man/man8/ovn-*
-%exclude /usr/share/openvswitch/ovn-*
-%exclude /usr/share/openvswitch/scripts/ovn-*
 
 %files %{module_package}
 /lib/modules/%{xen_version}/extra/openvswitch/openvswitch.ko
