On 9/23/25 8:40 AM, Ales Musil wrote:
> On Mon, Sep 22, 2025 at 4:27 PM Dumitru Ceara via dev <
> [email protected]> wrote:
> 
>> This closes the gap with the static routes support and allows users to
>> explicitly break the tie for cases that are ambiguous, e.g., when the
>> reroute next hop IP could be theoretically reachable on multiple
>> connected router ports.  An initial proposal tried to solve this problem
>> in a different way [0] but upon further discussions [1] we agreed to match
>> the support and static routes currently offer.
>>
>> The NB router policy schema is extended to include an "output_port"
>> optional field which will be used by the CMS to explicitly determine
>> which router port to be used when the reroute policy is applied.  This
>> overrides any automatic port detection that happens for policies without
>> an "output_port" explicitly specified.
>>
>> As mentioned in the documentation and in the TODO.rst file, the support
>> is limited to non-ECMP policies.  That's mainly because the current
>> router policy schema makes it extremely cumbersome to attach different
>> ports to different next hops that may be defined for the policy.  Unlike
>> NB static routes, the router policies store the ECMP nexthops as
>> comma-separated strings in the NB policy record.
>>
>> [0] https://mail.openvswitch.org/pipermail/ovs-dev/2025-July/424894.html
>> [1]
>> https://mail.openvswitch.org/pipermail/ovs-dev/2025-September/426231.html
>>
>> Reported-at: https://issues.redhat.com/browse/FDP-1554
>> Reported-by: Jakub Libosvar <[email protected]>
>> Signed-off-by: Dumitru Ceara <[email protected]>
>> ---
>>
> 
> Hi Dumitru,
> 

Hi Ales,

> thank you for the patch. I have two small nits below.
> 

Thanks for the review!

