This patch implements one approach to using ovn-controller to implement a software l2 gateway between logical and physical networks.
A new logical port type called "l2gateway" is introduced here. It is very close to how localnet ports work, with the following exception: - A localnet port makes OVN use the physical network as the transport between hypervisors instead of tunnels. An l2gateway port still uses tunnels between all hypervisors, and packets only go to/from the specified physical network as needed via the chassis the l2gateway port is bound to. - An l2gateway port also gets bound to a chassis while a localnet port does not. This binding is not done by ovn-controller. It is left as an administrative function. In the case of OpenStack, the Neutron plugin will do this. Signed-off-by: Russell Bryant <russ...@ovn.org> Acked-by: Ryan Moats <rmo...@us.ibm.com> Acked-by: Ben Pfaff <b...@ovn.org> --- ovn/controller/binding.c | 9 ++ ovn/controller/ovn-controller.8.xml | 31 ++++++- ovn/controller/ovn-controller.c | 5 +- ovn/controller/patch.c | 37 +++++--- ovn/controller/patch.h | 3 +- ovn/controller/physical.c | 22 +++-- ovn/ovn-nb.xml | 21 ++++- ovn/ovn-sb.xml | 78 +++++++++++++++-- tests/ovn.at | 164 ++++++++++++++++++++++++++++++++++++ 9 files changed, 340 insertions(+), 30 deletions(-) v3->v4: - dusted off, rebased, resolved conflicts - Improved documentation in ovn-sb.xml to clarify the different meanings of the "chassis" column of "Port_Bindings" based on the different port types, as suggested by Ben. - Change port type from "gateway" to "l2gateway" to help avoid confusion with the l3 gateway work. - added ACKs from Ryan and Ben, but re-submitted so Justin can review, per his request. diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c index a0d8b96..758642c 100644 --- a/ovn/controller/binding.c +++ b/ovn/controller/binding.c @@ -200,6 +200,15 @@ binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, } sbrec_port_binding_set_chassis(binding_rec, chassis_rec); } + } else if (!strcmp(binding_rec->type, "l2gateway") + && binding_rec->chassis == chassis_rec) { + /* A locally bound gateway port. + * + * ovn-controller does not bind gateway ports itself. + * Choosing a chassis for a gateway port is left + * up to an entity external to OVN. */ + sset_add(&all_lports, binding_rec->logical_port); + add_local_datapath(local_datapaths, binding_rec); } else if (chassis_rec && binding_rec->chassis == chassis_rec) { if (ctx->ovnsb_idl_txn) { VLOG_INFO("Releasing lport %s from this chassis.", diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml index 1ee3a6e..228a8cd 100644 --- a/ovn/controller/ovn-controller.8.xml +++ b/ovn/controller/ovn-controller.8.xml @@ -184,10 +184,10 @@ The presence of this key identifies a patch port as one created by <code>ovn-controller</code> to connect the integration bridge and another bridge to implement a <code>localnet</code> logical port. - Its value is the name of the logical port with type=localnet that - the port implements. - See <code>external_ids:ovn-bridge-mappings</code>, above, - for more information. + Its value is the name of the logical port with <code>type</code> + set to <code>localnet</code> that the port implements. See + <code>external_ids:ovn-bridge-mappings</code>, above, for more + information. </p> <p> @@ -199,6 +199,29 @@ </dd> <dt> + <code>external-ids:ovn-gateway-port</code> in the <code>Port</code> + table + </dt> + <dd> + <p> + The presence of this key identifies a patch port as one created by + <code>ovn-controller</code> to connect the integration bridge and + another bridge to implement a <code>gateway</code> logical port. + Its value is the name of the logical port with <code>type</code> + set to <code>gateway</code> that the port implements. See + <code>external_ids:ovn-bridge-mappings</code>, above, for more + information. + </p> + + <p> + Each <code>gateway</code> logical port is implemented as a pair + of patch ports, one in the integration bridge, one in a different + bridge, with the same <code>external-ids:ovn-gateway-port</code> + value. + </p> + </dd> + + <dt> <code>external-ids:ovn-logical-patch-port</code> in the <code>Port</code> table </dt> diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 511b184..0c2bcdd 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -365,7 +365,10 @@ main(int argc, char *argv[]) } if (br_int) { - patch_run(&ctx, br_int, &local_datapaths, &patched_datapaths); + if (chassis_id) { + patch_run(&ctx, br_int, &local_datapaths, &patched_datapaths, + chassis_id); + } struct lport_index lports; struct mcgroup_index mcgroups; diff --git a/ovn/controller/patch.c b/ovn/controller/patch.c index 4808146..0ec5f72 100644 --- a/ovn/controller/patch.c +++ b/ovn/controller/patch.c @@ -134,7 +134,8 @@ static void add_bridge_mappings(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, struct shash *existing_ports, - struct hmap *local_datapaths) + struct hmap *local_datapaths, + const char *chassis_id) { /* Get ovn-bridge-mappings. */ const char *mappings_cfg = ""; @@ -175,6 +176,7 @@ add_bridge_mappings(struct controller_ctx *ctx, const struct sbrec_port_binding *binding; SBREC_PORT_BINDING_FOR_EACH (binding, ctx->ovnsb_idl) { + const char *patch_port_id; if (!strcmp(binding->type, "localnet")) { struct local_datapath *ld = get_local_datapath(local_datapaths, @@ -195,31 +197,40 @@ add_bridge_mappings(struct controller_ctx *ctx, continue; } ld->localnet_port = binding; + patch_port_id = "ovn-localnet-port"; + } else if (!strcmp(binding->type, "l2gateway")) { + if (!binding->chassis || strcmp(chassis_id, binding->chassis->name)) { + /* This gateway port is not bound to this chassis, so we should + * not create any patch ports for it. */ + continue; + } + patch_port_id = "ovn-gateway-port"; } else { - /* Not a binding for a localnet port. */ + /* not a localnet or gateway port. */ continue; } const char *network = smap_get(&binding->options, "network_name"); if (!network) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "localnet port '%s' has no network name.", - binding->logical_port); + VLOG_ERR_RL(&rl, "%s port '%s' has no network name.", + binding->type, binding->logical_port); continue; } 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 localnet port '%s' " - "with network name '%s'", binding->logical_port, network); + VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' " + "with network name '%s'", + binding->type, binding->logical_port, network); continue; } 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(ctx, "ovn-localnet-port", binding->logical_port, + create_patch_port(ctx, patch_port_id, binding->logical_port, br_int, name1, br_ln, name2, existing_ports); - create_patch_port(ctx, "ovn-localnet-port", binding->logical_port, + create_patch_port(ctx, patch_port_id, binding->logical_port, br_ln, name2, br_int, name1, existing_ports); free(name1); free(name2); @@ -293,7 +304,8 @@ add_logical_patch_ports(struct controller_ctx *ctx, void patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, - struct hmap *local_datapaths, struct hmap *patched_datapaths) + struct hmap *local_datapaths, struct hmap *patched_datapaths, + const char *chassis_id) { if (!ctx->ovs_idl_txn) { return; @@ -303,8 +315,9 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, struct shash existing_ports = SHASH_INITIALIZER(&existing_ports); const struct ovsrec_port *port; OVSREC_PORT_FOR_EACH (port, ctx->ovs_idl) { - if (smap_get(&port->external_ids, "ovn-localnet-port") || - smap_get(&port->external_ids, "ovn-logical-patch-port")) { + if (smap_get(&port->external_ids, "ovn-localnet-port") + || smap_get(&port->external_ids, "ovn-gateway-port") + || smap_get(&port->external_ids, "ovn-logical-patch-port")) { shash_add(&existing_ports, port->name, port); } } @@ -312,7 +325,7 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, /* Create in the database any patch ports that should exist. Remove from * 'existing_ports' any patch ports that do exist in the database and * should be there. */ - add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths); + add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths, chassis_id); add_logical_patch_ports(ctx, br_int, &existing_ports, patched_datapaths); /* Now 'existing_ports' only still contains patch ports that exist in the diff --git a/ovn/controller/patch.h b/ovn/controller/patch.h index d5d842e..5ff0a2b 100644 --- a/ovn/controller/patch.h +++ b/ovn/controller/patch.h @@ -27,6 +27,7 @@ struct hmap; struct ovsrec_bridge; void patch_run(struct controller_ctx *, const struct ovsrec_bridge *br_int, - struct hmap *local_datapaths, struct hmap *patched_datapaths); + struct hmap *local_datapaths, struct hmap *patched_datapaths, + const char *chassis_id); #endif /* ovn/patch.h */ diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c index 576c695..60453d6 100644 --- a/ovn/controller/physical.c +++ b/ovn/controller/physical.c @@ -168,6 +168,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, const char *localnet = smap_get(&port_rec->external_ids, "ovn-localnet-port"); + const char *gateway = smap_get(&port_rec->external_ids, + "ovn-gateway-port"); const char *logpatch = smap_get(&port_rec->external_ids, "ovn-logical-patch-port"); @@ -190,6 +192,10 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, /* localnet patch ports can be handled just like VIFs. */ simap_put(&localvif_to_ofport, localnet, ofport); break; + } else if (is_patch && gateway) { + /* gateway patch ports can be handled just like VIFs. */ + simap_put(&localvif_to_ofport, gateway, ofport); + break; } else if (is_patch && logpatch) { /* Logical patch ports can be handled just like VIFs. */ simap_put(&localvif_to_ofport, logpatch, ofport); @@ -268,13 +274,13 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, * OpenFlow port for the VIF. 'tun' will be NULL. * * The same logic handles logical patch ports, as well as - * localnet patch ports. + * localnet and gateway patch ports. * * For a container nested inside a VM and accessible via a VLAN, * 'tag' is the VLAN ID; otherwise 'tag' is 0. * - * For a localnet patch port, if a VLAN ID was configured, 'tag' - * is set to that VLAN ID; otherwise 'tag' is 0. + * For a localnet or gateway patch port, if a VLAN ID was + * configured, 'tag' is set to that VLAN ID; otherwise 'tag' is 0. * * - If the port is on a remote chassis, the OpenFlow port for a * tunnel to the VIF's remote chassis. 'tun' identifies that @@ -296,7 +302,9 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, } else { ofport = u16_to_ofp(simap_get(&localvif_to_ofport, binding->logical_port)); - if (!strcmp(binding->type, "localnet") && ofport && binding->tag) { + if ((!strcmp(binding->type, "localnet") + || !strcmp(binding->type, "l2gateway")) + && ofport && binding->tag) { tag = *binding->tag; } } @@ -354,7 +362,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, /* Match a VLAN tag and strip it, including stripping priority tags * (e.g. VLAN ID 0). In the latter case we'll add a second flow * for frames that lack any 802.1Q header later. */ - if (tag || !strcmp(binding->type, "localnet")) { + if (tag || !strcmp(binding->type, "localnet") + || !strcmp(binding->type, "l2gateway")) { match_set_dl_vlan(&match, htons(tag)); ofpact_put_STRIP_VLAN(&ofpacts); } @@ -376,7 +385,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, tag ? 150 : 100, &match, &ofpacts); - if (!tag && !strcmp(binding->type, "localnet")) { + if (!tag && (!strcmp(binding->type, "localnet") + || !strcmp(binding->type, "l2gateway"))) { /* Add a second flow for frames that lack any 802.1Q * header. For these, drop the OFPACT_STRIP_VLAN * action. */ diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index c01455d..c9da74f 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -134,6 +134,11 @@ to model direct connectivity to an existing network. </dd> + <dt><code>l2gateway</code></dt> + <dd> + A connection to a physical network. + </dd> + <dt><code>vtep</code></dt> <dd> A port to a logical switch on a VTEP gateway. @@ -182,6 +187,20 @@ </column> </group> + <group title="Options for l2gateway ports"> + <p> + These options apply when <ref column="type"/> is + <code>l2gateway</code>. + </p> + + <column name="options" key="network_name"> + Required. The name of the network to which the <code>l2gateway</code> + port is connected. The gateway, via <code>ovn-controller</code>, + uses its local configuration to determine exactly how to connect to + this network. + </column> + </group> + <group title="Options for vtep ports"> <p> These options apply when <ref column="type"/> is <code>vtep</code>. @@ -637,7 +656,7 @@ column is set to <code>false</code>, the router is disabled. A disabled router has all ingress and egress traffic dropped. </column> - + <group title="Common Columns"> <column name="external_ids"> See <em>External IDs</em> at the beginning of this document. diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index efd2f9a..c178c9c 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1247,10 +1247,39 @@ tcp.flags = RST; </column> <column name="chassis"> - The physical location of the logical port. To successfully identify a - chassis, this column must be a <ref table="Chassis"/> record. This is - populated by - <code>ovn-controller</code>/<code>ovn-controller-vtep</code>. + The meaning of this column depends on the value of the <ref column="type"/> + column. This is the meaning for each <ref column="type"/> + + <dl> + <dt>(empty string)</dt> + <dd> + The physical location of the logical port. To successfully identify a + chassis, this column must be a <ref table="Chassis"/> record. This is + populated by <code>ovn-controller</code>. + </dd> + + <dt>vtep</dt> + <dd> + The physical location of the hardware_vtep gateway. To successfully + identify a chassis, this column must be a <ref table="Chassis"/> record. + This is populated by <code>ovn-controller-vtep</code>. + </dd> + + <dt>localnet</dt> + <dd> + Always empty. A localnet port is realized on every chassis that has + connectivity to the corresponding physical network. + </dd> + + <dt>l2gateway</dt> + <dd> + The physical location of this L2 gateway. To successfully identify a + chassis, this column must be a <ref table="Chassis"/> record. + This is populated by an entity external to OVN, either manually or by + a CMS. + </dd> + </dl> + </column> <column name="tunnel_key"> @@ -1306,6 +1335,14 @@ tcp.flags = RST; to model direct connectivity to an existing network. </dd> + <dt><code>l2gateway</code></dt> + <dd> + A connection to a physical network. The chassis this + <ref table="Port_Binding"/> is bound to will serve as + an L2 gateway to the network named by + <ref column="options" table="Port_Binding"/>:<code>network_name</code>. + </dd> + <dt><code>vtep</code></dt> <dd> A port to a logical switch on a VTEP gateway chassis. In order to @@ -1368,6 +1405,36 @@ tcp.flags = RST; </column> </group> + <group title="Gateway Options"> + <p> + These options apply to logical ports with <ref column="type"/> of + <code>l2gateway</code>. + </p> + + <column name="options" key="network_name"> + Required. <code>ovn-controller</code> uses the configuration entry + <code>ovn-bridge-mappings</code> to determine how to connect to this + network. <code>ovn-bridge-mappings</code> is a list of network names + mapped to a local OVS bridge that provides access to that network. An + example of configuring <code>ovn-bridge-mappings</code> would be: + + <pre>$ ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1</pre> + + <p> + When a logical switch has a <code>l2gateway</code> port attached, + the chassis that the <code>l2gateway</code> port is bound to + must have a bridge mapping configured to reach the network + identified by <code>network_name</code>. + </p> + </column> + + <column name="tag"> + If set, indicates that the gateway is connected to a specific + VLAN on the physical network. The VLAN ID is used to match + incoming traffic and is also added to outgoing traffic. + </column> + </group> + <group title="VTEP Options"> <p> These options apply to logical ports with <ref column="type"/> of @@ -1425,7 +1492,8 @@ tcp.flags = RST; <p> This column is used for a different purpose when <ref column="type"/> - is <code>localnet</code> (see <code>Localnet Options</code>, above). + is <code>localnet</code> (see <code>Localnet Options</code>, above) + or <code>l2gateway</code> (see <code>Gateway Options</code>, above). </p> </column> </group> diff --git a/tests/ovn.at b/tests/ovn.at index e6ac1d7..6d11ac3 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1229,6 +1229,170 @@ for sim in hv1 hv2 hv3 vtep main; do done AT_CLEANUP +# Similar test to "hardware GW" +AT_SETUP([ovn -- 3 HVs, 1 VIFs/HV, 1 software GW, 1 LS]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# Configure the Northbound database +ovn-nbctl lswitch-add lsw0 + +ovn-nbctl lport-add lsw0 lp1 +ovn-nbctl lport-set-addresses lp1 f0:00:00:00:00:01 + +ovn-nbctl lport-add lsw0 lp2 +ovn-nbctl lport-set-addresses lp2 f0:00:00:00:00:02 + +ovn-nbctl lport-add lsw0 lp-gw +ovn-nbctl lport-set-type lp-gw l2gateway +ovn-nbctl lport-set-options lp-gw network_name=physnet1 +ovn-nbctl lport-set-addresses lp-gw unknown + +net_add n1 # Network to connect hv1, hv2, and gw +net_add n2 # Network to connect gw and hv3 + +# Create hypervisor hv1 connected to n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 + +# Create hypervisor hv2 connected to n1 +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1 + +# Create hypervisor hv_gw connected to n1 and n2 +# connect br-phys bridge to n1; connect hv-gw bridge to n2 +sim_add hv_gw +as hv_gw +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.3 +ovs-vsctl add-br br-phys2 +net_attach n2 br-phys2 +ovs-vsctl set open . external_ids:ovn-bridge-mappings="physnet1:br-phys2" + +# Bind our gateway port to the hv_gw chassis +ovn-sbctl lport-bind lp-gw hv_gw + +# Add hv3 on the other side of the GW +sim_add hv3 +as hv3 +ovs-vsctl add-br br-phys +net_attach n2 br-phys +ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1 + + +# Pre-populate the hypervisors' ARP tables so that we don't lose any +# packets for ARP resolution (native tunneling doesn't queue packets +# for ARP resolution). +ovn_populate_arp + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +# test_packet INPORT DST SRC ETHTYPE OUTPORT... +# +# This shell function causes a packet to be received on INPORT. The packet's +# content has Ethernet destination DST and source SRC (each exactly 12 hex +# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or +# more) list the VIFs on which the packet should be received. INPORT and the +# OUTPORTs are specified as lport numbers, e.g. 1 for vif1. +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} +for i in 1 2 3; do + : > $i.expected +done +test_packet() { + local inport=$1 packet=$2$3$4; shift; shift; shift; shift + #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'` + hv=hv$inport + vif=vif$inport + as $hv ovs-appctl netdev-dummy/receive $vif $packet + for outport; do + echo $packet | trim_zeros >> $outport.expected + done +} + +# Send packets between all pairs of source and destination ports: +# +# 1. Unicast packets are delivered to exactly one lport (except that packets +# destined to their input ports are dropped). +# +# 2. Broadcast and multicast are delivered to all lports except the input port. +# +# 3. The lswitch delivers packets with an unknown destination to lports with +# "unknown" among their MAC addresses (and port security disabled). +for s in 1 2 3 ; do + bcast= + unknown= + for d in 1 2 3 ; do + if test $d != $s; then unicast=$d; else unicast=; fi + test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast #1 + + # The vtep (vif3) is the only one configured for "unknown" + if test $d != $s && test $d = 3; then + unknown="$unknown $d" + fi + bcast="$bcast $unicast" + done + + test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast #2 + test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast #3 + test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown #4 +done + +# Allow some time for packet forwarding. +# XXX This can be improved. +sleep 3 + +echo "------ ovn-nbctl show ------" +ovn-nbctl show +echo "------ ovn-sbctl show ------" +ovn-sbctl show + +echo "------ hv1 ------" +as hv1 ovs-vsctl show +echo "------ hv1 br-int ------" +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int +echo "------ hv1 br-phys ------" +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-phys + +echo "------ hv2 ------" +as hv2 ovs-vsctl show +echo "------ hv2 br-int ------" +as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int +echo "------ hv2 br-phys ------" +as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-phys + +echo "------ hv_gw ------" +as hv_gw ovs-vsctl show +echo "------ hv_gw br-phys ------" +as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys +echo "------ hv_gw br-phys2 ------" +as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys2 + +echo "------ hv3 ------" +as hv3 ovs-vsctl show +echo "------ hv3 br-phys ------" +as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-phys + +# Now check the packets actually received against the ones expected. +for i in 1 2 3; do + file=hv$i/vif$i-tx.pcap + echo $file + $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > $i.packets + sort $i.expected > expout + AT_CHECK([sort $i.packets], [0], [expout]) + echo +done +AT_CLEANUP + # 3 hypervisors, 3 logical switches with 3 logical ports each, 1 logical router AT_SETUP([ovn -- 3 HVs, 3 LS, 3 lports/LS, 1 LR]) AT_SKIP_IF([test $HAVE_PYTHON = no]) -- 2.5.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev