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 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; +} > + > 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
