diff mbox

[ovs-dev,v5,3/8] ovn: l3ha, ovn-northd gateway chassis propagation

Message ID 1499965361-32021-3-git-send-email-majopela@redhat.com
State Accepted
Delegated to: Russell Bryant
Headers show

Commit Message

Miguel Angel Ajo July 13, 2017, 5:02 p.m. UTC
From: "majopela@redhat.com" <majopela@redhat.com>

The redirect-chassis option of logical router ports is now
translated to Gateway_Chassis entries for backwards compatibility.

Gateway_Chassis entries in nbdb are copied over to sbdb and
linked them to the Chassis entry.

Co-authored-by: Anil Venkata <vkommadi@redhat.com>
Signed-off-by: Miguel Angel Ajo <majopela@redhat.com>
Signed-off-by: Anil Venkata <vkommadi@redhat.com>
---
 ovn/lib/automake.mk     |   2 +
 ovn/lib/chassis-index.c |  77 +++++++++++++++
 ovn/lib/chassis-index.h |  40 ++++++++
 ovn/northd/ovn-northd.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++--
 tests/automake.mk       |   1 +
 tests/ovn-northd.at     |  87 +++++++++++++++++
 tests/testsuite.at      |   1 +
 7 files changed, 448 insertions(+), 9 deletions(-)
 create mode 100644 ovn/lib/chassis-index.c
 create mode 100644 ovn/lib/chassis-index.h
 create mode 100644 tests/ovn-northd.at
diff mbox

Patch

diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index b86237e..9ad8b6a 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -5,6 +5,8 @@  ovn_lib_libovn_la_LDFLAGS = \
         $(AM_LDFLAGS)
 ovn_lib_libovn_la_SOURCES = \
 	ovn/lib/actions.c \
+	ovn/lib/chassis-index.c \
+	ovn/lib/chassis-index.h \
 	ovn/lib/expr.c \
 	ovn/lib/lex.c \
 	ovn/lib/ovn-dhcp.h \
diff --git a/ovn/lib/chassis-index.c b/ovn/lib/chassis-index.c
new file mode 100644
index 0000000..ae65375
--- /dev/null
+++ b/ovn/lib/chassis-index.c
@@ -0,0 +1,77 @@ 
+/* 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 "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+#include "ovn/actions.h"
+#include "ovn/lib/chassis-index.h"
+#include "ovn/lib/ovn-sb-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(chassis_index);
+
+struct chassis {
+    struct hmap_node name_node;
+    const struct sbrec_chassis *db;
+};
+
+const struct sbrec_chassis *
+chassis_lookup_by_name(const struct chassis_index *chassis_index,
+                       const char *name)
+{
+    const struct chassis *chassis;
+    HMAP_FOR_EACH_WITH_HASH (chassis, name_node, hash_string(name, 0),
+                             &chassis_index->by_name) {
+        if (!strcmp(chassis->db->name, name)) {
+            return chassis->db;
+        }
+    }
+    return NULL;
+}
+
+void
+chassis_index_init(struct chassis_index *chassis_index,
+                   struct ovsdb_idl *sb_idl)
+{
+    hmap_init(&chassis_index->by_name);
+
+    const struct sbrec_chassis *chassis;
+    SBREC_CHASSIS_FOR_EACH (chassis, sb_idl) {
+        if (!chassis->name) {
+            continue;
+        }
+        struct chassis *c = xmalloc(sizeof *c);
+        hmap_insert(&chassis_index->by_name, &c->name_node,
+                    hash_string(chassis->name, 0));
+        c->db = chassis;
+    }
+}
+
+void
+chassis_index_destroy(struct chassis_index *chassis_index)
+{
+    if (!chassis_index) {
+        return;
+    }
+
+    /* Destroy all of the "struct chassis"s. */
+    struct chassis *chassis, *next;
+    HMAP_FOR_EACH_SAFE (chassis, next, name_node, &chassis_index->by_name) {
+        hmap_remove(&chassis_index->by_name, &chassis->name_node);
+        free(chassis);
+    }
+
+    hmap_destroy(&chassis_index->by_name);
+}
diff --git a/ovn/lib/chassis-index.h b/ovn/lib/chassis-index.h
new file mode 100644
index 0000000..a490cbb
--- /dev/null
+++ b/ovn/lib/chassis-index.h
@@ -0,0 +1,40 @@ 
+/* 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
+
+#include "openvswitch/hmap.h"
+
+struct chassis_index {
+    struct hmap by_name;
+};
+
+struct ovsdb_idl;
+
+/* Finds and returns the chassis with the given 'name', or NULL if no such
+ * chassis exists. */
+const struct sbrec_chassis *
+chassis_lookup_by_name(const struct chassis_index *chassis_index,
+                       const char *name);
+
+/* Initializes the chassis index out of the ovsdb_idl to SBDB */
+void chassis_index_init(struct chassis_index *chassis_index,
+                        struct ovsdb_idl *sb_idl);
+
+/* Free a chassis index from memory */
+void chassis_index_destroy(struct chassis_index *chassis_index);
+
+#endif /* ovn/lib/chassis-index.h */
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index be3b371..990d268 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -28,6 +28,7 @@ 
 #include "openvswitch/hmap.h"
 #include "openvswitch/json.h"
 #include "ovn/lex.h"
+#include "ovn/lib/chassis-index.h"
 #include "ovn/lib/logical-fields.h"
 #include "ovn/lib/ovn-dhcp.h"
 #include "ovn/lib/ovn-nb-idl.h"
@@ -1453,7 +1454,7 @@  join_logical_ports(struct northd_context *ctx,
 
                 const char *redirect_chassis = smap_get(&op->nbrp->options,
                                                         "redirect-chassis");
-                if (redirect_chassis) {
+                if (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,
@@ -1678,8 +1679,159 @@  get_nat_addresses(const struct ovn_port *op, size_t *n)
     return addresses;
 }
 
+static bool
+gateway_chassis_equal(const struct nbrec_gateway_chassis *nb_gwc,
+                      const struct sbrec_chassis *nb_gwc_c,
+                      const struct sbrec_gateway_chassis *sb_gwc)
+{
+    return !strcmp(nb_gwc->name, sb_gwc->name)
+           && !strcmp(nb_gwc_c->name, sb_gwc->chassis->name)
+           && nb_gwc->priority == sb_gwc->priority
+           && smap_equal(&nb_gwc->options, &sb_gwc->options)
+           && smap_equal(&nb_gwc->external_ids, &sb_gwc->external_ids);
+}
+
+static bool
+sbpb_gw_chassis_needs_update(
+        const struct sbrec_port_binding *port_binding,
+        const struct nbrec_logical_router_port *lrp,
+        const struct chassis_index *chassis_index)
+{
+    if (!lrp || !port_binding) {
+        return false;
+    }
+
+    /* These arrays are used to collect valid Gateway_Chassis and valid
+     * Chassis records from the Logical_Router_Port Gateway_Chassis list,
+     * we ignore the ones we can't match on the SBDB */
+    struct nbrec_gateway_chassis **lrp_gwc = xzalloc(lrp->n_gateway_chassis *
+                                                     sizeof *lrp_gwc);
+    const struct sbrec_chassis **lrp_gwc_c = xzalloc(lrp->n_gateway_chassis *
+                                               sizeof *lrp_gwc_c);
+
+    /* Count the number of gateway chassis chassis names from the logical
+     * router port that we are able to match on the southbound database */
+    int lrp_n_gateway_chassis = 0;
+    int n;
+    for (n = 0; n < lrp->n_gateway_chassis; n++) {
+
+        if (!lrp->gateway_chassis[n]->chassis_name) {
+            continue;
+        }
+
+        const struct sbrec_chassis *chassis =
+            chassis_lookup_by_name(chassis_index,
+                                   lrp->gateway_chassis[n]->chassis_name);
+
+        if (chassis) {
+            lrp_gwc_c[lrp_n_gateway_chassis] = chassis;
+            lrp_gwc[lrp_n_gateway_chassis] = lrp->gateway_chassis[n];
+            lrp_n_gateway_chassis++;
+        } else {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(
+                &rl, "Chassis name %s referenced in NBDB via Gateway_Chassis "
+                     "on logical router port %s does not exist in SBDB",
+                     lrp->gateway_chassis[n]->chassis_name, lrp->name);
+
+        }
+
+    }
+
+    /* Basic check, different amount of Gateway_Chassis means that we
+     * need to update southbound database Port_Binding */
+    if (lrp_n_gateway_chassis != port_binding->n_gateway_chassis) {
+        free(lrp_gwc_c);
+        free(lrp_gwc);
+        return true;
+    }
+
+    for (n = 0; n < lrp_n_gateway_chassis; n++) {
+        int i;
+        /* For each of the valid gw chassis on the lrp, check if there's
+         * a match on the Port_Binding list, we assume order is not
+         * persisted */
+        for (i = 0; i < port_binding->n_gateway_chassis; i++) {
+            if (gateway_chassis_equal(lrp_gwc[n],
+                                      lrp_gwc_c[n],
+                                      port_binding->gateway_chassis[i])) {
+                break; /* we found a match */
+            }
+        }
+
+        /* if no Port_Binding gateway chassis matched for the entry... */
+        if (i == port_binding->n_gateway_chassis) {
+            free(lrp_gwc_c);
+            free(lrp_gwc);
+            return true; /* found no match for this gateway chassis on lrp */
+        }
+    }
+
+    /* no need for update, all ports matched */
+    free(lrp_gwc_c);
+    free(lrp_gwc);
+    return false;
+}
+
+/* This functions translates the gw chassis on the nb database
+ * to sb database entries, the only difference is that SB database
+ * Gateway_Chassis table references the chassis directly instead
+ * of using the name */
+static void
+copy_gw_chassis_from_nbrp_to_sbpb(
+        struct northd_context *ctx,
+        const struct nbrec_logical_router_port *lrp,
+        const struct chassis_index *chassis_index,
+        const struct sbrec_port_binding *port_binding) {
+
+    if (!lrp || !port_binding || !lrp->n_gateway_chassis) {
+        return;
+    }
+
+    struct sbrec_gateway_chassis **gw_chassis = NULL;
+    int n_gwc = 0;
+    int n;
+
+    /* XXX: This can be improved. This code will generate a set of new
+     * Gateway_Chassis and push them all in a single transaction, instead
+     * this would be more optimal if we just add/update/remove the rows in
+     * the southbound db that need to change. We don't expect lots of
+     * changes to the Gateway_Chassis table, but if that proves to be wrong
+     * we should optimize this. */
+    for (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(chassis_index, lrp_gwc->chassis_name);
+
+        if (!chassis) {
+            continue;
+        }
+
+        gw_chassis = xrealloc(gw_chassis, (n_gwc + 1) * sizeof *gw_chassis);
+
+        struct sbrec_gateway_chassis *pb_gwc =
+            sbrec_gateway_chassis_insert(ctx->ovnsb_txn);
+
+        sbrec_gateway_chassis_set_name(pb_gwc, lrp_gwc->name);
+        sbrec_gateway_chassis_set_priority(pb_gwc, lrp_gwc->priority);
+        sbrec_gateway_chassis_set_chassis(pb_gwc, chassis);
+        sbrec_gateway_chassis_set_options(pb_gwc, &lrp_gwc->options);
+        sbrec_gateway_chassis_set_external_ids(pb_gwc, &lrp_gwc->external_ids);
+
+        gw_chassis[n_gwc++] = pb_gwc;
+    }
+    sbrec_port_binding_set_gateway_chassis(port_binding, gw_chassis, n_gwc);
+    free(gw_chassis);
+}
+
 static void
-ovn_port_update_sbrec(const struct ovn_port *op,
+ovn_port_update_sbrec(struct northd_context *ctx,
+                      const struct ovn_port *op,
+                      const struct chassis_index *chassis_index,
                       struct hmap *chassis_qdisc_queues)
 {
     sbrec_port_binding_set_datapath(op->sb, op->od->sb);
@@ -1700,8 +1852,69 @@  ovn_port_update_sbrec(const struct ovn_port *op,
         if (op->derived) {
             const char *redirect_chassis = smap_get(&op->nbrp->options,
                                                     "redirect-chassis");
-            if (redirect_chassis) {
+            if (op->nbrp->n_gateway_chassis && redirect_chassis) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(
+                    &rl, "logical router port %s has both options:"
+                         "redirect-chassis and gateway_chassis populated "
+                         "redirect-chassis will be ignored in favour of "
+                         "gateway chassis", op->nbrp->name);
+            }
+
+            if (op->nbrp->n_gateway_chassis) {
+                if (sbpb_gw_chassis_needs_update(op->sb, op->nbrp,
+                                                   chassis_index)) {
+                    copy_gw_chassis_from_nbrp_to_sbpb(ctx, op->nbrp,
+                                                        chassis_index, op->sb);
+                }
+
+            } else if (redirect_chassis) {
+                /* XXX: Keep the "redirect-chassis" option on the Port_Binding
+                 * for compatibility purposes until ovn-controller implements
+                 * Gateway_Chassis handling */
                 smap_add(&new, "redirect-chassis", redirect_chassis);
+
+                /* Handle ports that had redirect-chassis option attached
+                 * to them for backwards compatibility */
+                const struct sbrec_chassis *chassis =
+                    chassis_lookup_by_name(chassis_index, redirect_chassis);
+                if (chassis) {
+                    /* If we found the chassis, and the gw chassis on record
+                     * differs from what we expect go ahead and update */
+                    if (op->sb->n_gateway_chassis !=1
+                        || strcmp(op->sb->gateway_chassis[0]->chassis->name,
+                                  chassis->name)
+                        || op->sb->gateway_chassis[0]->priority != 0) {
+                        /* Construct a single Gateway_Chassis entry on the
+                         * Port_Binding attached to the redirect_chassis
+                         * name */
+                        struct sbrec_gateway_chassis *gw_chassis =
+                            sbrec_gateway_chassis_insert(ctx->ovnsb_txn);
+
+                        char *gwc_name = xasprintf("%s_%s", op->nbrp->name,
+                                chassis->name);
+
+                        /* XXX: Again, here, we could just update an existing
+                         * Gateway_Chassis, instead of creating a new one
+                         * and replacing it */
+                        sbrec_gateway_chassis_set_name(gw_chassis, gwc_name);
+                        sbrec_gateway_chassis_set_priority(gw_chassis, 0);
+                        sbrec_gateway_chassis_set_chassis(gw_chassis, chassis);
+                        sbrec_gateway_chassis_set_external_ids(gw_chassis,
+                                &op->nbrp->external_ids);
+                        sbrec_port_binding_set_gateway_chassis(op->sb,
+                                                               &gw_chassis, 1);
+                        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->n_gateway_chassis) {
+                        sbrec_port_binding_set_gateway_chassis(op->sb, NULL,
+                                                               0);
+                    }
+                }
             }
             smap_add(&new, "distributed-port", op->nbrp->name);
         } else {
@@ -1845,7 +2058,7 @@  cleanup_mac_bindings(struct northd_context *ctx, struct hmap *ports)
  * datapaths. */
 static void
 build_ports(struct northd_context *ctx, struct hmap *datapaths,
-            struct hmap *ports)
+            const struct chassis_index *chassis_index, struct hmap *ports)
 {
     struct ovs_list sb_only, nb_only, both;
     struct hmap tag_alloc_table = HMAP_INITIALIZER(&tag_alloc_table);
@@ -1863,7 +2076,7 @@  build_ports(struct northd_context *ctx, struct hmap *datapaths,
         if (op->nbsp) {
             tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp);
         }
-        ovn_port_update_sbrec(op, &chassis_qdisc_queues);
+        ovn_port_update_sbrec(ctx, op, chassis_index, &chassis_qdisc_queues);
 
         add_tnlid(&op->od->port_tnlids, op->sb->tunnel_key);
         if (op->sb->tunnel_key > op->od->port_key_hint) {
@@ -1879,7 +2092,7 @@  build_ports(struct northd_context *ctx, struct hmap *datapaths,
         }
 
         op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
-        ovn_port_update_sbrec(op, &chassis_qdisc_queues);
+        ovn_port_update_sbrec(ctx, op, chassis_index, &chassis_qdisc_queues);
 
         sbrec_port_binding_set_logical_port(op->sb, op->key);
         sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key);
@@ -5606,14 +5819,15 @@  sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths)
 
 
 static void
-ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
+ovnnb_db_run(struct northd_context *ctx, struct chassis_index *chassis_index,
+             struct ovsdb_idl_loop *sb_loop)
 {
     if (!ctx->ovnsb_txn || !ctx->ovnnb_txn) {
         return;
     }
     struct hmap datapaths, ports;
     build_datapaths(ctx, &datapaths);
-    build_ports(ctx, &datapaths, &ports);
+    build_ports(ctx, &datapaths, chassis_index, &ports);
     build_ipam(&datapaths, &ports);
     build_lflows(ctx, &datapaths, &ports);
 
@@ -6183,6 +6397,17 @@  main(int argc, char *argv[])
     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_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);
@@ -6223,6 +6448,7 @@  main(int argc, char *argv[])
 
     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);
 
     /* Main loop. */
     exiting = false;
@@ -6234,7 +6460,10 @@  main(int argc, char *argv[])
             .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
         };
 
-        ovnnb_db_run(&ctx, &ovnsb_idl_loop);
+        struct chassis_index chassis_index;
+        chassis_index_init(&chassis_index, ctx.ovnsb_idl);
+
+        ovnnb_db_run(&ctx, &chassis_index, &ovnsb_idl_loop);
         ovnsb_db_run(&ctx, &ovnsb_idl_loop);
         if (ctx.ovnsb_txn) {
             check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
@@ -6254,6 +6483,8 @@  main(int argc, char *argv[])
         if (should_service_stop()) {
             exiting = true;
         }
+
+        chassis_index_destroy(&chassis_index);
     }
 
     unixctl_server_destroy(unixctl);
diff --git a/tests/automake.mk b/tests/automake.mk
index 3118bbc..58520a8 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -94,6 +94,7 @@  TESTSUITE_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 \
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
new file mode 100644
index 0000000..d0e8bcd
--- /dev/null
+++ b/tests/ovn-northd.at
@@ -0,0 +1,87 @@ 
+AT_BANNER([OVN northd])
+AT_SETUP([ovn -- check Gateway_Chassis propagation 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"`
+gwc1_uuid=`ovn-sbctl --bare --columns _uuid find Gateway_Chassis name="alice_gw1"`
+gwc2_uuid=`ovn-sbctl --bare --columns _uuid find Gateway_Chassis name="alice_gw2"`
+
+
+echo "Port_Binding for cr-alice:"
+ovn-sbctl find port_binding logical_port="cr-alice"
+echo "Gateway_Chassis list:"
+ovn-sbctl list Gateway_Chassis
+
+AT_CHECK([ovn-sbctl --bare --columns gateway_chassis find port_binding logical_port="cr-alice" | grep $gwc1_uuid | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl --bare --columns gateway_chassis find port_binding logical_port="cr-alice" | grep $gwc2_uuid | wc -l], [0], [1
+])
+
+# delete the 2nd Gateway_Chassis on NBDB for alice port
+
+ovn-nbctl --wait=sb set Logical_Router_Port alice gateway_chassis=${nb_gwc1_uuid}
+
+gwc1_uuid=`ovn-sbctl --bare --columns _uuid find Gateway_Chassis name="alice_gw1"`
+
+AT_CHECK([ovn-sbctl --bare --columns gateway_chassis find port_binding logical_port="cr-alice" | grep $gwc1_uuid | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl find Gateway_Chassis name=alice_gw2], [0], [])
+
+# delete all the gateway_chassis on NBDB for alice port
+
+ovn-nbctl --wait=sb clear Logical_Router_Port alice gateway_chassis
+
+# expect that the Gateway_Chassis doesn't exist anymore
+
+AT_CHECK([ovn-sbctl find Gateway_Chassis name=alice_gw1], [0], [])
+AT_CHECK([ovn-sbctl find Gateway_Chassis name=alice_gw2], [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 Gateway_Chassis entries in SBDB, and
+# still redirect-chassis is kept for backwards compatibility
+
+gwc1_uuid=`ovn-sbctl --bare --columns _uuid find Gateway_Chassis name="bob_gw1"`
+
+AT_CHECK([ovn-sbctl --bare --columns options find port_binding logical_port="cr-bob" | grep 'redirect-chassis=gw1' | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl --bare --columns gateway_chassis find port_binding logical_port="cr-bob" | grep $gwc1_uuid | wc -l], [0], [1
+])
+
+ovn-nbctl --wait=sb remove Logical_Router_Port bob options redirect-chassis
+
+# expect that the Gateway_Chassis doesn't exist anymore
+
+AT_CHECK([ovn-sbctl find Gateway_Chassis name=bob_gw1], [0], [])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c7febc6..3e2eb6d 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -72,6 +72,7 @@  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])