This commit adds a controller 'ovn-controller-gw' for controlling
vtep enabled physical switches.

Following are the short summary of each module:

gateway.c
=========

   register the physical switches in vtep and constantly update the
   gateway_ports entries in Chassis table and the vlan_map in Gateway table.

   the vlan_map value is the combination of "physical_switch_name+physical_port
   _name+vlan_number."

binding.c
=========

   scan through the Binding table, if there is a binding for the
   logical port in vlan_map, set the binding's chassis to the
   vtep switch.

pipeline.c
==========

   scan through the Binding table, and create the Ucast_Macs_Remote
   in VTEP database for each logical port MAC in the same logical datapath
   (logical switch for vtep).  also create the physical locators to each
   HV chassis in the same logical network.

unit tests
==========

   test the ovn-controller-gw using vtep device simulated via ovs-vtep.
   please check the tests/ovn-controller-gw.at for details.

Limitations:

-  How to make controller-gw connect to OVN_SB?

     my understanding is that vtep should not know the location of ovnsb.
     but there is no "external_ids" column in vtep schema.  and the manager
     for vtep should be the controller-gw.  so, the implementation requires
     user to specify both vtep, and ovnsb location from command line.

-  Assume each Chassis has only one Encap entry.  Need to extend it to
   find the vxlan encap entry.

-  Require logical switch name in VTEP be the same as logical datapath
   uuid in ovnsb db.  This provide the good consistency when creating
   the Ucast_Macs_Remote and Physical_Locators in vtep, since the
   logical switch name can be used directly to find the binding in
   ovn-sb.

Signed-off-by: Alex Wang <al...@nicira.com>
---
 ovn/automake.mk                       |    1 +
 ovn/controller-gw/.gitignore          |    1 +
 ovn/controller-gw/automake.mk         |   11 +
 ovn/controller-gw/binding.c           |  151 ++++++++++
 ovn/controller-gw/binding.h           |   25 ++
 ovn/controller-gw/gateway.c           |  388 ++++++++++++++++++++++++
 ovn/controller-gw/gateway.h           |   24 ++
 ovn/controller-gw/ovn-controller-gw.c |  272 +++++++++++++++++
 ovn/controller-gw/ovn-controller-gw.h |   45 +++
 ovn/controller-gw/pipeline.c          |  525 +++++++++++++++++++++++++++++++++
 ovn/controller-gw/pipeline.h          |   25 ++
 ovn/ovn-sb.xml                        |    3 +-
 tests/automake.mk                     |    6 +-
 tests/ovn-controller-gw.at            |  229 ++++++++++++++
 tests/testsuite.at                    |    1 +
 15 files changed, 1703 insertions(+), 4 deletions(-)
 create mode 100644 ovn/controller-gw/.gitignore
 create mode 100644 ovn/controller-gw/automake.mk
 create mode 100644 ovn/controller-gw/binding.c
 create mode 100644 ovn/controller-gw/binding.h
 create mode 100644 ovn/controller-gw/gateway.c
 create mode 100644 ovn/controller-gw/gateway.h
 create mode 100644 ovn/controller-gw/ovn-controller-gw.c
 create mode 100644 ovn/controller-gw/ovn-controller-gw.h
 create mode 100644 ovn/controller-gw/pipeline.c
 create mode 100644 ovn/controller-gw/pipeline.h
 create mode 100644 tests/ovn-controller-gw.at

diff --git a/ovn/automake.mk b/ovn/automake.mk
index 6d2063f..475817a 100644
--- a/ovn/automake.mk
+++ b/ovn/automake.mk
@@ -89,6 +89,7 @@ man_MANS += ovn/ovn-sbctl.8
 DISTCLEANFILES += ovn/ovn-sbctl.8
 
 include ovn/controller/automake.mk
+include ovn/controller-gw/automake.mk
 include ovn/lib/automake.mk
 include ovn/northd/automake.mk
 include ovn/utilities/automake.mk
diff --git a/ovn/controller-gw/.gitignore b/ovn/controller-gw/.gitignore
new file mode 100644
index 0000000..88ebb5c
--- /dev/null
+++ b/ovn/controller-gw/.gitignore
@@ -0,0 +1 @@
+/ovn-controller-gw
\ No newline at end of file
diff --git a/ovn/controller-gw/automake.mk b/ovn/controller-gw/automake.mk
new file mode 100644
index 0000000..356dfb5
--- /dev/null
+++ b/ovn/controller-gw/automake.mk
@@ -0,0 +1,11 @@
+bin_PROGRAMS += ovn/controller-gw/ovn-controller-gw
+ovn_controller_gw_ovn_controller_gw_SOURCES = \
+       ovn/controller-gw/binding.c \
+       ovn/controller-gw/binding.h \
+       ovn/controller-gw/gateway.c \
+       ovn/controller-gw/gateway.h \
+       ovn/controller-gw/pipeline.c \
+       ovn/controller-gw/pipeline.h \
+       ovn/controller-gw/ovn-controller-gw.c \
+       ovn/controller-gw/ovn-controller-gw.h
+ovn_controller_gw_ovn_controller_gw_LDADD = ovn/lib/libovn.la 
lib/libopenvswitch.la vtep/libvtep.la
diff --git a/ovn/controller-gw/binding.c b/ovn/controller-gw/binding.c
new file mode 100644
index 0000000..3d69635
--- /dev/null
+++ b/ovn/controller-gw/binding.c
@@ -0,0 +1,151 @@
+/* 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 "lib/hash.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-gw.h"
+
+VLOG_DEFINE_THIS_MODULE(binding);
+
+/* Checks and updates bindings for each physical switch in VTEP. */
+void
+binding_run(struct controller_gw_ctx *ctx)
+{
+    const struct vteprec_physical_switch *pswitch;
+    struct ovsdb_idl_txn *txn;
+    int retval;
+
+    txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+    ovsdb_idl_txn_add_comment(txn,
+                              "ovn-controller-gw: updating bindings");
+
+    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
+        const struct sbrec_chassis *chassis_rec
+            = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
+        const struct sbrec_binding *binding_rec;
+        struct sset lports;
+        const char *name;
+        int i;
+
+        sset_init(&lports);
+        /* Collects all logical ports of the gateway. */
+        for (i = 0; i < chassis_rec->n_gateway_ports; i++) {
+            const struct sbrec_gateway *gw_rec = 
chassis_rec->value_gateway_ports[i];
+            int j;
+
+            for (j = 0; j < gw_rec->n_vlan_map; j++) {
+                sset_add(&lports, gw_rec->value_vlan_map[j]);
+            }
+        }
+
+        SBREC_BINDING_FOR_EACH(binding_rec, ctx->ovnsb_idl) {
+            if (sset_find_and_delete(&lports, binding_rec->logical_port)) {
+                if (binding_rec->chassis == chassis_rec) {
+                    continue;
+                }
+                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);
+                }
+                sbrec_binding_set_chassis(binding_rec, chassis_rec);
+            } else if (binding_rec->chassis == chassis_rec) {
+                sbrec_binding_set_chassis(binding_rec, NULL);
+            }
+        }
+        SSET_FOR_EACH (name, &lports) {
+            VLOG_DBG("No binding record for lport %s", name);
+        }
+        sset_destroy(&lports);
+    }
+
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval == TXN_ERROR) {
+        VLOG_INFO("Problem committing binding information: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+    }
+    ovsdb_idl_txn_destroy(txn);
+}
+
+/* Removes the chassis reference for each gw related binding. */
+void
+binding_destroy(struct controller_gw_ctx *ctx)
+{
+    struct hmap bindings_with_chassis = 
HMAP_INITIALIZER(&bindings_with_chassis);
+    const struct sbrec_binding *binding_rec;
+    int retval = TXN_TRY_AGAIN;
+
+    struct bd {
+        struct hmap_node hmap_node;  /* Inside 'bindings_with_chassis'. */
+
+        const struct sbrec_binding *binding;
+    };
+
+    /* Collects all bindings with chassis, this is to avoid multiple
+     * linear iterations of Binding table. */
+    SBREC_BINDING_FOR_EACH(binding_rec, ctx->ovnsb_idl) {
+        if (binding_rec->chassis) {
+            struct bd *bd = xmalloc(sizeof *bd);
+
+            bd->binding = binding_rec;
+            hmap_insert(&bindings_with_chassis, &bd->hmap_node,
+                        hash_string(binding_rec->chassis->name, 0));
+        }
+    }
+
+    while (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        const struct vteprec_physical_switch *pswitch;
+        struct ovsdb_idl_txn *txn;
+
+        txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+        ovsdb_idl_txn_add_comment(txn, "ovn-controller-gw: removing bindings");
+
+        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 bd *bd;
+
+            HMAP_FOR_EACH_WITH_HASH (bd, hmap_node,
+                                     hash_string(chassis_rec->name, 0),
+                                     &bindings_with_chassis) {
+                if (!strcmp(bd->binding->chassis->name, chassis_rec->name)) {
+                    sbrec_binding_set_chassis(bd->binding, NULL);
+                }
+            }
+        }
+
+        retval = ovsdb_idl_txn_commit_block(txn);
+        if (retval == TXN_ERROR) {
+            VLOG_INFO("Problem removing binding: %s",
+                      ovsdb_idl_txn_status_to_string(retval));
+        }
+        ovsdb_idl_txn_destroy(txn);
+    }
+
+    struct bd *iter, *next;
+
+    HMAP_FOR_EACH_SAFE (iter, next, hmap_node, &bindings_with_chassis) {
+        hmap_remove(&bindings_with_chassis, &iter->hmap_node);
+        free(iter);
+    }
+    hmap_destroy(&bindings_with_chassis);
+}
diff --git a/ovn/controller-gw/binding.h b/ovn/controller-gw/binding.h
new file mode 100644
index 0000000..3d8bd19
--- /dev/null
+++ b/ovn/controller-gw/binding.h
@@ -0,0 +1,25 @@
+/* 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
+
+struct controller_gw_ctx;
+
+void binding_run(struct controller_gw_ctx *);
+void binding_destroy(struct controller_gw_ctx *);
+
+#endif /* ovn/controller-gw/binding.h */
diff --git a/ovn/controller-gw/gateway.c b/ovn/controller-gw/gateway.c
new file mode 100644
index 0000000..caf5ad8
--- /dev/null
+++ b/ovn/controller-gw/gateway.c
@@ -0,0 +1,388 @@
+/* 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 "lib/hash.h"
+#include "lib/hmap.h"
+#include "lib/poll-loop.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-gw.h"
+
+VLOG_DEFINE_THIS_MODULE(gateway);
+
+/* Global revalidation sequence number, incremented at each call to
+ * 'revalidate_gateway()'. */
+static uint64_t gw_reval_seq;
+
+/* Represents a chassis added by the gateway module.  The 'reval_seq'
+ * is increment each time a chassis is revalidated.  Chassis whose 'reval_seq'
+ * not equal to 'gw_reval_seq' will be removed. */
+struct gw_chassis {
+    struct hmap_node hmap_node; /* In 'gw_chassis'. */
+    char *name;                 /* Name of the Chassis. */
+    uint64_t reval_seq;         /* Chassis revalidation sequence number. */
+};
+
+/* Contains all chassis created by the gateway module. */
+static struct hmap gw_chassis_map = HMAP_INITIALIZER(&gw_chassis_map);
+
+/* Searchs 'gw_chassis_map' for chassis 'name' and returns the pointer.
+ * Returns NULL, if the chassis is not found. */
+static struct gw_chassis *
+get_gw_chassis(const char *name)
+{
+    struct gw_chassis *gw_chassis;
+
+    HMAP_FOR_EACH_WITH_HASH (gw_chassis, hmap_node, hash_string(name, 0),
+                             &gw_chassis_map) {
+        if (!strcmp(gw_chassis->name, name)) {
+            return gw_chassis;
+        }
+    }
+
+    return NULL;
+}
+
+/* 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;
+
+    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);
+    sbrec_chassis_set_encaps(chassis_rec, &encap_rec, 1);
+
+    return chassis_rec;
+}
+
+/* Revalidates chassis in ovnsb against vtep.  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_gw_ctx *ctx)
+{
+    const struct vteprec_physical_switch *pswitch;
+    struct ovsdb_idl_txn *txn;
+    struct gw_chassis *iter, *next;
+    int retval;
+
+    /* Increments the global revalidation sequence number. */
+    gw_reval_seq++;
+
+    txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+    ovsdb_idl_txn_add_comment(txn, "ovn-controller-gw: updating vtep chassis");
+
+    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
+        const struct sbrec_chassis *chassis_rec;
+        struct gw_chassis *gw_chassis;
+        const char *encap_ip;
+
+        encap_ip = pswitch->n_tunnel_ips ? pswitch->tunnel_ips[0] : "";
+        gw_chassis = get_gw_chassis(pswitch->name);
+        chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
+        if (gw_chassis && !chassis_rec) {
+            VLOG_WARN("Chassis for VTEP physical switch (%s) disappears, "
+                      "maybe deleted by ovn-sbctl, adding it back",
+                      pswitch->name);
+            create_chassis_rec(txn, pswitch->name, encap_ip);
+        } else if (!gw_chassis && chassis_rec) {
+            VLOG_WARN("Chassis for new VTEP physical switch (%s) has already "
+                      "been added, maybe by ovn-sbctl", pswitch->name);
+            if (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);
+                VLOG_WARN("Skip adding chassis for physical switch (%s)",
+                          pswitch->name);
+                continue;
+            }
+            gw_chassis = xmalloc(sizeof *gw_chassis);
+            gw_chassis->name = xstrdup(pswitch->name);
+            hmap_insert(&gw_chassis_map, &gw_chassis->hmap_node,
+                        hash_string(gw_chassis->name, 0));
+        } else if (gw_chassis && chassis_rec) {
+            /* Updates chassis's encap if anything changed. */
+            if (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE)) {
+                VLOG_WARN("Chassis for 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);
+            }
+        } else {
+            /* Creates a new chassis for the VTEP physical switch and a new
+             * gw_chassis record. */
+            create_chassis_rec(txn, pswitch->name, encap_ip);
+            gw_chassis = xmalloc(sizeof *gw_chassis);
+            gw_chassis->name = xstrdup(pswitch->name);
+            hmap_insert(&gw_chassis_map, &gw_chassis->hmap_node,
+                        hash_string(gw_chassis->name, 0));
+        }
+        /* Updates the 'gw_chassis's revalidation seq number to prevent
+         * it from being garbage collected. */
+        gw_chassis->reval_seq = gw_reval_seq;
+    }
+
+    /* For 'gw_chassis' in 'gw_chassis_map' whose reval_seq is not
+     * 'gw_chassis_map', it means the corresponding physical switch no
+     * longer exist.  So, garbage collects them. */
+    HMAP_FOR_EACH_SAFE (iter, next, hmap_node, &gw_chassis_map) {
+        if (iter->reval_seq != 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);
+            }
+            hmap_remove(&gw_chassis_map, &iter->hmap_node);
+            free(iter->name);
+            free(iter);
+        }
+    }
+
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        VLOG_INFO("Problem registering chassis: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+        poll_immediate_wake();
+    }
+    ovsdb_idl_txn_destroy(txn);
+}
+
+/* Updates the gateway_ports map in chassis table based on physical
+ * port configuration of each VTEP physical switch. */
+static void
+update_physical_ports(struct controller_gw_ctx *ctx)
+{
+    const struct vteprec_physical_switch *pswitch;
+    struct ovsdb_idl_txn *txn;
+    int retval;
+
+    txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+    ovsdb_idl_txn_add_comment(txn, "ovn-controller-gw: updating physcial 
ports");
+
+    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 sset = SSET_INITIALIZER(&sset);
+        struct sbrec_gateway **gws;
+        char **pp_names;
+        bool changed = false;
+        int idx = 0;
+        int i;
+
+        /* Collects all physical ports from physical switch. */
+        for (i = 0; i < pswitch->n_ports; i++) {
+            sset_add(&sset, pswitch->ports[i]->name);
+        }
+
+        gws = xmalloc(sizeof *gws * pswitch->n_ports);
+        pp_names = xmalloc(sizeof *pp_names * pswitch->n_ports);
+        /* Keeps existing rows. */
+        for (i = 0; i < chassis_rec->n_gateway_ports; i++) {
+            if (sset_find_and_delete(&sset, 
chassis_rec->key_gateway_ports[i])) {
+                pp_names[i] = xstrdup(chassis_rec->key_gateway_ports[i]);
+                gws[i] = chassis_rec->value_gateway_ports[i];
+                idx = i + 1;
+            } else {
+                /* Finds a deleted entry, reports changed. */
+                changed = true;
+            }
+        }
+
+        /* Deletes non-existing rows and adds new rows.  */
+        if (changed || !sset_is_empty(&sset)) {
+            const char *iter;
+
+            /* Adds new rows. */
+            SSET_FOR_EACH (iter, &sset) {
+                pp_names[idx] = xstrdup(iter);
+                gws[idx] = sbrec_gateway_insert(txn);
+                idx++;
+            }
+            sbrec_chassis_set_gateway_ports(chassis_rec,
+                                            (const char **)pp_names,
+                                            gws, pswitch->n_ports);
+        }
+
+        for (i = 0; i < idx; i++) {
+            free(pp_names[i]);
+        }
+        free(pp_names);
+        free(gws);
+        sset_destroy(&sset);
+    }
+
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        VLOG_INFO("Problem registering chassis: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+        poll_immediate_wake();
+    }
+    ovsdb_idl_txn_destroy(txn);
+}
+
+/* Updates the vlan_map in gateway table based on vlan bindings
+ * of physical ports. */
+static void
+update_vlan_map(struct controller_gw_ctx *ctx)
+{
+    const struct vteprec_physical_switch *pswitch;
+    struct ovsdb_idl_txn *txn;
+    int retval;
+
+    txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+    ovsdb_idl_txn_add_comment(txn,
+                              "ovn-controller-gw: updating vlan map");
+
+    struct pp {
+        struct hmap_node hmap_node;  /* Inside 'physical_ports'. */
+
+        struct vteprec_physical_port *pp;
+    };
+
+    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 hmap physical_ports = HMAP_INITIALIZER(&physical_ports);
+        int i;
+
+        /* Collects 'physical_port's from pswitch, to avoid multiple
+         * linear searches. */
+        for (i = 0; i < pswitch->n_ports; i++) {
+            struct pp *pp = xmalloc(sizeof *pp);
+
+            pp->pp = pswitch->ports[i];
+            hmap_insert(&physical_ports, &pp->hmap_node,
+                        hash_string(pp->pp->name, 0));
+        }
+
+        /* Checks each row in Gateway table. */
+        for (i = 0; i < chassis_rec->n_gateway_ports; i++) {
+            struct sbrec_gateway *gw = chassis_rec->value_gateway_ports[i];
+            char *port_name = chassis_rec->key_gateway_ports[i];
+            struct pp *pp = NULL;
+            struct pp *iter;
+
+            HMAP_FOR_EACH_WITH_HASH (iter, hmap_node,
+                                     hash_string(port_name, 0),
+                                     &physical_ports) {
+                if (!strcmp(iter->pp->name, port_name)) {
+                    pp = iter;
+                    break;
+                }
+            }
+            /* 'pp' must exist, since the update_physical_ports() already
+             * cleans up all non-exist ports. */
+            ovs_assert(pp);
+
+            /* If length or content is different, we need to recreate
+             * the 'vlan_map' in 'gw'. */
+            if (gw->n_vlan_map != pp->pp->n_vlan_bindings
+                || memcmp(gw->key_vlan_map, pp->pp->key_vlan_bindings, 
gw->n_vlan_map)) {
+                int64_t *vlans = xmalloc(sizeof *vlans * 
pp->pp->n_vlan_bindings);
+                char **lports = xmalloc(sizeof *lports * 
pp->pp->n_vlan_bindings);
+                int j;
+
+                for (j = 0; j < pp->pp->n_vlan_bindings; j++) {
+                    vlans[j] = pp->pp->key_vlan_bindings[j];
+                    lports[j] = xasprintf("%s_%s_%"PRId64, pswitch->name,
+                                          pp->pp->name, vlans[j]);
+                }
+                sbrec_gateway_set_vlan_map(gw, vlans, (const char **)lports,
+                                           pp->pp->n_vlan_bindings);
+
+                for (j = 0; j < pp->pp->n_vlan_bindings; j++) {
+                    free(lports[j]);
+                }
+                free(lports);
+                free(vlans);
+            }
+            hmap_remove(&physical_ports, &pp->hmap_node);
+            free(pp);
+        }
+        /* hmap must be empty, otherwise the update_physical_port() is
+         * buggy. */
+        ovs_assert(hmap_is_empty(&physical_ports));
+        hmap_destroy(&physical_ports);
+    }
+
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        VLOG_INFO("Problem registering chassis: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+        poll_immediate_wake();
+    }
+    ovsdb_idl_txn_destroy(txn);
+}
+
+
+void
+gateway_run(struct controller_gw_ctx *ctx)
+{
+    revalidate_gateway(ctx);
+    update_physical_ports(ctx);
+    update_vlan_map(ctx);
+}
+
+void
+gateway_destroy(struct controller_gw_ctx *ctx)
+{
+    int retval = TXN_TRY_AGAIN;
+
+    ovs_assert(ctx->ovnsb_idl);
+    while (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        const struct vteprec_physical_switch *pswitch;
+        struct ovsdb_idl_txn *txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
+
+        ovsdb_idl_txn_add_comment(txn,
+                                  "ovn-controller-gw: 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;
+            }
+            sbrec_chassis_delete(chassis_rec);
+        }
+        retval = ovsdb_idl_txn_commit_block(txn);
+        if (retval == TXN_ERROR) {
+            VLOG_INFO("Problem unregistering chassis: %s",
+                      ovsdb_idl_txn_status_to_string(retval));
+        }
+        ovsdb_idl_txn_destroy(txn);
+    }
+}
diff --git a/ovn/controller-gw/gateway.h b/ovn/controller-gw/gateway.h
new file mode 100644
index 0000000..97c8327
--- /dev/null
+++ b/ovn/controller-gw/gateway.h
@@ -0,0 +1,24 @@
+/* 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
+
+struct controller_gw_ctx;
+
+void gateway_run(struct controller_gw_ctx *);
+void gateway_destroy(struct controller_gw_ctx *);
+
+#endif /* ovn/controller-gw/gateway.h */
diff --git a/ovn/controller-gw/ovn-controller-gw.c 
b/ovn/controller-gw/ovn-controller-gw.c
new file mode 100644
index 0000000..589e03e
--- /dev/null
+++ b/ovn/controller-gw/ovn-controller-gw.c
@@ -0,0 +1,272 @@
+/* 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 <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 "dynamic-string.h"
+#include "openvswitch/vconn.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lib/ovn-sb-idl.h"
+#include "poll-loop.h"
+#include "fatal-signal.h"
+#include "vtep/vtep-idl.h"
+#include "smap.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "unixctl.h"
+#include "util.h"
+
+#include "binding.h"
+#include "gateway.h"
+#include "pipeline.h"
+#include "ovn-controller-gw.h"
+
+VLOG_DEFINE_THIS_MODULE(ovn_gw);
+
+static unixctl_cb_func ovn_controller_gw_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 void
+get_initial_snapshot(struct ovsdb_idl *idl)
+{
+    while (1) {
+        ovsdb_idl_run(idl);
+        if (ovsdb_idl_has_ever_connected(idl)) {
+            return;
+        }
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct unixctl_server *unixctl;
+    struct controller_gw_ctx ctx;
+    bool exiting;
+    int retval;
+
+    ovs_cmdl_proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    parse_options(argc, argv);
+    fatal_ignore_sigpipe();
+
+    daemonize_start();
+
+    retval = unixctl_server_create(NULL, &unixctl);
+    if (retval) {
+        exit(EXIT_FAILURE);
+    }
+    unixctl_command_register("exit", "", 0, 0, ovn_controller_gw_exit,
+                             &exiting);
+
+    daemonize_complete();
+
+    vteprec_init();
+    sbrec_init();
+
+    /* Connect to VTEP OVSDB instance.  We monitor all tables by
+     * default */
+    ctx.vtep_idl = ovsdb_idl_create(vtep_remote, &vteprec_idl_class, true, 
true);
+    get_initial_snapshot(ctx.vtep_idl);
+    ctx.ovnsb_idl = ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class,
+                                     true, true);
+    get_initial_snapshot(ctx.ovnsb_idl);
+
+    ovs_assert(vteprec_global_first(ctx.vtep_idl));
+
+    exiting = false;
+    while (!exiting) {
+        ovsdb_idl_run(ctx.vtep_idl);
+        ovsdb_idl_run(ctx.ovnsb_idl);
+
+        if (!ovsdb_idl_is_alive(ctx.ovnsb_idl)) {
+            int retval = ovsdb_idl_get_last_error(ctx.ovnsb_idl);
+            VLOG_ERR("%s: database connection failed (%s)",
+                     ovnsb_remote, ovs_retval_to_string(retval));
+            retval = EXIT_FAILURE;
+            break;
+        }
+
+        if (!ovsdb_idl_is_alive(ctx.vtep_idl)) {
+            int retval = ovsdb_idl_get_last_error(ctx.vtep_idl);
+            VLOG_ERR("%s: database connection failed (%s)",
+                     vtep_remote, ovs_retval_to_string(retval));
+            retval = EXIT_FAILURE;
+            break;
+        }
+
+        gateway_run(&ctx);
+        binding_run(&ctx);
+        pipeline_run(&ctx);
+        unixctl_server_run(unixctl);
+
+        unixctl_server_wait(unixctl);
+        if (exiting) {
+            poll_immediate_wake();
+        }
+
+        ovsdb_idl_wait(ctx.vtep_idl);
+        ovsdb_idl_wait(ctx.ovnsb_idl);
+        poll_block();
+    }
+
+    unixctl_server_destroy(unixctl);
+    gateway_destroy(&ctx);
+    binding_destroy(&ctx);
+    pipeline_destroy(&ctx);
+
+    ovsdb_idl_destroy(ctx.vtep_idl);
+    ovsdb_idl_destroy(ctx.ovnsb_idl);
+
+    free(ovnsb_remote);
+    free(vtep_remote);
+
+    exit(retval);
+}
+
+static char *
+default_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = xasprintf("unix:%s/db.sock", ovs_rundir());
+    }
+    return def;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_PEER_CA_CERT = UCHAR_MAX + 1,
+        VLOG_OPTION_ENUMS,
+        DAEMON_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},
+        {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 = optarg;
+            break;
+
+        case 'D':
+            vtep_remote = 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 '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    if (!ovnsb_remote) {
+        ovnsb_remote = default_db();
+    }
+
+    if (!vtep_remote) {
+        vtep_remote = default_db();
+    }
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: OVN controller GW\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_db());
+    stream_usage("database", true, false, false);
+    daemon_usage();
+    vlog_usage();
+    exit(EXIT_SUCCESS);
+}
+
+
+static void
+ovn_controller_gw_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-gw/ovn-controller-gw.h 
b/ovn/controller-gw/ovn-controller-gw.h
new file mode 100644
index 0000000..53eed21
--- /dev/null
+++ b/ovn/controller-gw/ovn-controller-gw.h
@@ -0,0 +1,45 @@
+/* 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_GW_H
+#define OVN_CONTROLLER_GW_H 1
+
+#include "ovn/lib/ovn-sb-idl.h"
+
+struct controller_gw_ctx {
+    struct ovsdb_idl *ovnsb_idl;
+    struct ovsdb_idl *vtep_idl;
+};
+
+/* 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, 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-gw.h */
diff --git a/ovn/controller-gw/pipeline.c b/ovn/controller-gw/pipeline.c
new file mode 100644
index 0000000..9d2bf54
--- /dev/null
+++ b/ovn/controller-gw/pipeline.c
@@ -0,0 +1,525 @@
+/* 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 "pipeline.h"
+
+#include "dynamic-string.h"
+#include "lib/hash.h"
+#include "lib/poll-loop.h"
+#include "lib/sset.h"
+#include "lib/util.h"
+#include "lib/uuid.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lib/ovn-sb-idl.h"
+#include "vtep/vtep-idl.h"
+#include "ovn-controller-gw.h"
+
+VLOG_DEFINE_THIS_MODULE(pipeline);
+
+/* One for each 'mac' in the binding. */
+struct mac_to_binding {
+    struct hmap_node hmap_node;
+
+    const char *mac;
+    const struct sbrec_binding *binding;
+};
+
+/* Stores all info related to one logical switch to program vtep. */
+struct lswitch {
+    struct hmap_node hmap_node;
+
+    /* logical switch reference in vtep. */
+    const struct vteprec_logical_switch *lswitch;
+    /* corresponding logical datapath uuid in ovn-sb. */
+    struct uuid ldp_uuid;
+    struct hmap bindings;       /* stores 'struct mac_to_binding's. */
+};
+
+/* An 'Ucast_Macs_Remote' entry in vtep. */
+struct ucast_macs_rmt {
+    struct hmap_node hmap_node;
+
+    const struct vteprec_ucast_macs_remote *umr;
+};
+
+/* An 'Physical_Locator' entry in vtep. */
+struct physical_locator {
+    struct hmap_node hmap_node;
+
+    const struct vteprec_physical_locator *pl;
+};
+
+
+/* Returns true if the 'binding's logical datapath has already been
+ * stored in 'ls_map', also saves found record in 'lsp'.  Otherwise,
+ * returns false.*/
+static bool
+get_lswitch_from_binding(struct hmap *ls_map,
+                         const struct sbrec_binding *binding,
+                         struct lswitch **lsp)
+{
+    struct lswitch *ls;
+
+    HMAP_FOR_EACH_WITH_HASH (ls, hmap_node,
+                             uuid_hash(&binding->logical_datapath), ls_map) {
+        if (uuid_equals(&ls->ldp_uuid, &binding->logical_datapath)) {
+            *lsp = ls;
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Finds and returns the reference to logical switch in vtep with same name
+ * as 'uuid'. */
+static const struct vteprec_logical_switch *
+find_vtep_lswitch(struct controller_gw_ctx *ctx, const struct uuid *uuid)
+{
+    const struct vteprec_logical_switch *vlswitch;
+
+    VTEPREC_LOGICAL_SWITCH_FOR_EACH (vlswitch, ctx->vtep_idl) {
+        struct uuid uuid_;
+
+        uuid_from_string(&uuid_, vlswitch->name);
+        if (!memcmp(uuid, &uuid_, sizeof *uuid)) {
+            return vlswitch;
+        }
+    }
+    return NULL;
+}
+
+/* Finds and returns the reference to 'struct lswitch' with the same name. */
+static const struct lswitch *
+find_struct_lswitch(const struct hmap *ls_map,
+                    const struct vteprec_logical_switch *vlswitch)
+{
+    struct lswitch *ls;
+    struct uuid uuid_;
+
+    uuid_from_string(&uuid_, vlswitch->name);
+    HMAP_FOR_EACH_WITH_HASH (ls, hmap_node, uuid_hash(&uuid_), ls_map) {
+        if (uuid_equals(&ls->ldp_uuid, &uuid_)) {
+            return ls;
+        }
+    }
+
+    return NULL;
+}
+
+/* Returns true if the logical switch containing the logical port represented
+ * by 'binding_rec' is also defined in VTEP. */
+static bool
+binding_pipeline_use_vtep(struct controller_gw_ctx *ctx,
+                          const struct sbrec_binding *binding)
+{
+    return find_vtep_lswitch(ctx, &binding->logical_datapath)
+           ? true : false;
+}
+
+/* If there is already an 'mb' entry for 'mac' in 'ls->bindings',
+ * returns the true and sets 'mbp' to 'mb'.  Otherwise, returns NULL
+ * and leaves 'mbp' unchanged. */
+static bool
+get_mb_from_lswitch(const struct lswitch *ls, const char *mac,
+                    struct mac_to_binding **mbp)
+{
+    struct mac_to_binding *mb;
+
+    HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash_string(mac, 0),
+                             &ls->bindings) {
+        if (!strcmp(mac, mb->mac)) {
+            *mbp = mb;
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Creates an entry in 'ls->bindings' for each mac in 'binding'. */
+static void
+add_binding_macs(struct lswitch *ls, const struct sbrec_binding *binding)
+{
+    int i;
+
+    for (i = 0; i < binding->n_mac; i++) {
+        struct mac_to_binding *mb;
+        char *mac = binding->mac[i];
+
+        /* Duplication check. */
+        /* TODO: It is actually possible to have duplicate MACs. */
+        if (get_mb_from_lswitch(ls, mac, &mb)) {
+            VLOG_ERR("MAC address (%s) already exists in logical "
+                     "switch (%s)", mac, ls->lswitch->name);
+            continue;
+        } else {
+            mb = xmalloc(sizeof *mb);
+            mb->mac = mac;
+            mb->binding = binding;
+            hmap_insert(&ls->bindings, &mb->hmap_node, hash_string(mb->mac, 
0));
+        }
+    }
+}
+
+/* Destroys the 'ls'. */
+static void
+lswitch_destroy(struct lswitch *ls)
+{
+    struct mac_to_binding *mb, *next;
+
+    HMAP_FOR_EACH_SAFE (mb, next, hmap_node, &ls->bindings) {
+        hmap_remove(&ls->bindings, &mb->hmap_node);
+        free(mb);
+    }
+}
+
+/* Destroys the 'ls_map' containing 'struct lswitch *'. */
+static void
+lswitch_map_destroy(struct hmap *ls_map)
+{
+    struct lswitch *ls, *next;
+
+    HMAP_FOR_EACH_SAFE (ls, next, hmap_node, ls_map) {
+        hmap_remove(ls_map, &ls->hmap_node);
+        lswitch_destroy(ls);
+        free(ls);
+
+    }
+    hmap_destroy(ls_map);
+}
+
+/* Creates and adds an 'struct ucast_macs_rmt' entry to 'umr_map'. */
+static void
+ucast_macs_rmt_map_add(const struct vteprec_ucast_macs_remote *vteprec_umr,
+                       struct hmap *umr_map)
+{
+    struct ucast_macs_rmt *umr = xmalloc(sizeof *umr);
+
+    umr->umr = vteprec_umr;
+    hmap_insert(umr_map, &umr->hmap_node, hash_string(vteprec_umr->MAC, 0));
+}
+
+/* Destroys the 'umr_map' containing 'struct ucast_macs_remote *'. */
+static void
+ucast_macs_rmt_map_destory(struct hmap *umr_map)
+{
+    struct ucast_macs_rmt *umr, *next;
+
+    HMAP_FOR_EACH_SAFE (umr, next, hmap_node, umr_map) {
+        hmap_remove(umr_map, &umr->hmap_node);
+        free(umr);
+    }
+    hmap_destroy(umr_map);
+}
+
+/* Creates and adds an 'struct physical_locator' entry to 'pl_map'. */
+static void
+physical_locator_map_add(const struct vteprec_physical_locator *vteprec_pl,
+                         struct hmap *pl_map)
+{
+    struct physical_locator *pl = xmalloc(sizeof *pl);
+
+    pl->pl = vteprec_pl;
+    hmap_insert(pl_map, &pl->hmap_node, hash_string(vteprec_pl->dst_ip, 0));
+}
+
+/* Destroys the 'pl_map' containing 'struct physical_locator *'. */
+static void
+physical_locator_map_destory(struct hmap *pl_map)
+{
+    struct physical_locator *pl, *next;
+
+    HMAP_FOR_EACH_SAFE (pl, next, hmap_node, pl_map) {
+        hmap_remove(pl_map, &pl->hmap_node);
+        free(pl);
+    }
+    hmap_destroy(pl_map);
+}
+
+/* Returns true if there is 'umr' with same 'mac' in 'umr_map'
+ * and sets the 'umrp'.  Returns false otherwise. */
+static bool
+get_umr_by_mac(struct hmap *umr_map, const char *mac,
+               struct ucast_macs_rmt **umrp)
+{
+    struct ucast_macs_rmt *umr;
+
+    HMAP_FOR_EACH_WITH_HASH (umr, hmap_node, hash_string(mac, 0), umr_map) {
+        if (!strcmp(mac, umr->umr->MAC)) {
+            *umrp = umr;
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Returns true if there is 'pl' with same 'ip' in 'pl_map'
+ * and sets the 'plp'.  Returns false otherwise. */
+static bool
+get_pl_by_ip(struct hmap *pl_map, const char *ip,
+             struct physical_locator **plp)
+{
+    struct physical_locator *pl;
+
+    HMAP_FOR_EACH_WITH_HASH (pl, hmap_node, hash_string(ip, 0), pl_map) {
+        if (!strcmp(ip, pl->pl->dst_ip)) {
+            *plp = pl;
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/* Collects and categorizes bindings from ovnsb.  Returns a hmap
+ * containing 'struct lswitch's for bindings in the same logical
+ * switch as the vtep. */
+static struct hmap *
+collect_bindings(struct controller_gw_ctx *ctx)
+{
+    struct hmap *vtep_lswitches, *other_lswitches;
+    const struct sbrec_binding *binding_rec;
+
+    vtep_lswitches = xmalloc(sizeof *vtep_lswitches);
+    other_lswitches = xmalloc(sizeof *other_lswitches);
+    hmap_init(vtep_lswitches);
+    hmap_init(other_lswitches);
+    SBREC_BINDING_FOR_EACH (binding_rec, ctx->ovnsb_idl) {
+        struct lswitch *ls;
+
+        if (get_lswitch_from_binding(other_lswitches, binding_rec, &ls)) {
+            /* Do not care if 'binding_rec' is not on a logical switch
+             * specified in vtep. */
+            continue;
+        } else if (get_lswitch_from_binding(vtep_lswitches, binding_rec, &ls)) 
{
+            /* The logical switch is already registered, just update the
+             * bindings. */
+            add_binding_macs(ls, binding_rec);
+        } else {
+            struct lswitch *ls = xmalloc(sizeof *ls);
+
+            hmap_init(&ls->bindings);
+            /* If the binding is in the same logical switch as vtep,
+             * add 'ls' to 'vtep_lswitches'. */
+            if (binding_pipeline_use_vtep(ctx, binding_rec)) {
+                ls->ldp_uuid = binding_rec->logical_datapath;
+                ls->lswitch = find_vtep_lswitch(ctx, &ls->ldp_uuid);
+                hmap_insert(vtep_lswitches, &ls->hmap_node,
+                            uuid_hash(&ls->ldp_uuid));
+                add_binding_macs(ls, binding_rec);
+            } else {
+                ls->ldp_uuid = binding_rec->logical_datapath;
+                hmap_insert(other_lswitches, &ls->hmap_node,
+                            uuid_hash(&ls->ldp_uuid));
+            }
+        }
+    }
+    lswitch_map_destroy(other_lswitches);
+    free(other_lswitches);
+
+    return vtep_lswitches;
+}
+
+/* Collects all 'ucast_macs_remote's from vtep. */
+static struct hmap *
+collect_umrs(struct controller_gw_ctx *ctx)
+{
+    const struct vteprec_ucast_macs_remote *vteprec_umr;
+    struct hmap *ucast_macs_rmts = xmalloc(sizeof *ucast_macs_rmts);
+
+    hmap_init(ucast_macs_rmts);
+    VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (vteprec_umr, ctx->vtep_idl) {
+        ucast_macs_rmt_map_add(vteprec_umr, ucast_macs_rmts);
+    }
+
+    return ucast_macs_rmts;
+}
+
+/* Collects all 'physical_locator's from vtep. */
+static struct hmap *
+collect_pl(struct controller_gw_ctx *ctx)
+{
+    const struct vteprec_physical_locator *vteprec_pl;
+    struct hmap *physical_locators = xmalloc(sizeof *physical_locators);
+
+    hmap_init(physical_locators);
+    VTEPREC_PHYSICAL_LOCATOR_FOR_EACH (vteprec_pl, ctx->vtep_idl) {
+        physical_locator_map_add(vteprec_pl, physical_locators);
+    }
+
+    return physical_locators;
+}
+
+
+/* First collects required info from ovnsb and vtep.  Then updates the
+ * 'Ucast_Macs_Remote' and 'Physical_Locator' tables in vtep. */
+void
+pipeline_run(struct controller_gw_ctx *ctx)
+{
+    struct hmap *vtep_lswitches = collect_bindings(ctx);
+    struct hmap *ucast_macs_rmts = collect_umrs(ctx);
+    struct hmap *physical_locators = collect_pl(ctx);
+    struct ovsdb_idl_txn *txn;
+    struct lswitch *ls;
+
+    txn = ovsdb_idl_txn_create(ctx->vtep_idl);
+    ovsdb_idl_txn_add_comment(txn,
+                              "ovn-controller-gw: update Ucast_Macs_Remote and 
"
+                              "Physical_Locator");
+    /* Updates the 'Ucast_Macs_Remote' table and 'Physical_Locator' table. */
+    HMAP_FOR_EACH (ls, hmap_node, vtep_lswitches) {
+        struct mac_to_binding *mb;
+
+        HMAP_FOR_EACH (mb, hmap_node, &ls->bindings) {
+            struct ucast_macs_rmt *umr;
+
+            /* Checks if there is a 'Ucast_Macs_Remote' entry. */
+            if (get_umr_by_mac(ucast_macs_rmts, mb->mac, &umr)) {
+                /* Checks if 'chassis' entry is empty. */
+                if (mb->binding->chassis) {
+                    const struct sbrec_chassis *chassis_rec;
+                    struct physical_locator *pl;
+
+                    chassis_rec = mb->binding->chassis;
+                    /* Checks the logical_switch consistency. */
+                    if (umr->umr->logical_switch != ls->lswitch) {
+                        vteprec_ucast_macs_remote_set_logical_switch(umr->umr, 
ls->lswitch);
+                    }
+
+                    /* Checks the physical_locator consistency. */
+                    if (get_pl_by_ip(physical_locators,
+                                     chassis_rec->encaps[0]->ip, &pl)) {
+                        if (umr->umr->locator != pl->pl) {
+                            vteprec_ucast_macs_remote_set_locator(umr->umr,
+                                                                  pl->pl);
+                        } else {
+                            /* Do nothing, since ucast_macs_remote entry,
+                             * chassis and physical_locator are all there. */
+                        }
+                    } else {
+                        struct vteprec_physical_locator *new_pl;
+
+                        /* Creates a new 'Physical_Locator' row and updates
+                         * the umr. */
+                        new_pl = vteprec_physical_locator_insert(txn);
+                        vteprec_physical_locator_set_dst_ip(new_pl, 
chassis_rec->encaps[0]->ip);
+                        
vteprec_physical_locator_set_encapsulation_type(new_pl, VTEP_ENCAP_TYPE);
+                        vteprec_ucast_macs_remote_set_locator(umr->umr, 
new_pl);
+                        physical_locator_map_add(new_pl, physical_locators);
+                    }
+                } else {
+                    /* Removes the 'Ucast_Macs_Remote' entry in vtep, since
+                     * there is no chassis. */
+                    vteprec_ucast_macs_remote_delete(umr->umr);
+                }
+            } else {
+                /* Checks if 'chassis' entry is empty. */
+                if (mb->binding->chassis) {
+                    const struct sbrec_chassis *chassis_rec;
+                    struct vteprec_ucast_macs_remote *new_umr;
+                    struct physical_locator *pl;
+
+                    chassis_rec = mb->binding->chassis;
+                    new_umr = vteprec_ucast_macs_remote_insert(txn);
+                    vteprec_ucast_macs_remote_set_MAC(new_umr, mb->mac);
+                    vteprec_ucast_macs_remote_set_logical_switch(new_umr, 
ls->lswitch);
+                    /* Checks if physical_locator already exists. */
+                    if (get_pl_by_ip(physical_locators,
+                                     chassis_rec->encaps[0]->ip, &pl)) {
+                        vteprec_ucast_macs_remote_set_locator(new_umr, pl->pl);
+                    } else {
+                        struct vteprec_physical_locator *new_pl;
+
+                        /* Creates a new 'Physical_Locator' row and updates
+                         * the umr. */
+                        new_pl = vteprec_physical_locator_insert(txn);
+                        vteprec_physical_locator_set_dst_ip(new_pl, 
chassis_rec->encaps[0]->ip);
+                        
vteprec_physical_locator_set_encapsulation_type(new_pl, VTEP_ENCAP_TYPE);
+                        vteprec_ucast_macs_remote_set_locator(new_umr, new_pl);
+                        physical_locator_map_add(new_pl, physical_locators);
+                    }
+                    ucast_macs_rmt_map_add(new_umr, ucast_macs_rmts);
+                } else {
+                    /* Do nothing, since the chassis has not been identified
+                     * yet. */
+                }
+            }
+        }
+    }
+
+    int retval;
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        VLOG_INFO("Problem registering chassis: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+        poll_immediate_wake();
+    }
+    ovsdb_idl_txn_destroy(txn);
+
+    /* Cleans up. */
+    lswitch_map_destroy(vtep_lswitches);
+    ucast_macs_rmt_map_destory(ucast_macs_rmts);
+    physical_locator_map_destory(physical_locators);
+    free(vtep_lswitches);
+    free(ucast_macs_rmts);
+    free(physical_locators);
+}
+
+/* First collects required info from ovnsb and vtep.  Then remove the
+ * unused 'Ucast_Macs_Remote' and 'Physical_Locator' entries in vtep. */
+void
+pipeline_destroy(struct controller_gw_ctx *ctx)
+{
+    struct hmap *vtep_lswitches = collect_bindings(ctx);
+    struct hmap *ucast_macs_rmts = collect_umrs(ctx);
+    struct ucast_macs_rmt *umr;
+    struct ovsdb_idl_txn *txn;
+
+    txn = ovsdb_idl_txn_create(ctx->vtep_idl);
+    ovsdb_idl_txn_add_comment(txn,
+                              "ovn-controller-gw: clean up Ucast_Macs_Remote 
and "
+                              "Physical_Locator");
+    /* Cleans up all unused 'Ucast_Macs_Remote' entires. */
+    HMAP_FOR_EACH (umr, hmap_node, ucast_macs_rmts) {
+        const struct lswitch *ls;
+        struct mac_to_binding *mb;
+
+        /* Finds 'ls' corresponding to the same logical switch. */
+        ls = find_struct_lswitch(vtep_lswitches, umr->umr->logical_switch);
+        ovs_assert(ls);
+
+        /* If could not find 'mb' from lswitch or binding's chassis is empty,
+         * removes the 'umr'. */
+        if (!get_mb_from_lswitch(ls, umr->umr->MAC, &mb)) {
+            vteprec_ucast_macs_remote_delete(umr->umr);
+        } else if (mb->binding->chassis) {
+            vteprec_ucast_macs_remote_delete(umr->umr);
+        }
+    }
+
+    int retval;
+    retval = ovsdb_idl_txn_commit_block(txn);
+    if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
+        VLOG_INFO("Problem registering chassis: %s",
+                  ovsdb_idl_txn_status_to_string(retval));
+        poll_immediate_wake();
+    }
+    ovsdb_idl_txn_destroy(txn);
+
+    lswitch_map_destroy(vtep_lswitches);
+    ucast_macs_rmt_map_destory(ucast_macs_rmts);
+    free(vtep_lswitches);
+    free(ucast_macs_rmts);
+}
diff --git a/ovn/controller-gw/pipeline.h b/ovn/controller-gw/pipeline.h
new file mode 100644
index 0000000..3611c1b
--- /dev/null
+++ b/ovn/controller-gw/pipeline.h
@@ -0,0 +1,25 @@
+/* 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_PIPELINE_H
+#define OVN_PIPELINE_H 1
+
+struct controller_gw_ctx;
+
+void pipeline_run(struct controller_gw_ctx *);
+void pipeline_destroy(struct controller_gw_ctx *);
+
+#endif /* ovn/controller-gw/pipeline.h */
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 6d74c9f..1cb49a7 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -194,7 +194,8 @@
 
     <column name="vlan_map">
       Maps from a VLAN ID to a logical port name.  Thus, each named logical
-      port corresponds to one VLAN on the gateway port.
+      port corresponds to one VLAN on the gateway port.  The logical port name
+      is the combination of physical_switch_name+physical_port+vlan_number.
     </column>
   </table>
 
diff --git a/tests/automake.mk b/tests/automake.mk
index 239b247..7178b05 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -84,8 +84,8 @@ TESTSUITE_AT = \
        tests/vtep-ctl.at \
        tests/auto-attach.at \
        tests/ovn.at \
-       tests/ovn-sbctl.at
-
+       tests/ovn-sbctl.at \
+       tests/ovn-controller-gw.at
 KMOD_TESTSUITE_AT = \
        tests/kmod-testsuite.at \
        tests/kmod-macros.at \
@@ -96,7 +96,7 @@ TESTSUITE_PATCH = $(srcdir)/tests/testsuite.patch
 KMOD_TESTSUITE = $(srcdir)/tests/kmod-testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 
-AUTOTEST_PATH = 
utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):ovn:ovn/northd
+AUTOTEST_PATH = 
utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):ovn:ovn/northd:ovn/controller-gw
 
 check-local: tests/atconfig tests/atlocal $(TESTSUITE)
        $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH) 
$(TESTSUITEFLAGS)
diff --git a/tests/ovn-controller-gw.at b/tests/ovn-controller-gw.at
new file mode 100644
index 0000000..c17b72d
--- /dev/null
+++ b/tests/ovn-controller-gw.at
@@ -0,0 +1,229 @@
+AT_BANNER([ovn_controller_gw])
+
+# OVN_CONTROLLER_GW_START
+#
+# Starts the test with a setup with vtep device.
+#
+# 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_GW_START],
+  [OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+   OVS_LOGDIR=`pwd`; export OVS_LOGDIR
+   OVS_DBDIR=`pwd`; export OVS_DBDIR
+   OVS_SYSCONFDIR=`pwd`; export OVS_SYSCONFDIR
+
+   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 ovn-nb.db ovn-sb.db], 
[0], [], [stderr])
+    ON_EXIT_UNQUOTED([kill `cat ovsdb-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 --disable-system --detach --no-chdir 
--pidfile --log-file -vvconn -vofproto_dpif], [0], [], [stderr])
+   AT_CAPTURE_FILE([ovs-vswitchd.log])
+   ON_EXIT_UNQUOTED([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
+/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 
br-vtep \], [0], [], [stderr])
+   ON_EXIT_UNQUOTED([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 lswitch-add br-test])
+   AT_CHECK([ovn-northd --detach --pidfile --log-file 
--ovnnb-db=unix:$OVS_RUNDIR/db.sock --ovnsb-db=unix:$OVS_RUNDIR/db.sock], [0], 
[], [stderr])
+   ON_EXIT_UNQUOTED([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-gw.
+   AT_CHECK([ovn-controller-gw --detach --pidfile --log-file 
--vtep-db=unix:$OVS_RUNDIR/db.sock --ovnsb-db=unix:$OVS_RUNDIR/db.sock], [0], 
[], [stderr])
+   AT_CAPTURE_FILE([ovn-controller-gw.log])
+   ON_EXIT_UNQUOTED([kill `cat ovn-controller-gw.pid`])
+   AT_CHECK([[sed < stderr '
+/vlog|INFO|opened log file/d
+/reconnect|INFO|/d']])
+])
+
+# OVN_CONTROLLER_GW_STOP
+#
+# So many exits... Yeah, we started a lot daemons~
+#
+m4_define([OVN_CONTROLLER_GW_STOP],
+  [AT_CHECK([check_logs "ovs-vswitchd.log ovn-northd.log ovn-controller-gw.log 
ovsdb-server.log ovs-vtep.log" $1])
+   AT_CHECK([ovs-appctl -t ovs-vtep exit])
+   AT_CHECK([ovs-appctl -t ovn-northd exit])
+   AT_CHECK([ovs-appctl -t ovn-controller-gw exit])
+   AT_CHECK([ovs-appctl -t ovsdb-server exit])
+   AT_CHECK([ovs-appctl -t ovs-vswitchd exit])])
+
+
+
+# tests chassis related updates.
+AT_SETUP([ovn-controller-gw - test chassis])
+OVN_CONTROLLER_GW_START
+
+# verifies the initial ovn-sb db configuration.
+AT_CHECK([ovn-sbctl show], [0], [dnl
+Chassis br-vtep
+    Encap vxlan
+        ip: "1.2.3.4"
+])
+
+# deletes the chassis via ovn-sbctl and check that it is readded back
+# with the log.
+AT_CHECK([ovn-sbctl del-ch br-vtep])
+OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-gw.log`"])
+AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-gw.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"
+])
+
+# checks for gateway_ports.
+AT_CHECK([ovn-sbctl --columns=gateway_ports list Chassis br-vtep | cut -d ':' 
-f2 | sed 's/=[[-0-9a-f]][[-0-9a-f]]*/=<GW_row>/g' | tr -d ' '], [0], [dnl
+{"p0"=<GW_row>,"p1"=<GW_row>}
+])
+
+# adds a new physical port p2.
+AT_CHECK([ovs-vsctl add-port br-vtep p2 -- set Interface p2 type=dummy 
ofport_request=3])
+AT_CHECK([vtep-ctl add-port br-vtep p2])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis br-vtep | grep p2`"])
+AT_CHECK([ovn-sbctl --columns=gateway_ports list Chassis br-vtep | cut -d ':' 
-f2 | sed 's/=[[-0-9a-f]][[-0-9a-f]]*/=<GW_row>/g' | tr -d ' '], [0], [dnl
+{"p0"=<GW_row>,"p1"=<GW_row>,"p2"=<GW_row>}
+])
+
+# deletes the port p2.
+AT_CHECK([ovs-vsctl del-port p2])
+AT_CHECK([vtep-ctl del-port br-vtep p2])
+OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis br-vtep | grep p2`"])
+AT_CHECK([ovn-sbctl --columns=gateway_ports list Chassis br-vtep | cut -d ':' 
-f2 | sed 's/=[[-0-9a-f]][[-0-9a-f]]*/=<GW_row>/g' | tr -d ' '], [0], [dnl
+{"p0"=<GW_row>,"p1"=<GW_row>}
+])
+
+# adds vlan_bindings to physical ports.
+AT_CHECK([vtep-ctl add-ls lswitch -- bind-ls br-vtep p0 100 lswitch -- bind-ls 
br-vtep p0 200 lswitch -- bind-ls br-vtep p1 300 lswitch])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Gateway | grep -- br-vtep_`"])
+AT_CHECK([ovn-sbctl --columns=vlan_map list Gateway | cut -d ':' -f2 | tr -d ' 
' | sort], [0], [dnl
+
+{100="br-vtep_p0_100",200="br-vtep_p0_200"}
+{300="br-vtep_p1_300"}
+])
+
+# adds one more vlan_binding to p1.
+AT_CHECK([vtep-ctl bind-ls br-vtep p1 400 lswitch])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Gateway | grep -- br-vtep_p1_400`"])
+AT_CHECK([ovn-sbctl --columns=vlan_map list gateway | cut -d ':' -f2 | tr -d ' 
' | sort], [0], [dnl
+
+{100="br-vtep_p0_100",200="br-vtep_p0_200"}
+{300="br-vtep_p1_300",400="br-vtep_p1_400"}
+])
+
+OVN_CONTROLLER_GW_STOP(["/Chassis for VTEP physical switch (br-vtep) 
disappears/d"])
+AT_CLEANUP
+
+
+# Tests binding updates.
+AT_SETUP([ovn-controller-gw - test binding])
+OVN_CONTROLLER_GW_START
+
+# adds vlan_bindings to physical ports.
+AT_CHECK([vtep-ctl add-ls lswitch -- bind-ls br-vtep p0 100 lswitch -- bind-ls 
br-vtep p0 200 lswitch -- bind-ls br-vtep p1 300 lswitch])
+# adds lport in ovn-nb db.
+for lport_name in br-vtep_p0_100 br-vtep_p0_200 br-vtep_p1_300; do
+    AT_CHECK_UNQUOTED([ovn-nbctl lport-add br-test ${lport_name}])
+done
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Binding  | grep br-vtep_p1_300`"])
+
+chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 
| tr -d ' ')
+for lport_name in br-vtep_p0_100 br-vtep_p0_200 br-vtep_p1_300; do
+    AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Binding ${lport_name} 
| cut -d ':' -f2 | tr -d ' '], [0], [dnl
+${chassis_uuid}
+])
+done
+
+# deletes physical ports from vtep.
+AT_CHECK([ovs-vsctl del-port p0 -- del-port p1])
+AT_CHECK([vtep-ctl del-port br-vtep p0 -- del-port br-vtep p1])
+OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Gateway | grep uuid`"])
+
+for lport_name in br-vtep_p0_100 br-vtep_p0_200 br-vtep_p1_300; do
+    AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Binding ${lport_name} 
| cut -d ':' -f2 | tr -d ' '], [0], [dnl
+[[]]
+])
+done
+
+OVN_CONTROLLER_GW_STOP
+AT_CLEANUP
+
+
+# Tests pipeline updates.
+AT_SETUP([ovn-controller-gw - test pipeline])
+OVN_CONTROLLER_GW_START
+
+# creates a simple logical network with the vtep device and a fake hv chassis
+# 'ch0'.
+AT_CHECK([ovn-nbctl lport-add br-test vif0])
+AT_CHECK([ovn-nbctl lport-set-macs vif0 f0:ab:cd:ef:01:02])
+AT_CHECK([ovn-nbctl lport-add br-test vif0])
+AT_CHECK([ovn-sbctl add-ch ch0 vxlan 1.2.3.5])
+AT_CHECK([ovn-sbctl bind-lport vif0 ch0])
+
+# vtep logical switch name must be the same as logical_datapath uuid.
+uuid=$(ovn-sbctl --columns=logical_datapath list Binding vif0 | cut -d ':' -f2 
| tr -d ' ')
+AT_CHECK([vtep-ctl add-ls ${uuid} -- set Logical_Switch ${uuid} tunnel_key=1 
-- bind-ls br-vtep p0 100 ${uuid}])
+
+# 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,encapsulation_type list Physical_Locator | 
cut -d ':' -f2 | tr -d ' '], [0], [dnl
+"1.2.3.5"
+"vxlan_over_ipv4"
+])
+
+# 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"}
+])
+
+OVN_CONTROLLER_GW_STOP
+AT_CLEANUP
\ No newline at end of file
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 1746efd..ae2cebd 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -18,6 +18,7 @@ m4_include([tests/ovs-macros.at])
 m4_include([tests/ovsdb-macros.at])
 m4_include([tests/ofproto-macros.at])
 
+m4_include([tests/ovn-controller-gw.at])
 m4_include([tests/completion.at])
 m4_include([tests/bfd.at])
 m4_include([tests/cfm.at])
-- 
1.7.9.5

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to