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.
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
--- /dev/null
+/* 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/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);
+ }
+}
+
+\f
+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;
+}
--- /dev/null
+/* 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 <stdbool.h>
+
+struct controller_vtep_ctx;
+
+void gateway_run(struct controller_vtep_ctx *);
+bool gateway_cleanup(struct controller_vtep_ctx *);
+
+#endif /* ovn/controller-gw/gateway.h */
#include <stdlib.h>
#include <string.h>
-#include "ovn-controller-vtep.h"
-
#include "command-line.h"
#include "compiler.h"
#include "daemon.h"
#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[]);
/* 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);
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);
#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;
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 */
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