From: Numan Siddique <[email protected]>
Signed-off-by: Numan Siddique <[email protected]>
---
br-controller/automake.mk | 2 +
br-controller/br-ofctrl.c | 730 ++++++++++++++++++++++++++++++
br-controller/br-ofctrl.h | 33 ++
br-controller/en-bridge-data.c | 40 ++
br-controller/en-bridge-data.h | 4 +
br-controller/ovn-br-controller.c | 116 ++++-
tests/automake.mk | 5 +-
tests/ovn-br-controller.at | 330 ++++++++++++++
tests/testsuite.at | 1 +
9 files changed, 1255 insertions(+), 6 deletions(-)
create mode 100644 br-controller/br-ofctrl.c
create mode 100644 br-controller/br-ofctrl.h
create mode 100644 tests/ovn-br-controller.at
diff --git a/br-controller/automake.mk b/br-controller/automake.mk
index 4baea4f6fe..f8cae3a098 100644
--- a/br-controller/automake.mk
+++ b/br-controller/automake.mk
@@ -2,6 +2,8 @@ bin_PROGRAMS += br-controller/ovn-br-controller
br_controller_ovn_br_controller_SOURCES = \
br-controller/br-flow-mgr.c \
br-controller/br-flow-mgr.h \
+ br-controller/br-ofctrl.c \
+ br-controller/br-ofctrl.h \
br-controller/en-bridge-data.c \
br-controller/en-bridge-data.h \
br-controller/en-lflow.c \
diff --git a/br-controller/br-ofctrl.c b/br-controller/br-ofctrl.c
new file mode 100644
index 0000000000..ababee463f
--- /dev/null
+++ b/br-controller/br-ofctrl.c
@@ -0,0 +1,730 @@
+/*
+ * 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>
+
+/* OVS includes. */
+#include "bitmap.h"
+#include "byte-order.h"
+#include "dirs.h"
+#include "dp-packet.h"
+#include "flow.h"
+#include "hash.h"
+#include "hindex.h"
+#include "lib/socket-util.h"
+#include "lib/util.h"
+#include "lib/vswitch-idl.h"
+#include "openflow/openflow.h"
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/list.h"
+#include "openvswitch/match.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-bundle.h"
+#include "openvswitch/ofp-flow.h"
+#include "openvswitch/ofp-group.h"
+#include "openvswitch/ofp-match.h"
+#include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-meter.h"
+#include "openvswitch/ofp-packet.h"
+#include "openvswitch/ofp-print.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/vlog.h"
+#include "openvswitch/poll-loop.h"
+#include "openvswitch/rconn.h"
+
+/* OVN includes. */
+#include "br-flow-mgr.h"
+#include "en-bridge-data.h"
+#include "br-ofctrl.h"
+#include "lib/ovn-util.h"
+#include "lib/ovn-br-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(brofctrl);
+
+/* Connection state machine. */
+#define STATES \
+ STATE(S_NEW) \
+ STATE(S_WAIT_BEFORE_CLEAR) \
+ STATE(S_CLEAR_FLOWS) \
+ STATE(S_UPDATE_FLOWS)
+
+enum br_ofctrl_state {
+#define STATE(NAME) NAME,
+ STATES
+#undef STATE
+};
+
+/* An in-flight update to the switch's flow table.
+ *
+ * When we receive a barrier reply from the switch with the given 'xid', we
+ * know that the switch is caught up to the requested sequence number
+ * 'req_cfg' (and make that available to the client via
+ * br_ofctrl_get_cur_cfg(), so that it can store it into external state. */
+struct br_ofctrl_flow_update {
+ struct ovs_list list_node; /* In 'flow_updates'. */
+ ovs_be32 xid; /* OpenFlow transaction ID for barrier. */
+ uint64_t req_cfg; /* Requested sequence number. */
+};
+
+struct br_ofctrl {
+ struct hmap_node hmap_node;
+ char *bridge; /* key. */
+
+ /* OpenFlow connection to the switch. */
+ struct rconn *swconn;
+ int probe_interval;
+ char *conn_target;
+
+ unsigned int wait_before_clear_time;
+ /* The time when the state S_WAIT_BEFORE_CLEAR should complete.
+ * If the timer is not started yet, it is set to 0. */
+ long long int wait_before_clear_expire;
+
+ /* Currently in-flight updates. */
+ struct ovs_list flow_updates;
+
+ /* req_cfg of latest committed flow update. */
+ uint64_t cur_cfg;
+ uint64_t old_req_cfg;
+ bool skipped_last_time;
+
+ /* Indicates if we just went through the S_CLEAR_FLOWS state, which means
+ * we need to perform a one time deletion for all the existing flows,
+ * groups and meters. This can happen during initialization or OpenFlow
+ * reconnection (e.g. after OVS restart). */
+ bool br_ofctrl_initial_clear;
+
+ /* Last seen sequence number for 'swconn'. When this differs from
+ * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
+ unsigned int seqno;
+
+ /* Counter for in-flight OpenFlow messages on 'swconn'. We only send a new
+ * round of flow table modifications to the switch when the counter falls
+ * to zero, to avoid unbounded buffering. */
+ struct rconn_packet_counter *tx_counter;
+
+ /* Current state. */
+ enum br_ofctrl_state state;
+};
+
+static struct hmap br_ofctrls = HMAP_INITIALIZER(&br_ofctrls);
+
+static struct br_ofctrl *br_ofctrl_get(const char *bridge);
+static void br_ofctrl_put(struct br_ofctrl *br_ofctrl, uint64_t req_cfg,
+ bool lflows_changed, bool pflows_changed);
+static void br_ofctrl_destroy(struct br_ofctrl *);
+static ovs_be32 queue_msg(struct br_ofctrl *, struct ofpbuf *);
+static struct br_ofctrl_flow_update *br_ofctrl_flow_update_from_list_node(
+ const struct ovs_list *);
+static bool br_ofctrl_run__(struct br_ofctrl *);
+static bool br_ofctrl_has_backlog(struct br_ofctrl *);
+static bool br_ofctrl_can_put(struct br_ofctrl *);
+
+void
+br_ofctrls_init(void)
+{
+
+}
+
+void
+br_ofctrls_destroy(void)
+{
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH_POP (br_ofctrl, hmap_node, &br_ofctrls) {
+ br_ofctrl_destroy(br_ofctrl);
+ }
+
+ hmap_destroy(&br_ofctrls);
+}
+
+void
+br_ofctrls_add_or_update_bridge(struct ovn_bridge *br)
+{
+ ovs_assert(br->ovs_br);
+
+ struct br_ofctrl *br_ofctrl = br_ofctrl_get(br->db_br->name);
+
+ if (!br_ofctrl) {
+ br_ofctrl = xzalloc(sizeof *br_ofctrl);
+ br_ofctrl->bridge = xstrdup(br->db_br->name);
+ br_ofctrl->swconn = rconn_create(0, 0, DSCP_DEFAULT,
+ 1 << OFP15_VERSION);
+ br_ofctrl->tx_counter = rconn_packet_counter_create();
+ ovs_list_init(&br_ofctrl->flow_updates);
+
+ hmap_insert(&br_ofctrls, &br_ofctrl->hmap_node,
+ hash_string(br_ofctrl->bridge, 0));
+ } else {
+ free(br_ofctrl->conn_target);
+ }
+
+ br_ofctrl->probe_interval = br->probe_interval;
+ br_ofctrl->conn_target = xstrdup(br->conn_target);
+ br_ofctrl->wait_before_clear_time = br->wait_before_clear_time;
+}
+
+void
+br_ofctrls_remove_bridge(const char *bridge)
+{
+ struct br_ofctrl *br_ofctrl = br_ofctrl_get(bridge);
+ if (br_ofctrl) {
+ hmap_remove(&br_ofctrls, &br_ofctrl->hmap_node);
+ br_ofctrl_destroy(br_ofctrl);
+ }
+}
+
+void
+br_ofctrls_get_bridges(struct sset *managed_bridges)
+{
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH (br_ofctrl, hmap_node, &br_ofctrls) {
+ sset_add(managed_bridges, br_ofctrl->bridge);
+ }
+}
+
+/* Runs the OpenFlow state machine against each bridge in the br_ofctrls hmap,
+ * which is local to the hypervisor on which we are running.
+ *
+ * Returns 'true' if an OpenFlow reconnect happened for any of the bridge;
+ * 'false' otherwise.
+ */
+bool
+br_ofctrls_run(void)
+{
+ bool reconnected = false;
+
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH (br_ofctrl, hmap_node, &br_ofctrls) {
+ reconnected |= br_ofctrl_run__(br_ofctrl);
+ }
+
+ return reconnected;
+}
+
+/* Programs the flow table on the switch, if possible, by the flows
+ * added to the br-flow-mgr.
+ *
+ * This should be called after br_ofctrls_run() within the main loop. */
+void
+br_ofctrls_put(uint64_t req_cfg, bool lflows_changed, bool pflows_changed)
+{
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH (br_ofctrl, hmap_node, &br_ofctrls) {
+ br_ofctrl_put(br_ofctrl, req_cfg, lflows_changed, pflows_changed);
+ }
+}
+
+void
+br_ofctrls_wait(void)
+{
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH (br_ofctrl, hmap_node, &br_ofctrls) {
+ rconn_run_wait(br_ofctrl->swconn);
+ rconn_recv_wait(br_ofctrl->swconn);
+ }
+}
+
+uint64_t
+br_ofctrl_get_cur_cfg(void)
+{
+ uint64_t of_cur_cfg = UINT64_MAX;
+ struct br_ofctrl *br_ofctrl;
+ HMAP_FOR_EACH (br_ofctrl, hmap_node, &br_ofctrls) {
+ of_cur_cfg = MIN(of_cur_cfg, br_ofctrl->cur_cfg);
+ }
+
+ return of_cur_cfg;
+}
+
+/* Static functions. */
+
+static void
+br_ofctrl_destroy(struct br_ofctrl *br_ofctrl)
+{
+ rconn_destroy(br_ofctrl->swconn);
+ rconn_packet_counter_destroy(br_ofctrl->tx_counter);
+ free(br_ofctrl->bridge);
+ free(br_ofctrl);
+}
+
+static struct br_ofctrl *
+br_ofctrl_get(const char *bridge)
+{
+ struct br_ofctrl *br_ofctrl;
+ uint32_t hash = hash_string(bridge, 0);
+ HMAP_FOR_EACH_WITH_HASH (br_ofctrl, hmap_node, hash, &br_ofctrls) {
+ if (!strcmp(br_ofctrl->bridge, bridge)) {
+ return br_ofctrl;
+ }
+ }
+
+ return NULL;
+}
+
+static ovs_be32
+queue_msg(struct br_ofctrl *br_ofctrl, struct ofpbuf *msg)
+{
+ const struct ofp_header *oh = msg->data;
+ ovs_be32 xid_ = oh->xid;
+ rconn_send(br_ofctrl->swconn, msg, br_ofctrl->tx_counter);
+ return xid_;
+}
+
+static void
+log_openflow_rl(struct vlog_rate_limit *rl, enum vlog_level level,
+ const struct ofp_header *oh, const char *title)
+{
+ if (!vlog_should_drop(&this_module, level, rl)) {
+ char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 2);
+ vlog(&this_module, level, "%s: %s", title, s);
+ free(s);
+ }
+}
+
+static struct br_ofctrl_flow_update *
+br_ofctrl_flow_update_from_list_node(const struct ovs_list *list_node)
+{
+ return CONTAINER_OF(list_node, struct br_ofctrl_flow_update, list_node);
+}
+
+/* br_ofctrl state machine functions. */
+
+static void
+br_ofctrl_recv(struct br_ofctrl *br_ofctrl, const struct ofp_header *oh,
+ enum ofptype type)
+{
+ if (type == OFPTYPE_ECHO_REQUEST) {
+ queue_msg(br_ofctrl, ofputil_encode_echo_reply(oh));
+ } else if (type == OFPTYPE_ERROR) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+ log_openflow_rl(&rl, VLL_INFO, oh, "OpenFlow error");
+ rconn_reconnect(br_ofctrl->swconn);
+ } else {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+ log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored");
+ }
+}
+
+
+/* S_NEW, for a new connection.
+ *
+ */
+
+static void
+run_S_NEW(struct br_ofctrl *br_ofctrl)
+{
+ br_ofctrl->state = S_WAIT_BEFORE_CLEAR;
+}
+
+static void
+recv_S_NEW(struct br_ofctrl *br_ofctrl OVS_UNUSED,
+ const struct ofp_header *oh OVS_UNUSED,
+ enum ofptype type OVS_UNUSED)
+{
+ OVS_NOT_REACHED();
+}
+
+/* S_WAIT_BEFORE_CLEAR, we are almost ready to set up flows, but just wait for
+ * a while until the initial flow compute to complete before we clear the
+ * existing flows in OVS, so that we won't end up with an empty flow table,
+ * which may cause data plane down time. */
+static void
+run_S_WAIT_BEFORE_CLEAR(struct br_ofctrl *br_ofctrl)
+{
+ if (!br_ofctrl->wait_before_clear_time ||
+ (br_ofctrl->wait_before_clear_expire &&
+ time_msec() >= br_ofctrl->wait_before_clear_expire)) {
+ br_ofctrl->state = S_CLEAR_FLOWS;
+ return;
+ }
+
+ if (!br_ofctrl->wait_before_clear_expire) {
+ /* Start the timer. */
+ br_ofctrl->wait_before_clear_expire =
+ time_msec() + br_ofctrl->wait_before_clear_time;
+ }
+ poll_timer_wait_until(br_ofctrl->wait_before_clear_expire);
+}
+
+static void
+recv_S_WAIT_BEFORE_CLEAR(struct br_ofctrl *br_ofctrl,
+ const struct ofp_header *oh, enum ofptype type)
+{
+ br_ofctrl_recv(br_ofctrl, oh, type);
+}
+
+/* Sends an OFPT_TABLE_MOD to clear all flows, then transitions to
+ * S_UPDATE_FLOWS. */
+
+static void
+run_S_CLEAR_FLOWS(struct br_ofctrl *br_ofctrl)
+{
+ VLOG_DBG("clearing all flows for bridge %s", br_ofctrl->bridge);
+
+ /* Set the flag so that the ofctrl_run() can clear the existing flows,
+ * groups and meters. We clear them in ofctrl_run() right before the new
+ * ones are installed to avoid data plane downtime. */
+ br_ofctrl->br_ofctrl_initial_clear = true;
+
+ /* Clear installed_flows, to match the state of the switch. */
+ br_flow_flush_oflows(br_ofctrl->bridge);
+
+ /* All flow updates are irrelevant now. */
+ struct br_ofctrl_flow_update *fup;
+ LIST_FOR_EACH_SAFE (fup, list_node, &br_ofctrl->flow_updates) {
+ ovs_list_remove(&fup->list_node);
+ free(fup);
+ }
+
+ br_ofctrl->state = S_UPDATE_FLOWS;
+
+ /* Give a chance for the main loop to call br_ofctrl_put() in case there
+ * were pending flows waiting ofctrl state change to S_UPDATE_FLOWS. */
+ poll_immediate_wake();
+}
+
+static void
+recv_S_CLEAR_FLOWS(struct br_ofctrl *br_ofctrl,
+ const struct ofp_header *oh, enum ofptype type)
+{
+ br_ofctrl_recv(br_ofctrl, oh, type);
+}
+
+/* S_UPDATE_FLOWS, for maintaining the flow table over time.
+ *
+ * Compare the installed flows to the ones we want. Send OFPT_FLOW_MOD as
+ * necessary.
+ *
+ * This is a terminal state. We only transition out of it if the connection
+ * drops. */
+
+static void
+run_S_UPDATE_FLOWS(struct br_ofctrl *br_ofctrl OVS_UNUSED)
+{
+ /* Nothing to do here.
+ *
+ * Being in this state enables br_ofctrl_put() to work, however. */
+}
+
+static void
+br_flow_updates_handle_barrier_reply(struct br_ofctrl *br_ofctrl,
+ const struct ofp_header *oh)
+{
+ if (ovs_list_is_empty(&br_ofctrl->flow_updates)) {
+ return;
+ }
+
+ struct br_ofctrl_flow_update *fup = br_ofctrl_flow_update_from_list_node(
+ ovs_list_front(&br_ofctrl->flow_updates));
+ if (fup->xid == oh->xid) {
+ if (fup->req_cfg >= br_ofctrl->cur_cfg) {
+ br_ofctrl->cur_cfg = fup->req_cfg;
+ }
+ ovs_list_remove(&fup->list_node);
+ free(fup);
+ }
+}
+
+static void
+recv_S_UPDATE_FLOWS(struct br_ofctrl *br_ofctrl,
+ const struct ofp_header *oh, enum ofptype type)
+{
+ if (type == OFPTYPE_BARRIER_REPLY) {
+ br_flow_updates_handle_barrier_reply(br_ofctrl, oh);
+ } else {
+ br_ofctrl_recv(br_ofctrl, oh, type);
+ }
+}
+
+static bool
+br_ofctrl_run__(struct br_ofctrl *br_ofctrl)
+{
+ struct rconn *swconn = br_ofctrl->swconn;
+
+ ovn_update_swconn_at(swconn, br_ofctrl->conn_target,
+ br_ofctrl->probe_interval, "br_ofctrl");
+ rconn_run(swconn);
+
+ if (!rconn_is_connected(swconn)) {
+ return false;
+ }
+
+ bool reconnected = false;
+
+ if (br_ofctrl->seqno != rconn_get_connection_seqno(swconn)) {
+ br_ofctrl->seqno = rconn_get_connection_seqno(swconn);
+ reconnected = true;
+ br_ofctrl->state = S_NEW;
+ }
+
+ bool progress = true;
+ for (int i = 0; progress && i < 50; i++) {
+ /* Allow the state machine to run. */
+ enum br_ofctrl_state old_state = br_ofctrl->state;
+ switch (br_ofctrl->state) {
+#define STATE(NAME) case NAME: run_##NAME(br_ofctrl); break;
+ STATES
+#undef STATE
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ /* Try to process a received packet. */
+ struct ofpbuf *msg = rconn_recv(swconn);
+ if (msg) {
+ const struct ofp_header *oh = msg->data;
+ enum ofptype type;
+ enum ofperr error;
+
+ error = ofptype_decode(&type, oh);
+ if (!error) {
+ switch (br_ofctrl->state) {
+#define STATE(NAME) case NAME: recv_##NAME(br_ofctrl, oh, type); break;
+ STATES
+#undef STATE
+ default:
+ OVS_NOT_REACHED();
+ }
+ } else {
+ char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1);
+ VLOG_WARN("could not decode OpenFlow message (%s): %s",
+ ofperr_to_string(error), s);
+ free(s);
+ }
+
+ ofpbuf_delete(msg);
+ }
+
+ /* If we did some work, plan to go around again. */
+ progress = old_state != br_ofctrl->state || msg;
+ }
+ if (progress) {
+ /* We bailed out to limit the amount of work we do in one go, to allow
+ * other code a chance to run. We were still making progress at that
+ * point, so ensure that we come back again without waiting. */
+ poll_immediate_wake();
+ }
+
+ return reconnected;
+}
+
+static bool
+br_ofctrl_has_backlog(struct br_ofctrl *br_ofctrl)
+{
+ if (rconn_packet_counter_n_packets(br_ofctrl->tx_counter)
+ || rconn_get_version(br_ofctrl->swconn) < 0) {
+ return true;
+ }
+ return false;
+}
+
+/* The flow table can be updated if the connection to the switch is up and
+ * in the correct state and not backlogged with existing flow_mods. (Our
+ * criteria for being backlogged appear very conservative, but the socket
+ * between ovn-controller and OVS provides some buffering.) */
+static bool
+br_ofctrl_can_put(struct br_ofctrl *br_ofctrl)
+{
+ if (br_ofctrl->state != S_UPDATE_FLOWS
+ || br_ofctrl_has_backlog(br_ofctrl)) {
+ return false;
+ }
+ return true;
+}
+
+static struct ofpbuf *
+encode_flow_mod(struct ofputil_flow_mod *fm)
+{
+ fm->buffer_id = UINT32_MAX;
+ fm->out_port = OFPP_ANY;
+ fm->out_group = OFPG_ANY;
+ return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF15_OXM);
+}
+
+static struct ofpbuf *
+encode_bundle_add(struct ofpbuf *msg, struct ofputil_bundle_ctrl_msg *bc)
+{
+ struct ofputil_bundle_add_msg bam = {
+ .bundle_id = bc->bundle_id,
+ .flags = bc->flags,
+ .msg = msg->data,
+ };
+ return ofputil_encode_bundle_add(OFP15_VERSION, &bam);
+}
+
+static bool
+add_flow_mod(struct ofputil_flow_mod *fm,
+ struct ofputil_bundle_ctrl_msg *bc,
+ struct ovs_list *msgs)
+{
+ struct ofpbuf *msg = encode_flow_mod(fm);
+ struct ofpbuf *bundle_msg = encode_bundle_add(msg, bc);
+
+ uint32_t flow_mod_len = msg->size;
+ uint32_t bundle_len = bundle_msg->size;
+
+ ofpbuf_delete(msg);
+
+ if (flow_mod_len > UINT16_MAX || bundle_len > UINT16_MAX) {
+ ofpbuf_delete(bundle_msg);
+
+ return false;
+ }
+
+ ovs_list_push_back(msgs, &bundle_msg->list_node);
+ return true;
+}
+
+static void
+br_ofctrl_put(struct br_ofctrl *br_ofctrl, uint64_t req_cfg,
+ bool lflows_changed, bool pflows_changed)
+{
+ bool need_put = false;
+
+ if (lflows_changed || pflows_changed || br_ofctrl->skipped_last_time ||
+ br_ofctrl->br_ofctrl_initial_clear) {
+ need_put = true;
+ br_ofctrl->old_req_cfg = req_cfg;
+ } else if (req_cfg != br_ofctrl->old_req_cfg) {
+ /* req_cfg changed since last br_ofctrl_put() call */
+ if (br_ofctrl->cur_cfg == br_ofctrl->old_req_cfg) {
+ /* If there are no updates pending, we were up-to-date already,
+ * update with the new req_cfg.
+ */
+ if (ovs_list_is_empty(&br_ofctrl->flow_updates)) {
+ br_ofctrl->cur_cfg = req_cfg;
+ br_ofctrl->old_req_cfg = req_cfg;
+ }
+ } else {
+ need_put = true;
+ br_ofctrl->old_req_cfg = req_cfg;
+ }
+ }
+
+ if (!need_put) {
+ VLOG_DBG("br_ofctrl_put not needed for bridge %s", br_ofctrl->bridge);
+ return;
+ }
+
+ /* OpenFlow messages to send to the switch to bring it up-to-date. */
+ struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs);
+
+ if (!br_ofctrl_can_put(br_ofctrl)) {
+ VLOG_DBG("br_ofctrl_put can't be performed for bridge %s",
+ br_ofctrl->bridge);
+
+ br_ofctrl->skipped_last_time = true;
+ return;
+ }
+
+ /* Add all flow updates into a bundle. */
+ static int bundle_id = 0;
+ struct ofputil_bundle_ctrl_msg bc = {
+ .bundle_id = bundle_id++,
+ .flags = OFPBF_ORDERED | OFPBF_ATOMIC,
+ };
+ struct ofpbuf *bundle_open, *bundle_commit;
+
+ /* Open a new bundle. */
+ bc.type = OFPBCT_OPEN_REQUEST;
+ bundle_open = ofputil_encode_bundle_ctrl_request(OFP15_VERSION, &bc);
+ ovs_list_push_back(&msgs, &bundle_open->list_node);
+
+ if (br_ofctrl->br_ofctrl_initial_clear) {
+ /* Send a flow_mod to delete all flows. */
+ struct ofputil_flow_mod fm = {
+ .table_id = OFPTT_ALL,
+ .command = OFPFC_DELETE,
+ };
+ minimatch_init_catchall(&fm.match);
+ add_flow_mod(&fm, &bc, &msgs);
+ minimatch_destroy(&fm.match);
+
+ br_ofctrl->br_ofctrl_initial_clear = false;
+ }
+
+ br_flow_populate_oflow_msgs(br_ofctrl->bridge, &msgs);
+
+ if (ovs_list_back(&msgs) == &bundle_open->list_node) {
+ /* No flow updates. Removing the bundle open request. */
+ ovs_list_pop_back(&msgs);
+ ofpbuf_delete(bundle_open);
+ } else {
+ /* Committing the bundle. */
+ bc.type = OFPBCT_COMMIT_REQUEST;
+ bundle_commit = ofputil_encode_bundle_ctrl_request(OFP15_VERSION, &bc);
+ ovs_list_push_back(&msgs, &bundle_commit->list_node);
+ }
+
+ if (!ovs_list_is_empty(&msgs)) {
+ /* Add a barrier to the list of messages. */
+ struct ofpbuf *barrier = ofputil_encode_barrier_request(OFP15_VERSION);
+ const struct ofp_header *oh = barrier->data;
+ ovs_be32 xid_ = oh->xid;
+ ovs_list_push_back(&msgs, &barrier->list_node);
+
+ /* Queue the messages. */
+ struct ofpbuf *msg;
+ LIST_FOR_EACH_POP (msg, list_node, &msgs) {
+ queue_msg(br_ofctrl, msg);
+ }
+
+ /* Track the flow update. */
+ struct br_ofctrl_flow_update *fup;
+ LIST_FOR_EACH_REVERSE_SAFE (fup, list_node, &br_ofctrl->flow_updates) {
+ if (req_cfg < fup->req_cfg) {
+ /* This br_ofctrl_flow_update is for a configuration later than
+ * 'req_cfg'. This should not normally happen, because it
+ * means that the local seqno decreased and it should normally
+ * be monotonically increasing. */
+ VLOG_WARN("req_cfg regressed from %"PRId64" to %"PRId64,
+ fup->req_cfg, req_cfg);
+ ovs_list_remove(&fup->list_node);
+ free(fup);
+ } else if (req_cfg == fup->req_cfg) {
+ /* This br_ofctrl_flow_update is for the same configuration as
+ * 'req_cfg'. Probably, some change to the physical topology
+ * means that we had to revise the OpenFlow flow table even
+ * though the logical topology did not change. Update fp->xid,
+ * so that we don't send a notification that we're up-to-date
+ * until we're really caught up. */
+ VLOG_DBG("advanced xid target for req_cfg=%"PRId64, req_cfg);
+ fup->xid = xid_;
+
+ return;
+ } else {
+ break;
+ }
+ }
+
+ /* Add a flow update. */
+ fup = xmalloc(sizeof *fup);
+ ovs_list_push_back(&br_ofctrl->flow_updates, &fup->list_node);
+ fup->xid = xid_;
+ fup->req_cfg = req_cfg;
+ } else if (!ovs_list_is_empty(&br_ofctrl->flow_updates)) {
+ /* Getting up-to-date with 'req_cfg' didn't require any extra flow
+ * table changes, so whenever we get up-to-date with the most recent
+ * flow table update, we're also up-to-date with 'req_cfg'. */
+ struct br_ofctrl_flow_update *fup =
+ br_ofctrl_flow_update_from_list_node(
+ ovs_list_back(&br_ofctrl->flow_updates));
+ fup->req_cfg = req_cfg;
+ } else {
+ /* We were completely up-to-date before and still are. */
+ br_ofctrl->cur_cfg = req_cfg;
+ }
+}
diff --git a/br-controller/br-ofctrl.h b/br-controller/br-ofctrl.h
new file mode 100644
index 0000000000..9b629e2123
--- /dev/null
+++ b/br-controller/br-ofctrl.h
@@ -0,0 +1,33 @@
+/*
+ * 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 BR_OFCTRL_H
+#define BR_OFCTRL_H 1
+
+struct ovn_bridge;
+struct shash;
+
+void br_ofctrls_init(void);
+bool br_ofctrls_run(void);
+void br_ofctrls_put(uint64_t req_cfg, bool lflows_changed,
+ bool pflows_changed);
+void br_ofctrls_destroy(void);
+void br_ofctrls_wait(void);
+
+void br_ofctrls_add_or_update_bridge(struct ovn_bridge *);
+void br_ofctrls_remove_bridge(const char *);
+uint64_t br_ofctrl_get_cur_cfg(void);
+void br_ofctrls_get_bridges(struct sset *);
+
+#endif /* BR_OFCTRL_H */
\ No newline at end of file
diff --git a/br-controller/en-bridge-data.c b/br-controller/en-bridge-data.c
index 483c784a37..5c10a1f1f1 100644
--- a/br-controller/en-bridge-data.c
+++ b/br-controller/en-bridge-data.c
@@ -26,6 +26,7 @@
/* OVN includes. */
#include "en-bridge-data.h"
+#include "lib/dirs.h"
#include "lib/ovn-br-idl.h"
VLOG_DEFINE_THIS_MODULE(en_bridge_data);
@@ -40,6 +41,7 @@ static const struct ovsrec_bridge *ovsbridge_lookup_by_name(
struct ovsdb_idl_index *ovsrec_bridge_by_name,
const char *name);
static void build_ovn_bridge_iface_simap(struct ovn_bridge *);
+static void update_ovn_br_remote(struct ovn_bridge *);
void *
en_bridge_data_init(struct engine_node *node OVS_UNUSED,
@@ -114,6 +116,7 @@ ovn_bridges_run(const struct ovnbrrec_bridge_table
*br_table,
br->ovs_br = ovs_br;
build_ovn_bridge_iface_simap(br);
+ update_ovn_br_remote(br);
}
}
@@ -121,6 +124,7 @@ static void
ovn_bridge_destroy(struct ovn_bridge *br)
{
simap_destroy(&br->ovs_ifaces);
+ free(br->conn_target);
free(br);
}
@@ -157,3 +161,39 @@ build_ovn_bridge_iface_simap(struct ovn_bridge *br)
}
}
}
+
+static void
+update_ovn_br_remote(struct ovn_bridge *br)
+{
+ ovs_assert(br->ovs_br);
+
+ const char *ext_target = smap_get(&br->ovs_br->external_ids,
+ "ovn-bridge-remote");
+ char *target = ext_target
+ ? xstrdup(ext_target)
+ : xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br->ovs_br->name);
+
+ if (!br->conn_target || strcmp(br->conn_target, target)) {
+ free(br->conn_target);
+ br->conn_target = target;
+ } else {
+ free(target);
+ }
+
+ unsigned long long probe_interval =
+ smap_get_ullong(&br->ovs_br->external_ids,
+ "ovn-openflow-remote-probe-interval", 0);
+ br->probe_interval = MIN(probe_interval / 1000, INT_MAX);
+
+ unsigned int _wait_before_clear_time =
+ smap_get_uint(&br->ovs_br->external_ids,
+ "ovn-ofctrl-wait-before-clear", 0);
+
+ if (_wait_before_clear_time != br->wait_before_clear_time) {
+ VLOG_INFO("ofctrl-wait-before-clear is now %u ms (was %u ms) "
+ "for bridge %s",
+ _wait_before_clear_time, br->wait_before_clear_time,
+ br->ovs_br->name);
+ br->wait_before_clear_time = _wait_before_clear_time;
+ }
+}
diff --git a/br-controller/en-bridge-data.h b/br-controller/en-bridge-data.h
index b374798649..05ab556637 100644
--- a/br-controller/en-bridge-data.h
+++ b/br-controller/en-bridge-data.h
@@ -26,6 +26,10 @@ struct ovn_bridge {
/* simap of ovs interface names to ofport numbers. */
struct simap ovs_ifaces;
+
+ int probe_interval;
+ char *conn_target;
+ unsigned int wait_before_clear_time;
};
struct ed_type_bridge_data {
diff --git a/br-controller/ovn-br-controller.c
b/br-controller/ovn-br-controller.c
index ae0e192429..74f2b7a2d2 100644
--- a/br-controller/ovn-br-controller.c
+++ b/br-controller/ovn-br-controller.c
@@ -35,11 +35,13 @@
/* OVN includes. */
+#include "br-ofctrl.h"
#include "en-bridge-data.h"
#include "en-lflow.h"
#include "en-pflow.h"
#include "lib/ovn-br-idl.h"
#include "lib/inc-proc-eng.h"
+#include "lib/ofctrl-seqno.h"
#include "lib/ovn-util.h"
VLOG_DEFINE_THIS_MODULE(main);
@@ -55,6 +57,9 @@ static const char *ssl_ca_cert_file;
/* --unixctl-path: Path to use for unixctl server socket. */
static char *unixctl_path;
+/* Registered ofctrl seqno type for br_cfg propagation. */
+static size_t ofctrl_seq_type_br_cfg;
+
#define BRCTL_NODES \
BRCTL_NODE(br_global) \
BRCTL_NODE(bridge) \
@@ -110,7 +115,12 @@ en_br_controller_output_run(struct engine_node *node
OVS_UNUSED,
/* Static function declarations. */
static void ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl);
static void update_br_db(struct ovsdb_idl *ovs_idl,
- struct ovsdb_idl *ovn_br_idl);
+ struct ovsdb_idl *ovnbr_idl,
+ unsigned int *ovnbr_cond_seqno);
+static unsigned int update_ovnbr_monitors(struct ovsdb_idl *);
+static uint64_t get_ovnbr_cfg(const struct ovnbrrec_br_global_table *,
+ unsigned int cond_seqno,
+ unsigned int expected_cond_seqno);
int
main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
@@ -138,6 +148,9 @@ main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
daemonize_complete();
+ /* Register ofctrl seqno types. */
+ ofctrl_seq_type_br_cfg = ofctrl_seqno_add_type();
+
/* Connect to OVS OVSDB instance. */
struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true));
@@ -206,8 +219,12 @@ main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
engine_init(&en_br_controller_output, &engine_arg);
engine_ovsdb_node_add_index(&en_ovs_bridge, "name", ovsrec_bridge_by_name);
- unsigned int ovs_cond_seqno = UINT_MAX;
+ unsigned int ovnbr_expected_cond_seqno = UINT_MAX;
unsigned int ovnbr_cond_seqno = UINT_MAX;
+ unsigned int ovs_cond_seqno = UINT_MAX;
+
+ struct ed_type_bridge_data *br_data =
+ engine_get_internal_data(&en_bridge_data);
/* Main loop. */
while (!exit_args.exiting) {
@@ -224,7 +241,8 @@ main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
ovs_cond_seqno = new_ovs_cond_seqno;
}
- update_br_db(ovs_idl_loop.idl, ovnbr_idl_loop.idl);
+ update_br_db(ovs_idl_loop.idl, ovnbr_idl_loop.idl,
+ &ovnbr_expected_cond_seqno);
struct ovsdb_idl_txn *ovnbr_idl_txn
= ovsdb_idl_loop_run(&ovnbr_idl_loop);
unsigned int new_ovnbr_cond_seqno
@@ -251,10 +269,48 @@ main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
if (ovsdb_idl_has_ever_connected(ovnbr_idl_loop.idl) && cfg) {
engine_run(true);
+
+ br_data = engine_get_data(&en_bridge_data);
+ if (br_data) {
+ struct sset bridges_in_br_ofctrl =
+ SSET_INITIALIZER(&bridges_in_br_ofctrl);
+ br_ofctrls_get_bridges(&bridges_in_br_ofctrl);
+ struct shash_node *node;
+ SHASH_FOR_EACH (node, &br_data->bridges) {
+ struct ovn_bridge *br = node->data;
+
+ if (br->ovs_br) {
+ sset_find_and_delete(&bridges_in_br_ofctrl,
+ br->db_br->name);
+ br_ofctrls_add_or_update_bridge(br);
+ }
+ }
+
+ const char *bridge;
+ SSET_FOR_EACH (bridge, &bridges_in_br_ofctrl) {
+ br_ofctrls_remove_bridge(bridge);
+ }
+
+ sset_destroy(&bridges_in_br_ofctrl);
+ }
+
+ br_ofctrls_run();
+
+ ofctrl_seqno_update_create(
+ ofctrl_seq_type_br_cfg,
+ get_ovnbr_cfg(ovnbrrec_br_global_table_get(ovnbr_idl_loop.idl),
+ ovnbr_cond_seqno, ovnbr_expected_cond_seqno));
+
+ br_ofctrls_put(ofctrl_seqno_get_req_cfg(),
+ engine_node_changed(&en_lflow_output),
+ engine_node_changed(&en_pflow_output));
+
+ ofctrl_seqno_run(br_ofctrl_get_cur_cfg());
}
unixctl_server_run(unixctl);
+ br_ofctrls_wait();
unixctl_server_wait(unixctl);
if (exit_args.exiting) {
poll_immediate_wake();
@@ -440,7 +496,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
/* Retrieves the pointer to the OVN Bridge Controller database from 'ovs_idl'
* and updates 'brdb_idl' with that pointer. */
static void
-update_br_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnbr_idl)
+update_br_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnbr_idl,
+ unsigned int *ovnbr_cond_seqno)
{
const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
if (!cfg) {
@@ -449,4 +506,55 @@ update_br_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl
*ovnbr_idl)
const char *remote = smap_get(&cfg->external_ids, "ovn-br-remote");
ovsdb_idl_set_remote(ovnbr_idl, remote, true);
+
+ unsigned int next_cond_seqno = update_ovnbr_monitors(ovnbr_idl);
+ if (ovnbr_cond_seqno) {
+ *ovnbr_cond_seqno = next_cond_seqno;
+ }
+}
+
+/* Assume the table exists in the server schema and set its condition. */
+#define ovnbr_table_set_req_mon_condition(idl, table, cond) \
+ ovnbrrec_##table##_set_condition(idl, cond)
+
+static unsigned int
+update_ovnbr_monitors(struct ovsdb_idl *ovnbr_idl)
+{
+ struct ovsdb_idl_condition br = OVSDB_IDL_CONDITION_INIT(&br);
+ struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf);
+
+ ovsdb_idl_condition_add_clause_true(&br);
+ ovsdb_idl_condition_add_clause_true(&lf);
+
+ unsigned int cond_seqnos[] = {
+ ovnbr_table_set_req_mon_condition(ovnbr_idl, bridge, &br),
+ ovnbr_table_set_req_mon_condition(ovnbr_idl, logical_flow, &lf),
+ };
+
+ unsigned int expected_cond_seqno = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(cond_seqnos); i++) {
+ expected_cond_seqno = MAX(expected_cond_seqno, cond_seqnos[i]);
+ }
+
+ return expected_cond_seqno;
+}
+
+static uint64_t
+get_ovnbr_cfg(const struct ovnbrrec_br_global_table *br_global_table,
+ unsigned int cond_seqno, unsigned int expected_cond_seqno)
+{
+ static uint64_t br_cfg = 0;
+
+ /* Delay getting br_cfg if there are monitor condition changes
+ * in flight. It might be that those changes would instruct the
+ * server to send updates that happened before PR_Global.pr_cfg.
+ */
+ if (cond_seqno != expected_cond_seqno) {
+ return br_cfg;
+ }
+
+ const struct ovnbrrec_br_global *br_global
+ = ovnbrrec_br_global_table_first(br_global_table);
+ br_cfg = br_global ? br_global->br_cfg : 0;
+ return br_cfg;
}
diff --git a/tests/automake.mk b/tests/automake.mk
index 5d55042e61..8ae3105478 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -46,7 +46,8 @@ TESTSUITE_AT = \
tests/ovn-lflow-conj-ids.at \
tests/ovn-ipsec.at \
tests/ovn-vif-plug.at \
- tests/ovn-util.at
+ tests/ovn-util.at \
+ tests/ovn-br-controller.at
SYSTEM_DPDK_TESTSUITE_AT = \
tests/system-dpdk-testsuite.at \
@@ -91,7 +92,7 @@ DISTCLEANFILES += tests/atconfig tests/atlocal
MULTINODE_TESTSUITE = $(srcdir)/tests/multinode-testsuite
MULTINODE_TESTSUITE_DIR = $(abs_top_builddir)/tests/multinode-testsuite.dir
MULTINODE_TESTSUITE_RESULTS = $(MULTINODE_TESTSUITE_DIR)/results
-AUTOTEST_PATH =
$(ovs_builddir)/utilities:$(ovs_builddir)/vswitchd:$(ovs_builddir)/ovsdb:$(ovs_builddir)/vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):controller-vtep:northd:utilities:controller:ic
+AUTOTEST_PATH =
$(ovs_builddir)/utilities:$(ovs_builddir)/vswitchd:$(ovs_builddir)/ovsdb:$(ovs_builddir)/vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):controller-vtep:northd:utilities:controller:ic:br-controller
export ovs_srcdir
export ovs_builddir
diff --git a/tests/ovn-br-controller.at b/tests/ovn-br-controller.at
new file mode 100644
index 0000000000..0c197e222b
--- /dev/null
+++ b/tests/ovn-br-controller.at
@@ -0,0 +1,330 @@
+AT_BANNER([ovn_br_controller])
+
+# OVN_BR_CONTROLLER_START(SIM_NAME)
+#
+# $1 - optional simulator name. If none is given, runs ovn-br-controller
+# in $ovs_dir.
+# Starts the test with a setup with ovn bridge controller. Each test case
must first
+# call this macro and ovn_start.
+#
+m4_define([OVN_BR_CONTROLLER_START], [
+ AT_KEYWORDS([ovn-br-controller])
+ mkdir -p "$ovs_dir" || return 1
+ mkdir "$ovs_base"/ovn-br || return 1
+
+ dnl Create databases (vswitch).
+ check ovsdb-tool create "$ovs_dir"/vswitchd.db
$ovs_srcdir/vswitchd/vswitch.ovsschema
+ check ovsdb-tool create "$ovs_base"/ovn-br/ovn-br.db
"$abs_top_srcdir"/ovn-br.ovsschema
+
+ dnl Start ovsdb-server.
+ start_daemon ovsdb-server --remote=punix:"$ovs_dir"/db.sock \
+ "$ovs_dir"/vswitchd.db
+
+ ovn_br_remote=unix:"$ovs_base"/ovn-br/ovnbr_db.sock
+ dnl Start ovs-vswitchd.
+ start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif
+
+ ovs-vsctl \
+ -- set Open_vSwitch . external-ids:ovn-br-remote=$ovn_br_remote
+ dnl Start ovsdb-server for ovn-br.
+ as ovn-br start_daemon ovsdb-server
--remote=punix:"$ovs_base"/ovn-br/ovnbr_db.sock \
+ "$ovs_base"/ovn-br/ovn-br.db
+
+ which ovn-br-controller
+ dnl Start ovn-br-controller.
+ start_daemon ovn-br-controller
+])
+
+m4_define([OVN_BR_CONTROLLER_STOP],[
+ echo
+ echo "Clean up ovn-br-controller related processes in $2"
+ test -n "$2" && as "$2"
+ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+ OVS_APP_EXIT_AND_WAIT([ovn-br-controller])
+
+ as ovn-br
+ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+])
+
+AT_SETUP([ovn-br-controller - brctl test])
+OVN_BR_CONTROLLER_START
+
+check as ovn-br ovn-brctl show
+check as ovn-br ovn-brctl add-br br0
+
+AT_CHECK([as ovn-br ovn-brctl show | uuidfilt], [0],
+ [bridge <0> (br0)
+])
+
+AT_CHECK([as ovn-br ovn-brctl show br0 | uuidfilt], [0],
+ [bridge <0> (br0)
+])
+
+AT_CHECK([as ovn-br ovn-brctl show br1 | uuidfilt], [0], [],
+ [ovn-brctl: no row "br1" in table Bridge
+])
+
+check as ovn-br ovn-brctl del-br br0
+check as ovn-br ovn-brctl show
+
+check as ovn-br ovn-brctl add-br br0
+
+check as ovn-br ovn-brctl add-flow br0 0 1000 "ip4 && tcp" "drop;"
+check as ovn-br ovn-brctl add-flow br0 0 1000 "ip4 && udp" "next;"
+check as ovn-br ovn-brctl add-flow br0 1 0 "ip4 && udp" "output;"
+
+check as ovn-br ovn-brctl add-br br1
+
+check as ovn-br ovn-brctl add-flow br1 0 1000 "ip4 && tcp.dst == 1000 &&
ip4.dst == 10.0.0.10" "drop;"
+check as ovn-br ovn-brctl add-flow br1 0 0 "1" "output;"
+
+AT_CHECK([as ovn-br ovn-brctl dump-flows | uuidfilt], [0],
+ [dnl
+Bridge: br0 (<0>)
+ table=0 , priority=1000 , match=(ip4 && tcp), action=(drop;)
+ table=0 , priority=1000 , match=(ip4 && udp), action=(next;)
+ table=1 , priority=0 , match=(ip4 && udp), action=(output;)
+Bridge: br1 (<1>)
+ table=0 , priority=1000 , match=(ip4 && tcp.dst == 1000 && ip4.dst ==
10.0.0.10), action=(drop;)
+ table=0 , priority=0 , match=(1), action=(output;)
+])
+
+as ovn-br ovn-brctl del-flows br1
+
+AT_CHECK([as ovn-br ovn-brctl dump-flows | uuidfilt], [0],
+ [dnl
+Bridge: br0 (<0>)
+ table=0 , priority=1000 , match=(ip4 && tcp), action=(drop;)
+ table=0 , priority=1000 , match=(ip4 && udp), action=(next;)
+ table=1 , priority=0 , match=(ip4 && udp), action=(output;)
+])
+
+lflow_uuid=$(as ovn-br ovn-brctl --bare --columns _uuid find logical_flow
table_id=1)
+check as ovn-br ovn-brctl del-flow $lflow_uuid
+
+AT_CHECK([as ovn-br ovn-brctl dump-flows | uuidfilt], [0],
+ [dnl
+Bridge: br0 (<0>)
+ table=0 , priority=1000 , match=(ip4 && tcp), action=(drop;)
+ table=0 , priority=1000 , match=(ip4 && udp), action=(next;)
+])
+
+OVN_BR_CONTROLLER_STOP
+AT_CLEANUP
+
+AT_SETUP([ovn-br-controller - logical flows])
+OVN_BR_CONTROLLER_START
+
+check as ovn-br ovn-brctl add-br br0
+
+check ovs-vsctl add-br br0
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br0 | grep -v NXST_FLOW | wc -l`
-eq 3])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br0 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+NXST_FLOW reply:
+])
+
+check ovs-vsctl add-port br0 p1 -- set interface p1 ofport-request=2
+check ovs-vsctl add-port br0 p2 -- set interface p2 ofport-request=3
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br0 | grep -v NXST_FLOW | wc -l`
-eq 7])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br0 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=3 actions=load:0x3->NXM_NX_REG14[[]],resubmit(,8)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=121, priority=100,reg15=0x3 actions=output:3
+NXST_FLOW reply:
+])
+
+check as ovn-br ovn-brctl add-flow br0 0 1000 'inport == "p1"' "next;"
+check as ovn-br ovn-brctl add-flow br0 0 1000 'inport == "p2"' "drop;"
+check as ovn-br ovn-brctl add-flow br0 1 1000 'ip4 && tcp' "ip4.src <->
ip4.dst; tcp.dst = 8080; next;"
+check as ovn-br ovn-brctl add-flow br0 1 1000 'ip4' "next;"
+check as ovn-br ovn-brctl add-flow br0 2 1000 '1' "output;"
+
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br0 | grep -v NXST_FLOW | wc -l`
-eq 12])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br0 | ofctl_strip_all], [0], [dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=3 actions=load:0x3->NXM_NX_REG14[[]],resubmit(,8)
+ table=10, priority=1000 actions=resubmit(,120)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=121, priority=100,reg15=0x3 actions=output:3
+ table=8, priority=1000,reg14=0x2 actions=resubmit(,9)
+ table=8, priority=1000,reg14=0x3 actions=drop
+ table=9, priority=1000,ip actions=resubmit(,10)
+ table=9, priority=1000,tcp
actions=push:NXM_OF_IP_DST[[]],push:NXM_OF_IP_SRC[[]],pop:NXM_OF_IP_DST[[]],pop:NXM_OF_IP_SRC[[]],mod_tp_dst:8080,resubmit(,10)
+NXST_FLOW reply:
+])
+
+check ovs-vsctl del-port p2
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br0 | grep -v NXST_FLOW | wc -l`
-eq 9])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br0 | ofctl_strip_all], [0], [dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ table=10, priority=1000 actions=resubmit(,120)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=8, priority=1000,reg14=0x2 actions=resubmit(,9)
+ table=9, priority=1000,ip actions=resubmit(,10)
+ table=9, priority=1000,tcp
actions=push:NXM_OF_IP_DST[[]],push:NXM_OF_IP_SRC[[]],pop:NXM_OF_IP_DST[[]],pop:NXM_OF_IP_SRC[[]],mod_tp_dst:8080,resubmit(,10)
+NXST_FLOW reply:
+])
+
+check ovs-vsctl add-br br1
+check ovs-vsctl add-port br1 br1-p1 -- set interface br1-p1 ofport-request=1
+check ovs-vsctl add-port br1 br1-p2 -- set interface br1-p2 ofport-request=2
+
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br1 | grep -v NXST_FLOW | wc -l`
-eq 1])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br1 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+NXST_FLOW reply:
+])
+
+br_id="4830e8c3-9b6b-48db-ba52-e030d9db7256"
+as ovn-br ovn-brctl --id=${br_id} create bridge name=br1
+as ovn-br ovn-brctl list bridge
+
+# check as ovn-br ovn-brctl add-br br1
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br1 | grep -v NXST_FLOW | wc -l`
-eq 7])
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br1 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=1 actions=load:0x1->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x1 actions=output:1
+ table=121, priority=100,reg15=0x2 actions=output:2
+NXST_FLOW reply:
+])
+
+check as ovn-br ovn-brctl add-flow br1 1 1000 "ip4" "ct_snat;"
+check as ovn-br ovn-brctl add-flow br1 2 1000 "ip4 && ct.new && ct.trk &&
ip4.src == 10.0.0.11" "ct_snat(100.64.0.11); next;"
+check as ovn-br ovn-brctl add-flow br1 3 1000 "inport == \"br1-p1\"" "outport
= \"br1-p2\"; output;"
+check as ovn-br ovn-brctl add-flow br1 3 1000 "inport == \"br1-p2\"" "outport
= \"br1-p1\"; output;"
+
+lflow_id="75bf46aa-4204-4e36-af23-6114f59e3fe8"
+
+as ovn-br ovn-brctl --id=${lflow_id} create logical_flow \
+match='"ip4 && tcp.src > 0 && tcp.src < 1000 && tcp.dst > 1000 && tcp.dst <
2000"' \
+actions="next;" bridge=${br_id} table_id=10 priority=1000
+
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br1 | grep -v NXST_FLOW | wc -l`
-eq 35])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br1 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=1 actions=load:0x1->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ table=10, priority=1000,ct_state=+new+trk,ip,nw_src=10.0.0.11
actions=ct(commit,table=11,zone=NXM_NX_REG12[[0..15]],nat(src=100.64.0.11)),resubmit(,11)
+ table=11, priority=1000,reg14=0x1
actions=load:0x2->NXM_NX_REG15[[]],resubmit(,120)
+ table=11, priority=1000,reg14=0x2
actions=load:0x1->NXM_NX_REG15[[]],resubmit(,120)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x1 actions=output:1
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=18, priority=1000,conj_id=1644032429,tcp actions=resubmit(,19)
+ table=18, priority=1000,tcp,tp_dst=0x3ea/0xfffe
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x3ec/0xfffc
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x3f0/0xfff0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x400/0xfe00
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x600/0xff00
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x700/0xff80
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x780/0xffc0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x7c0/0xfff0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=1001 actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_src=0x1/0xfe01
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x10/0xfe10
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x100/0xff00
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x2/0xfe02
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x20/0xfe20
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x200/0xff00
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x300/0xff80
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x380/0xffc0
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x3c0/0xffe0
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x3e0/0xfff8
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x4/0xfe04
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x40/0xfe40
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x8/0xfe08
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x80/0xfe80
actions=conjunction(1644032429,2/2)
+ table=9, priority=1000,ip actions=ct(table=10,zone=NXM_NX_REG12[[0..15]],nat)
+NXST_FLOW reply:
+])
+
+as ovn-br ovn-brctl set logical_flow ${lflow_id} match='"ip4 && sctp"'
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br1 | grep -v NXST_FLOW | wc -l`
-eq 12])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br1 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=1 actions=load:0x1->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ table=10, priority=1000,ct_state=+new+trk,ip,nw_src=10.0.0.11
actions=ct(commit,table=11,zone=NXM_NX_REG12[[0..15]],nat(src=100.64.0.11)),resubmit(,11)
+ table=11, priority=1000,reg14=0x1
actions=load:0x2->NXM_NX_REG15[[]],resubmit(,120)
+ table=11, priority=1000,reg14=0x2
actions=load:0x1->NXM_NX_REG15[[]],resubmit(,120)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x1 actions=output:1
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=18, priority=1000,sctp actions=resubmit(,19)
+ table=9, priority=1000,ip actions=ct(table=10,zone=NXM_NX_REG12[[0..15]],nat)
+NXST_FLOW reply:
+])
+
+# Make sure that the same conj_id is used when the lflow is updated with the
conj match.
+as ovn-br ovn-brctl set logical_flow ${lflow_id} \
+match='"ip4 && tcp.src > 0 && tcp.src < 1000 && tcp.dst > 1000 && tcp.dst <
2000"'
+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br1 | grep -v NXST_FLOW | wc -l`
-eq 35])
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br1 | sort | ofctl_strip_all], [0],
[dnl
+ priority=0 actions=NORMAL
+ priority=100,in_port=1 actions=load:0x1->NXM_NX_REG14[[]],resubmit(,8)
+ priority=100,in_port=2 actions=load:0x2->NXM_NX_REG14[[]],resubmit(,8)
+ table=10, priority=1000,ct_state=+new+trk,ip,nw_src=10.0.0.11
actions=ct(commit,table=11,zone=NXM_NX_REG12[[0..15]],nat(src=100.64.0.11)),resubmit(,11)
+ table=11, priority=1000,reg14=0x1
actions=load:0x2->NXM_NX_REG15[[]],resubmit(,120)
+ table=11, priority=1000,reg14=0x2
actions=load:0x1->NXM_NX_REG15[[]],resubmit(,120)
+ table=120, priority=0 actions=resubmit(,121)
+ table=121, priority=0 actions=NORMAL
+ table=121, priority=100,reg15=0x1 actions=output:1
+ table=121, priority=100,reg15=0x2 actions=output:2
+ table=18, priority=1000,conj_id=1644032429,tcp actions=resubmit(,19)
+ table=18, priority=1000,tcp,tp_dst=0x3ea/0xfffe
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x3ec/0xfffc
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x3f0/0xfff0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x400/0xfe00
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x600/0xff00
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x700/0xff80
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x780/0xffc0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=0x7c0/0xfff0
actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_dst=1001 actions=conjunction(1644032429,1/2)
+ table=18, priority=1000,tcp,tp_src=0x1/0xfe01
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x10/0xfe10
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x100/0xff00
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x2/0xfe02
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x20/0xfe20
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x200/0xff00
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x300/0xff80
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x380/0xffc0
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x3c0/0xffe0
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x3e0/0xfff8
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x4/0xfe04
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x40/0xfe40
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x8/0xfe08
actions=conjunction(1644032429,2/2)
+ table=18, priority=1000,tcp,tp_src=0x80/0xfe80
actions=conjunction(1644032429,2/2)
+ table=9, priority=1000,ip actions=ct(table=10,zone=NXM_NX_REG12[[0..15]],nat)
+NXST_FLOW reply:
+])
+
+OVN_BR_CONTROLLER_STOP
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 8e60bf82e1..5f5eabb42a 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -41,3 +41,4 @@ m4_include([tests/checkpatch.at])
m4_include([tests/ovn-ipsec.at])
m4_include([tests/ovn-vif-plug.at])
m4_include([tests/ovn-util.at])
+m4_include([tests/ovn-br-controller.at])
--
2.51.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev