Add LSP/LRP option `requested-encap-ip` so the CMS can request a specific
`Port_Binding.encap` IP (e.g., transit switch ports in ovn-k8s interconnect
mode).

When the same encap IP exists for both Geneve and VXLAN, prefer Geneve.

If `requested-chassis` is set, consider only encaps on that chassis; otherwise
(router-LSP cases), use IP-only lookup.

NB-requested encap overrides the VIF `external_ids:encap-ip`. Track NB
ownership with Port_Binding option `is-encap-nb-bound` to avoid ovn-northd vs
ovn-controller races.

CC: Han Zhou <[email protected]>
Signed-off-by: Lei Huang <[email protected]>
---
 NEWS                             |   3 +
 controller/binding.c             |  11 ++-
 northd/en-northd.c               |   4 ++
 northd/inc-proc-northd.c         |   7 ++
 northd/northd.c                  | 116 +++++++++++++++++++++++++++++--
 northd/northd.h                  |   1 +
 ovn-nb.xml                       |  18 +++++
 tests/ovn-inc-proc-graph-dump.at |   2 +
 tests/ovn-northd.at              |  92 ++++++++++++++++++++++++
 tests/ovn.at                     |  55 +++++++++++++++
 10 files changed, 300 insertions(+), 9 deletions(-)

diff --git a/NEWS b/NEWS
index ef7b4210b..888946b54 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,9 @@ Post v26.03.0
 
 OVN v26.03.0 - xxx xx xxxx
 --------------------------
+   - Added LSP/LRP option "requested-encap-ip" to let CMS request a specific
+     SB Port_Binding encap IP (e.g., for transit switch ports in ovn-k8s
+     interconnect mode).
    - Added DNS query statistics tracking in ovn-controller using OVS coverage
      counters. Statistics can be queried using "ovn-appctl -t ovn-controller
      coverage/read-counter <counter_name>" or "coverage/show". Tracked metrics
diff --git a/controller/binding.c b/controller/binding.c
index eb9524142..8c7da101e 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -1306,6 +1306,10 @@ update_port_encap_if_needed(const struct 
sbrec_port_binding *pb,
                             const struct ovsrec_interface *iface_rec,
                             bool sb_readonly)
 {
+    if (smap_get_bool(&pb->options, "is-encap-nb-bound", false)) {
+        return true;
+    }
+
     const struct sbrec_encap *encap_rec =
         sbrec_get_port_encap(chassis_rec, iface_rec);
     if ((encap_rec && pb->encap != encap_rec) ||
@@ -1508,7 +1512,8 @@ release_lport_main_chassis(const struct 
sbrec_port_binding *pb,
                            bool sb_readonly,
                            struct if_status_mgr *if_mgr)
 {
-    if (pb->encap) {
+    if (pb->encap &&
+        !smap_get_bool(&pb->options, "is-encap-nb-bound", false)) {
         if (sb_readonly) {
             return false;
         }
@@ -2406,7 +2411,9 @@ binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
     bool any_changes = false;
     SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
         if (binding_rec->chassis == chassis_rec) {
-            if (binding_rec->encap) {
+            if (binding_rec->encap &&
+                !smap_get_bool(&binding_rec->options,
+                               "is-encap-nb-bound", false)) {
                 sbrec_port_binding_set_encap(binding_rec, NULL);
             }
             sbrec_port_binding_set_chassis(binding_rec, NULL);
diff --git a/northd/en-northd.c b/northd/en-northd.c
index a828f9a5f..c34818dba 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -51,6 +51,10 @@ northd_get_input_data(struct engine_node *node,
         engine_ovsdb_node_get_index(
             engine_get_input("SB_chassis", node),
             "sbrec_chassis_by_hostname");
+    input_data->sbrec_encap_by_ip =
+        engine_ovsdb_node_get_index(
+            engine_get_input("SB_encap", node),
+            "sbrec_encap_by_ip");
     input_data->sbrec_ha_chassis_grp_by_name =
         engine_ovsdb_node_get_index(
             engine_get_input("SB_ha_chassis_group", node),
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index b79272324..cdab8d168 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -97,6 +97,7 @@ static unixctl_cb_func chassis_features_list;
 #define SB_NODES \
     SB_NODE(sb_global) \
     SB_NODE(chassis) \
+    SB_NODE(encap) \
     SB_NODE(address_set) \
     SB_NODE(port_group) \
     SB_NODE(logical_flow) \
@@ -261,6 +262,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                      NULL);
 
     engine_add_input(&en_northd, &en_sb_chassis, NULL);
+    engine_add_input(&en_northd, &en_sb_encap, NULL);
     engine_add_input(&en_northd, &en_sb_mirror, NULL);
     engine_add_input(&en_northd, &en_sb_meter, NULL);
     engine_add_input(&en_northd, &en_sb_dns, NULL);
@@ -514,6 +516,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                          ip_mcast_index_create(sb->idl);
     struct ovsdb_idl_index *sbrec_chassis_by_hostname =
         chassis_hostname_index_create(sb->idl);
+    struct ovsdb_idl_index *sbrec_encap_by_ip =
+        ovsdb_idl_index_create1(sb->idl, &sbrec_encap_col_ip);
     struct ovsdb_idl_index *sbrec_mac_binding_by_datapath
         = mac_binding_by_datapath_index_create(sb->idl);
     struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip
@@ -529,6 +533,9 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_ovsdb_node_add_index(&en_sb_chassis,
                                 "sbrec_chassis_by_hostname",
                                 sbrec_chassis_by_hostname);
+    engine_ovsdb_node_add_index(&en_sb_encap,
+                                "sbrec_encap_by_ip",
+                                sbrec_encap_by_ip);
     engine_ovsdb_node_add_index(&en_sb_ha_chassis_group,
                                 "sbrec_ha_chassis_grp_by_name",
                                 sbrec_ha_chassis_grp_by_name);
diff --git a/northd/northd.c b/northd/northd.c
index 983975dac..8caddc62d 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -2546,6 +2546,90 @@ ovn_port_update_sbrec_chassis(
     free(requested_chassis_sb);
 }
 
+static const struct sbrec_encap *
+encap_lookup_by_ip(struct ovsdb_idl_index *sbrec_encap_by_ip,
+                   const char *ip, const char *requested_chassis)
+{
+    struct sbrec_encap *key =
+        sbrec_encap_index_init_row(sbrec_encap_by_ip);
+    sbrec_encap_index_set_ip(key, ip);
+
+    const struct sbrec_encap *best = NULL;
+    const struct sbrec_encap *encap;
+    SBREC_ENCAP_FOR_EACH_EQUAL (encap, key, sbrec_encap_by_ip) {
+        if (requested_chassis &&
+            strcmp(encap->chassis_name, requested_chassis)) {
+            continue;
+        }
+
+        enum chassis_tunnel_type tun_type = get_tunnel_type(encap->type);
+        if (tun_type == TUNNEL_TYPE_INVALID) {
+            continue;
+        }
+        /* Pick the highest-preference tunnel type (geneve > vxlan)
+         * when multiple encap types share the same IP. */
+        if (!best || get_tunnel_type(best->type) < tun_type) {
+            best = encap;
+        }
+    }
+    sbrec_encap_index_destroy_row(key);
+
+    return best;
+}
+
+static void
+ovn_port_update_requested_encap(
+    struct ovsdb_idl_index *sbrec_encap_by_ip,
+    const struct ovn_port *op,
+    bool was_encap_nb_bound)
+{
+    if (is_cr_port(op)) {
+        return;
+    }
+
+    const struct smap *options = op->nbsp ? &op->nbsp->options
+                                          : &op->nbrp->options;
+    const char *requested_ip = smap_get(options, "requested-encap-ip");
+    if (!requested_ip || !requested_ip[0]) {
+        if (was_encap_nb_bound) {
+            if (op->sb->encap) {
+                sbrec_port_binding_set_encap(op->sb, NULL);
+            }
+            sbrec_port_binding_update_options_delkey(op->sb,
+                                                     "is-encap-nb-bound");
+        }
+        return;
+    }
+
+    const char *requested_chassis = op->sb->requested_chassis
+                                    ? op->sb->requested_chassis->name
+                                    : NULL;
+    const struct sbrec_encap *encap = encap_lookup_by_ip(sbrec_encap_by_ip,
+                                                          requested_ip,
+                                                          requested_chassis);
+    if (!encap) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        if (requested_chassis) {
+            VLOG_WARN_RL(&rl,
+                         "No Encap matches options requested-encap-ip=\"%s\" "
+                         "and requested-chassis=\"%s\" for logical port %s; "
+                         "clearing Port_Binding.encap.",
+                         requested_ip, requested_chassis, op->key);
+        } else {
+            VLOG_WARN_RL(&rl,
+                         "No Encap matches option requested-encap-ip=\"%s\" "
+                         "for logical port %s; clearing Port_Binding.encap.",
+                         requested_ip, op->key);
+        }
+    }
+
+    if (op->sb->encap != encap) {
+        sbrec_port_binding_set_encap(op->sb, encap);
+    }
+    sbrec_port_binding_update_options_setkey(op->sb, "is-encap-nb-bound",
+                                             "true");
+}
+
 static void
 check_and_do_sb_mirror_deletion(const struct ovn_port *op)
 {
@@ -2616,11 +2700,14 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
                       struct ovsdb_idl_index *sbrec_chassis_by_hostname,
                       struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
                       const struct sbrec_mirror_table *sbrec_mirror_table,
+                      struct ovsdb_idl_index *sbrec_encap_by_ip,
                       const struct ovn_port *op,
                       unsigned long *queue_id_bitmap,
                       struct sset *active_ha_chassis_grps)
 {
     sbrec_port_binding_set_datapath(op->sb, op->od->sdp->sb_dp);
+    bool was_encap_nb_bound = smap_get_bool(&op->sb->options,
+                                            "is-encap-nb-bound", false);
     if (op->nbrp) {
         /* Note: SB port binding options for router ports are set in
          * sync_pbs(). */
@@ -2952,6 +3039,9 @@ common:
         sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
     }
 
+    ovn_port_update_requested_encap(sbrec_encap_by_ip, op,
+                                    was_encap_nb_bound);
+
     /* ovn-controller will update 'Port_Binding.up' only if it was explicitly
      * set to 'false'.
      */
@@ -4232,6 +4322,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_ha_chassis_group_table *sbrec_ha_chassis_group_table,
     struct ovsdb_idl_index *sbrec_chassis_by_name,
     struct ovsdb_idl_index *sbrec_chassis_by_hostname,
+    struct ovsdb_idl_index *sbrec_encap_by_ip,
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
     struct hmap *ls_datapaths, struct hmap *lr_datapaths,
     struct hmap *ls_ports, struct hmap *lr_ports,
@@ -4307,6 +4398,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
                               sbrec_chassis_by_hostname,
                               sbrec_ha_chassis_grp_by_name,
                               sbrec_mirror_table,
+                              sbrec_encap_by_ip,
                               op, queue_id_bitmap,
                               &active_ha_chassis_grps);
         op->od->is_transit_router |= is_transit_router_port(op);
@@ -4321,6 +4413,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
                               sbrec_chassis_by_hostname,
                               sbrec_ha_chassis_grp_by_name,
                               sbrec_mirror_table,
+                              sbrec_encap_by_ip,
                               op, queue_id_bitmap,
                               &active_ha_chassis_grps);
         sbrec_port_binding_set_logical_port(op->sb, op->key);
@@ -4543,7 +4636,8 @@ ls_port_init(struct ovn_port *op, struct ovsdb_idl_txn 
*ovnsb_txn,
              const struct sbrec_port_binding *sb,
              const struct sbrec_mirror_table *sbrec_mirror_table,
              struct ovsdb_idl_index *sbrec_chassis_by_name,
-             struct ovsdb_idl_index *sbrec_chassis_by_hostname)
+             struct ovsdb_idl_index *sbrec_chassis_by_hostname,
+             struct ovsdb_idl_index *sbrec_encap_by_ip)
 {
     op->od = od;
     parse_lsp_addrs(op);
@@ -4573,6 +4667,7 @@ ls_port_init(struct ovn_port *op, struct ovsdb_idl_txn 
*ovnsb_txn,
     }
     ovn_port_update_sbrec(ovnsb_txn, sbrec_chassis_by_name,
                           sbrec_chassis_by_hostname, NULL, sbrec_mirror_table,
+                          sbrec_encap_by_ip,
                           op, NULL, NULL);
     return true;
 }
@@ -4583,13 +4678,15 @@ ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct 
hmap *ls_ports,
                struct ovn_datapath *od,
                const struct sbrec_mirror_table *sbrec_mirror_table,
                struct ovsdb_idl_index *sbrec_chassis_by_name,
-               struct ovsdb_idl_index *sbrec_chassis_by_hostname)
+               struct ovsdb_idl_index *sbrec_chassis_by_hostname,
+               struct ovsdb_idl_index *sbrec_encap_by_ip)
 {
     struct ovn_port *op = ovn_port_create(ls_ports, key, nbsp, NULL,
                                           NULL);
     hmap_insert(&od->ports, &op->dp_node, hmap_node_hash(&op->key_node));
     if (!ls_port_init(op, ovnsb_txn, od, NULL, sbrec_mirror_table,
-                      sbrec_chassis_by_name, sbrec_chassis_by_hostname)) {
+                      sbrec_chassis_by_name, sbrec_chassis_by_hostname,
+                      sbrec_encap_by_ip)) {
         ovn_port_destroy(ls_ports, op);
         return NULL;
     }
@@ -4604,14 +4701,16 @@ ls_port_reinit(struct ovn_port *op, struct 
ovsdb_idl_txn *ovnsb_txn,
                 const struct sbrec_port_binding *sb,
                 const struct sbrec_mirror_table *sbrec_mirror_table,
                 struct ovsdb_idl_index *sbrec_chassis_by_name,
-                struct ovsdb_idl_index *sbrec_chassis_by_hostname)
+                struct ovsdb_idl_index *sbrec_chassis_by_hostname,
+                struct ovsdb_idl_index *sbrec_encap_by_ip)
 {
     ovn_port_cleanup(op);
     op->sb = sb;
     ovn_port_set_nb(op, nbsp, NULL);
     op->primary_port = op->cr_port = NULL;
     return ls_port_init(op, ovnsb_txn, od, sb, sbrec_mirror_table,
-                        sbrec_chassis_by_name, sbrec_chassis_by_hostname);
+                        sbrec_chassis_by_name, sbrec_chassis_by_hostname,
+                        sbrec_encap_by_ip);
 }
 
 /* Returns true if the logical switch has changes which can be
@@ -4810,7 +4909,8 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                     new_nbsp->name, new_nbsp, od,
                                     ni->sbrec_mirror_table,
                                     ni->sbrec_chassis_by_name,
-                                    ni->sbrec_chassis_by_hostname);
+                                    ni->sbrec_chassis_by_hostname,
+                                    ni->sbrec_encap_by_ip);
                 if (!op) {
                     goto fail;
                 }
@@ -4853,7 +4953,8 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                     new_nbsp,
                                     od, sb, ni->sbrec_mirror_table,
                                     ni->sbrec_chassis_by_name,
-                                    ni->sbrec_chassis_by_hostname)) {
+                                    ni->sbrec_chassis_by_hostname,
+                                    ni->sbrec_encap_by_ip)) {
                     if (sb) {
                         sbrec_port_binding_delete(sb);
                     }
@@ -20959,6 +21060,7 @@ ovnnb_db_run(struct northd_input *input_data,
                 input_data->sbrec_ha_chassis_group_table,
                 input_data->sbrec_chassis_by_name,
                 input_data->sbrec_chassis_by_hostname,
+                input_data->sbrec_encap_by_ip,
                 input_data->sbrec_ha_chassis_grp_by_name,
                 &data->ls_datapaths.datapaths, &data->lr_datapaths.datapaths,
                 &data->ls_ports, &data->lr_ports,
diff --git a/northd/northd.h b/northd/northd.h
index 4dcd128cc..a876e0a56 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -86,6 +86,7 @@ struct northd_input {
     struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink;
     struct ovsdb_idl_index *sbrec_chassis_by_name;
     struct ovsdb_idl_index *sbrec_chassis_by_hostname;
+    struct ovsdb_idl_index *sbrec_encap_by_ip;
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
     struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
     struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port;
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 954ed1166..f1cd89509 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1567,6 +1567,15 @@
           </p>
         </column>
 
+        <column name="options" key="requested-encap-ip">
+          Requests the encapsulation IP address for the port binding. If set,
+          <code>ovn-northd</code> uses this IP to select the
+          <ref table="Encap" db="OVN_Southbound"/> entry written to
+          <ref table="Port_Binding" column="encap" db="OVN_Southbound"/>.
+          This is intended for ports without a local OVS interface, e.g. remote
+          transit switch ports in ovn-kubernetes interconnect mode.
+        </column>
+
         <column name="options" key="activation-strategy">
           If used with multiple chassis set in
           <ref column="requested-chassis"/>, specifies an activation strategy
@@ -4543,6 +4552,15 @@ or
         </p>
       </column>
 
+      <column name="options" key="requested-encap-ip">
+        Requests the encapsulation IP address for the port binding. If set,
+        <code>ovn-northd</code> uses this IP to select the
+        <ref table="Encap" db="OVN_Southbound"/> entry written to
+        <ref table="Port_Binding" column="encap" db="OVN_Southbound"/>.
+        This is intended for ports without a local OVS interface, e.g. remote
+        transit router ports in ovn-kubernetes interconnect mode.
+      </column>
+
       <column name="options" key="dynamic-routing-redistribute"
               type='{"type": "string"}'>
         <p>
diff --git a/tests/ovn-inc-proc-graph-dump.at b/tests/ovn-inc-proc-graph-dump.at
index a31aad6e7..44fa4dee9 100644
--- a/tests/ovn-inc-proc-graph-dump.at
+++ b/tests/ovn-inc-proc-graph-dump.at
@@ -20,6 +20,7 @@ digraph "Incremental-Processing-Engine" {
        NB_network_function_group [[style=filled, shape=box, fillcolor=white, 
label="NB_network_function_group"]];
        NB_logical_switch_port_health_check [[style=filled, shape=box, 
fillcolor=white, label="NB_logical_switch_port_health_check"]];
        SB_chassis [[style=filled, shape=box, fillcolor=white, 
label="SB_chassis"]];
+       SB_encap [[style=filled, shape=box, fillcolor=white, label="SB_encap"]];
        SB_mirror [[style=filled, shape=box, fillcolor=white, 
label="SB_mirror"]];
        SB_meter [[style=filled, shape=box, fillcolor=white, label="SB_meter"]];
        SB_dns [[style=filled, shape=box, fillcolor=white, label="SB_dns"]];
@@ -78,6 +79,7 @@ digraph "Incremental-Processing-Engine" {
        NB_network_function_group -> northd [[label=""]];
        NB_logical_switch_port_health_check -> northd [[label=""]];
        SB_chassis -> northd [[label=""]];
+       SB_encap -> northd [[label=""]];
        SB_mirror -> northd [[label=""]];
        SB_meter -> northd [[label=""]];
        SB_dns -> northd [[label=""]];
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index e29f6d7b5..90f64f6fe 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2896,6 +2896,98 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([check options:requested-encap-ip fills port binding encap col])
+AT_KEYWORDS([requested encap ip])
+ovn_start
+
+check_uuid ovn-sbctl \
+    -- --id=@e11 create encap chassis_name=ch1 ip="192.168.1.1" type="vxlan" \
+    -- --id=@e12 create encap chassis_name=ch1 ip="192.168.1.2" type="geneve" \
+    -- --id=@c1 create chassis name=ch1 encaps=@e11,@e12
+check_uuid ovn-sbctl \
+    -- --id=@e21 create encap chassis_name=ch2 ip="192.168.2.1" type="geneve" \
+    -- --id=@e22 create encap chassis_name=ch2 ip="192.168.2.2" type="geneve" \
+    -- --id=@c2 create chassis name=ch2 encaps=@e21,@e22
+
+wait_row_count Chassis 2
+wait_row_count Encap 4
+en11_uuid=$(fetch_column Encap _uuid ip="192.168.1.1")
+en12_uuid=$(fetch_column Encap _uuid ip="192.168.1.2")
+en21_uuid=$(fetch_column Encap _uuid ip="192.168.2.1")
+en22_uuid=$(fetch_column Encap _uuid ip="192.168.2.2")
+ovn-sbctl show
+
+echo "__file__:__line__: encap uuid: $en11_uuid, ip: 192.168.1.1"
+echo "__file__:__line__: encap uuid: $en12_uuid, ip: 192.168.1.2"
+echo "__file__:__line__: encap uuid: $en21_uuid, ip: 192.168.2.1"
+echo "__file__:__line__: encap uuid: $en22_uuid, ip: 192.168.2.2"
+
+check ovn-nbctl --wait=sb ls-add ls1
+check ovn-nbctl --wait=sb lsp-add ls1 lsp1
+check ovn-nbctl --wait=sb lsp-add ls1 lsp2
+ovn-nbctl show
+
+# set options:requested-encap-ip without requested-chassis
+check ovn-nbctl --wait=sb set logical-switch-port lsp1 \
+    options:requested-encap-ip=192.168.1.1
+check ovn-nbctl --wait=sb sync
+wait_row_count Port_Binding 1 logical_port=lsp1 encap="$en11_uuid"
+
+# With requested-chassis set to a different chassis, encap should be cleared.
+check ovn-nbctl --wait=sb set logical-switch-port lsp1 \
+    options:requested-chassis=ch2
+wait_row_count Port_Binding 1 logical_port=lsp1 'encap=[[]]'
+
+# Set both options to a matching chassis+IP.
+check ovn-nbctl --wait=sb set logical-switch-port lsp1 \
+    options:requested-chassis=ch1 \
+    options:requested-encap-ip=192.168.1.1
+check ovn-nbctl --wait=sb set logical-switch-port lsp2 \
+    options:requested-chassis=ch2 \
+    options:requested-encap-ip=192.168.2.2
+
+wait_row_count Port_Binding 1 logical_port=lsp1 encap="$en11_uuid"
+wait_row_count Port_Binding 1 logical_port=lsp2 encap="$en22_uuid"
+
+# Add geneve encap with the same IP and ensure it is preferred over vxlan.
+check_uuid ovn-sbctl \
+    -- --id=@e11g create encap chassis_name=ch1 ip="192.168.1.1" type="geneve" 
\
+    -- add chassis ch1 encaps @e11g
+wait_row_count Encap 5
+en11g_uuid=$(fetch_column Encap _uuid chassis_name=ch1 ip="192.168.1.1" 
type=geneve)
+wait_row_count Port_Binding 1 logical_port=lsp1 encap="$en11g_uuid"
+
+# Router LSP path has no requested-chassis, but requested-encap-ip should work.
+check ovn-nbctl --wait=sb lr-add lr1
+check ovn-nbctl --wait=sb lrp-add lr1 lrp1 00:00:00:01:00:01 100.64.0.1/24
+check ovn-nbctl --wait=sb lsp-add ls1 lsp-router1 \
+    -- lsp-set-type lsp-router1 router \
+    -- lsp-set-options lsp-router1 router-port=lrp1 \
+    -- lsp-set-addresses lsp-router1 router
+check ovn-nbctl --wait=sb set logical-switch-port lsp-router1 \
+    options:requested-encap-ip=192.168.2.1
+wait_row_count Port_Binding 1 logical_port=lsp-router1 encap="$en21_uuid"
+
+# remove options:requested-encap-ip from lsp1
+check ovn-nbctl --wait=sb remove logical_switch_port lsp1 \
+    options requested-encap-ip=192.168.1.1
+wait_row_count Port_Binding 1 logical_port=lsp1 'encap=[[]]'
+
+# remove options:requested-chassis from lsp2 and keep requested-encap-ip
+check ovn-nbctl --wait=sb remove logical_switch_port lsp2 \
+    options requested-chassis=ch2
+wait_row_count Port_Binding 1 logical_port=lsp2 encap="$en22_uuid"
+
+# remove options:requested-encap-ip from lsp2
+check ovn-nbctl --wait=sb remove logical_switch_port lsp2 \
+    options requested-encap-ip=192.168.2.2
+wait_row_count Port_Binding 1 logical_port=lsp2 'encap=[[]]'
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([port requested-tnl-key])
 AT_KEYWORDS([requested tnl tunnel key keys])
diff --git a/tests/ovn.at b/tests/ovn.at
index 9082bba82..ae7909899 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -40748,6 +40748,61 @@ ignored_dp=lsw0])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-controller - requested-encap-ip should override VIF encap-ip])
+ovn_start
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl set Open_vSwitch . \
+    external_ids:ovn-encap-ip="192.168.0.1,192.168.0.11" \
+    external_ids:ovn-encap-ip-default=192.168.0.1 \
+    external_ids:ovn-enable-flow-based-tunnels=false
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 lsp0
+check ovn-nbctl lsp-set-addresses lsp0 "50:54:00:00:00:01 10.0.0.1"
+
+as hv1
+check ovs-vsctl -- add-port br-int vif0 -- \
+    set Interface vif0 external-ids:iface-id=lsp0 \
+    external-ids:encap-ip=192.168.0.11
+
+wait_row_count Chassis 1 name=hv1
+wait_row_count Encap 4 chassis_name=hv1
+check ovn-nbctl --wait=hv sync
+wait_for_ports_up lsp0
+
+encap_uuid_vif=$(ovn-sbctl --bare --columns _uuid find Encap \
+    chassis_name=hv1 ip="192.168.0.11" type=geneve)
+encap_uuid_nb=$(ovn-sbctl --bare --columns _uuid find Encap \
+    chassis_name=hv1 ip="192.168.0.1" type=geneve)
+
+get_pb_encap_uuid() {
+    fetch_column Port_Binding encap logical_port=lsp0
+}
+
+# Verify local VIF encap-ip is reflected by ovn-controller.
+OVS_WAIT_UNTIL([test "$encap_uuid_vif" = "$(get_pb_encap_uuid)"])
+
+# Now request NB-driven encap.
+check ovn-nbctl --wait=sb lsp-set-options lsp0 \
+    requested-chassis=hv1 requested-encap-ip=192.168.0.1
+
+# Touch iface to trigger ovn-controller processing for this VIF.
+as hv1
+check ovs-vsctl set Interface vif0 external-ids:encap-ip=192.168.0.11
+
+# Expected behavior: NB request should win over local iface encap-ip.
+OVS_WAIT_UNTIL([test "$encap_uuid_nb" = "$(get_pb_encap_uuid)"])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([DHCP RELAY])
 ovn_start
-- 
2.43.0

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

Reply via email to