[ovs-dev] [ovn-controller-vtep V6 4/7] ovn-controller-vtep: Add binding module.

Alex Wang alexw at nicira.com
Mon Aug 10 02:50:44 UTC 2015


This commit adds the binding module to ovn-controller-vtep.  The
module will scan through the Port_Binding table in ovnsb.  If there is
a port binding entry for a logical switch on the vtep gateway chassis's
"vtep_logical_switches", sets the port binding's chassis column to the
vtep gateway chassis.

Signed-off-by: Alex Wang <alexw at nicira.com>

---
V5->V6:
- avoid iterating Port_Binding table for each vtep physical switch.
- refine code based on Russell's comments.

V4->V5:
- rebase on top of master.
- change to use port binding type, and options when finding bindings
  for vtep gateway chassis.

V3->V4:
- rebase to master.

V2->V3:
- since ovn-sb schema changes (removal of Gateway table), the binding
  module code needs to be adapted.

PATCH->V2:
- split into separate commit.
- disallow and warn if more than one logical port from one 'vlan_map'
  are attached to the same logical datapath.
---
 ovn/controller-vtep/automake.mk           |    2 +
 ovn/controller-vtep/binding.c             |  273 +++++++++++++++++++++++++++++
 ovn/controller-vtep/binding.h             |   27 +++
 ovn/controller-vtep/ovn-controller-vtep.c |    4 +
 tests/ovn-controller-vtep.at              |  112 ++++++++++++
 5 files changed, 418 insertions(+)
 create mode 100644 ovn/controller-vtep/binding.c
 create mode 100644 ovn/controller-vtep/binding.h

diff --git a/ovn/controller-vtep/automake.mk b/ovn/controller-vtep/automake.mk
index 514cafa..33f063f 100644
--- a/ovn/controller-vtep/automake.mk
+++ b/ovn/controller-vtep/automake.mk
@@ -1,5 +1,7 @@
 bin_PROGRAMS += ovn/controller-vtep/ovn-controller-vtep
 ovn_controller_vtep_ovn_controller_vtep_SOURCES = \
+	ovn/controller-vtep/binding.c \
+	ovn/controller-vtep/binding.h \
 	ovn/controller-vtep/gateway.c \
 	ovn/controller-vtep/gateway.h \
 	ovn/controller-vtep/ovn-controller-vtep.c \
diff --git a/ovn/controller-vtep/binding.c b/ovn/controller-vtep/binding.c
new file mode 100644
index 0000000..36bf02f
--- /dev/null
+++ b/ovn/controller-vtep/binding.c
@@ -0,0 +1,273 @@
+/* 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/shash.h"
+#include "lib/smap.h"
+#include "lib/util.h"
+#include "openvswitch/vlog.h"
+#include "ovn-controller-vtep.h"
+#include "ovn/lib/ovn-sb-idl.h"
+#include "vtep/vtep-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(binding);
+
+/*
+ * This module scans through the Port_Binding table in ovnsb.  If there is a
+ * logical port binding entry for logical switch in vtep gateway chassis's
+ * 'vtep_logical_switches' column, sets the binding's chassis column to the
+ * corresponding vtep gateway chassis.
+ *
+ */
+
+
+/* Returns true if the vtep logical switch specified by 'port_binding_rec'
+ * has already been bound to another port binding entry, and resets
+ * 'port_binding_rec''s chassis column.  Otherwise, updates 'ls_to_pb'
+ * and returns false. */
+static bool
+check_pb_conflict(struct shash *ls_to_pb,
+                  const struct sbrec_port_binding *port_binding_rec,
+                  const char *chassis_name)
+{
+    const char *vtep_lswitch =
+        smap_get(&port_binding_rec->options, "vtep-logical-switch");
+    const struct sbrec_port_binding *pb_conflict =
+        shash_find_data(ls_to_pb, vtep_lswitch);
+
+    if (pb_conflict) {
+        VLOG_WARN("logical switch (%s), on vtep gateway chassis "
+                  "(%s) has already been associated with logical "
+                  "port (%s), ignore logical port (%s)",
+                  vtep_lswitch, chassis_name,
+                  pb_conflict->logical_port,
+                  port_binding_rec->logical_port);
+        sbrec_port_binding_set_chassis(port_binding_rec, NULL);
+
+        return true;
+    }
+
+    shash_replace(ls_to_pb, vtep_lswitch, port_binding_rec);
+    return false;
+}
+
+/* Returns true if the vtep logical switch specified by 'port_binding_rec'
+ * has already been bound to a different datapath, and resets
+ * 'port_binding_rec''s chassis column.  Otherwise, updates 'ls_to_db' and
+ * returns false. */
+static bool
+check_db_conflict(struct shash *ls_to_db,
+                  const struct sbrec_port_binding *port_binding_rec,
+                  const char *chassis_name)
+{
+    const char *vtep_lswitch =
+        smap_get(&port_binding_rec->options, "vtep-logical-switch");
+    const struct sbrec_datapath_binding *db_conflict =
+        shash_find_data(ls_to_db, vtep_lswitch);
+
+    if (db_conflict && db_conflict != port_binding_rec->datapath) {
+        VLOG_WARN("logical switch (%s), on vtep gateway chassis "
+                  "(%s) has already been associated with logical "
+                  "datapath (with tunnel key %"PRId64"), ignore "
+                  "logical port (%s) which belongs to logical "
+                  "datapath (with tunnel key %"PRId64")",
+                  vtep_lswitch, chassis_name,
+                  db_conflict->tunnel_key,
+                  port_binding_rec->logical_port,
+                  port_binding_rec->datapath->tunnel_key);
+        sbrec_port_binding_set_chassis(port_binding_rec, NULL);
+
+        return true;
+    }
+
+    shash_replace(ls_to_db, vtep_lswitch, port_binding_rec->datapath);
+    return false;
+}
+
+/* Updates the 'port_binding_rec''s chassis column to 'chassis_rec'. */
+static void
+update_pb_chassis(const struct sbrec_port_binding *port_binding_rec,
+                  const struct sbrec_chassis *chassis_rec)
+{
+    if (port_binding_rec->chassis != chassis_rec) {
+        if (chassis_rec && port_binding_rec->chassis) {
+            VLOG_DBG("Changing chassis association of logical "
+                     "port (%s) from (%s) to (%s)",
+                     port_binding_rec->logical_port,
+                     port_binding_rec->chassis->name,
+                     chassis_rec->name);
+        }
+        sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec);
+    }
+}
+
+
+/* Checks and updates logical port to vtep logical switch bindings for each
+ * physical switch in VTEP. */
+void
+binding_run(struct controller_vtep_ctx *ctx)
+{
+    if (!ctx->ovnsb_idl_txn) {
+        return;
+    }
+
+    /* 'ls_to_db'
+     *
+     * Maps vtep logical switch name to the datapath binding entry.  This is
+     * used to guarantee that each vtep logical switch is only included
+     * in only one ovn datapath (ovn logical switch).  See check_db_conflict()
+     * for details.
+     *
+     * 'ls_to_pb'
+     *
+     * Maps vtep logical switch name to the port binding entry.  This is used
+     * to guarantee that each vtep logical switch on a vtep physical switch
+     * is only bound to one logical port.  See check_pb_conflict() for
+     * details.
+     *
+     */
+    struct shash ls_to_db = SHASH_INITIALIZER(&ls_to_db);
+    const struct vteprec_logical_switch *vteprec_ls;
+    VTEPREC_LOGICAL_SWITCH_FOR_EACH (vteprec_ls, ctx->vtep_idl) {
+        shash_add(&ls_to_db, vteprec_ls->name, NULL);
+    }
+
+    /* Stores the 'chassis' and the 'ls_to_pb' map related to
+     * a vtep physcial switch. */
+    struct ps {
+        const struct sbrec_chassis *chassis_rec;
+        struct shash ls_to_pb;
+    };
+    struct shash ps_map = SHASH_INITIALIZER(&ps_map);
+    const struct vteprec_physical_switch *pswitch;
+    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
+        const struct sbrec_chassis *chassis_rec
+            = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
+        struct ps *ps = xmalloc(sizeof *ps);
+        size_t i;
+
+        /* 'chassis_rec' must exist. */
+        ovs_assert(chassis_rec);
+        ps->chassis_rec = chassis_rec;
+        shash_init(&ps->ls_to_pb);
+        for (i = 0; i < chassis_rec->n_vtep_logical_switches; i++) {
+            shash_add(&ps->ls_to_pb, chassis_rec->vtep_logical_switches[i],
+                      NULL);
+        }
+        shash_add(&ps_map, chassis_rec->name, ps);
+    }
+
+    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn,
+                              "ovn-controller-vtep: updating bindings");
+
+    const struct sbrec_port_binding *port_binding_rec;
+    /* Port binding for vtep gateway chassis must have type "vtep",
+     * and matched physical switch name and logical switch name. */
+    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) {
+        const char *type = port_binding_rec->type;
+        const char *vtep_pswitch = smap_get(&port_binding_rec->options,
+                                            "vtep-physical-switch");
+        const char *vtep_lswitch = smap_get(&port_binding_rec->options,
+                                            "vtep-logical-switch");
+        struct ps *ps
+            = vtep_pswitch ? shash_find_data(&ps_map, vtep_pswitch) : NULL;
+        bool found_ls
+            = ps && vtep_lswitch && shash_find(&ps->ls_to_pb, vtep_lswitch);
+
+        if (!strcmp(type, "vtep") && found_ls) {
+            bool pb_conflict, db_conflict;
+
+            pb_conflict = check_pb_conflict(&ps->ls_to_pb, port_binding_rec,
+                                            ps->chassis_rec->name);
+            db_conflict = check_db_conflict(&ls_to_db, port_binding_rec,
+                                            ps->chassis_rec->name);
+            /* Updates port binding's chassis column when there
+             * is no conflict. */
+            if (!pb_conflict && !db_conflict) {
+                update_pb_chassis(port_binding_rec, ps->chassis_rec);
+            }
+        } else if (port_binding_rec->chassis
+                   && shash_find(&ps_map, port_binding_rec->chassis->name)) {
+            /* Resets 'port_binding_rec' since it is no longer bound to
+             * any vtep logical switch. */
+            update_pb_chassis(port_binding_rec, NULL);
+        }
+    }
+
+    struct shash_node *iter, *next;
+    SHASH_FOR_EACH_SAFE (iter, next, &ps_map) {
+        struct ps *ps = iter->data;
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &ps->ls_to_pb) {
+            if (!node->data) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_DBG_RL(&rl, "No port binding entry for logical switch (%s)"
+                            "on vtep gateway chassis (%s)", node->name,
+                            ps->chassis_rec->name);
+            }
+        }
+        shash_delete(&ps_map, iter);
+        shash_destroy(&ps->ls_to_pb);
+        free(ps);
+    }
+    shash_destroy(&ls_to_db);
+    shash_destroy(&ps_map);
+}
+
+/* Removes all port binding association with vtep gateway chassis.
+ * Returns true when all done. */
+bool
+binding_cleanup(struct controller_vtep_ctx *ctx)
+{
+    if (!ctx->ovnsb_idl_txn) {
+        return false;
+    }
+
+    struct shash ch_to_pb = SHASH_INITIALIZER(&ch_to_pb);
+    const struct sbrec_port_binding *port_binding_rec;
+    bool all_done = true;
+    /* Hashs all port binding entries using the associated chassis name. */
+    SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) {
+        if (port_binding_rec->chassis) {
+            shash_add(&ch_to_pb, port_binding_rec->chassis->name,
+                      port_binding_rec);
+        }
+    }
+
+    ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn,
+                              "ovn-controller-vtep: removing bindings");
+
+    const struct vteprec_physical_switch *pswitch;
+    VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) {
+        const struct sbrec_chassis *chassis_rec
+            = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name);
+
+        for (;;) {
+            port_binding_rec = shash_find_and_delete(&ch_to_pb,
+                                                     chassis_rec->name);
+            if (!port_binding_rec) {
+                break;
+            }
+            all_done = false;
+            update_pb_chassis(port_binding_rec, NULL);
+        }
+    }
+    shash_destroy(&ch_to_pb);
+
+    return all_done;
+}
diff --git a/ovn/controller-vtep/binding.h b/ovn/controller-vtep/binding.h
new file mode 100644
index 0000000..374c1cc
--- /dev/null
+++ b/ovn/controller-vtep/binding.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef OVN_BINDING_H
+#define OVN_BINDING_H 1
+
+#include <stdbool.h>
+
+struct controller_vtep_ctx;
+
+void binding_run(struct controller_vtep_ctx *);
+bool binding_cleanup(struct controller_vtep_ctx *);
+
+#endif /* ovn/controller-gw/binding.h */
diff --git a/ovn/controller-vtep/ovn-controller-vtep.c b/ovn/controller-vtep/ovn-controller-vtep.c
index 93a0458..a3b0f96 100644
--- a/ovn/controller-vtep/ovn-controller-vtep.c
+++ b/ovn/controller-vtep/ovn-controller-vtep.c
@@ -37,6 +37,7 @@
 #include "ovn/lib/ovn-sb-idl.h"
 #include "vtep/vtep-idl.h"
 
+#include "binding.h"
 #include "gateway.h"
 #include "ovn-controller-vtep.h"
 
@@ -95,6 +96,7 @@ main(int argc, char *argv[])
         };
 
         gateway_run(&ctx);
+        binding_run(&ctx);
         unixctl_server_run(unixctl);
 
         unixctl_server_wait(unixctl);
@@ -119,6 +121,7 @@ main(int argc, char *argv[])
         /* Run all of the cleanup functions, even if one of them returns false.
          * We're done if all of them return true. */
         done = gateway_cleanup(&ctx);
+        done = binding_cleanup(&ctx) && done;
         if (done) {
             poll_immediate_wake();
         }
@@ -129,6 +132,7 @@ main(int argc, char *argv[])
     }
 
     unixctl_server_destroy(unixctl);
+
     ovsdb_idl_loop_destroy(&vtep_idl_loop);
     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
 
diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
index ed9cc50..3ff0b93 100644
--- a/tests/ovn-controller-vtep.at
+++ b/tests/ovn-controller-vtep.at
@@ -84,7 +84,19 @@ m4_define([OVN_CONTROLLER_VTEP_STOP],
    AT_CHECK([ovs-appctl -t ovn-controller-vtep exit])
    AT_CHECK([ovs-appctl -t ovs-vswitchd exit])])
 
+# Adds logical port for a vtep gateway chassis in ovn-nb database.
+#
+# $1: logical switch name in ovn-nb database
+# $2: logical port name
+# $3: physical vtep gateway name
+# $4: logical switch name on vtep gateway chassis
+m4_define([OVN_NB_ADD_VTEP_PORT], [
+AT_CHECK([ovn-nbctl lport-add $1 $2])
+AT_CHECK([ovn-nbctl lport-set-type $2 vtep])
+AT_CHECK([ovn-nbctl lport-set-options $2 vtep-physical-switch=$3 vtep-logical-switch=$4])
+])
 
+##############################################
 
 # tests chassis related updates.
 AT_SETUP([ovn-controller-vtep - test chassis])
@@ -150,3 +162,103 @@ AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f
 
 OVN_CONTROLLER_VTEP_STOP(["/Chassis for VTEP physical switch (br-vtep) disappears/d"])
 AT_CLEANUP
+
+
+# Tests binding updates.
+AT_SETUP([ovn-controller-vtep - test binding 1])
+OVN_CONTROLLER_VTEP_START
+
+# adds logical switch 'lswitch0' and vlan_bindings.
+AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p1 300 lswitch0])
+# adds lport in ovn-nb db, and sets the type and options.
+OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-vtep_lswitch0`"])
+# should see one binding.
+chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ')
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
+${chassis_uuid}
+])
+
+# adds another logical switch 'lswitch1' and vlan_bindings.
+AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1])
+# adds lport in ovn-nb db for lswitch1.
+OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep -- br-vtep_lswitch1`"])
+# This is allowed, but not recommended.
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort -d], [0], [dnl
+
+${chassis_uuid}
+${chassis_uuid}
+])
+
+# adds another lport in ovn-nb db for lswitch0.
+OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep -- br-vtep_lswitch0_dup`"])
+# This is not allowed, so should still see only two port_binding entries bound.
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort -d], [0], [dnl
+
+
+[[]]
+${chassis_uuid}
+${chassis_uuid}
+])
+# confirms the warning log.
+AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g' | uniq], [0], [dnl
+|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical port (), ignore logical port ()
+])
+
+# deletes physical ports from vtep.
+AT_CHECK([ovs-vsctl del-port p0 -- del-port p1])
+AT_CHECK([vtep-ctl del-port br-vtep p0 -- del-port br-vtep p1])
+OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"])
+# should see empty chassis column in both binding entries.
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl
+
+
+[[]]
+[[]]
+[[]]
+])
+
+OVN_CONTROLLER_VTEP_STOP(["/has already been associated with logical port/d"])
+AT_CLEANUP
+
+
+# Tests corner case: Binding the vtep logical switch from two different
+# datapath.
+AT_SETUP([ovn-controller-vtep - test binding 2])
+OVN_CONTROLLER_VTEP_START
+
+# adds logical switch 'lswitch0' and vlan_bindings.
+AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0])
+# adds lport in ovn-nb db, and sets the type and options.
+OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-vtep_lswitch0`"])
+
+# adds another lswitch 'br-void' in ovn-nb database.
+AT_CHECK([ovn-nbctl lswitch-add br-void])
+# adds another vtep pswitch 'br-vtep-void' in vtep database.
+AT_CHECK([vtep-ctl add-ps br-vtep-void -- add-port br-vtep-void p0-void -- bind-ls br-vtep-void p0-void 100 lswitch0])
+# adds a conflicting logical port (both br-vtep_lswitch0 and br-vtep-void_lswitch0
+# are bound to the same logical switch, but they are on different datapath).
+OVN_NB_ADD_VTEP_PORT([br-void], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0])
+OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding  | grep br-vtep-void_lswitch0`"])
+OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"])
+# confirms the warning log.
+AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g;s/(with tunnel key [[0-9]][[0-9]]*)/()/g' | uniq], [0], [dnl
+|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical datapath (), ignore logical port () which belongs to logical datapath ()
+])
+
+# then deletes 'br-void' and 'br-vtep-void', should see 'br-vtep_lswitch0'
+# bound correctly.
+AT_CHECK([ovn-nbctl lswitch-del br-void])
+# adds another vtep pswitch 'br-vtep-void' in vtep database.
+AT_CHECK([vtep-ctl del-ps br-vtep-void])
+OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Port_Binding | grep br-vtep-void_lswitch0`"])
+chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ')
+AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
+${chassis_uuid}
+])
+
+OVN_CONTROLLER_VTEP_STOP(["/has already been associated with logical datapath/d"])
+AT_CLEANUP
-- 
1.7.9.5




More information about the dev mailing list