>  NEWS                           |   1 +
>>  TODO.rst                       |   6 ++
>>  northd/en-learned-route-sync.c |   2 +-
>>  northd/northd.c                | 137 +++++++++++++++++++++------------
>>  northd/northd.h                |   3 +-
>>  ovn-nb.ovsschema               |  10 ++-
>>  ovn-nb.xml                     |  17 ++++
>>  tests/ovn-nbctl.at             |  17 ++++
>>  tests/ovn-northd.at            |  45 +++++++++++
>>  tests/system-ovn.at            |  98 +++++++++++++++++++++++
>>  utilities/ovn-nbctl.c          |  33 +++++++-
>>  11 files changed, 313 insertions(+), 56 deletions(-)
>>
>> diff --git a/NEWS b/NEWS
>> index 4d0c45e4dc..ede89aea35 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -2,6 +2,7 @@ Post v25.09.0
>>  -------------
>>     - Added disable_garp_rarp option to logical_router table in order to
>> disable
>>       GARP/RARP announcements by all the peer ports of this logical router.
>> +   - Support for specifying output_port for logical router reroute
>> policies.
>>
>>  OVN v25.09.0 - xxx xx xxxx
>>  --------------------------
>> diff --git a/TODO.rst b/TODO.rst
>> index cda5f0d996..b992b8e5ae 100644
>> --- a/TODO.rst
>> +++ b/TODO.rst
>> @@ -171,6 +171,12 @@ OVN To-do List
>>      allow for the eventual removal of the ovn\_datapath structure from the
>>      codebase.
>>
>> +* Logical Router Policies
>> +
>> +  * Add support for configuring output\_port for reroute router policies
>> that
>> +    have more than one nexthop (ECMP).  This probably requires a redesign
>> of
>> +    the Northbound Logical_Router_Policy database schema.
>> +
>>  * CI
>>
>>    * ovn-kubernetes: Only a subset of the ovn-kubernetes features is
>> currently
>> diff --git a/northd/en-learned-route-sync.c
>> b/northd/en-learned-route-sync.c
>> index 7ba43ec10a..c8e5ade0b4 100644
>> --- a/northd/en-learned-route-sync.c
>> +++ b/northd/en-learned-route-sync.c
>> @@ -192,7 +192,7 @@ parse_route_from_sbrec_route(struct hmap
>> *parsed_routes_out,
>>      const char *lrp_addr_s = NULL;
>>      struct ovn_port *out_port = NULL;
>>      if (!find_route_outport(lr_ports, route->logical_port->logical_port,
>> -                            route->ip_prefix, route->nexthop,
>> +                            "static route", route->ip_prefix,
>> route->nexthop,
>>                              IN6_IS_ADDR_V4MAPPED(&prefix),
>>                              false,
>>                              &out_port, &lrp_addr_s)) {
>> diff --git a/northd/northd.c b/northd/northd.c
>> index 23a4c0784f..e83aacc57a 100644
>> --- a/northd/northd.c
>> +++ b/northd/northd.c
>> @@ -10754,29 +10754,59 @@ lrp_find_member_ip(const struct ovn_port *op,
>> const char *ip_s)
>>      return find_lport_address(&op->lrp_networks, ip_s);
>>  }
>>
>> -static struct ovn_port*
>> -get_outport_for_routing_policy_nexthop(struct ovn_datapath *od,
>> -                                       const struct hmap *lr_ports,
>> -                                       int priority, const char *nexthop)
>> +/* Returns true if the output port to be used for forwarding traffic
>> through
>> + * this policy could be determined.  Stores a pointer to the output port
>> + * in 'p_output_port' and a pointer to the router IP address to be used
>> for
>> + * this policy, in 'p_lrp_addr_s'. */
>> +static bool
>> +find_policy_outport(struct ovn_datapath *od, const struct hmap *lr_ports,
>> +                    const struct nbrec_logical_router_policy *policy,
>> +                    const char *nexthop, bool is_ipv4,
>> +                    const char **p_lrp_addr_s, struct ovn_port
>> **p_out_port)
>>  {
>>      if (nexthop == NULL) {
>>          return NULL;
>>
> 
> nit: The signature changed to bool, it should return false.
> 
> 
>>      }
>>
>> -    /* Find the router port matching the next hop. */
>> -    for (int i = 0; i < od->nbr->n_ports; i++) {
>> -       struct nbrec_logical_router_port *lrp = od->nbr->ports[i];
>> +    struct ovn_port *out_port = NULL;
>> +    const char *lrp_addr_s = NULL;
>>
>> -       struct ovn_port *out_port = ovn_port_find(lr_ports, lrp->name);
>> -       if (out_port && lrp_find_member_ip(out_port, nexthop)) {
>> -           return out_port;
>> -       }
>> +    if (policy->output_port) {
>> +        if (!find_route_outport(lr_ports, policy->output_port->name,
>> +                                "policy", policy->match,
>> +                                nexthop, is_ipv4, true, &out_port,
>> +                                &lrp_addr_s)) {
>> +            return NULL;
>>
> 
> nit: Same here it should return false.
> 
> 

Oops, good catch, thanks!

>> +        }
>> +    } else {
>> +        /* If output_port is not specified, find the router port matching
>> +         * the next hop. */
>> +        HMAP_FOR_EACH (out_port, dp_node, &od->ports) {
>> +            lrp_addr_s = lrp_find_member_ip(out_port, nexthop);
>> +            if (lrp_addr_s) {
>> +                break;
>> +            }
>> +        }
>>      }
>>
>> -    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
>> -    VLOG_WARN_RL(&rl, "No path for routing policy priority %d on router
>> %s; "
>> -                 "next hop %s", priority, od->nbr->name, nexthop);
>> -    return NULL;
>> +    if (!out_port || !lrp_addr_s) {
>> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
>> +        VLOG_WARN_RL(&rl, "Logical Router: %s, policy "
>> +                          "(chain: '%s', match: '%s', priority
>> %"PRId64"): "
>> +                          "no path for next hop %s",
>> +                     od->nbr->name,
>> +                     policy->chain ? policy->chain : "<Default>",
>> +                     policy->match, policy->priority, nexthop);
>> +        return false;
>> +    }
>> +    if (p_out_port) {
>> +        *p_out_port = out_port;
>> +    }
>> +    if (p_lrp_addr_s) {
>> +        *p_lrp_addr_s = lrp_addr_s;
>> +    }
>> +
>> +    return true;
>>  }
>>
>>  static bool check_bfd_state(const struct nbrec_logical_router_policy
>> *rule,
>> @@ -10852,18 +10882,12 @@ build_routing_policy_flow(struct lflow_table
>> *lflows, struct ovn_datapath *od,
>>          }
>>
>>          char *nexthop = rp->valid_nexthops[0];
>> -        struct ovn_port *out_port =
>> get_outport_for_routing_policy_nexthop(
>> -             od, lr_ports, rule->priority, nexthop);
>> -        if (!out_port) {
>> -            return;
>> -        }
>> +        bool is_ipv4 = strchr(nexthop, '.') ? true : false;
>> +        const char *lrp_addr_s = NULL;
>> +        struct ovn_port *out_port = NULL;
>>
>> -        const char *lrp_addr_s = lrp_find_member_ip(out_port, nexthop);
>> -        if (!lrp_addr_s) {
>> -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
>> -            VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy "
>> -                         " priority %"PRId64" nexthop %s",
>> -                         rule->priority, nexthop);
>> +        if (!find_policy_outport(od, lr_ports, rule, nexthop, is_ipv4,
>> +                                 &lrp_addr_s, &out_port)) {
>>              return;
>>          }
>>
>> @@ -10872,7 +10896,6 @@ build_routing_policy_flow(struct lflow_table
>> *lflows, struct ovn_datapath *od,
>>              ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark);
>>          }
>>
>> -        bool is_ipv4 = strchr(nexthop, '.') ? true : false;
>>          ds_put_format(&actions, "%s = %s; "
>>                        "%s = %s; "
>>                        "eth.src = %s; "
>> @@ -10952,19 +10975,12 @@ build_ecmp_routing_policy_flows(struct
>> lflow_table *lflows,
>>      struct ds actions = DS_EMPTY_INITIALIZER;
>>
>>      for (size_t i = 0; i < rp->n_valid_nexthops; i++) {
>> -        struct ovn_port *out_port =
>> get_outport_for_routing_policy_nexthop(
>> -             od, lr_ports, rule->priority, rp->valid_nexthops[i]);
>> -        if (!out_port) {
>> -            goto cleanup;
>> -        }
>> +        bool is_ipv4 = strchr(rp->valid_nexthops[i], '.') ? true : false;
>> +        const char *lrp_addr_s = NULL;
>> +        struct ovn_port *out_port = NULL;
>>
>> -        const char *lrp_addr_s =
>> -            lrp_find_member_ip(out_port, rp->valid_nexthops[i]);
>> -        if (!lrp_addr_s) {
>> -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
>> -            VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy "
>> -                            " priority %"PRId64" nexthop %s",
>> -                            rule->priority, rp->valid_nexthops[i]);
>> +        if (!find_policy_outport(od, lr_ports, rule,
>> rp->valid_nexthops[i],
>> +                                 is_ipv4, &lrp_addr_s, &out_port)) {
>>              goto cleanup;
>>          }
>>
>> @@ -10974,7 +10990,6 @@ build_ecmp_routing_policy_flows(struct lflow_table
>> *lflows,
>>              ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark);
>>          }
>>
>> -        bool is_ipv4 = strchr(rp->valid_nexthops[i], '.') ? true : false;
>>
>>          ds_put_format(&actions, "%s = %s; "
>>                        "%s = %s; "
>> @@ -11571,15 +11586,16 @@ build_route_match(const struct ovn_port
>> *op_inport, uint32_t rtb_id,
>>
>>  bool
>>  find_route_outport(const struct hmap *lr_ports, const char *output_port,
>> -                   const char *ip_prefix, const char *nexthop, bool
>> is_ipv4,
>> +                   const char *route_type, const char *route_desc,
>> +                   const char *nexthop, bool is_ipv4,
>>                     bool force_out_port,
>>                     struct ovn_port **out_port, const char **lrp_addr_s)
>>  {
>>      *out_port = ovn_port_find(lr_ports, output_port);
>>      if (!*out_port) {
>>          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
>> -        VLOG_WARN_RL(&rl, "Bad out port %s for static route %s",
>> -                     output_port, ip_prefix);
>> +        VLOG_WARN_RL(&rl, "Bad out port %s for %s %s",
>> +                     output_port, route_type, route_desc);
>>          return false;
>>      }
>>      if (nexthop[0]) {
>> @@ -11619,8 +11635,10 @@ find_static_route_outport(const struct
>> ovn_datapath *od,
>>      struct ovn_port *out_port = NULL;
>>      if (route->output_port) {
>>          /* XXX: we should be able to use &od->ports instead of lr_ports.
>> */
>> -        if (!find_route_outport(lr_ports, route->output_port,
>> route->ip_prefix,
>> -              route->nexthop, is_ipv4, true, &out_port, &lrp_addr_s)) {
>> +        if (!find_route_outport(lr_ports, route->output_port,
>> +                                "static route", route->ip_prefix,
>> +                                route->nexthop, is_ipv4, true, &out_port,
>> +                                &lrp_addr_s)) {
>>              return false;
>>          }
>>      } else {
>> @@ -14217,16 +14235,33 @@ build_route_policies(struct ovn_datapath *od,
>> const struct hmap *lr_ports,
>>          if (!strcmp(rule->action, "reroute")) {
>>              size_t n_nexthops = rule->n_nexthops ? rule->n_nexthops : 1;
>>
>> +            if (rule->output_port && n_nexthops != 1) {
>> +                static struct vlog_rate_limit rl =
>> VLOG_RATE_LIMIT_INIT(5, 1);
>> +                VLOG_WARN_RL(&rl,
>> +                             "Logical router: %s, policy "
>> +                             "(chain: '%s', match: '%s', priority
>> %"PRId64"): "
>> +                             "output_port only supported on non-ECMP "
>> +                             "reroute policies",
>> +                             od->nbr->name,
>> +                             rule->chain ? rule->chain : "<Default>",
>> +                             rule->match, rule->priority);
>> +                continue;
>> +            }
>> +
>>              valid_nexthops = xcalloc(n_nexthops, sizeof *valid_nexthops);
>>              for (size_t j = 0; j < n_nexthops; j++) {
>>                  char *nexthop = rule->n_nexthops
>>                      ? rule->nexthops[j] : rule->nexthop;
>> -                struct ovn_port *out_port =
>> -                    get_outport_for_routing_policy_nexthop(
>> -                            od, lr_ports, rule->priority, nexthop);
>> -                if (!out_port || !check_bfd_state(rule, out_port, nexthop,
>> -                                                  bfd_connections,
>> -
>> bfd_active_connections)) {
>> +                struct ovn_port *out_port = NULL;
>> +                bool is_ipv4 = strchr(nexthop, '.') ? true : false;
>> +
>> +                if (!find_policy_outport(od, lr_ports, rule, nexthop,
>> is_ipv4,
>> +                                         NULL, &out_port)) {
>> +                    continue;
>> +                }
>> +                if (!check_bfd_state(rule, out_port, nexthop,
>> +                                     bfd_connections,
>> +                                     bfd_active_connections)) {
>>                      continue;
>>                  }
>>                  valid_nexthops[n_valid_nexthops++] = nexthop;
>> diff --git a/northd/northd.h b/northd/northd.h
>> index ca35eb91e4..0348a3234f 100644
>> --- a/northd/northd.h
>> +++ b/northd/northd.h
>> @@ -855,7 +855,8 @@ struct svc_monitors_map_data {
>>
>>  bool
>>  find_route_outport(const struct hmap *lr_ports, const char *output_port,
>> -                   const char *ip_prefix, const char *nexthop, bool
>> is_ipv4,
>> +                   const char *route_type, const char *route_desc,
>> +                   const char *nexthop, bool is_ipv4,
>>                     bool force_out_port,
>>                     struct ovn_port **out_port, const char **lrp_addr_s);
>>
>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>> index f55930a2e5..cfb1f5f7dc 100644
>> --- a/ovn-nb.ovsschema
>> +++ b/ovn-nb.ovsschema
>> @@ -1,7 +1,7 @@
>>  {
>>      "name": "OVN_Northbound",
>> -    "version": "7.12.0",
>> -    "cksum": "2749576410 39903",
>> +    "version": "7.13.0",
>> +    "cksum": "296941325 40198",
>>      "tables": {
>>          "NB_Global": {
>>              "columns": {
>> @@ -555,6 +555,12 @@
>>                  "nexthop": {"type": {"key": "string", "min": 0, "max":
>> 1}},
>>                  "nexthops": {"type": {
>>                      "key": "string", "min": 0, "max": "unlimited"}},
>> +                "output_port": {
>> +                    "type": {"key": {"type": "uuid",
>> +                                     "refTable": "Logical_Router_Port",
>> +                                     "refType": "weak"},
>> +                             "min": 0,
>> +                             "max": 1}},
>>                  "bfd_sessions": {"type": {"key": {"type": "uuid",
>>                                                    "refTable": "BFD",
>>                                                    "refType": "weak"},
>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>> index 1f5c58490c..c83ec1b7f4 100644
>> --- a/ovn-nb.xml
>> +++ b/ovn-nb.xml
>> @@ -4725,6 +4725,23 @@ or
>>        </p>
>>      </column>
>>
>> +    <column name="output_port">
>> +      <p>
>> +        The <ref table="Logical_Router_Port"/> via which the packet
>> +        needs to be sent out.  This is optional and when not specified,
>> +        OVN will automatically figure this out based on the
>> +        <ref column="nexthops"/> column.  When this is specified and
>> there are
>> +        multiple IP addresses on the router port and none of them are in
>> the
>> +        same subnet of <ref column="nexthops"/>, OVN chooses the first IP
>> +        address as the one via which the <ref column="nexthops"/> are
>> +        reachable.
>> +
>> +        NOTE: for now OVN does not support configuring the output port on
>> +        ECMP reroute policies (with more than one value in the
>> +        <ref column="nexthops"/> column).
>> +      </p>
>> +    </column>
>> +
>>      <column name="bfd_sessions">
>>        <p>
>>          Reference to <ref table="BFD"/> row if the route policy has
>> associated
>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
>> index fe4a984d07..c6edd0f17b 100644
>> --- a/tests/ovn-nbctl.at
>> +++ b/tests/ovn-nbctl.at
>> @@ -2512,6 +2512,21 @@ AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src
>> == 2.1.1.0/24" jump], [1], []
>>    [ovn-nbctl: Chain name is required when action is jump.
>>  ])
>>
>> +AT_CHECK([ovn-nbctl --output-port=lrp0 lr-policy-add lr0 101 "ip4.src ==
>> 3.1.1.0/24" drop], [1], [],
>> +  [ovn-nbctl: Specifying output-port is only allowed for reroute policies.
>> +])
>> +
>> +AT_CHECK([ovn-nbctl --output-port=lrp0 lr-policy-add lr0 101 "ip4.src ==
>> 3.1.1.0/24" reroute 192.168.0.10,192.168.0.11], [1], [],
>> +  [ovn-nbctl: output-port option is supported only for policies with a
>> single reroute next hop
>> +])
>> +
>> +AT_CHECK([ovn-nbctl --output-port=lrp0 lr-policy-add lr0 101 "ip4.src ==
>> 3.1.1.0/24" reroute 192.168.0.10], [1], [],
>> +  [ovn-nbctl: unknown output-port: lrp0
>> +])
>> +
>> +check ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:00:01 192.168.0.1/24
>> +check ovn-nbctl --output-port=lrp0 lr-policy-add lr0 101 "ip4.src ==
>> 3.1.1.0/24" reroute 192.168.0.10
>> +
>>  dnl Add duplicated policy
>>  AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop],
>> [1], [],
>>    [ovn-nbctl: Same routing policy already existed on the logical router
>> lr0.
>> @@ -2532,6 +2547,7 @@ Routing Policies
>>  Chain <Default>:
>>         101                              ip4.src == 2.1.1.0/24
>>  allow
>>         101                              ip4.src == 2.1.2.0/24
>> drop
>> +       101                              ip4.src == 3.1.1.0/24
>>  reroute              192.168.0.10 lrp0
>>         101                               ip6.src == 2002::/64
>> drop
>>         100                              ip4.src == 1.1.2.0/24
>>  allow               pkt_mark=100,foo=bar
>>
>> @@ -2549,6 +2565,7 @@ Routing Policies
>>  Chain <Default>:
>>         101                              ip4.src == 2.1.1.0/24
>>  allow
>>         101                              ip4.src == 2.1.2.0/24
>> drop
>> +       101                              ip4.src == 3.1.1.0/24
>>  reroute              192.168.0.10 lrp0
>>         101                               ip6.src == 2002::/64
>> drop
>>         100                              ip4.src == 1.1.2.0/24
>>  allow               pkt_mark=100,foo=bar
>>
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index 6298222247..83a142d20f 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -3643,6 +3643,39 @@ AT_CHECK([grep "lr_in_policy[[^_]]" lr0flows2 |
>> ovn_strip_lflows | sort], [0], [
>>  AT_CLEANUP
>>  ])
>>
>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> +AT_SETUP([Router policies - output_port])
>> +ovn_start
>> +
>> +dnl Test with a simple non-ECMP reroute policy with an explicit
>> output-port set.
>> +check ovn-nbctl                                                          \
>> +    -- lr-add lr0                                                        \
>> +    -- lrp-add lr0 lrp1 00:00:00:00:00:01 1.1.1.1/24
>>  \
>> +    -- lrp-add lr0 lrp2 00:00:00:00:00:02 2.2.2.1/24
>>  \
>> +    -- --output-port=lrp2 lr-policy-add lr0 100 "ip4.src == 42.42.42.42" \
>> +                                        reroute 3.3.3.3
>> +
>> +dnl Also add an ECMP reroute policy and forcefully set output-port;
>> ovn-northd
>> +dnl should warn and ignore this second policy.
>> +check ovn-nbctl lr-policy-add lr0 200 "ip4.src == 43.43.43.43" reroute
>> 3.3.3.4,3.3.3.5
>> +lrpol=$(fetch_column nb:logical_router_policy _uuid priority=200)
>> +lrp2=$(fetch_column nb:logical_router_port _uuid name=lrp2)
>> +check ovn-nbctl set logical_router_policy $lrpol output_port=$lrp2
>> +check ovn-nbctl --wait=sb sync
>> +
>> +check grep -qE "output_port only supported on non-ECMP reroute policies"
>> northd/ovn-northd.log
>> +
>> +ovn-sbctl dump-flows lr0 > lr0flows
>> +AT_CAPTURE_FILE([lr0flows])
>> +
>> +AT_CHECK([grep "lr_in_policy[[^_]]" lr0flows | ovn_strip_lflows | sort],
>> [0], [dnl
>> +  table=??(lr_in_policy       ), priority=0    , match=(1),
>> action=(reg8[[0..15]] = 0; next;)
>> +  table=??(lr_in_policy       ), priority=100  , match=(ip4.src ==
>> 42.42.42.42), action=(reg0 = 3.3.3.3; reg5 = 2.2.2.1; eth.src =
>> 00:00:00:00:00:02; outport = "lrp2"; flags.loopback = 1; reg8[[0..15]] = 0;
>> reg9[[9]] = 1; next;)
>> +])
>> +
>> +AT_CLEANUP
>> +])
>> +
>>  OVN_FOR_EACH_NORTHD_NO_HV([
>>  AT_SETUP([ACL allow-stateless omit conntrack - Logical_Switch])
>>  ovn_start
>> @@ -12739,6 +12772,18 @@ check_engine_stats sync_to_sb_lb recompute
>> nocompute
>>  check_engine_stats lflow recompute nocompute
>>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>
>> +# Change router Policy to use explicit output port.
>> +lrp_lr0_sw0=$(fetch_column nb:logical_router_port _uuid name=lr0-sw0)
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb set logical_router_policy .
>> output_port=$lrp_lr0_sw0
>> +check_engine_stats northd recompute nocompute
>> +check_engine_stats lr_nat recompute nocompute
>> +check_engine_stats lr_stateful recompute nocompute
>> +check_engine_stats sync_to_sb_pb recompute nocompute
>> +check_engine_stats sync_to_sb_lb recompute nocompute
>> +check_engine_stats lflow recompute nocompute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>>  check ovn-nbctl --wait=sb lr-policy-del lr0  10 "ip4.src == 10.0.0.3"
>>  check_engine_stats northd recompute nocompute
>> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
>> index b2319b1801..e5d97bf152 100644
>> --- a/tests/system-ovn.at
>> +++ b/tests/system-ovn.at
>> @@ -18485,3 +18485,101 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query
>> port patch-.*/d
>>  /connection dropped.*/d"])
>>  AT_CLEANUP
>>  ])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Router reroute policies - output port])
>> +AT_SKIP_IF([test $HAVE_NC = no])
>> +
>> +ovn_start
>> +OVS_TRAFFIC_VSWITCHD_START()
>> +ADD_BR([br-int])
>> +
>> +check ovs-vsctl \
>> +    -- set Open_vSwitch . external-ids:system-id=hv1 \
>> +    -- set Open_vSwitch .
>> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
>> +    -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
>> +    -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
>> +    -- set bridge br-int fail-mode=secure
>> other-config:disable-in-band=true
>> +start_daemon ovn-controller
>> +
>> +dnl Logical network:
>> +dnl                        +--- LR2  --- LS2 --- VM2 (20.0.0.1)
>> +dnl                        |
>> +dnl vm1 -- LS1 --- LR1 --- +
>> +dnl                        +--- LR3  --- LS3 --- VM3
>> +dnl
>> +dnl - Link-Local only connections between LR1 and LR2 and between LR1 and
>> LR3.
>> +dnl - LR1 with a default route via LR2's LLA
>> +dnl - LR1 with a routing policy to reroute traffic from vm1 via LR3's LLA
>> +dnl   and, to avoid ambiguity, with output-port set to be the port
>> towards LR3.
>> +dnl
>> +check ovn-nbctl                                                    \
>> +    -- lr-add lr1                                                  \
>> +       -- lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/24        \
>> +       -- lrp-add lr1 lr1-lr2 00:00:00:00:00:02 peer=lr2-lr1       \
>> +       -- lrp-add lr1 lr1-lr3 00:00:00:00:00:03 peer=lr3-lr1       \
>> +       -- lr-route-add lr1 0.0.0.0/0 fe80::200:ff:fe00:200         \
>> +       -- --output-port=lr1-lr3 lr-policy-add lr1 100              \
>> +                                "ip4.src == 10.0.0.2"              \
>> +                                reroute fe80::200:ff:fe00:300      \
>> +    -- lr-add lr2                                                  \
>> +       -- lrp-add lr2 lr2-lr1 00:00:00:00:02:00 peer=lr1-lr2       \
>> +       -- lrp-add lr2 lr2-ls2 00:00:00:00:00:12 20.0.0.1/24        \
>> +       -- lr-route-add lr2 10.0.0.0/24 fe80::200:ff:fe00:2 lr2-lr1 \
>> +    -- lr-add lr3                                                  \
>> +       -- lrp-add lr3 lr3-lr1 00:00:00:00:03:00 peer=lr1-lr3       \
>> +       -- lrp-add lr3 lr3-ls3 00:00:00:00:00:13 30.0.0.1/24        \
>> +       -- lr-route-add lr3 10.0.0.0/24 fe80::200:ff:fe00:3 lr3-lr1 \
>> +    -- ls-add ls1                                                  \
>> +       -- lsp-add ls1 ls1-lr1                                      \
>> +          -- lsp-set-type ls1-lr1 router                           \
>> +          -- lsp-set-addresses ls1-lr1 router                      \
>> +          -- lsp-set-options ls1-lr1 router-port=lr1-ls1           \
>> +    -- ls-add ls2                                                  \
>> +       -- lsp-add ls2 ls2-lr2                                      \
>> +          -- lsp-set-type ls2-lr2 router                           \
>> +          -- lsp-set-addresses ls2-lr2 router                      \
>> +          -- lsp-set-options ls2-lr2 router-port=lr2-ls2           \
>> +    -- ls-add ls3                                                  \
>> +       -- lsp-add ls3 ls3-lr3                                      \
>> +          -- lsp-set-type ls3-lr3 router                           \
>> +          -- lsp-set-addresses ls3-lr3 router                      \
>> +          -- lsp-set-options ls3-lr3 router-port=lr3-ls3           \
>> +    -- lsp-add ls1 vm1                                             \
>> +       -- lsp-set-addresses vm1 "00:00:00:00:01:00 10.0.0.2"       \
>> +    -- lsp-add ls2 vm2                                             \
>> +       -- lsp-set-addresses vm2 "00:00:00:00:02:00 20.0.0.2"       \
>> +    -- lsp-add ls3 vm3                                             \
>> +       -- lsp-set-addresses vm3 "00:00:00:00:03:00 30.0.0.2"
>> +
>> +ADD_NAMESPACES(vm1)
>> +ADD_VETH(vm1, vm1, br-int, "10.0.0.2/24", "00:00:00:00:01:00",
>> "10.0.0.1")
>> +ADD_NAMESPACES(vm2)
>> +ADD_VETH(vm2, vm2, br-int, "20.0.0.2/24", "00:00:00:00:02:00",
>> "20.0.0.1")
>> +ADD_NAMESPACES(vm3)
>> +ADD_VETH(vm3, vm3, br-int, "30.0.0.2/24", "00:00:00:00:03:00",
>> "30.0.0.1")
>> +
>> +OVN_POPULATE_ARP
>> +check ovn-nbctl --wait=hv sync
>> +wait_for_ports_up
>> +
>> +NETNS_DAEMONIZE([vm3], [nc -l -k 80], [vm3.pid])
>> +NS_CHECK_EXEC([vm1], [nc 30.0.0.2 80 -z])
>> +
>> +OVN_CLEANUP_CONTROLLER([hv1])
>> +
>> +as ovn-sb
>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>> +
>> +as ovn-nb
>> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>> +
>> +as northd
>> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
>> +
>> +as
>> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
>> +/Failed to acquire.*/d
>> +/connection dropped.*/d"])
>> +AT_CLEANUP
>> +])
>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>> index 58517f9668..9d58094e98 100644
>> --- a/utilities/ovn-nbctl.c
>> +++ b/utilities/ovn-nbctl.c
>> @@ -4189,6 +4189,9 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
>>      const struct nbrec_bfd **bfd_sessions = NULL;
>>      char *chain_s = shash_find_data(&ctx->options, "--chain");
>>
>> +    const char *output_port = shash_find_data(&ctx->options,
>> "--output-port");
>> +    const struct nbrec_logical_router_port *out_lrp = NULL;
>> +
>>      bool reroute = false;
>>      bool jump = false;
>>
>> @@ -4205,6 +4208,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
>>              return;
>>          }
>>          reroute = true;
>> +    } else if (output_port) {
>> +        ctl_error(ctx, "Specifying output-port is only allowed for "
>> +                       "reroute policies.");
>> +        return;
>>      }
>>
>>      if (!strcmp(action, "jump")) {
>> @@ -4264,6 +4271,21 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
>>          }
>>
>>          free(nexthops_arg);
>> +
>> +        if (output_port) {
>> +            if (vector_len(&nexthops) != 1) {
>> +                ctl_error(ctx, "output-port option is supported only for "
>> +                               "policies with a single reroute next hop");
>> +                goto free_nexthops;
>> +            }
>> +
>> +            error = lrp_by_name_or_uuid(ctx, output_port, true, &out_lrp);
>> +            if (error) {
>> +                ctl_error(ctx, "unknown output-port: %s", output_port);
>> +                free(error);
>> +                goto free_nexthops;
>> +            }
>> +        }
>>      }
>>
>>      struct nbrec_logical_router_policy *policy;
>> @@ -4283,6 +4305,7 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
>>      if (reroute) {
>>          nbrec_logical_router_policy_set_nexthops(
>>              policy, vector_get_array(&nexthops), vector_len(&nexthops));
>> +        nbrec_logical_router_policy_set_output_port(policy, out_lrp);
>>      }
>>
>>      /* Parse the options. */
>> @@ -4505,6 +4528,10 @@ print_routing_policy(const struct
>> nbrec_logical_router_policy *policy,
>>          free(next_hop);
>>      }
>>
>> +    if (policy->output_port) {
>> +        ds_put_format(s, " %s", policy->output_port->name);
>> +    }
>> +
>>      if (!smap_is_empty(&policy->options) || policy->n_bfd_sessions) {
>>          ds_put_format(s, "%15s", "");
>>          if (policy->n_bfd_sessions) {
>> @@ -4526,10 +4553,14 @@ nbctl_pre_lr_policy_list(struct ctl_context *ctx)
>>      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
>>      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_policies);
>>
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
>> +
>>      ovsdb_idl_add_column(ctx->idl,
>> &nbrec_logical_router_policy_col_priority);
>>      ovsdb_idl_add_column(ctx->idl,
>> &nbrec_logical_router_policy_col_match);
>>      ovsdb_idl_add_column(ctx->idl,
>> &nbrec_logical_router_policy_col_nexthops);
>>      ovsdb_idl_add_column(ctx->idl,
>> &nbrec_logical_router_policy_col_action);
>> +    ovsdb_idl_add_column(ctx->idl,
>> +                         &nbrec_logical_router_policy_col_output_port);
>>      ovsdb_idl_add_column(ctx->idl,
>> &nbrec_logical_router_policy_col_options);
>>      ovsdb_idl_add_column(ctx->idl,
>>                           &nbrec_logical_router_policy_col_bfd_sessions);
>> @@ -8418,7 +8449,7 @@ static const struct ctl_command_syntax
>> nbctl_commands[] = {
>>      { "lr-policy-add", 4, INT_MAX,
>>       "ROUTER PRIORITY MATCH ACTION [NEXTHOP] [OPTIONS - KEY=VALUE ...]",
>>       nbctl_pre_lr_policy_add, nbctl_lr_policy_add, NULL,
>> -     "--may-exist,--bfd?,--chain=", RW },
>> +     "--may-exist,--bfd?,--chain=,--output-port=", RW },
>>      { "lr-policy-del", 1, 3, "ROUTER [{PRIORITY | UUID} [MATCH]]",
>>        nbctl_pre_lr_policy_del, nbctl_lr_policy_del, NULL,
>>        "--if-exists,--chain=", RW },
>> --
>> 2.51.0
>>
>> _______________________________________________
>> dev mailing list
>> [email protected]
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
> With that addressed:
> Acked-by: Ales Musil <[email protected]>
> 

I fixed up the two returns and applied this to main.

Regards,
Dumitru

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to