From: Alex Wang Date: Sun, 9 Aug 2015 07:02:47 +0000 (-0700) Subject: ovn-controller-vtep: Add gateway module. X-Git-Tag: v2.5.0~755 X-Git-Url: http://git.cascardo.eti.br/?a=commitdiff_plain;h=0c1e8a7d637eb080e755c4ddca2e3d38583230e1;p=cascardo%2Fovs.git ovn-controller-vtep: Add gateway module. This commit adds the gateway module to ovn-controller-vtep. The module will register the physical switches to ovnsb as chassis and constantly update the "vtep_logical_switches" column in Chassis table. Limitation (Recorded in TODO file): - Do not support reading multiple tunnel ips of physical switch. Signed-off-by: Alex Wang Acked-by: Russell Bryant --- diff --git a/ovn/TODO b/ovn/TODO index 509e5d004..356b3bad2 100644 --- a/ovn/TODO +++ b/ovn/TODO @@ -74,3 +74,9 @@ Epstein et al., "What's the Difference? Efficient Set Reconciliation Without Prior Context". (I'm not yet aware of previous non-academic use of this technique.) + +** Support multiple tunnel encapsulations in Chassis. + + So far, both ovn-controller and ovn-controller-vtep only allow + chassis to have one tunnel encapsulation entry. We should extend + the implementation to support multiple tunnel encapsulations. diff --git a/ovn/controller-vtep/automake.mk b/ovn/controller-vtep/automake.mk index 7adda15ff..514cafa45 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/gateway.c \ + ovn/controller-vtep/gateway.h \ ovn/controller-vtep/ovn-controller-vtep.c \ ovn/controller-vtep/ovn-controller-vtep.h ovn_controller_vtep_ovn_controller_vtep_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la vtep/libvtep.la diff --git a/ovn/controller-vtep/gateway.c b/ovn/controller-vtep/gateway.c new file mode 100644 index 000000000..025aff850 --- /dev/null +++ b/ovn/controller-vtep/gateway.c @@ -0,0 +1,222 @@ +/* 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 +#include "gateway.h" + +#include "lib/poll-loop.h" +#include "lib/simap.h" +#include "lib/sset.h" +#include "lib/util.h" +#include "openvswitch/vlog.h" +#include "ovn/lib/ovn-sb-idl.h" +#include "vtep/vtep-idl.h" +#include "ovn-controller-vtep.h" + +VLOG_DEFINE_THIS_MODULE(gateway); + +/* + * Registers the physical switches in vtep to ovnsb as chassis. For each + * physical switch in the vtep database, finds all vtep logical switches that + * are associated with the physical switch, and updates the corresponding + * chassis's 'vtep_logical_switches' column. + * + */ + +/* Global revalidation sequence number, incremented at each call to + * 'revalidate_gateway()'. */ +static unsigned int gw_reval_seq; + +/* Maps all chassis created by the gateway module to their own reval_seq. */ +static struct simap gw_chassis_map = SIMAP_INITIALIZER(&gw_chassis_map); + +/* Creates and returns a new instance of 'struct sbrec_chassis'. */ +static const struct sbrec_chassis * +create_chassis_rec(struct ovsdb_idl_txn *txn, const char *name, + const char *encap_ip) +{ + const struct sbrec_chassis *chassis_rec; + struct sbrec_encap *encap_rec; + + VLOG_INFO("add Chassis row for VTEP physical switch (%s)", name); + + chassis_rec = sbrec_chassis_insert(txn); + sbrec_chassis_set_name(chassis_rec, name); + encap_rec = sbrec_encap_insert(txn); + sbrec_encap_set_type(encap_rec, OVN_SB_ENCAP_TYPE); + sbrec_encap_set_ip(encap_rec, encap_ip); + sbrec_chassis_set_encaps(chassis_rec, &encap_rec, 1); + + return chassis_rec; +} + +/* Revalidates chassis in ovnsb against vtep database. Creates chassis for + * new vtep physical switch. And removes chassis which no longer have + * physical switch in vtep. + * + * xxx: Support multiple tunnel encaps. + * + * */ +static void +revalidate_gateway(struct controller_vtep_ctx *ctx) +{ + const struct vteprec_physical_switch *pswitch; + + /* Increments the global revalidation sequence number. */ + gw_reval_seq++; + + ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, + "ovn-controller-vtep: updating vtep chassis"); + + VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { + const struct sbrec_chassis *chassis_rec; + struct simap_node *gw_node; + const char *encap_ip; + + encap_ip = pswitch->n_tunnel_ips ? pswitch->tunnel_ips[0] : ""; + gw_node = simap_find(&gw_chassis_map, pswitch->name); + chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); + if (chassis_rec) { + if (!gw_node && + (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE) + || strcmp(chassis_rec->encaps[0]->ip, encap_ip))) { + VLOG_WARN("Chassis config changing on startup, make sure " + "multiple chassis are not configured : %s/%s->%s/%s", + chassis_rec->encaps[0]->type, + chassis_rec->encaps[0]->ip, + OVN_SB_ENCAP_TYPE, encap_ip); + } + /* Updates chassis's encap if anything changed. */ + if (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE)) { + VLOG_WARN("Chassis for VTEP physical switch (%s) can only have " + "encap type \"%s\"", pswitch->name, OVN_SB_ENCAP_TYPE); + sbrec_encap_set_type(chassis_rec->encaps[0], OVN_SB_ENCAP_TYPE); + } + if (strcmp(chassis_rec->encaps[0]->ip, encap_ip)) { + sbrec_encap_set_ip(chassis_rec->encaps[0], encap_ip); + } + } else { + if (gw_node) { + VLOG_WARN("Chassis for VTEP physical switch (%s) disappears, " + "maybe deleted by ovn-sbctl, adding it back", + pswitch->name); + } + /* Creates a new chassis for the VTEP physical switch. */ + create_chassis_rec(ctx->ovnsb_idl_txn, pswitch->name, encap_ip); + } + /* Updates or creates the simap node for 'pswitch->name'. */ + simap_put(&gw_chassis_map, pswitch->name, gw_reval_seq); + } + + struct simap_node *iter, *next; + /* For 'gw_node' in 'gw_chassis_map' whose data is not + * 'gw_reval_seq', it means the corresponding physical switch no + * longer exist. So, garbage collects them. */ + SIMAP_FOR_EACH_SAFE (iter, next, &gw_chassis_map) { + if (iter->data != gw_reval_seq) { + const struct sbrec_chassis *chassis_rec; + + chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, iter->name); + if (chassis_rec) { + sbrec_chassis_delete(chassis_rec); + } + simap_delete(&gw_chassis_map, iter); + } + } +} + +/* Updates the 'vtep_logical_switches' column in the Chassis table based + * on vtep database configuration. */ +static void +update_vtep_logical_switches(struct controller_vtep_ctx *ctx) +{ + const struct vteprec_physical_switch *pswitch; + + ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: " + "updating chassis's vtep_logical_switches"); + + VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { + const struct sbrec_chassis *chassis_rec = + get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); + struct sset lswitches = SSET_INITIALIZER(&lswitches); + size_t i; + + for (i = 0; i < pswitch->n_ports; i++) { + const struct vteprec_physical_port *port = pswitch->ports[i]; + size_t j; + + for (j = 0; j < port->n_vlan_bindings; j++) { + const struct vteprec_logical_switch *vtep_lswitch; + + vtep_lswitch = port->value_vlan_bindings[j]; + /* If not already in 'lswitches', records it. */ + if (!sset_find(&lswitches, vtep_lswitch->name)) { + sset_add(&lswitches, vtep_lswitch->name); + } + } + } + + const char **ls_arr = sset_array(&lswitches); + sbrec_chassis_set_vtep_logical_switches(chassis_rec, ls_arr, + sset_count(&lswitches)); + free(ls_arr); + sset_destroy(&lswitches); + } +} + + +void +gateway_run(struct controller_vtep_ctx *ctx) +{ + if (!ctx->ovnsb_idl_txn) { + return; + } + + revalidate_gateway(ctx); + update_vtep_logical_switches(ctx); +} + +/* Destroys the chassis table entries for vtep physical switches. + * Returns true when all done. */ +bool +gateway_cleanup(struct controller_vtep_ctx *ctx) +{ + static bool simap_destroyed = false; + const struct vteprec_physical_switch *pswitch; + + if (!ctx->ovnsb_idl_txn) { + return false; + } + + bool all_done = true; + ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: " + "unregistering vtep chassis"); + VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { + const struct sbrec_chassis *chassis_rec; + + chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); + if (!chassis_rec) { + continue; + } + all_done = false; + sbrec_chassis_delete(chassis_rec); + } + if (!simap_destroyed) { + simap_destroy(&gw_chassis_map); + simap_destroyed = true; + } + + return all_done; +} diff --git a/ovn/controller-vtep/gateway.h b/ovn/controller-vtep/gateway.h new file mode 100644 index 000000000..0086191d9 --- /dev/null +++ b/ovn/controller-vtep/gateway.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2015 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVN_GATEWAY_H +#define OVN_GATEWAY_H 1 + +#include + +struct controller_vtep_ctx; + +void gateway_run(struct controller_vtep_ctx *); +bool gateway_cleanup(struct controller_vtep_ctx *); + +#endif /* ovn/controller-gw/gateway.h */ diff --git a/ovn/controller-vtep/ovn-controller-vtep.c b/ovn/controller-vtep/ovn-controller-vtep.c index 21a92decb..93a0458c0 100644 --- a/ovn/controller-vtep/ovn-controller-vtep.c +++ b/ovn/controller-vtep/ovn-controller-vtep.c @@ -21,8 +21,6 @@ #include #include -#include "ovn-controller-vtep.h" - #include "command-line.h" #include "compiler.h" #include "daemon.h" @@ -39,6 +37,9 @@ #include "ovn/lib/ovn-sb-idl.h" #include "vtep/vtep-idl.h" +#include "gateway.h" +#include "ovn-controller-vtep.h" + static unixctl_cb_func ovn_controller_vtep_exit; static void parse_options(int argc, char *argv[]); @@ -86,13 +87,14 @@ main(int argc, char *argv[]) /* Main loop. */ exiting = false; while (!exiting) { - struct controller_vtep_ctx OVS_UNUSED ctx = { + struct controller_vtep_ctx ctx = { .vtep_idl = vtep_idl_loop.idl, .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop), .ovnsb_idl = ovnsb_idl_loop.idl, .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop), }; + gateway_run(&ctx); unixctl_server_run(unixctl); unixctl_server_wait(unixctl); @@ -104,8 +106,29 @@ main(int argc, char *argv[]) poll_block(); } - unixctl_server_destroy(unixctl); + /* It's time to exit. Clean up the databases. */ + bool done = false; + while (!done) { + struct controller_vtep_ctx ctx = { + .vtep_idl = vtep_idl_loop.idl, + .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop), + .ovnsb_idl = ovnsb_idl_loop.idl, + .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop), + }; + /* Run all of the cleanup functions, even if one of them returns false. + * We're done if all of them return true. */ + done = gateway_cleanup(&ctx); + if (done) { + poll_immediate_wake(); + } + + ovsdb_idl_loop_commit_and_wait(&vtep_idl_loop); + ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); + poll_block(); + } + + unixctl_server_destroy(unixctl); ovsdb_idl_loop_destroy(&vtep_idl_loop); ovsdb_idl_loop_destroy(&ovnsb_idl_loop); diff --git a/ovn/controller-vtep/ovn-controller-vtep.h b/ovn/controller-vtep/ovn-controller-vtep.h index bb01eb918..435a730d9 100644 --- a/ovn/controller-vtep/ovn-controller-vtep.h +++ b/ovn/controller-vtep/ovn-controller-vtep.h @@ -17,6 +17,8 @@ #ifndef OVN_CONTROLLER_VTEP_H #define OVN_CONTROLLER_VTEP_H 1 +#include "ovn/lib/ovn-sb-idl.h" + struct ovsdb_idl; struct ovsdb_idl_txn; @@ -28,4 +30,22 @@ struct controller_vtep_ctx { struct ovsdb_idl_txn *vtep_idl_txn; }; +/* VTEP needs what VTEP needs. */ +#define OVN_SB_ENCAP_TYPE "vxlan" +#define VTEP_ENCAP_TYPE "vxlan_over_ipv4" + +static inline const struct sbrec_chassis * +get_chassis_by_name(struct ovsdb_idl *ovnsb_idl, const char *chassis_id) +{ + const struct sbrec_chassis *chassis_rec; + + SBREC_CHASSIS_FOR_EACH(chassis_rec, ovnsb_idl) { + if (!strcmp(chassis_rec->name, chassis_id)) { + break; + } + } + + return chassis_rec; +} + #endif /* ovn/ovn-controller-vtep.h */ diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at index bcae89cf4..ed9cc50b1 100644 --- a/tests/ovn-controller-vtep.at +++ b/tests/ovn-controller-vtep.at @@ -1 +1,152 @@ AT_BANNER([ovn_controller_vtep]) + +# OVN_CONTROLLER_VTEP_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_VTEP_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-vtep. + AT_CHECK([ovn-controller-vtep --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-vtep.log]) + ON_EXIT_UNQUOTED([kill `cat ovn-controller-vtep.pid`]) + AT_CHECK([[sed < stderr ' +/vlog|INFO|opened log file/d +/reconnect|INFO|/d']]) +]) + +# OVN_CONTROLLER_VTEP_STOP +# +# So many exits... Yeah, we started a lot daemons~ +# +m4_define([OVN_CONTROLLER_VTEP_STOP], + [AT_CHECK([check_logs $1]) + AT_CHECK([ovs-appctl -t ovs-vtep exit]) + AT_CHECK([ovs-appctl -t ovn-northd exit]) + AT_CHECK([ovs-appctl -t ovsdb-server exit]) + AT_CHECK([ovs-appctl -t ovn-controller-vtep exit]) + AT_CHECK([ovs-appctl -t ovs-vswitchd exit])]) + + + +# tests chassis related updates. +AT_SETUP([ovn-controller-vtep - test chassis]) +OVN_CONTROLLER_VTEP_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 chassis-del br-vtep]) +OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"]) +AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log], [0], [dnl +|WARN|Chassis for VTEP physical switch (br-vtep) disappears, maybe deleted by ovn-sbctl, adding it back +]) + +# changes the tunnel_ip on physical switch, watches the update of chassis's +# encap. +AT_CHECK([vtep-ctl set Physical_Switch br-vtep tunnel_ips=1.2.3.5]) +OVS_WAIT_UNTIL([test -n "`ovn-sbctl show | grep 1\.2\.3\.5`"]) +AT_CHECK([ovn-sbctl --columns=ip list Encap | cut -d ':' -f2 | tr -d ' '], [0], [dnl +"1.2.3.5" +]) + +# adds vlan_bindings to physical ports. +AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p0 200 lswitch0 -- bind-ls br-vtep p1 300 lswitch0]) +OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch0`"]) +AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl +[["lswitch0"]] +]) + +# adds another logical switch and new vlan_bindings. +AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 300 lswitch1]) +OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch1`"]) +AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl +[["lswitch0","lswitch1"]] +]) + +# unbinds one port from lswitch0, nothing should change. +AT_CHECK([vtep-ctl unbind-ls br-vtep p0 200]) +OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=vlan_bindings list physical_port p0 | grep -- 200`"]) +AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl +[["lswitch0","lswitch1"]] +]) + +# unbinds all ports from lswitch0. +AT_CHECK([vtep-ctl unbind-ls br-vtep p0 100 -- unbind-ls br-vtep p1 300]) +OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch0`"]) +AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl +[["lswitch1"]] +]) + +# unbinds all ports from lswitch1. +AT_CHECK([vtep-ctl unbind-ls br-vtep p0 300]) +OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch1`"]) +AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl +[[]] +]) + +OVN_CONTROLLER_VTEP_STOP(["/Chassis for VTEP physical switch (br-vtep) disappears/d"]) +AT_CLEANUP