On 5/11/20 7:00 PM, Ihar Hrachyshka 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]>
> ---
> controller/binding.c | 16 ++
> controller/patch.c | 24 +-
> northd/ovn-northd.c | 65 ++++--
> ovn-architecture.7.xml | 50 ++--
> ovn-nb.xml | 23 +-
> ovn-sb.xml | 21 +-
> tests/ovn.at | 504 +++++++++++++++++++++++++++++++++++++++++
> 7 files changed, 646 insertions(+), 57 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index 20a89d07d..c88c4ece8 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -692,12 +692,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);
> +}
> +
> 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..52255cc3a 100644
> --- a/controller/patch.c
> +++ b/controller/patch.c
> @@ -198,9 +198,9 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
> continue;
> }
>
> - const char *patch_port_id;
> + bool is_localnet = false;
> if (!strcmp(binding->type, "localnet")) {
> - patch_port_id = "ovn-localnet-port";
To avoid the second "if (is_localnet)" below I'd keep setting the
patch_port_id here.
> + is_localnet = true;
> } else if (!strcmp(binding->type, "l2gateway")) {
> if (!binding->chassis
> || strcmp(chassis->name, binding->chassis->name)) {
> @@ -208,7 +208,6 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
> * so we should not create any patch ports for it. */
> continue;
> }
> - patch_port_id = "ovn-l2gateway-port";
Same here.
> } else {
> /* not a localnet or L2 gateway port. */
> continue;
> @@ -224,12 +223,25 @@ 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;
> }
>
> + const char *patch_port_id;
> + if (is_localnet) {
> + patch_port_id = "ovn-localnet-port";
> + } else {
> + patch_port_id = "ovn-l2gateway-port";
> + }
> +
With above comments, this block can go away.
> char *name1 = patch_port_name(br_int->name, binding->logical_port);
> char *name2 = patch_port_name(binding->logical_port, br_int->name);
> create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port,
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 860f0d596..89103905c 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);
>
> @@ -2019,6 +2022,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;
> for (size_t i = 0; i < od->nbs->n_ports; i++) {
> const struct nbrec_logical_switch_port *nbsp
> = od->nbs->ports[i];
> @@ -2053,7 +2057,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
> @@ -3012,7 +3021,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;
> }
>
> @@ -4705,9 +4714,10 @@ build_pre_acls(struct ovn_datapath *od, struct hmap
> *lflows)
> struct ovn_port *op = od->router_ports[i];
> build_pre_acl_flows_for_nbsp(od, op->nbsp, op->json_key, lflows);
> }
> - if (od->localnet_port) {
> - build_pre_acl_flows_for_nbsp(od, od->localnet_port->nbsp,
> - od->localnet_port->json_key,
> lflows);
> + for (size_t i = 0; i < od->n_localnet_ports; i++) {
> + build_pre_acl_flows_for_nbsp(od, od->localnet_ports[i]->nbsp,
> + od->localnet_ports[i]->json_key,
> + lflows);
> }
>
> /* Ingress and Egress Pre-ACL Table (Priority 110).
> @@ -5975,9 +5985,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,
> @@ -6572,25 +6582,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);
> }
> }
>
> @@ -6646,8 +6662,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;
> }
>
> @@ -6655,8 +6670,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);
> @@ -6874,7 +6891,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
> @@ -8180,7 +8197,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 533ae716d..5b9ed1a1d 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
> @@ -1895,13 +1919,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
> @@ -1916,11 +1940,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.
> @@ -1935,13 +1959,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
> @@ -1957,7 +1981,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>
>
> @@ -1967,7 +1991,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
> @@ -1978,12 +2002,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 af15c550a..181939da1 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
Nit: s/networks.In this/networks. In/
Regards,
Dumitru
> + 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..42cbe11a7 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 e6febd4c2..54810314e 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])
> @@ -2475,6 +2479,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
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev