This patch adds local router support. The CMS provides a hint for a NB distributed logical router as to which logical switches the logical router services; the CMS always knows this information. A new external ID tentatively named local-router-lss is used, which is a list of NB logical switch UUIDs associated with a logical router.
This association is converted by northd into SB database format also using an external ID, local-router-lss, but storing logical switch datapath tunnel keys. During lflow processing the above southbound association is queried to allow only processing southbound logical flows that are needed on a given HV. Two hash tables are used to keep track of which logical router data paths are needed locally and which are already known to be not needed. Signed-off-by: Darrell Ball <dlu...@gmail.com> --- ovn/controller/lflow.c | 113 +++++++++++++++++++++++++++ ovn/northd/ovn-northd.c | 38 +++++++++- ovn/ovn-nb.xml | 13 +++- ovn/ovn-sb.xml | 12 +++ tests/ovn.at | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 369 insertions(+), 4 deletions(-) diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 96b7c66..98dab9f 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -35,6 +35,14 @@ VLOG_DEFINE_THIS_MODULE(lflow); /* Contains "struct expr_symbol"s for fields supported by OVN lflows. */ static struct shash symtab; +struct local_router { + struct hmap_node hmap_node; +}; + +struct non_local_router { + struct hmap_node hmap_node; +}; + static void add_logical_register(struct shash *symtab, enum mf_field_id id) { @@ -193,6 +201,72 @@ is_switch(const struct sbrec_datapath_binding *ldp) } +static bool +is_router_needed_locally(const char *local_router_lss, + const struct hmap *local_datapaths) +{ + char *cur, *next, *start; + next = start = xstrdup(local_router_lss); + while ((cur = strsep(&next, ",")) && *cur) { + + int tunnel_key; + if (!str_to_int(cur, 10, &tunnel_key)) { + VLOG_WARN("Invalid value for OVN tunnel_key: %s", + cur); + continue; + } + + if (get_local_datapath(local_datapaths, tunnel_key)) { + free(start); + return true; + } + } + free(start); + return false; +} + +struct local_router * +get_local_router(const struct hmap *local_routers, uint32_t tunnel_key) +{ + struct hmap_node *node = hmap_first_with_hash(local_routers, + tunnel_key); + return (node + ? CONTAINER_OF(node, struct local_router, hmap_node) + : NULL); +} + +static void +add_local_router(struct hmap *local_routers, uint32_t tunnel_key) +{ + if (get_local_router(local_routers, tunnel_key)) { + return; + } + + struct local_router *lr = xzalloc(sizeof *lr); + hmap_insert(local_routers, &lr->hmap_node, tunnel_key); +} + +struct non_local_router * +get_non_local_router(const struct hmap *non_local_routers, uint32_t tunnel_key) +{ + struct hmap_node *node = hmap_first_with_hash(non_local_routers, + tunnel_key); + return (node + ? CONTAINER_OF(node, struct non_local_router, hmap_node) + : NULL); +} + +static void +add_non_local_router(struct hmap *non_local_routers, uint32_t tunnel_key) +{ + if (get_non_local_router(non_local_routers, tunnel_key)) { + return; + } + + struct non_local_router *lr = xzalloc(sizeof *lr); + hmap_insert(non_local_routers, &lr->hmap_node, tunnel_key); +} + /* Adds the logical flows from the Logical_Flow table to 'flow_table'. */ static void add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, @@ -203,6 +277,9 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, { uint32_t conj_id_ofs = 1; + struct hmap local_routers = HMAP_INITIALIZER(&local_routers); + struct hmap non_local_routers = HMAP_INITIALIZER(&non_local_routers); + const struct sbrec_logical_flow *lflow; SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) { /* Determine translation of logical table IDs to physical table IDs. */ @@ -250,6 +327,26 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, continue; } } + } else { + const char *local_router_lss = + smap_get(&ldp->external_ids, "local-router-lss"); + if (local_router_lss && strcmp(local_router_lss, "")) { + if (get_local_router(&local_routers, ldp->tunnel_key)) { + ; + } else if (get_non_local_router(&non_local_routers, + ldp->tunnel_key)) { + continue; + } else { + if (is_router_needed_locally(local_router_lss, + local_datapaths)) { + add_local_router(&local_routers,ldp->tunnel_key); + } else { + add_non_local_router(&non_local_routers, + ldp->tunnel_key); + continue; + } + } + } } /* Determine translation of logical table IDs to physical table IDs. */ @@ -360,6 +457,22 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, ofpbuf_uninit(&ofpacts); conj_id_ofs += n_conjs; } + + struct local_router *lr_cur_node, *lr_next_node; + HMAP_FOR_EACH_SAFE (lr_cur_node, lr_next_node, hmap_node, + &local_routers) { + hmap_remove(&local_routers, &lr_cur_node->hmap_node); + free(lr_cur_node); + } + hmap_destroy(&local_routers); + + struct non_local_router *nlr_cur_node, *nlr_next_node; + HMAP_FOR_EACH_SAFE (nlr_cur_node, nlr_next_node, hmap_node, + &non_local_routers) { + hmap_remove(&non_local_routers, &nlr_cur_node->hmap_node); + free(nlr_cur_node); + } + hmap_destroy(&non_local_routers); } static void diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 44e9430..7ea4544 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -461,8 +461,42 @@ build_datapaths(struct northd_context *ctx, struct hmap *datapaths) char uuid_s[UUID_LEN + 1]; sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key)); const char *key = od->nbs ? "logical-switch" : "logical-router"; - const struct smap id = SMAP_CONST1(&id, key, uuid_s); - sbrec_datapath_binding_set_external_ids(od->sb, &id); + struct smap ids = SMAP_INITIALIZER(&ids); + smap_add(&ids, key, uuid_s); + + if (od->nbr) { + const char *value = smap_get(&od->nbr->external_ids, + "local-router-lss"); + if (value) { + struct ds tunnel_key_ds = DS_EMPTY_INITIALIZER; + char *cur, *next, *start; + const char *tunnel_key_s; + + next = start = xstrdup(value); + while ((cur = strsep(&next, ",")) && *cur) { + struct uuid nb_ls_uuid; + bool is_uuid = uuid_from_string(&nb_ls_uuid, cur); + if (is_uuid) { + struct ovn_datapath *od = + ovn_datapath_find(datapaths, &nb_ls_uuid); + if (od) { + ds_put_format(&tunnel_key_ds, "%"PRIu32, + (uint32_t) od->sb->tunnel_key); + ds_put_char(&tunnel_key_ds, ','); + } + } + } + free(start); + ds_chomp(&tunnel_key_ds, ','); + tunnel_key_s = ds_cstr(&tunnel_key_ds); + smap_add(&ids, "local-router-lss", tunnel_key_s); + ds_destroy(&tunnel_key_ds); + } else { + smap_add(&ids, "local-router-lss", ""); + } + } + sbrec_datapath_binding_set_external_ids(od->sb, &ids); + smap_destroy(&ids); sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key); } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 34251af..2657122 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -639,8 +639,17 @@ </column> <group title="Common Columns"> - <column name="external_ids"> - See <em>External IDs</em> at the beginning of this document. + <column name="external_ids" key="local-router-lss"> + The CMS populates this optional key with a list of + <ref table="Logical_Switch"/> UUID strings, that are associated with + <ref table="Logical_Switch"/>s that are serviced by a + <code>local router</code> <ref table="Logical_Router"/>. + <code>ovn-northd</code> converts the UUID string list to a southbound + datapath tunnel key list and writes the tunnel key list to the + <ref table="Datapath_Binding" db="OVN_Southbound"/> table + <code>external_ids</code> <code>local-router-lss</code> in the + <ref db="OVN_Southbound"/> database. The use of this external_id + is optional. </column> </group> </table> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index efd2f9a..79db3e9 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1189,6 +1189,18 @@ tcp.flags = RST; corresponding <ref table="Logical_Router" db="OVN_Northbound"/> row in the <ref db="OVN_Northbound"/> database. </column> + + <column name="external_ids" key="local-router-lss"> + <code>ovn-northd</code> populates this key with a list of tunnel keys + associated with <ref table="Logical_Switch"/>s that a + <code>local router</code> + <ref table="Logical_Router" db="OVN_Northbound"/> services; + <code>ovn-northd</code> reads from the corresponding + <ref table="Logical_Router" db="OVN_Northbound"/> + <code>external_ids</code> <code>local-router-lss</code>. The use of + this external_id is optional. + </column> + </group> <group title="Common Columns"> diff --git a/tests/ovn.at b/tests/ovn.at index e6ac1d7..e732569 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2611,3 +2611,200 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP + +AT_SETUP([ovn -- 2 HVs, 3 LS, 1 lport/LS, 2 peer LRs, local router]) +AT_KEYWORDS([ovnlocalrouter]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# Logical network: +# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24 +# network. R1 has switch foo (192.168.1.0/24) connected to it. +# R2 has alice (172.16.1.0/24) and bob (172.16.2.0/24) connected to it. + +lswitch_uuid_foo=`ovn-nbctl -- create Logical_Switch name=foo` +lswitch_uuid_alice=`ovn-nbctl -- create Logical_Switch name=alice` +lswitch_uuid_bob=`ovn-nbctl -- create Logical_Switch name=bob` + +ovn-nbctl create Logical_Router name=R1 +ovn-nbctl create Logical_Router name=R2 external-ids:local-router-lss=\ +$lswitch_uuid_alice,$lswitch_uuid_bob + +# Connect foo to R1 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=foo \ +network=192.168.1.1/24 mac=\"00:00:00:01:02:03\" -- add Logical_Router R1 \ +ports @lrp -- lport-add foo rp-foo + +ovn-nbctl set Logical_port rp-foo type=router options:router-port=foo \ +addresses=\"00:00:00:01:02:03\" + + +# Connect alice to R2 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=alice \ +network=172.16.1.1/24 mac=\"00:00:00:01:02:04\" -- add Logical_Router R2 \ +ports @lrp -- lport-add alice rp-alice + +ovn-nbctl set Logical_port rp-alice type=router options:router-port=alice \ +addresses=\"00:00:00:01:02:04\" + + +# Connect bob to R1 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=bob-r1 \ +network=172.16.2.3/24 mac=\"00:00:00:01:02:06\" -- add Logical_Router R1 \ +ports @lrp -- lport-add bob rp-bob-r1 + +ovn-nbctl set Logical_port rp-bob-r1 type=router options:router-port=bob-r1 \ +addresses=\"00:00:00:01:02:06\" + + +# Connect bob to R2 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=bob \ +network=172.16.2.1/24 mac=\"00:00:00:01:02:05\" -- add Logical_Router R2 \ +ports @lrp -- lport-add bob rp-bob + +ovn-nbctl set Logical_port rp-bob type=router options:router-port=bob \ +addresses=\"00:00:00:01:02:05\" + + +# Create logical port foo1 in foo +ovn-nbctl lport-add foo foo1 \ +-- lport-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" + +# Create logical port alice1 in alice +ovn-nbctl lport-add alice alice1 \ +-- lport-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" + +# Create logical port bob1 in bob +ovn-nbctl lport-add bob bob1 \ +-- lport-set-addresses bob1 "f0:00:00:01:02:05 172.16.2.2" + +# Create two hypervisor and create OVS ports corresponding to logical ports. +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=foo1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl -- add-port br-int hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=bob1 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int hv2-vif2 -- \ + set interface hv2-vif2 external-ids:iface-id=alice1 \ + options:tx_pcap=hv2/vif2-tx.pcap \ + options:rxq_pcap=hv2/vif2-rx.pcap \ + ofport-request=2 + + +# Pre-populate the hypervisors' ARP tables so that we don't lose any +# packets for ARP resolution (native tunneling doesn't queue packets +# for ARP resolution). +ovn_populate_arp + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# Send ip packets between foo1 and bob1 +src_mac="f00000010203" +dst_mac="000000010203" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 2 2` +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + +# Send ip packets between bob1 and alice1 +src_mac="f00000010205" +dst_mac="000000010205" +src_ip=`ip_to_hex 172 16 2 2` +dst_ip=`ip_to_hex 172 16 1 2` +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +as hv2 ovs-appctl netdev-dummy/receive hv2-vif1 $packet + +echo "---------NB dump-----" +ovn-nbctl show +echo "---------------------" +ovn-nbctl list logical_router +echo "---------------------" +ovn-nbctl list logical_router_port +echo "---------------------" + +echo "---------SB dump-----" +ovn-sbctl list datapath_binding +echo "---------------------" +ovn-sbctl list port_binding +echo "---------------------" +ovn-sbctl dump-flows + +echo "------ hv1 dump ----------" +as hv1 ovs-vsctl show +as hv1 ovs-ofctl show br-int +as hv1 ovs-ofctl dump-flows br-int +echo "------ hv2 dump ----------" +as hv2 ovs-vsctl show +as hv2 ovs-ofctl show br-int +as hv2 ovs-ofctl dump-flows br-int + +# Packet to Expect at bob1 +src_mac="000000010206" +dst_mac="f00000010205" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 2 2` +expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | trim_zeros > received.packets +echo $expected | trim_zeros > expout +AT_CHECK([cat received.packets], [0], [expout]) + +# Packet to Expect at alice1 +src_mac="000000010204" +dst_mac="f00000010204" +src_ip=`ip_to_hex 172 16 2 2` +dst_ip=`ip_to_hex 172 16 1 2` +expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/vif2-tx.pcap | trim_zeros > received1.packets +echo $expected | trim_zeros > expout +AT_CHECK([cat received1.packets], [0], [expout]) + +for sim in hv1 hv2; do + as $sim + OVS_APP_EXIT_AND_WAIT([ovn-controller]) + OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) + OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +done + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as main +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +AT_CLEANUP -- 1.9.1 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev