On 5/19/20 8:34 AM, Numan Siddique wrote:
On Wed, May 13, 2020 at 7:10 PM Ihar Hrachyshka <[email protected]> wrote:

Assuming only a single localnet port is actually plugged mapped on
each chassis, this allows to maintain disjoint networks plugged to the
same switch.  This is useful to simplify resource management for
OpenStack "routed provider networks" feature [1] where a single
"network" (which traditionally maps to logical switches in OVN) is
comprised of multiple L2 segments and assumes external L3 routing
implemented between the segments.

[1]:
https://docs.openstack.org/ocata/networking-guide/config-routed-networks.html

Signed-off-by: Ihar Hrachyshka <[email protected]>

Hi Ihar,

Please see below. I've 2 small comments.

With the first comment addressed
Acked-by: Numan Siddique <[email protected]>

Since Dumitru has already acked your patches, can you please put an
"Acked-by" tag for Dumitru
and also mine too when you spin up v8.


Thanks for review!

I'll push v8 once I understand the nature of the first request to change (see below).



Thanks
Numan


---
  controller/binding.c   |  16 ++
  controller/patch.c     |  14 +-
  northd/ovn-northd.c    |  62 +++--
  ovn-architecture.7.xml |  50 ++--
  ovn-nb.xml             |  23 +-
  ovn-sb.xml             |  21 +-
  tests/ovn.at           | 504 +++++++++++++++++++++++++++++++++++++++++
  7 files changed, 637 insertions(+), 53 deletions(-)

diff --git a/controller/binding.c b/controller/binding.c
index 9d37a23cc..774c6f242 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -683,12 +683,28 @@ add_localnet_egress_interface_mappings(
      }
  }

+static bool
+is_network_plugged(const struct sbrec_port_binding *binding_rec,
+                   struct shash *bridge_mappings)
+{
+    const char *network = smap_get(&binding_rec->options, "network_name");
+    if (!network) {
+        return false;
+    }
+    return shash_find_data(bridge_mappings, network);


shash_find_data returns (void *) and not bool.

I'd suggest to have like

return network ?  !!shash_find_data(bridge_mappings, network) : false;


It's not hard to change that, but I'm slightly puzzled and wonder if I miss something in the picture that I should grasp before I adjust the code without proper understanding. Let me elaborate.

My understanding is that we assume native C99 _Bool in our compiler (not a typedef / macro mapped onto int). If that's the case, AFAIU C99 standard requires implicit conversion of pointer types to booleans, and defines it safely (meaning, a NULL-pointer always maps to false regardless of actual representation of the NULL-pointer in memory). Considering this, your suggestion seems effectively the same as what is already written, just more "traditional" (for example, it would be safer in C89 environment). Which particular issue do you think we are to tackle here?



+}
+
  static void
  consider_localnet_port(const struct sbrec_port_binding *binding_rec,
                         struct shash *bridge_mappings,
                         struct sset *egress_ifaces,
                         struct hmap *local_datapaths)
  {
+    /* Ignore localnet ports for unplugged networks. */
+    if (!is_network_plugged(binding_rec, bridge_mappings)) {
+        return;
+    }
+
      add_localnet_egress_interface_mappings(binding_rec,
              bridge_mappings, egress_ifaces);

diff --git a/controller/patch.c b/controller/patch.c
index 349faae17..7ad30d9cc 100644
--- a/controller/patch.c
+++ b/controller/patch.c
@@ -198,8 +198,10 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
              continue;
          }

+        bool is_localnet = false;
          const char *patch_port_id;
          if (!strcmp(binding->type, "localnet")) {
+            is_localnet = true;
              patch_port_id = "ovn-localnet-port";
          } else if (!strcmp(binding->type, "l2gateway")) {
              if (!binding->chassis
@@ -224,9 +226,15 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
          struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings,
network);
          if (!br_ln) {
              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
-                    "with network name '%s'",
-                    binding->type, binding->logical_port, network);
+            if (!is_localnet) {
+                VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
+                        "with network name '%s'",
+                        binding->type, binding->logical_port, network);
+            } else {
+                VLOG_INFO_RL(&rl, "bridge not found for localnet port
'%s' "
+                        "with network name '%s'; skipping",
+                        binding->logical_port, network);
+            }
              continue;
          }

diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 5e7796c5b..2308b10e6 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -543,7 +543,9 @@ struct ovn_datapath {
      /* The "derived" OVN port representing the instance of l3dgw_port on
       * the "redirect-chassis". */
      struct ovn_port *l3redirect_port;
-    struct ovn_port *localnet_port;
+
+    struct ovn_port **localnet_ports;
+    size_t n_localnet_ports;

      struct ovs_list lr_list; /* In list of logical router datapaths. */
      /* The logical router group to which this datapath belongs.
@@ -611,6 +613,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct
ovn_datapath *od)
          ovn_destroy_tnlids(&od->port_tnlids);
          bitmap_free(od->ipam_info.allocated_ipv4s);
          free(od->router_ports);
+        free(od->localnet_ports);
          ovn_ls_port_group_destroy(&od->nb_pgs);
          destroy_mcast_info_for_datapath(od);

@@ -2025,6 +2028,7 @@ join_logical_ports(struct northd_context *ctx,
      struct ovn_datapath *od;
      HMAP_FOR_EACH (od, key_node, datapaths) {
          if (od->nbs) {
+            size_t allocated_localnet_ports = 0;

Small nit:  s/allocated_localnet_ports/n_allocated_localnet_ports

              for (size_t i = 0; i < od->nbs->n_ports; i++) {
                  const struct nbrec_logical_switch_port *nbsp
                      = od->nbs->ports[i];
@@ -2059,7 +2063,12 @@ join_logical_ports(struct northd_context *ctx,
                  }

                  if (!strcmp(nbsp->type, "localnet")) {
-                   od->localnet_port = op;
+                   if (od->n_localnet_ports >= allocated_localnet_ports) {
+                       od->localnet_ports = x2nrealloc(
+                           od->localnet_ports, &allocated_localnet_ports,
+                           sizeof *od->localnet_ports);
+                   }
+                   od->localnet_ports[od->n_localnet_ports++] = op;
                  }

                  op->lsp_addrs
@@ -3018,7 +3027,7 @@ ovn_port_update_sbrec(struct northd_context *ctx,
                                "reside-on-redirect-chassis", false) ||
                  op->peer == op->peer->od->l3dgw_port)) {
                  add_router_port_garp = true;
-            } else if (chassis && op->od->localnet_port) {
+            } else if (chassis && op->od->n_localnet_ports) {
                  add_router_port_garp = true;
              }

@@ -4736,8 +4745,8 @@ build_pre_acls(struct ovn_datapath *od, struct hmap
*lflows)
          for (size_t i = 0; i < od->n_router_ports; i++) {
              build_pre_acl_flows(od, od->router_ports[i], lflows);
          }
-        if (od->localnet_port) {
-            build_pre_acl_flows(od, od->localnet_port, lflows);
+        for (size_t i = 0; i < od->n_localnet_ports; i++) {
+            build_pre_acl_flows(od, od->localnet_ports[i], lflows);
          }

          /* Ingress and Egress Pre-ACL Table (Priority 110).
@@ -6003,9 +6012,9 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset
*ips,
      /* Send a the packet only to the router pipeline and skip flooding it
       * in the broadcast domain (except for the localnet port).
       */
-    if (od->localnet_port) {
+    for (size_t i = 0; i < od->n_localnet_ports; i++) {
          ds_put_format(&actions, "clone { outport = %s; output; }; ",
-                      od->localnet_port->json_key);
+                      od->localnet_ports[i]->json_key);
      }
      ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
      ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
@@ -6587,25 +6596,31 @@ build_lswitch_flows(struct hmap *datapaths, struct
hmap *ports,
          }

          bool is_external = lsp_is_external(op->nbsp);
-        if (is_external && (!op->od->localnet_port ||
+        if (is_external && (!op->od->n_localnet_ports ||
                              !op->nbsp->ha_chassis_group)) {
-            /* If it's an external port and there is no localnet port
+            /* If it's an external port and there are no localnet ports
               * and if it doesn't belong to an HA chassis group ignore it.
*/
              continue;
          }

          for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-            const char *json_key;
              if (is_external) {
-                json_key = op->od->localnet_port->json_key;
+                for (size_t j = 0; j < op->od->n_localnet_ports; j++) {
+                    build_dhcpv4_options_flows(
+                        op, &op->lsp_addrs[i],
+                        op->od->localnet_ports[j]->json_key, is_external,
+                        lflows);
+                    build_dhcpv6_options_flows(
+                        op, &op->lsp_addrs[i],
+                        op->od->localnet_ports[j]->json_key, is_external,
+                        lflows);
+                }
              } else {
-                json_key = op->json_key;
+                build_dhcpv4_options_flows(op, &op->lsp_addrs[i],
op->json_key,
+                                           is_external, lflows);
+                build_dhcpv6_options_flows(op, &op->lsp_addrs[i],
op->json_key,
+                                           is_external, lflows);
              }
-            build_dhcpv4_options_flows(op, &op->lsp_addrs[i], json_key,
-                                       is_external, lflows);
-
-            build_dhcpv6_options_flows(op, &op->lsp_addrs[i], json_key,
-                                       is_external, lflows);
          }
      }

@@ -6661,8 +6676,7 @@ build_lswitch_flows(struct hmap *datapaths, struct
hmap *ports,
      }

      HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp || !lsp_is_external(op->nbsp) ||
-            !op->od->localnet_port) {
+        if (!op->nbsp || !lsp_is_external(op->nbsp)) {
             continue;
          }

@@ -6670,8 +6684,10 @@ build_lswitch_flows(struct hmap *datapaths, struct
hmap *ports,
           * external ports  on chassis not binding those ports.
           * This makes the router pipeline to be run only on the chassis
           * binding the external ports. */
-        build_drop_arp_nd_flows_for_unbound_router_ports(
-            op, op->od->localnet_port, lflows);
+        for (size_t i = 0; i < op->od->n_localnet_ports; i++) {
+            build_drop_arp_nd_flows_for_unbound_router_ports(
+                op, op->od->localnet_ports[i], lflows);
+        }
      }

      char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
@@ -6889,7 +6905,7 @@ build_lswitch_flows(struct hmap *datapaths, struct
hmap *ports,
                                ETH_ADDR_ARGS(mac));
                  if (op->peer->od->l3dgw_port
                      && op->peer->od->l3redirect_port
-                    && op->od->localnet_port) {
+                    && op->od->n_localnet_ports) {
                      bool add_chassis_resident_check = false;
                      if (op->peer == op->peer->od->l3dgw_port) {
                          /* The peer of this port represents a distributed
@@ -8195,7 +8211,7 @@ build_lrouter_flows(struct hmap *datapaths, struct
hmap *ports,
                            op->lrp_networks.ipv4_addrs[i].addr_s);

              if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-                && op->peer->od->localnet_port) {
+                && op->peer->od->n_localnet_ports) {
                  bool add_chassis_resident_check = false;
                  if (op == op->od->l3dgw_port) {
                      /* Traffic with eth.src =
l3dgw_port->lrp_networks.ea_s
diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
index 5c125043e..246cebc19 100644
--- a/ovn-architecture.7.xml
+++ b/ovn-architecture.7.xml
@@ -441,9 +441,8 @@

    <p>
      A <code>localnet</code> logical switch port bridges a logical switch
to a
-    physical VLAN.  Any given logical switch should have no more than one
-    <code>localnet</code> port.  Such a logical switch is used in two
-    scenarios:
+    physical VLAN.  A logical switch may have one or more
<code>localnet</code>
+    ports.  Such a logical switch is used in two scenarios:
    </p>

    <ul>
@@ -463,6 +462,31 @@
      </li>
    </ul>

+  <p>
+    When a logical switch contains multiple <code>localnet</code> ports,
the
+    following is assumed.
+  </p>
+
+  <ul>
+    <li>
+      Each chassis has a bridge mapping for one of the
<code>localnet</code>
+      physical networks only.
+    </li>
+
+    <li>
+      To facilitate interconnectivity between VIF ports of the switch
that are
+      located on different chassis with different physical network
+      connectivity, the fabric implements L3 routing between these
adjacent
+      physical network segments.
+    </li>
+  </ul>
+
+  <p>
+    Note: nothing said above implies that a chassis cannot be plugged to
+    multiple physical networks as long as they belong to different
+    switches.
+  </p>
+
    <p>
      A <code>localport</code> logical switch port is a special kind of VIF
      logical switch port.  These ports are present in every chassis, not
bound
@@ -1951,13 +1975,13 @@
    <ol>
      <li>
        The packet first enters the ingress pipeline, and then egress
pipeline of
-      the source localnet logical switch datapath and is sent out via the
+      the source localnet logical switch datapath and is sent out via a
        localnet port of the source localnet logical switch (instead of
sending
        it to router pipeline).
      </li>

      <li>
-      The gateway chassis receives the packet via the localnet port of the
+      The gateway chassis receives the packet via a localnet port of the
        source localnet logical switch and sends it to the integration
bridge.
        The packet then enters the ingress pipeline, and then egress
pipeline of
        the source localnet logical switch datapath and enters the ingress
@@ -1972,11 +1996,11 @@
        From the router datapath, packet enters the ingress pipeline and
then
        egress pipeline of the destination localnet logical switch datapath.
        It then goes out of the integration bridge to the provider bridge (
-      belonging to the destination logical switch) via the localnet port.
+      belonging to the destination logical switch) via a localnet port.
      </li>

      <li>
-      The destination chassis receives the packet via the localnet port
and
+      The destination chassis receives the packet via a localnet port and
        sends it to the integration bridge. The packet enters the
        ingress pipeline and then egress pipeline of the destination
localnet
        logical switch and finally delivered to the destination VM port.
@@ -1991,13 +2015,13 @@
    <ol>
      <li>
        The packet first enters the ingress pipeline, and then egress
pipeline of
-      the source localnet logical switch datapath and is sent out via the
+      the source localnet logical switch datapath and is sent out via a
        localnet port of the source localnet logical switch (instead of
sending
        it to router pipeline).
      </li>

      <li>
-      The gateway chassis receives the packet via the localnet port of the
+      The gateway chassis receives the packet via a localnet port of the
        source localnet logical switch and sends it to the integration
bridge.
        The packet then enters the ingress pipeline, and then egress
pipeline of
        the source localnet logical switch datapath and enters the ingress
@@ -2013,7 +2037,7 @@
        egress pipeline of the localnet logical switch datapath which
provides
        external connectivity. It then goes out of the integration bridge
to the
        provider bridge (belonging to the logical switch which provides
external
-      connectivity) via the localnet port.
+      connectivity) via a localnet port.
      </li>
    </ol>

@@ -2023,7 +2047,7 @@

    <ol>
      <li>
-      The gateway chassis receives the packet from the localnet port of
+      The gateway chassis receives the packet from a localnet port of
        the logical switch which provides external connectivity. The packet
then
        enters the ingress pipeline and then egress pipeline of the localnet
        logical switch (which provides external connectivity). The packet
then
@@ -2034,12 +2058,12 @@
        The ingress pipeline of the logical router datapath applies the
unNATting
        rules. The packet then enters the ingress pipeline and then egress
        pipeline of the source localnet logical switch. Since the source VM
-      doesn't reside in the gateway chassis, the packet is sent out via
the
+      doesn't reside in the gateway chassis, the packet is sent out via a
        localnet port of the source logical switch.
      </li>

      <li>
-      The source chassis receives the packet via the localnet port and
+      The source chassis receives the packet via a localnet port and
        sends it to the integration bridge. The packet enters the
        ingress pipeline and then egress pipeline of the source localnet
        logical switch and finally gets delivered to the source VM port.
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 95ee4c9e6..acf56486b 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -244,14 +244,14 @@
      <p>
        There are two kinds of logical switches, that is, ones that fully
        virtualize the network (overlay logical switches) and ones that
provide
-      simple connectivity to a physical network (bridged logical
switches).
+      simple connectivity to physical networks (bridged logical switches).
        They work in the same way when providing connectivity between
logical
-      ports on same chasis, but differently when connecting remote logical
+      ports on same chassis, but differently when connecting remote
logical
        ports.  Overlay logical switches connect remote logical ports by
tunnels,
        while bridged logical switches provide connectivity to remote ports
by
-      bridging the packets to directly connected physical L2 segment with
the
+      bridging the packets to directly connected physical L2 segments
with the
        help of <code>localnet</code> ports.  Each bridged logical switch
has
-      one and only one <code>localnet</code> port, which has only one
special
+      one or more <code>localnet</code> ports, which have only one special
        address <code>unknown</code>.
      </p>

@@ -527,10 +527,15 @@

            <dt><code>localnet</code></dt>
            <dd>
-            A connection to a locally accessible network from each
-            <code>ovn-controller</code> instance.  A logical switch can
only
-            have a single <code>localnet</code> port attached.  This is
used
-            to model direct connectivity to an existing network.
+            A connection to a locally accessible network from
+            <code>ovn-controller</code> instances that have a
corresponding
+            bridge mapping.  A logical switch can have multiple
+            <code>localnet</code> ports attached.  This type is used to
model
+            direct connectivity to existing networks.  In this case, each
+            chassis should have a mapping for one of the physical networks
+            only.  Note: nothing said above implies that a chassis cannot
be
+            plugged to multiple physical networks as long as they belong
to
+            different switches.
            </dd>

            <dt><code>localport</code></dt>
@@ -721,7 +726,7 @@
            Required.  The name of the network to which the
<code>localnet</code>
            port is connected.  Each hypervisor, via
<code>ovn-controller</code>,
            uses its local configuration to determine exactly how to
connect to
-          this locally accessible network.
+          this locally accessible network, if at all.
          </column>
        </group>

diff --git a/ovn-sb.xml b/ovn-sb.xml
index 3aa7cd4da..1fa769f3d 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2626,10 +2626,15 @@ tcp.flags = RST;

            <dt><code>localnet</code></dt>
            <dd>
-            A connection to a locally accessible network from each
-            <code>ovn-controller</code> instance.  A logical switch can
only
-            have a single <code>localnet</code> port attached.  This is
used
-            to model direct connectivity to an existing network.
+            A connection to a locally accessible network from
+            <code>ovn-controller</code> instances that have a
corresponding
+            bridge mapping.  A logical switch can have multiple
+            <code>localnet</code> ports attached.  This type is used to
model
+            direct connectivity to existing networks.  In this case, each
+            chassis should have a mapping for one of the physical networks
+            only.  Note: nothing said above implies that a chassis cannot
be
+            plugged to multiple physical networks as long as they belong
to
+            different switches.
            </dd>

            <dt><code>localport</code></dt>
@@ -2777,7 +2782,13 @@ tcp.flags = RST;
            switch must have a bridge mapping configured to reach that
            <code>localnet</code>.  Traffic that arrives on a
            <code>localnet</code> port is never forwarded over a tunnel to
-          another chassis.
+          another chassis.  If there are multiple <code>localnet</code>
+          ports in a logical switch, each chassis should only have a
single
+          bridge mapping for one of the physical networks.  Note: In case
of
+          multiple <code>localnet</code> ports, to provide
interconnectivity
+          between all VIFs located on different chassis with different
fabric
+          connectivity, the fabric should implement some form of routing
+          between the segments.
          </p>
        </column>

diff --git a/tests/ovn.at b/tests/ovn.at
index 52d994972..0fe3765f8 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -48,6 +48,10 @@ m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST],
    [ovn_check_packets_remove_broadcast__ "$1" "$2"
     AT_CHECK([sort $rcv_text], [0], [expout])])

+m4_define([OVN_CHECK_PACKETS_CONTAIN],
+  [ovn_check_packets__ "$1" "$2"
+   AT_CHECK([sort $rcv_text | comm --nocheck-order -2 -3 expout -], [0],
[])])
+
  AT_BANNER([OVN components])

  AT_SETUP([ovn -- lexer])
@@ -2488,6 +2492,506 @@ OVN_CLEANUP([hv1],[hv2])

  AT_CLEANUP

+AT_SETUP([ovn -- 2 HVs, 2 LS, routing works for multiple colacated
segments attached to different switches])
+ovn_start
+
+for tag in `seq 10 30`; do
+    net_add n-$tag
+done
+
+for i in 1 2; do
+    sim_add hv-$i
+    as hv-$i
+    ovs-vsctl add-br br-phys11
+    ovs-vsctl add-br br-phys21
+    ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys-11:br-phys11,phys-21:br-phys21
+    ovn_attach n-11 br-phys11 192.168.0.${i}1
+    ovn_attach n-21 br-phys21 192.168.0.${i}2
+done
+
+for i in 1 2; do
+    lsname=ls-${i}0
+    ovn-nbctl ls-add $lsname
+    for tag in `seq ${i}1 ${i}9`; do
+        ln_port_name=ln-$tag
+        ovn-nbctl lsp-add $lsname $ln_port_name "" $tag
+        ovn-nbctl lsp-set-addresses $ln_port_name unknown
+        ovn-nbctl lsp-set-type $ln_port_name localnet
+        ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag
+    done
+done
+
+for hv in 1 2; do
+    as hv-$hv
+    for ls in 1 2; do
+        lsp_name=lp-$hv-$ls
+        ovs-vsctl add-port br-int vif-$hv-$ls -- \
+            set Interface vif-$hv-$ls external-ids:iface-id=$lsp_name \
+
options:tx_pcap=hv-$hv/vif-$hv-$ls-tx.pcap \
+
options:rxq_pcap=hv-$hv/vif-$hv-$ls-rx.pcap \
+                                  ofport-request=$hv$ls
+
+        ovn-nbctl lsp-add ls-${ls}0 $lsp_name
+        ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:00:${hv}${ls}
+        ovn-nbctl lsp-set-port-security $lsp_name
f0:00:00:00:00:${hv}${ls}
+
+        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+    done
+done
+
+
+ovn-nbctl --wait=sb sync
+ovn-nbctl show
+ovn-sbctl dump-flows
+
+echo "------ OVN dump ------"
+ovn-nbctl show
+ovn-sbctl show
+
+for i in 1 2; do
+    hv=hv-$i
+    echo "------ $hv dump ------"
+    as $hv ovs-vsctl show
+    as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int
+done
+
+# vif ports
+for i in 1-1 1-2 2-1 2-2; do
+    : > vif-$i.expected
+done
+
+# localnet ports
+for hv in 1 2; do
+    : > out-$hv.expected
+done
+
+test_packet() {
+    local hv=$1 inport=$2 outport=$3 dst=$4 src=$5 eth=$6 lout=$7
+
+    : > expout
+    if test $lout = unknown; then
+        # Expect the packet cloned to all localnet ports
+        for tag in `seq ${hv}1 ${hv}9`; do
+            echo "output(\"ln-$tag\");" >> expout
+        done
+    else
+        echo "output(\"$lout\");" >> expout
+    fi
+
+    # First try tracing the packet.
+    uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src &&
eth.type==0x$eth"
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all ls-${hv}0 "$uflow" | tee trace | sed
'1,/Minimal trace/d'], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    local packet=$(echo $dst$src | sed 's/://g')${eth}
+    as hv-$hv ovs-appctl netdev-dummy/receive vif-$inport $packet
+
+    if test $lout != unknown; then
+        # Expect the packet received by the peer VIF port
+        echo $packet >> vif-$outport.expected
+    fi
+
+    # regardless, the packet is sent through the bridge
+    local packet=$(echo $dst$src | sed 's/://g')810000$(printf "%.2x\n"
${hv}1)${eth}
+    echo $packet >> out-$hv.expected
+}
+
+test_packet 1 1-1 2-1 f0:00:00:00:00:21 f0:00:00:00:00:11 1001 lp-2-1
+test_packet 2 2-2 1-2 f0:00:00:00:00:12 f0:00:00:00:00:22 1001 lp-1-2
+
+# unknown mac goes through localnet port
+test_packet 1 1-1 2-1 f0:00:00:00:00:e0 f0:00:00:00:00:11 1001 unknown
+test_packet 2 2-2 1-2 f0:00:00:00:00:e0 f0:00:00:00:00:22 1001 unknown
+
+# Now check the packets actually received against the ones expected.
+for hv in 1 2; do
+    for ls in 1 2; do
+        port=$hv-$ls
+        # check that packets targeted to actual vifs arrived on the other
end
+        OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$hv/vif-$port-tx.pcap],
[vif-$port.expected])
+    done
+    # check that all packets, whether to known or unknown mac addresses,
were sent to fabric
+
OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$hv/br-phys${hv}1_n-${hv}1-tx.pcap],
[out-$hv.expected])
+done
+
+OVN_CLEANUP([hv-1],[hv-2])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- 2 HVs, 2 LS, broadcast traffic with multiple localnet
ports per switch])
+ovn_start
+
+for tag in `seq 10 30`; do
+    net_add n-$tag
+done
+
+for i in 1 2; do
+    sim_add hv-$i
+    as hv-$i
+    ovs-vsctl add-br br-phys11
+    ovs-vsctl add-br br-phys21
+    ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys-11:br-phys11,phys-21:br-phys21
+    ovn_attach n-11 br-phys11 192.168.0.${i}1
+    ovn_attach n-21 br-phys21 192.168.0.${i}2
+done
+
+for i in 1 2; do
+    lsname=ls-${i}0
+    ovn-nbctl ls-add $lsname
+    for tag in `seq ${i}1 ${i}9`; do
+        ln_port_name=ln-$tag
+        ovn-nbctl lsp-add $lsname $ln_port_name "" $tag
+        ovn-nbctl lsp-set-addresses $ln_port_name unknown
+        ovn-nbctl lsp-set-type $ln_port_name localnet
+        ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag
+    done
+done
+
+for hv in 1 2; do
+    as hv-$hv
+    for ls in 1 2; do
+        for peer in 8 9; do
+            lsp_name=lp-$hv-$ls-$peer
+            ovs-vsctl add-port br-int vif-$hv-$ls-$peer -- \
+                set Interface vif-$hv-$ls-$peer
external-ids:iface-id=$lsp_name \
+
options:tx_pcap=hv-$hv/vif-$hv-$ls-$peer-tx.pcap \
+
options:rxq_pcap=hv-$hv/vif-$hv-$ls-$peer-rx.pcap \
+                                      ofport-request=$hv$ls$peer
+
+            ovn-nbctl lsp-add ls-${ls}0 $lsp_name
+            ovn-nbctl lsp-set-addresses $lsp_name
f0:00:00:00:0${peer}:${hv}${ls}
+            ovn-nbctl lsp-set-port-security $lsp_name
f0:00:00:00:0${peer}:${hv}${ls}
+
+            OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+
+            : > vif-$hv-$ls-$peer.expected
+        done
+    done
+done
+
+
+ovn-nbctl --wait=sb sync
+ovn-nbctl show
+ovn-sbctl dump-flows
+
+echo "------ OVN dump ------"
+ovn-nbctl show
+ovn-sbctl show
+
+for i in 1 2; do
+    hv=hv-$i
+    echo "------ $hv dump ------"
+    as $hv ovs-vsctl show
+    as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int
+done
+
+# localnet ports
+for hv in 1 2; do
+    : > out-$hv.expected
+done
+
+test_packet() {
+    local hv=$1 inport=$2 dst=$3 src=$4 eth=$5
+    shift; shift; shift; shift; shift
+
+    : > expout
+    for lout in "$@"; do
+        if test $lout = unknown; then
+            # Expect the packet cloned to all localnet ports
+            for tag in `seq ${hv}1 ${hv}9`; do
+                echo "output(\"ln-$tag\");" >> expout
+            done
+        else
+            echo "output(\"$lout\");" >> expout
+        fi
+    done
+
+    # First try tracing the packet.
+    uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src &&
eth.type==0x$eth"
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all ls-${hv}0 "$uflow" | tee trace | sed
'1,/Minimal trace/d' | sort], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    local packet=$(echo $dst$src | sed 's/://g')${eth}
+    as hv-$hv ovs-appctl netdev-dummy/receive vif-$inport $packet
+
+    for lout in "$@"; do
+        if test $lout != unknown; then
+            # Expect the packet received by the peer VIF port
+            echo $packet >> vif-${lout#lp-}.expected
+        fi
+    done
+
+    # regardless, the packet is sent through the bridge
+    local packet=$(echo $dst$src | sed 's/://g')810000$(printf "%.2x\n"
${hv}1)${eth}
+    echo $packet >> out-$hv.expected
+}
+
+test_packet 1 1-1-8 f0:00:00:00:08:21 f0:00:00:00:08:11 1001 lp-2-1-8
+test_packet 2 2-2-8 f0:00:00:00:08:12 f0:00:00:00:08:22 1001 lp-1-2-8
+
+# unknown mac goes through localnet port
+test_packet 1 1-1-8 f0:00:00:00:08:e0 f0:00:00:00:08:11 1001 unknown
+test_packet 2 2-2-8 f0:00:00:00:08:e0 f0:00:00:00:08:22 1001 unknown
+
+# broadcast traffic goes to all peers, foreign and local
+test_packet 1 1-1-8 ff:ff:ff:ff:ff:ff f0:00:00:00:08:11 1001 $(for n in
`seq 11 19`; do echo ln-$n; done) lp-1-1-9 lp-2-1-8 lp-2-1-9
+
+# Now check the packets actually received against the ones expected.
+for hv in 1 2; do
+    for ls in 1 2; do
+        for peer in 8 9; do
+            port=$hv-$ls-$peer
+            # check that packets targeted to actual vifs arrived on the
other end
+            OVN_CHECK_PACKETS_CONTAIN([hv-$hv/vif-$port-tx.pcap],
[vif-$port.expected])
+        done
+    done
+    # check that all packets, whether to known or unknown mac addresses,
were sent to fabric
+    OVN_CHECK_PACKETS_CONTAIN([hv-$hv/br-phys${hv}1_n-${hv}1-tx.pcap],
[out-$hv.expected])
+done
+
+OVN_CLEANUP([hv-1],[hv-2])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- 2 HVs, 2 LS, switching between multiple localnet ports
with same tags])
+ovn_start
+
+# In this test case we create two switches with multiple localnet ports.
Only a
+# single localnet of the same tag is connected to fabric for each switch.
Two
+# hypervisors have VIFs that belong to these switches. The test validates
that
+# routing between these switches and hypervisors still works regardless
of the
+# number of (unplugged) localnet ports.
+
+# two switches, each connected to lots of networks
+for i in 1 2; do
+    ovn-nbctl ls-add ls-$i
+    for tag in `seq 10 20`; do
+        ln_port_name=ln-$i-$tag
+        ovn-nbctl lsp-add ls-$i $ln_port_name "" $tag
+        ovn-nbctl lsp-set-addresses $ln_port_name unknown
+        ovn-nbctl lsp-set-type $ln_port_name localnet
+        ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag
+    done
+done
+
+# multiple networks
+for tag in `seq 10 20`; do
+    net_add n-$tag
+done
+
+# two hypervisors, each connected to the same network
+for i in 1 2; do
+    sim_add hv-$i
+    as hv-$i
+    ovs-vsctl add-br br-phys
+    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys-20:br-phys
+    ovn_attach n-10 br-phys 192.168.0.$i
+done
+
+# two vif ports, one per switch
+for i in 1 2; do
+    as hv-$i
+    ovs-vsctl add-port br-int vif-$i -- \
+        set Interface vif-$i external-ids:iface-id=lp-$i \
+                              options:tx_pcap=hv-$i/vif-$i-tx.pcap \
+                              options:rxq_pcap=hv-$i/vif-$i-rx.pcap \
+                              ofport-request=$i
+
+    lsp_name=lp-$i
+    ovn-nbctl lsp-add ls-$i $lsp_name
+    ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:00:0$i
+    ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:0$i
+
+    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+done
+
+ovn-nbctl --wait=sb sync
+ovn-nbctl show
+ovn-sbctl dump-flows
+
+# vif ports
+for i in 1 2; do
+    : > vif-$i.expected
+done
+
+# localnet ports
+for i in 1 2; do
+    for tag in `seq 10 20`; do
+        : > $i-$tag.expected
+    done
+done
+
+test_packet() {
+    local inport=$1 outport=$2 dst=$3 src=$4 eth=$5 eout=$6 lout=$7
+
+    # Expect the packet cloned to all localnet ports
+    : > expout
+    for tag in `seq 10 20`; do
+        echo "output(\"ln-$inport-$tag\");" >> expout
+    done
+
+    # First try tracing the packet.
+    uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src &&
eth.type==0x$eth"
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all ls-$inport "$uflow" | tee trace | sed
'1,/Minimal trace/d'], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    local packet=$(echo $dst$src | sed 's/://g')${eth}
+    as hv-$1 ovs-appctl netdev-dummy/receive vif-$inport $packet
+
+    # Expect the packet received by the peer VIF port
+    echo $packet >> vif-$outport.expected
+
+    # Expect the packet to transfer through the common fabric network
+    local packet=$(echo $dst$src | sed 's/://g')810000$(printf "%.2x"
20)${eth}
+    echo $packet >> $1-10.expected
+}
+
+test_packet 1 2 f0:00:00:00:00:02 f0:00:00:00:00:01 1001 ln-1-10 ln-1-10
+test_packet 1 2 f0:00:00:00:00:02 f0:00:00:00:00:01 1002 ln-1-10 ln-1-10
+
+test_packet 2 1 f0:00:00:00:00:01 f0:00:00:00:00:02 1003 ln-2-10 ln-2-10
+test_packet 2 1 f0:00:00:00:00:01 f0:00:00:00:00:02 1004 ln-2-10 ln-2-10
+
+# Dump a bunch of info helpful for debugging if there's a failure.
+
+echo "------ OVN dump ------"
+ovn-nbctl show
+ovn-sbctl show
+
+for i in 1 2; do
+    hv=hv-$i
+    echo "------ $hv dump ------"
+    as $hv ovs-vsctl show
+    as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int
+done
+
+# Now check the packets actually received against the ones expected.
+for i in 1 2; do
+    OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$i/vif-$i-tx.pcap],
[vif-$i.expected])
+    OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$i/br-phys_n-10-tx.pcap],
[$i-10.expected])
+done
+
+OVN_CLEANUP([hv-1],[hv-2])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- 2 HVs, 1 LS, no switching between multiple localnet
ports with different tags])
+ovn_start
+
+# In this test case we create a single switch connected to two physical
+# networks via two localnet ports. Then we create two hypervisors, with 2
+# ports on each. The test validates no interconnectivity between VIF ports
+# located on chassis plugged to different physical networks.
+
+# create the single switch with two locanet ports
+ovn-nbctl ls-add ls1
+for tag in 10 20; do
+    ln_port_name=ln-$tag
+    ovn-nbctl lsp-add ls1 $ln_port_name "" $tag
+    ovn-nbctl lsp-set-addresses $ln_port_name unknown
+    ovn-nbctl lsp-set-type $ln_port_name localnet
+    ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag
+done
+
+# create fabric networks
+for tag in 10 20; do
+    net_add n-$tag
+done
+
+# create four chassis, each connected to one network, each with a single
VIF port
+for tag in 10 20; do
+    for i in 1 2; do
+        sim_add hv-$tag-$i
+        as hv-$tag-$i
+        ovs-vsctl add-br br-phys
+        ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys-$tag:br-phys
+        ovn_attach n-$tag br-phys 192.168.$i.$tag
+
+        ovs-vsctl add-port br-int vif-$tag-$i -- \
+            set Interface vif-$tag-$i external-ids:iface-id=lp-$tag-$i \
+
options:tx_pcap=hv-$tag-$i/vif-$tag-$i-tx.pcap \
+
options:rxq_pcap=hv-$tag-$i/vif-$tag-$i-rx.pcap \
+                                  ofport-request=$tag$i
+
+        lsp_name=lp-$tag-$i
+        ovn-nbctl lsp-add ls1 $lsp_name
+        ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:0$i:$tag
+        ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:0$i:$tag
+
+        OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+    done
+done
+ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows
+
+for tag in 10 20; do
+    for i in 1 2; do
+        : > $tag-$i.expected
+    done
+done
+
+vif_to_hv() {
+    echo hv-$1
+}
+
+test_packet() {
+    local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6
+
+    # First try tracing the packet.
+    uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src &&
eth.type==0x$eth"
+    echo "output(\"$lout\");" > expout
+    AT_CAPTURE_FILE([trace])
+    AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal
trace/d'], [0], [expout])
+
+    # Then actually send a packet, for an end-to-end test.
+    local packet=$(echo $dst$src | sed 's/://g')${eth}
+    hv=`vif_to_hv $inport`
+    vif=vif-$inport
+    as $hv ovs-appctl netdev-dummy/receive $vif $packet
+    if test $eth = 1002 -o $eth = 2002; then
+        echo $packet >> ${eout#lp-}.expected
+    fi
+}
+
+# different fabric networks -> should fail
+test_packet 10-1 f0:00:00:00:01:20 f0:00:00:00:01:10 1001 lp-20-1 lp-20-1
+test_packet 20-1 f0:00:00:00:01:10 f0:00:00:00:01:20 2001 lp-10-1 lp-10-1
+
+# same fabric networks -> should pass
+test_packet 10-1 f0:00:00:00:02:10 f0:00:00:00:01:10 1002 lp-10-2 lp-10-2
+test_packet 20-1 f0:00:00:00:02:20 f0:00:00:00:01:20 2002 lp-20-2 lp-20-2
+test_packet 10-2 f0:00:00:00:01:10 f0:00:00:00:02:10 1002 lp-10-1 lp-10-1
+test_packet 20-2 f0:00:00:00:01:20 f0:00:00:00:02:20 2002 lp-20-1 lp-20-1
+
+# Dump a bunch of info helpful for debugging if there's a failure.
+echo "------ OVN dump ------"
+ovn-nbctl show
+ovn-sbctl show
+
+for tag in 10 20; do
+    for i in 1 2; do
+        hv=hv-$tag-$i
+        echo "------ $hv dump ------"
+        as $hv ovs-vsctl show
+        as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int
+    done
+done
+
+# Now check the packets actually received against the ones expected.
+for tag in 10 20; do
+    for i in 1 2; do
+        echo "hv = $tag-$i"
+
OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$tag-$i/vif-$tag-$i-tx.pcap],
[$tag-$i.expected])
+    done
+done
+
+OVN_CLEANUP([hv-10-1],[hv-10-2],[hv-20-1],[hv-20-2])
+
+AT_CLEANUP
+
  AT_SETUP([ovn -- vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS])
  AT_KEYWORDS([vtep])
  ovn_start
--
2.26.2

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev




_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to