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 <al...@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 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev