From patchwork Mon May 27 07:22:24 2019
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Numan Siddique
+ If inport is of type
+ and applies the action
+
+ and advances the packet to the next table.
+
+ Where VIP is the virtual ip configured in the column
+ .
+
Priority-50 flows that match ARP requests to each known IP address
@@ -541,7 +570,8 @@ output;
These flows are omitted for logical ports (other than router ports or
-
These flows are omitted for logical ports (other than router ports or
- virtual
adds a priority-100
+ logical flow for each P configured in the
+
+ column if P is not the present virtual parent of the
+ logical port with the match
+
+
+
+ inport == P && ((arp.op == 1 && arp.spa == VIP && arp.tpa == VIP) || (arp.op == 2 && arp.spa == VIP))
+
+
+
+ bind_vport(V, inport);
+ localport
ports) that are down.
+ localport
ports) that are down and for logical ports of
+ type virtual
.
localport
ports) that are down.
+ localport
ports) that are down and for logical ports of
+ type virtual
.
eth.dst = E; next;
.
+ For each virtual ip A configured on a logical port
+ of type virtual
and its virtual parent set in
+ its corresponding
+ record and the virtual parent with the Ethernet address E
+ and the virtual ip is reachable via the router port P, a
+ priority-100 flow with match outport === P
+ && reg0 == A
has actions
+ eth.dst = E; next;
.
+
+ For each virtual ip A configured on a logical port
+ of type virtual
and its virtual parent not
+ set in its corresponding
+
+ record and the virtual ip A is reachable via the
+ router port P, a
+ priority-100 flow with match outport === P
+ && reg0 == A
has actions
+ eth.dst = 00:00:00:00:00:00; next;
.
+ This flow is added so that the ARP is always resolved for the
+ virtual ip A by generating ARP request and
+ not
consulting the MAC_Binding table as it can have
+ incorrect value for the virtual ip A.
+
For each IPv6 address A whose host is known to have Ethernet address E on router port P, a diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index de0c06d4b..8250875c4 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -4509,96 +4509,153 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, continue; } - /* - * Add ARP/ND reply flows if either the - * - port is up or - * - port type is router or - * - port type is localport - */ - if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") && - strcmp(op->nbsp->type, "localport")) { - continue; - } + if (!strcmp(op->nbsp->type, "virtual")) { + /* Handle + * - GARPs for virtual ip which belongs to a logical port + * of type 'virtual' and bind that port. + * + * - ARP reply from the virtual ip which belongs to a logical + * port of type 'virtual' and bind that port. + * */ + ovs_be32 ip; + const char *virtual_ip = smap_get(&op->nbsp->options, + "virtual-ip"); + const char *virtual_parents = smap_get(&op->nbsp->options, + "virtual-parents"); + if (!virtual_ip || !virtual_parents || + !ip_parse(virtual_ip, &ip)) { + continue; + } - if (lsp_is_external(op->nbsp)) { - continue; - } + char *tokstr = xstrdup(virtual_parents); + char *save_ptr = NULL; + char *vparent; + for (vparent = strtok_r(tokstr, ",", &save_ptr); vparent != NULL; + vparent = strtok_r(NULL, ",", &save_ptr)) { + if (op->sb && op->sb->virtual_parent && + op->sb->virtual_parent[0] && + !strcmp(op->sb->virtual_parent, vparent)) { + /* virtual port is presently claimed by the + * ``vparent``. There is no need to add the lflow + * to bind the port. */ + continue; + } + + struct ovn_port *vp = ovn_port_find(ports, vparent); + if (!vp || vp->od != op->od) { + /* vparent name should be valid and it should belong + * to the same logical switch. */ + 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); - ds_put_format(&match, "arp.tpa == %s && arp.op == 1", - op->lsp_addrs[i].ipv4_addrs[j].addr_s); + ds_put_format(&match, "inport == \"%s\" && " + "((arp.op == 1 && arp.spa == %s && " + "arp.tpa == %s) || (arp.op == 2 && " + "arp.spa == %s))", + vparent, virtual_ip, virtual_ip, virtual_ip); ds_clear(&actions); ds_put_format(&actions, - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; " - "arp.sha = %s; " - "arp.tpa = arp.spa; " - "arp.spa = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output;", - op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s, - op->lsp_addrs[i].ipv4_addrs[j].addr_s); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, + "bind_vport(%s, inport); " + "next;", + op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, ds_cstr(&match), ds_cstr(&actions)); + } - /* Do not reply to an ARP request from the port that owns the - * address (otherwise a DHCP client that ARPs to check for a - * duplicate address will fail). Instead, forward it the usual - * way. - * - * (Another alternative would be to simply drop the packet. If - * everything is working as it is configured, then this would - * produce equivalent results, since no one should reply to the - * request. But ARPing for one's own IP address is intended to - * detect situations where the network is not working as - * configured, so dropping the request would frustrate that - * intent.) */ - ds_put_format(&match, " && inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, - ds_cstr(&match), "next;"); + free(tokstr); + } else { + /* + * Add ARP/ND reply flows if either the + * - port is up or + * - port type is router or + * - port type is localport + */ + if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") && + strcmp(op->nbsp->type, "localport")) { + continue; } - /* For ND solicitations, we need to listen for both the - * unicast IPv6 address and its all-nodes multicast address, - * but always respond with the unicast IPv6 address. */ - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { - ds_clear(&match); - ds_put_format(&match, - "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s); + if (lsp_is_external(op->nbsp)) { + continue; + } - ds_clear(&actions); - ds_put_format(&actions, - "%s { " + 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); + ds_put_format(&match, "arp.tpa == %s && arp.op == 1", + op->lsp_addrs[i].ipv4_addrs[j].addr_s); + ds_clear(&actions); + ds_put_format(&actions, + "eth.dst = eth.src; " "eth.src = %s; " - "ip6.src = %s; " - "nd.target = %s; " - "nd.tll = %s; " + "arp.op = 2; /* ARP reply */ " + "arp.tha = arp.sha; " + "arp.sha = %s; " + "arp.tpa = arp.spa; " + "arp.spa = %s; " "outport = inport; " "flags.loopback = 1; " - "output; " - "};", - !strcmp(op->nbsp->type, "router") ? - "nd_na_router" : "nd_na", - op->lsp_addrs[i].ea_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ea_s); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, - ds_cstr(&match), ds_cstr(&actions)); + "output;", + op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s, + op->lsp_addrs[i].ipv4_addrs[j].addr_s); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, + ds_cstr(&match), ds_cstr(&actions)); + + /* Do not reply to an ARP request from the port that owns + * the address (otherwise a DHCP client that ARPs to check + * for a duplicate address will fail). Instead, forward + * it the usual way. + * + * (Another alternative would be to simply drop the packet. + * If everything is working as it is configured, then this + * would produce equivalent results, since no one should + * reply to the request. But ARPing for one's own IP + * address is intended to detect situations where the + * network is not working as configured, so dropping the + * request would frustrate that intent.) */ + ds_put_format(&match, " && inport == %s", op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, + ds_cstr(&match), "next;"); + } - /* Do not reply to a solicitation from the port that owns the - * address (otherwise DAD detection will fail). */ - ds_put_format(&match, " && inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, - ds_cstr(&match), "next;"); + /* For ND solicitations, we need to listen for both the + * unicast IPv6 address and its all-nodes multicast address, + * but always respond with the unicast IPv6 address. */ + for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { + ds_clear(&match); + ds_put_format(&match, + "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s); + + ds_clear(&actions); + ds_put_format(&actions, + "%s { " + "eth.src = %s; " + "ip6.src = %s; " + "nd.target = %s; " + "nd.tll = %s; " + "outport = inport; " + "flags.loopback = 1; " + "output; " + "};", + !strcmp(op->nbsp->type, "router") ? + "nd_na_router" : "nd_na", + op->lsp_addrs[i].ea_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ipv6_addrs[j].addr_s, + op->lsp_addrs[i].ea_s); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, + ds_cstr(&match), ds_cstr(&actions)); + + /* Do not reply to a solicitation from the port that owns + * the address (otherwise DAD detection will fail). */ + ds_put_format(&match, " && inport == %s", op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, + ds_cstr(&match), "next;"); + } } } } @@ -7080,7 +7137,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, 100, ds_cstr(&match), ds_cstr(&actions)); } } - } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")) { + } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router") + && strcmp(op->nbsp->type, "virtual")) { /* This is a logical switch port that backs a VM or a container. * Extract its addresses. For each of the address, go through all * the router ports attached to the switch (to which this port @@ -7157,6 +7215,105 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, } } } + } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router") + && !strcmp(op->nbsp->type, "virtual")) { + /* This is a virtual port. Add ARP replies for the virtual ip with + * the mac of the present active virtual parent. + * If the logical port doesn't have virtual parent set in + * Port_Binding table, then add the flow to set eth.dst to + * 00:00:00:00:00:00 and advance to next table so that ARP is + * resolved by router pipeline using the arp{} action. + * The MAC_Binding entry for the virtual ip might be invalid. */ + ovs_be32 ip; + + const char *vip = smap_get(&op->nbsp->options, + "virtual-ip"); + const char *virtual_parents = smap_get(&op->nbsp->options, + "virtual-parents"); + if (!vip || !virtual_parents || + !ip_parse(vip, &ip) || !op->sb) { + continue; + } + + if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || + !op->sb->chassis) { + /* The virtual port is not claimed yet. */ + for (size_t i = 0; i < op->od->n_router_ports; i++) { + const char *peer_name = smap_get( + &op->od->router_ports[i]->nbsp->options, + "router-port"); + if (!peer_name) { + continue; + } + + struct ovn_port *peer = ovn_port_find(ports, peer_name); + if (!peer || !peer->nbrp) { + continue; + } + + if (find_lrp_member_ip(peer, vip)) { + ds_clear(&match); + ds_put_format(&match, "outport == %s && reg0 == %s", + peer->json_key, vip); + + ds_clear(&actions); + ds_put_format(&actions, + "eth.dst = 00:00:00:00:00:00; next;"); + ovn_lflow_add(lflows, peer->od, + S_ROUTER_IN_ARP_RESOLVE, 100, + ds_cstr(&match), ds_cstr(&actions)); + break; + } + } + } else { + struct ovn_port *vp = + ovn_port_find(ports, op->sb->virtual_parent); + if (!vp || !vp->nbsp) { + continue; + } + + for (size_t i = 0; i < vp->n_lsp_addrs; i++) { + bool found_vip_network = false; + const char *ea_s = vp->lsp_addrs[i].ea_s; + for (size_t j = 0; j < vp->od->n_router_ports; j++) { + /* Get the Logical_Router_Port that the + * Logical_Switch_Port is connected to, as + * 'peer'. */ + const char *peer_name = smap_get( + &vp->od->router_ports[j]->nbsp->options, + "router-port"); + if (!peer_name) { + continue; + } + + struct ovn_port *peer = + ovn_port_find(ports, peer_name); + if (!peer || !peer->nbrp) { + continue; + } + + if (!find_lrp_member_ip(peer, vip)) { + continue; + } + + ds_clear(&match); + ds_put_format(&match, "outport == %s && reg0 == %s", + peer->json_key, vip); + + ds_clear(&actions); + ds_put_format(&actions, "eth.dst = %s; next;", ea_s); + ovn_lflow_add(lflows, peer->od, + S_ROUTER_IN_ARP_RESOLVE, 100, + ds_cstr(&match), ds_cstr(&actions)); + found_vip_network = true; + break; + } + + if (found_vip_network) { + break; + } + } + } } else if (!strcmp(op->nbsp->type, "router")) { /* This is a logical switch port that connects to a router. */ @@ -8712,6 +8869,8 @@ main(int argc, char *argv[]) &sbrec_port_binding_col_gateway_chassis); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_ha_chassis_group); + ovsdb_idl_add_column(ovnsb_idl_loop.idl, + &sbrec_port_binding_col_virtual_parent); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_chassis); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name); diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index cbaa9495f..bb09550a7 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -400,6 +400,31 @@ + +
virtual
+ Represents a logical port which does not have an OVS + port in the integration bridge and has a virtual ip configured + in the column. This virtual ip + can move around between the logical ports configured in + the column. +
+ +
+ One of the use case where virtual
+ ports can be used is.
+
virtual ip
represents a load balancer vip
+ and the virtual parents
provide load balancer
+ service in an active-standby setup with the active virtual
+ parent owning the virtual ip
.
+
+ These options apply when is
+ virtual
.
+
virtual ip
configured
+ in the . All these virtual parents
+ should add the virtual ip
in the
+ if port security addressed are enabled.
+ Prerequisite: tcp
bind_vport(V, P);
+ Parameters: logical port string field V
+ of type virtual
, logical port string field
+ P.
+
+ Binds the virtual logical port V and sets the + column and + of + the table . + is + set to P. +
+outport
will be reset to the value of the
distributed port.
+
+ virtual
virtual ip
.
+ This virtual ip
can be configured on a
+ logical port (which is refered as virtual parent).
+
+ This column is set by ovn-controller
with one of the
+ value from the
+ in the OVN_Northbound database's
+ table
+ when the OVN action bind_vport
is executed.
+ ovn-controller
also sets the
+ column when it executes this action
+ with its chassis id.
+
+ ovn-controller
sets this column only if the
+ is "virtual".
+
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c index fff432d61..4a8bede9c 100644 --- a/ovn/utilities/ovn-trace.c +++ b/ovn/utilities/ovn-trace.c @@ -2137,6 +2137,9 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, case OVNACT_CHECK_PKT_LARGER: break; + + case OVNACT_BIND_VPORT: + break; } } ds_destroy(&s); diff --git a/tests/ovn.at b/tests/ovn.at index 1231f4106..909a7b449 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1354,6 +1354,15 @@ reg0 = check_pkt_larger(foo); reg0[0] = check_pkt_larger(foo); Syntax error at `foo' expecting `;'. +# bind_vport +# lsp1's port key is 0x11. +bind_vport("lsp1", inport); + encodes as controller(userdata=00.00.00.0f.00.00.00.00.11.00.00.00) + +# lsp2 doesn't exist. So it should be encoded as drop. +bind_vport("lsp2", inport); + encodes as drop + # Miscellaneous negative tests. ; Syntax error at `;'. @@ -14017,3 +14026,300 @@ ovn-hv4-0 OVN_CLEANUP([hv1], [hv2], [hv3]) AT_CLEANUP + +AT_SETUP([ovn -- virtual ports]) +AT_KEYWORDS([virtual ports]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +send_garp() { + local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6 + local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa} + as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request +} + +send_arp_reply() { + local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6 + local request=${eth_dst}${eth_src}08060001080006040002${eth_src}${spa}${eth_dst}${tpa} + as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request +} + +net_add 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 hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=sw0-p1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=sw0-p3 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +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 hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=sw0-p2 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv2-vif2 -- \ + set interface hv2-vif2 external-ids:iface-id=sw1-p1 \ + options:tx_pcap=hv2/vif2-tx.pcap \ + options:rxq_pcap=hv2/vif2-rx.pcap \ + ofport-request=2 + +ovn-nbctl ls-add sw0 + +ovn-nbctl lsp-add sw0 sw0-vir +ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" +ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" +ovn-nbctl lsp-set-type sw0-vir virtual +ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 +ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2 + +ovn-nbctl lsp-add sw0 sw0-p1 +ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" +ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" + +ovn-nbctl lsp-add sw0 sw0-p2 +ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" +ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" + +ovn-nbctl lsp-add sw0 sw0-p3 +ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" +ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5" + +# Create the second logical switch with one port +ovn-nbctl ls-add sw1 +ovn-nbctl lsp-add sw1 sw1-p1 +ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" +ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" + +# Create a logical router and attach both logical switches +ovn-nbctl lr-add lr0 +ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 +ovn-nbctl lsp-add sw0 sw0-lr0 +ovn-nbctl lsp-set-type sw0-lr0 router +ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 +ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 + +ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 +ovn-nbctl lsp-add sw1 sw1-lr0 +ovn-nbctl lsp-set-type sw1-lr0 router +ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 +ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 + +OVN_POPULATE_ARP +ovn-nbctl --wait=hv sync + +# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline +# with bind_vport action. + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) +]) + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} + +hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` +hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` + +AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = x]) + +# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir +# and sw0-p1 should be its virtual_parent. +eth_src=505400000003 +eth_dst=ffffffffffff +spa=$(ip_to_hex 10 0 0 10) +tpa=$(ip_to_hex 10 0 0 10) +send_garp 1 1 $eth_src $eth_dst $spa $tpa + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = xsw0-p1]) + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) +]) + +# send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir +# and sw0-p2 shpuld be its virtual_parent. +eth_src=505400000004 +eth_dst=ffffffffffff +spa=$(ip_to_hex 10 0 0 10) +tpa=$(ip_to_hex 10 0 0 10) +send_garp 2 1 $eth_src $eth_dst $spa $tpa + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = xsw0-p2]) + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) +]) + +# Now send arp reply from sw0-p1. hv1 should claim sw0-vir +# and sw0-p1 shpuld be its virtual_parent. +eth_src=505400000003 +eth_dst=ffffffffffff +spa=$(ip_to_hex 10 0 0 10) +tpa=$(ip_to_hex 10 0 0 4) +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = xsw0-p1]) + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) +]) + +# Delete hv1-vif1 port. hv1 should release sw0-vir +# and the logical flows to bind_vport on sw0-p1 should +# be added back. +as hv1 ovs-vsctl del-port hv1-vif1 + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = x]) + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) +]) + +# Now send arp reply from sw0-p2. hv2 should claim sw0-vir +# and sw0-p2 shpuld be its virtual_parent. +eth_src=505400000004 +eth_dst=ffffffffffff +spa=$(ip_to_hex 10 0 0 10) +tpa=$(ip_to_hex 10 0 0 3) +send_arp_reply 2 1 $eth_src $eth_dst $spa $tpa + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = xsw0-p2]) + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=9 (lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) +]) + +# Delete sw0-p2 logical port +ovn-nbctl lsp-del sw0-p2 + +OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ +logical_port=sw0-vir) = x], [0], []) + +AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ +logical_port=sw0-vir) = x]) + +# Clear virtual_ip column of sw0-vir. There should be no bind_vport flows. +ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-ip + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl +]) + +# Add back virtual_ip and clear virtual_parents. +ovn-nbctl --wait=hv set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 + +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl + table=11(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +]) + +ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents +ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl +]) + +ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ +> lflows.txt + +AT_CHECK([cat lflows.txt], [0], [dnl +]) + +OVN_CLEANUP([hv1], [hv2]) +AT_CLEANUP diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 450cb9db5..6042e5bc9 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -1248,6 +1248,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) simap_put(&ports, "eth0", 5); simap_put(&ports, "eth1", 6); simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL)); + simap_put(&ports, "lsp1", 0x11); ds_init(&input); while (!ds_get_test_line(&input, stdin)) {