On Fri, Jul 19, 2019 at 12:53 AM Numan Siddique <nusid...@redhat.com> wrote:
> > > On Thu, Jul 18, 2019 at 11:20 PM Guru Shetty <g...@ovn.org> wrote: > >> >> >> On Thu, 18 Jul 2019 at 07:58, <nusid...@redhat.com> wrote: >> >>> From: Numan Siddique <nusid...@redhat.com> >>> >>> 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" && !is_chassis_resident("sw0-vip") && >>> ((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" && !is_chassis_resident("sw0-vip") && >>> ((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. >>> >>> Co-authored-by: Ben Pfaff <b...@ovn.org> >>> >> Hi Ben, I will be submitting this patch targeting the new OVN repo tomorrow. I will put your signed-off-by tag since I have co authored you in this patch. I hope that's fine. Thanks Numan > Signed-off-by: Numan Siddique <nusid...@redhat.com> >>> >> >> Thanks for answering the questions. Looks like Ben already looked at the >> code styling etc. I only looked at the logic. It felt like the right way >> would have been to update NB with the active backend. But you probably >> don't get that information. So, it looks okay to solve it this way. But >> openstack will likely keep adding more things and you will likely have to >> keep adding more such new types of logical ports. >> > > Thanks for the review. I agree. I hope this will be the last patch to > address open stack specific ones :). > > Although we may have a problem when CMS keeps updating the VIP with the > active backend. > There could be a mac_binding entry for the VIP in south db and when the > VIP moves from one logical port to other, > we may resolve the wrong or old mac for the VIP. > > We have to some how delete stale mac_Binding table entries. There is > already a thread started by Daniel on this topic. > > Thanks > Numan > > > >> >> Acked-by: Gurucharan Shetty <g...@ovn.org> >> >> >>> --- >>> v8 -> v9 >>> ======= >>> * Added entry in NEWS. >>> >>> v7 -> v8 >>> ======= >>> * Applied the code suggestions from Ben. >>> >>> v6 -> v7 >>> ======== >>> * Resolved merge conflicts. >>> >>> v5 -> v6 >>> ======== >>> * Resolved conflicts after rebasing to latest master in tests/ovn.at >>> >>> v4 -> v5 >>> ======= >>> * Rebased to master to resolve merge conflicts. >>> >>> v3 -> v4 >>> ======= >>> * Addressed the review comment and removed the code in northd which >>> referenced the Southbound db state while adding the logical flows. >>> Instead >>> using the ovn match - is_chassis_resident() - which I should have >>> used >>> it in the first place. >>> >>> 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. >>> >>> >>> NEWS | 1 + >>> include/ovn/actions.h | 18 ++- >>> ovn/controller/binding.c | 30 +++- >>> ovn/controller/pinctrl.c | 174 ++++++++++++++++++++ >>> ovn/lib/actions.c | 59 +++++++ >>> ovn/lib/ovn-util.c | 1 + >>> ovn/northd/ovn-northd.8.xml | 61 ++++++- >>> ovn/northd/ovn-northd.c | 306 +++++++++++++++++++++++++++--------- >>> ovn/ovn-nb.xml | 45 ++++++ >>> ovn/ovn-sb.ovsschema | 6 +- >>> ovn/ovn-sb.xml | 46 ++++++ >>> ovn/utilities/ovn-trace.c | 3 + >>> tests/ovn.at | 290 ++++++++++++++++++++++++++++++++++ >>> tests/test-ovn.c | 1 + >>> 14 files changed, 954 insertions(+), 87 deletions(-) >>> >>> diff --git a/NEWS b/NEWS >>> index feae994e8..c2698d2e3 100644 >>> --- a/NEWS >>> +++ b/NEWS >>> @@ -55,6 +55,7 @@ Post-v2.11.0 >>> logical groups which results in tunnels only been formed between >>> members of the same transport zone(s). >>> * Support for IGMP Snooping and IGMP Querier. >>> + * Support for new logical switch port type - 'virtual'. >>> - New QoS type "linux-netem" on Linux. >>> - Added support for TLS Server Name Indication (SNI). >>> - Linux datapath: >>> diff --git a/include/ovn/actions.h b/include/ovn/actions.h >>> index 63d3907d8..0ca06537c 100644 >>> --- a/include/ovn/actions.h >>> +++ b/include/ovn/actions.h >>> @@ -85,7 +85,8 @@ struct ovn_extend_table; >>> OVNACT(SET_METER, ovnact_set_meter) \ >>> OVNACT(OVNFIELD_LOAD, ovnact_load) \ >>> OVNACT(CHECK_PKT_LARGER, ovnact_check_pkt_larger) \ >>> - OVNACT(TRIGGER_EVENT, ovnact_controller_event) >>> + OVNACT(TRIGGER_EVENT, ovnact_controller_event) \ >>> + OVNACT(BIND_VPORT, ovnact_bind_vport) >>> >>> /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ >>> enum OVS_PACKED_ENUM ovnact_type { >>> @@ -328,6 +329,13 @@ struct ovnact_controller_event { >>> size_t n_options; >>> }; >>> >>> +/* 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); >>> @@ -505,6 +513,14 @@ enum action_opcode { >>> * Snoop IGMP, learn the multicast participants >>> */ >>> ACTION_OPCODE_IGMP, >>> + >>> + /* "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 ace0f811b..dfe002b60 100644 >>> --- a/ovn/controller/binding.c >>> +++ b/ovn/controller/binding.c >>> @@ -571,11 +571,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 d857067a5..357050eb5 100644 >>> --- a/ovn/controller/pinctrl.c >>> +++ b/ovn/controller/pinctrl.c >>> @@ -273,9 +273,22 @@ static void pinctrl_ip_mcast_handle_igmp( >>> >>> 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_controller_event); >>> +COVERAGE_DEFINE(pinctrl_drop_put_vport_binding); >>> >>> struct empty_lb_backends_event { >>> struct hmap_node hmap_node; >>> @@ -432,6 +445,7 @@ pinctrl_init(void) >>> init_buffered_packets_map(); >>> init_event_table(); >>> ip_mcast_snoop_init(); >>> + init_put_vport_bindings(); >>> pinctrl.br_int_name = NULL; >>> pinctrl_handler_seq = seq_create(); >>> pinctrl_main_seq = seq_create(); >>> @@ -1957,6 +1971,12 @@ process_packet_in(struct rconn *swconn, const >>> struct ofp_header *msg) >>> ovs_mutex_unlock(&pinctrl_mutex); >>> 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)); >>> @@ -2135,6 +2155,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); >>> @@ -2481,6 +2503,7 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn) >>> { >>> wait_put_mac_bindings(ovnsb_idl_txn); >>> wait_controller_event(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); >>> } >>> @@ -2498,6 +2521,7 @@ pinctrl_destroy(void) >>> destroy_buffered_packets_map(); >>> event_table_destroy(); >>> destroy_put_mac_bindings(); >>> + destroy_put_vport_bindings(); >>> destroy_dns_cache(); >>> ip_mcast_snoop_destroy(); >>> seq_destroy(pinctrl_main_seq); >>> @@ -4341,3 +4365,153 @@ pinctrl_handle_event(struct ofpbuf *userdata) >>> return; >>> } >>> } >>> + >>> +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 4eacc44ed..66916a837 100644 >>> --- a/ovn/lib/actions.c >>> +++ b/ovn/lib/actions.c >>> @@ -2599,6 +2599,63 @@ 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_syntax_error(ctx->lexer, "expecting port name string"); >>> + 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); >>> + (void) (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) >>> @@ -2706,6 +2763,8 @@ parse_action(struct action_context *ctx) >>> parse_set_meter_action(ctx); >>> } else if (lexer_match_id(ctx->lexer, "trigger_event")) { >>> parse_trigger_event(ctx, >>> ovnact_put_TRIGGER_EVENT(ctx->ovnacts)); >>> + } 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 d2267de0e..6ff7aaff1 100644 >>> --- a/ovn/northd/ovn-northd.8.xml >>> +++ b/ovn/northd/ovn-northd.8.xml >>> @@ -519,6 +519,34 @@ >>> some additional flow cost for this and the value appears >>> limited. >>> </li> >>> >>> + <li> >>> + <p> >>> + If inport <code>V</code> is of type <code>virtual</code> adds >>> a >>> + priority-100 logical flow for each <var>P</var> configured in >>> the >>> + <ref table="Logical_Switch_Port" >>> column="options:virtual-parents"/> >>> + column with the match >>> + </p> >>> + <pre> >>> +<code>inport == <var>P</var> && >>> !is_chassis_resident(<var>V</var>) && ((arp.op == 1 && >>> arp.spa == <var>VIP</var> && arp.tpa == <var>VIP</var>) || (arp.op >>> == 2 && arp.spa == <var>VIP</var>))</code> >>> + </pre> >>> + >>> + <p> >>> + and applies the action >>> + </p> >>> + <pre> >>> +<code>bind_vport(<var>V</var>, inport);</code> >>> + </pre> >>> + >>> + <p> >>> + and advances the packet to the next table. >>> + </p> >>> + >>> + <p> >>> + Where <var>VIP</var> is the virtual ip configured in the >>> column >>> + <ref table="Logical_Switch_Port" >>> column="options:virtual-ip"/>. >>> + </p> >>> + </li> >>> + >>> <li> >>> <p> >>> Priority-50 flows that match ARP requests to each known IP >>> address >>> @@ -541,7 +569,8 @@ output; >>> >>> <p> >>> These flows are omitted for logical ports (other than router >>> ports or >>> - <code>localport</code> ports) that are down. >>> + <code>localport</code> ports) that are down and for logical >>> ports of >>> + type <code>virtual</code>. >>> </p> >>> </li> >>> >>> @@ -588,7 +617,8 @@ nd_na_router { >>> >>> <p> >>> These flows are omitted for logical ports (other than router >>> ports or >>> - <code>localport</code> ports) that are down. >>> + <code>localport</code> ports) that are down and for logical >>> ports of >>> + type <code>virtual</code>. >>> </p> >>> </li> >>> >>> @@ -2031,6 +2061,33 @@ next; >>> <code>eth.dst = <var>E</var>; next;</code>. >>> </p> >>> >>> + <p> >>> + For each virtual ip <var>A</var> configured on a logical port >>> + of type <code>virtual</code> and its virtual parent set in >>> + its corresponding <ref db="OVN_Southbound" >>> table="Port_Binding"/> >>> + record and the virtual parent with the Ethernet address >>> <var>E</var> >>> + and the virtual ip is reachable via the router port >>> <var>P</var>, a >>> + priority-100 flow with match <code>outport === <var>P</var> >>> + && reg0 == <var>A</var></code> has actions >>> + <code>eth.dst = <var>E</var>; next;</code>. >>> + </p> >>> + >>> + <p> >>> + For each virtual ip <var>A</var> configured on a logical port >>> + of type <code>virtual</code> and its virtual parent >>> <code>not</code> >>> + set in its corresponding >>> + <ref db="OVN_Southbound" table="Port_Binding"/> >>> + record and the virtual ip <var>A</var> is reachable via the >>> + router port <var>P</var>, a >>> + priority-100 flow with match <code>outport === <var>P</var> >>> + && reg0 == <var>A</var></code> has actions >>> + <code>eth.dst = <var>00:00:00:00:00:00</var>; next;</code>. >>> + This flow is added so that the ARP is always resolved for the >>> + virtual ip <var>A</var> by generating ARP request and >>> + <code>not</code> consulting the MAC_Binding table as it can >>> have >>> + incorrect value for the virtual ip <var>A</var>. >>> + </p> >>> + >>> <p> >>> For each IPv6 address <var>A</var> whose host is known to have >>> Ethernet address <var>E</var> on router port <var>P</var>, a >>> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c >>> index eb6c47cad..ae09cf338 100644 >>> --- a/ovn/northd/ovn-northd.c >>> +++ b/ovn/northd/ovn-northd.c >>> @@ -4878,96 +4878,146 @@ 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)) { >>> + 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\" && " >>> + "!is_chassis_resident(%s) && " >>> + "((arp.op == 1 && arp.spa == %s && " >>> + "arp.tpa == %s) || (arp.op == 2 && " >>> + "arp.spa == %s))", >>> + vparent, op->json_key, 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;"); >>> + } >>> } >>> } >>> } >>> @@ -7504,7 +7554,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 >>> @@ -7581,6 +7632,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. >>> */ >>> >>> @@ -9256,6 +9406,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 57b6edbf8..f5f10a5c1 100644 >>> --- a/ovn/ovn-nb.xml >>> +++ b/ovn/ovn-nb.xml >>> @@ -465,6 +465,31 @@ >>> </li> >>> </ul> >>> </dd> >>> + >>> + <dt><code>virtual</code></dt> >>> + <dd> >>> + <p> >>> + Represents a logical port which does not have an OVS >>> + port in the integration bridge and has a virtual ip >>> configured >>> + in the <ref column="options:virtual-ip"/> column. This >>> virtual ip >>> + can move around between the logical ports configured in >>> + the <ref column="options:virtual-parents"/> column. >>> + </p> >>> + >>> + <p> >>> + One of the use case where <code>virtual</code> >>> + ports can be used is. >>> + </p> >>> + >>> + <ul> >>> + <li> >>> + The <code>virtual ip</code> represents a load balancer >>> vip >>> + and the <code>virtual parents</code> provide load >>> balancer >>> + service in an active-standby setup with the active >>> virtual >>> + parent owning the <code>virtual ip</code>. >>> + </li> >>> + </ul> >>> + </dd> >>> </dl> >>> </column> >>> </group> >>> @@ -618,6 +643,26 @@ >>> interface, in bits. >>> </column> >>> </group> >>> + >>> + <group title="Virtual port Options"> >>> + <p> >>> + These options apply when <ref column="type"/> is >>> + <code>virtual</code>. >>> + </p> >>> + >>> + <column name="options" key="virtual-ip"> >>> + This option represents the virtual IPv4 address. >>> + </column> >>> + >>> + <column name="options" key="virtual-parents"> >>> + This options represents a set of logical port names (with in >>> the same >>> + logical switch) which can own the <code>virtual ip</code> >>> configured >>> + in the <ref column="options:virtual-ip"/>. All these virtual >>> parents >>> + should add the <code>virtual ip</code> in the >>> + <ref column="port_security"/> if port security addressed are >>> enabled. >>> + </column> >>> + </group> >>> + >>> </group> >>> >>> <group title="Containers"> >>> diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema >>> index 2b7bc57a7..5c013b17e 100644 >>> --- a/ovn/ovn-sb.ovsschema >>> +++ b/ovn/ovn-sb.ovsschema >>> @@ -1,7 +1,7 @@ >>> { >>> "name": "OVN_Southbound", >>> - "version": "2.4.0", >>> - "cksum": "3059284885 20260", >>> + "version": "2.5.0", >>> + "cksum": "1257419092 20387", >>> "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 544a071fa..17c45bbac 100644 >>> --- a/ovn/ovn-sb.xml >>> +++ b/ovn/ovn-sb.xml >>> @@ -2017,6 +2017,24 @@ tcp.flags = RST; >>> </p> >>> <p><b>Prerequisite:</b> <code>igmp</code></p> >>> </dd> >>> + >>> + <dt><code>bind_vport(<var>V</var>, <var>P</var>);</code></dt> >>> + <dd> >>> + <p> >>> + <b>Parameters</b>: logical port string field <var>V</var> >>> + of type <code>virtual</code>, logical port string field >>> + <var>P</var>. >>> + </p> >>> + >>> + <p> >>> + Binds the virtual logical port <var>V</var> and sets the >>> + <ref table="Port_Binding" column="chassis"/> column and >>> + <ref table="Port_Binding" column="virtual_parent"/> of >>> + the table <ref table="Port_Binding"/>. >>> + <ref table="Port_Binding" column="virtual_parent"/> is >>> + set to <var>P</var>. >>> + </p> >>> + </dd> >>> </dl> >>> </column> >>> >>> @@ -2480,6 +2498,13 @@ tcp.flags = RST; >>> the <code>outport</code> will be reset to the value of the >>> distributed port. >>> </dd> >>> + >>> + <dt><code>virtual</code></dt> >>> + <dd> >>> + Represents a logical port with an <code>virtual ip</code>. >>> + This <code>virtual ip</code> can be configured on a >>> + logical port (which is refered as virtual parent). >>> + </dd> >>> </dl> >>> </column> >>> </group> >>> @@ -2720,6 +2745,27 @@ tcp.flags = RST; >>> </column> >>> </group> >>> >>> + <group title="Virtual ports"> >>> + <column name="virtual_parent"> >>> + <p> >>> + This column is set by <code>ovn-controller</code> with one of >>> the >>> + value from the >>> + <ref table="Logical_Switch_Port" >>> column="options:virtual-parents" >>> + db="OVN_Northbound"/> in the OVN_Northbound database's >>> + <ref table="Logical_Switch_Port" db="OVN_Northbound"/> table >>> + when the OVN action <code>bind_vport</code> is executed. >>> + <code>ovn-controller</code> also sets the >>> + <ref column="chassis"/> column when it executes this action >>> + with its chassis id. >>> + </p> >>> + >>> + <p> >>> + <code>ovn-controller</code> sets this column only if the >>> + <ref column="type"/> is "virtual". >>> + </p> >>> + </column> >>> + </group> >>> + >>> <group title="Naming"> >>> <column name="external_ids" key="name"> >>> <p> >>> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c >>> index 044eb1cc2..b532b8eaf 100644 >>> --- a/ovn/utilities/ovn-trace.c >>> +++ b/ovn/utilities/ovn-trace.c >>> @@ -2144,6 +2144,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 cb380d275..5d6c90c5f 100644 >>> --- a/tests/ovn.at >>> +++ b/tests/ovn.at >>> @@ -1368,6 +1368,24 @@ 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.11.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 >>> +bind_vport; >>> + Syntax error at `;' expecting `('. >>> +bind_vport(; >>> + Syntax error at `;' expecting port name string. >>> +bind_vport("xyzzy"; >>> + Syntax error at `;' expecting `,'. >>> +bind_vport("xyzzy",; >>> + Syntax error at `;' expecting field name. >>> +bind_vport("xyzzy", inport; >>> + Syntax error at `;' expecting `)'. >>> + >>> # Miscellaneous negative tests. >>> ; >>> Syntax error at `;'. >>> @@ -14345,6 +14363,278 @@ OVN_CLEANUP([hv1],[hv2]) >>> >>> 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" && !is_chassis_resident("sw0-vir") && ((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" && !is_chassis_resident("sw0-vir") && ((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 >>> + >>> +# Since the sw0-vir is not claimed by any chassis, eth.dst should be >>> set to >>> +# zero if the ip4.dst is the virtual ip in the router pipeline. >>> +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 lr0 | grep lr_in_arp_resolve | grep "reg0 == >>> 10.0.0.10" \ >>> +> lflows.txt >>> + >>> +# There should be an arp resolve flow to resolve the virtual_ip with the >>> +# sw0-p1's MAC. >>> +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 lr0 | grep lr_in_arp_resolve | grep "reg0 == >>> 10.0.0.10" \ >>> +> lflows.txt >>> + >>> +# There should be an arp resolve flow to resolve the virtual_ip with the >>> +# sw0-p2's MAC. >>> +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 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 >>> +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]) >>> + >>> +# Since the sw0-vir is not claimed by any chassis, eth.dst should be >>> set to >>> +# zero if the ip4.dst is the virtual ip. >>> +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 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" && !is_chassis_resident("sw0-vir") && ((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 >>> + >>> # Run ovn-nbctl in daemon mode, change to a backup database and verify >>> that >>> # an insert operation is not allowed. >>> AT_SETUP([ovn -- can't write to a backup database server instance]) >>> diff --git a/tests/test-ovn.c b/tests/test-ovn.c >>> index 0b9e8246e..cf1bc5432 100644 >>> --- a/tests/test-ovn.c >>> +++ b/tests/test-ovn.c >>> @@ -1253,6 +1253,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)) { >>> -- >>> 2.21.0 >>> >>> _______________________________________________ >>> dev mailing list >>> d...@openvswitch.org >>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >>> >> _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev