@@ -25,6 +25,7 @@ Post-v2.11.0
- OVN:
* Select IPAM mac_prefix in a random manner if not provided by the user
* Added the HA chassis group support and deprecated Gateway chassis.
+ * Added 'external' logical port support.
v2.11.0 - 19 Feb 2019
---------------------
@@ -502,6 +502,18 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn,
* for them. */
sset_add(local_lports, binding_rec->logical_port);
our_chassis = false;
+ } else if (!strcmp(binding_rec->type, "external")) {
+ if (ha_chassis_group_contains(binding_rec->ha_chassis_group,
+ chassis_rec)) {
+ our_chassis = ha_chassis_group_is_active(
+ binding_rec->ha_chassis_group,
+ active_tunnels, chassis_rec);
+
+ add_local_datapath(sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_datapath,
+ sbrec_port_binding_by_name,
+ binding_rec->datapath, false, local_datapaths);
+ }
}
if (our_chassis
@@ -145,6 +145,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
* ports that have a Gateway_Chassis that point's to our own
* chassis */
sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect");
+ sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external");
if (chassis) {
/* This should be mostly redundant with the other clauses for port
* bindings, but it allows us to catch any ports that are assigned to
@@ -319,6 +319,7 @@ static const char *OVN_NB_LSP_TYPES[] = {
"localport",
"router",
"vtep",
+ "external",
};
bool
@@ -626,7 +626,8 @@ nd_na_router {
<p>
This table adds the DHCPv4 options to a DHCPv4 packet from the
logical ports configured with IPv4 address(es) and DHCPv4 options,
- and similarly for DHCPv6 options.
+ and similarly for DHCPv6 options. This table also adds flows for the
+ logical ports of type <code>external</code>.
</p>
<ul>
@@ -827,7 +828,39 @@ output;
</li>
</ul>
- <h3>Ingress Table 16 Destination Lookup</h3>
+ <h3>Ingress table 16 External ports</h3>
+
+ <p>
+ Traffic from the <code>external</code> logical ports enter the ingress
+ datapath pipeline via the <code>localnet</code> port. This table adds the
+ below logical flows to handle the traffic from these ports.
+ </p>
+
+ <ul>
+ <li>
+ <p>
+ A priority-100 flow is added for each <code>external</code> logical
+ port which doesn't reside on a chassis to drop the ARP/IPv6 NS
+ request to the router IP(s) (of the logical switch) which matches
+ on the <code>inport</code> of the <code>external</code> logical port
+ and the valid <code>eth.src</code> address(es) of the
+ <code>external</code> logical port.
+ </p>
+
+ <p>
+ This flow guarantees that the ARP/NS request to the router IP
+ address from the external ports is responded by only the chassis
+ which has claimed these external ports. All the other chassis,
+ drops these packets.
+ </p>
+ </li>
+
+ <li>
+ A priority-0 flow that matches all packets to advances to table 17.
+ </li>
+ </ul>
+
+ <h3>Ingress Table 17 Destination Lookup</h3>
<p>
This table implements switching behavior. It contains these logical
@@ -119,7 +119,8 @@ enum ovn_stage {
PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 14, "ls_in_dns_lookup") \
PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 15, "ls_in_dns_response") \
- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 16, "ls_in_l2_lkup") \
+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 16, "ls_in_external_port") \
+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 17, "ls_in_l2_lkup") \
\
/* Logical switch egress stages. */ \
PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
@@ -2332,6 +2333,18 @@ ovn_port_update_sbrec(struct northd_context *ctx,
}
sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
+
+ if (!strcmp(op->nbsp->type, "external")) {
+ if (op->nbsp->ha_chassis_group) {
+ sync_ha_chassis_group_for_sbpb(
+ ctx, op->nbsp->ha_chassis_group,
+ sbrec_chassis_by_name, op->sb);
+ sset_add(active_ha_chassis_grps,
+ op->nbsp->ha_chassis_group->name);
+ } else {
+ sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
+ }
+ }
} else {
const char *chassis = NULL;
if (op->peer && op->peer->od && op->peer->od->nbr) {
@@ -3029,6 +3042,12 @@ lsp_is_up(const struct nbrec_logical_switch_port *lsp)
return !lsp->up || *lsp->up;
}
+static bool
+lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
+{
+ return !strcmp(nbsp->type, "external");
+}
+
static bool
build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
struct ds *options_action, struct ds *response_action,
@@ -3926,6 +3945,10 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
* logical ports of the datapath if the CMS has configured DHCPv4 options.
* */
for (size_t i = 0; i < od->nbs->n_ports; i++) {
+ if (lsp_is_external(od->nbs->ports[i])) {
+ continue;
+ }
+
if (od->nbs->ports[i]->dhcpv4_options) {
const char *server_id = smap_get(
&od->nbs->ports[i]->dhcpv4_options->options, "server_id");
@@ -4312,6 +4335,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
+ if (lsp_is_external(op->nbsp)) {
+ continue;
+ }
+
ds_clear(&match);
ds_clear(&actions);
ds_put_format(&match, "inport == %s", op->json_key);
@@ -4378,6 +4405,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
+ if (lsp_is_external(op->nbsp)) {
+ continue;
+ }
+
for (size_t i = 0; i < op->n_lsp_addrs; i++) {
for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
ds_clear(&match);
@@ -4486,6 +4517,14 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
+ bool is_external = lsp_is_external(op->nbsp);
+ if (is_external && (!op->od->localnet_port ||
+ !op->nbsp->ha_chassis_group)) {
+ /* If it's an external port and there is no localnet port
+ * 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++) {
for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
struct ds options_action = DS_EMPTY_INITIALIZER;
@@ -4498,9 +4537,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ds_put_format(
&match, "inport == %s && eth.src == %s && "
"ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
- "udp.src == 68 && udp.dst == 67", op->json_key,
+ "udp.src == 68 && udp.dst == 67",
+ is_external ? op->od->localnet_port->json_key :
+ op->json_key,
op->lsp_addrs[i].ea_s);
+ if (is_external) {
+ ds_put_format(&match, " && is_chassis_resident(%s)",
+ op->json_key);
+ }
+
ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
100, ds_cstr(&match),
ds_cstr(&options_action));
@@ -4515,9 +4561,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
*/
ds_put_format(
&match, "inport == %s && eth.src == %s && "
- "%s && udp.src == 68 && udp.dst == 67", op->json_key,
+ "%s && udp.src == 68 && udp.dst == 67",
+ is_external ? op->od->localnet_port->json_key :
+ op->json_key,
op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match));
+ if (is_external) {
+ ds_put_format(&match, " && is_chassis_resident(%s)",
+ op->json_key);
+ }
+
ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS,
100, ds_cstr(&match),
ds_cstr(&options_action));
@@ -4528,8 +4581,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ds_put_format(
&match, "inport == %s && eth.src == %s && "
"ip4 && udp.src == 68 && udp.dst == 67"
- " && "REGBIT_DHCP_OPTS_RESULT, op->json_key,
+ " && "REGBIT_DHCP_OPTS_RESULT,
+ is_external ? op->od->localnet_port->json_key :
+ op->json_key,
op->lsp_addrs[i].ea_s);
+
+ if (is_external) {
+ ds_put_format(&match, " && is_chassis_resident(%s)",
+ op->json_key);
+ }
+
ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE,
100, ds_cstr(&match),
ds_cstr(&response_action));
@@ -4550,9 +4611,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ds_put_format(
&match, "inport == %s && eth.src == %s"
" && ip6.dst == ff02::1:2 && udp.src == 546 &&"
- " udp.dst == 547", op->json_key,
+ " udp.dst == 547",
+ is_external ? op->od->localnet_port->json_key :
+ op->json_key,
op->lsp_addrs[i].ea_s);
+ if (is_external) {
+ ds_put_format(&match, " && is_chassis_resident(%s)",
+ op->json_key);
+ }
+
ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100,
ds_cstr(&match), ds_cstr(&options_action));
@@ -4604,7 +4672,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
/* Ingress table 12 and 13: DHCP options and response, by default goto
* next. (priority 0).
* Ingress table 14 and 15: DNS lookup and response, by default goto next.
- * (priority 0).*/
+ * (priority 0).
+ * Ingress table 16 - External port handling, by default goto next.
+ * (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
@@ -4615,9 +4685,60 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
}
- /* Ingress table 16: Destination lookup, broadcast and multicast handling
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (!op->nbsp || !lsp_is_external(op->nbsp) ||
+ !op->od->localnet_port) {
+ continue;
+ }
+
+ /* Table 16: External port. Drop ARP request for router ips from
+ * external ports on chassis not binding those ports.
+ * This makes the router pipeline to be run only on the chassis
+ * binding the external ports. */
+
+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+ for (size_t j = 0; j < op->od->n_router_ports; j++) {
+ struct ovn_port *rp = op->od->router_ports[j];
+ for (size_t k = 0; k < rp->n_lsp_addrs; k++) {
+ for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs;
+ l++) {
+ ds_clear(&match);
+ ds_put_format(
+ &match, "inport == %s && eth.src == %s"
+ " && !is_chassis_resident(%s)"
+ " && arp.tpa == %s && arp.op == 1",
+ op->od->localnet_port->json_key,
+ op->lsp_addrs[i].ea_s, op->json_key,
+ rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
+ ovn_lflow_add(lflows, op->od,
+ S_SWITCH_IN_EXTERNAL_PORT, 100,
+ ds_cstr(&match), "drop;");
+ }
+ for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs;
+ l++) {
+ ds_clear(&match);
+ ds_put_format(
+ &match, "inport == %s && eth.src == %s"
+ " && !is_chassis_resident(%s)"
+ " && nd_ns && ip6.dst == {%s, %s} && "
+ "nd.target == %s",
+ op->od->localnet_port->json_key,
+ op->lsp_addrs[i].ea_s, op->json_key,
+ rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
+ rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
+ rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
+ ovn_lflow_add(lflows, op->od,
+ S_SWITCH_IN_EXTERNAL_PORT, 100,
+ ds_cstr(&match), "drop;");
+ }
+ }
+ }
+ }
+ }
+ /* Ingress table 17: Destination lookup, broadcast and multicast handling
* (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
@@ -4637,9 +4758,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
"outport = \""MC_FLOOD"\"; output;");
}
- /* Ingress table 16: Destination lookup, unicast handling (priority 50), */
+ /* Ingress table 17: Destination lookup, unicast handling (priority 50), */
HMAP_FOR_EACH (op, key_node, ports) {
- if (!op->nbsp) {
+ if (!op->nbsp || lsp_is_external(op->nbsp)) {
continue;
}
@@ -4756,7 +4877,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 16: Destination lookup for unknown MACs (priority 0). */
+ /* Ingress table 17: Destination lookup for unknown MACs (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
continue;
@@ -4791,7 +4912,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
* Priority 150 rules drop packets to disabled logical ports, so that they
* don't even receive multicast or broadcast packets. */
HMAP_FOR_EACH (op, key_node, ports) {
- if (!op->nbsp) {
+ if (!op->nbsp || lsp_is_external(op->nbsp)) {
continue;
}
@@ -1678,6 +1678,77 @@
</li>
</ol>
+ <h2>Native OVN services for external logical ports</h2>
+
+ <p>
+ To support OVN native services (like DHCP/IPv6 RA/DNS lookup) to the
+ cloud resources which are external, OVN supports <code>external</code>
+ logical ports.
+ </p>
+
+ <p>
+ Below are some of the use cases where <code>external</code> ports can be
+ used.
+ </p>
+
+ <ul>
+ <li>
+ VMs connected to SR-IOV nics - Traffic from these VMs by passes the
+ kernel stack and local <code>ovn-controller</code> do not bind these
+ ports and cannot serve the native services.
+ </li>
+ <li>
+ When CMS supports provisioning baremetal servers.
+ </li>
+ </ul>
+
+ <p>
+ OVN will provide the native services if CMS has done the below
+ configuration in the <dfn>OVN Northbound Database</dfn>.
+ </p>
+
+ <ul>
+ <li>
+ A row is created in <code>Logical_Switch_Port</code>, configuring the
+ <ref column="addresses" table="Logical_Switch_Port" db="OVN_NB"/> column
+ and setting the <ref column="type" table="Logical_Switch_Port"
+ db="OVN_NB"/> to <code>external</code>.
+ </li>
+
+ <li>
+ <ref column="ha_chassis_group" table="Logical_Switch_Port"
+ db="OVN_NB"/> column is configured.
+ </li>
+
+ <li>
+ The HA chassis which belongs to the HA chassis group has the
+ <code>ovn-bridge-mappings</code> configured and has proper L2
+ connectivity so that it can receive the DHCP and other related request
+ packets from these external resources.
+ </li>
+
+ <li>
+ The Logical_Switch of this port has a <code>localnet</code> port.
+ </li>
+
+ <li>
+ Native OVN services are enabled by configuring the DHCP and other
+ options like the way it is done for the normal logical ports.
+ </li>
+ </ul>
+
+ <p>
+ It is recommended to use the same HA chassis group for all the external
+ ports of a logical switch. Otherwise, the physical switch might see MAC
+ flap issue when different chassis provide the native services. For
+ example when supporting native DHCPv4 service, DHCPv4 server mac
+ (configured in <ref column="options:server_mac" table="DHCP_Options"
+ db="OVN_NB"/> column in table <ref table="DHCP_Options"/>) originating
+ from different ports can cause MAC flap issue.
+ The MAC of the logical router IP(s) can also flap if the same HA chassis
+ group is not set for all the external ports of a logical switch.
+ </p>
+
<h1>Security</h1>
<h2>Role-Based Access Controls for the Soutbound DB</h2>
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "5.15.0",
- "cksum": "1078795414 21917",
+ "version": "5.15.1",
+ "cksum": "2715809578 22214",
"tables": {
"NB_Global": {
"columns": {
@@ -102,6 +102,12 @@
"refType": "weak"},
"min": 0,
"max": 1}},
+ "ha_chassis_group": {
+ "type": {"key": {"type": "uuid",
+ "refTable": "HA_Chassis_Group",
+ "refType": "weak"},
+ "min": 0,
+ "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
@@ -353,6 +353,53 @@
<dd>
A port to a logical switch on a VTEP gateway.
</dd>
+
+ <dt><code>external</code></dt>
+ <dd>
+ <p>
+ Represents a logical port which is external and not having
+ an OVS port in the integration bridge.
+ <code>OVN</code> will never receive any traffic from this port or
+ send any traffic to this port. <code>OVN</code> can support
+ native services like DHCPv4/DHCPv6/DNS for this port.
+ If <ref column="ha_chassis_group"/> is defined,
+ <code>ovn-controller</code> running in the master chassis of
+ the HA chassis group will bind this port to provide these native
+ services. It is expected that this port belong to a bridged
+ logical switch (with a <code>localnet</code> port).
+ </p>
+
+ <p>
+ It is recommended to use the same HA chassis group for all the
+ external ports of a logical switch. Otherwise, the physical
+ switch might see MAC flap issue when different chassis provide
+ the native services. For example when supporting native DHCPv4
+ service, DHCPv4 server mac (configured in
+ <ref column="options:server_mac" table="DHCP_Options"
+ db="OVN_NB"/> column in table <ref table="DHCP_Options"/>)
+ originating from different ports can cause MAC flap issue.
+ The MAC of the logical router IP(s) can also flap if the
+ same HA chassis group is not set for all the external ports
+ of a logical switch.
+ </p>
+
+ <p>
+ Below are some of the use cases where <code>external</code>
+ ports can be used.
+ </p>
+
+ <ul>
+ <li>
+ VMs connected to SR-IOV nics - Traffic from these VMs by passes
+ the kernel stack and local <code>ovn-controller</code> do not
+ bind these ports and cannot serve the native services.
+ </li>
+
+ <li>
+ When CMS supports provisioning baremetal servers.
+ </li>
+ </ul>
+ </dd>
</dl>
</column>
</group>
@@ -901,6 +948,15 @@
</column>
</group>
+ <column name="ha_chassis_group">
+ References a row in the OVN Northbound database's
+ <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
+ It indicates the HA chassis group to use if the
+ <ref column="type"/> is set to <code>external</code>.
+ If <ref column="type"/> is not to <code>external</code>, this
+ column is ignored.
+ </column>
+
<group title="Naming">
<column name="external_ids" key="neutron:port_name">
<p>
@@ -397,7 +397,7 @@ ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
# ovn-northd should not create HA chassis group and HA chassis rows
# unless the HA chassis group in OVN NB DB is associated to
-# a logical router port.
+# a logical router port or logical port of type external.
AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \
| wc -l], [0], [0
])
@@ -731,4 +731,104 @@ ovn-nbctl clear logical_router_port lr0-public options
OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`])
AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+# Delete old sw0.
+ovn-nbctl ls-del sw0
+
+# Create external logical ports and associate ha_chassis_group
+ovn-nbctl ls-add sw0
+ovn-nbctl lsp-add sw0 sw0-pext1
+ovn-nbctl lsp-add sw0 sw0-pext2
+ovn-nbctl lsp-add sw0 sw0-p1
+
+ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3"
+ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4"
+ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5"
+
+ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
+
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20
+ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
+
+# ovn-northd should not create HA chassis group and HA chassis rows
+# unless the HA chassis group in OVN NB DB is associated to
+# a logical router port or logical port of type external.
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`])
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+
+hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \
+name=hagrp1`
+
+# The type of the lsp - sw0-pext1 is still not set to external.
+# So ha_chassis_group should be ignored.
+ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+# Set the type of sw0-pext1 to external
+ovn-nbctl lsp-set-type sw0-pext1 external
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
+name=hagrp1`
+
+AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext1`])
+
+# Set the type of sw0-pext2 to external and associate ha_chassis_group
+ovn-nbctl lsp-set-type sw0-pext2 external
+ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext1`])
+
+OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
+ha_chassis_group find port_binding logical_port=sw0-pext2`])
+
+# sw0-p1 is a normal port. So ha_chassis_group should not be set
+# in port_binding.
+ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \
+ha_chassis_group=$hagrp1_uuid
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-p1) = x], [0], [])
+
+# Clear ha_chassis_group for sw0-pext1
+ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-pext1) = x], [0], [])
+
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
+ha_chassis_group name="hagrp1" | wc -l`])
+
+AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+
+# Clear ha_chassis_group for sw0-pext2
+ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group
+
+OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=sw0-pext2) = x], [0], [])
+
+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`])
+AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
AT_CLEANUP
@@ -9857,11 +9857,10 @@ grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
])
# make sure that flows for handling the outside router port reside on gw1
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[1
]])
-
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[0
]])
@@ -9972,10 +9971,10 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
]])
# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[1
]])
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[0
]])
@@ -9987,10 +9986,10 @@ as main ovs-vsctl del-port n1 $port
bfd_dump
# make sure that flows for handling the outside router port reside on gw2 now
-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[1
]])
-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
grep 00:00:02:01:02:04 | wc -l], [0], [[0
]])
@@ -12111,6 +12110,723 @@ as hv2 start_daemon ovn-controller
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
+AT_SETUP([ovn -- external logical port])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+net_add n1
+sim_add hv1
+sim_add hv2
+sim_add hv3
+
+ovn-nbctl ls-add ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
+
+# Add a couple of external logical port
+ovn-nbctl lsp-add ls1 ls1-lp_ext1 \
+-- lsp-set-addresses ls1-lp_ext1 "f0:00:00:00:00:03 10.0.0.6 ae70::6"
+ovn-nbctl lsp-set-port-security ls1-lp_ext1 \
+"f0:00:00:00:00:03 10.0.0.6 ae70::6"
+ovn-nbctl lsp-set-type ls1-lp_ext1 external
+
+ovn-nbctl lsp-add ls1 ls1-lp_ext2 \
+-- lsp-set-addresses ls1-lp_ext2 "f0:00:00:00:00:04 10.0.0.7 ae70::7"
+ovn-nbctl lsp-set-port-security ls1-lp_ext2 \
+"f0:00:00:00:00:04 10.0.0.7 ae70::8"
+ovn-nbctl lsp-set-type ls1-lp_ext2 external
+
+d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \
+options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \
+\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")"
+
+d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
+options="\"server_id\"=\"00:00:00:10:00:01\"")"
+
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1}
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext1 ${d1}
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext2 ${d1}
+
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d2}
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext1 ${d2}
+ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext2 ${d2}
+
+# Create a logical router and connect it to ls1
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-ls1 a0:10:00:00:00:01 10.0.0.1/24
+ovn-nbctl lsp-add ls1 ls1-lr0
+ovn-nbctl set Logical_Switch_Port ls1-lr0 type=router \
+ options:router-port=lr0-ls1 addresses=router
+
+# Create HA chassis group
+ovn-nbctl ha-chassis-group-add hagrp1
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
+
+hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name="hagrp1"`
+
+# There should be 1 HA_Chassis rows with chassis sets
+OVS_WAIT_UNTIL([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
+| grep '-' | wc -l ], [0], [1
+])
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-phys hv1-ext1 -- \
+ set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \
+ options:rxq_pcap=hv1/ext1-rx.pcap \
+ ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-phys hv2-ext2 -- \
+ set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \
+ options:rxq_pcap=hv2/ext2-rx.pcap \
+ ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+as hv3
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.3
+ovs-vsctl -- add-port br-phys hv3-ext3 -- \
+ set interface hv3-ext3 options:tx_pcap=hv3/ext3-tx.pcap \
+ options:rxq_pcap=hv3/ext3-rx.pcap \
+ ofport-request=2
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and
+# hv2 as ha-chassis-group is not set and no localnet port added to ls1.
+AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
+wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+hv1_uuid=$(ovn-sbctl list chassis hv1 | grep uuid | awk '{print $3}')
+hv2_uuid=$(ovn-sbctl list chassis hv2 | grep uuid | awk '{print $3}')
+hv3_uuid=$(ovn-sbctl list chassis hv3 | grep uuid | awk '{print $3}')
+
+# The port_binding row for ls1-lp_ext1 should have empty chassis
+chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+
+AT_CHECK([test x$chassis == x], [0], [])
+
+# Associate hagrp1 ha-chassis-group to ls1-lp_ext1
+ovn-nbctl --wait=hv set Logical_Switch_Port ls1-lp_ext1 \
+ha-chassis-group=$hagrp1_uuid
+
+# Get the hagrp1 uuid in SB DB.
+sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
+name="hagrp1"`
+
+# Wait till ls1-lp_ext1 port_binding has ha_chassis_group set
+OVS_WAIT_UNTIL(
+ [sb_pb_hagrp=`ovn-sbctl --bare --columns ha_chassis_group find \
+port_binding logical_port=ls1-lp_ext1`
+ test "$sb_pb_hagrp" = "$sb_hagrp1_uuid"])
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2
+# as no localnet port added to ls1 yet.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+# Add the localnet port to the logical switch ls1
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl --wait=hv lsp-set-options ln-public network_name=phys
+
+ln_public_key=$(ovn-sbctl list port_binding ln-public | grep tunnel_key | \
+awk '{print $3}')
+
+# The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the
+# ha chassis group.
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = "$hv1_uuid"])
+
+# There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+wc -l], [0], [3
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [1
+])
+
+# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
+])
+
+# No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and
+# hv2 as requested-chassis option is not set.
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
+])
+
+as hv1
+ovs-vsctl show
+
+# This shell function sends a DHCP request packet
+# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ...
+test_dhcp() {
+ local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 use_ip=$5
+ shift; shift; shift; shift; shift;
+ if test $use_ip != 0; then
+ src_ip=$1
+ dst_ip=$2
+ shift; shift;
+ else
+ src_ip=`ip_to_hex 0 0 0 0`
+ dst_ip=`ip_to_hex 255 255 255 255`
+ fi
+ local request=ffffffffffff${src_mac}0800451001100000000080110000${src_ip}${dst_ip}
+ # udp header and dhcp header
+ request=${request}0044004300fc0000
+ request=${request}010106006359aa760000000000000000000000000000000000000000${src_mac}
+ # client hardware padding
+ request=${request}00000000000000000000
+ # server hostname
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ # boot file name
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ request=${request}0000000000000000000000000000000000000000000000000000000000000000
+ # dhcp magic cookie
+ request=${request}63825363
+ # dhcp message type
+ request=${request}3501${dhcp_type}ff
+
+ local srv_mac=$1 srv_ip=$2 expected_dhcp_opts=$3
+ # total IP length will be the IP length of the request packet
+ # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2)
+ ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
+ udp_len=`expr $ip_len - 20`
+ ip_len=$(printf "%x" $ip_len)
+ udp_len=$(printf "%x" $udp_len)
+ # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len
+ local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip}
+ # udp header and dhcp header.
+ # $udp_len var will be in 3 digits. So adding a '0' before $udp_len
+ reply=${reply}004300440${udp_len}0000020106006359aa760000000000000000
+ # your ip address
+ reply=${reply}${offer_ip}
+ # next server ip address, relay agent ip address, client mac address
+ reply=${reply}0000000000000000${src_mac}
+ # client hardware padding
+ reply=${reply}00000000000000000000
+ # server hostname
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ # boot file name
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
+ # dhcp magic cookie
+ reply=${reply}63825363
+ # dhcp message type
+ local dhcp_reply_type=02
+ if test $dhcp_type = 03; then
+ dhcp_reply_type=05
+ fi
+ reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
+ echo $reply >> ext1_v4.expected
+
+ as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
+}
+
+
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+
+# This shell function sends a DHCPv6 request packet
+# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
+# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
+# packet should be received twice (one from ovn-controller and the other
+# from the "ovs-ofctl monitor br-int resume"
+test_dhcpv6() {
+ local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
+ local req_pkt_in_expected=$6
+ local request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla}
+ # dst ip ff02::1:2
+ request=${request}ff020000000000000000000000010002
+ # udp header and dhcpv6 header
+ request=${request}02220223002affff${msg_code}010203
+ # Client identifier
+ request=${request}0001000a00030001${src_mac}
+ # IA-NA (Identity Association for Non Temporary Address)
+ request=${request}0003000c0102030400000e1000001518
+ shift; shift; shift; shift; shift;
+
+ local server_mac=000000100001
+ local server_lla=fe80000000000000020000fffe100001
+ local reply_code=07
+ if test $msg_code = 01; then
+ reply_code=02
+ fi
+ local msg_len=54
+ if test $offer_ip = 1; then
+ msg_len=28
+ fi
+ local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101
+ reply=${reply}${server_lla}${src_lla}
+
+ # udp header and dhcpv6 header
+ reply=${reply}0223022200${msg_len}ffff${reply_code}010203
+ # Client identifier
+ reply=${reply}0001000a00030001${src_mac}
+ # IA-NA
+ if test $offer_ip != 1; then
+ reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}
+ reply=${reply}ffffffffffffffff
+ fi
+ # Server identifier
+ reply=${reply}0002000a00030001${server_mac}
+
+ echo $reply | trim_zeros >> ext${inport}_v6.expected
+ # The inport also receives the request packet since it is connected
+ # to the br-phys.
+ #echo $request >> ext${inport}_v6.expected
+
+ as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
+}
+
+reset_pcap_file() {
+ local iface=$1
+ local pcap_file=$2
+ ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+ rm -f ${pcap_file}*.pcap
+ ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "$@"
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0_hv1.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log
+
+AT_CAPTURE_FILE([ofctl_monitor0_hv2.log])
+as hv2 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv2.log
+
+AT_CAPTURE_FILE([ofctl_monitor0_hv3.log])
+as hv3 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv3.log
+
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 1 in hv1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 0 in hv2.
+OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+
+# ovs-ofctl also resumes the packets and this causes other ports to receive
+# the DHCP request packet. So reset the pcap files so that its easier to test.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 0 in hv2.
+OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+# Delete the ha-chassis hv1.
+ovn-nbctl ha-chassis-group-remove-chassis hagrp1 hv1
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = ""])
+
+# Add hv2 to the ha chassis group
+ovn-nbctl --wait=hv ha-chassis-group-add-chassis hagrp1 hv2 20
+
+ovn-sbctl list ha_chassis_group
+ovn-sbctl list ha_chassis
+
+ovn-sbctl find port_binding logical_port=ls1-lp_ext1
+
+# The ls1-lp_ext1 should be bound to hv2
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = "$hv2_uuid"])
+
+# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+wc -l], [0], [3
+])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [1
+])
+
+# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
+grep controller | grep tp_src=546 | grep \
+"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+grep reg14=0x$ln_public_key | wc -l], [0], [0
+])
+
+# Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from
+# hv2 ovn-controller.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 1 in hv2.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+
+# ovs-ofctl also resumes the packets and this causes other ports to receive
+# the DHCP request packet. So reset the pcap files so that its easier to test.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+
+# Send DHCPv6 request again
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 1
+
+# NXT_RESUMEs should be 2 in hv1.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+# From ls1-lp_ext1, send ARP request for the router ip. The ARP
+# response should come from the router pipeline of hv2.
+ext1_mac=f00000000003
+router_mac=a01000000001
+ext1_ip=`ip_to_hex 10 0 0 6`
+router_ip=`ip_to_hex 10 0 0 1`
+arp_request=ffffffffffff${ext1_mac}08060001080006040001${ext1_mac}${ext1_ip}000000000000${router_ip}
+
+as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request
+expected_response=${src_mac}${router_mac}08060001080006040002${router_mac}${router_ip}${ext1_mac}${ext1_ip}
+echo $expected_response > expout
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_arp_resp
+AT_CHECK([cat ext1_arp_resp], [0], [expout])
+
+# Verify that the response came from hv2
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > ext1_arp_resp
+AT_CHECK([cat ext1_arp_resp], [0], [expout])
+
+# Now add 3 ha chassis to the ha chassis group
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv2 20
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 10
+
+# hv1 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = "$hv1_uuid"])
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+as hv3
+ovs-vsctl show
+reset_pcap_file hv3-ext3 hv3/ext3
+reset_pcap_file br-phys_n1 hv3/br-phys_n1
+reset_pcap_file br-phys hv3/br-phys
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 3 in hv1.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+
+# ovs-ofctl also resumes the packets and this causes other ports to receive
+# the DHCP request packet. So reset the pcap files so that its easier to test.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+rm -f ext1_v6.expected
+rm -f ext1_v6.packets
+as hv1 reset_pcap_file hv1-ext1 hv1/ext1
+
+# Now increase the priority of hv3 so it becomes master.
+ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 50
+
+# hv3 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = "$hv3_uuid"])
+
+as hv1
+ovs-vsctl show
+reset_pcap_file hv1-ext1 hv1/ext1
+reset_pcap_file br-phys_n1 hv1/br-phys_n1
+reset_pcap_file br-phys hv1/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv2-ext2 hv2/ext2
+reset_pcap_file br-phys_n1 hv2/br-phys_n1
+reset_pcap_file br-phys hv2/br-phys
+
+as hv2
+ovs-vsctl show
+reset_pcap_file hv3-ext3 hv3/ext3
+reset_pcap_file br-phys_n1 hv3/br-phys_n1
+reset_pcap_file br-phys hv3/br-phys
+
+# Send DHCPDISCOVER.
+offer_ip=`ip_to_hex 10 0 0 6`
+server_ip=`ip_to_hex 10 0 0 1`
+server_mac=ff1000000001
+expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
+$expected_dhcp_opts
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 1 in hv3.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+cat ext1_v4.expected | cut -c -48 > expout
+AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat ext1_v4.expected | cut -c 53- > expout
+AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+
+# ovs-ofctl also resumes the packets and this causes other ports to receive
+# the DHCP request packet. So reset the pcap files so that its easier to test.
+as hv1
+reset_pcap_file hv1-ext1 hv1/ext1
+
+rm -f ext1_v4.expected
+rm -f ext1_v4.packets
+
+# Send DHCPv6 request
+src_mac=f00000000003
+src_lla=fe80000000000000f20000fffe000003
+offer_ip=ae700000000000000000000000000006
+test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
+
+# NXT_RESUMEs should be 4 in hv1.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+
+# NXT_RESUMEs should be 2 in hv3.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+sort > ext1_v6.packets
+cat ext1_v6.expected | cut -c -120 > expout
+AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+# Skipping the UDP checksum
+cat ext1_v6.expected | cut -c 125- > expout
+AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+
+# disconnect hv3 from the network, hv1 should take over
+as hv3
+port=${sandbox}_br-phys
+as main ovs-vsctl del-port n1 $port
+
+# hv1 should be master and claim ls1-lp_ext1
+OVS_WAIT_UNTIL(
+ [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
+logical_port=ls1-lp_ext1`
+ test "$chassis" = "$hv1_uuid"])
+
+OVN_CLEANUP([hv1],[hv2],[hv3])
+AT_CLEANUP
+
AT_SETUP([ovn -- ovn-controller restart])
AT_SKIP_IF([test $HAVE_PYTHON = no])
ovn_start