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 X-Patchwork-Id: 1105665 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 45C7jW1QVhz9sB8 for ; Mon, 27 May 2019 17:22:53 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 0850F14B8; Mon, 27 May 2019 07:22:50 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id B386B143D for ; Mon, 27 May 2019 07:22:36 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 8A144A9 for ; Mon, 27 May 2019 07:22:33 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id DF6046378 for ; Mon, 27 May 2019 07:22:32 +0000 (UTC) Received: from nusiddiq.mac (unknown [10.74.10.62]) by smtp.corp.redhat.com (Postfix) with ESMTP id 9515F27C38 for ; Mon, 27 May 2019 07:22:29 +0000 (UTC) From: nusiddiq@redhat.com To: dev@openvswitch.org Date: Mon, 27 May 2019 12:52:24 +0530 Message-Id: <20190527072224.26074-1-nusiddiq@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Mon, 27 May 2019 07:22:32 +0000 (UTC) X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, HTML_SINGLET_MANY, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v3] ovn: Add a new logical switch port type - 'virtual' X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Numan Siddique This new type is added for the following reasons: - When a load balancer is created in an OpenStack deployment with Octavia service, it creates a logical port 'VIP' for the virtual ip. - This logical port is not bound to any VIF. - Octavia service creates a service VM (with another logical port 'P' which belongs to the same logical switch) - The virtual ip 'VIP' is configured on this service VM. - This service VM provides the load balancing for the VIP with the configured backend IPs. - Octavia service can be configured to create few service VMs with active-standby mode with the active VM configured with the VIP. The VIP can move between these service nodes. Presently there are few problems: - When a floating ip (externally reachable IP) is associated to the VIP and if the compute nodes have external connectivity then the external traffic cannot reach the VIP using the floating ip as the VIP logical port would be down. dnat_and_snat entry in NAT table for this vip will have 'external_mac' and 'logical_port' configured. - The only way to make it work is to clear the 'external_mac' entry so that the gateway chassis does the DNAT for the VIP. To solve these problems, this patch proposes a new logical port type - virtual. CMS when creating the logical port for the VIP, should - set the type as 'virtual' - configure the VIP in the options - Logical_Switch_Port.options:virtual-ip - And set the virtual parents in the options Logical_Switch_Port.options:virtual-parents. These virtual parents are the one which can be configured with the VIP. If suppose the virtual_ip is configured to 10.0.0.10 on a virtual logical port 'sw0-vip' and the virtual_parents are set to - [sw0-p1, sw0-p2] then below logical flows are added in the lsp_in_arp_rsp logical switch pipeline - 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-vip", 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-vip", inport); next;) The action bind_vport will claim the logical port - sw0-vip on the chassis where this action is executed. Since the port - sw0-vip is claimed by a chassis, the dnat_and_snat rule for the VIP will be handled by the compute node. Signed-off-by: Numan Siddique --- v2 -> v3 ======= * Addressed the review comments from Ben - deleted the new columns - virtual_ip and virtual_parents from Logical_Switch_Port and instead is making use of options column for this purpose. v1 -> v2 ======== * In v1, was not updating the 'put_vport_binding' struct if it already exists in the put_vport_bindings hmap in the function - pinctrl_handle_bind_vport(). In v2 handled it. * Improved the if else check in binding.c when releasing the lports. include/ovn/actions.h | 18 ++- ovn/controller/binding.c | 30 +++- ovn/controller/pinctrl.c | 174 ++++++++++++++++++++ ovn/lib/actions.c | 60 +++++++ ovn/lib/ovn-util.c | 1 + ovn/northd/ovn-northd.8.xml | 62 ++++++- ovn/northd/ovn-northd.c | 313 +++++++++++++++++++++++++++--------- ovn/ovn-nb.xml | 45 ++++++ ovn/ovn-sb.ovsschema | 6 +- ovn/ovn-sb.xml | 46 ++++++ ovn/utilities/ovn-trace.c | 3 + tests/ovn.at | 306 +++++++++++++++++++++++++++++++++++ tests/test-ovn.c | 1 + 13 files changed, 978 insertions(+), 87 deletions(-) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index f42bbc277..48c64f792 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -83,7 +83,8 @@ struct ovn_extend_table; OVNACT(ND_NS, ovnact_nest) \ OVNACT(SET_METER, ovnact_set_meter) \ OVNACT(OVNFIELD_LOAD, ovnact_load) \ - OVNACT(CHECK_PKT_LARGER, ovnact_check_pkt_larger) + OVNACT(CHECK_PKT_LARGER, ovnact_check_pkt_larger)\ + OVNACT(BIND_VPORT, ovnact_bind_vport) /* enum ovnact_type, with a member OVNACT_ for each action. */ enum OVS_PACKED_ENUM ovnact_type { @@ -318,6 +319,13 @@ struct ovnact_check_pkt_larger { struct expr_field dst; /* 1-bit destination field. */ }; +/* OVNACT_BIND_VPORT. */ +struct ovnact_bind_vport { + struct ovnact ovnact; + char *vport; + struct expr_field vport_parent; /* Logical virtual port's port name. */ +}; + /* Internal use by the helpers below. */ void ovnact_init(struct ovnact *, enum ovnact_type, size_t len); void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len); @@ -486,6 +494,14 @@ enum action_opcode { * The actions, in OpenFlow 1.3 format, follow the action_header. */ ACTION_OPCODE_ICMP4_ERROR, + + /* "bind_vport(vport, vport_parent)". + * + * 'vport' follows the action_header, in the format - 32-bit field. + * 'vport_parent' is passed through the packet metadata as + * MFF_LOG_INPORT. + */ + ACTION_OPCODE_BIND_VPORT, }; /* Header. */ diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c index b62b3da23..29ef78418 100644 --- a/ovn/controller/binding.c +++ b/ovn/controller/binding.c @@ -573,11 +573,31 @@ consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn, sbrec_port_binding_set_encap(binding_rec, encap_rec); } } else if (binding_rec->chassis == chassis_rec) { - VLOG_INFO("Releasing lport %s from this chassis.", - binding_rec->logical_port); - if (binding_rec->encap) - sbrec_port_binding_set_encap(binding_rec, NULL); - sbrec_port_binding_set_chassis(binding_rec, NULL); + if (!strcmp(binding_rec->type, "virtual")) { + /* pinctrl module takes care of binding the ports + * of type 'virtual'. + * Release such ports if their virtual parents are no + * longer claimed by this chassis. */ + const struct sbrec_port_binding *parent + = lport_lookup_by_name(sbrec_port_binding_by_name, + binding_rec->virtual_parent); + if (!parent || parent->chassis != chassis_rec) { + VLOG_INFO("Releasing lport %s from this chassis.", + binding_rec->logical_port); + if (binding_rec->encap) { + sbrec_port_binding_set_encap(binding_rec, NULL); + } + sbrec_port_binding_set_chassis(binding_rec, NULL); + sbrec_port_binding_set_virtual_parent(binding_rec, NULL); + } + } else { + VLOG_INFO("Releasing lport %s from this chassis.", + binding_rec->logical_port); + if (binding_rec->encap) { + sbrec_port_binding_set_encap(binding_rec, NULL); + } + sbrec_port_binding_set_chassis(binding_rec, NULL); + } } else if (our_chassis) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_INFO_RL(&rl, diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index b7bb4c990..cf0228a0d 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -223,8 +223,21 @@ static void send_ipv6_ras(struct rconn *swconn, OVS_REQUIRES(pinctrl_mutex); static bool may_inject_pkts(void); +static void init_put_vport_bindings(void); +static void destroy_put_vport_bindings(void); +static void run_put_vport_bindings( + struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + struct ovsdb_idl_index *sbrec_port_binding_by_key, + const struct sbrec_chassis *chassis) + OVS_REQUIRES(pinctrl_mutex); +static void wait_put_vport_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn); +static void pinctrl_handle_bind_vport(const struct flow *md, + struct ofpbuf *userdata); + COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map); +COVERAGE_DEFINE(pinctrl_drop_put_vport_binding); void pinctrl_init(void) @@ -233,6 +246,7 @@ pinctrl_init(void) init_send_garps(); init_ipv6_ras(); init_buffered_packets_map(); + init_put_vport_bindings(); pinctrl.br_int_name = NULL; pinctrl_handler_seq = seq_create(); pinctrl_main_seq = seq_create(); @@ -1745,6 +1759,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) &pin, &userdata, &continuation); break; + case ACTION_OPCODE_BIND_VPORT: + ovs_mutex_lock(&pinctrl_mutex); + pinctrl_handle_bind_vport(&pin.flow_metadata.flow, &userdata); + ovs_mutex_unlock(&pinctrl_mutex); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -1912,6 +1932,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, sbrec_mac_binding_by_lport_ip); + run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key, + sbrec_port_binding_by_key, chassis); send_garp_prepare(sbrec_port_binding_by_datapath, sbrec_port_binding_by_name, br_int, chassis, local_datapaths, active_tunnels); @@ -2251,6 +2273,7 @@ void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn) { wait_put_mac_bindings(ovnsb_idl_txn); + wait_put_vport_bindings(ovnsb_idl_txn); int64_t new_seq = seq_read(pinctrl_main_seq); seq_wait(pinctrl_main_seq, new_seq); } @@ -2267,6 +2290,7 @@ pinctrl_destroy(void) destroy_ipv6_ras(); destroy_buffered_packets_map(); destroy_put_mac_bindings(); + destroy_put_vport_bindings(); destroy_dns_cache(); seq_destroy(pinctrl_main_seq); seq_destroy(pinctrl_handler_seq); @@ -3290,3 +3314,153 @@ exit: dp_packet_delete(pkt_out); } } + +struct put_vport_binding { + struct hmap_node hmap_node; + + /* Key and value. */ + uint32_t dp_key; + uint32_t vport_key; + + uint32_t vport_parent_key; +}; + +/* Contains "struct put_vport_binding"s. */ +static struct hmap put_vport_bindings; + +static void +init_put_vport_bindings(void) +{ + hmap_init(&put_vport_bindings); +} + +static void +flush_put_vport_bindings(void) +{ + struct put_vport_binding *vport_b; + HMAP_FOR_EACH_POP (vport_b, hmap_node, &put_vport_bindings) { + free(vport_b); + } +} + +static void +destroy_put_vport_bindings(void) +{ + flush_put_vport_bindings(); + hmap_destroy(&put_vport_bindings); +} + +static void +wait_put_vport_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn) +{ + if (ovnsb_idl_txn && !hmap_is_empty(&put_vport_bindings)) { + poll_immediate_wake(); + } +} + +static struct put_vport_binding * +pinctrl_find_put_vport_binding(uint32_t dp_key, uint32_t vport_key, + uint32_t hash) +{ + struct put_vport_binding *vpb; + HMAP_FOR_EACH_WITH_HASH (vpb, hmap_node, hash, &put_vport_bindings) { + if (vpb->dp_key == dp_key && vpb->vport_key == vport_key) { + return vpb; + } + } + return NULL; +} + +static void +run_put_vport_binding(struct ovsdb_idl_txn *ovnsb_idl_txn OVS_UNUSED, + struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + struct ovsdb_idl_index *sbrec_port_binding_by_key, + const struct sbrec_chassis *chassis, + const struct put_vport_binding *vpb) +{ + /* Convert logical datapath and logical port key into lport. */ + const struct sbrec_port_binding *pb = lport_lookup_by_key( + sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, + vpb->dp_key, vpb->vport_key); + if (!pb) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + + VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" " + "and port %"PRIu32, vpb->dp_key, vpb->vport_key); + return; + } + + /* pinctrl module updates the port binding only for type 'virtual'. */ + if (!strcmp(pb->type, "virtual")) { + const struct sbrec_port_binding *parent = lport_lookup_by_key( + sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, + vpb->dp_key, vpb->vport_parent_key); + if (parent) { + VLOG_INFO("Claiming virtual lport %s for this chassis " + "with the virtual parent %s", + pb->logical_port, parent->logical_port); + sbrec_port_binding_set_chassis(pb, chassis); + sbrec_port_binding_set_virtual_parent(pb, parent->logical_port); + } + } +} + +/* Called by pinctrl_run(). Runs with in the main ovn-controller + * thread context. */ +static void +run_put_vport_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + struct ovsdb_idl_index *sbrec_port_binding_by_key, + const struct sbrec_chassis *chassis) + OVS_REQUIRES(pinctrl_mutex) +{ + if (!ovnsb_idl_txn) { + return; + } + + const struct put_vport_binding *vpb; + HMAP_FOR_EACH (vpb, hmap_node, &put_vport_bindings) { + run_put_vport_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key, + sbrec_port_binding_by_key, chassis, vpb); + } + + flush_put_vport_bindings(); +} + +/* Called with in the pinctrl_handler thread context. */ +static void +pinctrl_handle_bind_vport( + const struct flow *md, struct ofpbuf *userdata) + OVS_REQUIRES(pinctrl_mutex) +{ + /* Get the datapath key from the packet metadata. */ + uint32_t dp_key = ntohll(md->metadata); + uint32_t vport_parent_key = md->regs[MFF_LOG_INPORT - MFF_REG0]; + + /* Get the virtual port key from the userdata buffer. */ + uint32_t *vport_key = ofpbuf_try_pull(userdata, sizeof *vport_key); + + if (!vport_key) { + return; + } + + uint32_t hash = hash_2words(dp_key, *vport_key); + + struct put_vport_binding *vpb + = pinctrl_find_put_vport_binding(dp_key, *vport_key, hash); + if (!vpb) { + if (hmap_count(&put_vport_bindings) >= 1000) { + COVERAGE_INC(pinctrl_drop_put_vport_binding); + return; + } + + vpb = xmalloc(sizeof *vpb); + hmap_insert(&put_vport_bindings, &vpb->hmap_node, hash); + } + + vpb->dp_key = dp_key; + vpb->vport_key = *vport_key; + vpb->vport_parent_key = vport_parent_key; + + notify_pinctrl_main(); +} diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index d132214bf..ac943255b 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -2411,6 +2411,64 @@ ovnact_check_pkt_larger_free(struct ovnact_check_pkt_larger *cipl OVS_UNUSED) { } +static void +parse_bind_vport(struct action_context *ctx) +{ + if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) { + return; + } + + if (ctx->lexer->token.type != LEX_T_STRING) { + lexer_error(ctx->lexer, + "bind_vport requires port name to be specified."); + return; + } + + struct ovnact_bind_vport *bind_vp = ovnact_put_BIND_VPORT(ctx->ovnacts); + bind_vp->vport = xstrdup(ctx->lexer->token.s); + lexer_get(ctx->lexer); + lexer_force_match(ctx->lexer, LEX_T_COMMA); + action_parse_field(ctx, 0, false, &bind_vp->vport_parent); + lexer_force_match(ctx->lexer, LEX_T_RPAREN); +} + +static void +format_BIND_VPORT(const struct ovnact_bind_vport *bind_vp, + struct ds *s ) +{ + ds_put_format(s, "bind_vport(\"%s\", ", bind_vp->vport); + expr_field_format(&bind_vp->vport_parent, s); + ds_put_cstr(s, ");"); +} + +static void +encode_BIND_VPORT(const struct ovnact_bind_vport *vp, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + uint32_t vport_key; + if (!ep->lookup_port(ep->aux, vp->vport, &vport_key)) { + return; + } + + const struct arg args[] = { + { expr_resolve_field(&vp->vport_parent), MFF_LOG_INPORT }, + }; + encode_setup_args(args, ARRAY_SIZE(args), ofpacts); + size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_BIND_VPORT, + false, NX_CTLR_NO_METER, + ofpacts); + ofpbuf_put(ofpacts, &vport_key, sizeof(uint32_t)); + encode_finish_controller_op(oc_offset, ofpacts); + encode_restore_args(args, ARRAY_SIZE(args), ofpacts); +} + +static void +ovnact_bind_vport_free(struct ovnact_bind_vport *bp) +{ + free(bp->vport); +} + /* Parses an assignment or exchange or put_dhcp_opts action. */ static void parse_set_action(struct action_context *ctx) @@ -2514,6 +2572,8 @@ parse_action(struct action_context *ctx) parse_LOG(ctx); } else if (lexer_match_id(ctx->lexer, "set_meter")) { parse_set_meter_action(ctx); + } else if (lexer_match_id(ctx->lexer, "bind_vport")) { + parse_bind_vport(ctx); } else { lexer_syntax_error(ctx->lexer, "expecting action"); } diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c index 0f07d80ac..de745d73f 100644 --- a/ovn/lib/ovn-util.c +++ b/ovn/lib/ovn-util.c @@ -326,6 +326,7 @@ static const char *OVN_NB_LSP_TYPES[] = { "router", "vtep", "external", + "virtual", }; bool diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index e6417220f..734ec0d07 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -519,6 +519,35 @@ some additional flow cost for this and the value appears limited. +
  • +

    + If inport is of type 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))
    +        
    + +

    + and applies the action +

    +
    +bind_vport(V, inport);
    +        
    + +

    + 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 - localport ports) that are down. + localport ports) that are down and for logical ports of + type virtual.

  • @@ -588,7 +618,8 @@ nd_na_router {

    These flows are omitted for logical ports (other than router ports or - localport ports) that are down. + localport ports) that are down and for logical ports of + type virtual.

    @@ -2024,6 +2055,33 @@ next; 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. +

    + +
      +
    • + The 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. +
    • +
    +
    @@ -553,6 +578,26 @@ interface, in bits. + + +

    + These options apply when is + virtual. +

    + + + This option represents the virtual IPv4 address. + + + + This options represents a set of logical port names (with in the same + logical switch) which can own the virtual ip configured + in the . All these virtual parents + should add the virtual ip in the + if port security addressed are enabled. + +
    + diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index 2b543c6f5..73d89551a 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "2.3.0", - "cksum": "3092285199 17409", + "version": "2.4.0", + "cksum": "2392204563 17536", "tables": { "SB_Global": { "columns": { @@ -173,6 +173,8 @@ "minInteger": 1, "maxInteger": 4095}, "min": 0, "max": 1}}, + "virtual_parent": {"type": {"key": "string", "min": 0, + "max": 1}}, "chassis": {"type": {"key": {"type": "uuid", "refTable": "Chassis", "refType": "weak"}, diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 1a2bc1da9..4cb12d599 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1968,6 +1968,24 @@ tcp.flags = RST;

    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. +

    +
    @@ -2431,6 +2449,13 @@ tcp.flags = RST; the outport will be reset to the value of the distributed port. + +
    virtual
    +
    + Represents a logical port with an virtual ip. + This virtual ip can be configured on a + logical port (which is refered as virtual parent). +
    @@ -2671,6 +2696,27 @@ tcp.flags = RST; + + +

    + 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)) {