This implementation is really simple, but it seems effective enough in my minimal testing.
We still need code to generate flows for logical-to-physical and physical-to-logical translation. With that, plus code to set up tunnels, we should be able to start end-to-end testing. Signed-off-by: Ben Pfaff <b...@nicira.com> --- ovn/controller/automake.mk | 2 + ovn/controller/ofctrl.c | 463 ++++++++++++++++++++++++++++++++++++++++ ovn/controller/ofctrl.h | 37 ++++ ovn/controller/ovn-controller.c | 9 +- ovn/controller/pipeline.c | 27 +-- 5 files changed, 517 insertions(+), 21 deletions(-) create mode 100644 ovn/controller/ofctrl.c create mode 100644 ovn/controller/ofctrl.h diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk index 51c73be..74cfb62 100644 --- a/ovn/controller/automake.mk +++ b/ovn/controller/automake.mk @@ -4,6 +4,8 @@ ovn_controller_ovn_controller_SOURCES = \ ovn/controller/bindings.h \ ovn/controller/chassis.c \ ovn/controller/chassis.h \ + ovn/controller/ofctrl.c \ + ovn/controller/ofctrl.h \ ovn/controller/ovn-controller.c \ ovn/controller/ovn-controller.h \ ovn/controller/pipeline.c \ diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c new file mode 100644 index 0000000..cf201c8 --- /dev/null +++ b/ovn/controller/ofctrl.c @@ -0,0 +1,463 @@ +/* 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 "ofctrl.h" +#include "dirs.h" +#include "dynamic-string.h" +#include "hmap.h" +#include "match.h" +#include "ofp-actions.h" +#include "ofp-msgs.h" +#include "ofp-print.h" +#include "ofp-util.h" +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "openvswitch/vlog.h" +#include "ovn-controller.h" +#include "rconn.h" +#include "socket-util.h" + +VLOG_DEFINE_THIS_MODULE(ofctrl); + +/* An OpenFlow flow. */ +struct ovn_flow { + /* Key. */ + struct hmap_node hmap_node; /* In 'desired_flows' or 'installed_flows'. */ + uint8_t table_id; + uint16_t priority; + struct match match; + + /* Data. */ + struct ofpact *ofpacts; + size_t ofpacts_len; +}; + +static uint32_t ovn_flow_hash(const struct ovn_flow *); +static struct ovn_flow *ovn_flow_lookup(struct hmap *flow_table, + const struct ovn_flow *target); +static char *ovn_flow_to_string(const struct ovn_flow *); +static void ovn_flow_log(const struct ovn_flow *, const char *action); +static void ovn_flow_destroy(struct ovn_flow *); + +/* OpenFlow connection to the switch. */ +static struct rconn *swconn; + +/* Last seen sequence number for 'swconn'. When this differs from + * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */ +static 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. */ +static struct rconn_packet_counter *tx_counter; + +/* Flow tables. Each holds "struct ovn_flow"s. + * + * 'desired_flows' is the flow table that we want the switch to have. + * 'installed_flows' is the flow table currently installed in the switch. */ +static struct hmap desired_flows; +static struct hmap installed_flows; + +static void ovn_flow_table_clear(struct hmap *flow_table); +static void ovn_flow_table_destroy(struct hmap *flow_table); + +static void ofctrl_update_flows(void); +static void ofctrl_recv(const struct ofpbuf *msg); + +void +ofctrl_init(void) +{ + swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION); + tx_counter = rconn_packet_counter_create(); + hmap_init(&desired_flows); + hmap_init(&installed_flows); +} + +/* This function should be called in the main loop after anything that updates + * the flow table (e.g. after calls to ofctrl_clear_flows() and + * ofctrl_add_flow()). */ +void +ofctrl_run(struct controller_ctx *ctx) +{ + char *target; + target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), ctx->br_int_name); + if (strcmp(target, rconn_get_target(swconn))) { + rconn_connect(swconn, target, target); + } + free(target); + + rconn_run(swconn); + + if (!rconn_is_connected(swconn)) { + return; + } + if (!rconn_packet_counter_n_packets(tx_counter)) { + ofctrl_update_flows(); + } + + for (int i = 0; i < 50; i++) { + struct ofpbuf *msg = rconn_recv(swconn); + if (!msg) { + break; + } + + ofctrl_recv(msg); + ofpbuf_delete(msg); + } +} + +void +ofctrl_wait(void) +{ + rconn_run_wait(swconn); + rconn_recv_wait(swconn); +} + +void +ofctrl_destroy(void) +{ + rconn_destroy(swconn); + ovn_flow_table_destroy(&installed_flows); + ovn_flow_table_destroy(&desired_flows); + rconn_packet_counter_destroy(tx_counter); +} + +static void +queue_msg(struct ofpbuf *msg) +{ + rconn_send(swconn, msg, tx_counter); +} + +static void +ofctrl_recv(const struct ofpbuf *msg) +{ + enum ofptype type; + struct ofpbuf b; + + b = *msg; + if (ofptype_pull(&type, &b)) { + return; + } + + switch (type) { + case OFPTYPE_ECHO_REQUEST: + queue_msg(make_echo_reply(msg->data)); + break; + + case OFPTYPE_ECHO_REPLY: + case OFPTYPE_PACKET_IN: + case OFPTYPE_PORT_STATUS: + case OFPTYPE_FLOW_REMOVED: + /* Nothing to do. */ + break; + + case OFPTYPE_HELLO: + case OFPTYPE_ERROR: + case OFPTYPE_FEATURES_REQUEST: + case OFPTYPE_FEATURES_REPLY: + case OFPTYPE_GET_CONFIG_REQUEST: + case OFPTYPE_GET_CONFIG_REPLY: + case OFPTYPE_SET_CONFIG: + case OFPTYPE_PACKET_OUT: + case OFPTYPE_FLOW_MOD: + case OFPTYPE_GROUP_MOD: + case OFPTYPE_PORT_MOD: + case OFPTYPE_TABLE_MOD: + case OFPTYPE_BARRIER_REQUEST: + case OFPTYPE_BARRIER_REPLY: + case OFPTYPE_QUEUE_GET_CONFIG_REQUEST: + case OFPTYPE_QUEUE_GET_CONFIG_REPLY: + case OFPTYPE_DESC_STATS_REQUEST: + case OFPTYPE_DESC_STATS_REPLY: + case OFPTYPE_FLOW_STATS_REQUEST: + case OFPTYPE_FLOW_STATS_REPLY: + case OFPTYPE_AGGREGATE_STATS_REQUEST: + case OFPTYPE_AGGREGATE_STATS_REPLY: + case OFPTYPE_TABLE_STATS_REQUEST: + case OFPTYPE_TABLE_STATS_REPLY: + case OFPTYPE_PORT_STATS_REQUEST: + case OFPTYPE_PORT_STATS_REPLY: + case OFPTYPE_QUEUE_STATS_REQUEST: + case OFPTYPE_QUEUE_STATS_REPLY: + case OFPTYPE_PORT_DESC_STATS_REQUEST: + case OFPTYPE_PORT_DESC_STATS_REPLY: + case OFPTYPE_ROLE_REQUEST: + case OFPTYPE_ROLE_REPLY: + case OFPTYPE_ROLE_STATUS: + case OFPTYPE_SET_FLOW_FORMAT: + case OFPTYPE_FLOW_MOD_TABLE_ID: + case OFPTYPE_SET_PACKET_IN_FORMAT: + case OFPTYPE_FLOW_AGE: + case OFPTYPE_SET_CONTROLLER_ID: + case OFPTYPE_FLOW_MONITOR_STATS_REQUEST: + case OFPTYPE_FLOW_MONITOR_STATS_REPLY: + case OFPTYPE_FLOW_MONITOR_CANCEL: + case OFPTYPE_FLOW_MONITOR_PAUSED: + case OFPTYPE_FLOW_MONITOR_RESUMED: + case OFPTYPE_GET_ASYNC_REQUEST: + case OFPTYPE_GET_ASYNC_REPLY: + case OFPTYPE_SET_ASYNC_CONFIG: + case OFPTYPE_METER_MOD: + case OFPTYPE_GROUP_STATS_REQUEST: + case OFPTYPE_GROUP_STATS_REPLY: + case OFPTYPE_GROUP_DESC_STATS_REQUEST: + case OFPTYPE_GROUP_DESC_STATS_REPLY: + case OFPTYPE_GROUP_FEATURES_STATS_REQUEST: + case OFPTYPE_GROUP_FEATURES_STATS_REPLY: + case OFPTYPE_METER_STATS_REQUEST: + case OFPTYPE_METER_STATS_REPLY: + case OFPTYPE_METER_CONFIG_STATS_REQUEST: + case OFPTYPE_METER_CONFIG_STATS_REPLY: + case OFPTYPE_METER_FEATURES_STATS_REQUEST: + case OFPTYPE_METER_FEATURES_STATS_REPLY: + case OFPTYPE_TABLE_FEATURES_STATS_REQUEST: + case OFPTYPE_TABLE_FEATURES_STATS_REPLY: + case OFPTYPE_BUNDLE_CONTROL: + case OFPTYPE_BUNDLE_ADD_MESSAGE: + default: + /* Messages that are generally unexpected. */ + if (VLOG_IS_DBG_ENABLED()) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); + + char *s = ofp_to_string(msg->data, msg->size, 2); + VLOG_DBG_RL(&rl, "OpenFlow packet ignored: %s", s); + free(s); + } + } +} + +/* Flow table interface to the rest of ovn-controller. */ + +/* Clears the table of flows desired to be in the switch. Call this before + * adding the desired flows (with ofctrl_add_flow()). */ +void +ofctrl_clear_flows(void) +{ + ovn_flow_table_clear(&desired_flows); +} + +/* Adds a flow with the specified 'match' and 'actions' to the OpenFlow table + * numbered 'table_id' with the given 'priority'. + * + * This just assembles the desired flow table in memory. Nothing is actually + * sent to the switch until a later call to ofctrl_run(). */ +void +ofctrl_add_flow(uint8_t table_id, uint16_t priority, + const struct match *match, const struct ofpbuf *ofpacts) +{ + struct ovn_flow *f = xmalloc(sizeof *f); + f->table_id = table_id; + f->priority = priority; + f->match = *match; + f->ofpacts = xmemdup(ofpacts->data, ofpacts->size); + f->ofpacts_len = ofpacts->size; + + if (ovn_flow_lookup(&desired_flows, f)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + if (!VLOG_DROP_INFO(&rl)) { + char *s = ovn_flow_to_string(f); + VLOG_INFO("dropping duplicate flow: %s", s); + free(s); + } + + ovn_flow_destroy(f); + return; + } + + hmap_insert(&desired_flows, &f->hmap_node, ovn_flow_hash(f)); +} + +/* ovn_flow. */ + +/* Returns a hash of the key in 'f'. */ +static uint32_t +ovn_flow_hash(const struct ovn_flow *f) +{ + return hash_2words((f->table_id << 16) | f->priority, + match_hash(&f->match, 0)); + +} + +/* Finds and returns an ovn_flow in 'flow_table' whose key is identical to + * 'target''s key, or NULL if there is none. */ +static struct ovn_flow * +ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target) +{ + struct ovn_flow *f; + + HMAP_FOR_EACH_WITH_HASH (f, hmap_node, target->hmap_node.hash, + flow_table) { + if (f->table_id == target->table_id + && f->priority == target->priority + && match_equal(&f->match, &target->match)) { + return f; + } + } + return NULL; +} + +static char * +ovn_flow_to_string(const struct ovn_flow *f) +{ + struct ds s = DS_EMPTY_INITIALIZER; + ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id); + ds_put_format(&s, "priority=%"PRIu16", ", f->priority); + match_format(&f->match, &s, OFP_DEFAULT_PRIORITY); + ds_put_cstr(&s, ", actions="); + ofpacts_format(f->ofpacts, f->ofpacts_len, &s); + return ds_steal_cstr(&s); +} + +static void +ovn_flow_log(const struct ovn_flow *f, const char *action) +{ + if (VLOG_IS_DBG_ENABLED()) { + char *s = ovn_flow_to_string(f); + VLOG_DBG("%s flow: %s", action, s); + free(s); + } +} + +static void +ovn_flow_destroy(struct ovn_flow *f) +{ + if (f) { + free(f->ofpacts); + free(f); + } +} + +/* Flow tables of struct ovn_flow. */ + +static void +ovn_flow_table_clear(struct hmap *flow_table) +{ + struct ovn_flow *f, *next; + HMAP_FOR_EACH_SAFE (f, next, hmap_node, flow_table) { + hmap_remove(flow_table, &f->hmap_node); + ovn_flow_destroy(f); + } +} +static void +ovn_flow_table_destroy(struct hmap *flow_table) +{ + ovn_flow_table_clear(flow_table); + hmap_destroy(flow_table); +} + +/* Flow table update. */ + +static void +queue_flow_mod(struct ofputil_flow_mod *fm) +{ + fm->buffer_id = UINT32_MAX; + fm->out_port = OFPP_ANY; + fm->out_group = OFPG_ALL; + queue_msg(ofputil_encode_flow_mod(fm, OFPUTIL_P_OF13_OXM)); +} + +static void +ofctrl_update_flows(void) +{ + /* If we've (re)connected, don't make any assumptions about the flows in + * the switch: delete all of them. (We'll immediately repopulate it + * below.) */ + if (seqno != rconn_get_connection_seqno(swconn)) { + seqno = rconn_get_connection_seqno(swconn); + + /* Send a flow_mod to delete all flows. */ + struct ofputil_flow_mod fm = { + .match = MATCH_CATCHALL_INITIALIZER, + .table_id = OFPTT_ALL, + .command = OFPFC_DELETE, + }; + queue_flow_mod(&fm); + VLOG_DBG("clearing all flows"); + + /* Clear installed_flows, to match the state of the switch. */ + ovn_flow_table_clear(&installed_flows); + } + + /* Iterate through all of the installed flows. If any of them are no + * longer desired, delete them; if any of them should have different + * actions, update them. */ + struct ovn_flow *i, *next; + HMAP_FOR_EACH_SAFE (i, next, hmap_node, &installed_flows) { + struct ovn_flow *d = ovn_flow_lookup(&desired_flows, i); + if (!d) { + /* Installed flow is no longer desirable. Delete it from the + * switch and from installed_flows. */ + struct ofputil_flow_mod fm = { + .match = i->match, + .priority = i->priority, + .table_id = i->table_id, + .command = OFPFC_DELETE_STRICT, + }; + queue_flow_mod(&fm); + ovn_flow_log(i, "removing"); + + hmap_remove(&installed_flows, &i->hmap_node); + ovn_flow_destroy(i); + } else { + if (!ofpacts_equal(i->ofpacts, i->ofpacts_len, + d->ofpacts, d->ofpacts_len)) { + /* Update actions in installed flow. */ + struct ofputil_flow_mod fm = { + .match = i->match, + .priority = i->priority, + .table_id = i->table_id, + .ofpacts = i->ofpacts, + .ofpacts_len = i->ofpacts_len, + .command = OFPFC_MODIFY_STRICT, + }; + queue_flow_mod(&fm); + ovn_flow_log(i, "updating"); + + /* Replace 'i''s actions by 'd''s. */ + free(i->ofpacts); + i->ofpacts = d->ofpacts; + i->ofpacts_len = d->ofpacts_len; + d->ofpacts = NULL; + d->ofpacts_len = 0; + } + + hmap_remove(&desired_flows, &d->hmap_node); + ovn_flow_destroy(d); + } + } + + /* The previous loop removed from desired_flows all of the flows that are + * already installed. Thus, any flows remaining in desired_flows need to + * be added to the flow table. */ + struct ovn_flow *d; + HMAP_FOR_EACH_SAFE (d, next, hmap_node, &desired_flows) { + /* Send flow_mod to add flow. */ + struct ofputil_flow_mod fm = { + .match = d->match, + .priority = d->priority, + .table_id = d->table_id, + .ofpacts = d->ofpacts, + .ofpacts_len = d->ofpacts_len, + .command = OFPFC_ADD, + }; + queue_flow_mod(&fm); + ovn_flow_log(d, "adding"); + + /* Move 'd' from desired_flows to installed_flows. */ + hmap_remove(&desired_flows, &d->hmap_node); + hmap_insert(&installed_flows, &d->hmap_node, d->hmap_node.hash); + } +} diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h new file mode 100644 index 0000000..8c689e6 --- /dev/null +++ b/ovn/controller/ofctrl.h @@ -0,0 +1,37 @@ +/* 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 OFCTRL_H +#define OFCTRL_H 1 + +#include <stdint.h> + +struct controller_ctx; +struct match; +struct ofpbuf; + +/* Interface for OVN main loop. */ +void ofctrl_init(void); +void ofctrl_run(struct controller_ctx *); +void ofctrl_wait(void); +void ofctrl_destroy(void); + +/* Flow table interface to the rest of ovn-controller. */ +void ofctrl_clear_flows(void); +void ofctrl_add_flow(uint8_t table_id, uint16_t priority, + const struct match *, const struct ofpbuf *ofpacts); + +#endif /* ovn/ofctrl.h */ diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 12931b5..32bbc67 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -15,6 +15,8 @@ #include <config.h> +#include "ovn-controller.h" + #include <errno.h> #include <getopt.h> #include <signal.h> @@ -37,7 +39,7 @@ #include "unixctl.h" #include "util.h" -#include "ovn-controller.h" +#include "ofctrl.h" #include "bindings.h" #include "chassis.h" #include "pipeline.h" @@ -166,6 +168,8 @@ main(int argc, char *argv[]) ovsrec_init(); sbrec_init(); + ofctrl_init(); + /* Connect to OVS OVSDB instance. We do not monitor all tables by * default, so modules must register their interest explicitly. */ ctx.ovs_idl = ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true); @@ -220,6 +224,7 @@ main(int argc, char *argv[]) chassis_run(&ctx); bindings_run(&ctx); pipeline_run(&ctx); + ofctrl_run(&ctx); unixctl_server_run(unixctl); unixctl_server_wait(unixctl); @@ -229,11 +234,13 @@ main(int argc, char *argv[]) ovsdb_idl_wait(ctx.ovs_idl); ovsdb_idl_wait(ctx.ovnsb_idl); + ofctrl_wait(); poll_block(); } unixctl_server_destroy(unixctl); pipeline_destroy(&ctx); + ofctrl_destroy(); bindings_destroy(&ctx); chassis_destroy(&ctx); diff --git a/ovn/controller/pipeline.c b/ovn/controller/pipeline.c index f124354..5013060 100644 --- a/ovn/controller/pipeline.c +++ b/ovn/controller/pipeline.c @@ -16,6 +16,7 @@ #include <config.h> #include "pipeline.h" #include "dynamic-string.h" +#include "ofctrl.h" #include "ofp-actions.h" #include "ofpbuf.h" #include "openvswitch/vlog.h" @@ -241,20 +242,6 @@ pipeline_init(struct controller_ctx *ctx) ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_actions); } -static void -add_ovn_flow(uint8_t table_id, uint16_t priority, const struct match *match, - const struct ofpbuf *ofpacts) -{ - struct ds s = DS_EMPTY_INITIALIZER; - ds_put_format(&s, "table_id=%"PRIu8", ", table_id); - ds_put_format(&s, "priority=%"PRIu16", ", priority); - match_format(match, &s, OFP_DEFAULT_PRIORITY); - ds_put_cstr(&s, ", actions="); - ofpacts_format(ofpacts->data, ofpacts->size, &s); - VLOG_INFO("%s", ds_cstr(&s)); - ds_destroy(&s); -} - /* Translates logical flows in the Pipeline table in the OVN_SB database * into OpenFlow flows. */ void @@ -265,7 +252,8 @@ pipeline_run(struct controller_ctx *ctx) ldp_run(ctx); - VLOG_INFO("starting run..."); + ofctrl_clear_flows(); + const struct sbrec_pipeline *pipeline; SBREC_PIPELINE_FOR_EACH (pipeline, ctx->ovnsb_idl) { /* Find the "struct logical_datapath" asssociated with this Pipeline @@ -335,8 +323,8 @@ pipeline_run(struct controller_ctx *ctx) m->match.flow.conj_id += conj_id_ofs; } if (!m->n) { - add_ovn_flow(pipeline->table_id + 16, pipeline->priority, - &m->match, &ofpacts); + ofctrl_add_flow(pipeline->table_id + 16, pipeline->priority, + &m->match, &ofpacts); } else { uint64_t conj_stubs[64 / 8]; struct ofpbuf conj; @@ -351,8 +339,8 @@ pipeline_run(struct controller_ctx *ctx) dst->clause = src->clause; dst->n_clauses = src->n_clauses; } - add_ovn_flow(pipeline->table_id + 16, pipeline->priority, - &m->match, &conj); + ofctrl_add_flow(pipeline->table_id + 16, pipeline->priority, + &m->match, &conj); ofpbuf_uninit(&conj); } } @@ -362,7 +350,6 @@ pipeline_run(struct controller_ctx *ctx) ofpbuf_uninit(&ofpacts); conj_id_ofs += n_conjs; } - VLOG_INFO("...done"); } void -- 2.1.3 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev