[ovs-dev] [patch_v4] ovn: Add local router support (RFC)

Darrell Ball dlu998 at gmail.com
Tue May 31 16:37:10 UTC 2016


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 <dlu998 at 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




More information about the dev mailing list